@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,37 @@
|
|
|
1
|
+
import { type ColumnFilter } from './auto-filter.js';
|
|
2
|
+
import type { SheetDocument } from './document.js';
|
|
3
|
+
import type { Rect } from './rects.js';
|
|
4
|
+
/** What a cell shows, as the filter menu shows it: the value under its format. */
|
|
5
|
+
export declare function displayTextOf(doc: SheetDocument, name: string, r: number, c: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* The rows a sheet's filter hides, worked out afresh: the shell keeps
|
|
8
|
+
* `filterHidden` for the sheet it shows, and a writer cannot trust it for
|
|
9
|
+
* the others.
|
|
10
|
+
*/
|
|
11
|
+
export declare function filteredRows(doc: SheetDocument, name: string): Set<number>;
|
|
12
|
+
/**
|
|
13
|
+
* Rows a file marked hidden that the sheet's own criteria hide: the
|
|
14
|
+
* filter's to fold, not the user's, so they leave the hand-hidden set and
|
|
15
|
+
* Clear Filter can bring them back. A row hidden by hand that the filter
|
|
16
|
+
* does not reach stays hidden by hand; one under a criterion a file could
|
|
17
|
+
* not carry stays hidden too, which shows the rows the file showed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function releaseFilteredRows(doc: SheetDocument, name: string): void;
|
|
20
|
+
/** The sheet's `<autoFilter>` element with its criteria, or '' without a filter. */
|
|
21
|
+
export declare function autoFilterXlsx(doc: SheetDocument, name: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The criteria of an `<autoFilter>` element, by absolute column, with
|
|
24
|
+
* whether every `<filterColumn>` was understood: the rows Excel hid under
|
|
25
|
+
* one this cannot read must stay hidden by hand, or they would reappear.
|
|
26
|
+
*/
|
|
27
|
+
export declare function filtersFromXlsx(node: Element, range: Rect, isDateColumn: (col: number) => boolean): {
|
|
28
|
+
filters: Record<number, ColumnFilter>;
|
|
29
|
+
complete: boolean;
|
|
30
|
+
};
|
|
31
|
+
/** The `<table:filter>` for a sheet's criteria, or '' when none can be said. */
|
|
32
|
+
export declare function autoFilterOds(doc: SheetDocument, name: string): string;
|
|
33
|
+
/** The criteria under a database range, by absolute column. */
|
|
34
|
+
export declare function filtersFromOds(filterEl: Element, range: Rect): {
|
|
35
|
+
filters: Record<number, ColumnFilter>;
|
|
36
|
+
complete: boolean;
|
|
37
|
+
};
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AutoFilter in a file: which rows it hides, and its criteria as an
|
|
3
|
+
* .xlsx `<autoFilter>` or an .ods `<table:filter>`, both ways.
|
|
4
|
+
*
|
|
5
|
+
* The first writers put the filter's REGION in the file and nothing else,
|
|
6
|
+
* so a sheet saved while filtered (34 of 40 rows showing) opened in Excel,
|
|
7
|
+
* in LibreOffice and here with every row visible and the arrows plain.
|
|
8
|
+
* Excel writes the rows a filter hides as hidden rows and keeps the
|
|
9
|
+
* criteria beside the region, so the file reopens in the same view and
|
|
10
|
+
* Clear Filter knows what to bring back. This does the same.
|
|
11
|
+
*
|
|
12
|
+
* What each format can say: .xlsx holds every kind the menu makes except a
|
|
13
|
+
* colour (that needs a differential style, and the rows are hidden anyway);
|
|
14
|
+
* .ods holds values and conditions, not a date period or a colour; .xls
|
|
15
|
+
* holds only the hidden rows. A criterion a format cannot hold is left
|
|
16
|
+
* out, and its rows go hidden by hand, which shows the same rows.
|
|
17
|
+
*/
|
|
18
|
+
import { colToLetters } from './address.js';
|
|
19
|
+
import { compileNumberFormat } from './number-format.js';
|
|
20
|
+
import { isError } from './ast.js';
|
|
21
|
+
import { hiddenRowsFor } from './auto-filter.js';
|
|
22
|
+
// Excel's day serials, as xlsx-document spells them; here again so this module
|
|
23
|
+
// stays free of the writers, since the document itself reads it.
|
|
24
|
+
const EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
|
|
25
|
+
const DAY_MS = 86400000;
|
|
26
|
+
const isoToSerial = (iso) => {
|
|
27
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
28
|
+
if (!m)
|
|
29
|
+
return null;
|
|
30
|
+
return Math.round((Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) - EXCEL_EPOCH_MS) / DAY_MS);
|
|
31
|
+
};
|
|
32
|
+
const serialToIso = (serial) => new Date(EXCEL_EPOCH_MS + Math.round(serial) * DAY_MS).toISOString().slice(0, 10);
|
|
33
|
+
const esc = (text) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
34
|
+
/** What a cell shows, as the filter menu shows it: the value under its format. */
|
|
35
|
+
export function displayTextOf(doc, name, r, c) {
|
|
36
|
+
const value = doc.workbook.getValue(name, r, c);
|
|
37
|
+
if (isError(value))
|
|
38
|
+
return value.error;
|
|
39
|
+
const entry = doc.get(name).formats.get(`r${r}`, colToLetters(c));
|
|
40
|
+
if (entry?.numFmt)
|
|
41
|
+
return compileNumberFormat(entry.numFmt).format(value).text;
|
|
42
|
+
if (value === '' || value == null)
|
|
43
|
+
return '';
|
|
44
|
+
if (typeof value === 'boolean')
|
|
45
|
+
return value ? 'TRUE' : 'FALSE';
|
|
46
|
+
return String(value);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The rows a sheet's filter hides, worked out afresh: the shell keeps
|
|
50
|
+
* `filterHidden` for the sheet it shows, and a writer cannot trust it for
|
|
51
|
+
* the others.
|
|
52
|
+
*/
|
|
53
|
+
export function filteredRows(doc, name) {
|
|
54
|
+
const state = doc.get(name);
|
|
55
|
+
if (!state.autoFilter)
|
|
56
|
+
return new Set();
|
|
57
|
+
return hiddenRowsFor(state.autoFilter, (r, c) => doc.workbook.getValue(name, r, c), (r, c) => displayTextOf(doc, name, r, c), { fillAt: (r, c) => state.formats.get(`r${r}`, colToLetters(c))?.fill ?? null });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Rows a file marked hidden that the sheet's own criteria hide: the
|
|
61
|
+
* filter's to fold, not the user's, so they leave the hand-hidden set and
|
|
62
|
+
* Clear Filter can bring them back. A row hidden by hand that the filter
|
|
63
|
+
* does not reach stays hidden by hand; one under a criterion a file could
|
|
64
|
+
* not carry stays hidden too, which shows the rows the file showed.
|
|
65
|
+
*/
|
|
66
|
+
export function releaseFilteredRows(doc, name) {
|
|
67
|
+
const state = doc.get(name);
|
|
68
|
+
if (!state.autoFilter || !Object.keys(state.autoFilter.filters).length || !state.hidden.rows.size)
|
|
69
|
+
return;
|
|
70
|
+
for (const r of filteredRows(doc, name))
|
|
71
|
+
state.hidden.rows.delete(r);
|
|
72
|
+
}
|
|
73
|
+
/** The distinct texts a column offers, header row left out. */
|
|
74
|
+
function columnTexts(doc, name, state, col) {
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (let r = state.range[0] + 1; r <= state.range[2]; r += 1)
|
|
77
|
+
seen.add(displayTextOf(doc, name, r, col));
|
|
78
|
+
return [...seen];
|
|
79
|
+
}
|
|
80
|
+
/** Whether a column's cells are dates, which decides how a bound is spelled. */
|
|
81
|
+
function dateColumn(doc, name, state, col) {
|
|
82
|
+
let dates = 0;
|
|
83
|
+
let filled = 0;
|
|
84
|
+
for (let r = state.range[0] + 1; r <= state.range[2]; r += 1) {
|
|
85
|
+
const raw = doc.workbook.getRaw(name, r, col);
|
|
86
|
+
if (raw === '')
|
|
87
|
+
continue;
|
|
88
|
+
filled += 1;
|
|
89
|
+
if (isoToSerial(raw.trim()) !== null)
|
|
90
|
+
dates += 1;
|
|
91
|
+
}
|
|
92
|
+
return filled > 0 && dates === filled;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// .xlsx
|
|
96
|
+
/** OOXML's dynamic filter types that a date period maps onto, both ways. */
|
|
97
|
+
const DYNAMIC = {
|
|
98
|
+
today: 'today', yesterday: 'yesterday', tomorrow: 'tomorrow',
|
|
99
|
+
thisWeek: 'thisWeek', lastWeek: 'lastWeek', nextWeek: 'nextWeek',
|
|
100
|
+
thisMonth: 'thisMonth', lastMonth: 'lastMonth', nextMonth: 'nextMonth',
|
|
101
|
+
thisQuarter: 'thisQuarter', lastQuarter: 'lastQuarter', nextQuarter: 'nextQuarter',
|
|
102
|
+
thisYear: 'thisYear', lastYear: 'lastYear', nextYear: 'nextYear', yearToDate: 'yearToDate',
|
|
103
|
+
};
|
|
104
|
+
const PERIOD_OF = Object.fromEntries(Object.entries(DYNAMIC).map(([k, v]) => [v, k]));
|
|
105
|
+
/** One condition as Excel's `<customFilter>`: the text operators ride as wildcards. */
|
|
106
|
+
function customFilterXml(cond, isDate) {
|
|
107
|
+
const value = cond.value ?? '';
|
|
108
|
+
const bound = (text) => {
|
|
109
|
+
if (!isDate)
|
|
110
|
+
return text;
|
|
111
|
+
const serial = isoToSerial(text.trim());
|
|
112
|
+
return serial === null ? text : String(serial);
|
|
113
|
+
};
|
|
114
|
+
const one = (operator, val) => `<customFilter operator="${operator}" val="${esc(val)}"/>`;
|
|
115
|
+
switch (cond.op) {
|
|
116
|
+
case 'equals': return [one('equal', bound(value))];
|
|
117
|
+
case 'notEquals': return [one('notEqual', bound(value))];
|
|
118
|
+
case 'greaterThan': return [one('greaterThan', bound(value))];
|
|
119
|
+
case 'lessThan': return [one('lessThan', bound(value))];
|
|
120
|
+
case 'between': return [one('greaterThanOrEqual', bound(value)), one('lessThanOrEqual', bound(cond.valueTo ?? ''))];
|
|
121
|
+
case 'contains': return [one('equal', `*${value}*`)];
|
|
122
|
+
case 'notContains': return [one('notEqual', `*${value}*`)];
|
|
123
|
+
case 'startsWith': return [one('equal', `${value}*`)];
|
|
124
|
+
case 'endsWith': return [one('equal', `*${value}`)];
|
|
125
|
+
case 'isBlank': return [one('equal', '')];
|
|
126
|
+
case 'isNotBlank': return [one('notEqual', '')];
|
|
127
|
+
default: return [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** A column's filter as the `<filterColumn>` Excel keeps, or '' for one .xlsx cannot hold. */
|
|
131
|
+
function filterColumnXml(doc, name, state, col, filter) {
|
|
132
|
+
const colId = col - state.range[1];
|
|
133
|
+
const open = `<filterColumn colId="${colId}">`;
|
|
134
|
+
switch (filter.kind) {
|
|
135
|
+
case 'values': {
|
|
136
|
+
// Excel lists what is IN, and the blanks as a flag on the list.
|
|
137
|
+
const excluded = new Set(filter.excluded);
|
|
138
|
+
const kept = filter.included ?? columnTexts(doc, name, state, col).filter((text) => !excluded.has(text));
|
|
139
|
+
const blank = kept.includes('');
|
|
140
|
+
const items = kept.filter((text) => text !== '').map((text) => `<filter val="${esc(text)}"/>`).join('');
|
|
141
|
+
return `${open}<filters${blank ? ' blank="1"' : ''}>${items}</filters></filterColumn>`;
|
|
142
|
+
}
|
|
143
|
+
case 'condition': {
|
|
144
|
+
const isDate = dateColumn(doc, name, state, col);
|
|
145
|
+
const parts = [...customFilterXml(filter.first, isDate), ...(filter.second ? customFilterXml(filter.second, isDate) : [])];
|
|
146
|
+
if (!parts.length)
|
|
147
|
+
return '';
|
|
148
|
+
// `and` is the default; a between is an and of its two bounds.
|
|
149
|
+
const and = filter.second ? filter.join !== 'or' : true;
|
|
150
|
+
return `${open}<customFilters${and ? ' and="1"' : ''}>${parts.join('')}</customFilters></filterColumn>`;
|
|
151
|
+
}
|
|
152
|
+
case 'top':
|
|
153
|
+
return `${open}<top10 top="${filter.top ? 1 : 0}" percent="${filter.percent ? 1 : 0}" val="${filter.count}"/></filterColumn>`;
|
|
154
|
+
case 'date': {
|
|
155
|
+
const dynamic = DYNAMIC[filter.period];
|
|
156
|
+
if (dynamic)
|
|
157
|
+
return `${open}<dynamicFilter type="${dynamic}"/></filterColumn>`;
|
|
158
|
+
const serial = (text) => { const s = isoToSerial((text ?? '').trim()); return s === null ? '' : String(s); };
|
|
159
|
+
const from = serial(filter.value);
|
|
160
|
+
if (!from)
|
|
161
|
+
return '';
|
|
162
|
+
if (filter.period === 'equals')
|
|
163
|
+
return `${open}<customFilters and="1"><customFilter operator="greaterThanOrEqual" val="${from}"/><customFilter operator="lessThan" val="${Number(from) + 1}"/></customFilters></filterColumn>`;
|
|
164
|
+
if (filter.period === 'before')
|
|
165
|
+
return `${open}<customFilters><customFilter operator="lessThan" val="${from}"/></customFilters></filterColumn>`;
|
|
166
|
+
if (filter.period === 'after')
|
|
167
|
+
return `${open}<customFilters><customFilter operator="greaterThan" val="${from}"/></customFilters></filterColumn>`;
|
|
168
|
+
const to = serial(filter.valueTo);
|
|
169
|
+
return to ? `${open}<customFilters and="1"><customFilter operator="greaterThanOrEqual" val="${from}"/><customFilter operator="lessThanOrEqual" val="${to}"/></customFilters></filterColumn>` : '';
|
|
170
|
+
}
|
|
171
|
+
case 'color':
|
|
172
|
+
return '';
|
|
173
|
+
default:
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** The sheet's `<autoFilter>` element with its criteria, or '' without a filter. */
|
|
178
|
+
export function autoFilterXlsx(doc, name) {
|
|
179
|
+
const state = doc.get(name).autoFilter;
|
|
180
|
+
if (!state)
|
|
181
|
+
return '';
|
|
182
|
+
const [r1, c1, r2, c2] = state.range;
|
|
183
|
+
const ref = `${colToLetters(c1)}${r1 + 1}:${colToLetters(c2)}${r2 + 1}`;
|
|
184
|
+
const columns = Object.entries(state.filters)
|
|
185
|
+
.map(([col, filter]) => filterColumnXml(doc, name, state, Number(col), filter))
|
|
186
|
+
.filter(Boolean)
|
|
187
|
+
.join('');
|
|
188
|
+
return columns ? `<autoFilter ref="${ref}">${columns}</autoFilter>` : `<autoFilter ref="${ref}"/>`;
|
|
189
|
+
}
|
|
190
|
+
/** A `<customFilter>` read back as a condition; the wildcards become the text operators. */
|
|
191
|
+
function conditionFromXlsx(operator, val, isDate) {
|
|
192
|
+
const unbound = (text) => {
|
|
193
|
+
if (!isDate)
|
|
194
|
+
return text;
|
|
195
|
+
const n = Number(text);
|
|
196
|
+
return Number.isFinite(n) && text.trim() !== '' ? serialToIso(n) : text;
|
|
197
|
+
};
|
|
198
|
+
const wild = (text) => {
|
|
199
|
+
if (text === '')
|
|
200
|
+
return null;
|
|
201
|
+
if (text.startsWith('*') && text.endsWith('*') && text.length >= 2)
|
|
202
|
+
return { op: 'contains', value: text.slice(1, -1) };
|
|
203
|
+
if (text.endsWith('*'))
|
|
204
|
+
return { op: 'startsWith', value: text.slice(0, -1) };
|
|
205
|
+
if (text.startsWith('*'))
|
|
206
|
+
return { op: 'endsWith', value: text.slice(1) };
|
|
207
|
+
return null;
|
|
208
|
+
};
|
|
209
|
+
switch (operator) {
|
|
210
|
+
case 'equal': {
|
|
211
|
+
if (val === '')
|
|
212
|
+
return { op: 'isBlank' };
|
|
213
|
+
return wild(val) ?? { op: 'equals', value: unbound(val) };
|
|
214
|
+
}
|
|
215
|
+
case 'notEqual': {
|
|
216
|
+
if (val === '')
|
|
217
|
+
return { op: 'isNotBlank' };
|
|
218
|
+
const w = wild(val);
|
|
219
|
+
if (w)
|
|
220
|
+
return w.op === 'contains' ? { op: 'notContains', value: w.value } : null;
|
|
221
|
+
return { op: 'notEquals', value: unbound(val) };
|
|
222
|
+
}
|
|
223
|
+
case 'greaterThan': return { op: 'greaterThan', value: unbound(val) };
|
|
224
|
+
case 'lessThan': return { op: 'lessThan', value: unbound(val) };
|
|
225
|
+
// The menu has no "or equal" operators; the nearest reading keeps the
|
|
226
|
+
// rows Excel showed apart from the one on the bound itself.
|
|
227
|
+
case 'greaterThanOrEqual': return { op: 'greaterThan', value: unbound(val) };
|
|
228
|
+
case 'lessThanOrEqual': return { op: 'lessThan', value: unbound(val) };
|
|
229
|
+
default: return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The criteria of an `<autoFilter>` element, by absolute column, with
|
|
234
|
+
* whether every `<filterColumn>` was understood: the rows Excel hid under
|
|
235
|
+
* one this cannot read must stay hidden by hand, or they would reappear.
|
|
236
|
+
*/
|
|
237
|
+
export function filtersFromXlsx(node, range, isDateColumn) {
|
|
238
|
+
const filters = {};
|
|
239
|
+
let complete = true;
|
|
240
|
+
const children = (el, local) => Array.from(el.children).filter((k) => k.localName === local);
|
|
241
|
+
for (const column of children(node, 'filterColumn')) {
|
|
242
|
+
const colId = Number(column.getAttribute('colId'));
|
|
243
|
+
if (!Number.isFinite(colId)) {
|
|
244
|
+
complete = false;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const col = range[1] + colId;
|
|
248
|
+
const isDate = isDateColumn(col);
|
|
249
|
+
const filtersEl = children(column, 'filters')[0];
|
|
250
|
+
const custom = children(column, 'customFilters')[0];
|
|
251
|
+
const top = children(column, 'top10')[0];
|
|
252
|
+
const dynamic = children(column, 'dynamicFilter')[0];
|
|
253
|
+
if (filtersEl) {
|
|
254
|
+
// Excel lists what is IN; the filter carries it as `included`.
|
|
255
|
+
const kept = children(filtersEl, 'filter').map((f) => f.getAttribute('val') ?? '');
|
|
256
|
+
if (filtersEl.getAttribute('blank') === '1')
|
|
257
|
+
kept.push('');
|
|
258
|
+
// Date group items name days, months or years; those become a period only for a single day.
|
|
259
|
+
const groups = children(filtersEl, 'dateGroupItem');
|
|
260
|
+
if (groups.length && !kept.length) {
|
|
261
|
+
complete = false;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
filters[col] = { kind: 'values', excluded: [], included: kept };
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (custom) {
|
|
268
|
+
const parts = children(custom, 'customFilter').map((f) => ({ operator: f.getAttribute('operator') ?? 'equal', val: f.getAttribute('val') ?? '' }));
|
|
269
|
+
const and = custom.getAttribute('and') === '1';
|
|
270
|
+
// A between: >= and <= together, on a number or a date.
|
|
271
|
+
if (parts.length === 2 && and && parts[0].operator === 'greaterThanOrEqual' && (parts[1].operator === 'lessThanOrEqual' || parts[1].operator === 'lessThan')) {
|
|
272
|
+
if (isDate) {
|
|
273
|
+
const from = Number(parts[0].val);
|
|
274
|
+
const to = Number(parts[1].val);
|
|
275
|
+
if (Number.isFinite(from) && Number.isFinite(to)) {
|
|
276
|
+
filters[col] = parts[1].operator === 'lessThan' && to === from + 1
|
|
277
|
+
? { kind: 'date', period: 'equals', value: serialToIso(from) }
|
|
278
|
+
: { kind: 'date', period: 'between', value: serialToIso(from), valueTo: serialToIso(to) };
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
filters[col] = { kind: 'condition', first: { op: 'between', value: parts[0].val, valueTo: parts[1].val } };
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (isDate && parts.length === 1 && (parts[0].operator === 'lessThan' || parts[0].operator === 'greaterThan')) {
|
|
286
|
+
const n = Number(parts[0].val);
|
|
287
|
+
if (Number.isFinite(n)) {
|
|
288
|
+
filters[col] = { kind: 'date', period: parts[0].operator === 'lessThan' ? 'before' : 'after', value: serialToIso(n) };
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const first = parts[0] ? conditionFromXlsx(parts[0].operator, parts[0].val, isDate) : null;
|
|
293
|
+
if (!first) {
|
|
294
|
+
complete = false;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const second = parts[1] ? conditionFromXlsx(parts[1].operator, parts[1].val, isDate) : null;
|
|
298
|
+
if (parts[1] && !second) {
|
|
299
|
+
complete = false;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
filters[col] = { kind: 'condition', first, ...(second ? { join: and ? 'and' : 'or', second } : {}) };
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (top) {
|
|
306
|
+
const val = Number(top.getAttribute('val'));
|
|
307
|
+
if (!Number.isFinite(val)) {
|
|
308
|
+
complete = false;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
filters[col] = { kind: 'top', top: top.getAttribute('top') !== '0', count: val, ...(top.getAttribute('percent') === '1' ? { percent: true } : {}) };
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (dynamic) {
|
|
315
|
+
const period = PERIOD_OF[dynamic.getAttribute('type') ?? ''];
|
|
316
|
+
if (!period) {
|
|
317
|
+
complete = false;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
filters[col] = { kind: 'date', period };
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
// A colour filter, or something newer: the rows stay hidden as they are.
|
|
324
|
+
complete = false;
|
|
325
|
+
}
|
|
326
|
+
return { filters, complete };
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// .ods
|
|
330
|
+
/** A condition as ODF's operator and value, or null for one ODF cannot say. */
|
|
331
|
+
function odsCondition(cond) {
|
|
332
|
+
const value = cond.value ?? '';
|
|
333
|
+
switch (cond.op) {
|
|
334
|
+
case 'equals': return [{ operator: '=', value }];
|
|
335
|
+
case 'notEquals': return [{ operator: '!=', value }];
|
|
336
|
+
case 'greaterThan': return [{ operator: '>', value }];
|
|
337
|
+
case 'lessThan': return [{ operator: '<', value }];
|
|
338
|
+
case 'between': return [{ operator: '>=', value }, { operator: '<=', value: cond.valueTo ?? '' }];
|
|
339
|
+
case 'contains': return [{ operator: 'contains', value }];
|
|
340
|
+
case 'notContains': return [{ operator: '!contains', value }];
|
|
341
|
+
case 'startsWith': return [{ operator: 'begins', value }];
|
|
342
|
+
case 'endsWith': return [{ operator: 'ends', value }];
|
|
343
|
+
case 'isBlank': return [{ operator: 'empty', value: '' }];
|
|
344
|
+
case 'isNotBlank': return [{ operator: '!empty', value: '' }];
|
|
345
|
+
default: return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/** The `<table:filter>` for a sheet's criteria, or '' when none can be said. */
|
|
349
|
+
export function autoFilterOds(doc, name) {
|
|
350
|
+
const state = doc.get(name).autoFilter;
|
|
351
|
+
if (!state)
|
|
352
|
+
return '';
|
|
353
|
+
const conditions = [];
|
|
354
|
+
for (const [key, filter] of Object.entries(state.filters)) {
|
|
355
|
+
const col = Number(key);
|
|
356
|
+
const field = col - state.range[1];
|
|
357
|
+
const one = (operator, value, inner = '') => `<table:filter-condition table:field-number="${field}" table:operator="${esc(operator)}" table:value="${esc(value)}"${inner ? `>${inner}</table:filter-condition>` : '/>'}`;
|
|
358
|
+
if (filter.kind === 'values') {
|
|
359
|
+
const excluded = new Set(filter.excluded);
|
|
360
|
+
const kept = filter.included ?? columnTexts(doc, name, state, col).filter((text) => !excluded.has(text));
|
|
361
|
+
conditions.push(one('=', kept[0] ?? '', kept.map((text) => `<table:filter-set-item table:value="${esc(text)}"/>`).join('')));
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (filter.kind === 'condition') {
|
|
365
|
+
const first = odsCondition(filter.first);
|
|
366
|
+
const second = filter.second ? odsCondition(filter.second) : null;
|
|
367
|
+
if (!first || (filter.second && !second))
|
|
368
|
+
continue;
|
|
369
|
+
const parts = [...first, ...(second ?? [])].map((p) => one(p.operator, p.value));
|
|
370
|
+
conditions.push(parts.length === 1 ? parts[0] : `<table:filter-${filter.second && filter.join === 'or' ? 'or' : 'and'}>${parts.join('')}</table:filter-${filter.second && filter.join === 'or' ? 'or' : 'and'}>`);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (filter.kind === 'top') {
|
|
374
|
+
conditions.push(one(`${filter.top ? 'top' : 'bottom'} ${filter.percent ? 'percent' : 'values'}`, String(filter.count)));
|
|
375
|
+
}
|
|
376
|
+
// A date period or a colour has no ODF spelling; its rows go hidden.
|
|
377
|
+
}
|
|
378
|
+
if (!conditions.length)
|
|
379
|
+
return '';
|
|
380
|
+
return `<table:filter>${conditions.length === 1 ? conditions[0] : `<table:filter-and>${conditions.join('')}</table:filter-and>`}</table:filter>`;
|
|
381
|
+
}
|
|
382
|
+
/** The criteria under a database range, by absolute column. */
|
|
383
|
+
export function filtersFromOds(filterEl, range) {
|
|
384
|
+
const filters = {};
|
|
385
|
+
let complete = true;
|
|
386
|
+
const local = (el) => el.localName;
|
|
387
|
+
const get = (el, attribute) => el.getAttribute(`table:${attribute}`) ?? el.getAttributeNS?.('urn:oasis:names:tc:opendocument:xmlns:table:1.0', attribute) ?? el.getAttribute(attribute);
|
|
388
|
+
const conditionOf = (el) => {
|
|
389
|
+
const field = Number(get(el, 'field-number'));
|
|
390
|
+
if (!Number.isFinite(field))
|
|
391
|
+
return null;
|
|
392
|
+
const col = range[1] + field;
|
|
393
|
+
const operator = get(el, 'operator') ?? '=';
|
|
394
|
+
const value = get(el, 'value') ?? '';
|
|
395
|
+
const items = Array.from(el.children).filter((k) => local(k) === 'filter-set-item').map((k) => get(k, 'value') ?? '');
|
|
396
|
+
if (items.length)
|
|
397
|
+
return { col, cond: null, kept: items };
|
|
398
|
+
const topMatch = /^(top|bottom) (values|percent)$/.exec(operator);
|
|
399
|
+
if (topMatch)
|
|
400
|
+
return { col, cond: null, top: { kind: 'top', top: topMatch[1] === 'top', count: Number(value) || 10, ...(topMatch[2] === 'percent' ? { percent: true } : {}) } };
|
|
401
|
+
const cond = (() => {
|
|
402
|
+
switch (operator) {
|
|
403
|
+
case '=': return { op: 'equals', value };
|
|
404
|
+
case '!=': return { op: 'notEquals', value };
|
|
405
|
+
case '>': return { op: 'greaterThan', value };
|
|
406
|
+
case '<': return { op: 'lessThan', value };
|
|
407
|
+
case '>=': return { op: 'greaterThan', value };
|
|
408
|
+
case '<=': return { op: 'lessThan', value };
|
|
409
|
+
case 'contains': return { op: 'contains', value };
|
|
410
|
+
case '!contains': return { op: 'notContains', value };
|
|
411
|
+
case 'begins': return { op: 'startsWith', value };
|
|
412
|
+
case 'ends': return { op: 'endsWith', value };
|
|
413
|
+
case 'empty': return { op: 'isBlank' };
|
|
414
|
+
case '!empty': return { op: 'isNotBlank' };
|
|
415
|
+
default: return null;
|
|
416
|
+
}
|
|
417
|
+
})();
|
|
418
|
+
return { col, cond };
|
|
419
|
+
};
|
|
420
|
+
const walk = (el, join) => {
|
|
421
|
+
for (const child of Array.from(el.children)) {
|
|
422
|
+
if (local(child) === 'filter-and') {
|
|
423
|
+
walk(child, 'and');
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (local(child) === 'filter-or') {
|
|
427
|
+
walk(child, 'or');
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (local(child) !== 'filter-condition')
|
|
431
|
+
continue;
|
|
432
|
+
const read = conditionOf(child);
|
|
433
|
+
if (!read) {
|
|
434
|
+
complete = false;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (read.kept) {
|
|
438
|
+
filters[read.col] = { kind: 'values', excluded: [], included: read.kept };
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (read.top) {
|
|
442
|
+
filters[read.col] = read.top;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (!read.cond) {
|
|
446
|
+
complete = false;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const have = filters[read.col];
|
|
450
|
+
if (have && have.kind === 'condition' && !have.second) {
|
|
451
|
+
// The second half of a pair: a between written as >= and <=, or an or.
|
|
452
|
+
if (have.first.op === 'greaterThan' && read.cond.op === 'lessThan' && join === 'and') {
|
|
453
|
+
filters[read.col] = { kind: 'condition', first: { op: 'between', value: have.first.value, valueTo: read.cond.value } };
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
filters[read.col] = { kind: 'condition', first: have.first, join, second: read.cond };
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
filters[read.col] = { kind: 'condition', first: read.cond };
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
walk(filterEl, 'and');
|
|
464
|
+
return { filters, complete };
|
|
465
|
+
}
|
|
@@ -29,6 +29,9 @@ export type CellFormatEntry = {
|
|
|
29
29
|
/** Background fill. */
|
|
30
30
|
fill?: string;
|
|
31
31
|
align?: 'left' | 'center' | 'right';
|
|
32
|
+
/** Vertical alignment within the row's height. Absent is the middle the
|
|
33
|
+
* sheet draws by default, so only top and bottom are ever stored. */
|
|
34
|
+
valign?: 'top' | 'center' | 'bottom';
|
|
32
35
|
fontFamily?: string;
|
|
33
36
|
fontSize?: number;
|
|
34
37
|
wrap?: boolean;
|
|
@@ -85,6 +88,18 @@ export type SheetFormatStore = {
|
|
|
85
88
|
/** The same for column ids. */
|
|
86
89
|
remapColumns(fn: (columnId: string) => string | null): void;
|
|
87
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Keys are "rowId columnId" with each half percent-encoded.
|
|
93
|
+
*
|
|
94
|
+
* Encoding is what makes the key reversible. Ids come from the grid and may
|
|
95
|
+
* contain spaces, so a plain join cannot be split back apart - and `hydrate`
|
|
96
|
+
* has to split it, or the row and column indexes come back empty and
|
|
97
|
+
* `forgetRow` / `forgetColumn` become silent no-ops on a restored store.
|
|
98
|
+
*/
|
|
99
|
+
export declare function keyOf(rowId: string, columnId: string): string;
|
|
100
|
+
/** The stored key for a cell by position, for anything building a saved
|
|
101
|
+
* sheet from the outside: an importer, a fixture, a test. */
|
|
102
|
+
export declare const formatKeyAt: (row: number, col: number) => string;
|
|
88
103
|
export declare function createFormatStore(initial?: Record<string, CellFormatEntry>): SheetFormatStore;
|
|
89
104
|
/** Turn an entry into inline style text for a cell renderer. */
|
|
90
105
|
export declare function entryToStyle(entry: CellFormatEntry | undefined): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { colToLetters } from './address.js';
|
|
1
2
|
/**
|
|
2
3
|
* Keys are "rowId columnId" with each half percent-encoded.
|
|
3
4
|
*
|
|
@@ -6,9 +7,12 @@
|
|
|
6
7
|
* has to split it, or the row and column indexes come back empty and
|
|
7
8
|
* `forgetRow` / `forgetColumn` become silent no-ops on a restored store.
|
|
8
9
|
*/
|
|
9
|
-
function keyOf(rowId, columnId) {
|
|
10
|
+
export function keyOf(rowId, columnId) {
|
|
10
11
|
return `${encodeURIComponent(rowId)} ${encodeURIComponent(columnId)}`;
|
|
11
12
|
}
|
|
13
|
+
/** The stored key for a cell by position, for anything building a saved
|
|
14
|
+
* sheet from the outside: an importer, a fixture, a test. */
|
|
15
|
+
export const formatKeyAt = (row, col) => keyOf(`r${row}`, colToLetters(col));
|
|
12
16
|
function splitKey(key) {
|
|
13
17
|
const gap = key.indexOf(' ');
|
|
14
18
|
if (gap < 0)
|
|
@@ -184,6 +188,12 @@ export function entryToStyle(entry) {
|
|
|
184
188
|
parts.push(`background:${entry.fill}`);
|
|
185
189
|
if (entry.align)
|
|
186
190
|
parts.push(`text-align:${entry.align}`);
|
|
191
|
+
// The cell span is a grid box that fills its row and centres its content;
|
|
192
|
+
// top and bottom move that anchor, the middle is the default it already draws.
|
|
193
|
+
if (entry.valign === 'top')
|
|
194
|
+
parts.push('align-items:start');
|
|
195
|
+
else if (entry.valign === 'bottom')
|
|
196
|
+
parts.push('align-items:end');
|
|
187
197
|
if (entry.fontFamily)
|
|
188
198
|
parts.push(`font-family:${entry.fontFamily}`);
|
|
189
199
|
if (entry.fontSize)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SheetField = {
|
|
2
|
+
field: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
};
|
|
5
|
+
export type SheetFromRowsOptions = {
|
|
6
|
+
/** A last row with `=SUM(...)` over each column that held a number. */
|
|
7
|
+
totals?: boolean;
|
|
8
|
+
/** The first cell of the totals row. */
|
|
9
|
+
totalsLabel?: string;
|
|
10
|
+
};
|
|
11
|
+
/** A record's value as the raw text a cell holds. */
|
|
12
|
+
export declare function cellTextOf(value: unknown): string;
|
|
13
|
+
/** The cells: a header row, a row per record, and a totals row when asked. */
|
|
14
|
+
export declare function sheetCellsFromRows(rows: ReadonlyArray<Record<string, unknown>>, fields: ReadonlyArray<SheetField | string>, options?: SheetFromRowsOptions): string[][];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rows into a sheet: the cells a workbook takes from an array of records,
|
|
3
|
+
* the way a grid's rows read when pasted into Excel. A header row of
|
|
4
|
+
* labels, one row per record with each value as the text the engine
|
|
5
|
+
* reads (a number as its digits, a boolean as TRUE / FALSE, a Date as
|
|
6
|
+
* `yyyy-mm-dd`, nothing for null), and, when asked, a totals row with a
|
|
7
|
+
* SUM over every column that held a number. What comes out is what
|
|
8
|
+
* `createWorkbook` and `<SvSheet data>` take.
|
|
9
|
+
*/
|
|
10
|
+
import { colToLetters } from './address.js';
|
|
11
|
+
/** A record's value as the raw text a cell holds. */
|
|
12
|
+
export function cellTextOf(value) {
|
|
13
|
+
if (value === null || value === undefined)
|
|
14
|
+
return '';
|
|
15
|
+
if (typeof value === 'number')
|
|
16
|
+
return Number.isFinite(value) ? String(value) : '';
|
|
17
|
+
if (typeof value === 'boolean')
|
|
18
|
+
return value ? 'TRUE' : 'FALSE';
|
|
19
|
+
if (value instanceof Date)
|
|
20
|
+
return Number.isNaN(value.getTime()) ? '' : value.toISOString().slice(0, 10);
|
|
21
|
+
if (typeof value === 'string')
|
|
22
|
+
return value.startsWith('=') ? `'${value}` : value;
|
|
23
|
+
if (typeof value === 'bigint')
|
|
24
|
+
return value.toString();
|
|
25
|
+
if (typeof value === 'object')
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
return String(value);
|
|
28
|
+
}
|
|
29
|
+
/** The cells: a header row, a row per record, and a totals row when asked. */
|
|
30
|
+
export function sheetCellsFromRows(rows, fields, options = {}) {
|
|
31
|
+
const specs = fields.map((f) => (typeof f === 'string' ? { field: f, label: f } : { field: f.field, label: f.label ?? f.field }));
|
|
32
|
+
const out = [specs.map((s) => s.label)];
|
|
33
|
+
const numeric = specs.map(() => false);
|
|
34
|
+
for (const row of rows) {
|
|
35
|
+
out.push(specs.map((s, i) => {
|
|
36
|
+
const value = row[s.field];
|
|
37
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
38
|
+
numeric[i] = true;
|
|
39
|
+
return cellTextOf(value);
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
if (options.totals && rows.length) {
|
|
43
|
+
const last = rows.length + 1;
|
|
44
|
+
const totals = specs.map((_, i) => (numeric[i] ? `=SUM(${colToLetters(i)}2:${colToLetters(i)}${last})` : ''));
|
|
45
|
+
if (!numeric[0])
|
|
46
|
+
totals[0] = options.totalsLabel ?? 'Total';
|
|
47
|
+
out.push(totals);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FunctionGroup = 'Math' | 'Statistical' | 'Logical' | 'Information' | 'Text' | 'Date & Time' | 'Lookup & Reference' | 'Other';
|
|
1
|
+
export type FunctionGroup = 'Financial' | 'Math' | 'Statistical' | 'Logical' | 'Information' | 'Text' | 'Date & Time' | 'Lookup & Reference' | 'Engineering' | 'Database' | 'Other';
|
|
2
2
|
export type FunctionInfo = {
|
|
3
3
|
name: string;
|
|
4
4
|
group: FunctionGroup;
|