@sheetpane/grid 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +429 -2
- package/custom-elements.json +1246 -0
- package/dist/errors/element.d.ts +108 -0
- package/dist/errors/element.d.ts.map +1 -0
- package/dist/errors/element.js +456 -0
- package/dist/errors/element.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/excel/element.d.ts +434 -0
- package/dist/excel/element.d.ts.map +1 -0
- package/dist/excel/element.js +3382 -0
- package/dist/excel/element.js.map +1 -0
- package/dist/excel/geometry.d.ts +81 -0
- package/dist/excel/geometry.d.ts.map +1 -0
- package/dist/excel/geometry.js +137 -0
- package/dist/excel/geometry.js.map +1 -0
- package/dist/excel/index.d.ts +2 -0
- package/dist/excel/index.d.ts.map +1 -0
- package/dist/excel/index.js +3 -0
- package/dist/excel/index.js.map +1 -0
- package/dist/excel/selection.d.ts +103 -0
- package/dist/excel/selection.d.ts.map +1 -0
- package/dist/excel/selection.js +189 -0
- package/dist/excel/selection.js.map +1 -0
- package/dist/excel/styles.d.ts +3 -0
- package/dist/excel/styles.d.ts.map +1 -0
- package/dist/excel/styles.js +206 -0
- package/dist/excel/styles.js.map +1 -0
- package/dist/frameworks/elements.d.ts +136 -0
- package/dist/frameworks/jsx.d.ts +64 -0
- package/dist/frameworks/jsx.js +2 -0
- package/dist/frameworks/solid.d.ts +133 -0
- package/dist/frameworks/solid.js +2 -0
- package/dist/frameworks/svelte.d.ts +102 -0
- package/dist/frameworks/svelte.js +2 -0
- package/dist/frameworks/vue.d.ts +69 -0
- package/dist/frameworks/vue.js +2 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/llms-full.txt +8028 -0
- package/dist/llms.txt +111 -0
- package/dist/pane/index.d.ts +2 -0
- package/dist/pane/index.d.ts.map +1 -0
- package/dist/pane/index.js +3 -0
- package/dist/pane/index.js.map +1 -0
- package/dist/pane/registry.d.ts +33 -0
- package/dist/pane/registry.d.ts.map +1 -0
- package/dist/pane/registry.js +73 -0
- package/dist/pane/registry.js.map +1 -0
- package/dist/pane/rules.d.ts +231 -0
- package/dist/pane/rules.d.ts.map +1 -0
- package/dist/pane/rules.js +792 -0
- package/dist/pane/rules.js.map +1 -0
- package/dist/pane/store.d.ts +385 -0
- package/dist/pane/store.d.ts.map +1 -0
- package/dist/pane/store.js +1028 -0
- package/dist/pane/store.js.map +1 -0
- package/dist/pane/types.d.ts +130 -0
- package/dist/pane/types.d.ts.map +1 -0
- package/dist/pane/types.js +22 -0
- package/dist/pane/types.js.map +1 -0
- package/dist/plugins/host.d.ts +56 -0
- package/dist/plugins/host.d.ts.map +1 -0
- package/dist/plugins/host.js +124 -0
- package/dist/plugins/host.js.map +1 -0
- package/dist/plugins/types.d.ts +159 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +12 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/rules.schema.json +52 -0
- package/dist/shared/dom.d.ts +23 -0
- package/dist/shared/dom.d.ts.map +1 -0
- package/dist/shared/dom.js +32 -0
- package/dist/shared/dom.js.map +1 -0
- package/dist/skills-bin.d.ts +3 -0
- package/dist/skills-bin.d.ts.map +1 -0
- package/dist/skills-bin.js +9 -0
- package/dist/skills-bin.js.map +1 -0
- package/dist/style.css +3 -0
- package/dist/tokens.d.ts +49 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +43 -0
- package/dist/tokens.js.map +1 -0
- package/dist/vscode.css-custom-data.json +365 -0
- package/dist/vscode.html-custom-data.json +218 -0
- package/dist/web-types.json +914 -0
- package/package.json +72 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arpit Arya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,430 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sheetpane/grid
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**View and edit `.xlsx` and `.csv` files inside any web page, and review them.**
|
|
4
|
+
Two web components, always in sync through one in-memory workbook:
|
|
5
|
+
|
|
6
|
+
- `<sheetpane-excel>` — the sheet. View or Edit; every change stays in memory;
|
|
7
|
+
**Download** hands back the file — as opened when nothing changed, else a
|
|
8
|
+
copy of the uploaded file with the changes in it.
|
|
9
|
+
- `<sheetpane-errors>` — the errors flagged on the sheet: what's wrong, what it
|
|
10
|
+
should be. Put it anywhere — beside the sheet, in a side column, in another tab.
|
|
11
|
+
|
|
12
|
+
Nothing is uploaded: the file is read in the browser tab. No framework inside
|
|
13
|
+
or outside; no font of its own — yours is used.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm add @sheetpane/grid # or npm / yarn
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Try it first in the [live playground](https://arpitarya.github.io/sheetpane/) or
|
|
20
|
+
open a [starter in StackBlitz](https://github.com/arpitarya/sheetpane/blob/main/examples/starters/README.md)
|
|
21
|
+
(from the first published release).
|
|
22
|
+
|
|
23
|
+
## Plain HTML
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script type="module">import '@sheetpane/grid';</script>
|
|
27
|
+
|
|
28
|
+
<sheetpane-excel pane="q3" src="/files/holdings.xlsx" mode="edit" author="Arpit" style="height:640px"></sheetpane-excel>
|
|
29
|
+
<sheetpane-errors pane="q3" author="Arpit" style="height:640px"></sheetpane-errors>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The same `pane` id links them. Mount order does not matter, and they may sit
|
|
33
|
+
in different containers, tabs or dialogs.
|
|
34
|
+
|
|
35
|
+
## React 19
|
|
36
|
+
|
|
37
|
+
React 19 sets properties and listens to custom events on custom elements
|
|
38
|
+
natively:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import '@sheetpane/grid';
|
|
42
|
+
|
|
43
|
+
export function Review({ file }: { file: File }) {
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<sheetpane-excel pane="q3" mode="edit" file={file} onsheetpane-change={(e) => console.log(e.detail)} />
|
|
47
|
+
<sheetpane-errors pane="q3" onsheetpane-errors={(e) => save(e.detail.errors)} />
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Typed JSX:** add `import type {} from '@sheetpane/grid/jsx';` once — e.g. in
|
|
54
|
+
a `sheetpane.d.ts` beside your sources — and both tags, their attributes,
|
|
55
|
+
properties and `onsheetpane-…` props are typed, payloads included
|
|
56
|
+
(`e.detail.ref`). See [Typings and editor support](#typings-and-editor-support).
|
|
57
|
+
|
|
58
|
+
**React 18** sets attributes only: use a `ref` for properties and events.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
const ref = useRef<HTMLElement & { file?: File }>(null);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const el = ref.current!;
|
|
64
|
+
el.file = file;
|
|
65
|
+
const on = (e: Event) => save((e as CustomEvent).detail.errors);
|
|
66
|
+
el.addEventListener('sheetpane-errors', on);
|
|
67
|
+
return () => el.removeEventListener('sheetpane-errors', on);
|
|
68
|
+
}, [file]);
|
|
69
|
+
return <sheetpane-excel ref={ref} pane="q3" />;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### React hooks (recipes)
|
|
73
|
+
|
|
74
|
+
Not a package (SR-LAW-4: no framework wrappers in v1) — copy
|
|
75
|
+
[`examples/react/src/sheetpane-hooks.ts`](../../examples/react/src/sheetpane-hooks.ts)
|
|
76
|
+
into your app. Each hook takes a `ref` to `<sheetpane-excel>`, is built on
|
|
77
|
+
`useSyncExternalStore` with a stable selector (a component re-renders only
|
|
78
|
+
when what it reads changes), removes its listeners on unmount, and is safe
|
|
79
|
+
under StrictMode. React 19 and 18.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
const sheet = useRef<SheetpaneExcel>(null);
|
|
83
|
+
const sel = useSelection(sheet); // { sheet, ref, range, column: { index, letter, header }, value, text }
|
|
84
|
+
const edit = useCellEdit(sheet); // { editing, ref, column, draft } while the user types
|
|
85
|
+
const { count, canUndo } = useChanges(sheet);
|
|
86
|
+
const { errors, open } = useErrors(sheet);
|
|
87
|
+
const wb = useWorkbook(sheet); // the pane's store
|
|
88
|
+
const rules = useColumnRules(sheet, { Age: { type: 'integer', min: 18 } }); // { checking, done, total }
|
|
89
|
+
useSheetpaneEvent(sheet, 'sheetpane-beforechange', (e) => { if (Number(e.detail.now) < 0) e.preventDefault(); });
|
|
90
|
+
return <sheetpane-excel ref={sheet} pane="r1" mode="edit" src="people.xlsx" />;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The primitive under all of them, for a hook of your own:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const units = useSheetpane(sheet, (store) => store.value('Holdings', 'F3'), null);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Vue, Svelte, Solid, Angular
|
|
100
|
+
|
|
101
|
+
The elements are standard custom elements, so every framework renders them
|
|
102
|
+
once `import '@sheetpane/grid'` has run in the browser. No wrapper package;
|
|
103
|
+
an `import type` line types them.
|
|
104
|
+
|
|
105
|
+
**Vue 3** — tell the compiler the tags are custom elements, then bind
|
|
106
|
+
properties with `.prop` and listen with `@`:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
// vite.config.js
|
|
110
|
+
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('sheetpane-') } } })
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```vue
|
|
114
|
+
<script setup lang="ts">
|
|
115
|
+
import '@sheetpane/grid';
|
|
116
|
+
import type {} from '@sheetpane/grid/vue'; // GlobalComponents: props and @sheetpane-… typed
|
|
117
|
+
</script>
|
|
118
|
+
<template>
|
|
119
|
+
<sheetpane-excel pane="q3" mode="edit" :file.prop="file" @sheetpane-change="(e) => log(e.detail.ref)" />
|
|
120
|
+
<sheetpane-errors pane="q3" @sheetpane-errors="(e) => save(e.detail.errors)" />
|
|
121
|
+
</template>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Svelte** — `import type {} from '@sheetpane/grid/svelte';` once (e.g. in
|
|
125
|
+
`src/app.d.ts`); Svelte 5 listens with `onsheetpane-change={…}`, Svelte 4 with
|
|
126
|
+
`on:sheetpane-change={…}`; both set properties when the element has one.
|
|
127
|
+
|
|
128
|
+
```svelte
|
|
129
|
+
<sheetpane-excel pane="q3" mode="edit" file={file} onsheetpane-change={(e) => log(e.detail.ref)}></sheetpane-excel>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Solid** — `import type {} from '@sheetpane/grid/solid';` once. Solid sets a
|
|
133
|
+
custom element's props as properties, so write an attribute that has no
|
|
134
|
+
property with `attr:`, and listen with `on:`:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<sheetpane-excel attr:pane="q3" attr:header-row="2" mode="edit" prop:file={file()} on:sheetpane-change={(e) => log(e.detail.ref)} />
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Angular** — no package and no typings to import: add
|
|
141
|
+
`CUSTOM_ELEMENTS_SCHEMA` to the component that renders the tags, bind
|
|
142
|
+
properties with `[…]` and events with `(…)`:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
146
|
+
import '@sheetpane/grid';
|
|
147
|
+
|
|
148
|
+
@Component({
|
|
149
|
+
selector: 'app-review',
|
|
150
|
+
standalone: true,
|
|
151
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
152
|
+
template: `
|
|
153
|
+
<sheetpane-excel pane="q3" mode="edit" [file]="file" (sheetpane-change)="onChange($event)"></sheetpane-excel>
|
|
154
|
+
<sheetpane-errors pane="q3" (sheetpane-errors)="save($event.detail.errors)"></sheetpane-errors>`,
|
|
155
|
+
})
|
|
156
|
+
export class ReviewComponent { file: File | null = null; onChange(e: Event) { console.log((e as CustomEvent).detail); } save(errors: unknown[]) {} }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Next.js and other SSR** — the import is safe on the server (the elements
|
|
160
|
+
just do not register); render the tags in a client component.
|
|
161
|
+
|
|
162
|
+
## Typings and editor support
|
|
163
|
+
|
|
164
|
+
| file | for |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `@sheetpane/grid/custom-elements.json` | the [Custom Elements Manifest](https://github.com/webcomponents/custom-elements-manifest) (schema 2.1.0): every attribute, property, method, event (with its payload type), slot, CSS part and `--sp-*` property; `package.json`'s `customElements` points at it |
|
|
167
|
+
| `@sheetpane/grid/jsx` | React 19 — `React.JSX.IntrinsicElements` |
|
|
168
|
+
| `@sheetpane/grid/vue` | Vue 3 — `GlobalComponents` (Volar / vue-tsc) |
|
|
169
|
+
| `@sheetpane/grid/svelte` | Svelte 4 and 5 — `SvelteHTMLElements` |
|
|
170
|
+
| `@sheetpane/grid/solid` | Solid — `JSX.IntrinsicElements` |
|
|
171
|
+
| `@sheetpane/grid/vscode.html-custom-data.json` | VS Code: tags, attributes and their values in `.html` |
|
|
172
|
+
| `@sheetpane/grid/vscode.css-custom-data.json` | VS Code: the `--sp-*` properties in `.css` |
|
|
173
|
+
| `@sheetpane/grid/web-types.json` | JetBrains IDEs (WebStorm, IntelliJ) — read automatically through `package.json`'s `web-types` |
|
|
174
|
+
|
|
175
|
+
Each typing entry is types only (`import type {} from '…'`); the framework is
|
|
176
|
+
never a dependency. Each also exports the element types it builds on —
|
|
177
|
+
`SheetpaneExcelEventMap`, `SheetpaneExcelAttributes`,
|
|
178
|
+
`SheetpaneExcelProperties` and the same for `SheetpaneErrors` — for typing
|
|
179
|
+
your own wrappers.
|
|
180
|
+
|
|
181
|
+
**VS Code** — point the workspace settings at the editor data:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
// .vscode/settings.json
|
|
185
|
+
{
|
|
186
|
+
"html.customData": ["./node_modules/@sheetpane/grid/dist/vscode.html-custom-data.json"],
|
|
187
|
+
"css.customData": ["./node_modules/@sheetpane/grid/dist/vscode.css-custom-data.json"]
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
All of it is generated from the element sources and their TSDoc
|
|
192
|
+
(`scripts/build-manifest.mjs`, run by the build); a test fails when the
|
|
193
|
+
committed manifest drifts from the elements.
|
|
194
|
+
|
|
195
|
+
## `<sheetpane-excel>`
|
|
196
|
+
|
|
197
|
+
| kind | name | what |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| attribute | `pane` | the shared id; the same on both elements |
|
|
200
|
+
| attribute | `src` | a URL (read with a GET), a `data:` URL or base64 |
|
|
201
|
+
| attribute | `mode` | `view` (default) or `edit` |
|
|
202
|
+
| attribute | `display` | `tab` (default; fills its container) or `popup` (a modal dialog; `open` shows it) |
|
|
203
|
+
| attribute | `open` | shows the pop-up |
|
|
204
|
+
| attribute | `author` | written on errors flagged here |
|
|
205
|
+
| attribute | `plugins` | the enabled plugin ids, e.g. `numfmt formulas` |
|
|
206
|
+
| attribute | `header-row` | the row holding column titles (default `1`) — every event names the column by it, and column rules match by it |
|
|
207
|
+
| attribute | `theme` | `light` (default), `dark` or `auto` (follows the page's `prefers-color-scheme`) |
|
|
208
|
+
| attribute / property | `on-invalid` / `onInvalid` | what a failing column rule does by default: `flag` (default) or `block` |
|
|
209
|
+
| attribute / property | `rules-src` / `rules` | a rules file by URL, or as an object — see [Rules file](#rules-file) |
|
|
210
|
+
| attribute / property | `flagging` | `on` (default) or `off`: no Flag issue, no composer, no pop-up card — errors are read in `<sheetpane-errors>` |
|
|
211
|
+
| property | `columns`, `validate` | column rules by title, and a check function — see [Column rules](#column-rules) |
|
|
212
|
+
| property | `file` | a `File`, `Blob`, `ArrayBuffer` or `Uint8Array` to read |
|
|
213
|
+
| property | `workbook` | the pane's store (get, or set your own from `createWorkbook()`) |
|
|
214
|
+
| property | `errors`, `errorTypes` | the review; the error-type catalog |
|
|
215
|
+
| method | `load(src, name?)` | read a file (any of the above) |
|
|
216
|
+
| method | `export(options?, { format? })` | `Promise<Blob>` — the upload with every change; `format: 'csv'` from an `.xlsx` is the current sheet, `'xlsx'` from a `.csv` one sheet |
|
|
217
|
+
| method | `select(sheet, ref)`, `getErrors()`, `setErrors(list)`, `undo()`, `redo()` | |
|
|
218
|
+
| event | `sheetpane-load`, `sheetpane-errors {errors, plugins}`, `sheetpane-mode {mode}`, `sheetpane-history`, `sheetpane-export {blob, kind, name, format, converted, original?}` (cancelable — prevent it to save the file yourself; `original: true` when Download hands back the untouched file, `converted` when ▾ chose the other format), `sheetpane-close` | bubbling, composed |
|
|
219
|
+
|
|
220
|
+
### Following an edit
|
|
221
|
+
|
|
222
|
+
Every edit fires these, in this order, on the element where it happened;
|
|
223
|
+
every payload names the column `{ index, letter, header }` (the title from
|
|
224
|
+
`header-row`):
|
|
225
|
+
|
|
226
|
+
| event | fires | detail | cancel it to |
|
|
227
|
+
|---|---|---|---|
|
|
228
|
+
| `sheetpane-select` | the selected cell moves | `{sheet, ref, column, value}` | — |
|
|
229
|
+
| `sheetpane-editstart` | the cell bar opens on a cell | `{sheet, ref, column, value}` | keep the cell read-only |
|
|
230
|
+
| `sheetpane-input` | each keystroke, before saving | `{sheet, ref, column, draft, was}` | — |
|
|
231
|
+
| `sheetpane-beforechange` | Enter, Tab, blur, Delete, or each cell of a paste | `{sheet, ref, column, was, now}` | refuse the value |
|
|
232
|
+
| `sheetpane-change` | after saving | `{sheet, ref, kind, was, now}` | — |
|
|
233
|
+
| `sheetpane-editcancel` | Esc, or a column rule set to `block` refused the value | `{sheet, ref}` (refused: `{sheet, ref, reason: 'blocked', message}`) | — |
|
|
234
|
+
| `sheetpane-sheet` | the sheet tab changes | `{sheet}` | — |
|
|
235
|
+
|
|
236
|
+
A refused value never reaches the change map, undo or a plugin.
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
excel.addEventListener('sheetpane-beforechange', (e) => {
|
|
240
|
+
if (e.detail.column.header === 'Units' && Number(e.detail.now) < 0) e.preventDefault();
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Keys:** arrows, Shift+arrows, Home/End, Ctrl+Home/End, PageUp/PageDown;
|
|
245
|
+
Enter or F2 edits; typing replaces; Delete clears; Ctrl/⌘+V pastes tab-separated cells; **F flags an issue**;
|
|
246
|
+
**right-click, Shift+F10 or the Menu key** opens the selection actions (Flag issue, Copy ref, Copy value and
|
|
247
|
+
plugin actions) under the cell — the same actions sit at the right end of the cell bar, never over the cells; Esc
|
|
248
|
+
cancels or closes the pop-up; Ctrl/⌘+Z undo, Ctrl+Y / ⌘+Shift+Z redo. The `?`
|
|
249
|
+
button lists them, plugins' shortcuts included.
|
|
250
|
+
|
|
251
|
+
**Selecting a range:** drag (the grid scrolls when the pointer reaches an edge), Shift+click, or click a
|
|
252
|
+
column header, row header or the corner — drag or Shift+click headers for several. Ctrl/⌘+arrow jumps to
|
|
253
|
+
the edge of the data (with Shift: selects to it); Ctrl+Space selects the column, Shift+Space the row,
|
|
254
|
+
Ctrl/⌘+A the data and then the sheet; Shift+Backspace goes back to the active cell; Enter and Tab move
|
|
255
|
+
inside a range. On a range, Ctrl/⌘+C copies it so Excel and Google Sheets paste a grid, Delete clears it
|
|
256
|
+
(Edit, one undo step), one pasted value fills it, and the status line shows **Count · Sum · Average**.
|
|
257
|
+
`sheetpane-select` carries `range` (`B3:D8`) and fires once a drag is released. On a touch screen a finger
|
|
258
|
+
scrolls, a tap selects, and a range shows two handles to extend it.
|
|
259
|
+
|
|
260
|
+
**Resizing:** drag a column header's right edge or a row header's bottom edge; double-click it to fit the
|
|
261
|
+
text; with several whole columns or rows selected, one drag or fit sizes them all. Right-click a header (or
|
|
262
|
+
Shift+F10 with a whole column or row selected) for *Fit to data*, *Column width…* and *Reset*. Rows keep the
|
|
263
|
+
file's heights. Sizes are a view setting, like zoom — not a change, not in the download; save and restore
|
|
264
|
+
them with the `sizes` property and the `sheetpane-resize` event:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
excel.addEventListener('sheetpane-resize', () => localStorage.setItem('sizes', JSON.stringify(excel.sizes)));
|
|
268
|
+
excel.sizes = JSON.parse(localStorage.getItem('sizes') ?? '{}'); // { Sheet1: { cols: { B: 120 }, rows: { 4: 40 } } }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Column rules
|
|
272
|
+
|
|
273
|
+
Check what a user types, by column title. Rules are plain JSON — save and load
|
|
274
|
+
them as a file if you like — and failures go into the one error list as
|
|
275
|
+
`author: "host"` errors that resolve themselves when the cell passes.
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
excel.columns = {
|
|
279
|
+
Name: { required: true },
|
|
280
|
+
Email: { pattern: '^[^@\\s]+@[^@\\s]+\\.[a-z]{2,}$', unique: true },
|
|
281
|
+
Age: { type: 'integer', min: 18, max: 99, onInvalid: 'block' },
|
|
282
|
+
Status: { oneOf: ['Active', 'Inactive'], severity: 'warning' },
|
|
283
|
+
};
|
|
284
|
+
excel.validate = async (ctx, signal) =>
|
|
285
|
+
ctx.column.header === 'Email' && ctx.phase !== 'input' && (await api.taken(ctx.value, { signal }))
|
|
286
|
+
? { severity: 'error', message: 'Email already used' }
|
|
287
|
+
: null;
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
| rule | passes when |
|
|
291
|
+
|---|---|
|
|
292
|
+
| `required` | the cell is not blank (every other rule skips a blank cell) |
|
|
293
|
+
| `type` | `text` · `number` · `integer` · `date` · `boolean` |
|
|
294
|
+
| `min`, `max` | a number in range; for `type: 'date'`, an ISO date or a serial |
|
|
295
|
+
| `minLength`, `maxLength` | the text's length |
|
|
296
|
+
| `pattern` | the text matches (values over 10,000 characters are not tested — **avoid patterns that backtrack catastrophically**: they run on the page) |
|
|
297
|
+
| `oneOf` | the value is one of the list (trimmed, case-insensitive) |
|
|
298
|
+
| `unique` | no other row of the column holds it |
|
|
299
|
+
| `message`, `severity`, `onInvalid` | replace the generated message; `error` (default) · `warning` · `note`; `flag` (default) · `block` |
|
|
300
|
+
|
|
301
|
+
- **Titles** match trimmed and case-insensitive (an exact title wins). A
|
|
302
|
+
title the file lacks is one `note` error; a duplicated title checks the
|
|
303
|
+
first column and adds one `note`. The file always loads.
|
|
304
|
+
- **`validate(ctx, signal)`** runs for every cell of a titled column, sync or
|
|
305
|
+
async. `ctx` is `{phase, sheet, ref, row, column, value, was, original,
|
|
306
|
+
rowValues}`, `rowValues` keyed by title, so a rule can read the row; editing
|
|
307
|
+
any cell asks the row again. A newer edit to a cell aborts the older check
|
|
308
|
+
(`signal`) and its result is dropped. Return `null` or `{severity, message,
|
|
309
|
+
expected?}`.
|
|
310
|
+
- **Phases:** `input` — 150 ms after typing stops, an outline and a message
|
|
311
|
+
at the cell bar, nothing stored; `commit` — on save, `flag` stores the value
|
|
312
|
+
and adds an error, `block` refuses it (only a JSON rule or a synchronous
|
|
313
|
+
result can block; an async result flags); `load` — every row, in slices,
|
|
314
|
+
with the progress in the status line.
|
|
315
|
+
- Setting `columns`, `validate` or `onInvalid` again re-checks every row;
|
|
316
|
+
host errors no longer produced are removed. People's errors are never
|
|
317
|
+
touched, and a rule never edits a cell.
|
|
318
|
+
- Rules belong to the pane: `workbook.setColumnRules({ columns, validate,
|
|
319
|
+
onInvalid, headerRow })` does the same without an element.
|
|
320
|
+
|
|
321
|
+
## Rules file
|
|
322
|
+
|
|
323
|
+
Ship the rules next to your code as JSON and point the sheet at them; every
|
|
324
|
+
cell they cover is checked on load and the failures are listed in
|
|
325
|
+
`<sheetpane-errors>`. With `flagging="off"` the sheet shows no Flag issue and
|
|
326
|
+
opens no pop-up — the errors panel is the one place errors are read.
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<sheetpane-excel pane="q3" src="./q3.xlsx" rules-src="./sheetpane.rules.json" flagging="off"></sheetpane-excel>
|
|
330
|
+
<sheetpane-errors pane="q3"></sheetpane-errors>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"$schema": "https://unpkg.com/@sheetpane/grid/rules.schema.json",
|
|
336
|
+
"version": 1,
|
|
337
|
+
"sheets": {
|
|
338
|
+
"*": {
|
|
339
|
+
"columns": { "Email": { "pattern": "^[^@\\s]+@[^@\\s]+\\.[a-z]{2,}$", "unique": true } }
|
|
340
|
+
},
|
|
341
|
+
"Accounts": {
|
|
342
|
+
"headerRow": 2,
|
|
343
|
+
"columns": {
|
|
344
|
+
"Account ID": { "required": true, "pattern": "^AC-\\d{5}$", "message": "Account IDs are AC- and five digits" },
|
|
345
|
+
"Units": { "type": "integer", "min": 0 },
|
|
346
|
+
"Status": { "oneOf": ["Active", "Inactive"], "noneOf": ["TBD", "N/A"], "severity": "warning" }
|
|
347
|
+
},
|
|
348
|
+
"cells": {
|
|
349
|
+
"B1": { "equals": "Q3 review", "note": "the title cell the export pipeline reads" },
|
|
350
|
+
"H3:H40": { "type": "number", "min": 0, "max": 1 }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
- **`sheets`** by name; `"*"` applies to every sheet and a named sheet's rule
|
|
358
|
+
for the same title wins. Each sheet has `headerRow`, `columns` (by title,
|
|
359
|
+
as [Column rules](#column-rules)) and `cells` (an A1 cell or range).
|
|
360
|
+
- **The words** are Column rules' plus `equals` (the one valid value; a blank
|
|
361
|
+
cell fails it), `noneOf` (values never valid) and `note` (for whoever
|
|
362
|
+
maintains the file — never shown as the message).
|
|
363
|
+
- **`$schema`** gives your editor autocomplete and red squiggles: the schema
|
|
364
|
+
ships as `@sheetpane/grid/rules.schema.json`.
|
|
365
|
+
- **Never silent, never a throw:** an unknown key, word or version, a sheet,
|
|
366
|
+
title or cell the workbook lacks, or a pattern that is not a regular
|
|
367
|
+
expression becomes one `note` in the errors panel; the rest still applies.
|
|
368
|
+
- `rules-src` is read with a GET like `src`; `excel.rules = object` does the
|
|
369
|
+
same without a fetch. Setting either checks every row again. The element's
|
|
370
|
+
`columns` and `validate` still work beside it — the file's rule wins for the
|
|
371
|
+
same title, with a note.
|
|
372
|
+
- **YAML or TOML:** parse it yourself and pass the object — the package reads
|
|
373
|
+
JSON only, so it carries no parser:
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
import { parse } from 'yaml'; // or: import { parse } from 'smol-toml';
|
|
377
|
+
excel.rules = parse(await (await fetch('./sheetpane.rules.yaml')).text());
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## `<sheetpane-errors>`
|
|
381
|
+
|
|
382
|
+
| kind | name | what |
|
|
383
|
+
|---|---|---|
|
|
384
|
+
| attribute | `pane`, `author`, `plugins` | as above |
|
|
385
|
+
| attribute | `readonly` | hides the form, Resolve and Delete — a sign-off screen |
|
|
386
|
+
| property / method | `errors`, `errorTypes`, `getErrors()`, `setErrors(list, { plugins })` | |
|
|
387
|
+
| event | `sheetpane-errors {errors, plugins}`, `sheetpane-reveal {error}` | **Show in sheet**: switch to the sheet's tab or open the pop-up |
|
|
388
|
+
|
|
389
|
+
## Saving and restoring a review
|
|
390
|
+
|
|
391
|
+
Errors are plain JSON (`SheetpaneError`, schema `v: 1`). Listen to
|
|
392
|
+
`sheetpane-errors`, store `detail.errors` and `detail.plugins` where you like,
|
|
393
|
+
and give them back after the file loads:
|
|
394
|
+
|
|
395
|
+
```js
|
|
396
|
+
excel.addEventListener('sheetpane-load', () => errors.setErrors(saved.errors, { plugins: saved.plugins }));
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
An error whose cell no longer shows what was found is tagged *stale*, never
|
|
400
|
+
moved.
|
|
401
|
+
|
|
402
|
+
## Plugins
|
|
403
|
+
|
|
404
|
+
Features beyond review — every Excel number format, live formulas, cell
|
|
405
|
+
styles, data validation, conditional formatting, pictures, charts, comments —
|
|
406
|
+
are separate packages that plug into six hooks and six slots. With none
|
|
407
|
+
installed the grid is the plain review tool. **See each one on the same
|
|
408
|
+
workbook:** the demos' plugin gallery (`just demo-html` or `just demo-react`)
|
|
409
|
+
has a tab per plugin, plus Vanilla and All plugins.
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
import '@sheetpane/numfmt'; // registers "numfmt"
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
```html
|
|
416
|
+
<sheetpane-excel pane="q3" plugins="numfmt"></sheetpane-excel>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Or build the store yourself: `el.workbook = createWorkbook({ plugins: [...] })`.
|
|
420
|
+
|
|
421
|
+
## Styling
|
|
422
|
+
|
|
423
|
+
Colours, spacing and radii are `--sp-*` custom properties on the elements
|
|
424
|
+
(`--sp-accent`, `--sp-ink`, `--sp-line`, …); `@sheetpane/grid/style.css`
|
|
425
|
+
lists the defaults, and `theme="dark"` swaps in the dark set. The font is yours: set it on the element or any ancestor.
|
|
426
|
+
|
|
427
|
+
The specs are the records [SR-GRID](../../records/0105_GRID.md) and
|
|
428
|
+
[SR-ERRORS](../../records/0106_ERRORS.md).
|
|
429
|
+
|
|
430
|
+
**Using an AI coding agent?** `npx sheetpane-skills install` puts sheetpane's skills for Claude Code, Kiro and Codex into your repository.
|