diff --git a/docz/docs/03-demos/02-mobile.md b/docz/docs/03-demos/02-mobile.md
deleted file mode 100644
index 4e41096..0000000
--- a/docz/docs/03-demos/02-mobile.md
+++ /dev/null
@@ -1,1704 +0,0 @@
----
-title: iOS and Android Apps
----
-
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-
-Many mobile app frameworks mix JavaScript / CSS / HTML5 concepts with native
-extensions and libraries to create a hybrid development experience. Developers
-well-versed in web technologies can now build actual mobile applications that
-run on iOS and Android!
-
-:::warning
-
-**The ecosystem has broken backwards-compatibility many times!**
-
-iOS and Android, as well as the underlying JavaScript frameworks, make breaking
-changes regularly. The demos were tested against emulators / real devices at
-some point in time. A framework or OS change can render the demos inoperable.
-
-Each demo section will mention test dates and platform versions.
-
-:::
-
-The ["JavaScript Engines"](/docs/demos/engines) section includes samples for JavaScript
-engines used in the mobile app frameworks. SheetJS libraries have been tested
-in the relevant engines and should "just work" with some caveats.
-
-:::caution `readFile` and `writeFile`
-
-`XLSX.readFile` and `XLSX.writeFile` do not work in mobile apps! The demos
-include platform-specific details for fetching file data for `XLSX.read` and
-writing file data generated by `XLSX.write`.
-
-Some platforms provide this functionality as part of the standard library.
-Other platforms, including React Native, do not. When the platform does not
-provide, usually there are third-party modules to provide needed functionality.
-
-:::
-
-MacOS is required for the iOS demos. The Android demos were tested on MacOS.
-
-## React Native
-
-:::note
-
-This demo was tested on an Intel Mac on 2022 August 14 with RN `0.67.2`.
-
-The iOS simulator runs iOS 15.5 on an iPhone 13.
-
-The Android simulator runs Android 12 (S) Platform 31 on a Pixel 5.
-
-:::
-
-:::warning
-
-React Native does not provide a native file picker or a method for reading and
-writing data from documents on the devices. A third-party library must be used.
-
-Since React Native internals change between releases, libraries may only work
-with specific versions of React Native. Project documentation should be
-consulted before picking a library.
-
-:::
-
-The following table lists tested file plugins. "OS" lists tested platforms
-("A" for Android and "I" for iOS). "Copy" indicates whether an explicit copy
-is needed (file picker copies to cache directory and file plugin reads cache).
-
-| File system Plugin | File Picker Plugin | OS | Copy |
-|:---------------------------|:-------------------------------|:----:|:-----|
-| `react-native-file-access` | `react-native-document-picker` | `AI` | |
-| `react-native-blob-util` | `react-native-document-picker` | `AI` | YES |
-| `rn-fetch-blob` | `react-native-document-picker` | `AI` | YES |
-| `react-native-fs` | `react-native-document-picker` | `AI` | YES |
-| `expo-file-system` | `expo-document-picker` | ` I` | YES |
-
-### RN File Picker
-
-The following libraries have been tested:
-
-#### `react-native-document-picker`
-
-Selecting a file (click to show)
-
-When a copy is not needed:
-
-```js
-import { pickSingle } from 'react-native-document-picker';
-
-const f = await pickSingle({allowMultiSelection: false, mode: "open" });
-const path = f.uri; // this path can be read by RN file plugins
-```
-
-When a copy is needed:
-
-```js
-import { pickSingle } from 'react-native-document-picker';
-
-const f = await pickSingle({allowMultiSelection: false, copyTo: "cachesDirectory", mode: "open" });
-const path = f.fileCopyUri; // this path can be read by RN file plugins
-```
-
-
-
-#### `expo-document-picker`
-
-Selecting a file (click to show)
-
-When using `DocumentPicker.getDocumentAsync`, enable `copyToCacheDirectory`:
-
-```js
-import * as DocumentPicker from 'expo-document-picker';
-
-const result = await DocumentPicker.getDocumentAsync({
- // highlight-next-line
- copyToCacheDirectory: true,
- type: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
-});
-const path = result.uri;
-```
-
-
-
-
-### RN File Plugins
-
-The following libraries have been tested:
-
-#### `react-native-blob-util` and `rn-fetch-blob`
-
-:::note Historical Context
-
-The `react-native-fetch-blob` project was archived in 2019. At the time, there
-were a number of project forks. The maintainers blessed the `rn-fetch-blob`
-fork as the spiritual successor.
-
-`react-native-blob-util` is an active fork of `rn-fetch-blob`
-
-On the day that this demo was tested (2022 August 14), both `rn-fetch-blob` and
-`react-native-blob-util` worked with the tested iOS and Android SDK versions.
-The APIs are identical for the purposes of working with files.
-
-:::
-
-The `ascii` type returns an array of numbers corresponding to the raw bytes.
-A `Uint8Array` from the data is compatible with the `buffer` type.
-
-Reading and Writing snippets (click to show)
-
-The snippets use `rn-fetch-blob`. To use `react-native-blob-util`, change the
-`import` statements to load the module.
-
-_Reading Data_
-
-```js
-import * as XLSX from "xlsx";
-import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util
-const { readFile } = RNFetchBlob.fs;
-
-const res = await readFile(path, 'ascii');
-const wb = XLSX.read(new Uint8Array(res), {type:'buffer'});
-```
-
-:::caution
-
-On iOS, the URI from `react-native-document-picker` must be massaged:
-
-```js
-import { pickSingle } from 'react-native-document-picker';
-import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util
-const { readFile, dirs: { DocumentDir } } = RNFetchBlob.fs;
-
-const f = await pickSingle({
-// highlight-start
- // Instruct the document picker to copy file to Documents directory
- copyTo: "documentDirectory",
-// highlight-end
- allowMultiSelection: false, mode: "open" });
-// highlight-start
-// `f.uri` is the original path and `f.fileCopyUri` is the path to the copy
-let path = f.fileCopyUri;
-// iOS workaround
-if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, DDP + "/");
-// highlight-end
-
-const res = await readFile(path, 'ascii');
-```
-
-:::
-
-_Writing Data_
-
-```js
-import * as XLSX from "xlsx";
-import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util
-const { writeFile, readFile, dirs:{ DocumentDir } } = RNFetchBlob.fs;
-
-const wbout = XLSX.write(wb, {type:'buffer', bookType:"xlsx"});
-const file = DocumentDir + "/sheetjsw.xlsx";
-const res = await writeFile(file, Array.from(wbout), 'ascii');
-```
-
-
-
-
-#### `react-native-file-access`
-
-The `base64` encoding returns strings compatible with the `base64` type:
-
-Reading and Writing snippets (click to show)
-
-_Reading Data_
-
-```js
-import * as XLSX from "xlsx";
-import { FileSystem } from "react-native-file-access";
-
-const b64 = await FileSystem.readFile(path, "base64");
-/* b64 is a Base64 string */
-const workbook = XLSX.read(b64, {type: "base64"});
-```
-
-_Writing Data_
-
-```js
-import * as XLSX from "xlsx";
-import { Dirs, FileSystem } from "react-native-file-access";
-const DDP = Dirs.DocumentDir + "/";
-
-const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
-/* b64 is a Base64 string */
-await FileSystem.writeFile(DDP + "sheetjs.xlsx", b64, "base64");
-```
-
-
-
-#### `react-native-fs`
-
-The `ascii` encoding returns binary strings compatible with the `binary` type:
-
-Reading and Writing snippets (click to show)
-
-_Reading Data_
-
-```js
-import * as XLSX from "xlsx";
-import { readFile } from "react-native-fs";
-
-const bstr = await readFile(path, "ascii");
-/* bstr is a binary string */
-const workbook = XLSX.read(bstr, {type: "binary"});
-```
-
-_Writing Data_
-
-```js
-import * as XLSX from "xlsx";
-import { writeFile, DocumentDirectoryPath } from "react-native-fs";
-
-const bstr = XLSX.write(workbook, {type:'binary', bookType:"xlsx"});
-/* bstr is a binary string */
-await writeFile(DocumentDirectoryPath + "/sheetjs.xlsx", bstr, "ascii");
-```
-
-
-
-#### `expo-file-system`
-
-:::caution
-
-Some Expo APIs return URI that cannot be read with `expo-file-system`. This
-will manifest as an error:
-
-> Unsupported scheme for location '...'
-
-The [`expo-document-picker`](#expo-document-picker) snippet makes a local copy.
-
-:::
-
-The `EncodingType.Base64` encoding is compatible with `base64` type.
-
-Reading and Writing snippets (click to show)
-
-_Reading Data_
-
-Calling `FileSystem.readAsStringAsync` with `FileSystem.EncodingType.Base64`
-encoding returns a promise resolving to a string compatible with `base64` type:
-
-```js
-import * as XLSX from "xlsx";
-import * as FileSystem from 'expo-file-system';
-
-const b64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
-const workbook = XLSX.read(b64, { type: "base64" });
-```
-
-_Writing Data_
-
-The `FileSystem.EncodingType.Base64` encoding accepts Base64 strings:
-
-```js
-import * as XLSX from "xlsx";
-import * as FileSystem from 'expo-file-system';
-
-const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
-/* b64 is a Base64 string */
-await FileSystem.writeAsStringAsync(FileSystem.documentDirectory + "sheetjs.xlsx", b64, { encoding: FileSystem.EncodingType.Base64 });
-```
-
-
-
-### Demo
-
-:::warning
-
-There are many moving parts and pitfalls with React Native apps. It is strongly
-recommended to follow the official React Native tutorials for iOS and Android
-before approaching this demo. Details like creating an Android Virtual Device
-are not covered here.
-
-:::
-
-Complete Example (click to show)
-
-This example tries to separate the library-specific functions.
-
-0) **Follow the official React Native CLI Guide!**
-
-Development Environment Guide:
-
-Follow the instructions for iOS and for Android. They will cover installation
-and system configuration. By the end, you should be able to run the sample app
-in the Android and the iOS simulators.
-
-1) Create project:
-
-```
-npx react-native init SheetJSRN --version="0.67.2"
-```
-
-2) Install shared dependencies:
-
-```bash
-cd SheetJSRN
-curl -LO https://oss.sheetjs.com/assets/img/logo.png
-npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
-npm i -S react-native-table-component react-native-document-picker
-```
-
-Refresh iOS project by running `pod install` from the `ios` subfolder:
-
-```bash
-cd ios
-pod install
-cd ..
-```
-
-3) Download [`index.js`](pathname:///mobile/index.js) and replace:
-
-```bash
-curl -LO https://docs.sheetjs.com/mobile/index.js
-```
-
-Start the iOS emulator:
-
-```bash
-npx react-native run-ios
-```
-
-You should see the skeleton app:
-
-![React Native iOS App](pathname:///mobile/rnios1.png)
-
-4) Pick a filesystem library for integration:
-
-
-
-
-
-Install `react-native-blob-util` dependency:
-
-```bash
-npm i -S react-native-blob-util
-```
-
-Add the highlighted lines to `index.js`:
-
-```js title="index.js"
-import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
-
-// highlight-start
-import { read, write } from 'xlsx';
-import { pickSingle } from 'react-native-document-picker';
-import { Platform } from 'react-native';
-import RNFetchBlob from 'react-native-blob-util';
-
-async function pickAndParse() {
- /* rn-fetch-blob / react-native-blob-util need a copy */
- const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" });
- let path = f.fileCopyUri;
- if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, RNFetchBlob.fs.dirs.DocumentDir + "/");
- const res = await RNFetchBlob.fs.readFile(path, 'ascii');
- return read(new Uint8Array(res), {type: 'buffer'});
-}
-
-async function writeWorkbook(wb) {
- const wbout = write(wb, {type:'buffer', bookType:"xlsx"});
- const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx";
- await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii');
- return file;
-}
-// highlight-end
-
-const make_width = ws => {
-```
-
-
-
-
-Install `react-native-file-access` dependency:
-
-```bash
-npm i -S react-native-file-access
-```
-
-Add the highlighted lines to `index.js`:
-
-```js title="index.js"
-import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
-
-// highlight-start
-import { read, write } from 'xlsx';
-import { pickSingle } from 'react-native-document-picker';
-import { Dirs, FileSystem } from 'react-native-file-access';
-
-async function pickAndParse() {
- /* react-native-file-access does not need a copy */
- const f = await pickSingle({allowMultiSelection: false, mode: "open" });
- const res = await FileSystem.readFile(f.uri, "base64");
- return read(res, {type: 'base64'});
-}
-
-async function writeWorkbook(wb) {
- const wbout = write(wb, {type:'base64', bookType:"xlsx"});
- const file = Dirs.DocumentDir + "/sheetjsw.xlsx";
- await FileSystem.writeFile(file, wbout, "base64");
- return file;
-}
-// highlight-end
-
-const make_width = ws => {
-```
-
-
-
-
-Install `rn-fetch-blob` dependency:
-
-```bash
-npm i -S rn-fetch-blob
-```
-
-Add the highlighted lines to `index.js`:
-
-```js title="index.js"
-import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
-
-// highlight-start
-import { read, write } from 'xlsx';
-import { pickSingle } from 'react-native-document-picker';
-import { Platform } from 'react-native';
-import RNFetchBlob from 'rn-fetch-blob';
-
-async function pickAndParse() {
- /* rn-fetch-blob / react-native-blob-util need a copy */
- const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" });
- let path = f.fileCopyUri;
- if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, RNFetchBlob.fs.dirs.DocumentDir + "/");
- const res = await RNFetchBlob.fs.readFile(path, 'ascii');
- return read(new Uint8Array(res), {type: 'buffer'});
-}
-
-async function writeWorkbook(wb) {
- const wbout = write(wb, {type:'buffer', bookType:"xlsx"});
- const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx";
- await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii');
- return file;
-}
-// highlight-end
-
-const make_width = ws => {
-```
-
-
-
-
-Install `react-native-fs` dependency:
-
-```bash
-npm i -S react-native-fs
-```
-
-Add the highlighted lines to `index.js`:
-
-```js title="index.js"
-import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
-
-// highlight-start
-import { read, write } from 'xlsx';
-import { pickSingle } from 'react-native-document-picker';
-import { writeFile, readFile, DocumentDirectoryPath } from 'react-native-fs';
-
-async function pickAndParse() {
- /* react-native-fs needs a copy */
- const f = await pickSingle({allowMultiSelection: false, copyTo: "cachesDirectory", mode: "open" });
- const bstr = await readFile(f.fileCopyUri, 'ascii');
- return read(bstr, {type:'binary'});
-}
-
-async function writeWorkbook(wb) {
- const wbout = write(wb, {type:'binary', bookType:"xlsx"});
- const file = DocumentDirectoryPath + "/sheetjsw.xlsx";
- await writeFile(file, wbout, 'ascii');
- return file;
-}
-// highlight-end
-
-const make_width = ws => {
-```
-
-
-
-
-:::caution
-
-At the time of testing, the `npx install-expo-modules` step breaks the Android
-project. The demo works as expected on iOS.
-
-:::
-
-Install `expo-file-system` and `expo-document-picker` dependencies:
-
-```bash
-npx install-expo-modules
-npm i -S expo-file-system expo-document-picker
-```
-
-Add the highlighted lines to `index.js`:
-
-```js title="index.js"
-import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
-
-// highlight-start
-import { read, write } from 'xlsx';
-import { getDocumentAsync } from 'expo-document-picker';
-import { documentDirectory, readAsStringAsync, writeAsStringAsync } from 'expo-file-system';
-
-async function pickAndParse() {
- const result = await getDocumentAsync({copyToCacheDirectory: true});
- const path = result.uri;
- const res = await readAsStringAsync(path, { encoding: "base64" });
- return read(res, {type: 'base64'});
-}
-
-async function writeWorkbook(wb) {
- const wbout = write(wb, {type:'base64', bookType:"xlsx"});
- const file = documentDirectory + "sheetjsw.xlsx";
- await writeAsStringAsync(file, wbout, { encoding: "base64" });
- return file;
-}
-// highlight-end
-
-const make_width = ws => {
-```
-
-
-
-
-
-5) Refresh the app:
-
-```bash
-cd ios
-pod install
-cd ..
-```
-
-Once refreshed, the development process must be restarted:
-
-```bash
-npx react-native run-ios
-```
-
-**iOS Testing**
-
-The app can be tested with the following sequence in the simulator:
-
-- Download
-- In the simulator, click the Home icon to return to the home screen
-- Click on the "Files" icon
-- Click and drag `pres.numbers` from a Finder window into the simulator.
-
-![save file iOS](pathname:///mobile/quasar7a.png)
-
-- Make sure "On My iPhone" is highlighted and select "Save"
-- Click the Home icon again then select the `SheetJSRN` app
-- Click "Import data" and select `pres`:
-
-![pick file iOS](pathname:///mobile/rnios2.png)
-
-Once selected, the screen should refresh with new contents:
-
-![read file iOS](pathname:///mobile/rnios3.png)
-
-- Click "Export data". You will see a popup with a location:
-
-![write file iOS](pathname:///mobile/rnios4.png)
-
-- Find the file and verify the contents are correct:
-
-```bash
-find ~/Library/Developer/CoreSimulator -name sheetjsw.xlsx |
- while read x; do echo "$x"; npx xlsx-cli "$x"; done
-```
-
-Once testing is complete, stop the simulator and the development process.
-
-**Android Testing**
-
-There are no Android-specific steps. Emulator can be started with:
-
-```bash
-npx react-native run-android
-```
-
-![React Native Android App](pathname:///mobile/rnand1.png)
-
-The app can be tested with the following sequence in the simulator:
-
-- Download
-- Click and drag `pres.numbers` from a Finder window into the simulator.
-- Click "Import data" and select `pres.numbers`:
-
-![pick file Android](pathname:///mobile/rnand2.png)
-
-Once selected, the screen should refresh with new contents:
-
-![read file Android](pathname:///mobile/rnand3.png)
-
-- Click "Export data". You will see a popup with a location:
-
-![write file Android](pathname:///mobile/rnand4.png)
-
-- Pull the file from the simulator and verify the contents:
-
-```bash
-adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > /tmp/sheetjsw.xlsx
-npx xlsx-cli /tmp/sheetjsw.xlsx
-```
-
-
-
-## NativeScript
-
-:::note
-
-This demo was tested on an Intel Mac on 2022 August 10. NativeScript version
-(as verified with `ns --version`) is `8.3.2`. The iOS simulator runs iOS 15.5
-on an iPhone SE 3rd generation.
-
-:::
-
-:::warning Binary Data issues
-
-NativeScript will not safely transmit binary or UTF-8 strings. XLSB, NUMBERS,
-XLSX, XLS, ODS, SYLK, and DBF exports are known to be mangled.
-
-This is a known NativeScript bug.
-
-This demo will focus on ASCII CSV files. Once the bug is resolved, XLSX and
-other formats will be supported.
-
-:::
-
-The `@nativescript/core/file-system` package provides classes for file access.
-
-### Integration Details
-
-Reading and writing data require a file handle. The following snippet searches
-typical document folders for a specified filename:
-
-```ts
-import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
-
-function get_handle_for_filename(filename: string): File {
- const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
- const url: string = path.normalize(target.path + "///" + filename);
- return File.fromPath(url);
-}
-```
-
-The encoding `ISO_8859_1` spiritually resembles the `"binary"` SheetJS type
-
-**Reading data**
-
-`File#readText(encoding.ISO_8859_1)` returns strings compatible with `"binary"`
-
-```ts
-/* get binary string */
-const bstr: string = await file.readText(encoding.ISO_8859_1);
-
-/* read workbook */
-const wb = read(bstr, { type: "binary" });
-```
-
-**Writing data**
-
-`File#writeText` with the `ISO_8859_1` encoding accepts `"binary"` strings with
-the caveat listed in the warning at the top of this section:
-
-```ts
-/* generate binary string */
-const bstr: string = write(wb, { bookType: 'csv', type: 'binary' });
-
-/* attempt to save binary string to file */
-await file.writeText(bstr, encoding.ISO_8859_1);
-```
-
-### Demo
-
-The demo builds off of the NativeScript + Angular example. Familiarity with
-Angular and TypeScript is assumed.
-
-Complete Example (click to show)
-
-0) Follow the official Environment Setup instructions (tested with "MacOS + iOS")
-
-1) Create a skeleton NativeScript + Angular app:
-
-```bash
-ns create SheetJSNS --ng
-```
-
-2) Launch the app in the iOS simulator to verify that the demo built properly:
-
-```bash
-cd SheetJSNS
-ns run ios
-```
-
-(this may take a while)
-
-Once the simulator launches and the test app is displayed, end the script by
-selecting the terminal and entering the key sequence `CTRL + C`
-
-3) From the project folder, install the library:
-
-```bash
-npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
-```
-
-4) To confirm the library was loaded, change the title to show the version. The
-differences are highlighted.
-
-`src/app/item/items.component.ts` imports the version string to the component:
-
-```ts title="src/app/item/items.component.ts"
-// highlight-next-line
-import { version } from 'xlsx';
-import { Component, OnInit } from '@angular/core'
-
-import { Item } from './item'
-import { ItemService } from './item.service'
-
-@Component({
- selector: 'ns-items',
- templateUrl: './items.component.html',
-})
-export class ItemsComponent implements OnInit {
- items: Array
- // highlight-next-line
- version = `SheetJS - ${version}`;
-
- constructor(private itemService: ItemService) {}
-
- ngOnInit(): void {
- this.items = this.itemService.getItems()
- }
-}
-```
-
-`src/app/item/items.component.html` references the version in the title:
-
-```xml title="src/app/item/items.component.html"
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-Relaunch the app with `ns run ios` and the title bar should show the version.
-
-![NativeScript Step 4](pathname:///mobile/nativescript4.png)
-
-5) Add the Import and Export buttons to the template:
-
-```xml title="src/app/item/items.component.html"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-```ts title="src/app/item/items.component.ts"
-// highlight-start
-import { version, utils, read, write } from 'xlsx';
-import { Dialogs } from '@nativescript/core';
-import { encoding } from '@nativescript/core/text';
-import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
-// highlight-end
-import { Component, OnInit } from '@angular/core'
-
-import { Item } from './item'
-import { ItemService } from './item.service'
-
-// highlight-start
-function get_handle_for_filename(filename: string): [File, string] {
- const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
- const url: string = path.normalize(target.path + "///" + filename);
- return [File.fromPath(url), url];
-}
-// highlight-end
-
-@Component({
- selector: 'ns-items',
- templateUrl: './items.component.html',
-})
-export class ItemsComponent implements OnInit {
- items: Array
- version: string = `SheetJS - ${version}`;
-
- constructor(private itemService: ItemService) {}
-
- ngOnInit(): void {
- this.items = this.itemService.getItems()
- }
-
- // highlight-start
- /* Import button */
- async import() {
- }
-
- /* Export button */
- async export() {
- }
- // highlight-end
-}
-```
-
-Restart the app process and two buttons should show up at the top:
-
-![NativeScript Step 5](pathname:///mobile/nativescript5.png)
-
-6) Implement import and export:
-
-```ts title="src/app/item/items.component.ts"
-import { version, utils, read, write } from 'xlsx';
-import { Dialogs } from '@nativescript/core';
-import { encoding } from '@nativescript/core/text';
-import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
-import { Component, OnInit } from '@angular/core'
-
-import { Item } from './item'
-import { ItemService } from './item.service'
-
-function get_handle_for_filename(filename: string): [File, string] {
- const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
- const url: string = path.normalize(target.path + "///" + filename);
- return [File.fromPath(url), url];
-}
-
-@Component({
- selector: 'ns-items',
- templateUrl: './items.component.html',
-})
-export class ItemsComponent implements OnInit {
- items: Array
- version: string = `SheetJS - ${version}`;
-
- constructor(private itemService: ItemService) {}
-
- ngOnInit(): void {
- this.items = this.itemService.getItems()
- }
-
- /* Import button */
- async import() {
- // highlight-start
- /* find appropriate path */
- const [file, url] = get_handle_for_filename("SheetJSNS.csv");
-
- try {
- /* get binary string */
- const bstr: string = await file.readText(encoding.ISO_8859_1);
-
- /* read workbook */
- const wb = read(bstr, { type: "binary" });
-
- /* grab first sheet */
- const wsname: string = wb.SheetNames[0];
- const ws = wb.Sheets[wsname];
-
- /* update table */
- this.items = utils.sheet_to_json(ws);
- Dialogs.alert(`Attempting to read to ${filename} in ${url}`);
- } catch(e) { Dialogs.alert(e.message); }
- // highlight-end
- }
-
- /* Export button */
- async export() {
- // highlight-start
- /* find appropriate path */
- const [file, url] = get_handle_for_filename("SheetJSNS.csv");
-
- try {
- /* create worksheet from data */
- const ws = utils.json_to_sheet(this.items);
-
- /* create workbook from worksheet */
- const wb = utils.book_new();
- utils.book_append_sheet(wb, ws, "Sheet1");
-
- /* generate binary string */
- const wbout: string = write(wb, { bookType: 'csv', type: 'binary' });
-
- /* attempt to save binary string to file */
- await file.writeText(wbout, encoding.ISO_8859_1);
- Dialogs.alert(`Wrote to ${filename} in ${url}`);
- } catch(e) { Dialogs.alert(e.message); }
- // highlight-end
- }
-}
-```
-
-Restart the app process.
-
-**Testing**
-
-The app can be tested with the following sequence in the simulator:
-
-- Hit "Export File". A dialog will print where the file was written
-
-- Open that file with a text editor. It will be a 3-column CSV:
-
-```csv
-id,name,role
-1,Ter Stegen,Goalkeeper
-3,Piqué,Defender
-4,I. Rakitic,Midfielder
-...
-```
-
-After the header row, add the line `0,SheetJS,Library`:
-
-```csv
-id,name,role
-0,SheetJS,Library
-1,Ter Stegen,Goalkeeper
-3,Piqué,Defender
-...
-```
-
-- Hit "Import File". A dialog will print the path of the file that was read.
- The first item in the list will change:
-
-![NativeScript Step 7](pathname:///mobile/nativescript7.png)
-
-
-
-## Quasar
-
-:::note
-
-This demo was tested on an Intel Mac on 2022 August 14. Quasar version `2.7.7`.
-The iOS simulator runs iOS 15.5 on an iPhone SE 3rd generation.
-
-:::
-
-This demo will use the Quasar ViteJS starter project with VueJS and Cordova.
-
-### Integration Details
-
-The complete solution uses `cordova-plugin-file` for file operations. It can
-be installed like any other Cordova plugin:
-
-```bash
-cd src-cordova
-cordova plugin add cordova-plugin-file
-cd ..
-```
-
-#### Reading data
-
-The `q-file` component presents an API reminiscent of File Input elements:
-
-```html
-
-```
-
-When binding to the `input` element, the callback receives an `Event` object:
-
-```ts
-import { read } from 'xlsx';
-
-// assuming `todos` is a standard VueJS `ref`
-async function updateFile(v) { try {
- // `v.target.files[0]` is the desired file object
- const files = (v.target as HTMLInputElement).files;
- if(!files || files.length == 0) return;
-
- // read first file
- const wb = read(await files[0].arrayBuffer());
-
- // get data of first worksheet as an array of objects
- const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
-
- // update state
- todos.value = data.map(row => ({id: row.Index, content: row.Name}));
-
-} catch(e) { console.log(e); } }
-```
-
-#### Writing data
-
-The API is shaped like the File and Directory Entries API. For clarity, since
-the code is a "pyramid of doom", the error handlers are omitted:
-
-```ts
-import { write } from 'xlsx';
-
-// on iOS and android, `XLSX.write` with type "buffer" returns a `Uint8Array`
-const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"});
-// Request filesystem access for persistent storage
-window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
- // Request a handle to "SheetJSQuasar.xlsx", making a new file if necessary
- fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => {
- // Request a FileWriter for writing data
- entry.createWriter(writer => {
- // The FileWriter API needs an actual Blob
- const data = new Blob([u8], {type: "application/vnd.ms-excel"});
- // This callback is called if the write is successful
- writer.onwriteend = () => {
- // TODO: show a dialog
- };
- // writer.onerror will be invoked if there is an error in writing
-
- // write the data
- writer.write(data);
- });
- });
-});
-```
-
-### Demo
-
-The demo draws from the ViteJS example. Familiarity with VueJS and TypeScript
-is assumed.
-
-Complete Example (click to show)
-
-0) Ensure all of the dependencies are installed. Install the CLI globally:
-
-```bash
-npm i -g @quasar/cli cordova
-```
-
-(you may need to run `sudo npm i -g` if there are permission issues)
-
-1) Create a new app:
-
-```bash
-npm init quasar
-```
-
-
-
-When prompted:
-
-- "What would you like to build?": `App with Quasar CLI`
-- "Project folder": `SheetJSQuasar`
-- "Pick Quasar version": `Quasar v2 (Vue 3 | latest and greatest)`
-- "Pick script type": `Typescript`
-- "Pick Quasar App CLI variant": `Quasar App CLI with Vite`
-- "Package name": (just press enter, it will use the default `sheetjsquasar`
-- "Project product name": `SheetJSQuasar`
-- "Project description": `SheetJS + Quasar`
-- "Author": (just press enter, it will use your git config settings)
-- "Pick a Vue component style": `Composition API`
-- "Pick your CSS preprocessor": `None`
-- "Check the features needed for your project": Deselect everything
-- "Install project dependencies": `No`
-
-2) Install dependencies:
-
-
-
-```bash
-cd SheetJSQuasar
-npm i
-npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
-```
-
-3) Set up Cordova:
-
-```bash
-quasar mode add cordova
-```
-
-When prompted, enter the app id `org.sheetjs.quasar`.
-
-It will create a new `src-cordova` folder. Continue in that folder:
-
-```bash
-cd src-cordova
-cordova platform add ios
-cordova plugin add cordova-plugin-wkwebview-engine
-cordova plugin add cordova-plugin-file
-```
-
-:::note
-
-If there is an error `Could not load API for iOS project`, it needs to be reset:
-
-```bash
-cordova platform rm ios
-cordova platform add ios
-cordova plugin add cordova-plugin-file
-```
-
-:::
-
-Return to the project directory:
-
-```bash
-cd ..
-```
-
-4) Start the development server:
-
-```bash
-quasar dev -m ios
-```
-
-:::caution
-
-If the app is blank or not refreshing, delete the app and close the simulator,
-then restart the development process.
-
-:::
-
-![Quasar Step 4](pathname:///mobile/quasar4.png)
-
-
-5) Add the Dialog plugin to `quasar.config.js`:
-
-```js title="quasar.config.js"
- // Quasar plugins
- // highlight-next-line
- plugins: ['Dialog']
-```
-
-6) In the template section of `src/pages/IndexPage.vue`, add a Save button and
- a Load file picker component at the bottom of the page:
-
-```html title="src/pages/IndexPage.vue"
-
-
-
-
-
-
-
-
-```
-
-This uses two functions that should be added to the component script:
-
-```ts title="src/pages/IndexPage.vue"
- const meta = ref({
- totalCount: 1200
- });
-// highlight-start
- function saveFile() {
- }
- async function updateFile(v) {
- }
- return { todos, meta, saveFile, updateFile };
-// highlight-end
- }
-});
-
-```
-
-The app should now show two buttons at the bottom:
-
-![Quasar Step 6](pathname:///mobile/quasar6.png)
-
-:::caution
-
-If the app is blank or not refreshing, delete the app and close the simulator,
-then restart the development process.
-
-:::
-
-7) Wire up the `updateFile` function:
-
-```ts title="src/pages/IndexPage.vue"
-import { defineComponent, ref } from 'vue';
-// highlight-start
-import { read, write, utils } from 'xlsx';
-import { useQuasar } from 'quasar';
-// highlight-end
-
-export default defineComponent({
-// ...
-// highlight-start
- const $q = useQuasar();
- function dialogerr(e) { $q.dialog({title: "Error!", message: e.message || String(e)}); }
-// highlight-end
- function saveFile() {
- }
- async function updateFile(v) {
-// highlight-start
- try {
- const files = (v.target as HTMLInputElement).files;
- if(!files || files.length == 0) return;
-
- const wb = read(await files[0].arrayBuffer());
-
- const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
- todos.value = data.map(row => ({id: row.Index, content: row.Name}));
- } catch(e) { dialogerr(e); }
-// highlight-end
- }
-```
-
-To test that reading works:
-
-- Download
-- In the simulator, click the Home icon to return to the home screen
-- Click on the "Files" icon
-- Click and drag `pres.numbers` from a Finder window into the simulator.
-
-![Quasar Step 7 save file](pathname:///mobile/quasar7a.png)
-
-- Make sure "On My iPhone" is highlighted and select "Save"
-- Click the Home icon again then select the `SheetJSQuasar` app
-- Click the "Load" button, then select "Choose File" and select `pres`:
-
-![Quasar Step 7 load file](pathname:///mobile/quasar7b.png)
-
-Once selected, the screen should refresh with new contents:
-
-![Quasar Step 7 new data](pathname:///mobile/quasar7c.png)
-
-8) Wire up the `saveFile` function:
-
-```js
- function saveFile() {
-// highlight-start
- /* generate workbook from state */
- const ws = utils.json_to_sheet(todos.value);
- const wb = utils.book_new();
- utils.book_append_sheet(wb, ws, "SheetJSQuasar");
- const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"});
-
- /* save to file */
- window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
- try {
- fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => {
- const msg = `File stored at ${$q.cordova.file.documentsDirectory} ${entry.fullPath}`;
- entry.createWriter(writer => {
- try {
- const data = new Blob([u8], {type: "application/vnd.ms-excel"});
- writer.onwriteend = () => {
- try {
- $q.dialog({title: "Success!", message: msg});
- } catch(e) { dialogerr(e); }
- };
- writer.onerror = dialogerr;
- writer.write(data);
- } catch(e) { dialogerr(e); }
- }, dialogerr);
- }, dialogerr);
- } catch(e) { dialogerr(e) }
- }, dialogerr);
-// highlight-end
- }
-```
-
-The page should revert to the old contents.
-
-To test that writing works:
-
-- Click "Save File". You will see a popup with a location:
-
-![Quasar Step 8](pathname:///mobile/quasar8.png)
-
-- Find the file and verify the contents are correct. Run in a new terminal:
-
-```bash
-find ~/Library/Developer/CoreSimulator -name SheetJSQuasar.xlsx |
- while read x; do echo "$x"; npx xlsx-cli "$x"; done
-```
-
-Since the contents reverted, you should see
-
-```
-SheetJSQuasar
-id,content
-1,ct1
-2,ct2
-3,ct3
-4,ct4
-5,ct5
-```
-
-- Use "Load File" to select `pres.numbers` again. Wait for the app to refresh.
-
-- Click "Save File", then re-run the command:
-
-```bash
-find ~/Library/Developer/CoreSimulator -name SheetJSQuasar.xlsx |
- while read x; do echo "$x"; npx xlsx-cli "$x"; done
-```
-
-The contents from `pres.numbers` should show up now, with a new header row:
-
-```
-SheetJSQuasar
-id,content
-42,Bill Clinton
-43,GeorgeW Bush
-44,Barack Obama
-45,Donald Trump
-46,Joseph Biden
-```
-
-
-
-## Ionic
-
-:::note
-
-This demo was tested on an Intel Mac on 2022 August 18 with Cordova.
-The file integration uses `@ionic-native/file` version `5.36.0`.
-
-The iOS simulator runs iOS 15.5 on an iPod Touch 7th Gen.
-
-:::
-
-:::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 Cordova apps.
-
-:::
-
-`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});
-```
-
-### Demo
-
-The demo uses Cordova.
-
-Complete Example (click to show)
-
-0) Disable telemetry as noted in the warning.
-
-Install required global dependencies:
-
-```bash
-npm i -g cordova-res @angular/cli native-run
-```
-
-Follow the [React Native demo](#demo) to ensure iOS and Android sims are ready.
-
-
-1) Create a new project:
-
-```bash
-npx @ionic/cli start SheetJSIonic blank --type angular --cordova --quiet --no-git --no-link --confirm
-```
-
-If a prompt discusses Cordova and Capacitor, enter `Yes` to continue.
-
-If a prompt asks about creating an Ionic account, enter `N` to opt out.
-
-2) Set up Cordova:
-
-```bash
-npx @ionic/cli cordova platform add ios --confirm
-npx @ionic/cli cordova plugin add cordova-plugin-file
-npm install --save @ionic-native/core @ionic-native/file @ionic/cordova-builders
-```
-
-3) Install dependencies:
-
-```bash
-npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
-```
-
-4) Add `@ionic-native/file` to the module. Differences highlighted below:
-
-```ts title="src/app/app.module.ts"
-import { AppComponent } from './app.component';
-import { AppRoutingModule } from './app-routing.module';
-
-// highlight-next-line
-import { File } from '@ionic-native/file/ngx';
-
-@NgModule({
- declarations: [AppComponent],
- imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
-
- // highlight-next-line
- providers: [File, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
- bootstrap: [AppComponent],
-})
-export class AppModule {}
-```
-
-5) Download [`home.page.ts`](pathname:///ionic/home.page.ts) and replace:
-
-```bash
-curl -o src/app/home/home.page.ts -L https://docs.sheetjs.com/ionic/home.page.ts
-```
-
-6) Test the app:
-
-```bash
-npx @ionic/cli cordova emulate ios
-```
-
-
-
-## CapacitorJS
-
-:::note
-
-This demo was tested on an Intel Mac on 2022 August 26 with Svelte.
-
-The iOS simulator runs iOS 15.5 on an iPhone 13 Pro Max.
-
-:::
-
-:::warning Telemetry
-
-Before starting this demo, manually disable telemetry. On Linux and MacOS:
-
-```bash
-npx @capacitor/cli telemetry off
-```
-
-To verify telemetry was disabled:
-
-```bash
-npx @capacitor/cli telemetry
-```
-
-:::
-
-### Integration Details
-
-This example uses Svelte, but the same principles apply to other frameworks.
-
-#### Reading data
-
-The standard HTML5 File Input element logic works in CapacitorJS:
-
-```html
-
-
-
-
-
-
{@html html}
-
-```
-
-#### Writing data
-
-`@capacitor/filesystem` can write Base64 strings:
-
-```html
-
-
-
-
-
{@html html}
-
-```
-
-### Demo
-
-Complete Example (click to show)
-
-0) Disable telemetry as noted in the warning.
-
-Follow the [React Native demo](#demo) to ensure iOS and Android sims are ready.
-
-
-1) Create a new Svelte project:
-
-```bash
-npm create vite@latest sheetjs-cap -- --template svelte
-cd sheetjs-cap
-```
-
-2) Install dependencies:
-
-```bash
-npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
-npm i --save @capacitor/core @capacitor/cli @capacitor/ios @capacitor/filesystem
-```
-
-3) Create CapacitorJS structure:
-
-```bash
-npx cap init sheetjs-cap com.sheetjs.cap --web-dir=dist
-npx cap add ios
-```
-
-4) Replace the contents of `src/App.svelte` with the following:
-
-```html title="src/App.svelte"
-
-
-
-
SheetJS × CapacitorJS { version }
-
-
-
{@html html}
-
-```
-
-5) Test the app:
-
-```bash
-npm run build; npx cap sync; npx cap run ios
-```
-
-There are 3 steps: build the Svelte app, sync with CapacitorJS, and run sim.
-This sequence must be run every time to ensure changes are propagated.
-
-
diff --git a/docz/docs/03-demos/02-mobile/01-reactnative.md b/docz/docs/03-demos/02-mobile/01-reactnative.md
new file mode 100644
index 0000000..88498bd
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/01-reactnative.md
@@ -0,0 +1,647 @@
+---
+title: React Native
+pagination_prev: demos/salesforce
+pagination_next: demos/desktop/index
+sidebar_position: 1
+sidebar_custom_props:
+ summary: React + Native Rendering
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
+from the main `App.js` entrypoint or any script in the project.
+
+The "Complete Example" creates an app that looks like the screenshots below:
+
+
+
+
+## Native Libraries
+
+:::warning
+
+React Native does not provide a native file picker or a method for reading and
+writing data from documents on the devices. A third-party library must be used.
+
+Since React Native internals change between releases, libraries may only work
+with specific versions of React Native. Project documentation should be
+consulted before picking a library.
+
+:::
+
+The following table lists tested file plugins. "OS" lists tested platforms
+("A" for Android and "I" for iOS). "Copy" indicates whether an explicit copy
+is needed (file picker copies to cache directory and file plugin reads cache).
+
+| File system Plugin | File Picker Plugin | OS | Copy |
+|:---------------------------|:-------------------------------|:----:|:-----|
+| `react-native-file-access` | `react-native-document-picker` | `AI` | |
+| `react-native-blob-util` | `react-native-document-picker` | `AI` | YES |
+| `rn-fetch-blob` | `react-native-document-picker` | `AI` | YES |
+| `react-native-fs` | `react-native-document-picker` | `AI` | YES |
+| `expo-file-system` | `expo-document-picker` | ` I` | YES |
+
+### RN File Picker
+
+The following libraries have been tested:
+
+#### `react-native-document-picker`
+
+Selecting a file (click to show)
+
+When a copy is not needed:
+
+```js
+import { pickSingle } from 'react-native-document-picker';
+
+const f = await pickSingle({allowMultiSelection: false, mode: "open" });
+const path = f.uri; // this path can be read by RN file plugins
+```
+
+When a copy is needed:
+
+```js
+import { pickSingle } from 'react-native-document-picker';
+
+const f = await pickSingle({allowMultiSelection: false, copyTo: "cachesDirectory", mode: "open" });
+const path = f.fileCopyUri; // this path can be read by RN file plugins
+```
+
+
+
+#### `expo-document-picker`
+
+Selecting a file (click to show)
+
+When using `DocumentPicker.getDocumentAsync`, enable `copyToCacheDirectory`:
+
+```js
+import * as DocumentPicker from 'expo-document-picker';
+
+const result = await DocumentPicker.getDocumentAsync({
+ // highlight-next-line
+ copyToCacheDirectory: true,
+ type: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
+});
+const path = result.uri;
+```
+
+
+
+
+### RN File Plugins
+
+The following libraries have been tested:
+
+#### `react-native-blob-util` and `rn-fetch-blob`
+
+:::note Historical Context
+
+The `react-native-fetch-blob` project was archived in 2019. At the time, there
+were a number of project forks. The maintainers blessed the `rn-fetch-blob`
+fork as the spiritual successor.
+
+`react-native-blob-util` is an active fork of `rn-fetch-blob`
+
+On the day that this demo was tested (2022 August 14), both `rn-fetch-blob` and
+`react-native-blob-util` worked with the tested iOS and Android SDK versions.
+The APIs are identical for the purposes of working with files.
+
+:::
+
+The `ascii` type returns an array of numbers corresponding to the raw bytes.
+A `Uint8Array` from the data is compatible with the `buffer` type.
+
+Reading and Writing snippets (click to show)
+
+The snippets use `rn-fetch-blob`. To use `react-native-blob-util`, change the
+`import` statements to load the module.
+
+_Reading Data_
+
+```js
+import * as XLSX from "xlsx";
+import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util
+const { readFile } = RNFetchBlob.fs;
+
+const res = await readFile(path, 'ascii');
+const wb = XLSX.read(new Uint8Array(res), {type:'buffer'});
+```
+
+:::caution
+
+On iOS, the URI from `react-native-document-picker` must be massaged:
+
+```js
+import { pickSingle } from 'react-native-document-picker';
+import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util
+const { readFile, dirs: { DocumentDir } } = RNFetchBlob.fs;
+
+const f = await pickSingle({
+// highlight-start
+ // Instruct the document picker to copy file to Documents directory
+ copyTo: "documentDirectory",
+// highlight-end
+ allowMultiSelection: false, mode: "open" });
+// highlight-start
+// `f.uri` is the original path and `f.fileCopyUri` is the path to the copy
+let path = f.fileCopyUri;
+// iOS workaround
+if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, DDP + "/");
+// highlight-end
+
+const res = await readFile(path, 'ascii');
+```
+
+:::
+
+_Writing Data_
+
+```js
+import * as XLSX from "xlsx";
+import RNFetchBlob from 'rn-fetch-blob'; // or react-native-blob-util
+const { writeFile, readFile, dirs:{ DocumentDir } } = RNFetchBlob.fs;
+
+const wbout = XLSX.write(wb, {type:'buffer', bookType:"xlsx"});
+const file = DocumentDir + "/sheetjsw.xlsx";
+const res = await writeFile(file, Array.from(wbout), 'ascii');
+```
+
+
+
+
+#### `react-native-file-access`
+
+The `base64` encoding returns strings compatible with the `base64` type:
+
+Reading and Writing snippets (click to show)
+
+_Reading Data_
+
+```js
+import * as XLSX from "xlsx";
+import { FileSystem } from "react-native-file-access";
+
+const b64 = await FileSystem.readFile(path, "base64");
+/* b64 is a Base64 string */
+const workbook = XLSX.read(b64, {type: "base64"});
+```
+
+_Writing Data_
+
+```js
+import * as XLSX from "xlsx";
+import { Dirs, FileSystem } from "react-native-file-access";
+const DDP = Dirs.DocumentDir + "/";
+
+const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
+/* b64 is a Base64 string */
+await FileSystem.writeFile(DDP + "sheetjs.xlsx", b64, "base64");
+```
+
+
+
+#### `react-native-fs`
+
+The `ascii` encoding returns binary strings compatible with the `binary` type:
+
+Reading and Writing snippets (click to show)
+
+_Reading Data_
+
+```js
+import * as XLSX from "xlsx";
+import { readFile } from "react-native-fs";
+
+const bstr = await readFile(path, "ascii");
+/* bstr is a binary string */
+const workbook = XLSX.read(bstr, {type: "binary"});
+```
+
+_Writing Data_
+
+```js
+import * as XLSX from "xlsx";
+import { writeFile, DocumentDirectoryPath } from "react-native-fs";
+
+const bstr = XLSX.write(workbook, {type:'binary', bookType:"xlsx"});
+/* bstr is a binary string */
+await writeFile(DocumentDirectoryPath + "/sheetjs.xlsx", bstr, "ascii");
+```
+
+
+
+#### `expo-file-system`
+
+:::caution
+
+Some Expo APIs return URI that cannot be read with `expo-file-system`. This
+will manifest as an error:
+
+> Unsupported scheme for location '...'
+
+The [`expo-document-picker`](#expo-document-picker) snippet makes a local copy.
+
+:::
+
+The `EncodingType.Base64` encoding is compatible with `base64` type.
+
+Reading and Writing snippets (click to show)
+
+_Reading Data_
+
+Calling `FileSystem.readAsStringAsync` with `FileSystem.EncodingType.Base64`
+encoding returns a promise resolving to a string compatible with `base64` type:
+
+```js
+import * as XLSX from "xlsx";
+import * as FileSystem from 'expo-file-system';
+
+const b64 = await FileSystem.readAsStringAsync(uri, { encoding: FileSystem.EncodingType.Base64 });
+const workbook = XLSX.read(b64, { type: "base64" });
+```
+
+_Writing Data_
+
+The `FileSystem.EncodingType.Base64` encoding accepts Base64 strings:
+
+```js
+import * as XLSX from "xlsx";
+import * as FileSystem from 'expo-file-system';
+
+const b64 = XLSX.write(workbook, {type:'base64', bookType:"xlsx"});
+/* b64 is a Base64 string */
+await FileSystem.writeAsStringAsync(FileSystem.documentDirectory + "sheetjs.xlsx", b64, { encoding: FileSystem.EncodingType.Base64 });
+```
+
+
+
+## Demo
+
+:::note
+
+This demo was tested on an Intel Mac on 2022 August 14 with RN `0.67.2`.
+
+The iOS simulator runs iOS 15.5 on an iPhone 13.
+
+The Android simulator runs Android 12 (S) Platform 31 on a Pixel 5.
+
+:::
+
+:::warning
+
+There are many moving parts and pitfalls with React Native apps. It is strongly
+recommended to follow the official React Native tutorials for iOS and Android
+before approaching this demo. Details like creating an Android Virtual Device
+are not covered here.
+
+:::
+
+This example tries to separate the library-specific functions.
+
+0) **Follow the official React Native CLI Guide!**
+
+Development Environment Guide:
+
+Follow the instructions for iOS and for Android. They will cover installation
+and system configuration. By the end, you should be able to run the sample app
+in the Android and the iOS simulators.
+
+1) Create project:
+
+```
+npx react-native init SheetJSRN --version="0.67.2"
+```
+
+2) Install shared dependencies:
+
+```bash
+cd SheetJSRN
+curl -LO https://oss.sheetjs.com/assets/img/logo.png
+npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
+npm i -S react-native-table-component react-native-document-picker
+```
+
+Refresh iOS project by running `pod install` from the `ios` subfolder:
+
+```bash
+cd ios
+pod install
+cd ..
+```
+
+3) Download [`index.js`](pathname:///mobile/index.js) and replace:
+
+```bash
+curl -LO https://docs.sheetjs.com/mobile/index.js
+```
+
+Start the iOS emulator:
+
+```bash
+npx react-native run-ios
+```
+
+You should see the skeleton app:
+
+![React Native iOS App](pathname:///mobile/rnios1.png)
+
+4) Pick a filesystem library for integration:
+
+
+
+
+
+Install `react-native-blob-util` dependency:
+
+```bash
+npm i -S react-native-blob-util
+```
+
+Add the highlighted lines to `index.js`:
+
+```js title="index.js"
+import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
+
+// highlight-start
+import { read, write } from 'xlsx';
+import { pickSingle } from 'react-native-document-picker';
+import { Platform } from 'react-native';
+import RNFetchBlob from 'react-native-blob-util';
+
+async function pickAndParse() {
+ /* rn-fetch-blob / react-native-blob-util need a copy */
+ const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" });
+ let path = f.fileCopyUri;
+ if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, RNFetchBlob.fs.dirs.DocumentDir + "/");
+ const res = await RNFetchBlob.fs.readFile(path, 'ascii');
+ return read(new Uint8Array(res), {type: 'buffer'});
+}
+
+async function writeWorkbook(wb) {
+ const wbout = write(wb, {type:'buffer', bookType:"xlsx"});
+ const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx";
+ await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii');
+ return file;
+}
+// highlight-end
+
+const make_width = ws => {
+```
+
+
+
+
+Install `react-native-file-access` dependency:
+
+```bash
+npm i -S react-native-file-access
+```
+
+Add the highlighted lines to `index.js`:
+
+```js title="index.js"
+import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
+
+// highlight-start
+import { read, write } from 'xlsx';
+import { pickSingle } from 'react-native-document-picker';
+import { Dirs, FileSystem } from 'react-native-file-access';
+
+async function pickAndParse() {
+ /* react-native-file-access does not need a copy */
+ const f = await pickSingle({allowMultiSelection: false, mode: "open" });
+ const res = await FileSystem.readFile(f.uri, "base64");
+ return read(res, {type: 'base64'});
+}
+
+async function writeWorkbook(wb) {
+ const wbout = write(wb, {type:'base64', bookType:"xlsx"});
+ const file = Dirs.DocumentDir + "/sheetjsw.xlsx";
+ await FileSystem.writeFile(file, wbout, "base64");
+ return file;
+}
+// highlight-end
+
+const make_width = ws => {
+```
+
+
+
+
+Install `rn-fetch-blob` dependency:
+
+```bash
+npm i -S rn-fetch-blob
+```
+
+Add the highlighted lines to `index.js`:
+
+```js title="index.js"
+import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
+
+// highlight-start
+import { read, write } from 'xlsx';
+import { pickSingle } from 'react-native-document-picker';
+import { Platform } from 'react-native';
+import RNFetchBlob from 'rn-fetch-blob';
+
+async function pickAndParse() {
+ /* rn-fetch-blob / react-native-blob-util need a copy */
+ const f = await pickSingle({allowMultiSelection: false, copyTo: "documentDirectory", mode: "open" });
+ let path = f.fileCopyUri;
+ if (Platform.OS === 'ios') path = path.replace(/^.*\/Documents\//, RNFetchBlob.fs.dirs.DocumentDir + "/");
+ const res = await RNFetchBlob.fs.readFile(path, 'ascii');
+ return read(new Uint8Array(res), {type: 'buffer'});
+}
+
+async function writeWorkbook(wb) {
+ const wbout = write(wb, {type:'buffer', bookType:"xlsx"});
+ const file = RNFetchBlob.fs.dirs.DocumentDir + "/sheetjsw.xlsx";
+ await RNFetchBlob.fs.writeFile(file, Array.from(wbout), 'ascii');
+ return file;
+}
+// highlight-end
+
+const make_width = ws => {
+```
+
+
+
+
+Install `react-native-fs` dependency:
+
+```bash
+npm i -S react-native-fs
+```
+
+Add the highlighted lines to `index.js`:
+
+```js title="index.js"
+import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
+
+// highlight-start
+import { read, write } from 'xlsx';
+import { pickSingle } from 'react-native-document-picker';
+import { writeFile, readFile, DocumentDirectoryPath } from 'react-native-fs';
+
+async function pickAndParse() {
+ /* react-native-fs needs a copy */
+ const f = await pickSingle({allowMultiSelection: false, copyTo: "cachesDirectory", mode: "open" });
+ const bstr = await readFile(f.fileCopyUri, 'ascii');
+ return read(bstr, {type:'binary'});
+}
+
+async function writeWorkbook(wb) {
+ const wbout = write(wb, {type:'binary', bookType:"xlsx"});
+ const file = DocumentDirectoryPath + "/sheetjsw.xlsx";
+ await writeFile(file, wbout, 'ascii');
+ return file;
+}
+// highlight-end
+
+const make_width = ws => {
+```
+
+
+
+
+:::caution
+
+At the time of testing, the `npx install-expo-modules` step breaks the Android
+project. The demo works as expected on iOS.
+
+:::
+
+Install `expo-file-system` and `expo-document-picker` dependencies:
+
+```bash
+npx install-expo-modules
+npm i -S expo-file-system expo-document-picker
+```
+
+Add the highlighted lines to `index.js`:
+
+```js title="index.js"
+import { Table, Row, Rows, TableWrapper } from 'react-native-table-component';
+
+// highlight-start
+import { read, write } from 'xlsx';
+import { getDocumentAsync } from 'expo-document-picker';
+import { documentDirectory, readAsStringAsync, writeAsStringAsync } from 'expo-file-system';
+
+async function pickAndParse() {
+ const result = await getDocumentAsync({copyToCacheDirectory: true});
+ const path = result.uri;
+ const res = await readAsStringAsync(path, { encoding: "base64" });
+ return read(res, {type: 'base64'});
+}
+
+async function writeWorkbook(wb) {
+ const wbout = write(wb, {type:'base64', bookType:"xlsx"});
+ const file = documentDirectory + "sheetjsw.xlsx";
+ await writeAsStringAsync(file, wbout, { encoding: "base64" });
+ return file;
+}
+// highlight-end
+
+const make_width = ws => {
+```
+
+
+
+
+
+5) Refresh the app:
+
+```bash
+cd ios
+pod install
+cd ..
+```
+
+Once refreshed, the development process must be restarted:
+
+```bash
+npx react-native run-ios
+```
+
+**iOS Testing**
+
+The app can be tested with the following sequence in the simulator:
+
+- Download
+- In the simulator, click the Home icon to return to the home screen
+- Click on the "Files" icon
+- Click and drag `pres.numbers` from a Finder window into the simulator.
+
+![save file iOS](pathname:///mobile/quasar7a.png)
+
+- Make sure "On My iPhone" is highlighted and select "Save"
+- Click the Home icon again then select the `SheetJSRN` app
+- Click "Import data" and select `pres`:
+
+![pick file iOS](pathname:///mobile/rnios2.png)
+
+Once selected, the screen should refresh with new contents:
+
+![read file iOS](pathname:///mobile/rnios3.png)
+
+- Click "Export data". You will see a popup with a location:
+
+![write file iOS](pathname:///mobile/rnios4.png)
+
+- Find the file and verify the contents are correct:
+
+```bash
+find ~/Library/Developer/CoreSimulator -name sheetjsw.xlsx |
+ while read x; do echo "$x"; npx xlsx-cli "$x"; done
+```
+
+Once testing is complete, stop the simulator and the development process.
+
+**Android Testing**
+
+There are no Android-specific steps. Emulator can be started with:
+
+```bash
+npx react-native run-android
+```
+
+![React Native Android App](pathname:///mobile/rnand1.png)
+
+The app can be tested with the following sequence in the simulator:
+
+- Download
+- Click and drag `pres.numbers` from a Finder window into the simulator.
+- Click "Import data" and select `pres.numbers`:
+
+![pick file Android](pathname:///mobile/rnand2.png)
+
+Once selected, the screen should refresh with new contents:
+
+![read file Android](pathname:///mobile/rnand3.png)
+
+- Click "Export data". You will see a popup with a location:
+
+![write file Android](pathname:///mobile/rnand4.png)
+
+- Pull the file from the simulator and verify the contents:
+
+```bash
+adb exec-out run-as com.sheetjsrn cat files/sheetjsw.xlsx > /tmp/sheetjsw.xlsx
+npx xlsx-cli /tmp/sheetjsw.xlsx
+```
diff --git a/docz/docs/03-demos/02-mobile/02-nativescript.md b/docz/docs/03-demos/02-mobile/02-nativescript.md
new file mode 100644
index 0000000..64c4dd9
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/02-nativescript.md
@@ -0,0 +1,350 @@
+---
+title: NativeScript
+pagination_prev: demos/salesforce
+pagination_next: demos/desktop/index
+sidebar_position: 2
+sidebar_custom_props:
+ summary: JS + Native Elements
+---
+
+The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
+from the main entrypoint or any script in the project.
+
+:::warning Binary Data issues
+
+NativeScript will not safely transmit binary or UTF-8 strings. XLSB, NUMBERS,
+XLSX, XLS, ODS, SYLK, and DBF exports are known to be mangled.
+
+This is a known NativeScript bug.
+
+This demo will focus on ASCII CSV files. Once the bug is resolved, XLSX and
+other formats will be supported.
+
+:::
+
+## Integration Details
+
+The `@nativescript/core/file-system` package provides classes for file access.
+
+Reading and writing data require a file handle. The following snippet searches
+typical document folders for a specified filename:
+
+```ts
+import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
+
+function get_handle_for_filename(filename: string): File {
+ const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
+ const url: string = path.normalize(target.path + "///" + filename);
+ return File.fromPath(url);
+}
+```
+
+The encoding `ISO_8859_1` spiritually resembles the `"binary"` SheetJS type
+
+**Reading data**
+
+`File#readText(encoding.ISO_8859_1)` returns strings compatible with `"binary"`
+
+```ts
+/* get binary string */
+const bstr: string = await file.readText(encoding.ISO_8859_1);
+
+/* read workbook */
+const wb = read(bstr, { type: "binary" });
+```
+
+**Writing data**
+
+`File#writeText` with the `ISO_8859_1` encoding accepts `"binary"` strings with
+the caveat listed in the warning at the top of this section:
+
+```ts
+/* generate binary string */
+const bstr: string = write(wb, { bookType: 'csv', type: 'binary' });
+
+/* attempt to save binary string to file */
+await file.writeText(bstr, encoding.ISO_8859_1);
+```
+
+## Demo
+
+The demo builds off of the NativeScript + Angular example. Familiarity with
+Angular and TypeScript is assumed.
+
+:::note
+
+This demo was tested on an Intel Mac on 2022 August 10. NativeScript version
+(as verified with `ns --version`) is `8.3.2`. The iOS simulator runs iOS 15.5
+on an iPhone SE 3rd generation.
+
+:::
+
+Complete Example (click to show)
+
+0) Follow the official Environment Setup instructions (tested with "MacOS + iOS")
+
+1) Create a skeleton NativeScript + Angular app:
+
+```bash
+ns create SheetJSNS --ng
+```
+
+2) Launch the app in the iOS simulator to verify that the demo built properly:
+
+```bash
+cd SheetJSNS
+ns run ios
+```
+
+(this may take a while)
+
+Once the simulator launches and the test app is displayed, end the script by
+selecting the terminal and entering the key sequence `CTRL + C`
+
+3) From the project folder, install the library:
+
+```bash
+npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
+```
+
+4) To confirm the library was loaded, change the title to show the version. The
+differences are highlighted.
+
+`src/app/item/items.component.ts` imports the version string to the component:
+
+```ts title="src/app/item/items.component.ts"
+// highlight-next-line
+import { version } from 'xlsx';
+import { Component, OnInit } from '@angular/core'
+
+import { Item } from './item'
+import { ItemService } from './item.service'
+
+@Component({
+ selector: 'ns-items',
+ templateUrl: './items.component.html',
+})
+export class ItemsComponent implements OnInit {
+ items: Array
+ // highlight-next-line
+ version = `SheetJS - ${version}`;
+
+ constructor(private itemService: ItemService) {}
+
+ ngOnInit(): void {
+ this.items = this.itemService.getItems()
+ }
+}
+```
+
+`src/app/item/items.component.html` references the version in the title:
+
+```xml title="src/app/item/items.component.html"
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Relaunch the app with `ns run ios` and the title bar should show the version.
+
+![NativeScript Step 4](pathname:///mobile/nativescript4.png)
+
+5) Add the Import and Export buttons to the template:
+
+```xml title="src/app/item/items.component.html"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```ts title="src/app/item/items.component.ts"
+// highlight-start
+import { version, utils, read, write } from 'xlsx';
+import { Dialogs } from '@nativescript/core';
+import { encoding } from '@nativescript/core/text';
+import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
+// highlight-end
+import { Component, OnInit } from '@angular/core'
+
+import { Item } from './item'
+import { ItemService } from './item.service'
+
+// highlight-start
+function get_handle_for_filename(filename: string): [File, string] {
+ const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
+ const url: string = path.normalize(target.path + "///" + filename);
+ return [File.fromPath(url), url];
+}
+// highlight-end
+
+@Component({
+ selector: 'ns-items',
+ templateUrl: './items.component.html',
+})
+export class ItemsComponent implements OnInit {
+ items: Array
+ version: string = `SheetJS - ${version}`;
+
+ constructor(private itemService: ItemService) {}
+
+ ngOnInit(): void {
+ this.items = this.itemService.getItems()
+ }
+
+ // highlight-start
+ /* Import button */
+ async import() {
+ }
+
+ /* Export button */
+ async export() {
+ }
+ // highlight-end
+}
+```
+
+Restart the app process and two buttons should show up at the top:
+
+![NativeScript Step 5](pathname:///mobile/nativescript5.png)
+
+6) Implement import and export:
+
+```ts title="src/app/item/items.component.ts"
+import { version, utils, read, write } from 'xlsx';
+import { Dialogs } from '@nativescript/core';
+import { encoding } from '@nativescript/core/text';
+import { File, Folder, knownFolders, path } from '@nativescript/core/file-system';
+import { Component, OnInit } from '@angular/core'
+
+import { Item } from './item'
+import { ItemService } from './item.service'
+
+function get_handle_for_filename(filename: string): [File, string] {
+ const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
+ const url: string = path.normalize(target.path + "///" + filename);
+ return [File.fromPath(url), url];
+}
+
+@Component({
+ selector: 'ns-items',
+ templateUrl: './items.component.html',
+})
+export class ItemsComponent implements OnInit {
+ items: Array
+ version: string = `SheetJS - ${version}`;
+
+ constructor(private itemService: ItemService) {}
+
+ ngOnInit(): void {
+ this.items = this.itemService.getItems()
+ }
+
+ /* Import button */
+ async import() {
+ // highlight-start
+ /* find appropriate path */
+ const [file, url] = get_handle_for_filename("SheetJSNS.csv");
+
+ try {
+ /* get binary string */
+ const bstr: string = await file.readText(encoding.ISO_8859_1);
+
+ /* read workbook */
+ const wb = read(bstr, { type: "binary" });
+
+ /* grab first sheet */
+ const wsname: string = wb.SheetNames[0];
+ const ws = wb.Sheets[wsname];
+
+ /* update table */
+ this.items = utils.sheet_to_json(ws);
+ Dialogs.alert(`Attempting to read to ${filename} in ${url}`);
+ } catch(e) { Dialogs.alert(e.message); }
+ // highlight-end
+ }
+
+ /* Export button */
+ async export() {
+ // highlight-start
+ /* find appropriate path */
+ const [file, url] = get_handle_for_filename("SheetJSNS.csv");
+
+ try {
+ /* create worksheet from data */
+ const ws = utils.json_to_sheet(this.items);
+
+ /* create workbook from worksheet */
+ const wb = utils.book_new();
+ utils.book_append_sheet(wb, ws, "Sheet1");
+
+ /* generate binary string */
+ const wbout: string = write(wb, { bookType: 'csv', type: 'binary' });
+
+ /* attempt to save binary string to file */
+ await file.writeText(wbout, encoding.ISO_8859_1);
+ Dialogs.alert(`Wrote to ${filename} in ${url}`);
+ } catch(e) { Dialogs.alert(e.message); }
+ // highlight-end
+ }
+}
+```
+
+Restart the app process.
+
+**Testing**
+
+The app can be tested with the following sequence in the simulator:
+
+- Hit "Export File". A dialog will print where the file was written
+
+- Open that file with a text editor. It will be a 3-column CSV:
+
+```csv
+id,name,role
+1,Ter Stegen,Goalkeeper
+3,Piqué,Defender
+4,I. Rakitic,Midfielder
+...
+```
+
+After the header row, add the line `0,SheetJS,Library`:
+
+```csv
+id,name,role
+0,SheetJS,Library
+1,Ter Stegen,Goalkeeper
+3,Piqué,Defender
+...
+```
+
+- Hit "Import File". A dialog will print the path of the file that was read.
+ The first item in the list will change:
+
+![NativeScript Step 7](pathname:///mobile/nativescript7.png)
+
+
+
diff --git a/docz/docs/03-demos/02-mobile/03-quasar.md b/docz/docs/03-demos/02-mobile/03-quasar.md
new file mode 100644
index 0000000..f9ef1fb
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/03-quasar.md
@@ -0,0 +1,381 @@
+---
+title: Quasar
+pagination_prev: demos/salesforce
+pagination_next: demos/desktop/index
+sidebar_position: 3
+sidebar_custom_props:
+ summary: VueJS + Web View
+---
+
+The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
+from the main entrypoint or any script in the project.
+
+This demo will use the Quasar ViteJS starter project with VueJS and Cordova.
+
+### Integration Details
+
+The complete solution uses `cordova-plugin-file` for file operations. It can
+be installed like any other Cordova plugin:
+
+```bash
+cd src-cordova
+cordova plugin add cordova-plugin-file
+cd ..
+```
+
+#### Reading data
+
+The `q-file` component presents an API reminiscent of File Input elements:
+
+```html
+
+```
+
+When binding to the `input` element, the callback receives an `Event` object:
+
+```ts
+import { read } from 'xlsx';
+
+// assuming `todos` is a standard VueJS `ref`
+async function updateFile(v) { try {
+ // `v.target.files[0]` is the desired file object
+ const files = (v.target as HTMLInputElement).files;
+ if(!files || files.length == 0) return;
+
+ // read first file
+ const wb = read(await files[0].arrayBuffer());
+
+ // get data of first worksheet as an array of objects
+ const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+
+ // update state
+ todos.value = data.map(row => ({id: row.Index, content: row.Name}));
+
+} catch(e) { console.log(e); } }
+```
+
+#### Writing data
+
+The API is shaped like the File and Directory Entries API. For clarity, since
+the code is a "pyramid of doom", the error handlers are omitted:
+
+```ts
+import { write } from 'xlsx';
+
+// on iOS and android, `XLSX.write` with type "buffer" returns a `Uint8Array`
+const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"});
+// Request filesystem access for persistent storage
+window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
+ // Request a handle to "SheetJSQuasar.xlsx", making a new file if necessary
+ fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => {
+ // Request a FileWriter for writing data
+ entry.createWriter(writer => {
+ // The FileWriter API needs an actual Blob
+ const data = new Blob([u8], {type: "application/vnd.ms-excel"});
+ // This callback is called if the write is successful
+ writer.onwriteend = () => {
+ // TODO: show a dialog
+ };
+ // writer.onerror will be invoked if there is an error in writing
+
+ // write the data
+ writer.write(data);
+ });
+ });
+});
+```
+
+### Demo
+
+:::note
+
+This demo was tested on an Intel Mac on 2022 August 14. Quasar version `2.7.7`.
+The iOS simulator runs iOS 15.5 on an iPhone SE 3rd generation.
+
+:::
+
+The demo draws from the ViteJS example. Familiarity with VueJS and TypeScript
+is assumed.
+
+Complete Example (click to show)
+
+0) Ensure all of the dependencies are installed. Install the CLI globally:
+
+```bash
+npm i -g @quasar/cli cordova
+```
+
+(you may need to run `sudo npm i -g` if there are permission issues)
+
+1) Create a new app:
+
+```bash
+npm init quasar
+```
+
+
+
+When prompted:
+
+- "What would you like to build?": `App with Quasar CLI`
+- "Project folder": `SheetJSQuasar`
+- "Pick Quasar version": `Quasar v2 (Vue 3 | latest and greatest)`
+- "Pick script type": `Typescript`
+- "Pick Quasar App CLI variant": `Quasar App CLI with Vite`
+- "Package name": (just press enter, it will use the default `sheetjsquasar`
+- "Project product name": `SheetJSQuasar`
+- "Project description": `SheetJS + Quasar`
+- "Author": (just press enter, it will use your git config settings)
+- "Pick a Vue component style": `Composition API`
+- "Pick your CSS preprocessor": `None`
+- "Check the features needed for your project": Deselect everything
+- "Install project dependencies": `No`
+
+2) Install dependencies:
+
+
+
+```bash
+cd SheetJSQuasar
+npm i
+npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
+```
+
+3) Set up Cordova:
+
+```bash
+quasar mode add cordova
+```
+
+When prompted, enter the app id `org.sheetjs.quasar`.
+
+It will create a new `src-cordova` folder. Continue in that folder:
+
+```bash
+cd src-cordova
+cordova platform add ios
+cordova plugin add cordova-plugin-wkwebview-engine
+cordova plugin add cordova-plugin-file
+```
+
+:::note
+
+If there is an error `Could not load API for iOS project`, it needs to be reset:
+
+```bash
+cordova platform rm ios
+cordova platform add ios
+cordova plugin add cordova-plugin-file
+```
+
+:::
+
+Return to the project directory:
+
+```bash
+cd ..
+```
+
+4) Start the development server:
+
+```bash
+quasar dev -m ios
+```
+
+:::caution
+
+If the app is blank or not refreshing, delete the app and close the simulator,
+then restart the development process.
+
+:::
+
+![Quasar Step 4](pathname:///mobile/quasar4.png)
+
+
+5) Add the Dialog plugin to `quasar.config.js`:
+
+```js title="quasar.config.js"
+ // Quasar plugins
+ // highlight-next-line
+ plugins: ['Dialog']
+```
+
+6) In the template section of `src/pages/IndexPage.vue`, add a Save button and
+ a Load file picker component at the bottom of the page:
+
+```html title="src/pages/IndexPage.vue"
+
+
+
+
+
+
+
+
+```
+
+This uses two functions that should be added to the component script:
+
+```ts title="src/pages/IndexPage.vue"
+ const meta = ref({
+ totalCount: 1200
+ });
+// highlight-start
+ function saveFile() {
+ }
+ async function updateFile(v) {
+ }
+ return { todos, meta, saveFile, updateFile };
+// highlight-end
+ }
+});
+
+```
+
+The app should now show two buttons at the bottom:
+
+![Quasar Step 6](pathname:///mobile/quasar6.png)
+
+:::caution
+
+If the app is blank or not refreshing, delete the app and close the simulator,
+then restart the development process.
+
+:::
+
+7) Wire up the `updateFile` function:
+
+```ts title="src/pages/IndexPage.vue"
+import { defineComponent, ref } from 'vue';
+// highlight-start
+import { read, write, utils } from 'xlsx';
+import { useQuasar } from 'quasar';
+// highlight-end
+
+export default defineComponent({
+// ...
+// highlight-start
+ const $q = useQuasar();
+ function dialogerr(e) { $q.dialog({title: "Error!", message: e.message || String(e)}); }
+// highlight-end
+ function saveFile() {
+ }
+ async function updateFile(v) {
+// highlight-start
+ try {
+ const files = (v.target as HTMLInputElement).files;
+ if(!files || files.length == 0) return;
+
+ const wb = read(await files[0].arrayBuffer());
+
+ const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+ todos.value = data.map(row => ({id: row.Index, content: row.Name}));
+ } catch(e) { dialogerr(e); }
+// highlight-end
+ }
+```
+
+To test that reading works:
+
+- Download
+- In the simulator, click the Home icon to return to the home screen
+- Click on the "Files" icon
+- Click and drag `pres.numbers` from a Finder window into the simulator.
+
+![Quasar Step 7 save file](pathname:///mobile/quasar7a.png)
+
+- Make sure "On My iPhone" is highlighted and select "Save"
+- Click the Home icon again then select the `SheetJSQuasar` app
+- Click the "Load" button, then select "Choose File" and select `pres`:
+
+![Quasar Step 7 load file](pathname:///mobile/quasar7b.png)
+
+Once selected, the screen should refresh with new contents:
+
+![Quasar Step 7 new data](pathname:///mobile/quasar7c.png)
+
+8) Wire up the `saveFile` function:
+
+```js
+ function saveFile() {
+// highlight-start
+ /* generate workbook from state */
+ const ws = utils.json_to_sheet(todos.value);
+ const wb = utils.book_new();
+ utils.book_append_sheet(wb, ws, "SheetJSQuasar");
+ const u8: Uint8Array = write(wb, {bookType: "xlsx", type: "buffer"});
+
+ /* save to file */
+ window.requestFileSystem(window.PERSISTENT, 0, function(fs) {
+ try {
+ fs.root.getFile("SheetJSQuasar.xlsx", {create: true}, entry => {
+ const msg = `File stored at ${$q.cordova.file.documentsDirectory} ${entry.fullPath}`;
+ entry.createWriter(writer => {
+ try {
+ const data = new Blob([u8], {type: "application/vnd.ms-excel"});
+ writer.onwriteend = () => {
+ try {
+ $q.dialog({title: "Success!", message: msg});
+ } catch(e) { dialogerr(e); }
+ };
+ writer.onerror = dialogerr;
+ writer.write(data);
+ } catch(e) { dialogerr(e); }
+ }, dialogerr);
+ }, dialogerr);
+ } catch(e) { dialogerr(e) }
+ }, dialogerr);
+// highlight-end
+ }
+```
+
+The page should revert to the old contents.
+
+To test that writing works:
+
+- Click "Save File". You will see a popup with a location:
+
+![Quasar Step 8](pathname:///mobile/quasar8.png)
+
+- Find the file and verify the contents are correct. Run in a new terminal:
+
+```bash
+find ~/Library/Developer/CoreSimulator -name SheetJSQuasar.xlsx |
+ while read x; do echo "$x"; npx xlsx-cli "$x"; done
+```
+
+Since the contents reverted, you should see
+
+```
+SheetJSQuasar
+id,content
+1,ct1
+2,ct2
+3,ct3
+4,ct4
+5,ct5
+```
+
+- Use "Load File" to select `pres.numbers` again. Wait for the app to refresh.
+
+- Click "Save File", then re-run the command:
+
+```bash
+find ~/Library/Developer/CoreSimulator -name SheetJSQuasar.xlsx |
+ while read x; do echo "$x"; npx xlsx-cli "$x"; done
+```
+
+The contents from `pres.numbers` should show up now, with a new header row:
+
+```
+SheetJSQuasar
+id,content
+42,Bill Clinton
+43,GeorgeW Bush
+44,Barack Obama
+45,Donald Trump
+46,Joseph Biden
+```
+
+
+
diff --git a/docz/docs/03-demos/02-mobile/04-ionic.md b/docz/docs/03-demos/02-mobile/04-ionic.md
new file mode 100644
index 0000000..e6e879d
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/04-ionic.md
@@ -0,0 +1,155 @@
+---
+title: Ionic
+pagination_prev: demos/salesforce
+pagination_next: demos/desktop/index
+sidebar_position: 4
+sidebar_custom_props:
+ summary: Native Components + Web View
+---
+
+The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported
+from the main entrypoint or any script in the project.
+
+:::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 Cordova apps.
+
+:::
+
+`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});
+```
+
+## Demo
+
+:::note
+
+This demo was tested on an Intel Mac on 2022 August 18 with Cordova.
+The file integration uses `@ionic-native/file` version `5.36.0`.
+
+The iOS simulator runs iOS 15.5 on an iPod Touch 7th Gen.
+
+:::
+
+Complete Example (click to show)
+
+0) Disable telemetry as noted in the warning.
+
+Install required global dependencies:
+
+```bash
+npm i -g cordova-res @angular/cli native-run
+```
+
+Follow the [React Native demo](#demo) to ensure iOS and Android sims are ready.
+
+
+1) Create a new project:
+
+```bash
+npx @ionic/cli start SheetJSIonic blank --type angular --cordova --quiet --no-git --no-link --confirm
+```
+
+If a prompt discusses Cordova and Capacitor, enter `Yes` to continue.
+
+If a prompt asks about creating an Ionic account, enter `N` to opt out.
+
+2) Set up Cordova:
+
+```bash
+npx @ionic/cli cordova platform add ios --confirm
+npx @ionic/cli cordova plugin add cordova-plugin-file
+npm install --save @ionic-native/core @ionic-native/file @ionic/cordova-builders
+```
+
+3) Install dependencies:
+
+```bash
+npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
+```
+
+4) Add `@ionic-native/file` to the module. Differences highlighted below:
+
+```ts title="src/app/app.module.ts"
+import { AppComponent } from './app.component';
+import { AppRoutingModule } from './app-routing.module';
+
+// highlight-next-line
+import { File } from '@ionic-native/file/ngx';
+
+@NgModule({
+ declarations: [AppComponent],
+ imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
+
+ // highlight-next-line
+ providers: [File, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
+ bootstrap: [AppComponent],
+})
+export class AppModule {}
+```
+
+5) Download [`home.page.ts`](pathname:///ionic/home.page.ts) and replace:
+
+```bash
+curl -o src/app/home/home.page.ts -L https://docs.sheetjs.com/ionic/home.page.ts
+```
+
+6) Test the app:
+
+```bash
+npx @ionic/cli cordova emulate ios
+```
+
+
+
diff --git a/docz/docs/03-demos/02-mobile/05-capacitor.md b/docz/docs/03-demos/02-mobile/05-capacitor.md
new file mode 100644
index 0000000..fd7a82c
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/05-capacitor.md
@@ -0,0 +1,192 @@
+---
+title: CapacitorJS
+pagination_prev: demos/salesforce
+pagination_next: demos/desktop/index
+sidebar_position: 5
+sidebar_custom_props:
+ summary: JS + Web View
+---
+
+## CapacitorJS
+
+:::note
+
+This demo was tested on an Intel Mac on 2022 August 26 with Svelte.
+
+The iOS simulator runs iOS 15.5 on an iPhone 13 Pro Max.
+
+:::
+
+:::warning Telemetry
+
+Before starting this demo, manually disable telemetry. On Linux and MacOS:
+
+```bash
+npx @capacitor/cli telemetry off
+```
+
+To verify telemetry was disabled:
+
+```bash
+npx @capacitor/cli telemetry
+```
+
+:::
+
+### Integration Details
+
+This example uses Svelte, but the same principles apply to other frameworks.
+
+#### Reading data
+
+The standard HTML5 File Input element logic works in CapacitorJS:
+
+```html
+
+
+
+
+
+
{@html html}
+
+```
+
+#### Writing data
+
+`@capacitor/filesystem` can write Base64 strings:
+
+```html
+
+
+
+
+
{@html html}
+
+```
+
+### Demo
+
+Complete Example (click to show)
+
+0) Disable telemetry as noted in the warning.
+
+Follow the [React Native demo](#demo) to ensure iOS and Android sims are ready.
+
+
+1) Create a new Svelte project:
+
+```bash
+npm create vite@latest sheetjs-cap -- --template svelte
+cd sheetjs-cap
+```
+
+2) Install dependencies:
+
+```bash
+npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
+npm i --save @capacitor/core @capacitor/cli @capacitor/ios @capacitor/filesystem
+```
+
+3) Create CapacitorJS structure:
+
+```bash
+npx cap init sheetjs-cap com.sheetjs.cap --web-dir=dist
+npx cap add ios
+```
+
+4) Replace the contents of `src/App.svelte` with the following:
+
+```html title="src/App.svelte"
+
+
+
+
SheetJS × CapacitorJS { version }
+
+
+
{@html html}
+
+```
+
+5) Test the app:
+
+```bash
+npm run build; npx cap sync; npx cap run ios
+```
+
+There are 3 steps: build the Svelte app, sync with CapacitorJS, and run sim.
+This sequence must be run every time to ensure changes are propagated.
+
+
diff --git a/docz/docs/03-demos/02-mobile/_category_.json b/docz/docs/03-demos/02-mobile/_category_.json
new file mode 100644
index 0000000..4fbae02
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/_category_.json
@@ -0,0 +1,4 @@
+{
+ "label": "iOS and Android Apps",
+ "position": 3
+}
\ No newline at end of file
diff --git a/docz/docs/03-demos/02-mobile/index.md b/docz/docs/03-demos/02-mobile/index.md
new file mode 100644
index 0000000..cef0475
--- /dev/null
+++ b/docz/docs/03-demos/02-mobile/index.md
@@ -0,0 +1,62 @@
+---
+title: iOS and Android Apps
+pagination_prev: demos/salesforce
+pagination_next: demos/desktop/index
+---
+
+import DocCardList from '@theme/DocCardList';
+import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
+
+Many mobile app frameworks mix JavaScript / CSS / HTML5 concepts with native
+extensions and libraries to create a hybrid development experience. Developers
+well-versed in web technologies can now build actual mobile applications that
+run on iOS and Android!
+
+:::warning
+
+**The ecosystem has broken backwards-compatibility many times!**
+
+iOS and Android, as well as the underlying JavaScript frameworks, make breaking
+changes regularly. The demos were tested against emulators / real devices at
+some point in time. A framework or OS change can render the demos inoperable.
+
+MacOS is required for the iOS demos. The Android demos were tested on MacOS.
+
+:::
+
+The ["JavaScript Engines"](/docs/demos/engines) section includes samples for JS
+engines used in the mobile app frameworks. SheetJS libraries have been tested
+in the relevant engines and should "just work" with some caveats.
+
+Demos for common tools are included in separate pages. Each demo section will
+mention test dates and platform versions.
+
+
+
+:::note Recommendation
+
+React Native is extremely popular and is the recommended choice for greenfield
+projects that can use community modules. However, its "lean core" approach
+forces developers to learn iOS/Android programming or use community modules to
+provide basic app features.
+
+The original Web View framework was PhoneGap/Cordova. The modern frameworks
+are built atop Cordova. Cordova is waning in popularity but it has a deep
+library of community modules to solve many problems.
+
+Before creating a new app, it is important to identify what features the app
+should support and investigate community modules. If there are popular modules
+for features that must be included, or for teams that are comfortable with
+native app development, React Native is the obvious choice.
+
+NativeScript is not recommended at this time. There are known bugs related to
+binary data processing. The demo only supports plaintext file formats like CSV.
+
+:::
diff --git a/docz/docs/03-demos/03-desktop/01-electron.md b/docz/docs/03-demos/03-desktop/01-electron.md
index fd74a51..a6c1a7d 100644
--- a/docz/docs/03-demos/03-desktop/01-electron.md
+++ b/docz/docs/03-demos/03-desktop/01-electron.md
@@ -1,6 +1,6 @@
---
title: Electron
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
sidebar_position: 1
sidebar_custom_props:
diff --git a/docz/docs/03-demos/03-desktop/02-nwjs.md b/docz/docs/03-demos/03-desktop/02-nwjs.md
index ff143ae..5b1fa5b 100644
--- a/docz/docs/03-demos/03-desktop/02-nwjs.md
+++ b/docz/docs/03-demos/03-desktop/02-nwjs.md
@@ -1,6 +1,6 @@
---
title: NW.js
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
sidebar_position: 2
sidebar_custom_props:
diff --git a/docz/docs/03-demos/03-desktop/03-wails.md b/docz/docs/03-demos/03-desktop/03-wails.md
index c15dd98..b6db890 100644
--- a/docz/docs/03-demos/03-desktop/03-wails.md
+++ b/docz/docs/03-demos/03-desktop/03-wails.md
@@ -1,6 +1,6 @@
---
title: Wails
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
sidebar_position: 3
sidebar_custom_props:
diff --git a/docz/docs/03-demos/03-desktop/04-tauri.md b/docz/docs/03-demos/03-desktop/04-tauri.md
index 4abbedb..59524b9 100644
--- a/docz/docs/03-demos/03-desktop/04-tauri.md
+++ b/docz/docs/03-demos/03-desktop/04-tauri.md
@@ -1,6 +1,6 @@
---
title: Tauri
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
sidebar_position: 4
sidebar_custom_props:
diff --git a/docz/docs/03-demos/03-desktop/05-neutralino.md b/docz/docs/03-demos/03-desktop/05-neutralino.md
index 9fc8fd1..4f9568c 100644
--- a/docz/docs/03-demos/03-desktop/05-neutralino.md
+++ b/docz/docs/03-demos/03-desktop/05-neutralino.md
@@ -1,6 +1,6 @@
---
title: NeutralinoJS
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
sidebar_position: 5
sidebar_custom_props:
diff --git a/docz/docs/03-demos/03-desktop/06-reactnative.md b/docz/docs/03-demos/03-desktop/06-reactnative.md
index 10c107f..cf76f7c 100644
--- a/docz/docs/03-demos/03-desktop/06-reactnative.md
+++ b/docz/docs/03-demos/03-desktop/06-reactnative.md
@@ -1,6 +1,6 @@
---
title: React Native for Desktop
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
sidebar_position: 6
sidebar_custom_props:
@@ -13,7 +13,7 @@ import TabItem from '@theme/TabItem';
:::note
This section covers React Native for desktop applications. For iOS and Android
-applications, [check the mobile demo](/docs/demos/mobile)
+applications, [check the mobile demo](/docs/demos/mobile/reactnative)
:::
diff --git a/docz/docs/03-demos/03-desktop/index.md b/docz/docs/03-demos/03-desktop/index.md
index 54007b1..ec9e6be 100644
--- a/docz/docs/03-demos/03-desktop/index.md
+++ b/docz/docs/03-demos/03-desktop/index.md
@@ -1,6 +1,6 @@
---
title: Desktop Applications
-pagination_prev: demos/mobile
+pagination_prev: demos/mobile/index
pagination_next: demos/grid
---
diff --git a/docz/docs/03-demos/11-angular.md b/docz/docs/03-demos/11-angular.md
index c4ee60a..7fd5275 100644
--- a/docz/docs/03-demos/11-angular.md
+++ b/docz/docs/03-demos/11-angular.md
@@ -14,8 +14,8 @@ and TypeScript familiarity is assumed.
Other demos cover general Angular deployments, including:
-- [iOS and Android applications powered by NativeScript](/docs/demos/mobile#nativescript)
-- [iOS and Android applications powered by Ionic](/docs/demos/mobile#ionic)
+- [iOS and Android applications powered by NativeScript](/docs/demos/mobile/nativescript)
+- [iOS and Android applications powered by Ionic](/docs/demos/mobile/ionic)
:::warning
diff --git a/docz/docs/03-demos/12-react.md b/docz/docs/03-demos/12-react.md
index 0293b54..e401ca8 100644
--- a/docz/docs/03-demos/12-react.md
+++ b/docz/docs/03-demos/12-react.md
@@ -10,8 +10,8 @@ familiarity is assumed.
Other demos cover general React deployments, including:
- [Static Site Generation powered by NextJS](/docs/demos/content#nextjs)
-- [iOS and Android applications powered by React Native](/docs/demos/mobile#react-native)
-- [Desktop application powered by React-Native-Windows](/docs/demos/desktop#react-native-windows)
+- [iOS and Android applications powered by React Native](/docs/demos/mobile/reactnative)
+- [Desktop application powered by React Native Windows + macOS](/docs/demos/desktop/reactnative)
- [React Data Grid UI component](/docs/demos/grid#react-data-grid)
diff --git a/docz/docs/03-demos/13-vue.md b/docz/docs/03-demos/13-vue.md
index 649deb9..9bc6928 100644
--- a/docz/docs/03-demos/13-vue.md
+++ b/docz/docs/03-demos/13-vue.md
@@ -10,8 +10,8 @@ Components (SFC) and VueJS familiarity is assumed.
Other demos cover general VueJS deployments, including:
- [Static Site Generation powered by NuxtJS](/docs/demos/content#nuxtjs)
-- [iOS and Android applications powered by Quasar](/docs/demos/mobile#quasar)
-- [Desktop application powered by Tauri](/docs/demos/desktop#tauri)
+- [iOS and Android applications powered by Quasar](/docs/demos/mobile/quasar)
+- [Desktop application powered by Tauri](/docs/demos/desktop/tauri)
- [`vue3-table-lite` UI component](/docs/demos/grid#vue3-table-lite)
diff --git a/docz/docs/03-demos/14-svelte.md b/docz/docs/03-demos/14-svelte.md
index 155bc8e..2e7c01d 100644
--- a/docz/docs/03-demos/14-svelte.md
+++ b/docz/docs/03-demos/14-svelte.md
@@ -9,8 +9,8 @@ familiarity is assumed.
Other demos cover general Svelte deployments, including:
-- [iOS applications powered by CapacitorJS](/docs/demos/mobile#capacitorjs)
-- [Desktop application powered by Wails](/docs/demos/desktop#wails)
+- [iOS applications powered by CapacitorJS](/docs/demos/mobile/capacitor)
+- [Desktop application powered by Wails](/docs/demos/desktop/wails)
## Installation
diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md
index 18139f2..373df48 100644
--- a/docz/docs/03-demos/index.md
+++ b/docz/docs/03-demos/index.md
@@ -38,6 +38,14 @@ run in the web browser, demos will include interactive examples.
- [`angular-ui-grid`](/docs/demos/grid#angular-ui-grid)
- [`material ui`](/docs/demos/grid#material-ui-table)
+### iOS and Android Mobile Apps
+
+- [`React Native`](/docs/demos/mobile/reactnative)
+- [`NativeScript`](/docs/demos/mobile/nativescript)
+- [`Quasar`](/docs/demos/mobile/quasar)
+- [`Ionic`](/docs/demos/mobile/ionic)
+- [`CapacitorJS`](/docs/demos/mobile/capacitor)
+
### Desktop App Frameworks
- [`Electron`](/docs/demos/desktop/electron)
diff --git a/docz/static/electron/index.html b/docz/static/electron/index.html
index e7c091a..f20cfe4 100644
--- a/docz/static/electron/index.html
+++ b/docz/static/electron/index.html
@@ -5,6 +5,7 @@
+
SheetJS Electron Demo