Skip to content

v0.4.0 — results, review layers, insert and delete rows, Excel tables and three new plugins

Choose a tag to compare

@arpitarya arpitarya released this 19 Oct 09:52
22d2d2b

The features a host's review flow needs from a spreadsheet, built together
(W-50–W-69): results as well as faults, several sets of errors in one pane,
errors about a whole sheet or workbook, accepted deviations, an Edits view,
host data in an error card; insert and delete rows and columns with every
reference shifted; rich text, Excel tables, dependent dropdowns, locked cells
and protection; dropdowns and conditional formats written into downloads; the
pane's checks and a static HTML render in Node; and three new plugins —
@sheetpane/tables, @sheetpane/review (the review as its own sheet)
and @sheetpane/compare (what changed between two versions). Law L11 is
new: the workbook never leaves the browser.

npm i @sheetpane/grid@0.4.0
npm i @sheetpane/tables@0.4.0 @sheetpane/review@0.4.0 @sheetpane/compare@0.4.0   # the new plugins, as needed

Before you upgrade from 0.3.0:

  • Severity has two more values, pass and skipped: code that switches
    exhaustively on severity must handle them (isResult(e) tells a result
    from a problem).
  • SheetpaneError.range may be "" (the whole sheet), and sheet too (the
    workbook): code that reads range as A1 should use errorScope(e).
  • status may be accepted: count open errors as status === 'open', not
    !== 'resolved'.
  • WorkbookStore.setErrors(list, options?, origin?): origin moved from the
    second argument to the third. The elements' setErrors are unchanged.
  • A plugin's derive receives a third input kind, restructure: a provider
    that narrowed on load alone must also handle it (return the whole derived
    set).
  • The sheetpane-errors detail is now the envelope v: 2
    ({ v, errors, plugins, layers, changed }); a review saved before still
    restores.
  • In Edit mode <sheetpane-errors> always shows its Problems · Edits switch.

