Repository navigation
v0.4.0 — results, review layers, insert and delete rows, Excel tables and three new plugins
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 neededBefore you upgrade from 0.3.0:
Severityhas two more values,passandskipped: code that switches
exhaustively onseveritymust handle them (isResult(e)tells a result
from a problem).SheetpaneError.rangemay be""(the whole sheet), andsheettoo (the
workbook): code that readsrangeas A1 should useerrorScope(e).statusmay beaccepted: count open errors asstatus === 'open', not
!== 'resolved'.WorkbookStore.setErrors(list, options?, origin?):originmoved from the
second argument to the third. The elements'setErrorsare unchanged.- A plugin's
derivereceives a third input kind,restructure: a provider
that narrowed onloadalone must also handle it (return the whole derived
set). - The
sheetpane-errorsdetail is now the envelopev: 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
srcandrules-srcthe host sets. A test intest/packages.test.tsfails if any package adds another. Thesheetpane-backend,sheetpane-load-exportandsheetpane-securityskills now say that a server save is the host's own code and choice. - Two new severities:
passandskipped(not checked) — results as well as faults (W-50; SR-ERRORS, SR-GRID). A host or a plugin'svalidatecan report a passed cell and a cell it did not check; av: 1review without them loads as before. A host that switches exhaustively onseveritymeets two more values —isResult(e)tells a result from a problem. Results stay quiet: the newresultsattribute (on either element, pane-wide) issummaryby 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);offdrops results on arrival. Tab counts, the toolbar counts, n open and the severity filters count problems only.@sheetpane/commentswrites results into a download only withexport({ includeResults: true }). @sheetpane/grid: review layers (W-51). Every error has alayer(reviewunless named).setErrors(list, { layer })replaces that layer only, keeping the others and the undo stack;getErrors({ layer })reads one. Alayersattribute 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 fromlayerNames) 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. Avalidatedraft may name alayer. New:sheetpane-layers {visible, layers},setVisibleLayers(),snapshot.layers,visibleLayersandvisibleErrors,layerOf(),isLayerName(),DEFAULT_LAYER, theErrorsEnvelopetype 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-errorscarries the review envelopev: 2(W-51):{ v, errors, plugins, layers, changed }, and every error in it has alayer. Save the wholedetailand give it back withsetErrors(saved); a review saved before ({ errors, plugins }, or a bare list) restores into thereviewlayer as before.@sheetpane/grid: errors with no cell (W-55).range: ""is an error about the whole sheet;sheet: ""withrange: "", 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,sheetorworkbook) and theErrorScopetype.@sheetpane/comments: an error with no cell is not written as an Excel comment, because a comment needs a cell.lastExport.skippedlists it withreason: 'no-cell'and the new code SP-5804.@sheetpane/grid: room for the host in an error (W-68).SheetpaneError.metaholds 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 ornull. 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: theErrorMetaandRenderDetailtypes, and the codes SP-3016 (ametathat is not JSON or is over the cap is dropped; the error is kept) and SP-3017 (renderDetailthrew; nothing is shown).@sheetpane/grid: accepted deviations (W-69). An error'sstatusmay now beaccepted, withacceptedBy,acceptedAtand a requiredreason: 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'sauthoras 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 }), theErrorStatustype, 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 asstatus !== 'resolved'should usestatus === '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 }) andrevertEdits(keys?); new exportseditRef,EditandEditEffects.- 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 gainsinsertRows,deleteRows,insertCols,deleteCols,structureRefusal(op)andstructureOps(): one change of kindstructureand one undo step each, andsheetpane-changecarries{ 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'sderiveis told{ kind: 'restructure', sheet, op, at, count }— a thirdDeriveInputkind: a provider that narrowed onloadalone 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 partspart()serves included;PivotTable.sourcenames 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,shiftSqrefand the other shifts are exported for plugins (re-exported by the grid).@sheetpane/formulasrebuilds from the moved workbook;@sheetpane/chartsfollows 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/stylesand@sheetpane/grid: rich text (W-53). A cell with one word in italic is read as runs. Each sheet'srunsmaps the cell to[{ text, b, i, u, strike, color, sz, vertAlign }], from shared and inline strings, with phonetic runs left out;rowsandtextare unchanged. Withstyleson, 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 optionaldecorateRunson thedecoratehook. Editing a rich cell writes plain text. The cell bar warns first, andsheetpane-editstartcarriesrichText: true. Rich cells left untouched, and the shared strings, export byte for byte.@sheetpane/grid:sheetpane-hovernames the cell under a mouse or pen pointer —{ sheet, ref, column }, at most once per frame and only when the cell changes — andnullwhen 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,passorfail, clicks pass through, not a change and not in the download, cleared on a sheet switch unless pinned;revealscrolls 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). Therefattribute 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 warnsSP-3019and 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 withreplaceStateonce the selection settles (no history entry, nohashchange), 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 leaverefout (it is the framework's own ref); set it there withsetAttribute.@sheetpane/importand@sheetpane/grid: the file's SHA-256 (W-65).read()returnssha256, 64 lowercase hex digits of the bytes opened, andsha256(bytes)is exported.sheetpane-loadandsheetpane-exportcarrysha256— the file opened and the file handed back — and so doesstore.exportFile(). On a page without Web Crypto (nothttpsorlocalhost) it isundefinedwith a console warning, SP-1009, never a substitute hash. The demos' export line shows it.- The pane's checks with no page:
@sheetpane/grid/checkand@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 asyncvalidate, each plugin'svalidateafter 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; onlyidandcreatedAtdiffer. 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 andcheck()in Node to one golden on every@sheetpane/fixturesworkbook.@sheetpane/validation/checkis the validation plugin with no registration and no Write rules as Excel dropdowns switch (that is the main entry's, W-58), pluscheck()with it on. A new code, SP-3020, is thrown whencheck()gets something other than bytes.@sheetpane/validationnow importsshiftFormulafrom@sheetpane/export(a new dependency), so its main entry needs no new import-map line. @sheetpane/mcp'scheck_rulesruns the pane's owncheck()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 asfindings(the first 50) withfindingCount.- 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 (theformatplugin), the look it draws (decorateplugins 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 storecheck()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'sresults="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'sdataValidationsandconditionalFormats, and the workbook'sdxfs, are written bytoXlsxas Excel's own rules — the shapes@sheetpane/importreads, so a rule read from a file is written back unchanged.patchXlsxtakesdataValidationsandconditionalFormatsper sheet (the whole set;[]removes it) anddxfsto add, and leaves every other sheet and part byte for byte;setDataValidationsandsetConditionalFormatsdo the same from anexportPatchhook. 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/importre-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 — orexport({ rulesAsValidation: true }), orvalidation({ rulesAsValidation: true })to start with it on — the.xlsxcarries the host's rules file and column rules as the workbook's data validation:oneOfandequalsbecome a dropdown list,integer/numberwithmin/maxa whole-number or decimal check,datea date check,minLength/maxLengtha text-length check,requiredturns off "ignore blank", and the rule'smessageis Excel's error text. What Excel cannot hold — apattern,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 itapplies: 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/validationchecks a list whose source isINDIRECT(<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 andOFFSETare listed as skipped, not guessed.@sheetpane/fixturesaddsdependent-lists.xlsx(one defined name per parameter, one stale unit). - Excel tables (W-61).
@sheetpane/importreads each sheet's tables (sheet.tables: name, range, header and totals rows, columns, style).@sheetpane/exportaddsresolveStructuredRef('Units[Unit]', book.sheets), which turns a table reference —Units[Unit],Units[[#Data],[Unit]],#Headers,#Totals,#All, a column span — into its cells.@sheetpane/validationuses it, so a dropdown whose source is a table column (directly, or through a defined name such asUnitList) offers the table's values and checks against them. The new plugin@sheetpane/tablesdraws 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' }, beforesheetpane-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/importreadssheet.protection,workbookProtection, each format'sprotectionandcellProtection(); sheetpane never reads, checks or removes a password, and the download keeps the file's protection exactly.@sheetpane/fixturesaddsprotected.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, andreviewSheetOf(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'scomparelayer is a comparison, not the review: it is listed and coloured only whenlayersorreviewLayersnames it, as@sheetpane/commentswrites 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.xlsxandcompare/now.xlsx, two versions of one spec workbook.
Changed
@sheetpane/compare: the changes are now thecomparereview 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/commentsleaves the layer out of a download unlessexport({ layers: [..., 'compare'] })names it.
Fixed
@sheetpane/grid: a cell selected and scrolled to right after a file is read (select(), orref) 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'sexpectedtoo, not onlyfoundandmessage— 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-checkpasses 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.