@sheetpane/grid 0.3.0 → 0.4.1
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/README.md +294 -13
- package/custom-elements.json +292 -27
- package/dist/check.d.ts +75 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +151 -0
- package/dist/check.js.map +1 -0
- package/dist/errors/element.d.ts +90 -12
- package/dist/errors/element.d.ts.map +1 -1
- package/dist/errors/element.js +572 -50
- package/dist/errors/element.js.map +1 -1
- package/dist/excel/element.d.ts +168 -15
- package/dist/excel/element.d.ts.map +1 -1
- package/dist/excel/element.js +752 -90
- package/dist/excel/element.js.map +1 -1
- package/dist/excel/link.d.ts +40 -0
- package/dist/excel/link.d.ts.map +1 -0
- package/dist/excel/link.js +90 -0
- package/dist/excel/link.js.map +1 -0
- package/dist/excel/styles.d.ts.map +1 -1
- package/dist/excel/styles.js +37 -5
- package/dist/excel/styles.js.map +1 -1
- package/dist/frameworks/elements.d.ts +57 -17
- package/dist/frameworks/jsx.d.ts +15 -9
- package/dist/frameworks/solid.d.ts +51 -11
- package/dist/frameworks/svelte.d.ts +28 -16
- package/dist/frameworks/vue.d.ts +15 -9
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +3042 -246
- package/dist/llms.txt +92 -86
- package/dist/pane/protection.d.ts +61 -0
- package/dist/pane/protection.d.ts.map +1 -0
- package/dist/pane/protection.js +89 -0
- package/dist/pane/protection.js.map +1 -0
- package/dist/pane/rules.d.ts +1 -1
- package/dist/pane/rules.d.ts.map +1 -1
- package/dist/pane/store.d.ts +336 -15
- package/dist/pane/store.d.ts.map +1 -1
- package/dist/pane/store.js +891 -44
- package/dist/pane/store.js.map +1 -1
- package/dist/pane/structure.d.ts +58 -0
- package/dist/pane/structure.d.ts.map +1 -0
- package/dist/pane/structure.js +238 -0
- package/dist/pane/structure.js.map +1 -0
- package/dist/pane/types.d.ts +195 -6
- package/dist/pane/types.d.ts.map +1 -1
- package/dist/pane/types.js +57 -0
- package/dist/pane/types.js.map +1 -1
- package/dist/plugins/types.d.ts +34 -6
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/types.js.map +1 -1
- package/dist/shared/detail.d.ts +24 -0
- package/dist/shared/detail.d.ts.map +1 -0
- package/dist/shared/detail.js +75 -0
- package/dist/shared/detail.js.map +1 -0
- package/dist/shared/dom.d.ts +3 -0
- package/dist/shared/dom.d.ts.map +1 -1
- package/dist/shared/dom.js +3 -0
- package/dist/shared/dom.js.map +1 -1
- package/dist/static.d.ts +81 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +352 -0
- package/dist/static.js.map +1 -0
- package/dist/style.css +3 -3
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +2 -2
- package/dist/tokens.js.map +1 -1
- package/dist/vscode.css-custom-data.json +11 -1
- package/dist/vscode.html-custom-data.json +89 -2
- package/dist/web-types.json +204 -21
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -205,17 +205,24 @@ committed manifest drifts from the elements.
|
|
|
205
205
|
| attribute | `plugins` | the enabled plugin ids, e.g. `numfmt formulas` |
|
|
206
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
207
|
| attribute | `theme` | `light` (default), `dark` or `auto` (follows the page's `prefers-color-scheme`) |
|
|
208
|
+
| attribute / property | `ref` | the cell or range selected once a file is read, scrolled into view: `B4`, `Data!B4`, `'Q3 data'!B4:D8` — see [Opening at a cell](#opening-at-a-cell-and-links) |
|
|
209
|
+
| attribute / property | `link` | `none` (default) or `hash`: the selection kept in the URL fragment, and *Copy link* in the cell menu |
|
|
208
210
|
| attribute / property | `on-invalid` / `onInvalid` | what a failing column rule does by default: `flag` (default) or `block` |
|
|
209
211
|
| attribute / property | `rules-src` / `rules` | a rules file by URL, or as an object — see [Rules file](#rules-file) |
|
|
210
212
|
| attribute / property | `flagging` | `on` (default) or `off`: no Flag issue, no composer, no pop-up card — errors are read in `<sheetpane-errors>` |
|
|
213
|
+
| attribute / property | `results` | `summary` (default), `marks` or `off` — how passed and not-checked cells show, pane-wide; see [Results](#results-passed-and-not-checked) |
|
|
214
|
+
| attribute / property | `protection` | `honour` (default) or `ignore` — whether a protected sheet's locked cells refuse edits, pane-wide; see [Protected sheets](#protected-sheets) |
|
|
211
215
|
| property | `columns`, `validate` | column rules by title, and a check function — see [Column rules](#column-rules) |
|
|
212
216
|
| property | `file` | a `File`, `Blob`, `ArrayBuffer` or `Uint8Array` to read |
|
|
213
217
|
| property | `workbook` | the pane's store (get, or set your own from `createWorkbook()`) |
|
|
214
218
|
| property | `errors`, `errorTypes` | the review; the error-type catalog |
|
|
219
|
+
| property | `renderDetail` | your extra detail in the error card (W-68, under *Saving and restoring a review*) |
|
|
220
|
+
| attribute / property | `layers` | the review layers shown, space-separated — the pane's, shared with `<sheetpane-errors>`; none shows every layer (W-51, under *Saving and restoring a review*) |
|
|
215
221
|
| method | `load(src, name?)` | read a file (any of the above) |
|
|
216
222
|
| 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
|
-
|
|
|
223
|
+
| method | `select(sheet, ref)`, `getErrors({ layer? })`, `setErrors(list or envelope, { layer? })`, `undo()`, `redo()` | |
|
|
224
|
+
| method | `highlight(ranges, { tone, label, sheet, pinned, reveal }?)`, `clearHighlight()` | outline cells without selecting them — and `sheetpane-hover` — see *Pointing at cells* under [Following an edit](#following-an-edit) |
|
|
225
|
+
| event | `sheetpane-load` (`{ status: 'ready', name, kind, sheets, readMs, sha256 }`), `sheetpane-errors {v, errors, plugins, layers, changed}`, `sheetpane-layers {visible, layers}`, `sheetpane-mode {mode}`, `sheetpane-history`, `sheetpane-export {blob, kind, name, format, converted, sha256, 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` — `sha256` is the SHA-256 of the file opened / the blob handed back, `undefined` without Web Crypto (SP-1009) | bubbling, composed |
|
|
219
226
|
|
|
220
227
|
### Following an edit
|
|
221
228
|
|
|
@@ -226,11 +233,11 @@ every payload names the column `{ index, letter, header }` (the title from
|
|
|
226
233
|
| event | fires | detail | cancel it to |
|
|
227
234
|
|---|---|---|---|
|
|
228
235
|
| `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 |
|
|
236
|
+
| `sheetpane-editstart` | the cell bar opens on a cell | `{sheet, ref, column, value, richText?}` — `richText: true` on a rich-text cell, whose edit saves plain text | keep the cell read-only |
|
|
230
237
|
| `sheetpane-input` | each keystroke, before saving | `{sheet, ref, column, draft, was}` | — |
|
|
231
238
|
| `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,
|
|
239
|
+
| `sheetpane-change` | after saving | `{sheet, ref, kind, was, now}` (an insert or delete: `kind: 'structure'`, `op`, `at`, `count`) | — |
|
|
240
|
+
| `sheetpane-editcancel` | Esc, a column rule set to `block` refused the value, or the cell is locked on a protected sheet | `{sheet, ref}` (refused: `{sheet, ref, reason: 'blocked', code: 'SP-6003', message}`; locked: `{sheet, ref, reason: 'locked', code: 'SP-3026', message: 'Locked in the file'}`) | — |
|
|
234
241
|
| `sheetpane-sheet` | the sheet tab changes | `{sheet}` | — |
|
|
235
242
|
|
|
236
243
|
A refused value never reaches the change map, undo or a plugin.
|
|
@@ -243,8 +250,8 @@ excel.addEventListener('sheetpane-beforechange', (e) => {
|
|
|
243
250
|
|
|
244
251
|
**Keys:** arrows, Shift+arrows, Home/End, Ctrl+Home/End, PageUp/PageDown;
|
|
245
252
|
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
|
|
247
|
-
plugin actions) under the cell — the same actions sit at the right end of the cell bar, never over the cells; Esc
|
|
253
|
+
**right-click, Shift+F10 or the Menu key** opens the selection actions (Flag issue, Copy ref, Copy link with
|
|
254
|
+
`link="hash"`, Copy value and plugin actions) under the cell — the same actions sit at the right end of the cell bar, never over the cells; Esc
|
|
248
255
|
cancels or closes the pop-up; Ctrl/⌘+Z undo, Ctrl+Y / ⌘+Shift+Z redo. The `?`
|
|
249
256
|
button lists them, plugins' shortcuts included.
|
|
250
257
|
|
|
@@ -268,6 +275,67 @@ excel.addEventListener('sheetpane-resize', () => localStorage.setItem('sizes', J
|
|
|
268
275
|
excel.sizes = JSON.parse(localStorage.getItem('sizes') ?? '{}'); // { Sheet1: { cols: { B: 120 }, rows: { 4: 40 } } }
|
|
269
276
|
```
|
|
270
277
|
|
|
278
|
+
**Inserting and deleting rows and columns (Edit):** the cell menu and the ⋯ menu offer *Insert row above /
|
|
279
|
+
below*, *Insert column left / right*, *Delete row* and *Delete column* (with counts for a range); a header
|
|
280
|
+
menu offers its axis's three. Ctrl/⌘+Shift+= inserts rows above (columns left when whole columns are
|
|
281
|
+
selected); Ctrl/⌘+− deletes whole rows or columns. Everything that points at a cell moves — values, edits,
|
|
282
|
+
errors, formulas on every sheet (`#REF!` for a deleted target), merges, validations, conditional formats,
|
|
283
|
+
notes, pictures and charts, hidden and grouped rows, freeze, sizes — and the download carries it. Each is one
|
|
284
|
+
undo step and one `sheetpane-change` with `kind: 'structure'`, `op`, `at` (0-based) and `count`. What Excel
|
|
285
|
+
refuses is refused, greyed with its reason: the file's protection — a protected structure, or a protected
|
|
286
|
+
sheet without *Insert rows* / *Delete rows* / *Insert columns* / *Delete columns* (`SP-3026`) — a delete that
|
|
287
|
+
splits a merged range (`SP-3022`), anything that
|
|
288
|
+
would move a pivot table or its source (`SP-3023`), cells pushed off the sheet (`SP-3024`), a table cut
|
|
289
|
+
(`SP-3025`). A host drives the same through the store:
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
const book = excel.workbook;
|
|
293
|
+
if (!book.structureRefusal({ sheet: 'Spec', op: 'insertRows', at: 4, count: 1 })) book.insertRows('Spec', 4); // above row 5
|
|
294
|
+
book.deleteCols('Spec', 2, 2); // C and D
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Pointing at cells from outside (W-56):** `sheetpane-hover` names the cell under a mouse or pen pointer —
|
|
298
|
+
`{sheet, ref, column}`, a merge by its top-left, at most once per frame and only when the cell changes —
|
|
299
|
+
and `null` when the pointer leaves the cells; a finger never hovers. `highlight(ranges, options?)` outlines
|
|
300
|
+
cells without selecting them: `B3`, `B3:D8`, `Data!B3` or `{ ref, sheet?, tone? }`, in `tone` `info`
|
|
301
|
+
(default), `pass` or `fail`. The outline lets the pointer through; it is not a change, not undone, not in the
|
|
302
|
+
download. Each call replaces the last; a sheet switch clears it unless `pinned: true`, and so does a new
|
|
303
|
+
file; `reveal: true` scrolls the first range into view; a `label` is shown in the status line and said once
|
|
304
|
+
by screen readers. It returns how many ranges it outlined (a bad ref or a missing sheet is skipped);
|
|
305
|
+
`clearHighlight()` removes it. Together they make "hover a box, its cell lights up — hover a cell, its box
|
|
306
|
+
does":
|
|
307
|
+
|
|
308
|
+
```js
|
|
309
|
+
excel.addEventListener('sheetpane-hover', (e) => pdf.outline(e.detail && boxFor(e.detail.ref))); // null: none
|
|
310
|
+
pdf.onHover = (box) => (box ? excel.highlight(box.cell, { tone: box.ok ? 'pass' : 'fail', label: box.name }) : excel.clearHighlight());
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Opening at a cell, and links
|
|
314
|
+
|
|
315
|
+
`ref` opens the file at a cell — the selection once it is read, scrolled into view; set later, it selects
|
|
316
|
+
then. A cell or range, with or without its sheet (`B4`, `Data!B4`, `'Q3 data'!B4:D8`). One that names no
|
|
317
|
+
cell warns `SP-3019` in the console and the sheet opens at A1.
|
|
318
|
+
|
|
319
|
+
```html
|
|
320
|
+
<sheetpane-excel pane="q3" src="/files/q3.xlsx" ref="Data!F9"></sheetpane-excel>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`link="hash"` keeps the selection in the URL, so a reload or a shared link opens at the same cell, and adds
|
|
324
|
+
*Copy link* to the cell menu. It is off by default. Only one parameter of the fragment is sheetpane's —
|
|
325
|
+
`sheetpane=<pane>:<Sheet>!<ref>` — and the rest is left as it was: after a hash router's path it goes with
|
|
326
|
+
`?` (`#/reports/12?sheetpane=q3:Data!F9`), after an anchor or your own parameters with `&`. It is written
|
|
327
|
+
with `history.replaceState` once the selection settles — no history entry, no `hashchange` for your router
|
|
328
|
+
— and read when a file is read and on back and forward. A link wins over `ref`. Give each sheet its own
|
|
329
|
+
`pane` when a page has two. The fragment never leaves the browser: it is not part of any request.
|
|
330
|
+
|
|
331
|
+
**React, Vue and Solid** keep `ref` for their own refs, so their typings leave it out. Set it from a ref
|
|
332
|
+
callback, or call `select()` once the file is read:
|
|
333
|
+
|
|
334
|
+
```jsx
|
|
335
|
+
<sheetpane-excel pane="q3" src={url} ref={(el) => el?.setAttribute('ref', 'Data!F9')} />
|
|
336
|
+
<sheetpane-excel pane="q3" src={url} onsheetpane-load={(e) => e.detail.status === 'ready' && e.currentTarget.select('Data', 'F9')} />
|
|
337
|
+
```
|
|
338
|
+
|
|
271
339
|
## Column rules
|
|
272
340
|
|
|
273
341
|
Check what a user types, by column title. Rules are plain JSON — save and load
|
|
@@ -377,28 +445,241 @@ opens no pop-up — the errors panel is the one place errors are read.
|
|
|
377
445
|
excel.rules = parse(await (await fetch('./sheetpane.rules.yaml')).text());
|
|
378
446
|
```
|
|
379
447
|
|
|
448
|
+
## The same checks with no page (`@sheetpane/grid/check`)
|
|
449
|
+
|
|
450
|
+
`check()` runs what the pane runs on load — the rules file, column rules, an
|
|
451
|
+
async `validate`, and each plugin's `validate` after a `derive` plugin has
|
|
452
|
+
recalculated — in Node, a worker or a page, and returns the errors the pane
|
|
453
|
+
would hold. The entry imports no element and touches no DOM.
|
|
454
|
+
|
|
455
|
+
```js
|
|
456
|
+
import { readFile } from 'node:fs/promises';
|
|
457
|
+
import { check } from '@sheetpane/grid/check';
|
|
458
|
+
import { validation } from '@sheetpane/validation/check';
|
|
459
|
+
|
|
460
|
+
const errors = await check(await readFile('q3.xlsx'), {
|
|
461
|
+
name: 'q3.xlsx',
|
|
462
|
+
rules: JSON.parse(await readFile('sheetpane.rules.json', 'utf8')),
|
|
463
|
+
plugins: [validation()], // objects, or ids registered with registerPlugin
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
- **Options are the element's:** `rules` (an object, or its JSON text),
|
|
468
|
+
`columns`, `validate(ctx, signal)`, `onInvalid`, `headerRow`, `plugins`, and
|
|
469
|
+
the file's `name` (`.csv` reads CSV; unnamed bytes are sniffed as `load()`
|
|
470
|
+
does).
|
|
471
|
+
- **The same errors** as `<sheetpane-excel>` given the same file and settings
|
|
472
|
+
— only `id` and `createdAt` are per run — sorted by sheet, row and column.
|
|
473
|
+
A file that cannot be read throws its reader's code (SP-10xx); anything but
|
|
474
|
+
bytes throws SP-3020.
|
|
475
|
+
- The store, the rules checker and the plugin contract are exported here too
|
|
476
|
+
(`createWorkbook`, `checkDocument`, `registerPlugin`, the types), for a
|
|
477
|
+
DOM-free plugin or a host's own pipeline. `loadWorkbook(bytes, options)`
|
|
478
|
+
takes the same options and returns the loaded store, every check finished,
|
|
479
|
+
instead of its errors.
|
|
480
|
+
|
|
481
|
+
## A range as static HTML (`@sheetpane/grid/static`)
|
|
482
|
+
|
|
483
|
+
`renderStatic(book, { sheet, range, errors, theme, headers })` returns one
|
|
484
|
+
self-contained `<table>` — the text the pane shows (the `format` plugin), the
|
|
485
|
+
look it draws (`decorate` plugins, merges, the theme's tokens as inline hex),
|
|
486
|
+
hidden rows and columns left out, and the errors marked — for a report or an
|
|
487
|
+
email. Made with no DOM, in Node, a worker or a page.
|
|
488
|
+
|
|
489
|
+
```js
|
|
490
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
491
|
+
import { loadWorkbook, renderStatic } from '@sheetpane/grid/static';
|
|
492
|
+
import { numfmt } from '@sheetpane/numfmt';
|
|
493
|
+
import { styles } from '@sheetpane/styles';
|
|
494
|
+
import { condfmt } from '@sheetpane/condfmt';
|
|
495
|
+
import { validation } from '@sheetpane/validation/check';
|
|
496
|
+
|
|
497
|
+
const book = await loadWorkbook(await readFile('q3.xlsx'), { name: 'q3.xlsx', rules, plugins: [numfmt(), styles(), condfmt(), validation()] });
|
|
498
|
+
await writeFile('report.html', renderStatic(book, { sheet: 'Holdings', range: 'A1:F20' }));
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
- **`book`** is a loaded store: `loadWorkbook()`'s, `createWorkbook()`'s after
|
|
502
|
+
`load()`, or an element's `workbook` — its changes and derived values show.
|
|
503
|
+
`sheet` defaults to the selected one, `range` to the used range (≤ 200,000
|
|
504
|
+
cells), `errors` to the visible layers' (`[]` marks nothing; pass `check()`'s
|
|
505
|
+
or a restored review), `theme` to `light` (`dark` too), `headers` to true.
|
|
506
|
+
- **Marks:** a ring and a tint as the grid draws them, and a badge with the
|
|
507
|
+
glyph (✕ ! i ✓, `A` for an accepted deviation) and a number that the footer
|
|
508
|
+
explains — severity, range, type, message, *should be*, the acceptance's
|
|
509
|
+
reason. Never colour alone. Errors about the whole sheet or the workbook
|
|
510
|
+
head the footer and mark no cell. Results (✓ passed, — not checked) show
|
|
511
|
+
as the grid's corner glyph only when the pane's `results` level is
|
|
512
|
+
`marks`. Rich text keeps its runs (italic, bold, colour), never a font.
|
|
513
|
+
- **Inert:** no script, no event attribute, no `class`, no stylesheet, no URL
|
|
514
|
+
(a `decorate` value that loads or escapes is dropped), every value escaped,
|
|
515
|
+
no font family — the reader's font is the font.
|
|
516
|
+
- **Not drawn:** what an `overlay` plugin draws (pictures, charts) — the
|
|
517
|
+
footer says so and `data-sp-skipped` names the plugins. Interactive cell
|
|
518
|
+
controls (a list arrow) are not drawn either.
|
|
519
|
+
- Refusals: SP-3027 when `book` is not a loaded workbook; SP-3028 for an
|
|
520
|
+
unknown sheet, a range that is not A1, or too many cells.
|
|
521
|
+
|
|
380
522
|
## `<sheetpane-errors>`
|
|
381
523
|
|
|
382
524
|
| kind | name | what |
|
|
383
525
|
|---|---|---|
|
|
384
526
|
| attribute | `pane`, `author`, `plugins` | as above |
|
|
385
|
-
| attribute | `readonly` | hides the form, Resolve and Delete — a sign-off screen |
|
|
386
|
-
|
|
|
387
|
-
|
|
|
527
|
+
| attribute | `readonly` | hides the form, Resolve, Accept… and Delete — a sign-off screen |
|
|
528
|
+
| attribute / property | `results` | as above; the panel's *On sheet* switch moves the pane between `summary` and `marks` |
|
|
529
|
+
| property / method | `errors`, `errorTypes`, `getErrors({ layer? })`, `setErrors(list or envelope, { plugins?, layer? })` | |
|
|
530
|
+
| property | `renderDetail` | your extra detail in an open row — see below (W-68) |
|
|
531
|
+
| attribute / property | `layers`; `layerNames` | the review layers shown (as on the sheet); what the switch calls each, e.g. `{ pdf: 'PDF check' }` (W-51) |
|
|
532
|
+
| event | `sheetpane-errors {v, errors, plugins, layers, changed}`, `sheetpane-layers {visible, layers}`, `sheetpane-reveal {error}` | **Show in sheet**: switch to the sheet's tab or open the pop-up |
|
|
388
533
|
|
|
389
534
|
## Saving and restoring a review
|
|
390
535
|
|
|
391
536
|
Errors are plain JSON (`SheetpaneError`, schema `v: 1`). Listen to
|
|
392
|
-
`sheetpane-errors`, store `detail
|
|
393
|
-
and give
|
|
537
|
+
`sheetpane-errors`, store its `detail` — the envelope `{ v: 2, errors,
|
|
538
|
+
plugins, layers, changed }` — where you like, and give it back after the
|
|
539
|
+
file loads:
|
|
540
|
+
|
|
541
|
+
```js
|
|
542
|
+
excel.addEventListener('sheetpane-load', () => errors.setErrors(saved));
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
A review saved before W-51 (`{ errors, plugins }`) restores the same way, into
|
|
546
|
+
the `review` layer.
|
|
547
|
+
|
|
548
|
+
**Review layers** (W-51): several sets of errors in one pane — say a
|
|
549
|
+
validation run and a PDF check — each replaced whole without touching the
|
|
550
|
+
others. Every error has a `layer` (`review` unless named):
|
|
394
551
|
|
|
395
552
|
```js
|
|
396
|
-
|
|
553
|
+
errors.setErrors(validationFindings, { layer: 'validation' });
|
|
554
|
+
errors.setErrors(pdfFindings, { layer: 'pdf' }); // validation is kept
|
|
555
|
+
errors.layerNames = { pdf: 'PDF check' };
|
|
556
|
+
errors.layers = ['pdf']; // or layers="pdf" on either element; null shows all
|
|
397
557
|
```
|
|
398
558
|
|
|
559
|
+
With two or more layers the panel's header shows a switch — **All**, then
|
|
560
|
+
each layer with its open count. What is shown is the pane's: the sheet's
|
|
561
|
+
pins, marks and counts, the list and the comments an export writes all
|
|
562
|
+
follow it (`export({ layers: [...] })` names another set). Replacing a layer
|
|
563
|
+
keeps undo; a person's new error goes into the layer they are looking at.
|
|
564
|
+
Names are ids: 1–64 characters, no spaces (SP-3021).
|
|
565
|
+
|
|
399
566
|
An error whose cell no longer shows what was found is tagged *stale*, never
|
|
400
567
|
moved.
|
|
401
568
|
|
|
569
|
+
**An error with no cell** (W-55): `range: ""` is about the whole sheet, and
|
|
570
|
+
`sheet: ""` with `range: ""` about the whole workbook — "no Pages List", "a
|
|
571
|
+
listed tab has no sheet". Add one with `setErrors()`, `workbook.addError()` or
|
|
572
|
+
a `validate` hook (a whole-sheet draft only). The list shows them first — a
|
|
573
|
+
*Workbook* group, then each sheet's — above the cell errors; a whole-sheet
|
|
574
|
+
error is counted on its tab and **Show in sheet** opens the tab; for a
|
|
575
|
+
workbook-level one it is disabled. They are never placed on A1; no pin, no
|
|
576
|
+
cell mark, no `found`. `errorScope(e)` says which (`cell` · `sheet` ·
|
|
577
|
+
`workbook`). **If your code reads `range` as an A1 reference, handle `""`.**
|
|
578
|
+
|
|
579
|
+
**Your data on an error, and your detail beside it** (W-68): `meta` holds any
|
|
580
|
+
JSON of yours (64 KiB at most, `ERROR_META_MAX`); it is saved and restored
|
|
581
|
+
with the error and never written into the file. `renderDetail(error)` — on
|
|
582
|
+
`<sheetpane-excel>` for the error card and on `<sheetpane-errors>` for the
|
|
583
|
+
open row, each its own — returns a string (shown as text, never HTML), a
|
|
584
|
+
Node (yours, placed as it is) or `null`. It is drawn after the message, in
|
|
585
|
+
`::part(detail)`, and runs again only when that error changes.
|
|
586
|
+
|
|
587
|
+
```js
|
|
588
|
+
workbook.addError({ sheet: 'Spec', range: 'C7', type: 'out-of-range', severity: 'error', meta: { page: 3, measured: '6.63 in' } });
|
|
589
|
+
const detail = (e) => (e.meta?.measured ? `The PDF measures ${e.meta.measured}, outside ± 0.06 in` : null);
|
|
590
|
+
excel.renderDetail = detail;
|
|
591
|
+
errors.renderDetail = detail;
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
A `meta` that is not JSON or is over the cap is dropped (SP-3016); a hook
|
|
595
|
+
that throws shows nothing (SP-3017).
|
|
596
|
+
|
|
597
|
+
**Accepted deviations** (W-69): a known, approved difference can be accepted
|
|
598
|
+
instead of deleted. **Accept…** in an open row of `<sheetpane-errors>` asks
|
|
599
|
+
for a reason (required) and records it with who (the element's `author`) and
|
|
600
|
+
when: `status: 'accepted'`, `acceptedBy`, `acceptedAt`, `reason`. Accepted
|
|
601
|
+
errors stay in the review and the download but are not counted — not in
|
|
602
|
+
"n open", the severity chips or the sheet's counts — and the **Accepted n**
|
|
603
|
+
chip lists them. Reopen takes an acceptance back; so does undo. From code:
|
|
604
|
+
|
|
605
|
+
```js
|
|
606
|
+
workbook.acceptError(id, 'Approved by the customer on 18 Oct', { by: 'Ravi' });
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
A blank reason is refused (SP-3018). Count open problems with
|
|
610
|
+
`status === 'open' && !isResult(e)`. With `@sheetpane/comments`, the export adds a reply
|
|
611
|
+
*Accepted: <reason>* to the error's comment thread.
|
|
612
|
+
|
|
613
|
+
**The Edits view** (W-64): an *Edits n* chip beside *Problems* lists every
|
|
614
|
+
change in the pane, newest first — the cell or range and kind, *was → now*,
|
|
615
|
+
the time, and the errors that edit cleared or raised (each a link to the
|
|
616
|
+
error). Picking a row selects the cell; **Revert** on a row and **Revert
|
|
617
|
+
all** are each one undo step. It shows in Edit mode, and in View mode while
|
|
618
|
+
there are edits. The store gives the same list to your code:
|
|
619
|
+
|
|
620
|
+
```js
|
|
621
|
+
const edits = workbook.edits(); // [{ key, sheet, ref, kind, was, now, at }], newest first
|
|
622
|
+
workbook.editEffects(edits[0].key); // { cleared, raised, matched } — error ids
|
|
623
|
+
workbook.revertEdits([edits[0].key]); // omit the keys to revert all
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## Results: passed and not checked
|
|
627
|
+
|
|
628
|
+
A host that checks a sheet can report what passed as well as what failed:
|
|
629
|
+
an entry with `severity: 'pass'` (✓) or `'skipped'` (— not checked; its
|
|
630
|
+
`message` says why). Give them with `setErrors()` or from a plugin's
|
|
631
|
+
`validate`. Results stay quiet:
|
|
632
|
+
|
|
633
|
+
| `results=` | errors panel | sheet |
|
|
634
|
+
|---|---|---|
|
|
635
|
+
| `off` | results are dropped on arrival | as without results |
|
|
636
|
+
| `summary` (default) | one line — *1 problem · ✓ 180 passed · — 28 not checked · On sheet* — and *Passed* / *Not checked* chips; the list opens on *Problems* | nothing marked; the status line says *186 of 214 checked* |
|
|
637
|
+
| `marks` (or *On sheet*) | the same, switch on | a 9 px ✓ or — in each result cell's bottom-left corner |
|
|
638
|
+
|
|
639
|
+
Passes are listed grouped by check (`type` + `message`), not-checked cells
|
|
640
|
+
by reason (`message`); a group opens to its cells, and **Show ▸** selects its
|
|
641
|
+
range and marks it even at `summary`. Selecting a result cell names its check
|
|
642
|
+
in the cell bar. A problem on the same cell always wins. Tab counts, the
|
|
643
|
+
toolbar counts, the panel's *n open* and the severity filters count problems
|
|
644
|
+
only, and a download leaves results out (`@sheetpane/comments` writes them
|
|
645
|
+
only with `export({ includeResults: true })`). A host that switches on
|
|
646
|
+
`severity` meets two more values.
|
|
647
|
+
|
|
648
|
+
```js
|
|
649
|
+
errors.setErrors([
|
|
650
|
+
{ v: 1, id: 'ERR-0001', sheet: 'Holdings', range: 'H3', type: 'fee-rate', severity: 'pass', message: 'Fee rate 0.00%–2.00%',
|
|
651
|
+
found: '0.35%', expected: '', cellChanged: false, status: 'open', matchesExpected: false, author: 'host', createdAt: new Date().toISOString() },
|
|
652
|
+
]);
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## Protected sheets
|
|
656
|
+
|
|
657
|
+
What a file protects stays protected in Edit, with no plugin. On a protected
|
|
658
|
+
sheet (Excel: Review › Protect Sheet) a locked cell — every cell is locked
|
|
659
|
+
until someone unlocks it — shows its value and *🔒 Locked in the file* in the
|
|
660
|
+
cell bar; typing, paste and Delete there are refused with
|
|
661
|
+
`sheetpane-editcancel` `{ reason: 'locked', code: 'SP-3026' }`, before
|
|
662
|
+
`sheetpane-beforechange`. Unlocked cells edit as usual, and a paste or a
|
|
663
|
+
range Delete writes those only, saying how many locked cells it left. A
|
|
664
|
+
formula marked *Hidden* shows its value, never its formula. A workbook whose
|
|
665
|
+
structure is protected keeps its sheets and its rows and columns: Hide and
|
|
666
|
+
Unhide sheet, and every insert and delete, are disabled with the reason; a
|
|
667
|
+
protected sheet refuses the inserts and deletes it does not allow, as Excel
|
|
668
|
+
does (`SP-3026`).
|
|
669
|
+
|
|
670
|
+
```js
|
|
671
|
+
const wb = excel.workbook;
|
|
672
|
+
wb.isLocked('Spec', 'A2'); // true: a locked cell on a protected sheet
|
|
673
|
+
wb.isFormulaHidden('Spec', 'E2'); // true: shows its value only
|
|
674
|
+
wb.structureLocked(); // true: <workbookProtection lockStructure="1">
|
|
675
|
+
wb.sheetProtection('Spec'); // { formatCells: true, …, hasPassword: true } — never the password
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
sheetpane never reads, checks or removes a protection password, and the
|
|
679
|
+
download carries the file's protection exactly as it was. `protection="ignore"`
|
|
680
|
+
sets it aside for the pane — **the host's decision**: a host that lets people
|
|
681
|
+
edit what the file locks owns that choice.
|
|
682
|
+
|
|
402
683
|
## Plugins
|
|
403
684
|
|
|
404
685
|
Features beyond review — every Excel number format, live formulas, cell
|