@svgrid/enterprise 3.0.2 → 3.0.3
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 +94 -24
- package/README.md +36 -4
- package/dist/SvAdvancedFilter.svelte +1 -1
- package/dist/SvAlertsManager.svelte +2 -1
- package/dist/SvBoard.svelte +2 -1
- package/dist/SvExportMenu.svelte +4 -1
- package/dist/SvExportMenu.svelte.d.ts +0 -13
- package/dist/SvExpressionEditor.svelte +3 -3
- package/dist/SvFormulaBar.svelte +94 -20
- package/dist/SvFormulaBar.svelte.d.ts +7 -0
- package/dist/SvGridAlerts.svelte +49 -9
- package/dist/SvGridBoard.svelte +13 -8
- package/dist/SvGridBulkEditDrawer.svelte +26 -2
- package/dist/SvGridEditPanel.svelte +3 -0
- package/dist/SvGridScheduler.svelte +69 -40
- package/dist/SvGridSelectionBar.svelte +10 -3
- package/dist/SvPivotDesigner.svelte +198 -40
- package/dist/SvPivotDesigner.svelte.d.ts +27 -20
- package/dist/SvSheet.svelte +4043 -243
- package/dist/SvSheet.svelte.d.ts +76 -3
- package/dist/SvSheetChartSetup.svelte +151 -0
- package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
- package/dist/SvSheetComment.svelte +216 -31
- package/dist/SvSheetComment.svelte.d.ts +11 -11
- package/dist/SvSheetConditionalFormat.svelte +28 -31
- package/dist/SvSheetDataValidation.svelte +66 -42
- package/dist/SvSheetEditRanges.svelte +217 -0
- package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
- package/dist/SvSheetErrors.svelte +125 -0
- package/dist/SvSheetErrors.svelte.d.ts +20 -0
- package/dist/SvSheetEvaluate.svelte +133 -0
- package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
- package/dist/SvSheetFilterMenu.svelte +137 -34
- package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
- package/dist/SvSheetFindReplace.svelte +19 -19
- package/dist/SvSheetFormatCells.svelte +125 -81
- package/dist/SvSheetGoalSeek.svelte +17 -16
- package/dist/SvSheetInsertFunction.svelte +15 -13
- package/dist/SvSheetIteration.svelte +81 -0
- package/dist/SvSheetIteration.svelte.d.ts +10 -0
- package/dist/SvSheetLink.svelte +99 -0
- package/dist/SvSheetLink.svelte.d.ts +17 -0
- package/dist/SvSheetListPicker.svelte +4 -2
- package/dist/SvSheetListPicker.svelte.d.ts +0 -6
- package/dist/SvSheetManageRules.svelte +20 -18
- package/dist/SvSheetNameManager.svelte +17 -15
- package/dist/SvSheetPageSetup.svelte +159 -0
- package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
- package/dist/SvSheetPasteSpecial.svelte +22 -20
- package/dist/SvSheetPivot.svelte +220 -0
- package/dist/SvSheetPivot.svelte.d.ts +21 -0
- package/dist/SvSheetProtectSheet.svelte +81 -0
- package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
- package/dist/SvSheetRemoveDuplicates.svelte +13 -10
- package/dist/SvSheetRibbon.svelte +104 -51
- package/dist/SvSheetSizeDialog.svelte +4 -2
- package/dist/SvSheetSort.svelte +218 -0
- package/dist/SvSheetSort.svelte.d.ts +28 -0
- package/dist/SvSheetSparklines.svelte +135 -0
- package/dist/SvSheetSparklines.svelte.d.ts +14 -0
- package/dist/SvSheetTable.svelte +160 -0
- package/dist/SvSheetTable.svelte.d.ts +24 -0
- package/dist/SvSheetTabs.svelte +161 -31
- package/dist/SvSheetTabs.svelte.d.ts +22 -8
- package/dist/SvSheetTextToColumns.svelte +20 -20
- package/dist/SvSheetValidationAlert.svelte +8 -6
- package/dist/advanced-filter-enable.js +1 -1
- package/dist/alerts/alert-engine.d.ts +6 -0
- package/dist/alerts/alert-engine.js +21 -4
- package/dist/alerts/alert-formats.js +2 -0
- package/dist/alerts/alert-types.d.ts +1 -1
- package/dist/alerts.d.ts +1 -1
- package/dist/alerts.js +2 -2
- package/dist/board.js +2 -2
- package/dist/bulk-edit.d.ts +3 -1
- package/dist/bulk-edit.js +1 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
- package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
- package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
- package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
- package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
- package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
- package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
- package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
- package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
- package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
- package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
- package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
- package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
- package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
- package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
- package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
- package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
- package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
- package/dist/designer/assets/{index-DwU7CAMm.js → index-D5bVbuzI.js} +317 -315
- package/dist/designer/assets/index-_JVFxt02.css +1 -0
- package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
- package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
- package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
- package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
- package/dist/designer/index.html +17 -16
- package/dist/export-pdf.d.ts +75 -0
- package/dist/export-pdf.js +216 -15
- package/dist/export.js +10 -2
- package/dist/gantt/SvGridGantt.svelte +2890 -0
- package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
- package/dist/gantt/gantt-config.d.ts +73 -0
- package/dist/gantt/gantt-config.js +1 -0
- package/dist/gantt/gantt-critical-path.d.ts +67 -0
- package/dist/gantt/gantt-critical-path.js +207 -0
- package/dist/gantt/gantt-model.d.ts +257 -0
- package/dist/gantt/gantt-model.js +651 -0
- package/dist/gantt/gantt-resources.d.ts +59 -0
- package/dist/gantt/gantt-resources.js +71 -0
- package/dist/gantt/gantt.d.ts +7 -0
- package/dist/gantt/gantt.js +36 -0
- package/dist/gantt/index.d.ts +15 -0
- package/dist/gantt/index.js +22 -0
- package/dist/gantt/timeline-arrows.d.ts +102 -0
- package/dist/gantt/timeline-arrows.js +181 -0
- package/dist/import.d.ts +2 -2
- package/dist/import.js +1 -1
- package/dist/index.d.ts +34 -7
- package/dist/index.js +44 -7
- package/dist/install.js +8 -1
- package/dist/license-core.d.ts +17 -1
- package/dist/license-core.js +16 -5
- package/dist/license.d.ts +18 -2
- package/dist/license.js +71 -6
- package/dist/node/studio.js +140 -8
- package/dist/pivot-enable.js +2 -2
- package/dist/scheduler-dependencies.d.ts +35 -0
- package/dist/scheduler-dependencies.js +36 -1
- package/dist/scheduler.js +2 -2
- package/dist/selection-bar.js +2 -2
- package/dist/server/SvGroupCell.svelte +213 -0
- package/dist/server/SvGroupCell.svelte.d.ts +62 -0
- package/dist/server/SvRowGroupPanel.svelte +322 -0
- package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
- package/dist/server/enable.d.ts +17 -0
- package/dist/server/enable.js +49 -0
- package/dist/server/index.d.ts +22 -0
- package/dist/server/index.js +24 -0
- package/dist/server/messages.d.ts +45 -0
- package/dist/server/messages.js +35 -0
- package/dist/server/server-group-model.d.ts +104 -0
- package/dist/server/server-group-model.js +272 -0
- package/dist/server/server-pivot.d.ts +46 -0
- package/dist/server/server-pivot.js +57 -0
- package/dist/server/server-row-model.d.ts +547 -0
- package/dist/server/server-row-model.js +1551 -0
- package/dist/server/server-selection.d.ts +99 -0
- package/dist/server/server-selection.js +215 -0
- package/dist/server/svgrid-adapter.d.ts +155 -0
- package/dist/server/svgrid-adapter.js +202 -0
- package/dist/sheet/ast.d.ts +45 -1
- package/dist/sheet/ast.js +14 -0
- package/dist/sheet/auto-filter.d.ts +58 -4
- package/dist/sheet/auto-filter.js +159 -11
- package/dist/sheet/autocomplete.d.ts +9 -0
- package/dist/sheet/autocomplete.js +90 -4
- package/dist/sheet/cell-images.d.ts +39 -0
- package/dist/sheet/cell-images.js +90 -0
- package/dist/sheet/cell-types.d.ts +58 -0
- package/dist/sheet/cell-types.js +96 -0
- package/dist/sheet/coerce.d.ts +40 -0
- package/dist/sheet/coerce.js +170 -3
- package/dist/sheet/comments.d.ts +43 -9
- package/dist/sheet/comments.js +85 -27
- package/dist/sheet/conditional-formats.d.ts +29 -6
- package/dist/sheet/conditional-formats.js +37 -12
- package/dist/sheet/csv.d.ts +32 -0
- package/dist/sheet/csv.js +158 -0
- package/dist/sheet/culture.d.ts +87 -0
- package/dist/sheet/culture.js +247 -0
- package/dist/sheet/delta.d.ts +102 -0
- package/dist/sheet/delta.js +203 -0
- package/dist/sheet/deps.d.ts +45 -1
- package/dist/sheet/deps.js +77 -2
- package/dist/sheet/document.d.ts +106 -4
- package/dist/sheet/document.js +204 -3
- package/dist/sheet/engine.d.ts +67 -0
- package/dist/sheet/engine.js +19 -0
- package/dist/sheet/entry.d.ts +8 -4
- package/dist/sheet/entry.js +171 -21
- package/dist/sheet/error-check.d.ts +49 -0
- package/dist/sheet/error-check.js +83 -0
- package/dist/sheet/evaluate-steps.d.ts +48 -0
- package/dist/sheet/evaluate-steps.js +196 -0
- package/dist/sheet/evaluate.d.ts +67 -0
- package/dist/sheet/evaluate.js +725 -13
- package/dist/sheet/filter-files.d.ts +37 -0
- package/dist/sheet/filter-files.js +465 -0
- package/dist/sheet/format-store.d.ts +15 -0
- package/dist/sheet/format-store.js +11 -1
- package/dist/sheet/from-rows.d.ts +14 -0
- package/dist/sheet/from-rows.js +50 -0
- package/dist/sheet/function-catalog.d.ts +1 -1
- package/dist/sheet/function-catalog.js +296 -5
- package/dist/sheet/functions.js +363 -91
- package/dist/sheet/index.d.ts +35 -8
- package/dist/sheet/index.js +35 -8
- package/dist/sheet/links.d.ts +57 -0
- package/dist/sheet/links.js +174 -0
- package/dist/sheet/messages.d.ts +778 -0
- package/dist/sheet/messages.js +806 -0
- package/dist/sheet/number-format.d.ts +52 -5
- package/dist/sheet/number-format.js +465 -68
- package/dist/sheet/objects.d.ts +106 -0
- package/dist/sheet/objects.js +149 -0
- package/dist/sheet/ods-document.d.ts +44 -0
- package/dist/sheet/ods-document.js +1275 -0
- package/dist/sheet/open-file.d.ts +26 -0
- package/dist/sheet/open-file.js +56 -0
- package/dist/sheet/outline.d.ts +82 -0
- package/dist/sheet/outline.js +228 -0
- package/dist/sheet/packs/array.d.ts +16 -0
- package/dist/sheet/packs/array.js +172 -0
- package/dist/sheet/packs/database.d.ts +42 -0
- package/dist/sheet/packs/database.js +142 -0
- package/dist/sheet/packs/engineering.d.ts +2 -0
- package/dist/sheet/packs/engineering.js +296 -0
- package/dist/sheet/packs/financial.d.ts +5 -0
- package/dist/sheet/packs/financial.js +220 -0
- package/dist/sheet/packs/math-stats.d.ts +2 -0
- package/dist/sheet/packs/math-stats.js +372 -0
- package/dist/sheet/packs/special.d.ts +44 -0
- package/dist/sheet/packs/special.js +187 -0
- package/dist/sheet/packs/stats-dist.d.ts +2 -0
- package/dist/sheet/packs/stats-dist.js +785 -0
- package/dist/sheet/packs/text-date.d.ts +2 -0
- package/dist/sheet/packs/text-date.js +332 -0
- package/dist/sheet/packs/trig.d.ts +2 -0
- package/dist/sheet/packs/trig.js +73 -0
- package/dist/sheet/page-setup.d.ts +73 -0
- package/dist/sheet/page-setup.js +73 -0
- package/dist/sheet/parse.js +42 -10
- package/dist/sheet/paste-special.d.ts +22 -0
- package/dist/sheet/paste-special.js +14 -5
- package/dist/sheet/pivot-range.d.ts +160 -0
- package/dist/sheet/pivot-range.js +333 -0
- package/dist/sheet/presence.d.ts +73 -0
- package/dist/sheet/presence.js +79 -0
- package/dist/sheet/print.d.ts +104 -0
- package/dist/sheet/print.js +169 -0
- package/dist/sheet/protection.d.ts +55 -1
- package/dist/sheet/protection.js +55 -2
- package/dist/sheet/rects.d.ts +1 -1
- package/dist/sheet/refs.d.ts +48 -0
- package/dist/sheet/refs.js +155 -8
- package/dist/sheet/ribbon-icons.d.ts +1 -1
- package/dist/sheet/ribbon-icons.js +186 -0
- package/dist/sheet/ribbon.d.ts +1 -1
- package/dist/sheet/ribbon.js +324 -23
- package/dist/sheet/sheet-kinds.d.ts +74 -0
- package/dist/sheet/sheet-kinds.js +91 -0
- package/dist/sheet/shortcuts.d.ts +13 -3
- package/dist/sheet/shortcuts.js +63 -12
- package/dist/sheet/sort.d.ts +51 -0
- package/dist/sheet/sort.js +107 -0
- package/dist/sheet/sparklines.d.ts +111 -0
- package/dist/sheet/sparklines.js +204 -0
- package/dist/sheet/table-styles.d.ts +74 -0
- package/dist/sheet/table-styles.js +152 -0
- package/dist/sheet/tables.d.ts +19 -0
- package/dist/sheet/tables.js +31 -0
- package/dist/sheet/tokenize.d.ts +18 -1
- package/dist/sheet/tokenize.js +60 -9
- package/dist/sheet/validation.d.ts +16 -0
- package/dist/sheet/validation.js +37 -0
- package/dist/sheet/workbook.d.ts +148 -3
- package/dist/sheet/workbook.js +742 -31
- package/dist/sheet/xls-cfb.d.ts +32 -0
- package/dist/sheet/xls-cfb.js +290 -0
- package/dist/sheet/xls-document.d.ts +10 -0
- package/dist/sheet/xls-document.js +1243 -0
- package/dist/sheet/xls-formula.d.ts +40 -0
- package/dist/sheet/xls-formula.js +569 -0
- package/dist/sheet/xlsx-document.d.ts +108 -0
- package/dist/sheet/xlsx-document.js +1849 -0
- package/dist/sheet/xlsx-drawing.d.ts +99 -0
- package/dist/sheet/xlsx-drawing.js +420 -0
- package/dist/sheet-enable.js +6 -7
- package/dist/sheet-text.d.ts +7 -0
- package/dist/sheet-text.js +29 -0
- package/dist/sources/auth-supabase.js +2 -2
- package/dist/sources/realtime-supabase.js +2 -2
- package/dist/sources/rest.d.ts +6 -2
- package/dist/sources/rest.js +42 -7
- package/dist/sources/supabase.d.ts +10 -1
- package/dist/sources/supabase.js +73 -40
- package/dist/studio/emit-project.js +14 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio/project.d.ts +11 -2
- package/dist/studio/project.js +5 -2
- package/dist/studio/ui-components.generated.js +107 -2
- package/dist/sveltekit/in-memory.d.ts +2 -2
- package/dist/sveltekit/in-memory.js +125 -4
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/query-plan.d.ts +50 -4
- package/dist/sveltekit/query-plan.js +25 -3
- package/dist/sveltekit/sql-source.d.ts +6 -0
- package/dist/sveltekit/sql-source.js +34 -6
- package/dist/sveltekit/sql.d.ts +31 -0
- package/dist/sveltekit/sql.js +135 -25
- package/dist/sveltekit/transport.d.ts +10 -2
- package/dist/sveltekit/transport.js +30 -6
- package/dist/upgrade-prompt.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/watermark.js +35 -2
- package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
- package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
- package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
- package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
- package/dist/wc/SvField-B_kurx_P.js +149 -0
- package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
- package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
- package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
- package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
- package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
- package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
- package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
- package/dist/wc/SvModal-DrfOYYyX.js +189 -0
- package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
- package/dist/wc/chart-6OXlybVT.js +4341 -0
- package/dist/wc/chart-export-CeSADGfi.js +135 -0
- package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
- package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
- package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
- package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
- package/dist/wc/client-DQIzjDBG.js +3064 -0
- package/dist/wc/column-resize-DUPgynvc.js +101 -0
- package/dist/wc/date-format-DS-z_h9v.js +1130 -0
- package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
- package/dist/wc/editor-contract-peBDxir_.js +22 -0
- package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
- package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
- package/dist/wc/list-option-DrRyivWp.js +82 -0
- package/dist/wc/popover-WsGlvtQv.js +69 -0
- package/dist/wc/positioning-BiKLfu1E.js +112 -0
- package/dist/wc/react.d.ts +41 -0
- package/dist/wc/react.js +90 -0
- package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
- package/dist/wc/row-resize-limG_N1z.js +111 -0
- package/dist/wc/sv-sheet-element.d.ts +97 -0
- package/dist/wc/sv-sheet-element.js +44213 -0
- package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
- package/dist/wc/validate-BqDthkNr.js +88 -0
- package/dist/wc/vue.d.ts +31 -0
- package/dist/wc/vue.js +75 -0
- package/package.json +30 -5
- package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
- package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
- package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
- package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
- package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
- package/dist/designer/assets/index-CMFgAoVV.css +0 -1
- package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
- package/dist/designer/assets/src-EdI3Xqds.js +0 -58
- package/dist/designer/assets/validate-h-aslRV9.js +0 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a locale spells a number, a date and a formula.
|
|
3
|
+
*
|
|
4
|
+
* The rule this module exists to enforce: **the workbook stores the
|
|
5
|
+
* invariant spelling, always.** A formula in the document is
|
|
6
|
+
* `=SUM(1.5, A1)` whatever the user's locale, and a number is `1234.5`.
|
|
7
|
+
* The culture is a layer over the top, applied when a formula is shown for
|
|
8
|
+
* editing and un-applied when one is typed.
|
|
9
|
+
*
|
|
10
|
+
* That is what Excel does, and it is the only arrangement where a file
|
|
11
|
+
* written in Berlin opens in Boston. Storing `=SUMME(1,5;A1)` would make
|
|
12
|
+
* the document's meaning depend on who saved it, and every reader, writer
|
|
13
|
+
* and test would have to carry the locale to make sense of a cell.
|
|
14
|
+
*
|
|
15
|
+
* What a culture changes:
|
|
16
|
+
* - the decimal mark and the group mark, `1.234,5` against `1,234.5`
|
|
17
|
+
* - the argument separator, which Excel makes `;` wherever the decimal
|
|
18
|
+
* mark is `,`, since `,` is then busy
|
|
19
|
+
* - the order of a slash date, `31/12` against `12/31`
|
|
20
|
+
*
|
|
21
|
+
* What it does NOT change: function names, which stay English here, and
|
|
22
|
+
* the spelling in the file, which stays invariant.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The space-like group marks, written by code point rather than as
|
|
26
|
+
* literals so they are visible in the source.
|
|
27
|
+
*
|
|
28
|
+
* `fr-FR` formats with a narrow no-break space and `nb-NO` with a plain
|
|
29
|
+
* no-break one; neither is on anybody's keyboard, so a typed plain space
|
|
30
|
+
* has to mean the same thing.
|
|
31
|
+
*/
|
|
32
|
+
const NO_BREAK_SPACE = String.fromCharCode(0x00a0);
|
|
33
|
+
const NARROW_NO_BREAK_SPACE = String.fromCharCode(0x202f);
|
|
34
|
+
export const SPACE_GROUP_MARKS = [' ', NO_BREAK_SPACE, NARROW_NO_BREAK_SPACE];
|
|
35
|
+
/** The spelling the document itself uses, and the default. */
|
|
36
|
+
export const INVARIANT_CULTURE = {
|
|
37
|
+
locale: 'en-US',
|
|
38
|
+
decimal: '.',
|
|
39
|
+
group: ',',
|
|
40
|
+
argSeparator: ',',
|
|
41
|
+
dateOrder: 'mdy',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The culture a locale implies, read off `Intl` rather than a table, so a
|
|
45
|
+
* locale nobody thought of still comes out right.
|
|
46
|
+
*
|
|
47
|
+
* Falls back to the invariant culture where `Intl` is missing or the
|
|
48
|
+
* locale is not one it knows, since a sheet that renders in the wrong
|
|
49
|
+
* separators is better than one that does not render.
|
|
50
|
+
*/
|
|
51
|
+
export function cultureFromLocale(locale) {
|
|
52
|
+
try {
|
|
53
|
+
const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
|
|
54
|
+
const decimal = parts.find((p) => p.type === 'decimal')?.value ?? '.';
|
|
55
|
+
const group = parts.find((p) => p.type === 'group')?.value ?? ',';
|
|
56
|
+
const order = dateOrderOf(locale);
|
|
57
|
+
return {
|
|
58
|
+
locale,
|
|
59
|
+
decimal,
|
|
60
|
+
group,
|
|
61
|
+
// Excel's rule, not a preference: where the decimal mark is a comma
|
|
62
|
+
// the comma cannot also separate arguments.
|
|
63
|
+
argSeparator: decimal === ',' ? ';' : ',',
|
|
64
|
+
dateOrder: order,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return INVARIANT_CULTURE;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Which of day, month and year a locale writes first. */
|
|
72
|
+
function dateOrderOf(locale) {
|
|
73
|
+
try {
|
|
74
|
+
const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(Date.UTC(2026, 11, 31)));
|
|
75
|
+
const order = parts.filter((p) => p.type === 'day' || p.type === 'month' || p.type === 'year')
|
|
76
|
+
.map((p) => p.type[0]).join('');
|
|
77
|
+
if (order.startsWith('y'))
|
|
78
|
+
return 'ymd';
|
|
79
|
+
if (order.startsWith('d'))
|
|
80
|
+
return 'dmy';
|
|
81
|
+
return 'mdy';
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return 'mdy';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** True where the culture spells everything the way the document does. */
|
|
88
|
+
export const isInvariant = (c) => c.decimal === '.' && c.argSeparator === ',';
|
|
89
|
+
/**
|
|
90
|
+
* Walk a formula, handing each piece to `onNumber` and `onSeparator`.
|
|
91
|
+
*
|
|
92
|
+
* Shared by both directions so they cannot disagree about what counts as a
|
|
93
|
+
* number. The three things that must survive untouched are a string
|
|
94
|
+
* literal (`"a,b"`), a quoted sheet name (`'Q1,Q2'!A1`) and a function
|
|
95
|
+
* name with a dot in it (`NORM.DIST`), which is why a word is consumed
|
|
96
|
+
* whole before any punctuation is looked at.
|
|
97
|
+
*/
|
|
98
|
+
function scanFormula(text, decimal, separator, onNumber, onSeparator) {
|
|
99
|
+
let out = '';
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < text.length) {
|
|
102
|
+
const ch = text[i];
|
|
103
|
+
// A string literal, with Excel's doubled quote for an embedded one.
|
|
104
|
+
if (ch === '"') {
|
|
105
|
+
let j = i + 1;
|
|
106
|
+
while (j < text.length) {
|
|
107
|
+
if (text[j] === '"') {
|
|
108
|
+
if (text[j + 1] === '"') {
|
|
109
|
+
j += 2;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
j += 1;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
j += 1;
|
|
116
|
+
}
|
|
117
|
+
out += text.slice(i, j);
|
|
118
|
+
i = j;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// A quoted sheet name.
|
|
122
|
+
if (ch === "'") {
|
|
123
|
+
const end = text.indexOf("'", i + 1);
|
|
124
|
+
const close = end < 0 ? text.length : end + 1;
|
|
125
|
+
out += text.slice(i, close);
|
|
126
|
+
i = close;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// A word: a function name, a defined name or a cell reference. Taken
|
|
130
|
+
// whole so the dot in NORM.DIST is never read as a decimal mark.
|
|
131
|
+
if (/[A-Za-z_$]/.test(ch)) {
|
|
132
|
+
let j = i;
|
|
133
|
+
while (j < text.length && /[A-Za-z0-9_.$]/.test(text[j]))
|
|
134
|
+
j += 1;
|
|
135
|
+
out += text.slice(i, j);
|
|
136
|
+
i = j;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// A number, which is where the decimal mark lives.
|
|
140
|
+
if (/[0-9]/.test(ch)) {
|
|
141
|
+
let j = i;
|
|
142
|
+
while (j < text.length && /[0-9]/.test(text[j]))
|
|
143
|
+
j += 1;
|
|
144
|
+
let digits = text.slice(i, j);
|
|
145
|
+
// A decimal mark only counts with a digit after it, so `1,` in
|
|
146
|
+
// `SUM(1,2)` stays a separator where the mark is a comma.
|
|
147
|
+
if (text.startsWith(decimal, j) && /[0-9]/.test(text[j + decimal.length] ?? '')) {
|
|
148
|
+
let k = j + decimal.length;
|
|
149
|
+
while (k < text.length && /[0-9]/.test(text[k]))
|
|
150
|
+
k += 1;
|
|
151
|
+
digits = text.slice(i, k);
|
|
152
|
+
j = k;
|
|
153
|
+
}
|
|
154
|
+
// An exponent rides along so `1.5E-3` is one number.
|
|
155
|
+
if (/[eE]/.test(text[j] ?? '') && /[0-9+-]/.test(text[j + 1] ?? '')) {
|
|
156
|
+
let k = j + 1;
|
|
157
|
+
if (/[+-]/.test(text[k]))
|
|
158
|
+
k += 1;
|
|
159
|
+
while (k < text.length && /[0-9]/.test(text[k]))
|
|
160
|
+
k += 1;
|
|
161
|
+
digits = text.slice(i, k);
|
|
162
|
+
j = k;
|
|
163
|
+
}
|
|
164
|
+
out += onNumber(digits);
|
|
165
|
+
i = j;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (separator !== '' && text.startsWith(separator, i)) {
|
|
169
|
+
out += onSeparator();
|
|
170
|
+
i += separator.length;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
out += ch;
|
|
174
|
+
i += 1;
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A stored formula as the user's locale spells it, for the formula bar.
|
|
180
|
+
*
|
|
181
|
+
* `=SUM(1.5, A1)` becomes `=SUM(1,5; A1)` in a German sheet.
|
|
182
|
+
*/
|
|
183
|
+
export function formulaToCulture(formula, culture) {
|
|
184
|
+
if (isInvariant(culture))
|
|
185
|
+
return formula;
|
|
186
|
+
return scanFormula(formula, '.', ',', (digits) => digits.replace('.', culture.decimal), () => culture.argSeparator);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A typed formula back to the spelling the document stores.
|
|
190
|
+
*
|
|
191
|
+
* The inverse of `formulaToCulture`, with one deliberate kindness: where
|
|
192
|
+
* the culture's separator is `;`, a `,` between arguments is taken as a
|
|
193
|
+
* separator too, so a user with an English keyboard habit is not stopped.
|
|
194
|
+
* A `,` that sits between digits is still the decimal mark, so nothing
|
|
195
|
+
* ambiguous is guessed at.
|
|
196
|
+
*/
|
|
197
|
+
export function formulaFromCulture(formula, culture) {
|
|
198
|
+
if (isInvariant(culture))
|
|
199
|
+
return formula;
|
|
200
|
+
return scanFormula(formula, culture.decimal, culture.argSeparator, (digits) => digits.replace(culture.decimal, '.'), () => ',');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A number as the culture writes it, back to a plain number.
|
|
204
|
+
*
|
|
205
|
+
* Returns null when the text is not a number in this culture, so the
|
|
206
|
+
* caller can fall through to treating it as text. The group mark is
|
|
207
|
+
* dropped wherever it appears rather than checked for position, as Excel
|
|
208
|
+
* does: `1.2.3,4` is not worth refusing on a technicality.
|
|
209
|
+
*/
|
|
210
|
+
export function parseNumberInCulture(text, culture) {
|
|
211
|
+
const trimmed = text.trim();
|
|
212
|
+
if (trimmed === '')
|
|
213
|
+
return null;
|
|
214
|
+
const groups = culture.group === '' ? [] : [culture.group];
|
|
215
|
+
// A non-breaking or thin space as a group mark also arrives as a plain
|
|
216
|
+
// space when it is typed rather than pasted.
|
|
217
|
+
if (/\s/.test(culture.group))
|
|
218
|
+
groups.push(...SPACE_GROUP_MARKS);
|
|
219
|
+
let plain = trimmed;
|
|
220
|
+
for (const g of groups)
|
|
221
|
+
plain = plain.split(g).join('');
|
|
222
|
+
if (culture.decimal !== '.')
|
|
223
|
+
plain = plain.split(culture.decimal).join('.');
|
|
224
|
+
if (!/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/.test(plain))
|
|
225
|
+
return null;
|
|
226
|
+
const n = Number(plain);
|
|
227
|
+
return Number.isFinite(n) ? n : null;
|
|
228
|
+
}
|
|
229
|
+
/** A number as the culture writes it, for display. */
|
|
230
|
+
export function formatNumberInCulture(value, culture) {
|
|
231
|
+
const plain = String(value);
|
|
232
|
+
if (culture.decimal === '.')
|
|
233
|
+
return plain;
|
|
234
|
+
return plain.replace('.', culture.decimal);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The day, month and year a slash date names, in this culture's order.
|
|
238
|
+
*
|
|
239
|
+
* Returns null when the pieces cannot be a date, which is what tells a
|
|
240
|
+
* slash date apart from a fraction typed without a whole part.
|
|
241
|
+
*/
|
|
242
|
+
export function slashDateParts(a, b, culture) {
|
|
243
|
+
const [month, day] = culture.dateOrder === 'dmy' ? [b, a] : [a, b];
|
|
244
|
+
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
245
|
+
return null;
|
|
246
|
+
return { month, day };
|
|
247
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collaboration: the document's changes as a stream of deltas, and the other
|
|
3
|
+
* half, applying one that arrived from somewhere else.
|
|
4
|
+
*
|
|
5
|
+
* `onChange` already says WHAT changed; a collaborator needs to know WHAT IT
|
|
6
|
+
* BECAME, and small enough to send on every keystroke. So each reason is
|
|
7
|
+
* turned into a delta carrying just that:
|
|
8
|
+
*
|
|
9
|
+
* cells the raw text of the cells that were written, not their values:
|
|
10
|
+
* a collaborator has to receive `=SUM(A1:A9)` and work out 42 in
|
|
11
|
+
* their own workbook, or a formula would arrive as a number;
|
|
12
|
+
* structure the insert or delete itself, which both sides apply the same
|
|
13
|
+
* way, so every formula is rewritten identically on each;
|
|
14
|
+
* state the one part of one sheet that changed, serialized the way
|
|
15
|
+
* `getState` serializes it (the formats, the merges, the rules,
|
|
16
|
+
* the objects, ...);
|
|
17
|
+
* document the whole state, for the changes that are not worth
|
|
18
|
+
* describing piecemeal or cannot be: adding, removing or
|
|
19
|
+
* reordering sheets, a restore, and the parts the WORKBOOK
|
|
20
|
+
* keeps rather than a sheet - its tables, its defined names and
|
|
21
|
+
* its calculation settings.
|
|
22
|
+
*
|
|
23
|
+
* **Conflicts are last-writer-wins, per cell.** A delta carries no version
|
|
24
|
+
* and no transform: two people typing in the same cell end on whichever
|
|
25
|
+
* message arrived last, and everyone converges on that. Two people typing in
|
|
26
|
+
* DIFFERENT cells never conflict, which is the case that actually happens.
|
|
27
|
+
* Anything stronger (operational transform, CRDTs) is a different piece of
|
|
28
|
+
* work and does not belong behind an API this small.
|
|
29
|
+
*/
|
|
30
|
+
import type { SheetChangeReason, SheetDocument, SheetState, SheetStateEntry } from './document.js';
|
|
31
|
+
import type { SheetPresence } from './presence.js';
|
|
32
|
+
import type { StructuralEdit } from './refs.js';
|
|
33
|
+
/** One cell's raw text, as typed. */
|
|
34
|
+
export type SheetCellWrite = {
|
|
35
|
+
row: number;
|
|
36
|
+
col: number;
|
|
37
|
+
text: string;
|
|
38
|
+
};
|
|
39
|
+
export type SheetDelta = {
|
|
40
|
+
kind: 'cells';
|
|
41
|
+
sheet: string;
|
|
42
|
+
cells: SheetCellWrite[];
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'structure';
|
|
45
|
+
sheet: string;
|
|
46
|
+
edit: StructuralEdit;
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'state';
|
|
49
|
+
sheet: string;
|
|
50
|
+
entry: Partial<SheetStateEntry>;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'document';
|
|
53
|
+
state: SheetState;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Where someone is, which is not part of the document and is never
|
|
57
|
+
* applied to one: it rides the same wire because there is no reason to
|
|
58
|
+
* make an application run two, and the stream hands it to `onPresence`.
|
|
59
|
+
*/
|
|
60
|
+
| {
|
|
61
|
+
kind: 'presence';
|
|
62
|
+
who: SheetPresence;
|
|
63
|
+
};
|
|
64
|
+
/** The entry parts a batch of reasons could have changed, deduplicated. */
|
|
65
|
+
export declare function partsOfReasons(reasons: ReadonlyArray<SheetChangeReason>): Array<keyof SheetStateEntry>;
|
|
66
|
+
/**
|
|
67
|
+
* Apply a delta to a document. The change is reported, so a shell over it
|
|
68
|
+
* repaints; wrap the call in `doc.mute` where it should not be.
|
|
69
|
+
*
|
|
70
|
+
* Applying is idempotent for `cells` and `state`, and NOT for `structure`:
|
|
71
|
+
* an insert applied twice inserts twice. A transport that can deliver twice
|
|
72
|
+
* has to deduplicate, which is its job rather than this function's.
|
|
73
|
+
*/
|
|
74
|
+
export declare function applySheetDelta(doc: SheetDocument, delta: SheetDelta): void;
|
|
75
|
+
export type SheetDeltaStream = {
|
|
76
|
+
/** Apply a delta from elsewhere without sending it straight back out.
|
|
77
|
+
* A `presence` delta changes no document; it goes to `onPresence`. */
|
|
78
|
+
apply(delta: SheetDelta): void;
|
|
79
|
+
/** Say where this user is. Sent as it stands, since a cursor has no
|
|
80
|
+
* history worth batching. */
|
|
81
|
+
sendPresence(who: SheetPresence): void;
|
|
82
|
+
/** Send everything as one `document` delta: a new participant's first read. */
|
|
83
|
+
resync(): void;
|
|
84
|
+
/** Stop listening. */
|
|
85
|
+
stop(): void;
|
|
86
|
+
};
|
|
87
|
+
export type SheetDeltaOptions = {
|
|
88
|
+
/** Called with each delta, in the order they happened. */
|
|
89
|
+
onDelta(delta: SheetDelta): void;
|
|
90
|
+
/** Called when a `presence` delta arrives through `apply`: someone else
|
|
91
|
+
* moved. Pass what it carries to the shell's `presence` prop. */
|
|
92
|
+
onPresence?(who: SheetPresence): void;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Watch a document and report its changes as deltas.
|
|
96
|
+
*
|
|
97
|
+
* Cells come from the workbook's raw writes, so what goes out is the text a
|
|
98
|
+
* user typed rather than the value it computed. Everything else is worked
|
|
99
|
+
* out by comparing the sheet's serialized parts against the last ones sent,
|
|
100
|
+
* which is why a change that touches nothing sends nothing.
|
|
101
|
+
*/
|
|
102
|
+
export declare function createDeltaStream(doc: SheetDocument, options: SheetDeltaOptions): SheetDeltaStream;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/** Which parts of a sheet's saved entry a change reason can have touched. */
|
|
2
|
+
const PARTS = {
|
|
3
|
+
formats: ['formats'],
|
|
4
|
+
sizes: ['columnWidths', 'rowHeights'],
|
|
5
|
+
hidden: ['hidden', 'sheetHidden'],
|
|
6
|
+
freeze: ['freeze'],
|
|
7
|
+
comments: ['comments'],
|
|
8
|
+
validation: ['validation'],
|
|
9
|
+
'conditional-formats': ['conditionalFormats'],
|
|
10
|
+
protection: ['protected', 'protection'],
|
|
11
|
+
'page-setup': ['pageSetup'],
|
|
12
|
+
objects: ['objects'],
|
|
13
|
+
sparklines: ['sparklines'],
|
|
14
|
+
pivots: ['pivots'],
|
|
15
|
+
links: ['links'],
|
|
16
|
+
merges: ['merges'],
|
|
17
|
+
filter: ['autoFilter'],
|
|
18
|
+
};
|
|
19
|
+
/** The entry parts a batch of reasons could have changed, deduplicated. */
|
|
20
|
+
export function partsOfReasons(reasons) {
|
|
21
|
+
const out = new Set();
|
|
22
|
+
for (const reason of reasons)
|
|
23
|
+
for (const part of PARTS[reason.kind] ?? [])
|
|
24
|
+
out.add(part);
|
|
25
|
+
return [...out];
|
|
26
|
+
}
|
|
27
|
+
const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
28
|
+
/**
|
|
29
|
+
* Apply a delta to a document. The change is reported, so a shell over it
|
|
30
|
+
* repaints; wrap the call in `doc.mute` where it should not be.
|
|
31
|
+
*
|
|
32
|
+
* Applying is idempotent for `cells` and `state`, and NOT for `structure`:
|
|
33
|
+
* an insert applied twice inserts twice. A transport that can deliver twice
|
|
34
|
+
* has to deduplicate, which is its job rather than this function's.
|
|
35
|
+
*/
|
|
36
|
+
export function applySheetDelta(doc, delta) {
|
|
37
|
+
// Presence is a cursor, not a document change: there is nothing to apply,
|
|
38
|
+
// and applying it would put someone else's selection in the file.
|
|
39
|
+
if (delta.kind === 'presence')
|
|
40
|
+
return;
|
|
41
|
+
if (delta.kind === 'cells') {
|
|
42
|
+
for (const cell of delta.cells)
|
|
43
|
+
doc.workbook.setRaw(delta.sheet, cell.row, cell.col, cell.text);
|
|
44
|
+
doc.changed({ kind: 'cells' });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (delta.kind === 'structure') {
|
|
48
|
+
doc.workbook.applyStructuralEdit(delta.sheet, delta.edit);
|
|
49
|
+
doc.shift(delta.sheet, delta.edit);
|
|
50
|
+
doc.changed({ kind: 'structure', sheet: delta.sheet, edit: delta.edit });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (delta.kind === 'state') {
|
|
54
|
+
doc.patch(delta.sheet, delta.entry);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
doc.setState(delta.state);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Watch a document and report its changes as deltas.
|
|
61
|
+
*
|
|
62
|
+
* Cells come from the workbook's raw writes, so what goes out is the text a
|
|
63
|
+
* user typed rather than the value it computed. Everything else is worked
|
|
64
|
+
* out by comparing the sheet's serialized parts against the last ones sent,
|
|
65
|
+
* which is why a change that touches nothing sends nothing.
|
|
66
|
+
*/
|
|
67
|
+
export function createDeltaStream(doc, options) {
|
|
68
|
+
/** The last entry we know the other side has, per sheet. */
|
|
69
|
+
let sent = new Map();
|
|
70
|
+
/** Raw writes waiting to go out, per sheet, keyed by cell so a cell typed
|
|
71
|
+
* twice in one tick is sent once. */
|
|
72
|
+
const pending = new Map();
|
|
73
|
+
/**
|
|
74
|
+
* What arrived from elsewhere and must not go straight back out.
|
|
75
|
+
*
|
|
76
|
+
* A time-based "we are applying right now" flag cannot do this: the
|
|
77
|
+
* document reports its changes on a LATER microtask, by which time the
|
|
78
|
+
* flag is down, and two streams wired to each other would echo forever.
|
|
79
|
+
* So each applied change is marked instead, and the mark is what the
|
|
80
|
+
* listener drops - which also means a local change made in the same tick
|
|
81
|
+
* is still sent rather than being swallowed by the window.
|
|
82
|
+
*/
|
|
83
|
+
const appliedStructures = [];
|
|
84
|
+
let appliedDocument = false;
|
|
85
|
+
let stopped = false;
|
|
86
|
+
const snapshot = () => {
|
|
87
|
+
const state = doc.getState();
|
|
88
|
+
sent = new Map(Object.entries(state.sheets));
|
|
89
|
+
return state;
|
|
90
|
+
};
|
|
91
|
+
snapshot();
|
|
92
|
+
function flushCells() {
|
|
93
|
+
if (pending.size === 0)
|
|
94
|
+
return;
|
|
95
|
+
for (const [sheet, cells] of pending) {
|
|
96
|
+
if (cells.size)
|
|
97
|
+
options.onDelta({ kind: 'cells', sheet, cells: [...cells.values()] });
|
|
98
|
+
}
|
|
99
|
+
pending.clear();
|
|
100
|
+
}
|
|
101
|
+
const offWrites = doc.workbook.subscribeWrites((change) => {
|
|
102
|
+
if (stopped)
|
|
103
|
+
return;
|
|
104
|
+
const key = `${change.row},${change.col}`;
|
|
105
|
+
const cells = pending.get(change.sheet) ?? new Map();
|
|
106
|
+
cells.set(key, { row: change.row, col: change.col, text: change.text });
|
|
107
|
+
pending.set(change.sheet, cells);
|
|
108
|
+
});
|
|
109
|
+
const offChanges = doc.subscribe((reasons) => {
|
|
110
|
+
if (stopped)
|
|
111
|
+
return;
|
|
112
|
+
// The cells first: a structural edit or a rule that arrived in the same
|
|
113
|
+
// tick reads them, and the other side has to see them in that order too.
|
|
114
|
+
flushCells();
|
|
115
|
+
for (const reason of reasons) {
|
|
116
|
+
if (reason.kind !== 'structure')
|
|
117
|
+
continue;
|
|
118
|
+
const mark = JSON.stringify({ sheet: reason.sheet, edit: reason.edit });
|
|
119
|
+
const at = appliedStructures.indexOf(mark);
|
|
120
|
+
// One that came from elsewhere: the other side already has it.
|
|
121
|
+
if (at >= 0) {
|
|
122
|
+
appliedStructures.splice(at, 1);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
options.onDelta({ kind: 'structure', sheet: reason.sheet, edit: reason.edit });
|
|
126
|
+
}
|
|
127
|
+
// Sheets added, removed or reordered, a restore, and the parts that
|
|
128
|
+
// belong to the WORKBOOK rather than to a sheet - its tables, its names,
|
|
129
|
+
// its calculation settings - are the changes a part-by-part delta cannot
|
|
130
|
+
// describe: a `state` delta is keyed by sheet and carries a sheet's own
|
|
131
|
+
// entry, and none of these live there. Send the document.
|
|
132
|
+
if (reasons.some((r) => r.kind === 'sheets' || r.kind === 'restore' || r.kind === 'tables' || r.kind === 'workbook')) {
|
|
133
|
+
if (appliedDocument) {
|
|
134
|
+
appliedDocument = false;
|
|
135
|
+
snapshot();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
options.onDelta({ kind: 'document', state: snapshot() });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const parts = partsOfReasons(reasons);
|
|
142
|
+
if (parts.length === 0) {
|
|
143
|
+
snapshot();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const state = doc.getState();
|
|
147
|
+
for (const [sheet, entry] of Object.entries(state.sheets)) {
|
|
148
|
+
const before = sent.get(sheet);
|
|
149
|
+
const patch = {};
|
|
150
|
+
let any = false;
|
|
151
|
+
for (const part of parts) {
|
|
152
|
+
if (same(entry[part], before?.[part]))
|
|
153
|
+
continue;
|
|
154
|
+
patch[part] = entry[part];
|
|
155
|
+
any = true;
|
|
156
|
+
}
|
|
157
|
+
if (any)
|
|
158
|
+
options.onDelta({ kind: 'state', sheet, entry: patch });
|
|
159
|
+
}
|
|
160
|
+
sent = new Map(Object.entries(state.sheets));
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
apply(delta) {
|
|
164
|
+
if (stopped)
|
|
165
|
+
return;
|
|
166
|
+
if (delta.kind === 'presence') {
|
|
167
|
+
options.onPresence?.(delta.who);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (delta.kind === 'structure')
|
|
171
|
+
appliedStructures.push(JSON.stringify({ sheet: delta.sheet, edit: delta.edit }));
|
|
172
|
+
if (delta.kind === 'document')
|
|
173
|
+
appliedDocument = true;
|
|
174
|
+
applySheetDelta(doc, delta);
|
|
175
|
+
if (delta.kind === 'cells') {
|
|
176
|
+
// Written here, so there is nothing to write back.
|
|
177
|
+
const cells = pending.get(delta.sheet);
|
|
178
|
+
if (cells) {
|
|
179
|
+
for (const cell of delta.cells)
|
|
180
|
+
cells.delete(`${cell.row},${cell.col}`);
|
|
181
|
+
if (cells.size === 0)
|
|
182
|
+
pending.delete(delta.sheet);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// What arrived is what the other side already has: fold it into the
|
|
186
|
+
// baseline so the next comparison does not report it as ours.
|
|
187
|
+
snapshot();
|
|
188
|
+
},
|
|
189
|
+
sendPresence(who) {
|
|
190
|
+
if (stopped)
|
|
191
|
+
return;
|
|
192
|
+
options.onDelta({ kind: 'presence', who });
|
|
193
|
+
},
|
|
194
|
+
resync() {
|
|
195
|
+
options.onDelta({ kind: 'document', state: snapshot() });
|
|
196
|
+
},
|
|
197
|
+
stop() {
|
|
198
|
+
stopped = true;
|
|
199
|
+
offWrites();
|
|
200
|
+
offChanges();
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
package/dist/sheet/deps.d.ts
CHANGED
|
@@ -44,7 +44,49 @@ export declare function precedentsOf(ast: Node, self: {
|
|
|
44
44
|
* `Tax` leaves the formula showing its old value until something else
|
|
45
45
|
* forces a recalculation.
|
|
46
46
|
*/
|
|
47
|
-
resolveName?: (name: string) => Node | null,
|
|
47
|
+
resolveName?: (name: string) => Node | null,
|
|
48
|
+
/**
|
|
49
|
+
* The rectangle behind a structured reference (`Orders[Amount]`,
|
|
50
|
+
* `[@Qty]`). Without it a formula over a table records no precedents, so
|
|
51
|
+
* editing a cell in the table leaves every total reading it stale until
|
|
52
|
+
* something forces a full recalculation.
|
|
53
|
+
*/
|
|
54
|
+
resolveTable?: (node: Extract<Node, {
|
|
55
|
+
k: 'table';
|
|
56
|
+
}>) => {
|
|
57
|
+
sheet: string;
|
|
58
|
+
firstRow: number;
|
|
59
|
+
lastRow: number;
|
|
60
|
+
firstCol: number;
|
|
61
|
+
lastCol: number;
|
|
62
|
+
} | null,
|
|
63
|
+
/**
|
|
64
|
+
* The rectangle a spilled-range operator (`A1#`) currently covers. The
|
|
65
|
+
* anchor cell is always recorded regardless, so a formula reading `A1#`
|
|
66
|
+
* recalculates whenever the array is re-typed and picks up the new extent;
|
|
67
|
+
* the covered cells are added on top so a change anywhere in the spill
|
|
68
|
+
* reaches it too.
|
|
69
|
+
*/
|
|
70
|
+
resolveSpill?: (node: Extract<Node, {
|
|
71
|
+
k: 'spill';
|
|
72
|
+
}>) => {
|
|
73
|
+
sheet: string | null;
|
|
74
|
+
r1: number;
|
|
75
|
+
c1: number;
|
|
76
|
+
r2: number;
|
|
77
|
+
c2: number;
|
|
78
|
+
} | null,
|
|
79
|
+
/** The sheets a 3D reference spans, so it recalculates when the cell on any
|
|
80
|
+
* of them changes. */
|
|
81
|
+
resolveSheets?: (from: string, to: string) => string[] | null, depth?: number): CellKey[];
|
|
82
|
+
/**
|
|
83
|
+
* Functions whose result can change without any precedent changing: a
|
|
84
|
+
* reference built from text, a range built from arithmetic, the clock, a
|
|
85
|
+
* random number. A cell holding one is recomputed on every edit, as Excel
|
|
86
|
+
* recomputes its volatile cells, rather than trusting the graph.
|
|
87
|
+
*/
|
|
88
|
+
export declare const VOLATILE_FUNCTIONS: ReadonlySet<string>;
|
|
89
|
+
export declare function isVolatile(ast: Node): boolean;
|
|
48
90
|
export type DependencyGraph = {
|
|
49
91
|
/** Record (or clear, with null) what one cell reads. */
|
|
50
92
|
setPrecedents(cell: CellKey, precedents: ReadonlyArray<CellKey> | null): void;
|
|
@@ -55,6 +97,8 @@ export type DependencyGraph = {
|
|
|
55
97
|
cycles(): CellKey[];
|
|
56
98
|
/** Direct readers of a cell. */
|
|
57
99
|
dependentsOf(cell: CellKey): CellKey[];
|
|
100
|
+
/** What a cell reads directly, as last recorded. */
|
|
101
|
+
precedentsOf(cell: CellKey): CellKey[];
|
|
58
102
|
clear(): void;
|
|
59
103
|
};
|
|
60
104
|
export declare function createDependencyGraph(): DependencyGraph;
|