@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,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One door for a file the user picked.
|
|
3
|
+
*
|
|
4
|
+
* A spreadsheet arrives as one of four things, whatever the picker called
|
|
5
|
+
* it: an .xlsx from Excel or Google Sheets, an .ods from LibreOffice (or
|
|
6
|
+
* Google Sheets, which offers both), an .xls from any Excel since 1997, or a
|
|
7
|
+
* .csv, which every one of them exports. The kind is read from the BYTES
|
|
8
|
+
* rather than from the name: a file picked on a phone often arrives with no
|
|
9
|
+
* extension and no media type, a zip always starts PK with the package
|
|
10
|
+
* inside saying which package it is, and an .xls starts with the compound
|
|
11
|
+
* file signature.
|
|
12
|
+
*/
|
|
13
|
+
import { type ZipCtor } from './xlsx-document.js';
|
|
14
|
+
import type { SheetState } from './document.js';
|
|
15
|
+
export type OpenedFile = {
|
|
16
|
+
state: SheetState;
|
|
17
|
+
kind: 'xlsx' | 'ods' | 'xls' | 'csv';
|
|
18
|
+
};
|
|
19
|
+
/** What a file holds, and which kind of file it turned out to be. */
|
|
20
|
+
export declare function readSpreadsheetFile(file: Blob & {
|
|
21
|
+
name?: string;
|
|
22
|
+
}, JSZip?: ZipCtor): Promise<OpenedFile>;
|
|
23
|
+
/** The document a file holds: .xlsx, .ods, .xls or .csv, decided by its bytes. */
|
|
24
|
+
export declare function documentFromFile(file: Blob & {
|
|
25
|
+
name?: string;
|
|
26
|
+
}, JSZip?: ZipCtor): Promise<SheetState>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One door for a file the user picked.
|
|
3
|
+
*
|
|
4
|
+
* A spreadsheet arrives as one of four things, whatever the picker called
|
|
5
|
+
* it: an .xlsx from Excel or Google Sheets, an .ods from LibreOffice (or
|
|
6
|
+
* Google Sheets, which offers both), an .xls from any Excel since 1997, or a
|
|
7
|
+
* .csv, which every one of them exports. The kind is read from the BYTES
|
|
8
|
+
* rather than from the name: a file picked on a phone often arrives with no
|
|
9
|
+
* extension and no media type, a zip always starts PK with the package
|
|
10
|
+
* inside saying which package it is, and an .xls starts with the compound
|
|
11
|
+
* file signature.
|
|
12
|
+
*/
|
|
13
|
+
import { documentFromXlsxParts, loadZip } from './xlsx-document.js';
|
|
14
|
+
import { sheetStateFromOds } from './ods-document.js';
|
|
15
|
+
import { sheetStateFromCsv } from './csv.js';
|
|
16
|
+
import { isXlsFile, sheetStateFromXls } from './xls-document.js';
|
|
17
|
+
/** A picture travels as the data URL the document holds, as in the xlsx reader. */
|
|
18
|
+
const MEDIA_PREFIX = 'xl/media/';
|
|
19
|
+
const mediaTypeOf = (path) => {
|
|
20
|
+
const extension = path.slice(path.lastIndexOf('.') + 1).toLowerCase();
|
|
21
|
+
return extension === 'jpg' || extension === 'jpeg' ? 'image/jpeg' : `image/${extension || 'png'}`;
|
|
22
|
+
};
|
|
23
|
+
/** What a file holds, and which kind of file it turned out to be. */
|
|
24
|
+
export async function readSpreadsheetFile(file, JSZip) {
|
|
25
|
+
const head = new Uint8Array(await file.slice(0, 8).arrayBuffer());
|
|
26
|
+
if (isXlsFile(head))
|
|
27
|
+
return { state: sheetStateFromXls(new Uint8Array(await file.arrayBuffer())), kind: 'xls' };
|
|
28
|
+
if (head[0] !== 0x50 || head[1] !== 0x4b) {
|
|
29
|
+
const name = (file.name ?? '').replace(/\.[^.]+$/, '');
|
|
30
|
+
return { state: sheetStateFromCsv(await file.text(), (name || 'Sheet1').slice(0, 31)), kind: 'csv' };
|
|
31
|
+
}
|
|
32
|
+
const Zip = await loadZip(JSZip);
|
|
33
|
+
const zip = await Zip.loadAsync(file);
|
|
34
|
+
const parts = {};
|
|
35
|
+
const pending = [];
|
|
36
|
+
zip.forEach((path, entry) => {
|
|
37
|
+
if (entry.dir)
|
|
38
|
+
return;
|
|
39
|
+
const clean = path.replace(/^\//, '');
|
|
40
|
+
if (clean.startsWith(MEDIA_PREFIX)) {
|
|
41
|
+
pending.push(entry.async('base64').then((base64) => { parts[clean] = `data:${mediaTypeOf(clean)};base64,${base64}`; }));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!/\.(xml|rels|vml)$/i.test(clean))
|
|
45
|
+
return;
|
|
46
|
+
pending.push(entry.async('string').then((text) => { parts[clean] = text; }));
|
|
47
|
+
});
|
|
48
|
+
await Promise.all(pending);
|
|
49
|
+
if (parts['content.xml'] && !parts['xl/workbook.xml'])
|
|
50
|
+
return { state: sheetStateFromOds(parts), kind: 'ods' };
|
|
51
|
+
return { state: documentFromXlsxParts(parts), kind: 'xlsx' };
|
|
52
|
+
}
|
|
53
|
+
/** The document a file holds: .xlsx, .ods, .xls or .csv, decided by its bytes. */
|
|
54
|
+
export async function documentFromFile(file, JSZip) {
|
|
55
|
+
return (await readSpreadsheetFile(file, JSZip)).state;
|
|
56
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's row and column outline: Data > Group, Ungroup and the little
|
|
3
|
+
* numbered bar down the side that collapses a section away.
|
|
4
|
+
*
|
|
5
|
+
* Modelled the way the FILE models it, a level per line rather than a list
|
|
6
|
+
* of ranges, because that is what `<row outlineLevel="2">` carries and
|
|
7
|
+
* because ranges have to be split, merged and renumbered on every
|
|
8
|
+
* structural edit while levels only have to be shifted. A group is then
|
|
9
|
+
* not a stored object at all: it is a maximal run of lines whose level is
|
|
10
|
+
* higher than the summary line beside it.
|
|
11
|
+
*
|
|
12
|
+
* `summaryBelow` is Excel's own default and says which side the summary
|
|
13
|
+
* line sits on. With it on, grouping rows 2 to 5 puts the collapse button
|
|
14
|
+
* on row 6, because that is where the total goes; with it off the button
|
|
15
|
+
* is on row 1. Columns have the same idea under `summaryRight`.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here touches the cells. Hiding a line is the shell's business,
|
|
18
|
+
* and `hiddenByOutline` is what tells it which.
|
|
19
|
+
*/
|
|
20
|
+
/** Excel allows eight levels, 1 to 7 plus the ungrouped 0. */
|
|
21
|
+
export declare const MAX_OUTLINE_LEVEL = 7;
|
|
22
|
+
export type OutlineState = {
|
|
23
|
+
/** Level per line index, 1 to 7. A line with no entry is at level 0. */
|
|
24
|
+
levels: Record<number, number>;
|
|
25
|
+
/** Summary lines whose group is collapsed. */
|
|
26
|
+
collapsed: number[];
|
|
27
|
+
};
|
|
28
|
+
export type OutlineSide = {
|
|
29
|
+
/** The summary sits after the detail (Excel's default for rows). */
|
|
30
|
+
summaryBelow: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare const emptyOutline: () => OutlineState;
|
|
33
|
+
export declare const outlineLevel: (state: OutlineState, line: number) => number;
|
|
34
|
+
/** True where nothing is grouped, so the shell can skip the whole gutter. */
|
|
35
|
+
export declare const isOutlined: (state: OutlineState) => boolean;
|
|
36
|
+
/** The deepest level in use, which is how many buttons the level bar shows. */
|
|
37
|
+
export declare function deepestLevel(state: OutlineState): number;
|
|
38
|
+
/**
|
|
39
|
+
* Group `from`..`to`, deepening each line by one.
|
|
40
|
+
*
|
|
41
|
+
* Grouping an already grouped run is how a nested group is made, which is
|
|
42
|
+
* why this adds a level rather than setting one.
|
|
43
|
+
*/
|
|
44
|
+
export declare function groupLines(state: OutlineState, from: number, to: number): OutlineState;
|
|
45
|
+
/** Ungroup `from`..`to`, taking one level off each line. */
|
|
46
|
+
export declare function ungroupLines(state: OutlineState, from: number, to: number): OutlineState;
|
|
47
|
+
/** Remove every group on this axis. */
|
|
48
|
+
export declare const clearOutline: () => OutlineState;
|
|
49
|
+
/** The lines a summary line owns, or an empty range when it owns none. */
|
|
50
|
+
export declare function detailRange(state: OutlineState, summary: number, side: OutlineSide): {
|
|
51
|
+
from: number;
|
|
52
|
+
to: number;
|
|
53
|
+
} | null;
|
|
54
|
+
/** Collapse or expand the group `summary` owns. */
|
|
55
|
+
export declare function toggleCollapsed(state: OutlineState, summary: number): OutlineState;
|
|
56
|
+
/** Which lines an outline hides, detail of every collapsed group included. */
|
|
57
|
+
export declare function hiddenByOutline(state: OutlineState, side: OutlineSide): Set<number>;
|
|
58
|
+
/**
|
|
59
|
+
* Show levels up to `level` and collapse everything deeper: the numbered
|
|
60
|
+
* buttons at the corner of the outline bar.
|
|
61
|
+
*
|
|
62
|
+
* Level 1 is the most collapsed view, showing only the outermost
|
|
63
|
+
* summaries; the deepest level shows everything.
|
|
64
|
+
*/
|
|
65
|
+
export declare function showLevel(state: OutlineState, level: number, side: OutlineSide): OutlineState;
|
|
66
|
+
/**
|
|
67
|
+
* The outline after lines were inserted or deleted, so a group follows the
|
|
68
|
+
* rows it was put around.
|
|
69
|
+
*
|
|
70
|
+
* An inserted line takes the level of the line it pushed down, which is
|
|
71
|
+
* what Excel does and what makes a row added inside a group part of it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function shiftOutline(state: OutlineState, at: number, count: number): OutlineState;
|
|
74
|
+
/**
|
|
75
|
+
* An outline read off a block's shape: Excel's Auto Outline.
|
|
76
|
+
*
|
|
77
|
+
* A line is detail when the line on the summary side holds a formula that
|
|
78
|
+
* reads back over it, which is what a SUBTOTAL or SUM column looks like.
|
|
79
|
+
* `isSummary` answers that for one line, so this module stays free of the
|
|
80
|
+
* workbook.
|
|
81
|
+
*/
|
|
82
|
+
export declare function autoOutline(from: number, to: number, isSummary: (line: number) => boolean, side: OutlineSide): OutlineState;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's row and column outline: Data > Group, Ungroup and the little
|
|
3
|
+
* numbered bar down the side that collapses a section away.
|
|
4
|
+
*
|
|
5
|
+
* Modelled the way the FILE models it, a level per line rather than a list
|
|
6
|
+
* of ranges, because that is what `<row outlineLevel="2">` carries and
|
|
7
|
+
* because ranges have to be split, merged and renumbered on every
|
|
8
|
+
* structural edit while levels only have to be shifted. A group is then
|
|
9
|
+
* not a stored object at all: it is a maximal run of lines whose level is
|
|
10
|
+
* higher than the summary line beside it.
|
|
11
|
+
*
|
|
12
|
+
* `summaryBelow` is Excel's own default and says which side the summary
|
|
13
|
+
* line sits on. With it on, grouping rows 2 to 5 puts the collapse button
|
|
14
|
+
* on row 6, because that is where the total goes; with it off the button
|
|
15
|
+
* is on row 1. Columns have the same idea under `summaryRight`.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here touches the cells. Hiding a line is the shell's business,
|
|
18
|
+
* and `hiddenByOutline` is what tells it which.
|
|
19
|
+
*/
|
|
20
|
+
/** Excel allows eight levels, 1 to 7 plus the ungrouped 0. */
|
|
21
|
+
export const MAX_OUTLINE_LEVEL = 7;
|
|
22
|
+
export const emptyOutline = () => ({ levels: {}, collapsed: [] });
|
|
23
|
+
export const outlineLevel = (state, line) => state.levels[line] ?? 0;
|
|
24
|
+
/** True where nothing is grouped, so the shell can skip the whole gutter. */
|
|
25
|
+
export const isOutlined = (state) => Object.values(state.levels).some((l) => l > 0);
|
|
26
|
+
/** The deepest level in use, which is how many buttons the level bar shows. */
|
|
27
|
+
export function deepestLevel(state) {
|
|
28
|
+
let max = 0;
|
|
29
|
+
for (const l of Object.values(state.levels))
|
|
30
|
+
if (l > max)
|
|
31
|
+
max = l;
|
|
32
|
+
return max;
|
|
33
|
+
}
|
|
34
|
+
const clone = (state) => ({
|
|
35
|
+
levels: { ...state.levels },
|
|
36
|
+
collapsed: [...state.collapsed],
|
|
37
|
+
});
|
|
38
|
+
/** Drop levels that fell to zero, so the record stays sparse. */
|
|
39
|
+
function tidy(state) {
|
|
40
|
+
for (const [key, value] of Object.entries(state.levels)) {
|
|
41
|
+
if (value <= 0)
|
|
42
|
+
delete state.levels[Number(key)];
|
|
43
|
+
}
|
|
44
|
+
// A summary line whose group has gone is no longer collapsed.
|
|
45
|
+
state.collapsed = [...new Set(state.collapsed)].sort((a, b) => a - b);
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Group `from`..`to`, deepening each line by one.
|
|
50
|
+
*
|
|
51
|
+
* Grouping an already grouped run is how a nested group is made, which is
|
|
52
|
+
* why this adds a level rather than setting one.
|
|
53
|
+
*/
|
|
54
|
+
export function groupLines(state, from, to) {
|
|
55
|
+
const next = clone(state);
|
|
56
|
+
const lo = Math.min(from, to);
|
|
57
|
+
const hi = Math.max(from, to);
|
|
58
|
+
for (let line = lo; line <= hi; line += 1) {
|
|
59
|
+
next.levels[line] = Math.min(outlineLevel(next, line) + 1, MAX_OUTLINE_LEVEL);
|
|
60
|
+
}
|
|
61
|
+
return tidy(next);
|
|
62
|
+
}
|
|
63
|
+
/** Ungroup `from`..`to`, taking one level off each line. */
|
|
64
|
+
export function ungroupLines(state, from, to) {
|
|
65
|
+
const next = clone(state);
|
|
66
|
+
const lo = Math.min(from, to);
|
|
67
|
+
const hi = Math.max(from, to);
|
|
68
|
+
for (let line = lo; line <= hi; line += 1) {
|
|
69
|
+
const level = outlineLevel(next, line) - 1;
|
|
70
|
+
if (level > 0)
|
|
71
|
+
next.levels[line] = level;
|
|
72
|
+
else
|
|
73
|
+
delete next.levels[line];
|
|
74
|
+
}
|
|
75
|
+
// A line that is no longer in a group cannot be a collapsed summary of
|
|
76
|
+
// one either, or its detail would stay hidden with nothing to show it.
|
|
77
|
+
next.collapsed = next.collapsed.filter((line) => {
|
|
78
|
+
if (line < lo || line > hi)
|
|
79
|
+
return true;
|
|
80
|
+
return hasDetail({ ...next }, line, { summaryBelow: true })
|
|
81
|
+
|| hasDetail({ ...next }, line, { summaryBelow: false });
|
|
82
|
+
});
|
|
83
|
+
return tidy(next);
|
|
84
|
+
}
|
|
85
|
+
/** Remove every group on this axis. */
|
|
86
|
+
export const clearOutline = () => emptyOutline();
|
|
87
|
+
/** The lines a summary line owns, or an empty range when it owns none. */
|
|
88
|
+
export function detailRange(state, summary, side) {
|
|
89
|
+
const own = outlineLevel(state, summary);
|
|
90
|
+
const step = side.summaryBelow ? -1 : 1;
|
|
91
|
+
let line = summary + step;
|
|
92
|
+
if (outlineLevel(state, line) <= own)
|
|
93
|
+
return null;
|
|
94
|
+
while (outlineLevel(state, line + step) > own)
|
|
95
|
+
line += step;
|
|
96
|
+
return side.summaryBelow ? { from: line, to: summary - 1 } : { from: summary + 1, to: line };
|
|
97
|
+
}
|
|
98
|
+
const hasDetail = (state, summary, side) => detailRange(state, summary, side) !== null;
|
|
99
|
+
/** Collapse or expand the group `summary` owns. */
|
|
100
|
+
export function toggleCollapsed(state, summary) {
|
|
101
|
+
const next = clone(state);
|
|
102
|
+
const at = next.collapsed.indexOf(summary);
|
|
103
|
+
if (at >= 0)
|
|
104
|
+
next.collapsed.splice(at, 1);
|
|
105
|
+
else
|
|
106
|
+
next.collapsed.push(summary);
|
|
107
|
+
return tidy(next);
|
|
108
|
+
}
|
|
109
|
+
/** Which lines an outline hides, detail of every collapsed group included. */
|
|
110
|
+
export function hiddenByOutline(state, side) {
|
|
111
|
+
const hidden = new Set();
|
|
112
|
+
for (const summary of state.collapsed) {
|
|
113
|
+
const range = detailRange(state, summary, side);
|
|
114
|
+
if (!range)
|
|
115
|
+
continue;
|
|
116
|
+
for (let line = range.from; line <= range.to; line += 1)
|
|
117
|
+
hidden.add(line);
|
|
118
|
+
}
|
|
119
|
+
return hidden;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Show levels up to `level` and collapse everything deeper: the numbered
|
|
123
|
+
* buttons at the corner of the outline bar.
|
|
124
|
+
*
|
|
125
|
+
* Level 1 is the most collapsed view, showing only the outermost
|
|
126
|
+
* summaries; the deepest level shows everything.
|
|
127
|
+
*/
|
|
128
|
+
export function showLevel(state, level, side) {
|
|
129
|
+
const next = clone(state);
|
|
130
|
+
next.collapsed = [];
|
|
131
|
+
const lines = Object.keys(next.levels).map(Number);
|
|
132
|
+
if (lines.length === 0)
|
|
133
|
+
return tidy(next);
|
|
134
|
+
const lo = Math.min(...lines);
|
|
135
|
+
const hi = Math.max(...lines);
|
|
136
|
+
// A summary sits just outside its detail, so the sweep runs one line
|
|
137
|
+
// wider than the outlined block on both sides.
|
|
138
|
+
for (let line = lo - 1; line <= hi + 1; line += 1) {
|
|
139
|
+
const range = detailRange(next, line, side);
|
|
140
|
+
if (!range)
|
|
141
|
+
continue;
|
|
142
|
+
// A line at level L is visible under button N when L < N, so the group
|
|
143
|
+
// whose detail starts at the summary's level plus one has to close as
|
|
144
|
+
// soon as that depth reaches N rather than only once it passes it.
|
|
145
|
+
if (outlineLevel(next, line) + 1 >= level)
|
|
146
|
+
next.collapsed.push(line);
|
|
147
|
+
}
|
|
148
|
+
return tidy(next);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The outline after lines were inserted or deleted, so a group follows the
|
|
152
|
+
* rows it was put around.
|
|
153
|
+
*
|
|
154
|
+
* An inserted line takes the level of the line it pushed down, which is
|
|
155
|
+
* what Excel does and what makes a row added inside a group part of it.
|
|
156
|
+
*/
|
|
157
|
+
export function shiftOutline(state, at, count) {
|
|
158
|
+
const next = emptyOutline();
|
|
159
|
+
const move = (line) => {
|
|
160
|
+
if (count > 0)
|
|
161
|
+
return line >= at ? line + count : line;
|
|
162
|
+
// A deleted line takes its level with it.
|
|
163
|
+
if (line >= at && line < at - count)
|
|
164
|
+
return null;
|
|
165
|
+
return line >= at ? line + count : line;
|
|
166
|
+
};
|
|
167
|
+
for (const [key, level] of Object.entries(state.levels)) {
|
|
168
|
+
const to = move(Number(key));
|
|
169
|
+
if (to !== null)
|
|
170
|
+
next.levels[to] = level;
|
|
171
|
+
}
|
|
172
|
+
if (count > 0) {
|
|
173
|
+
// An insertion inside a group inherits the level of what it displaced.
|
|
174
|
+
const inherited = state.levels[at];
|
|
175
|
+
if (inherited !== undefined) {
|
|
176
|
+
for (let i = 0; i < count; i += 1)
|
|
177
|
+
next.levels[at + i] = inherited;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const summary of state.collapsed) {
|
|
181
|
+
const to = move(summary);
|
|
182
|
+
if (to !== null)
|
|
183
|
+
next.collapsed.push(to);
|
|
184
|
+
}
|
|
185
|
+
return tidy(next);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* An outline read off a block's shape: Excel's Auto Outline.
|
|
189
|
+
*
|
|
190
|
+
* A line is detail when the line on the summary side holds a formula that
|
|
191
|
+
* reads back over it, which is what a SUBTOTAL or SUM column looks like.
|
|
192
|
+
* `isSummary` answers that for one line, so this module stays free of the
|
|
193
|
+
* workbook.
|
|
194
|
+
*/
|
|
195
|
+
export function autoOutline(from, to, isSummary, side) {
|
|
196
|
+
const next = emptyOutline();
|
|
197
|
+
let runStart = null;
|
|
198
|
+
const close = (summary, start, end) => {
|
|
199
|
+
if (end < start)
|
|
200
|
+
return;
|
|
201
|
+
for (let line = start; line <= end; line += 1)
|
|
202
|
+
next.levels[line] = 1;
|
|
203
|
+
void summary;
|
|
204
|
+
};
|
|
205
|
+
if (side.summaryBelow) {
|
|
206
|
+
for (let line = from; line <= to; line += 1) {
|
|
207
|
+
if (isSummary(line)) {
|
|
208
|
+
if (runStart !== null)
|
|
209
|
+
close(line, runStart, line - 1);
|
|
210
|
+
runStart = null;
|
|
211
|
+
}
|
|
212
|
+
else if (runStart === null)
|
|
213
|
+
runStart = line;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
for (let line = to; line >= from; line -= 1) {
|
|
218
|
+
if (isSummary(line)) {
|
|
219
|
+
if (runStart !== null)
|
|
220
|
+
close(line, line + 1, runStart);
|
|
221
|
+
runStart = null;
|
|
222
|
+
}
|
|
223
|
+
else if (runStart === null)
|
|
224
|
+
runStart = line;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return tidy(next);
|
|
228
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The array pack: Excel's dynamic array functions, the ones whose answer
|
|
3
|
+
* is a grid rather than a value. FILTER, UNIQUE, SORT, SORTBY, SEQUENCE,
|
|
4
|
+
* TRANSPOSE and TEXTSPLIT. Each takes its arguments the way every other
|
|
5
|
+
* function does (`FnArgs`, with the 2-D shape of a range in `grids`) and
|
|
6
|
+
* returns `CellValue[][]`; the evaluator spills a grid over the cells
|
|
7
|
+
* below and to the right of the formula, or reads its top-left cell where
|
|
8
|
+
* only a value fits. A grid with nothing in it is `#CALC!`, as in Excel.
|
|
9
|
+
*/
|
|
10
|
+
import { type CellValue } from '../ast.js';
|
|
11
|
+
import type { FnArgs } from '../functions.js';
|
|
12
|
+
export type Grid = CellValue[][];
|
|
13
|
+
export type ArrayFunction = (a: FnArgs) => Grid | CellValue;
|
|
14
|
+
/** The i-th argument as a grid: a range keeps its shape, a value is 1 x 1. */
|
|
15
|
+
export declare function gridArg(a: FnArgs, i: number): Grid | null;
|
|
16
|
+
export declare const ARRAY_FUNCTIONS: Record<string, ArrayFunction>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The array pack: Excel's dynamic array functions, the ones whose answer
|
|
3
|
+
* is a grid rather than a value. FILTER, UNIQUE, SORT, SORTBY, SEQUENCE,
|
|
4
|
+
* TRANSPOSE and TEXTSPLIT. Each takes its arguments the way every other
|
|
5
|
+
* function does (`FnArgs`, with the 2-D shape of a range in `grids`) and
|
|
6
|
+
* returns `CellValue[][]`; the evaluator spills a grid over the cells
|
|
7
|
+
* below and to the right of the formula, or reads its top-left cell where
|
|
8
|
+
* only a value fits. A grid with nothing in it is `#CALC!`, as in Excel.
|
|
9
|
+
*/
|
|
10
|
+
import { isError, err } from '../ast.js';
|
|
11
|
+
import { toNumber, toBool, toText, compare, isBlank } from '../coerce.js';
|
|
12
|
+
/** The i-th argument as a grid: a range keeps its shape, a value is 1 x 1. */
|
|
13
|
+
export function gridArg(a, i) {
|
|
14
|
+
const grid = a.grids[i];
|
|
15
|
+
if (grid)
|
|
16
|
+
return grid.map((row) => [...row]);
|
|
17
|
+
const list = a.args[i];
|
|
18
|
+
return list === undefined ? null : [[list[0] ?? '']];
|
|
19
|
+
}
|
|
20
|
+
const scalar = (a, i) => a.args[i]?.[0] ?? '';
|
|
21
|
+
const given = (a, i) => a.args[i] !== undefined && !(a.args[i].length === 1 && a.args[i][0] === '');
|
|
22
|
+
/** The values of a 1-D array, whichever way it runs. */
|
|
23
|
+
const line = (grid) => (grid.length === 1 ? [...grid[0]] : grid.map((row) => row[0] ?? ''));
|
|
24
|
+
const transpose = (grid) => {
|
|
25
|
+
const cols = grid.reduce((m, row) => Math.max(m, row.length), 0);
|
|
26
|
+
return Array.from({ length: cols }, (_, c) => grid.map((row) => row[c] ?? ''));
|
|
27
|
+
};
|
|
28
|
+
const rowsEqual = (a, b) => a.length === b.length && a.every((v, i) => compare(v, b[i]) === 0 && typeof v === typeof b[i]);
|
|
29
|
+
/** Rows of `grid` in the order `keys` says, a stable sort. */
|
|
30
|
+
function sortRows(grid, keys) {
|
|
31
|
+
const index = grid.map((_, i) => i);
|
|
32
|
+
index.sort((x, y) => {
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const a = key.values[x] ?? '';
|
|
35
|
+
const b = key.values[y] ?? '';
|
|
36
|
+
const blankA = isBlank(a);
|
|
37
|
+
const blankB = isBlank(b);
|
|
38
|
+
// Blanks sort last whichever way, as Excel's SORT puts them.
|
|
39
|
+
if (blankA !== blankB)
|
|
40
|
+
return blankA ? 1 : -1;
|
|
41
|
+
const d = compare(a, b);
|
|
42
|
+
if (d !== 0)
|
|
43
|
+
return key.descending ? -d : d;
|
|
44
|
+
}
|
|
45
|
+
return x - y;
|
|
46
|
+
});
|
|
47
|
+
return index.map((i) => grid[i]);
|
|
48
|
+
}
|
|
49
|
+
const empty = () => err('#CALC!');
|
|
50
|
+
export const ARRAY_FUNCTIONS = {
|
|
51
|
+
/** FILTER(array, include, [if_empty]): the rows (or columns) where include is true. */
|
|
52
|
+
FILTER: (a) => {
|
|
53
|
+
const array = gridArg(a, 0);
|
|
54
|
+
const include = gridArg(a, 1);
|
|
55
|
+
if (!array || !include)
|
|
56
|
+
return err('#VALUE!');
|
|
57
|
+
const flags = line(include);
|
|
58
|
+
const byRows = include.length > 1 || (include.length === 1 && include[0].length === 1 && array.length === 1);
|
|
59
|
+
const along = byRows && include.length !== 1 ? 'rows' : include[0].length > 1 ? 'cols' : 'rows';
|
|
60
|
+
const count = along === 'rows' ? array.length : (array[0]?.length ?? 0);
|
|
61
|
+
if (flags.length !== count)
|
|
62
|
+
return err('#VALUE!');
|
|
63
|
+
for (const f of flags)
|
|
64
|
+
if (isError(f))
|
|
65
|
+
return f;
|
|
66
|
+
const keep = flags.map((f) => toBool(f));
|
|
67
|
+
const out = along === 'rows' ? array.filter((_, i) => keep[i]) : array.map((row) => row.filter((_, i) => keep[i]));
|
|
68
|
+
if (!out.length || !out[0].length)
|
|
69
|
+
return given(a, 2) ? scalar(a, 2) : empty();
|
|
70
|
+
return out;
|
|
71
|
+
},
|
|
72
|
+
/** UNIQUE(array, [by_col], [exactly_once]): the distinct rows, first seen first. */
|
|
73
|
+
UNIQUE: (a) => {
|
|
74
|
+
const array = gridArg(a, 0);
|
|
75
|
+
if (!array)
|
|
76
|
+
return err('#VALUE!');
|
|
77
|
+
const byCol = given(a, 1) && toBool(scalar(a, 1));
|
|
78
|
+
const once = given(a, 2) && toBool(scalar(a, 2));
|
|
79
|
+
const rows = byCol ? transpose(array) : array;
|
|
80
|
+
const counts = rows.map((row) => rows.filter((other) => rowsEqual(row, other)).length);
|
|
81
|
+
const out = [];
|
|
82
|
+
rows.forEach((row, i) => {
|
|
83
|
+
if (once ? counts[i] === 1 : !out.some((seen) => rowsEqual(seen, row)))
|
|
84
|
+
out.push([...row]);
|
|
85
|
+
});
|
|
86
|
+
if (!out.length)
|
|
87
|
+
return empty();
|
|
88
|
+
return byCol ? transpose(out) : out;
|
|
89
|
+
},
|
|
90
|
+
/** SORT(array, [sort_index], [sort_order], [by_col]). */
|
|
91
|
+
SORT: (a) => {
|
|
92
|
+
const array = gridArg(a, 0);
|
|
93
|
+
if (!array)
|
|
94
|
+
return err('#VALUE!');
|
|
95
|
+
const index = given(a, 1) ? Math.trunc(toNumber(scalar(a, 1))) : 1;
|
|
96
|
+
const order = given(a, 2) ? Math.trunc(toNumber(scalar(a, 2))) : 1;
|
|
97
|
+
const byCol = given(a, 3) && toBool(scalar(a, 3));
|
|
98
|
+
if (index < 1 || (order !== 1 && order !== -1))
|
|
99
|
+
return err('#VALUE!');
|
|
100
|
+
const rows = byCol ? transpose(array) : array;
|
|
101
|
+
if (index > (rows[0]?.length ?? 0))
|
|
102
|
+
return err('#VALUE!');
|
|
103
|
+
const sorted = sortRows(rows, [{ values: rows.map((row) => row[index - 1] ?? ''), descending: order === -1 }]);
|
|
104
|
+
return byCol ? transpose(sorted) : sorted;
|
|
105
|
+
},
|
|
106
|
+
/** SORTBY(array, by_array1, [order1], [by_array2, order2], ...). */
|
|
107
|
+
SORTBY: (a) => {
|
|
108
|
+
const array = gridArg(a, 0);
|
|
109
|
+
if (!array)
|
|
110
|
+
return err('#VALUE!');
|
|
111
|
+
const keys = [];
|
|
112
|
+
for (let i = 1; i < a.args.length; i += 2) {
|
|
113
|
+
const by = gridArg(a, i);
|
|
114
|
+
if (!by)
|
|
115
|
+
return err('#VALUE!');
|
|
116
|
+
const values = line(by);
|
|
117
|
+
if (values.length !== array.length)
|
|
118
|
+
return err('#VALUE!');
|
|
119
|
+
const order = given(a, i + 1) ? Math.trunc(toNumber(scalar(a, i + 1))) : 1;
|
|
120
|
+
if (order !== 1 && order !== -1)
|
|
121
|
+
return err('#VALUE!');
|
|
122
|
+
keys.push({ values, descending: order === -1 });
|
|
123
|
+
}
|
|
124
|
+
if (!keys.length)
|
|
125
|
+
return err('#VALUE!');
|
|
126
|
+
return sortRows(array, keys);
|
|
127
|
+
},
|
|
128
|
+
/** SEQUENCE(rows, [columns], [start], [step]). */
|
|
129
|
+
SEQUENCE: (a) => {
|
|
130
|
+
const rows = Math.trunc(toNumber(scalar(a, 0)));
|
|
131
|
+
const cols = given(a, 1) ? Math.trunc(toNumber(scalar(a, 1))) : 1;
|
|
132
|
+
const start = given(a, 2) ? toNumber(scalar(a, 2)) : 1;
|
|
133
|
+
const step = given(a, 3) ? toNumber(scalar(a, 3)) : 1;
|
|
134
|
+
if (rows < 1 || cols < 1)
|
|
135
|
+
return err('#VALUE!');
|
|
136
|
+
if (rows * cols > 1000000)
|
|
137
|
+
return err('#NUM!');
|
|
138
|
+
let n = start;
|
|
139
|
+
return Array.from({ length: rows }, () => Array.from({ length: cols }, () => { const v = n; n += step; return v; }));
|
|
140
|
+
},
|
|
141
|
+
/** TRANSPOSE(array). */
|
|
142
|
+
TRANSPOSE: (a) => {
|
|
143
|
+
const array = gridArg(a, 0);
|
|
144
|
+
if (!array)
|
|
145
|
+
return err('#VALUE!');
|
|
146
|
+
return transpose(array);
|
|
147
|
+
},
|
|
148
|
+
/** TEXTSPLIT(text, col_delimiter, [row_delimiter], [ignore_empty]). */
|
|
149
|
+
TEXTSPLIT: (a) => {
|
|
150
|
+
const text = scalar(a, 0);
|
|
151
|
+
if (isError(text))
|
|
152
|
+
return text;
|
|
153
|
+
const colDelim = given(a, 1) ? toText(scalar(a, 1)) : '';
|
|
154
|
+
const rowDelim = given(a, 2) ? toText(scalar(a, 2)) : '';
|
|
155
|
+
const ignoreEmpty = given(a, 3) && toBool(scalar(a, 3));
|
|
156
|
+
const split = (s, d) => (d === '' ? [s] : s.split(d));
|
|
157
|
+
const rows = split(toText(text), rowDelim).map((row) => split(row, colDelim));
|
|
158
|
+
const kept = ignoreEmpty ? rows.map((row) => row.filter((v) => v !== '')).filter((row) => row.length) : rows;
|
|
159
|
+
if (!kept.length)
|
|
160
|
+
return empty();
|
|
161
|
+
const width = kept.reduce((m, row) => Math.max(m, row.length), 0);
|
|
162
|
+
return kept.map((row) => Array.from({ length: width }, (_, i) => (i < row.length ? asValue(row[i]) : err('#N/A'))));
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
/** A piece of split text as the cell would read it: a number where it parses. */
|
|
166
|
+
function asValue(piece) {
|
|
167
|
+
const t = piece.trim();
|
|
168
|
+
if (t === '')
|
|
169
|
+
return '';
|
|
170
|
+
const n = Number(t);
|
|
171
|
+
return Number.isFinite(n) ? n : piece;
|
|
172
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's Database family: the twelve aggregates that read a labelled
|
|
3
|
+
* block of records through a labelled block of criteria.
|
|
4
|
+
*
|
|
5
|
+
* All twelve share one shape, `D<agg>(database, field, criteria)`, and all
|
|
6
|
+
* the interesting behaviour is in how the criteria block is read:
|
|
7
|
+
*
|
|
8
|
+
* - Its first row is header names, matched against the database's own
|
|
9
|
+
* headers by text, ignoring case. A header the database does not have
|
|
10
|
+
* matches nothing.
|
|
11
|
+
* - Every row UNDER the header is one alternative: the conditions across
|
|
12
|
+
* a row are ANDed, and the rows are ORed. That is Excel's advanced
|
|
13
|
+
* filter grammar, and it is why the criteria block is two-dimensional
|
|
14
|
+
* rather than a list.
|
|
15
|
+
* - An empty criteria cell places no condition, so a criteria block that
|
|
16
|
+
* is nothing but headers matches every record.
|
|
17
|
+
*
|
|
18
|
+
* `field` is either a header's text or its one-based position. Excel takes
|
|
19
|
+
* both, and a model built by hand usually holds the text while one built
|
|
20
|
+
* by a formula holds the number.
|
|
21
|
+
*
|
|
22
|
+
* These are the only aggregates that need the 2-D shape of their range
|
|
23
|
+
* rather than its flattened values, so they read `a.grids` throughout.
|
|
24
|
+
*/
|
|
25
|
+
import { type CellValue } from '../ast.js';
|
|
26
|
+
import type { SheetFunction } from '../functions.js';
|
|
27
|
+
type Grid = ReadonlyArray<ReadonlyArray<CellValue>>;
|
|
28
|
+
/** The column `field` names, or -1 when it names none. */
|
|
29
|
+
declare function fieldColumn(field: CellValue, header: ReadonlyArray<CellValue>): number;
|
|
30
|
+
/**
|
|
31
|
+
* The records that satisfy the criteria block, as rows of the database.
|
|
32
|
+
*
|
|
33
|
+
* A criteria row with every cell blank matches everything, which is what
|
|
34
|
+
* Excel does and what makes a criteria block of headers alone a no-op.
|
|
35
|
+
*/
|
|
36
|
+
declare function selected(database: Grid, criteria: Grid): Array<ReadonlyArray<CellValue>>;
|
|
37
|
+
export declare const DATABASE_FUNCTIONS: Record<string, SheetFunction>;
|
|
38
|
+
/** Exported for the tests, which check the criteria grammar on its own. */
|
|
39
|
+
export declare const __selectRecords: typeof selected;
|
|
40
|
+
/** Exported for the tests: `field` resolution takes text or a position. */
|
|
41
|
+
export declare const __fieldColumn: typeof fieldColumn;
|
|
42
|
+
export {};
|