diff --git a/docz/docs/02-getting-started/01-installation/07-bun.md b/docz/docs/02-getting-started/01-installation/07-bun.md
index aff4730..a72cf53 100644
--- a/docz/docs/02-getting-started/01-installation/07-bun.md
+++ b/docz/docs/02-getting-started/01-installation/07-bun.md
@@ -13,8 +13,7 @@ import current from '/version.js';
:::caution Bun support is considered experimental.
Great open source software grows with user tests and reports. Any issues should
-be reported to the [SheetJS project](https://github.com/SheetJS/sheetjs/issues)
-for further diagnosis.
+be reported to the Bun project for further diagnosis.
:::
diff --git a/docz/docs/03-demos/03-database.md b/docz/docs/03-demos/03-database.md
index d697d6a..0642c5a 100644
--- a/docz/docs/03-demos/03-database.md
+++ b/docz/docs/03-demos/03-database.md
@@ -570,7 +570,7 @@ can be adapted to generate SQL statements for a variety of databases, including:
**PostgreSQL**
-The `pg` connector library was tested against the `generate_sql` output as-is.
+The `pg` connector library was tested against the `generate_sql` output as-is.
The `rows` property of a query result is an array of objects that plays nice
with `json_to_sheet`:
diff --git a/docz/docs/03-demos/14-grid.md b/docz/docs/03-demos/14-grid.md
index 588d6be..4f143f3 100644
--- a/docz/docs/03-demos/14-grid.md
+++ b/docz/docs/03-demos/14-grid.md
@@ -27,6 +27,12 @@ suitable for a number of libraries. When more advanced shapes are needed,
it is easier to munge the output of an array of arrays.
+### Tabulator
+
+[Tabulator](http://tabulator.info/docs/5.3/download#xlsx) includes deep support
+through a special Export button. It handles the SheetJS-related operations.
+
+
### x-spreadsheet
With a familiar UI, [`x-spreadsheet`](https://myliang.github.io/x-spreadsheet/)
diff --git a/docz/docs/03-demos/16-desktop.md b/docz/docs/03-demos/16-desktop.md
index 4b6dbf8..1390e6e 100644
--- a/docz/docs/03-demos/16-desktop.md
+++ b/docz/docs/03-demos/16-desktop.md
@@ -74,7 +74,7 @@ Similarly, file input elements automatically map to standard Web APIs.
For example, assuming a file input element on the page:
```html
-
+
```
The event handler would process the event as if it were a web event:
@@ -235,7 +235,7 @@ File input elements automatically map to standard Web APIs.
For example, assuming a file input element on the page:
```html
-
+
```
The event handler would process the event as if it were a web event:
diff --git a/docz/docs/03-demos/19-mobile.md b/docz/docs/03-demos/19-mobile.md
index cd50890..afac178 100644
--- a/docz/docs/03-demos/19-mobile.md
+++ b/docz/docs/03-demos/19-mobile.md
@@ -1367,3 +1367,68 @@ id,content
```
+
+## Ionic
+
+:::warning Telemetry
+
+Before starting this demo, manually disable telemetry. On Linux and macOS:
+
+```bash
+rm -rf ~/.ionic/
+mkdir ~/.ionic
+cat < ~/.ionic/config.json
+{
+ "version": "6.20.1",
+ "telemetry": false,
+ "npmClient": "npm"
+}
+EOF
+npx @capacitor/cli telemetry off
+```
+
+To verify telemetry was disabled:
+
+```bash
+npx @ionic/cli config get -g telemetry
+npx @capacitor/cli telemetry
+```
+
+:::
+
+### Cordova
+
+:::caution
+
+The latest version of Ionic uses CapacitorJS. These notes are for older apps
+using Cordova
+
+:::
+
+`Array>` neatly maps to a table with `ngFor`:
+
+```html
+
+
+
+ {{val}}
+
+
+
+```
+
+`@ionic-native/file` reads and writes files on devices. `readAsArrayBuffer`
+returns `ArrayBuffer` objects suitable for `array` type, and `array` type can
+be converted to blobs that can be exported with `writeFile`:
+
+```ts
+/* read a workbook */
+const ab: ArrayBuffer = await this.file.readAsArrayBuffer(url, filename);
+const wb: XLSX.WorkBook = XLSX.read(bstr, {type: 'array'});
+
+/* write a workbook */
+const wbout: ArrayBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
+let blob = new Blob([wbout], {type: 'application/octet-stream'});
+this.file.writeFile(url, filename, blob, {replace: true});
+```
+
diff --git a/docz/docs/03-demos/23-angular.md b/docz/docs/03-demos/23-angular.md
new file mode 100644
index 0000000..2974eb4
--- /dev/null
+++ b/docz/docs/03-demos/23-angular.md
@@ -0,0 +1,496 @@
+---
+sidebar_position: 23
+title: Angular
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+[Angular](https://angular.io/) is a JS library for building user interfaces.
+
+This demo tries to cover common Angular data flow ideas and strategies. Angular
+and TypeScript familiarity is assumed.
+
+**SheetJS plays nice with each version of Angular**.
+
+Other demos cover general Angular deployments, including:
+
+- [iOS and Android applications powered by NativeScript](./mobile#nativescript)
+- [iOS and Android applications powered by ionic](./mobile#nativescript)
+
+:::warning
+
+Angular dev tooling uses native NodeJS modules. There are a number of issues
+when trying to run Angular projects with different NodeJS versions. These
+issues should be directed to the Angular project.
+
+:::
+
+
+## Installation
+
+[The "Frameworks" section](../getting-started/installation/frameworks) covers
+installation with pnpm and other package managers.
+
+The library can be imported directly from JS or TS code with:
+
+```js
+import { read, utils, writeFile } from 'xlsx';
+```
+
+
+## Internal State
+
+The various SheetJS APIs work with various data shapes. The preferred state
+depends on the application.
+
+### Array of Objects
+
+Typically, some users will create a spreadsheet with source data that should be
+loaded into the site. This sheet will have known columns. For example, our
+[presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns:
+
+![`pres.xlsx` data](pathname:///pres.png)
+
+This naturally maps to an array of typed objects, as in the TS example below:
+
+```ts
+import { read, utils } from 'xlsx';
+
+interface President {
+ Name: string;
+ Index: number;
+}
+
+const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
+const wb = read(f);
+const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+console.log(data);
+```
+
+`data` will be an array of objects:
+
+```js
+[
+ { Name: "Bill Clinton", Index: 42 },
+ { Name: "GeorgeW Bush", Index: 43 },
+ { Name: "Barack Obama", Index: 44 },
+ { Name: "Donald Trump", Index: 45 },
+ { Name: "Joseph Biden", Index: 46 }
+]
+```
+
+A component will typically loop over the data uaing `*ngFor`. The following
+example generates a TABLE with a row for each President:
+
+```ts title="src/app/app.component.ts"
+import { Component } from '@angular/core';
+import { read, utils } from 'xlsx';
+
+interface President { Name: string; Index: number };
+
+@Component({
+ selector: 'app-root',
+ template: `
+
+
Name
Index
+
+// highlight-start
+
+
{{row.Name}}
+
{{row.Index}}
+
+// highlight-end
+
+
+`
+})
+export class AppComponent {
+ // highlight-next-line
+ rows: President[] = [ { Name: "SheetJS", Index: 0 }];
+ ngOnInit(): void { (async() => {
+ /* Download from https://sheetjs.com/pres.numbers */
+ const f = await fetch("https://sheetjs.com/pres.numbers");
+ const ab = await f.arrayBuffer();
+
+ /* parse workbook */
+ // highlight-next-line
+ const wb = read(ab);
+
+ /* update data */
+ // highlight-next-line
+ this.rows = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+
+ })(); }
+}
+```
+
+### HTML
+
+The main disadvantage of the Array of Objects approach is the specific nature
+of the columns. For more general use, passing around an Array of Arrays works.
+However, this does not handle merge cells well!
+
+The `sheet_to_html` function generates HTML that is aware of merges and other
+worksheet features. The generated HTML does not contain any `