@svgrid/enterprise 3.0.1 → 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 +680 -0
- package/dist/SvFormulaBar.svelte.d.ts +69 -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 +263 -54
- package/dist/SvPivotDesigner.svelte.d.ts +27 -20
- package/dist/SvRibbonIcon.svelte +46 -0
- package/dist/SvRibbonIcon.svelte.d.ts +17 -0
- package/dist/SvSchemaChart.svelte +4 -2
- package/dist/SvSheet.svelte +7914 -0
- package/dist/SvSheet.svelte.d.ts +215 -0
- package/dist/SvSheetChartSetup.svelte +151 -0
- package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
- package/dist/SvSheetComment.svelte +283 -0
- package/dist/SvSheetComment.svelte.d.ts +22 -0
- package/dist/SvSheetConditionalFormat.svelte +159 -0
- package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
- package/dist/SvSheetDataValidation.svelte +273 -0
- package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
- 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 +320 -0
- package/dist/SvSheetFilterMenu.svelte.d.ts +22 -0
- package/dist/SvSheetFindReplace.svelte +132 -0
- package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
- package/dist/SvSheetFormatCells.svelte +465 -0
- package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
- package/dist/SvSheetGoalSeek.svelte +145 -0
- package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
- package/dist/SvSheetInsertFunction.svelte +149 -0
- package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
- 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 +91 -0
- package/dist/SvSheetListPicker.svelte.d.ts +10 -0
- package/dist/SvSheetManageRules.svelte +187 -0
- package/dist/SvSheetManageRules.svelte.d.ts +16 -0
- package/dist/SvSheetNameManager.svelte +227 -0
- package/dist/SvSheetNameManager.svelte.d.ts +11 -0
- package/dist/SvSheetPageSetup.svelte +159 -0
- package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
- package/dist/SvSheetPasteSpecial.svelte +107 -0
- package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
- 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 +152 -0
- package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
- package/dist/SvSheetRibbon.svelte +1254 -0
- package/dist/SvSheetRibbon.svelte.d.ts +34 -0
- package/dist/SvSheetSizeDialog.svelte +60 -0
- package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
- 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 +571 -0
- package/dist/SvSheetTabs.svelte.d.ts +50 -0
- package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
- package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
- package/dist/SvSheetTextToColumns.svelte +172 -0
- package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
- package/dist/SvSheetValidationAlert.svelte +85 -0
- package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
- 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 +43538 -6539
- package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
- package/dist/designer/assets/SvChartMenu-BDoeqGz6.js +1 -0
- package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
- package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
- package/dist/designer/assets/SvDateTimePicker-DCPg7WGN.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
- package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
- package/dist/designer/assets/SvGridChart-Cy1ATrWh.js +3 -0
- package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
- package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
- package/dist/designer/assets/SvGridChartPanel-CATdVc9X.js +4 -0
- package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
- package/dist/designer/assets/SvGridChartView-CmsZtCED.js +1 -0
- package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
- package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
- package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
- package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
- package/dist/designer/assets/SvTabs-2mHC6oaz.js +1 -0
- package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
- package/dist/designer/assets/chart-Cn7MnNqA.js +1 -0
- package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
- package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
- package/dist/designer/assets/chart-panel-messages-Dz7PTIqT.js +1 -0
- package/dist/designer/assets/chart-summary-BD1qYB2R.js +1 -0
- package/dist/designer/assets/chart-trend-B8xr5LuD.js +1 -0
- package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
- package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
- package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
- package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
- package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
- package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
- package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
- package/dist/designer/assets/focus-trap-V2-6Fy9I.js +1 -0
- package/dist/designer/assets/{index-C_a9Cj8Y.js → index-D5bVbuzI.js} +318 -315
- package/dist/designer/assets/index-_JVFxt02.css +1 -0
- package/dist/designer/assets/list-option-BP_dWS66.js +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-CITpcm6b.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 +29 -19
- package/dist/export-pdf.d.ts +164 -0
- package/dist/export-pdf.js +346 -15
- package/dist/export.js +25 -10
- 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 +93 -3
- package/dist/index.js +107 -3
- package/dist/install.js +13 -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 +203 -9
- package/dist/pivot-chart.d.ts +25 -1
- package/dist/pivot-chart.js +25 -69
- 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/address.d.ts +43 -0
- package/dist/sheet/address.js +81 -0
- package/dist/sheet/ast.d.ts +138 -0
- package/dist/sheet/ast.js +70 -0
- package/dist/sheet/auto-filter.d.ts +120 -0
- package/dist/sheet/auto-filter.js +295 -0
- package/dist/sheet/autocomplete.d.ts +51 -0
- package/dist/sheet/autocomplete.js +248 -0
- 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 +71 -0
- package/dist/sheet/coerce.js +289 -0
- package/dist/sheet/commands.d.ts +89 -0
- package/dist/sheet/commands.js +230 -0
- package/dist/sheet/comments.d.ts +63 -0
- package/dist/sheet/comments.js +131 -0
- package/dist/sheet/conditional-formats.d.ts +167 -0
- package/dist/sheet/conditional-formats.js +341 -0
- 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 +104 -0
- package/dist/sheet/deps.js +274 -0
- package/dist/sheet/document.d.ts +252 -0
- package/dist/sheet/document.js +465 -0
- package/dist/sheet/edit-keys.d.ts +14 -0
- package/dist/sheet/edit-keys.js +40 -0
- package/dist/sheet/engine.d.ts +67 -0
- package/dist/sheet/engine.js +19 -0
- package/dist/sheet/entry.d.ts +35 -0
- package/dist/sheet/entry.js +226 -0
- 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 +128 -0
- package/dist/sheet/evaluate.js +1049 -0
- package/dist/sheet/filter-files.d.ts +37 -0
- package/dist/sheet/filter-files.js +465 -0
- package/dist/sheet/find-replace.d.ts +62 -0
- package/dist/sheet/find-replace.js +152 -0
- package/dist/sheet/format-store.d.ts +114 -0
- package/dist/sheet/format-store.js +243 -0
- package/dist/sheet/freeze.d.ts +54 -0
- package/dist/sheet/freeze.js +58 -0
- package/dist/sheet/from-rows.d.ts +14 -0
- package/dist/sheet/from-rows.js +50 -0
- package/dist/sheet/function-catalog.d.ts +10 -0
- package/dist/sheet/function-catalog.js +385 -0
- package/dist/sheet/functions.d.ts +28 -0
- package/dist/sheet/functions.js +634 -0
- package/dist/sheet/goal-seek.d.ts +73 -0
- package/dist/sheet/goal-seek.js +153 -0
- package/dist/sheet/index.d.ts +80 -0
- package/dist/sheet/index.js +80 -0
- package/dist/sheet/links.d.ts +57 -0
- package/dist/sheet/links.js +174 -0
- package/dist/sheet/merges.d.ts +71 -0
- package/dist/sheet/merges.js +137 -0
- package/dist/sheet/messages.d.ts +778 -0
- package/dist/sheet/messages.js +806 -0
- package/dist/sheet/names.d.ts +31 -0
- package/dist/sheet/names.js +87 -0
- package/dist/sheet/navigate.d.ts +52 -0
- package/dist/sheet/navigate.js +135 -0
- package/dist/sheet/number-format.d.ts +110 -0
- package/dist/sheet/number-format.js +905 -0
- 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/palette.d.ts +19 -0
- package/dist/sheet/palette.js +84 -0
- package/dist/sheet/parse.d.ts +13 -0
- package/dist/sheet/parse.js +140 -0
- package/dist/sheet/paste-special.d.ts +186 -0
- package/dist/sheet/paste-special.js +601 -0
- 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 +75 -0
- package/dist/sheet/protection.js +101 -0
- package/dist/sheet/rects.d.ts +45 -0
- package/dist/sheet/rects.js +112 -0
- package/dist/sheet/refs.d.ts +130 -0
- package/dist/sheet/refs.js +413 -0
- package/dist/sheet/ribbon-icons.d.ts +31 -0
- package/dist/sheet/ribbon-icons.js +536 -0
- package/dist/sheet/ribbon.d.ts +173 -0
- package/dist/sheet/ribbon.js +1054 -0
- package/dist/sheet/sheet-kinds.d.ts +74 -0
- package/dist/sheet/sheet-kinds.js +91 -0
- package/dist/sheet/shortcuts.d.ts +174 -0
- package/dist/sheet/shortcuts.js +674 -0
- 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/structure.d.ts +87 -0
- package/dist/sheet/structure.js +166 -0
- package/dist/sheet/table-styles.d.ts +74 -0
- package/dist/sheet/table-styles.js +152 -0
- package/dist/sheet/tables.d.ts +89 -0
- package/dist/sheet/tables.js +178 -0
- package/dist/sheet/tokenize.d.ts +71 -0
- package/dist/sheet/tokenize.js +355 -0
- package/dist/sheet/transforms.d.ts +76 -0
- package/dist/sheet/transforms.js +154 -0
- package/dist/sheet/validation.d.ts +108 -0
- package/dist/sheet/validation.js +278 -0
- package/dist/sheet/workbook.d.ts +217 -0
- package/dist/sheet/workbook.js +1120 -0
- 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.d.ts +6 -0
- package/dist/sheet-enable.js +45 -0
- 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 +170 -3
- 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 +36 -6
- package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
- package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
- package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
- package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
- package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
- package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
- package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
- package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
- package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
- package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
- package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
- package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
- package/dist/designer/assets/chart-DXFganU8.js +0 -1
- package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
- package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
- package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
- package/dist/designer/assets/easing-zaypKrr0.js +0 -3
- package/dist/designer/assets/index-k2stW6L8.css +0 -1
- package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
- package/dist/designer/assets/src-D9p27OGC.js +0 -58
- package/dist/designer/assets/validate-h-aslRV9.js +0 -1
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workbook: several named sheets that can read each other.
|
|
3
|
+
*
|
|
4
|
+
* The engine already parsed `Orders!A1` and `'Price list'!A1:C9` from the day
|
|
5
|
+
* it was promoted, because demo 119 had them. What was missing is the thing
|
|
6
|
+
* those references point AT. This is that model, plus the recalculation that
|
|
7
|
+
* has to span sheets once they can.
|
|
8
|
+
*
|
|
9
|
+
* Cells hold raw text and nothing else. Everything derived - the computed
|
|
10
|
+
* value, the dependency edges, the display string - is recomputed from it, so
|
|
11
|
+
* there is exactly one copy of the truth and no way for a cached value to
|
|
12
|
+
* disagree with the formula above it.
|
|
13
|
+
*/
|
|
14
|
+
import { parseFormula } from './parse.js';
|
|
15
|
+
import { evaluate, rangeValues, tableRectOf } from './evaluate.js';
|
|
16
|
+
import { withCustomFunctions } from './functions.js';
|
|
17
|
+
import { isError, typedError } from './ast.js';
|
|
18
|
+
import { createDependencyGraph, precedentsOf, isVolatile, cellKey, parseCellKey } from './deps.js';
|
|
19
|
+
import { createTableRegistry, isValidTableName, shiftTables } from './tables.js';
|
|
20
|
+
import { createNames } from './names.js';
|
|
21
|
+
import { fixupReferences, renameSheetReferences, repointReferences } from './refs.js';
|
|
22
|
+
import { builtinEngine } from './engine.js';
|
|
23
|
+
export const DEFAULT_ITERATION = { enabled: false, maxIterations: 100, maxChange: 0.001 };
|
|
24
|
+
/** Excel's text prefix. A cell typed as `'007` holds the text `007`: the
|
|
25
|
+
* apostrophe says "what follows is text", and it is not part of the value,
|
|
26
|
+
* so it is never displayed, printed, exported or compared. */
|
|
27
|
+
export const TEXT_PREFIX = "'";
|
|
28
|
+
/** The value a typed text stands for once the prefix is taken off. */
|
|
29
|
+
export const withoutTextPrefix = (text) => text.startsWith(TEXT_PREFIX) ? text.slice(1) : text;
|
|
30
|
+
/** A name Excel would accept for a sheet. */
|
|
31
|
+
export function isValidSheetName(name) {
|
|
32
|
+
if (name.trim() === '' || name.length > 31)
|
|
33
|
+
return false;
|
|
34
|
+
// Excel forbids these because they are the reference grammar.
|
|
35
|
+
return !/[:\\/?*[\]]/.test(name);
|
|
36
|
+
}
|
|
37
|
+
/** `Sheet1`, `Sheet2`, ... skipping any that already exist. */
|
|
38
|
+
function nextSheetName(taken) {
|
|
39
|
+
const used = new Set(taken.map((n) => n.toLowerCase()));
|
|
40
|
+
for (let i = 1;; i += 1) {
|
|
41
|
+
const candidate = `Sheet${i}`;
|
|
42
|
+
if (!used.has(candidate.toLowerCase()))
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Iteration settings with the defaults filled in and the numbers sane. */
|
|
47
|
+
export function cleanIteration(next) {
|
|
48
|
+
const passes = Math.round(Number(next?.maxIterations ?? DEFAULT_ITERATION.maxIterations));
|
|
49
|
+
const change = Number(next?.maxChange ?? DEFAULT_ITERATION.maxChange);
|
|
50
|
+
return {
|
|
51
|
+
enabled: next?.enabled === true,
|
|
52
|
+
maxIterations: Number.isFinite(passes) ? Math.min(Math.max(passes, 1), 32767) : DEFAULT_ITERATION.maxIterations,
|
|
53
|
+
maxChange: Number.isFinite(change) && change >= 0 ? change : DEFAULT_ITERATION.maxChange,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* How far a cell moved between two passes. Two numbers are their distance;
|
|
58
|
+
* anything else has moved either not at all or immeasurably, and an
|
|
59
|
+
* immeasurable move keeps the loop going until the passes run out.
|
|
60
|
+
*/
|
|
61
|
+
function moved(before, after) {
|
|
62
|
+
if (typeof before === 'number' && typeof after === 'number')
|
|
63
|
+
return Math.abs(after - before);
|
|
64
|
+
if (before === undefined)
|
|
65
|
+
return Infinity;
|
|
66
|
+
if (isError(before) || isError(after)) {
|
|
67
|
+
return isError(before) && isError(after) && before.error === after.error ? 0 : Infinity;
|
|
68
|
+
}
|
|
69
|
+
return before === after ? 0 : Infinity;
|
|
70
|
+
}
|
|
71
|
+
export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], options = {}) {
|
|
72
|
+
const order = [];
|
|
73
|
+
/** What the shell is hiding, for SUBTOTAL. Null until it says. */
|
|
74
|
+
let hiddenRows = null;
|
|
75
|
+
/** Whether any cell holds a SUBTOTAL, so a filter click can skip the
|
|
76
|
+
* recalculation when nothing reads what is hidden. Null when unknown. */
|
|
77
|
+
let subtotalsHere = null;
|
|
78
|
+
const byName = new Map();
|
|
79
|
+
const graph = createDependencyGraph();
|
|
80
|
+
/**
|
|
81
|
+
* The workbook's tables. The parser has always read a structured
|
|
82
|
+
* reference (`Orders[Amount]`, `[@Qty]`); this is what makes one resolve,
|
|
83
|
+
* because a reference like that means nothing until something says where
|
|
84
|
+
* the table is.
|
|
85
|
+
*/
|
|
86
|
+
const rawTables = createTableRegistry(options.tables);
|
|
87
|
+
/**
|
|
88
|
+
* The registry as the workbook hands it out: every change drops the
|
|
89
|
+
* cached values and settles again.
|
|
90
|
+
*
|
|
91
|
+
* A table decides what `Orders[Amount]` means, so defining one, removing
|
|
92
|
+
* one or growing one changes the answer of every formula that mentions
|
|
93
|
+
* it. Without this, a table defined after the cells were read leaves each
|
|
94
|
+
* of those formulas showing the `#REF!` it worked out when there was no
|
|
95
|
+
* table, which is the first thing anyone hits.
|
|
96
|
+
*/
|
|
97
|
+
const tables = {
|
|
98
|
+
get: (name) => rawTables.get(name),
|
|
99
|
+
list: () => rawTables.list(),
|
|
100
|
+
at: (sheet, row, col) => rawTables.at(sheet, row, col),
|
|
101
|
+
define(table) { rawTables.define(table); afterTables(); },
|
|
102
|
+
remove(name) { const gone = rawTables.remove(name); if (gone)
|
|
103
|
+
afterTables(); return gone; },
|
|
104
|
+
growToInclude(sheet, row, col) { const grew = rawTables.growToInclude(sheet, row, col); if (grew)
|
|
105
|
+
afterTables(); return grew; },
|
|
106
|
+
clear() { rawTables.clear(); afterTables(); },
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* `Orders2`, `Orders3`, ...: a table name nothing has taken yet.
|
|
110
|
+
*
|
|
111
|
+
* A short name plus a number can read as a cell reference (`T2`), which
|
|
112
|
+
* Excel refuses as a table name and so does the registry, so those fall
|
|
113
|
+
* back to Excel's own `Table2`.
|
|
114
|
+
*/
|
|
115
|
+
function freeTableName(base) {
|
|
116
|
+
const taken = new Set(rawTables.list().map((t) => t.name.toLowerCase()));
|
|
117
|
+
const root = base.replace(/\d+$/, '') || 'Table';
|
|
118
|
+
for (let i = 2;; i += 1) {
|
|
119
|
+
for (const candidate of [`${root}${i}`, `Table${i}`]) {
|
|
120
|
+
if (!taken.has(candidate.toLowerCase()) && isValidTableName(candidate))
|
|
121
|
+
return candidate;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function afterTables() {
|
|
126
|
+
dropAll();
|
|
127
|
+
loadEngine();
|
|
128
|
+
settleAll();
|
|
129
|
+
}
|
|
130
|
+
const functions = withCustomFunctions(options.functions);
|
|
131
|
+
const engine = options.engine ?? builtinEngine();
|
|
132
|
+
/** Computed values, keyed the same way the graph is. Rebuilt lazily and
|
|
133
|
+
* invalidated by every write, so it can never hold a value whose formula
|
|
134
|
+
* has since changed. */
|
|
135
|
+
const values = new Map();
|
|
136
|
+
/** Cells currently being resolved, for cycle detection. */
|
|
137
|
+
const visiting = new Set();
|
|
138
|
+
/** Iterative calculation, and the value each cell had on the pass before:
|
|
139
|
+
* what a cell in a cycle reads for itself while iteration is on, and what
|
|
140
|
+
* the convergence test measures against. */
|
|
141
|
+
let iteration = cleanIteration(options.iteration);
|
|
142
|
+
const previous = new Map();
|
|
143
|
+
/** Cells whose formula is volatile (INDIRECT, OFFSET, RAND, NOW ...):
|
|
144
|
+
* recomputed on every write, since the graph cannot see what they read. */
|
|
145
|
+
const volatile = new Set();
|
|
146
|
+
/** Parsed formulas, keyed by their TEXT rather than their position: a
|
|
147
|
+
* column of =A1*2 filled down is one parse, not one per row. */
|
|
148
|
+
const astCache = new Map();
|
|
149
|
+
// Changing what a name refers to changes every formula that uses it, and
|
|
150
|
+
// the dependency graph only knows the CELLS a name pointed at when the
|
|
151
|
+
// formula was last evaluated. Dropping the cache is the honest answer:
|
|
152
|
+
// names change rarely, and a full recompute costs less than one stale total.
|
|
153
|
+
/**
|
|
154
|
+
* Dynamic arrays. A formula whose answer is a grid spills it over the
|
|
155
|
+
* cells below and to the right: `spills` keeps each anchor's rectangle,
|
|
156
|
+
* `spilledBy` points every covered cell back at its anchor, and a covered
|
|
157
|
+
* cell reads its value from the anchor's grid while its own text stays
|
|
158
|
+
* blank, as in Excel. An anchor whose rectangle runs into a cell that
|
|
159
|
+
* holds text, or into another spill, shows #SPILL! and sits in `blocked`
|
|
160
|
+
* until a write frees the way.
|
|
161
|
+
*/
|
|
162
|
+
const spills = new Map();
|
|
163
|
+
const spilledBy = new Map();
|
|
164
|
+
const blocked = new Set();
|
|
165
|
+
/** Cells whose value a spill just changed under them, to compute once the write settles. */
|
|
166
|
+
const pending = new Set();
|
|
167
|
+
const names = (() => {
|
|
168
|
+
const inner = createNames();
|
|
169
|
+
const drop = () => { dropAll(); settleAll(); };
|
|
170
|
+
return {
|
|
171
|
+
...inner,
|
|
172
|
+
define: (name, refersTo) => { inner.define(name, refersTo); drop(); },
|
|
173
|
+
remove: (name) => { inner.remove(name); drop(); },
|
|
174
|
+
hydrate: (entries) => { inner.hydrate(entries); drop(); },
|
|
175
|
+
clear: () => { inner.clear(); drop(); },
|
|
176
|
+
};
|
|
177
|
+
})();
|
|
178
|
+
let active = '';
|
|
179
|
+
function sheetCells(name) {
|
|
180
|
+
return byName.get(name.toLowerCase());
|
|
181
|
+
}
|
|
182
|
+
/** Resolve a sheet reference. A null sheet means the one being evaluated,
|
|
183
|
+
* which the resolver tracks as it descends. */
|
|
184
|
+
function resolveSheetName(sheet, self) {
|
|
185
|
+
return sheet ?? self;
|
|
186
|
+
}
|
|
187
|
+
/** The sheets from `from` to `to` in tab order, inclusive, for a 3D
|
|
188
|
+
* reference; null when either name is not a sheet. */
|
|
189
|
+
function sheetsInRange(from, to) {
|
|
190
|
+
const lower = order.map((n) => n.toLowerCase());
|
|
191
|
+
const i = lower.indexOf(from.toLowerCase());
|
|
192
|
+
const j = lower.indexOf(to.toLowerCase());
|
|
193
|
+
if (i < 0 || j < 0)
|
|
194
|
+
return null;
|
|
195
|
+
return i <= j ? order.slice(i, j + 1) : order.slice(j, i + 1);
|
|
196
|
+
}
|
|
197
|
+
function parseCached(text) {
|
|
198
|
+
if (astCache.has(text))
|
|
199
|
+
return astCache.get(text) ?? null;
|
|
200
|
+
let ast;
|
|
201
|
+
try {
|
|
202
|
+
ast = parseFormula(text);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
ast = null;
|
|
206
|
+
}
|
|
207
|
+
astCache.set(text, ast);
|
|
208
|
+
return ast;
|
|
209
|
+
}
|
|
210
|
+
function contextFor(self, override, at) {
|
|
211
|
+
return {
|
|
212
|
+
currentCell: at ? { sheet: self, row: at.row, col: at.col } : undefined,
|
|
213
|
+
resolve: (sheet, row, col) => {
|
|
214
|
+
const name = resolveSheetName(sheet, self);
|
|
215
|
+
// A validation rule reads the cell it is checking as the text being
|
|
216
|
+
// entered, which is not in the sheet yet; nothing else is overridden.
|
|
217
|
+
if (override && name === self && row === override.row && col === override.col)
|
|
218
|
+
return literal(override.text);
|
|
219
|
+
return compute(name, row, col);
|
|
220
|
+
},
|
|
221
|
+
lastRow: (sheet) => Math.max(rowCount(resolveSheetName(sheet, self)) - 1, 0),
|
|
222
|
+
spillRect: (sheet, row, col) => {
|
|
223
|
+
// The array anchored here, computed first so a formula that reads
|
|
224
|
+
// `A1#` sees the spill even on the pass that establishes it. Only an
|
|
225
|
+
// anchor has an entry; a covered cell or a plain value gives null,
|
|
226
|
+
// which the operator reads as #REF!.
|
|
227
|
+
const name = resolveSheetName(sheet, self);
|
|
228
|
+
compute(name, row, col);
|
|
229
|
+
const rect = spills.get(cellKey(name, row, col));
|
|
230
|
+
return rect ? [rect.r1, rect.c1, rect.r2, rect.c2] : null;
|
|
231
|
+
},
|
|
232
|
+
sheetsBetween: (from, to) => sheetsInRange(from, to),
|
|
233
|
+
resolveNameNode: (name) => names.resolve(name),
|
|
234
|
+
findTable: (name) => rawTables.get(name),
|
|
235
|
+
tableAt: (sheet, row, col) => rawTables.at(resolveSheetName(sheet, self), row, col),
|
|
236
|
+
hiddenRow: hiddenRows ? (sheet, row) => hiddenRows(resolveSheetName(sheet, self), row) : undefined,
|
|
237
|
+
isSubtotal: (sheet, row, col) => {
|
|
238
|
+
const text = sheetCells(resolveSheetName(sheet, self))?.[row]?.[col] ?? '';
|
|
239
|
+
return /^\s*=\s*SUBTOTAL\s*\(/i.test(text);
|
|
240
|
+
},
|
|
241
|
+
functions,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** The engine's copy of the cells, for an engine that keeps one. */
|
|
245
|
+
function loadEngine() {
|
|
246
|
+
engine.load?.(order.map((name) => ({ name, cells: sheetCells(name).map((r) => [...r]) })));
|
|
247
|
+
}
|
|
248
|
+
/** Forget every derived thing: values, edges, volatility, spills. */
|
|
249
|
+
function dropAll() {
|
|
250
|
+
// Cells may have been replaced wholesale, so the SUBTOTAL flag is stale.
|
|
251
|
+
subtotalsHere = null;
|
|
252
|
+
values.clear();
|
|
253
|
+
previous.clear();
|
|
254
|
+
graph.clear();
|
|
255
|
+
volatile.clear();
|
|
256
|
+
spills.clear();
|
|
257
|
+
spilledBy.clear();
|
|
258
|
+
blocked.clear();
|
|
259
|
+
pending.clear();
|
|
260
|
+
}
|
|
261
|
+
const SUBTOTAL_TEXT = /^\s*=[\s\S]*SUBTOTAL\s*\(/i;
|
|
262
|
+
/** Whether any cell in the workbook holds a SUBTOTAL. Worked out once and
|
|
263
|
+
* forgotten again whenever a write could change the answer. */
|
|
264
|
+
function hasSubtotal() {
|
|
265
|
+
if (subtotalsHere !== null)
|
|
266
|
+
return subtotalsHere;
|
|
267
|
+
let found = false;
|
|
268
|
+
for (const sheet of order) {
|
|
269
|
+
const cells = sheetCells(sheet);
|
|
270
|
+
for (const line of cells) {
|
|
271
|
+
for (const text of line) {
|
|
272
|
+
if (text && SUBTOTAL_TEXT.test(text)) {
|
|
273
|
+
found = true;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (found)
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
if (found)
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
subtotalsHere = found;
|
|
284
|
+
return found;
|
|
285
|
+
}
|
|
286
|
+
/** Whether a key holds a formula, for the settling passes. */
|
|
287
|
+
function isFormulaKey(key) {
|
|
288
|
+
const at = parseCellKey(key);
|
|
289
|
+
return (sheetCells(at.sheet ?? '')?.[at.row]?.[at.col] ?? '').trim().startsWith('=');
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Compute every formula in the workbook, so every spill is on record
|
|
293
|
+
* before any cell it covers is read. Lazy evaluation alone would leave a
|
|
294
|
+
* covered cell blank when it is read before its anchor, which a repaint
|
|
295
|
+
* from the top of a scrolled sheet does.
|
|
296
|
+
*/
|
|
297
|
+
function settleAll() {
|
|
298
|
+
for (const other of order) {
|
|
299
|
+
const cells = sheetCells(other) ?? [];
|
|
300
|
+
for (let r = 0; r < cells.length; r += 1) {
|
|
301
|
+
const line = cells[r];
|
|
302
|
+
for (let c = 0; c < line.length; c += 1)
|
|
303
|
+
if (line[c].trim().startsWith('='))
|
|
304
|
+
compute(other, r, c);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
settlePending();
|
|
308
|
+
runIterations();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Run the cycles until they stop moving.
|
|
312
|
+
*
|
|
313
|
+
* Everything in a cycle, and everything downstream of one, is recomputed
|
|
314
|
+
* from the values it last had; a pass that moves no cell by more than
|
|
315
|
+
* `maxChange` is the last one, and so is pass number `maxIterations`. This
|
|
316
|
+
* is Excel's model exactly: it does not promise convergence, it promises a
|
|
317
|
+
* bounded number of tries and whatever they reached.
|
|
318
|
+
*/
|
|
319
|
+
function runIterations() {
|
|
320
|
+
if (!iteration.enabled)
|
|
321
|
+
return [];
|
|
322
|
+
const seeds = graph.cycles();
|
|
323
|
+
if (seeds.length === 0)
|
|
324
|
+
return [];
|
|
325
|
+
// Cycle members are downstream of each other, so `dirtyFrom` already
|
|
326
|
+
// includes them; the union is for a seed whose readers were never
|
|
327
|
+
// evaluated.
|
|
328
|
+
const affected = [...new Set([...seeds, ...graph.dirtyFrom(seeds)])];
|
|
329
|
+
for (let pass = 0; pass < Math.max(1, iteration.maxIterations); pass += 1) {
|
|
330
|
+
const before = new Map();
|
|
331
|
+
for (const key of affected) {
|
|
332
|
+
before.set(key, values.get(key) ?? previous.get(key));
|
|
333
|
+
values.delete(key);
|
|
334
|
+
}
|
|
335
|
+
let worst = 0;
|
|
336
|
+
for (const key of affected) {
|
|
337
|
+
const at = parseCellKey(key);
|
|
338
|
+
const after = compute(at.sheet ?? '', at.row, at.col);
|
|
339
|
+
worst = Math.max(worst, moved(before.get(key), after));
|
|
340
|
+
}
|
|
341
|
+
if (worst <= iteration.maxChange)
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
return affected;
|
|
345
|
+
}
|
|
346
|
+
/** Compute what a spill changed under other formulas, until nothing is left. */
|
|
347
|
+
function settlePending() {
|
|
348
|
+
const done = [];
|
|
349
|
+
while (pending.size) {
|
|
350
|
+
const batch = [...pending];
|
|
351
|
+
pending.clear();
|
|
352
|
+
for (const key of batch) {
|
|
353
|
+
const at = parseCellKey(key);
|
|
354
|
+
compute(at.sheet ?? '', at.row, at.col);
|
|
355
|
+
done.push(key);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return done;
|
|
359
|
+
}
|
|
360
|
+
/** Drop an anchor's spill; the covered cells' values and edges go with it. */
|
|
361
|
+
function clearSpill(anchor) {
|
|
362
|
+
const rect = spills.get(anchor);
|
|
363
|
+
if (!rect)
|
|
364
|
+
return [];
|
|
365
|
+
spills.delete(anchor);
|
|
366
|
+
const at = parseCellKey(anchor);
|
|
367
|
+
const covered = [];
|
|
368
|
+
for (let r = rect.r1; r <= rect.r2; r += 1) {
|
|
369
|
+
for (let c = rect.c1; c <= rect.c2; c += 1) {
|
|
370
|
+
if (r === at.row && c === at.col)
|
|
371
|
+
continue;
|
|
372
|
+
const key = cellKey(at.sheet, r, c);
|
|
373
|
+
if (spilledBy.get(key) !== anchor)
|
|
374
|
+
continue;
|
|
375
|
+
spilledBy.delete(key);
|
|
376
|
+
values.delete(key);
|
|
377
|
+
graph.setPrecedents(key, null);
|
|
378
|
+
covered.push(key);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return covered;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Record an anchor's grid over its rectangle, or refuse it. Returns the
|
|
385
|
+
* anchor's own value: the grid's top-left cell, or #SPILL! when a cell
|
|
386
|
+
* in the way holds text or belongs to another spill.
|
|
387
|
+
*/
|
|
388
|
+
function recordSpill(anchor, sheet, row, col, grid) {
|
|
389
|
+
const cells = sheetCells(sheet);
|
|
390
|
+
const rect = { r1: row, c1: col, r2: row + grid.length - 1, c2: col + (grid[0]?.length ?? 1) - 1 };
|
|
391
|
+
for (let r = rect.r1; r <= rect.r2; r += 1) {
|
|
392
|
+
for (let c = rect.c1; c <= rect.c2; c += 1) {
|
|
393
|
+
if (r === row && c === col)
|
|
394
|
+
continue;
|
|
395
|
+
const key = cellKey(sheet, r, c);
|
|
396
|
+
const owner = spilledBy.get(key);
|
|
397
|
+
if ((cells[r]?.[c] ?? '').trim() !== '' || (owner !== undefined && owner !== anchor)) {
|
|
398
|
+
for (const k of clearSpill(anchor))
|
|
399
|
+
pending.add(k);
|
|
400
|
+
blocked.add(anchor);
|
|
401
|
+
return { error: '#SPILL!' };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
blocked.delete(anchor);
|
|
406
|
+
const before = spills.get(anchor);
|
|
407
|
+
const stillCovered = new Set();
|
|
408
|
+
for (let r = rect.r1; r <= rect.r2; r += 1) {
|
|
409
|
+
for (let c = rect.c1; c <= rect.c2; c += 1) {
|
|
410
|
+
if (r === row && c === col)
|
|
411
|
+
continue;
|
|
412
|
+
const key = cellKey(sheet, r, c);
|
|
413
|
+
stillCovered.add(key);
|
|
414
|
+
const fresh = spilledBy.get(key) !== anchor;
|
|
415
|
+
spilledBy.set(key, anchor);
|
|
416
|
+
values.set(key, grid[r - row]?.[c - col] ?? '');
|
|
417
|
+
graph.setPrecedents(key, [anchor]);
|
|
418
|
+
// Reported as changed, and a formula that read this cell while it
|
|
419
|
+
// was blank has a stale value.
|
|
420
|
+
pending.add(key);
|
|
421
|
+
if (fresh)
|
|
422
|
+
for (const k of graph.dirtyFrom([key]))
|
|
423
|
+
if (k !== key) {
|
|
424
|
+
values.delete(k);
|
|
425
|
+
pending.add(k);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// The cells the old rectangle covered and the new one does not read blank again.
|
|
430
|
+
if (before) {
|
|
431
|
+
for (let r = before.r1; r <= before.r2; r += 1) {
|
|
432
|
+
for (let c = before.c1; c <= before.c2; c += 1) {
|
|
433
|
+
const key = cellKey(sheet, r, c);
|
|
434
|
+
if (stillCovered.has(key) || spilledBy.get(key) !== anchor)
|
|
435
|
+
continue;
|
|
436
|
+
spilledBy.delete(key);
|
|
437
|
+
values.delete(key);
|
|
438
|
+
graph.setPrecedents(key, null);
|
|
439
|
+
pending.add(key);
|
|
440
|
+
for (const k of graph.dirtyFrom([key]))
|
|
441
|
+
if (k !== key) {
|
|
442
|
+
values.delete(k);
|
|
443
|
+
pending.add(k);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
spills.set(anchor, rect);
|
|
449
|
+
return grid[0]?.[0] ?? '';
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* How deep a cell's value may be resolved by recursion before the chain is
|
|
453
|
+
* primed from the far end instead. A running-balance column is a chain as
|
|
454
|
+
* long as the sheet, and evaluating the last cell of one by recursion
|
|
455
|
+
* overflows the JavaScript stack somewhere past a thousand rows, which the
|
|
456
|
+
* evaluator turns into a `#NUM!`. Priming keeps the depth flat, so the
|
|
457
|
+
* only limit left is the sheet's own size.
|
|
458
|
+
*/
|
|
459
|
+
const DEEP = 150;
|
|
460
|
+
let depth = 0;
|
|
461
|
+
/**
|
|
462
|
+
* Compute what a cell reads, furthest first, so the cell itself resolves
|
|
463
|
+
* against cached values rather than by recursing through the chain. The
|
|
464
|
+
* precedents are the ones the graph recorded at the last evaluation, which
|
|
465
|
+
* is exactly the chain a re-evaluation is about to walk again.
|
|
466
|
+
*/
|
|
467
|
+
function primeChain(from) {
|
|
468
|
+
const seen = new Set([from]);
|
|
469
|
+
const chain = [from];
|
|
470
|
+
const stack = [from];
|
|
471
|
+
while (stack.length) {
|
|
472
|
+
const cur = stack.pop();
|
|
473
|
+
for (const p of graph.precedentsOf(cur)) {
|
|
474
|
+
if (seen.has(p) || values.get(p) !== undefined)
|
|
475
|
+
continue;
|
|
476
|
+
seen.add(p);
|
|
477
|
+
chain.push(p);
|
|
478
|
+
stack.push(p);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (let i = chain.length - 1; i >= 1; i -= 1) {
|
|
482
|
+
const key = chain[i];
|
|
483
|
+
if (values.get(key) !== undefined)
|
|
484
|
+
continue;
|
|
485
|
+
const at = parseCellKey(key);
|
|
486
|
+
compute(at.sheet ?? '', at.row, at.col);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function compute(sheet, row, col) {
|
|
490
|
+
const cells = sheetCells(sheet);
|
|
491
|
+
if (!cells)
|
|
492
|
+
return { error: '#REF!' };
|
|
493
|
+
if (row < 0 || col < 0)
|
|
494
|
+
return { error: '#REF!' };
|
|
495
|
+
// Anything past the written area is BLANK. A sheet has no size in this
|
|
496
|
+
// model beyond what has been typed into it, so there is no edge to fall
|
|
497
|
+
// off: =SUM(A1:A100) over a twelve-row sheet is an ordinary thing to
|
|
498
|
+
// write, and returning #REF! for the empty rows would poison the total.
|
|
499
|
+
//
|
|
500
|
+
// #REF! is reserved for the two cases that really are broken: a sheet
|
|
501
|
+
// that does not exist, and a negative index, which is what a reference
|
|
502
|
+
// shifted off the top by a delete becomes.
|
|
503
|
+
const key = cellKey(sheet, row, col);
|
|
504
|
+
const cached = values.get(key);
|
|
505
|
+
if (cached !== undefined)
|
|
506
|
+
return cached;
|
|
507
|
+
if (row >= cells.length || col >= (cells[row]?.length ?? 0)) {
|
|
508
|
+
// Past the written area, unless a spill reaches here.
|
|
509
|
+
const anchor = spilledBy.get(key);
|
|
510
|
+
if (anchor === undefined)
|
|
511
|
+
return '';
|
|
512
|
+
const at = parseCellKey(anchor);
|
|
513
|
+
compute(at.sheet ?? sheet, at.row, at.col);
|
|
514
|
+
return values.get(key) ?? '';
|
|
515
|
+
}
|
|
516
|
+
if (visiting.has(key)) {
|
|
517
|
+
// The cell is being asked for itself. Without iteration that is the
|
|
518
|
+
// error; with it, the loop is meant, and the answer is what this cell
|
|
519
|
+
// was worth on the last pass, starting from 0 as Excel does.
|
|
520
|
+
if (!iteration.enabled)
|
|
521
|
+
return { error: '#CYCLE!' };
|
|
522
|
+
return previous.get(key) ?? 0;
|
|
523
|
+
}
|
|
524
|
+
// Deep enough that recursion is a risk: resolve what this cell reads from
|
|
525
|
+
// the far end first, and come back to it with everything cached.
|
|
526
|
+
if (depth >= DEEP)
|
|
527
|
+
primeChain(key);
|
|
528
|
+
visiting.add(key);
|
|
529
|
+
depth += 1;
|
|
530
|
+
const text = (cells[row]?.[col] ?? '').trim();
|
|
531
|
+
let value;
|
|
532
|
+
if (text === '') {
|
|
533
|
+
// A blank cell shows what an array formula spills into it.
|
|
534
|
+
const anchor = spilledBy.get(key);
|
|
535
|
+
if (anchor !== undefined) {
|
|
536
|
+
const at = parseCellKey(anchor);
|
|
537
|
+
compute(at.sheet ?? sheet, at.row, at.col);
|
|
538
|
+
const spilled = values.get(key);
|
|
539
|
+
if (spilled !== undefined) {
|
|
540
|
+
visiting.delete(key);
|
|
541
|
+
depth -= 1;
|
|
542
|
+
return spilled;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
value = '';
|
|
546
|
+
}
|
|
547
|
+
else if (text.startsWith('=')) {
|
|
548
|
+
const ast = parseCached(text);
|
|
549
|
+
// The graph, the volatile set and the spill ranges are read off the
|
|
550
|
+
// reference grammar, which is Excel's whatever evaluates it, so they
|
|
551
|
+
// stay right for any engine. Only the value and the grid are asked for.
|
|
552
|
+
const result = engine.evaluate(text, { sheet, row, col }, {
|
|
553
|
+
context: contextFor(sheet, undefined, { row, col }),
|
|
554
|
+
parse: parseCached,
|
|
555
|
+
});
|
|
556
|
+
value = result.value;
|
|
557
|
+
const grid = isError(value) ? null : result.spill ?? null;
|
|
558
|
+
if (grid)
|
|
559
|
+
value = recordSpill(key, sheet, row, col, grid);
|
|
560
|
+
else if (spills.has(key))
|
|
561
|
+
for (const k of clearSpill(key))
|
|
562
|
+
pending.add(k);
|
|
563
|
+
if (ast) {
|
|
564
|
+
if (isVolatile(ast))
|
|
565
|
+
volatile.add(key);
|
|
566
|
+
else
|
|
567
|
+
volatile.delete(key);
|
|
568
|
+
graph.setPrecedents(key, precedentsOf(ast, { sheet }, (s) => Math.max(rowCount(s ?? sheet) - 1, 0), (name) => names.resolve(name),
|
|
569
|
+
// A structured reference's cells, so a total over a table is
|
|
570
|
+
// recalculated when a cell inside the table is typed. Best effort:
|
|
571
|
+
// a reference that cannot be resolved records no precedents rather
|
|
572
|
+
// than taking the cell's value down with it, since the value has
|
|
573
|
+
// already been worked out by the time this runs.
|
|
574
|
+
(node) => {
|
|
575
|
+
try {
|
|
576
|
+
return tableRectOf(node, contextFor(sheet, undefined, { row, col }));
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
// The cells a spilled-range operator covers right now. The anchor
|
|
583
|
+
// has just been computed by this same pass, so its rectangle is on
|
|
584
|
+
// record; reading it here (rather than recomputing) keeps the
|
|
585
|
+
// dependency extraction free of side effects.
|
|
586
|
+
(node) => {
|
|
587
|
+
const name = resolveSheetName(node.ref.sheet, sheet);
|
|
588
|
+
const rect = spills.get(cellKey(name, node.ref.row ?? 0, node.ref.col));
|
|
589
|
+
return rect ? { sheet: name, r1: rect.r1, c1: rect.c1, r2: rect.r2, c2: rect.c2 } : null;
|
|
590
|
+
},
|
|
591
|
+
// The sheets a 3D reference spans, so it recalculates when the cell
|
|
592
|
+
// on any of them is typed into.
|
|
593
|
+
(from, to) => sheetsInRange(from, to)));
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
graph.setPrecedents(key, null);
|
|
597
|
+
volatile.delete(key);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
else if (text.startsWith(TEXT_PREFIX)) {
|
|
601
|
+
// Excel's apostrophe: what follows is text, whatever it looks like,
|
|
602
|
+
// and the apostrophe itself is not part of it. It is the only way to
|
|
603
|
+
// keep a leading zero, a part number that reads as a date, or the
|
|
604
|
+
// text of a formula in a cell.
|
|
605
|
+
value = text.slice(1);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
const n = Number(text);
|
|
609
|
+
const upper = text.toUpperCase();
|
|
610
|
+
value = text !== '' && Number.isFinite(n) ? n
|
|
611
|
+
: upper === 'TRUE' ? true
|
|
612
|
+
: upper === 'FALSE' ? false
|
|
613
|
+
: typedError(text) ?? text;
|
|
614
|
+
}
|
|
615
|
+
visiting.delete(key);
|
|
616
|
+
depth -= 1;
|
|
617
|
+
values.set(key, value);
|
|
618
|
+
if (iteration.enabled)
|
|
619
|
+
previous.set(key, value);
|
|
620
|
+
return value;
|
|
621
|
+
}
|
|
622
|
+
/** What typed text is worth when it is not a formula: a number, a boolean or itself. */
|
|
623
|
+
function literal(text) {
|
|
624
|
+
const t = text.trim();
|
|
625
|
+
if (t === '')
|
|
626
|
+
return '';
|
|
627
|
+
if (t.startsWith(TEXT_PREFIX))
|
|
628
|
+
return t.slice(1);
|
|
629
|
+
if (t.startsWith('='))
|
|
630
|
+
return t;
|
|
631
|
+
const n = Number(t);
|
|
632
|
+
const upper = t.toUpperCase();
|
|
633
|
+
return Number.isFinite(n) ? n
|
|
634
|
+
: upper === 'TRUE' ? true
|
|
635
|
+
: upper === 'FALSE' ? false
|
|
636
|
+
: typedError(t) ?? t;
|
|
637
|
+
}
|
|
638
|
+
function rowCount(sheet) {
|
|
639
|
+
return sheetCells(sheet)?.length ?? 0;
|
|
640
|
+
}
|
|
641
|
+
function colCount(sheet) {
|
|
642
|
+
const cells = sheetCells(sheet);
|
|
643
|
+
if (!cells)
|
|
644
|
+
return 0;
|
|
645
|
+
return cells.reduce((max, row) => Math.max(max, row.length), 0);
|
|
646
|
+
}
|
|
647
|
+
/** Drop cached values for a cell and everything downstream of it, and
|
|
648
|
+
* for every volatile cell and its readers, which any write may affect. */
|
|
649
|
+
function invalidate(keys) {
|
|
650
|
+
const touched = [...keys];
|
|
651
|
+
for (const key of keys)
|
|
652
|
+
values.delete(key);
|
|
653
|
+
for (const key of volatile) {
|
|
654
|
+
if (values.delete(key))
|
|
655
|
+
touched.push(key);
|
|
656
|
+
}
|
|
657
|
+
for (const key of graph.dirtyFrom([...keys, ...volatile])) {
|
|
658
|
+
values.delete(key);
|
|
659
|
+
touched.push(key);
|
|
660
|
+
}
|
|
661
|
+
return touched;
|
|
662
|
+
}
|
|
663
|
+
/** Every listener on raw writes, plus the option that is one of them. */
|
|
664
|
+
const writeListeners = new Set();
|
|
665
|
+
function emitWrite(change) {
|
|
666
|
+
options.onWrite?.(change);
|
|
667
|
+
for (const listener of writeListeners)
|
|
668
|
+
listener(change);
|
|
669
|
+
}
|
|
670
|
+
function reportRecalc(keys) {
|
|
671
|
+
const cb = options.onRecalc;
|
|
672
|
+
if (!cb || keys.length === 0)
|
|
673
|
+
return;
|
|
674
|
+
cb(keys.map((key) => {
|
|
675
|
+
const gap = key.indexOf(' ');
|
|
676
|
+
const gap2 = key.indexOf(' ', gap + 1);
|
|
677
|
+
const row = Number(key.slice(0, gap));
|
|
678
|
+
const col = Number(key.slice(gap + 1, gap2));
|
|
679
|
+
const sheet = key.slice(gap2 + 1);
|
|
680
|
+
return { sheet, row, col, value: compute(sheet, row, col) };
|
|
681
|
+
}));
|
|
682
|
+
}
|
|
683
|
+
const workbook = {
|
|
684
|
+
get sheets() { return [...order]; },
|
|
685
|
+
get active() { return active; },
|
|
686
|
+
get names() { return names; },
|
|
687
|
+
get tables() { return tables; },
|
|
688
|
+
setActive(name) {
|
|
689
|
+
if (sheetCells(name))
|
|
690
|
+
active = order.find((n) => n.toLowerCase() === name.toLowerCase()) ?? active;
|
|
691
|
+
},
|
|
692
|
+
addSheet(name, at) {
|
|
693
|
+
const chosen = name ?? nextSheetName(order);
|
|
694
|
+
if (!isValidSheetName(chosen)) {
|
|
695
|
+
throw new Error(`"${chosen}" is not a valid sheet name: it must be 1 to 31 characters and must not contain : \\ / ? * [ ]`);
|
|
696
|
+
}
|
|
697
|
+
if (sheetCells(chosen))
|
|
698
|
+
throw new Error(`a sheet named "${chosen}" already exists`);
|
|
699
|
+
byName.set(chosen.toLowerCase(), []);
|
|
700
|
+
const index = at === undefined ? order.length : Math.max(0, Math.min(at, order.length));
|
|
701
|
+
order.splice(index, 0, chosen);
|
|
702
|
+
active = chosen;
|
|
703
|
+
loadEngine();
|
|
704
|
+
return chosen;
|
|
705
|
+
},
|
|
706
|
+
removeSheet(name) {
|
|
707
|
+
const index = order.findIndex((n) => n.toLowerCase() === name.toLowerCase());
|
|
708
|
+
// A workbook with no sheets has nowhere to put the cursor, and Excel
|
|
709
|
+
// refuses the same way.
|
|
710
|
+
if (index < 0 || order.length <= 1)
|
|
711
|
+
return false;
|
|
712
|
+
const [removed] = order.splice(index, 1);
|
|
713
|
+
byName.delete(removed.toLowerCase());
|
|
714
|
+
// The tables that lived on it go with it: a table on a sheet that is
|
|
715
|
+
// gone resolves to cells that are gone.
|
|
716
|
+
for (const table of rawTables.list()) {
|
|
717
|
+
if (table.sheet.toLowerCase() === removed.toLowerCase())
|
|
718
|
+
rawTables.remove(table.name);
|
|
719
|
+
}
|
|
720
|
+
// Every cached value may have read the sheet that just went.
|
|
721
|
+
dropAll();
|
|
722
|
+
loadEngine();
|
|
723
|
+
if (active.toLowerCase() === removed.toLowerCase()) {
|
|
724
|
+
active = order[Math.min(index, order.length - 1)] ?? order[0];
|
|
725
|
+
}
|
|
726
|
+
settleAll();
|
|
727
|
+
return true;
|
|
728
|
+
},
|
|
729
|
+
renameSheet(from, to) {
|
|
730
|
+
const index = order.findIndex((n) => n.toLowerCase() === from.toLowerCase());
|
|
731
|
+
if (index < 0 || !isValidSheetName(to))
|
|
732
|
+
return false;
|
|
733
|
+
if (from.toLowerCase() !== to.toLowerCase() && sheetCells(to))
|
|
734
|
+
return false;
|
|
735
|
+
const cells = sheetCells(from);
|
|
736
|
+
byName.delete(from.toLowerCase());
|
|
737
|
+
byName.set(to.toLowerCase(), cells);
|
|
738
|
+
order[index] = to;
|
|
739
|
+
if (active.toLowerCase() === from.toLowerCase())
|
|
740
|
+
active = to;
|
|
741
|
+
// Every formula and name that pointed at the old name points at the
|
|
742
|
+
// new one, as in Excel; otherwise =Orders!B2 on Summary read #REF!
|
|
743
|
+
// the moment Orders became Sales. The rewrite goes through the parser,
|
|
744
|
+
// so a string literal holding the old name is not touched.
|
|
745
|
+
for (const [, sheet] of byName) {
|
|
746
|
+
for (const line of sheet) {
|
|
747
|
+
for (let c = 0; c < line.length; c += 1) {
|
|
748
|
+
const next = renameSheetReferences(line[c], from, to);
|
|
749
|
+
if (typeof next === 'string')
|
|
750
|
+
line[c] = next;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
for (const entry of names.list()) {
|
|
755
|
+
const next = renameSheetReferences(entry.refersTo, from, to);
|
|
756
|
+
if (typeof next === 'string' && next !== entry.refersTo)
|
|
757
|
+
names.define(entry.name, next);
|
|
758
|
+
}
|
|
759
|
+
// A table names the sheet it sits on, and a structured reference
|
|
760
|
+
// resolves through that name. Leaving it behind turned every
|
|
761
|
+
// `=SUM(Orders[Amount])` into #REF! the moment the sheet was renamed.
|
|
762
|
+
for (const table of rawTables.list()) {
|
|
763
|
+
if (table.sheet.toLowerCase() === from.toLowerCase())
|
|
764
|
+
rawTables.define({ ...table, sheet: to });
|
|
765
|
+
}
|
|
766
|
+
dropAll();
|
|
767
|
+
loadEngine();
|
|
768
|
+
settleAll();
|
|
769
|
+
return true;
|
|
770
|
+
},
|
|
771
|
+
moveSheet(name, to) {
|
|
772
|
+
const index = order.findIndex((n) => n.toLowerCase() === name.toLowerCase());
|
|
773
|
+
if (index < 0)
|
|
774
|
+
return false;
|
|
775
|
+
const [moved] = order.splice(index, 1);
|
|
776
|
+
order.splice(Math.max(0, Math.min(to, order.length)), 0, moved);
|
|
777
|
+
return true;
|
|
778
|
+
},
|
|
779
|
+
copySheet(from, to) {
|
|
780
|
+
const index = order.findIndex((n) => n.toLowerCase() === from.toLowerCase());
|
|
781
|
+
const source = sheetCells(from);
|
|
782
|
+
if (index < 0 || !source)
|
|
783
|
+
return null;
|
|
784
|
+
let name = to;
|
|
785
|
+
if (name === undefined) {
|
|
786
|
+
const base = order[index];
|
|
787
|
+
for (let i = 2;; i += 1) {
|
|
788
|
+
const candidate = `${base} (${i})`;
|
|
789
|
+
if (!sheetCells(candidate) && isValidSheetName(candidate)) {
|
|
790
|
+
name = candidate;
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
if (i > 1000)
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!isValidSheetName(name) || sheetCells(name))
|
|
798
|
+
return null;
|
|
799
|
+
byName.set(name.toLowerCase(), source.map((line) => [...line]));
|
|
800
|
+
order.splice(index + 1, 0, name);
|
|
801
|
+
active = name;
|
|
802
|
+
// A table belongs to its sheet, so the copy gets one of its own, as
|
|
803
|
+
// Excel's Move or Copy does. Without it the copied cells keep their
|
|
804
|
+
// `[@Qty]` formulas with no table to resolve them, and every one of
|
|
805
|
+
// them reads #REF!.
|
|
806
|
+
for (const table of rawTables.list()) {
|
|
807
|
+
if (table.sheet.toLowerCase() !== from.toLowerCase())
|
|
808
|
+
continue;
|
|
809
|
+
rawTables.define({ ...table, sheet: name, name: freeTableName(table.name) });
|
|
810
|
+
}
|
|
811
|
+
// Nothing cached read the new sheet yet, but a formula elsewhere
|
|
812
|
+
// that reads a whole column of it by name cannot exist either, so
|
|
813
|
+
// only the graph needs the new cells' precedents, which compute
|
|
814
|
+
// records on first read. An engine with a copy of the cells does
|
|
815
|
+
// need the new sheet.
|
|
816
|
+
loadEngine();
|
|
817
|
+
return name;
|
|
818
|
+
},
|
|
819
|
+
getRaw(sheet, row, col) {
|
|
820
|
+
return sheetCells(sheet)?.[row]?.[col] ?? '';
|
|
821
|
+
},
|
|
822
|
+
setRaw(sheet, row, col, text) {
|
|
823
|
+
const cells = sheetCells(sheet);
|
|
824
|
+
if (!cells || row < 0 || col < 0)
|
|
825
|
+
return;
|
|
826
|
+
while (cells.length <= row)
|
|
827
|
+
cells.push([]);
|
|
828
|
+
const line = cells[row];
|
|
829
|
+
while (line.length <= col)
|
|
830
|
+
line.push('');
|
|
831
|
+
if (line[col] === text)
|
|
832
|
+
return;
|
|
833
|
+
if (SUBTOTAL_TEXT.test(text) || SUBTOTAL_TEXT.test(line[col] ?? ''))
|
|
834
|
+
subtotalsHere = null;
|
|
835
|
+
line[col] = text;
|
|
836
|
+
engine.write?.(sheet, row, col, text);
|
|
837
|
+
emitWrite({ sheet: order.find((n) => n.toLowerCase() === sheet.toLowerCase()) ?? sheet, row, col, text });
|
|
838
|
+
const key = cellKey(sheet, row, col);
|
|
839
|
+
const keys = [key];
|
|
840
|
+
// A cell that is no longer a formula reads nothing, and spills nothing.
|
|
841
|
+
if (!text.trim().startsWith('=')) {
|
|
842
|
+
graph.setPrecedents(key, null);
|
|
843
|
+
volatile.delete(key);
|
|
844
|
+
}
|
|
845
|
+
if (spills.has(key))
|
|
846
|
+
keys.push(...clearSpill(key));
|
|
847
|
+
// Text landing in a spilled cell blocks its anchor; a cell going
|
|
848
|
+
// blank may free one that was blocked.
|
|
849
|
+
const anchor = spilledBy.get(key);
|
|
850
|
+
if (anchor !== undefined)
|
|
851
|
+
keys.push(anchor);
|
|
852
|
+
keys.push(...blocked);
|
|
853
|
+
const touched = invalidate(keys);
|
|
854
|
+
// The written cell and the anchors it touched compute now, so a new
|
|
855
|
+
// or changed spill is on record before anything reads under it;
|
|
856
|
+
// plain dependents wait for their next read, as before.
|
|
857
|
+
for (const k of keys)
|
|
858
|
+
if (isFormulaKey(k)) {
|
|
859
|
+
const at = parseCellKey(k);
|
|
860
|
+
compute(at.sheet ?? sheet, at.row, at.col);
|
|
861
|
+
}
|
|
862
|
+
reportRecalc([...new Set([...touched, ...settlePending(), ...runIterations()])]);
|
|
863
|
+
},
|
|
864
|
+
getValue(sheet, row, col) {
|
|
865
|
+
return compute(sheet, row, col);
|
|
866
|
+
},
|
|
867
|
+
evaluateText(sheet, text, override, at) {
|
|
868
|
+
const t = text.trim();
|
|
869
|
+
if (t === '')
|
|
870
|
+
return '';
|
|
871
|
+
if (t.startsWith('=')) {
|
|
872
|
+
if (!sheetCells(sheet))
|
|
873
|
+
return { error: '#REF!' };
|
|
874
|
+
const ast = parseCached(t);
|
|
875
|
+
// The cell the text is read for, so ROW() and COLUMN() and a
|
|
876
|
+
// [@Column] reference mean something in a rule's formula.
|
|
877
|
+
return ast ? evaluate(ast, contextFor(sheet, override, at ?? (override ? { row: override.row, col: override.col } : undefined))) : { error: '#PARSE!' };
|
|
878
|
+
}
|
|
879
|
+
return literal(t);
|
|
880
|
+
},
|
|
881
|
+
evaluateRange(sheet, text) {
|
|
882
|
+
const t = text.trim();
|
|
883
|
+
if (!t.startsWith('=') || !sheetCells(sheet))
|
|
884
|
+
return null;
|
|
885
|
+
const ast = parseCached(t);
|
|
886
|
+
return ast ? rangeValues(ast, contextFor(sheet)) : null;
|
|
887
|
+
},
|
|
888
|
+
snapshot(sheet) {
|
|
889
|
+
const rows = rowCount(sheet);
|
|
890
|
+
const cols = colCount(sheet);
|
|
891
|
+
const out = [];
|
|
892
|
+
for (let r = 0; r < rows; r += 1) {
|
|
893
|
+
const line = [];
|
|
894
|
+
for (let c = 0; c < cols; c += 1)
|
|
895
|
+
line.push(compute(sheet, r, c));
|
|
896
|
+
out.push(line);
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
},
|
|
900
|
+
repointAfterMove(move) {
|
|
901
|
+
if (!sheetCells(move.sheet) || !sheetCells(move.toSheet))
|
|
902
|
+
return;
|
|
903
|
+
if (move.dRow === 0 && move.dCol === 0 && move.sheet === move.toSheet)
|
|
904
|
+
return;
|
|
905
|
+
let touched = false;
|
|
906
|
+
// The whole workbook: another sheet's =Orders!A5 names a moved cell
|
|
907
|
+
// just as a local reference does.
|
|
908
|
+
for (const other of order) {
|
|
909
|
+
const cells = sheetCells(other);
|
|
910
|
+
for (let r = 0; r < cells.length; r += 1) {
|
|
911
|
+
const line = cells[r];
|
|
912
|
+
for (let c = 0; c < line.length; c += 1) {
|
|
913
|
+
const text = line[c] ?? '';
|
|
914
|
+
if (!text.startsWith('='))
|
|
915
|
+
continue;
|
|
916
|
+
const next = repointReferences(text, move, other);
|
|
917
|
+
if (typeof next === 'string' && next !== text) {
|
|
918
|
+
line[c] = next;
|
|
919
|
+
touched = true;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// A name has no home sheet, so only a qualified reference can match.
|
|
925
|
+
for (const entry of names.list()) {
|
|
926
|
+
const next = repointReferences(entry.refersTo, move, null);
|
|
927
|
+
if (typeof next === 'string' && next !== entry.refersTo) {
|
|
928
|
+
names.define(entry.name, next);
|
|
929
|
+
touched = true;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (!touched)
|
|
933
|
+
return;
|
|
934
|
+
dropAll();
|
|
935
|
+
astCache.clear();
|
|
936
|
+
loadEngine();
|
|
937
|
+
settleAll();
|
|
938
|
+
},
|
|
939
|
+
applyStructuralEdit(sheet, edit) {
|
|
940
|
+
const target = sheetCells(sheet);
|
|
941
|
+
if (!target || edit.count <= 0)
|
|
942
|
+
return;
|
|
943
|
+
// The tables move with their cells, so a structured reference keeps
|
|
944
|
+
// meaning the same column after a row is inserted above the table.
|
|
945
|
+
const moved = shiftTables(rawTables.list(), sheet, edit);
|
|
946
|
+
rawTables.clear();
|
|
947
|
+
for (const table of moved)
|
|
948
|
+
rawTables.define(table);
|
|
949
|
+
// Rewrite every formula in the WORKBOOK, not just this sheet: another
|
|
950
|
+
// sheet may hold =Orders!A5, and inserting a row in Orders has to move
|
|
951
|
+
// it exactly as if it were local.
|
|
952
|
+
for (const other of order) {
|
|
953
|
+
const cells = sheetCells(other);
|
|
954
|
+
for (let r = 0; r < cells.length; r += 1) {
|
|
955
|
+
const line = cells[r];
|
|
956
|
+
for (let c = 0; c < line.length; c += 1) {
|
|
957
|
+
const text = line[c] ?? '';
|
|
958
|
+
if (!text.startsWith('='))
|
|
959
|
+
continue;
|
|
960
|
+
// Only references INTO this sheet move: an unqualified one on
|
|
961
|
+
// another sheet belongs to that sheet's geometry, which did not
|
|
962
|
+
// change, and a reference on THIS sheet that names another sheet
|
|
963
|
+
// belongs to that one. The scope tells the rewriter both.
|
|
964
|
+
if (other !== sheet && !text.includes(`${sheet}!`) && !text.includes(`'${sheet}'!`))
|
|
965
|
+
continue;
|
|
966
|
+
const next = fixupReferences(text, edit, { sheet, self: other });
|
|
967
|
+
if (typeof next === 'string')
|
|
968
|
+
line[c] = next;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
// A name has no home sheet, so only one that names this sheet moves.
|
|
973
|
+
// NetSales = Orders!$I$2:$I$25 must not shift because a column went
|
|
974
|
+
// into Summary.
|
|
975
|
+
for (const entry of names.list()) {
|
|
976
|
+
const next = fixupReferences(entry.refersTo, edit, { sheet, self: null });
|
|
977
|
+
if (typeof next === 'string' && next !== entry.refersTo)
|
|
978
|
+
names.define(entry.name, next);
|
|
979
|
+
}
|
|
980
|
+
const blankRow = () => [];
|
|
981
|
+
if (edit.kind === 'insertRows') {
|
|
982
|
+
target.splice(edit.at, 0, ...Array.from({ length: edit.count }, blankRow));
|
|
983
|
+
}
|
|
984
|
+
else if (edit.kind === 'deleteRows') {
|
|
985
|
+
target.splice(edit.at, edit.count);
|
|
986
|
+
}
|
|
987
|
+
else if (edit.kind === 'insertCols') {
|
|
988
|
+
for (const line of target) {
|
|
989
|
+
if (line.length >= edit.at)
|
|
990
|
+
line.splice(edit.at, 0, ...Array(edit.count).fill(''));
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
for (const line of target) {
|
|
995
|
+
if (line.length > edit.at)
|
|
996
|
+
line.splice(edit.at, edit.count);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
dropAll();
|
|
1000
|
+
astCache.clear();
|
|
1001
|
+
loadEngine();
|
|
1002
|
+
settleAll();
|
|
1003
|
+
},
|
|
1004
|
+
precedents(sheet, row, col) {
|
|
1005
|
+
const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
|
|
1006
|
+
if (!name)
|
|
1007
|
+
return [];
|
|
1008
|
+
// Computing the cell records what it reads.
|
|
1009
|
+
compute(name, row, col);
|
|
1010
|
+
return graph.precedentsOf(cellKey(name, row, col)).map((key) => {
|
|
1011
|
+
const at = parseCellKey(key);
|
|
1012
|
+
return { sheet: at.sheet ?? name, row: at.row, col: at.col };
|
|
1013
|
+
});
|
|
1014
|
+
},
|
|
1015
|
+
dependents(sheet, row, col) {
|
|
1016
|
+
const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
|
|
1017
|
+
if (!name)
|
|
1018
|
+
return [];
|
|
1019
|
+
// A formula that was never evaluated has no edge yet: evaluate every
|
|
1020
|
+
// formula once so the graph is whole.
|
|
1021
|
+
for (const other of order) {
|
|
1022
|
+
const cells = sheetCells(other) ?? [];
|
|
1023
|
+
for (let r = 0; r < cells.length; r += 1) {
|
|
1024
|
+
const line = cells[r];
|
|
1025
|
+
for (let c = 0; c < line.length; c += 1)
|
|
1026
|
+
if (line[c].trim().startsWith('='))
|
|
1027
|
+
compute(other, r, c);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return graph.dependentsOf(cellKey(name, row, col)).map((key) => {
|
|
1031
|
+
const at = parseCellKey(key);
|
|
1032
|
+
return { sheet: at.sheet ?? name, row: at.row, col: at.col };
|
|
1033
|
+
});
|
|
1034
|
+
},
|
|
1035
|
+
subscribeWrites(listener) {
|
|
1036
|
+
writeListeners.add(listener);
|
|
1037
|
+
return () => { writeListeners.delete(listener); };
|
|
1038
|
+
},
|
|
1039
|
+
setHiddenRows(source) {
|
|
1040
|
+
hiddenRows = source;
|
|
1041
|
+
// Only SUBTOTAL reads what is hidden, and nothing in the dependency
|
|
1042
|
+
// graph can see that it changed - so everything is recomputed, but
|
|
1043
|
+
// only when there is a SUBTOTAL to recompute.
|
|
1044
|
+
if (!hasSubtotal())
|
|
1045
|
+
return;
|
|
1046
|
+
dropAll();
|
|
1047
|
+
settleAll();
|
|
1048
|
+
},
|
|
1049
|
+
recalculate() {
|
|
1050
|
+
dropAll();
|
|
1051
|
+
loadEngine();
|
|
1052
|
+
settleAll();
|
|
1053
|
+
},
|
|
1054
|
+
get iteration() { return { ...iteration }; },
|
|
1055
|
+
setIteration(next) {
|
|
1056
|
+
const wanted = cleanIteration({ ...iteration, ...next });
|
|
1057
|
+
if (wanted.enabled === iteration.enabled && wanted.maxIterations === iteration.maxIterations && wanted.maxChange === iteration.maxChange)
|
|
1058
|
+
return;
|
|
1059
|
+
iteration = wanted;
|
|
1060
|
+
// Turning it on turns #CYCLE! into a number, and turning it off turns
|
|
1061
|
+
// the number back into #CYCLE!, so every cached value is suspect.
|
|
1062
|
+
dropAll();
|
|
1063
|
+
settleAll();
|
|
1064
|
+
},
|
|
1065
|
+
spillOf(sheet, row, col) {
|
|
1066
|
+
const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
|
|
1067
|
+
if (!name)
|
|
1068
|
+
return null;
|
|
1069
|
+
compute(name, row, col);
|
|
1070
|
+
const key = cellKey(name, row, col);
|
|
1071
|
+
const anchor = spills.has(key) ? key : spilledBy.get(key);
|
|
1072
|
+
if (anchor === undefined)
|
|
1073
|
+
return null;
|
|
1074
|
+
const rect = spills.get(anchor);
|
|
1075
|
+
if (!rect)
|
|
1076
|
+
return null;
|
|
1077
|
+
const at = parseCellKey(anchor);
|
|
1078
|
+
return { anchor: { row: at.row, col: at.col }, rect: [rect.r1, rect.c1, rect.r2, rect.c2] };
|
|
1079
|
+
},
|
|
1080
|
+
spills(sheet) {
|
|
1081
|
+
const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
|
|
1082
|
+
if (!name)
|
|
1083
|
+
return [];
|
|
1084
|
+
settleAll();
|
|
1085
|
+
const out = [];
|
|
1086
|
+
for (const [anchor, rect] of spills) {
|
|
1087
|
+
const at = parseCellKey(anchor);
|
|
1088
|
+
if ((at.sheet ?? '').toLowerCase() !== name.toLowerCase())
|
|
1089
|
+
continue;
|
|
1090
|
+
out.push({ row: at.row, col: at.col, rect: [rect.r1, rect.c1, rect.r2, rect.c2] });
|
|
1091
|
+
}
|
|
1092
|
+
return out.sort((a, b) => a.row - b.row || a.col - b.col);
|
|
1093
|
+
},
|
|
1094
|
+
rowCount,
|
|
1095
|
+
colCount,
|
|
1096
|
+
serialize() {
|
|
1097
|
+
const list = rawTables.list();
|
|
1098
|
+
return {
|
|
1099
|
+
sheets: order.map((name) => ({ name, cells: sheetCells(name).map((r) => [...r]) })),
|
|
1100
|
+
active,
|
|
1101
|
+
names: names.serialize(),
|
|
1102
|
+
// Absent rather than empty in a workbook with no tables, so a saved
|
|
1103
|
+
// document from before they existed reads the same either way.
|
|
1104
|
+
...(list.length ? { tables: list.map((t) => ({ ...t })) } : {}),
|
|
1105
|
+
// Written only when it is on, so a document saved by a workbook that
|
|
1106
|
+
// never heard of iteration reads back identically.
|
|
1107
|
+
...(iteration.enabled ? { iteration: { ...iteration } } : {}),
|
|
1108
|
+
};
|
|
1109
|
+
},
|
|
1110
|
+
};
|
|
1111
|
+
for (const sheet of initial.length > 0 ? initial : [{ name: 'Sheet1', cells: [] }]) {
|
|
1112
|
+
byName.set(sheet.name.toLowerCase(), sheet.cells.map((r) => [...r]));
|
|
1113
|
+
order.push(sheet.name);
|
|
1114
|
+
}
|
|
1115
|
+
active = order[0] ?? '';
|
|
1116
|
+
loadEngine();
|
|
1117
|
+
settleAll();
|
|
1118
|
+
return workbook;
|
|
1119
|
+
}
|
|
1120
|
+
export { isError };
|