Added

  • Law L11 — the workbook never leaves the browser (SR-LAW-11). sheetpane reads, edits, checks and exports a file inside the page and sends nothing to any server: no file bytes, cells, edits, errors, exports or telemetry. Its only network calls are the GETs for the src and rules-src the host sets. A test in test/packages.test.ts fails if any package adds another. The sheetpane-backend, sheetpane-load-export and sheetpane-security skills now say that a server save is the host's own code and choice.
  • Two new severities: pass and skipped (not checked) — results as well as faults (W-50; SR-ERRORS, SR-GRID). A host or a plugin's validate can report a passed cell and a cell it did not check; a v: 1 review without them loads as before. A host that switches exhaustively on severity meets two more values — isResult(e) tells a result from a problem. Results stay quiet: the new results attribute (on either element, pane-wide) is summary by default — one line of counts in <sheetpane-errors> (1 problem · ✓ 180 passed · — 28 not checked · On sheet), Passed / Not checked chips, passes grouped by check and not-checked cells by reason, nothing on the sheet and 186 of 214 checked in the status line; marks (or the panel's On sheet switch) adds a 9 px ✓ or — in each result cell's bottom-left corner (new token --sp-skip); off drops results on arrival. Tab counts, the toolbar counts, n open and the severity filters count problems only. @sheetpane/comments writes results into a download only with export({ includeResults: true }).
  • @sheetpane/grid: review layers (W-51). Every error has a layer (review unless named). setErrors(list, { layer }) replaces that layer only, keeping the others and the undo stack; getErrors({ layer }) reads one. A layers attribute and property on either element chooses the layers shown — the pane's one setting — and <sheetpane-errors> shows a switch (All, then each layer with its open count; names from layerNames) when the pane has two or more. The sheet's pins, marks, counts and tab counts, the list and the comments export follow what is shown; a person's new error goes into the layer shown. A validate draft may name a layer. New: sheetpane-layers {visible, layers}, setVisibleLayers(), snapshot.layers, visibleLayers and visibleErrors, layerOf(), isLayerName(), DEFAULT_LAYER, the ErrorsEnvelope type and SP-3021 (a name that is not one).
  • @sheetpane/comments: an export writes the errors of the layers shown; export({ layers: [...] }) names another set (W-51).
  • sheetpane-errors carries the review envelope v: 2 (W-51): { v, errors, plugins, layers, changed }, and every error in it has a layer. Save the whole detail and give it back with setErrors(saved); a review saved before ({ errors, plugins }, or a bare list) restores into the review layer as before.
  • @sheetpane/grid: errors with no cell (W-55). range: "" is an error about the whole sheet; sheet: "" with range: "", about the whole workbook. <sheetpane-errors> lists them first, in a Workbook group and then one group per sheet, above the cell errors. A whole-sheet error counts on its tab, and Show in sheet opens that tab. For a workbook-level error the button is disabled and says why. Neither kind is placed on A1, gets a pin or marks a cell. New: errorScope(e) (cell, sheet or workbook) and the ErrorScope type.
  • @sheetpane/comments: an error with no cell is not written as an Excel comment, because a comment needs a cell. lastExport.skipped lists it with reason: 'no-cell' and the new code SP-5804.
  • @sheetpane/grid: room for the host in an error (W-68). SheetpaneError.meta holds any JSON of the host's, up to 64 KiB (ERROR_META_MAX). It is saved and restored with the error and never written into the file. renderDetail(error), a new property on <sheetpane-excel> (the error card) and <sheetpane-errors> (the open row), returns a string, a Node or null. The result is drawn after the message in ::part(detail), and the hook runs again only when that error changes. A string is shown as text, never as HTML. New: the ErrorMeta and RenderDetail types, and the codes SP-3016 (a meta that is not JSON or is over the cap is dropped; the error is kept) and SP-3017 (renderDetail threw; nothing is shown).
  • @sheetpane/grid: accepted deviations (W-69). An error's status may now be accepted, with acceptedBy, acceptedAt and a required reason: a known, approved difference that stays in the review and the download but is not counted as open. Accept… in an open row of <sheetpane-errors> asks why and records the element's author as who; an Accepted n chip lists them, and the severity chips, "n open" and the sheet's counts leave them out. In the sheet the pin reads A and the error card shows who, when and why, with Reopen. Undo and Reopen take an acceptance back. New: workbook.acceptError(id, reason, { by }), the ErrorStatus type, and the code SP-3018 (a blank reason, or an error the workbook itself wrote, is not accepted; restored without a reason, it comes back open). Code that counted open errors as status !== 'resolved' should use status === 'open'.
  • @sheetpane/comments: an accepted error gets one more reply in its thread, Accepted: , written by who accepted it, and its thread is marked done (W-69).
  • @sheetpane/grid: the Edits view (W-64). <sheetpane-errors> gets an Edits n chip beside Problems: every change in the pane, newest first — the cell or range and kind, was → now, the time, and links to the errors that edit cleared or raised. Picking a row selects its cell. Revert on a row and Revert all are each one undo step. It shows in Edit mode, and in View mode while the pane holds edits; in Edit mode the panel now always shows the Problems · Edits switch. New on the store: edits(), editEffects(key) ({ cleared, raised, matched }) and revertEdits(keys?); new exports editRef, Edit and EditEffects.
  • Insert and delete rows and columns, with every reference shifted (W-52; SR-GRID decision 21d, SR-EXPORT decision 11, SR-IMPORT decision 14). @sheetpane/grid: in Edit, the cell menu, the ⋯ menu and the header menu insert rows above or below and columns left or right, and delete them; Ctrl/⌘+Shift+= and Ctrl/⌘+− do it from the keyboard. The store gains insertRows, deleteRows, insertCols, deleteCols, structureRefusal(op) and structureOps(): one change of kind structure and one undo step each, and sheetpane-change carries { kind: 'structure', op, at, count }. Cell changes, errors (an error whose cells were deleted is resolved, "row deleted"), derived values, merges, freeze, hidden and outline state, the selection, sizes and highlights move with their rows. Refused, as Excel refuses: a delete that splits a merged range (SP-3022), anything that moves a pivot table or its source (SP-3023), cells pushed off the sheet (SP-3024), a table cut (SP-3025). A plugin's derive is told { kind: 'restructure', sheet, op, at, count } — a third DeriveInput kind: a provider that narrowed on load alone must now also handle it (it returns the whole derived set, as on a load). @sheetpane/import: restructureWorkbook(book, op) moves every table of the imported workbook, the chart parts part() serves included; PivotTable.source names a pivot's worksheet source. @sheetpane/export: patchXlsx(…, { structure }) replays the operations on the upload first — rows and cells, formulas on every sheet, merges, column spans, the autofilter, validations, conditional formats, hyperlinks, the sheet view, tables, drawing anchors, notes and their VML, threaded comments, chart series and defined names — and refuses (SP-2016) to move a pivot or cut a table or array formula. restructureFormula, shiftRefText, shiftSqref and the other shifts are exported for plugins (re-exported by the grid). @sheetpane/formulas rebuilds from the moved workbook; @sheetpane/charts follows through the moved chart parts. A CSV inserts and deletes rows and columns too. Opening a shifted Excel-saved file in Excel is owed (W-30).
  • @sheetpane/import, @sheetpane/styles and @sheetpane/grid: rich text (W-53). A cell with one word in italic is read as runs. Each sheet's runs maps the cell to [{ text, b, i, u, strike, color, sz, vertAlign }], from shared and inline strings, with phonetic runs left out; rows and text are unchanged. With styles on, the grid draws the runs as spans: weight, style, underline and strike, size, colour, and superscript and subscript. It never draws a font family. A plugin draws runs through the new optional decorateRuns on the decorate hook. Editing a rich cell writes plain text. The cell bar warns first, and sheetpane-editstart carries richText: true. Rich cells left untouched, and the shared strings, export byte for byte.
  • @sheetpane/grid: sheetpane-hover names the cell under a mouse or pen pointer — { sheet, ref, column }, at most once per frame and only when the cell changes — and null when the pointer leaves the cells; a finger never fires it. highlight(ranges, { tone, label, sheet, pinned, reveal }) outlines cells from outside without selecting them: several ranges, info, pass or fail, clicks pass through, not a change and not in the download, cleared on a sheet switch unless pinned; reveal scrolls it into view and the label is said once by the status line. clearHighlight() removes it. Typed in the manifest and the framework typings (W-56, SR-GRID decision 11c).
  • @sheetpane/grid: open straight at a cell (W-66, SR-GRID decision 11d). The ref attribute and property (B4, Data!B4, 'Q3 data'!B4:D8) is the selection once a file is read, scrolled into view; one that names no cell warns SP-3019 and the sheet opens at A1. link="hash" (off by default) keeps the selection in the URL fragment as #sheetpane=<pane>:<Sheet>!<ref> — only that parameter is touched, after a hash router's path (#/reports/12?sheetpane=…) or the host's own parameters; written with replaceState once the selection settles (no history entry, no hashchange), read when a file is read and on back and forward. With it, Copy link joins the cell menu after Copy ref. The fragment is never sent to a server. React, Vue and Solid typings leave ref out (it is the framework's own ref); set it there with setAttribute.
  • @sheetpane/import and @sheetpane/grid: the file's SHA-256 (W-65). read() returns sha256, 64 lowercase hex digits of the bytes opened, and sha256(bytes) is exported. sheetpane-load and sheetpane-export carry sha256 — the file opened and the file handed back — and so does store.exportFile(). On a page without Web Crypto (not https or localhost) it is undefined with a console warning, SP-1009, never a substitute hash. The demos' export line shows it.
  • The pane's checks with no page: @sheetpane/grid/check and @sheetpane/validation/check (W-54; SR-GRID decision 22f, SR-VALIDATION decision 11). check(bytes, { name, rules, columns, validate, plugins }) runs what <sheetpane-excel> runs on load — the rules file, column rules, an async validate, each plugin's validate after a formula engine has recalculated — in Node, a worker or a page, and returns the errors the pane would hold, sorted by sheet, row and column; only id and createdAt differ. The entries import no element and touch no DOM: a test walks their import graphs, and a parity test holds the pane in a browser and check() in Node to one golden on every @sheetpane/fixtures workbook. @sheetpane/validation/check is the validation plugin with no registration and no Write rules as Excel dropdowns switch (that is the main entry's, W-58), plus check() with it on. A new code, SP-3020, is thrown when check() gets something other than bytes. @sheetpane/validation now imports shiftFormula from @sheetpane/export (a new dependency), so its main entry needs no new import-map line.
  • @sheetpane/mcp's check_rules runs the pane's own check() over the workbook it is given: the workbook notes come from the grid (SP-6023 for a "*" title now sits on the first sheet's header cell, worded as the grid words it; SP-6024 only for a duplicated title a rule names), and the failing cells are listed as findings (the first 50) with findingCount.
  • A range as static HTML: @sheetpane/grid/static (W-67; SR-GRID decision 22g). renderStatic(book, { sheet, range, errors, theme, headers }) returns one self-contained <table> — the text the pane shows (the format plugin), the look it draws (decorate plugins such as styles and condfmt, merges, the light or dark tokens as inline colours), hidden rows and columns left out, and the errors marked with the grid's ring and tint plus a numbered glyph the footer explains — for a report or an email. It runs in Node, a worker or a page; the output has no script, event attribute, class, stylesheet, URL or font family, and escapes every value. Pictures and charts are not drawn; the footer says so. A test compares every cell with the grid's computed styles on the fixtures in Chromium, and a fast-check fuzz holds the escaping. loadWorkbook(bytes, options) (also on @sheetpane/grid/check) gives the loaded store check() reads. New codes SP-3027 (not a loaded workbook) and SP-3028 (a sheet or range it cannot draw). It draws the later marks as the grid does: results (✓ passed, — not checked) only at the pane's results="marks", an accepted deviation with its reason, the visible layers' errors by default, errors about the whole sheet or workbook in the footer, and rich text's runs (no font family).
  • @sheetpane/export: dropdowns and conditional formats in the files it writes (W-57). A sheet's dataValidations and conditionalFormats, and the workbook's dxfs, are written by toXlsx as Excel's own rules — the shapes @sheetpane/import reads, so a rule read from a file is written back unchanged. patchXlsx takes dataValidations and conditionalFormats per sheet (the whole set; [] removes it) and dxfs to add, and leaves every other sheet and part byte for byte; setDataValidations and setConditionalFormats do the same from an exportPatch hook. A rule Excel would repair (an unknown type, a title over 32 characters, a typed-out list over 255, a format that does not exist) is refused with SP-2015. The rule and format types (DataValidation, ConditionalFormat, CfRule, Dxf, Font, Fill, Border, Color…) now live in @sheetpane/export; @sheetpane/import re-exports them unchanged.
  • @sheetpane/validation: the rules file written into the download as Excel's own dropdowns and checks (W-58). With Write rules as Excel dropdowns on in the Download menu — or export({ rulesAsValidation: true }), or validation({ rulesAsValidation: true }) to start with it on — the .xlsx carries the host's rules file and column rules as the workbook's data validation: oneOf and equals become a dropdown list, integer / number with min / max a whole-number or decimal check, date a date check, minLength / maxLength a text-length check, required turns off "ignore blank", and the rule's message is Excel's error text. What Excel cannot hold — a pattern, noneOf, unique, two checks in one rule, a list value with a comma — is not written and is listed on the console with its code (SP-5401 – SP-5405) and on the status line. Where the workbook already has a rule, the workbook's rule wins and the conflict is listed (SP-5406); a rule that checks the same thing joins it instead of repeating it. rulesAsValidations(store) gives the plan, lastRulesWrite(book) the report. A plugin's Download-menu switch can now say when it applies: the switch is hidden without rules, and while it is on an untouched workbook is exported rather than handed back as the original file.
  • Dependent dropdowns (W-60). @sheetpane/validation checks a list whose source is INDIRECT(<cell>) — Excel's Unit depends on Parameter pattern: each row reads its parent cell's text and lists what it names, a defined name (any case, the sheet's own first), a table or table column, or an A1 range; INDIRECT("text") too. A parent edit re-checks the children in the same edit, so a unit the new parameter does not allow is a warning, and the list arrow offers the new parameter's units. A parent that names nothing lets anything in under "Ignore blank" and nothing without it, as Excel does. INDIRECT(SUBSTITUTE(…)), &, R1C1 and OFFSET are listed as skipped, not guessed. @sheetpane/fixtures adds dependent-lists.xlsx (one defined name per parameter, one stale unit).
  • Excel tables (W-61). @sheetpane/import reads each sheet's tables (sheet.tables: name, range, header and totals rows, columns, style). @sheetpane/export adds resolveStructuredRef('Units[Unit]', book.sheets), which turns a table reference — Units[Unit], Units[[#Data],[Unit]], #Headers, #Totals, #All, a column span — into its cells. @sheetpane/validation uses it, so a dropdown whose source is a table column (directly, or through a defined name such as UnitList) offers the table's values and checks against them. The new plugin @sheetpane/tables draws each table in its style: header row, banded rows and columns, first and last column, totals row. It covers Excel's Light 1–7 and Medium 1–7 in the workbook's theme colours; other styles get a bold header. A cell's own format and conditional formats still draw over the table. A download keeps the file's tables. Both demos gain a Tables tab, and @sheetpane/fixtures' showcase a Tables sheet (SR-TABLES).
  • Locked cells and sheet protection (W-62). A protected sheet stays protected in Edit: its locked cells — Excel's default — refuse typing, paste and Delete with Locked in the file (sheetpane-editcancel { reason: 'locked', code: 'SP-3026' }, before sheetpane-beforechange), its unlocked cells edit as usual, and a paste or range Delete writes the unlocked cells only and says how many it left. A formula marked Hidden shows its value, never its formula. A protected workbook structure keeps its sheets: @sheetpane/visibility's Hide sheet and the hidden-sheet banner's Unhide are disabled with the reason. Inserting and deleting rows and columns is refused, greyed with the reason (SP-3026), under the structure lock and — as Excel does — on a protected sheet without Insert rows, Delete rows, Insert columns or Delete columns. New store API: isLocked(), isFormulaHidden(), structureLocked(), sheetProtection(), setProtection(), and snapshot helpers for plugin controls. protection="ignore" on <sheetpane-excel> sets the file's protection aside — the host's decision. @sheetpane/import reads sheet.protection, workbookProtection, each format's protection and cellProtection(); sheetpane never reads, checks or removes a password, and the download keeps the file's protection exactly. @sheetpane/fixtures adds protected.xlsx (SR-GRID decision 21e).
  • @sheetpane/review, a new plugin (W-59, SR-REVIEW). On download it adds a Review sheet that lists every error: ID, sheet, cell, severity, type, found, expected, message, author, status, and for an accepted error Accepted: with who accepted it and when. The Cell column links to the cell. Errors about a whole sheet or the workbook are listed too, never coloured. Optionally, each open error's cells are filled by severity: the cell's own format is cloned, so its number format, font and borders stay, and every other cell keeps its exact bytes. The Download menu gains Add a Review sheet (on) and Colour flagged cells (off); export({ review, reviewFills, reviewSeverities, reviewLayers }) sets them for one export. A user's own sheet called Review is never overwritten: the new sheet is Review (2). A hidden defined name, _sheetpane_review, marks the sheet, and reviewSheetOf(book) finds it, so a host can skip it when the file comes back. Exporting the file again rewrites the plugin's own sheet instead of adding a second one. New codes: SP-5001 – SP-5005. @sheetpane/compare's compare layer is a comparison, not the review: it is listed and coloured only when layers or reviewLayers names it, as @sheetpane/comments writes it.
  • @sheetpane/compare — what changed between two versions of a workbook (SR-COMPARE). diffWorkbooks(a, b, { key }) runs in Node or the browser: sheets added, removed and renamed; rows added, removed, changed and — with key columns — moved; cells changed by value, display text or formula (a formula that only moved with its row is not a change); merges, hidden rows and columns, rule counts. @sheetpane/compare/ui (plugins="compare") compares the open file with a base the user picks (Compare…) or the host passes (compareWith), both read in the page: changed cells amber, added rows green, moved rows blue, a red line where rows were removed, n sheets · n rows · n cells changed in the status line, Alt+F5 / Alt+Shift+F5 to step through, and each change in the errors list. 50,000 rows diff in about 0.1 s. New code SP-5901 (a per-sheet key that is not a column). The demos gain a Compare tab.
  • @sheetpane/mcp: a seventh tool, diff_workbooks(base, path, key?), on the same diff.
  • @sheetpane/fixtures: compare/base.xlsx and compare/now.xlsx, two versions of one spec workbook.

Changed

  • @sheetpane/compare: the changes are now the compare review layer (W-51, W-63) — listing them keeps your review and the undo history, and the errors panel's layer switch shows them apart. @sheetpane/comments leaves the layer out of a download unless export({ layers: [..., 'compare'] }) names it.

Fixed

  • @sheetpane/grid: a cell selected and scrolled to right after a file is read (select(), or ref) could land just under the bottom edge — the status line's first content shrank the grid after the scroll. The status line is now laid out first (W-66).
  • @sheetpane/grid: an open workbook or host error now follows its rule's expected too, not only found and message — a list whose choices changed showed the old choices (W-60).
  • Demos (examples/html, examples/react): redesigned in the Proof look. A host bar with Views, Open file…, the CSV sample, the pop-up, Font and a new Theme switch (light, dark, auto, applied to every component). A Demo row of tabs, with Review, the plugins and the combinations set apart. Each demo's blurb shows the attribute that turns it on. Both components fill the window, and a footer shows the host events (change, errors, export). Every hook the demo check uses is kept; demo-check passes 51 of 51.
  • @sheetpane/grid: the toolbar stays one row, shedding the change label, find width, counts and file meta as the element narrows, and the Download ▾ menu no longer opens off the left edge. The format row stays one row, with a More ▾ for the groups that do not fit. Docked plugin buttons fold into ⋯ before they squeeze the value field, and greyed or header- and tab-only menu actions stay out of the cell bar. The grid's focus ring shows only for the keyboard, drawn over the cells instead of as a stray frame beside them. The right-click menu separates Flag issue and plugin items. Checked against the design canvas in the plain-HTML demo.