diff --git a/docz/docs/03-demos/01-frontend/01-react.md b/docz/docs/03-demos/01-frontend/01-react.md
index c08ed23..3d9ee2a 100644
--- a/docz/docs/03-demos/01-frontend/01-react.md
+++ b/docz/docs/03-demos/01-frontend/01-react.md
@@ -16,6 +16,7 @@ Other demos cover general React deployments, including:
- [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)
+- [Glide Data Grid UI component](/docs/demos/grid#glide-data-grid)
## Installation
diff --git a/docz/docs/03-demos/05-grid.md b/docz/docs/03-demos/05-grid.md
index cad4f43..cec0615 100644
--- a/docz/docs/03-demos/05-grid.md
+++ b/docz/docs/03-demos/05-grid.md
@@ -331,6 +331,16 @@ function ws_to_rdg(ws: WorkSheet): RowCol {
In the other direction, a worksheet can be generated with `aoa_to_sheet`:
+```ts
+import { WorkSheet, utils } from 'xlsx';
+
+type Row = any[];
+
+function rdg_to_ws(rows: Row[]): WorkSheet {
+ return utils.aoa_to_sheet(rows);
+}
+```
+
:::caution
When the demo was last refreshed, row array objects were preserved. This was
@@ -338,8 +348,6 @@ not the case in a later release. The row arrays must be re-created.
The snippet defines a `arrayify` function that creates arrays if necessary.
-:::
-
```ts
import { WorkSheet, utils } from 'xlsx';
@@ -360,14 +368,311 @@ function rdg_to_ws(rows: Row[]): WorkSheet {
}
```
-```ts
-import { WorkSheet, utils } from 'xlsx';
+:::
-type Row = any[];
-function rdg_to_ws(rows: Row[]): WorkSheet {
- return utils.aoa_to_sheet(rows);
+### Glide Data Grid
+
+:::note
+
+This demo was last tested on 2023 February 07 with the ViteJS+React+TypeScript
+starter (Vite `4.1.1`, React `18.2.0`) and `@glideapps/glide-data-grid@5.2.1`.
+
+:::
+
+#### GDG Demo
+
+Complete Example (click to show)
+
+1) Create a new project:
+
+```bash
+npm create vite@latest -- sheetjs-gdg --template react-ts
+cd sheetjs-gdg
+npm i
+```
+
+Install SheetJS and Glide Data Grid required dependencies:
+
+```bash
+npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
+npm i --save @glideapps/glide-data-grid lodash marked
+```
+
+Start dev server:
+
+```bash
+npm run dev
+```
+
+The terminal window will display a URL (typically `http://localhost:5173`).
+Open the URL with a web browser.
+
+2) Download [`App.tsx`](pathname:///gdg/App.tsx) and replace `src/App.tsx`:
+
+```bash
+curl -L -o src/App.tsx https://docs.sheetjs.com/gdg/App.tsx
+```
+
+Refresh the browser window and a grid should be displayed:
+
+![glide-data-grid initial view](pathname:///gdg/pre.png)
+
+3) To test the export functionality, make some changes to the grid data.
+
+Suppose you believe that President Grover Cleveland should be counted once.
+That would imply President Clinton should be index 41 and the indices of the
+other presidents should be decremented. By double-clicking on each cell in the
+Index column, a cell editor should appear. Decrement each index:
+
+![glide-data-grid after edits](pathname:///gdg/post.png)
+
+Click on the "Export" button to create a file! Open the file and verify.
+
+
+
+#### Backing Store
+
+Under the hood, the `DataEditor` component is designed to call methods and
+request data to display in the grid. It is typical to store data *outside* of
+component state. A `getCellContent` callback will pull data from the external
+backing store, while SheetJS operations will directly act on the store:
+
+```js
+// !! THESE OBJECTS ARE DEFINED OUTSIDE OF THE COMPONENT FUNCTION !!
+
+// this will store the raw data objects
+let data: any[] = [];
+// this will store the header names
+let header: string[] = [];
+```
+
+#### GDG Props
+
+:::note
+
+This is a high-level overview. The official documentation should be consulted.
+
+:::
+
+_Columns_
+
+`DataEditor` expects column metadata to be passed through a `columns` prop. This
+should be managed in the component state:
+
+```js
+import { useState } from 'react';
+import { DataEditor, GridColumn } from '@glideapps/glide-data-grid';
+
+function App() {
+ // highlight-next-line
+ const [cols, setCols] = useState([]); // gdg column objects
+ // ...
+ return ( <>
+ // ...
+
+ // ...
+ > );
}
+export default App;
+```
+
+Each `GridColumn` object expects a `title` representing the display name and an
+`id` representing the key to index within the data object.
+
+_Data_
+
+The `DataEditor` component expects a `getCellContent` callback for supplying
+data. The callback accepts column and row indices. The column index should be
+used to find the header key:
+
+```js
+import { useCallback } from 'react';
+import { DataEditor, GridCellKind, GridCell, Item } from '@glideapps/glide-data-grid';
+
+// ...
+
+function App() {
+ // ...
+ // backing data store -> gdg
+ // highlight-start
+ const getContent = useCallback((cell: Item): GridCell => {
+ const [col, row] = cell;
+ return {
+ kind: GridCellKind.Text,
+ // header[col] is the name of the field
+ displayData: String(data[row]?.[header[col]]??""),
+ data: data[row]?.[header[col]],
+ };
+ }, []);
+ // highlight-end
+ // ...
+ return ( <>
+ // ...
+
+ // ...
+ > );
+}
+```
+
+_Row Count_
+
+`DataEditor` also accepts a `rows` property indicating the number of rows. This
+is best managed in state:
+
+```js
+import { useState } from 'react';
+import { DataEditor } from '@glideapps/glide-data-grid';
+
+function App() {
+ // highlight-next-line
+ const [rows, setRows] = useState(0); // number of rows
+ // ...
+ return ( <>
+ // ...
+
+ // ...
+ > );
+}
+export default App;
+```
+
+_Editing Data_
+
+The demo uses the `onCellEdited` callback to write back to the data store.
+
+#### Parsing Data
+
+_SheetJS to Data Store_
+
+The raw data objects are readily generated with `sheet_to_json`. The headers
+can be pulled by extracting the first row of the worksheet:
+
+```js
+import { utils, WorkBook } from 'xlsx';
+
+// ...
+
+const update_backing_store = (wb: WorkBook) => {
+ // get first worksheet
+ const sheet = wb.Sheets[wb.SheetNames[0]];
+
+ // set data
+ // highlight-next-line
+ data = utils.sheet_to_json(sheet);
+
+ // create a range consisting of the first row
+ const range = utils.decode_range(sheet["!ref"]??"A1"); // original range
+ range.e.r = range.s.r; // set ending row to starting row (select first row)
+
+ // pull headers
+ // highlight-next-line
+ header = utils.sheet_to_json(sheet, {header: 1, range})[0];
+};
+
+// ...
+```
+
+_Data Store to GDG_
+
+Scheduling a refresh for the `DataEditor` involves updating the grid column
+metadata and row count through the standard state. It also requires a special
+`updateCells` call to instruct the grid to mark the cached data as stale:
+
+```js
+import { useRef } from 'react'
+import { WorkBook } from 'xlsx'
+import { DataEditor, GridColumn, Item, DataEditorRef } from '@glideapps/glide-data-grid'
+
+function App() {
+ const ref = useRef(null); // gdg ref
+ // ...
+ const parse_wb = (wb: WorkBook) => {
+ update_backing_store(wb);
+
+ // highlight-start
+ // update column metadata by pulling from external header keys
+ setCols(header.map(h => ({title: h, id: h} as GridColumn)));
+
+ // update number of rows
+ setRows(data.length);
+
+ if(data.length > 0) {
+ // create an array of the cells that must be updated
+ let cells = data.map(
+ (_,R) => Array.from({length:header.length}, (_,C) => ({cell: ([C,R] as Item)}))
+ ).flat();
+ // initiate update using the `ref` attached to the DataEditor
+ ref.current?.updateCells(cells)
+ }
+ // highlight-end
+ };
+ // ...
+ return ( <>
+ // ...
+
+ // ...
+ > );
+}
+export default App;
+```
+
+#### Writing Data
+
+`json_to_sheet` works directly on the `data` array:
+
+```js
+const ws = utils.json_to_sheet(data); // easy :)
+```
+
+Since the editor can change the header titles, it is strongly recommended to
+pull column data from the state and rewrite the header row:
+
+```js
+import { utils, writeFileXLSX } from 'xlsx';
+
+function App() {
+ // ...
+ const exportXLSX = useCallback(() => {
+ // highlight-start
+ // generate worksheet using data with the order specified in the columns array
+ const ws = utils.json_to_sheet(data, {header: cols.map(c => c.id ?? c.title)});
+
+ // rewrite header row with titles
+ utils.sheet_add_aoa(ws, [cols.map(c => c.title ?? c.id)], {origin: "A1"});
+ // highlight-end
+
+ // create workbook
+ const wb = utils.book_new();
+ utils.book_append_sheet(wb, ws, "Export"); // replace with sheet name
+ // download file
+ writeFileXLSX(wb, "sheetjs-gdg.xlsx");
+ }, []);
+ // ...
+ return ( <>
+ // ...
+ // highlight-next-line
+
+ // ...
+ > );
+}
+export default App;
```
### Material UI Data Grid
diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md
index 564e693..32c0a04 100644
--- a/docz/docs/03-demos/index.md
+++ b/docz/docs/03-demos/index.md
@@ -34,6 +34,7 @@ run in the web browser, demos will include interactive examples.
- [`canvas-datagrid`](/docs/demos/grid#canvas-datagrid)
- [`x-spreadsheet`](/docs/demos/grid#x-spreadsheet)
- [`react-data-grid`](/docs/demos/grid#react-data-grid)
+- [`glide-data-grid`](/docs/demos/grid#glide-data-grid)
- [`vue3-table-lite`](/docs/demos/grid#vue3-table-lite)
- [`angular-ui-grid`](/docs/demos/grid#angular-ui-grid)
- [`material ui`](/docs/demos/grid#material-ui-table)
diff --git a/docz/static/gdg/App.tsx b/docz/static/gdg/App.tsx
new file mode 100644
index 0000000..9ad32ce
--- /dev/null
+++ b/docz/static/gdg/App.tsx
@@ -0,0 +1,82 @@
+import { useState, useCallback, useEffect, useRef, ChangeEvent } from 'react';
+import { utils, read, writeFileXLSX, WorkBook } from 'xlsx';
+import { DataEditor, GridCellKind, GridCell, GridColumn, Item, DataEditorRef, EditableGridCell } from '@glideapps/glide-data-grid';
+import "@glideapps/glide-data-grid/dist/index.css";
+
+// this will store the raw data objects
+let data: any[] = [];
+// this will store the header names
+let header: string[] = [];
+
+function App() {
+ const [cols, setCols] = useState([]); // gdg column objects
+ const [rows, setRows] = useState(0); // number of rows
+ const ref = useRef(null); // gdg ref
+
+ // read/write between gdg and the backing data store
+ const getContent = useCallback((cell: Item): GridCell => {
+ const [col, row] = cell;
+ return {
+ kind: GridCellKind.Text,
+ allowOverlay: true,
+ readonly: false,
+ displayData: String(data[row]?.[header[col]]??""),
+ data: data[row]?.[header[col]],
+ };
+ }, []);
+ const onCellEdited = useCallback((cell: Item, newValue: EditableGridCell) => {
+ const [ col, row ] = cell;
+ data[row][header[col]] = newValue.data;
+ }, []);
+
+ // update the data store from a workbook object
+ const parse_wb = (wb: WorkBook) => {
+ const sheet = wb.Sheets[wb.SheetNames[0]];
+ data = utils.sheet_to_json(sheet);
+ const range = utils.decode_range(sheet["!ref"]??"A1"); range.e.r = range.s.r;
+ header = utils.sheet_to_json(sheet, {header: 1, range})[0];
+ setCols(header.map(h => ({title: h, id: h} as GridColumn)));
+ setRows(data.length);
+ if(data.length > 0) {
+ let cells = data.map(
+ (_,R) => Array.from({length:header.length}, (_,C) => ({cell: ([C,R] as Item)}))
+ ).flat();
+ ref.current?.updateCells(cells);
+ }
+ };
+ // file input element onchange event handler
+ const onChange = useCallback(async (e: ChangeEvent) => {
+ if(!e.target?.files) return;
+ parse_wb(read(await e.target.files[0].arrayBuffer()));
+ }, []);
+ // when the component loads, fetch and display a sample workbook
+ useEffect(() => {
+ (async() => {
+ parse_wb(read(await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer()));
+ })();
+ }, []);
+
+ // export data
+ const exportXLSX = useCallback(() => {
+ // generate worksheet using data with the order specified in the columns array
+ const ws = utils.json_to_sheet(data, {header: cols.map(c => c.id ?? c.title)});
+ // rewrite header row with titles
+ utils.sheet_add_aoa(ws, [cols.map(c => c.title ?? c.id)], {origin: "A1"});
+ // create workbook
+ const wb = utils.book_new();
+ utils.book_append_sheet(wb, ws, "Export"); // replace with sheet name
+ // download file
+ writeFileXLSX(wb, "sheetjs-gdg.xlsx");
+ }, []);
+
+ return ( <>
+
+
+
+
+
+
+ >
+ )
+}
+export default App;
diff --git a/docz/static/gdg/post.png b/docz/static/gdg/post.png
new file mode 100644
index 0000000..be66ae8
Binary files /dev/null and b/docz/static/gdg/post.png differ
diff --git a/docz/static/gdg/pre.png b/docz/static/gdg/pre.png
new file mode 100644
index 0000000..fb399c2
Binary files /dev/null and b/docz/static/gdg/pre.png differ