@svgrid/enterprise 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +94 -24
- package/README.md +36 -4
- package/dist/SvAdvancedFilter.svelte +1 -1
- package/dist/SvAlertsManager.svelte +2 -1
- package/dist/SvBoard.svelte +2 -1
- package/dist/SvExportMenu.svelte +4 -1
- package/dist/SvExportMenu.svelte.d.ts +0 -13
- package/dist/SvExpressionEditor.svelte +3 -3
- package/dist/SvFormulaBar.svelte +680 -0
- package/dist/SvFormulaBar.svelte.d.ts +69 -0
- package/dist/SvGridAlerts.svelte +49 -9
- package/dist/SvGridBoard.svelte +13 -8
- package/dist/SvGridBulkEditDrawer.svelte +26 -2
- package/dist/SvGridEditPanel.svelte +3 -0
- package/dist/SvGridScheduler.svelte +69 -40
- package/dist/SvGridSelectionBar.svelte +10 -3
- package/dist/SvPivotDesigner.svelte +263 -54
- package/dist/SvPivotDesigner.svelte.d.ts +27 -20
- package/dist/SvRibbonIcon.svelte +46 -0
- package/dist/SvRibbonIcon.svelte.d.ts +17 -0
- package/dist/SvSchemaChart.svelte +4 -2
- package/dist/SvSheet.svelte +7914 -0
- package/dist/SvSheet.svelte.d.ts +215 -0
- package/dist/SvSheetChartSetup.svelte +151 -0
- package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
- package/dist/SvSheetComment.svelte +283 -0
- package/dist/SvSheetComment.svelte.d.ts +22 -0
- package/dist/SvSheetConditionalFormat.svelte +159 -0
- package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
- package/dist/SvSheetDataValidation.svelte +273 -0
- package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
- package/dist/SvSheetEditRanges.svelte +217 -0
- package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
- package/dist/SvSheetErrors.svelte +125 -0
- package/dist/SvSheetErrors.svelte.d.ts +20 -0
- package/dist/SvSheetEvaluate.svelte +133 -0
- package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
- package/dist/SvSheetFilterMenu.svelte +320 -0
- package/dist/SvSheetFilterMenu.svelte.d.ts +22 -0
- package/dist/SvSheetFindReplace.svelte +132 -0
- package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
- package/dist/SvSheetFormatCells.svelte +465 -0
- package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
- package/dist/SvSheetGoalSeek.svelte +145 -0
- package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
- package/dist/SvSheetInsertFunction.svelte +149 -0
- package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
- package/dist/SvSheetIteration.svelte +81 -0
- package/dist/SvSheetIteration.svelte.d.ts +10 -0
- package/dist/SvSheetLink.svelte +99 -0
- package/dist/SvSheetLink.svelte.d.ts +17 -0
- package/dist/SvSheetListPicker.svelte +91 -0
- package/dist/SvSheetListPicker.svelte.d.ts +10 -0
- package/dist/SvSheetManageRules.svelte +187 -0
- package/dist/SvSheetManageRules.svelte.d.ts +16 -0
- package/dist/SvSheetNameManager.svelte +227 -0
- package/dist/SvSheetNameManager.svelte.d.ts +11 -0
- package/dist/SvSheetPageSetup.svelte +159 -0
- package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
- package/dist/SvSheetPasteSpecial.svelte +107 -0
- package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
- package/dist/SvSheetPivot.svelte +220 -0
- package/dist/SvSheetPivot.svelte.d.ts +21 -0
- package/dist/SvSheetProtectSheet.svelte +81 -0
- package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
- package/dist/SvSheetRemoveDuplicates.svelte +152 -0
- package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
- package/dist/SvSheetRibbon.svelte +1254 -0
- package/dist/SvSheetRibbon.svelte.d.ts +34 -0
- package/dist/SvSheetSizeDialog.svelte +60 -0
- package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
- package/dist/SvSheetSort.svelte +218 -0
- package/dist/SvSheetSort.svelte.d.ts +28 -0
- package/dist/SvSheetSparklines.svelte +135 -0
- package/dist/SvSheetSparklines.svelte.d.ts +14 -0
- package/dist/SvSheetTable.svelte +160 -0
- package/dist/SvSheetTable.svelte.d.ts +24 -0
- package/dist/SvSheetTabs.svelte +571 -0
- package/dist/SvSheetTabs.svelte.d.ts +50 -0
- package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
- package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
- package/dist/SvSheetTextToColumns.svelte +172 -0
- package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
- package/dist/SvSheetValidationAlert.svelte +85 -0
- package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
- package/dist/advanced-filter-enable.js +1 -1
- package/dist/alerts/alert-engine.d.ts +6 -0
- package/dist/alerts/alert-engine.js +21 -4
- package/dist/alerts/alert-formats.js +2 -0
- package/dist/alerts/alert-types.d.ts +1 -1
- package/dist/alerts.d.ts +1 -1
- package/dist/alerts.js +2 -2
- package/dist/board.js +2 -2
- package/dist/bulk-edit.d.ts +3 -1
- package/dist/bulk-edit.js +1 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +43538 -6539
- package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
- package/dist/designer/assets/SvChartMenu-BDoeqGz6.js +1 -0
- package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
- package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
- package/dist/designer/assets/SvDateTimePicker-DCPg7WGN.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
- package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
- package/dist/designer/assets/SvGridChart-Cy1ATrWh.js +3 -0
- package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
- package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
- package/dist/designer/assets/SvGridChartPanel-CATdVc9X.js +4 -0
- package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
- package/dist/designer/assets/SvGridChartView-CmsZtCED.js +1 -0
- package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
- package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
- package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
- package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
- package/dist/designer/assets/SvTabs-2mHC6oaz.js +1 -0
- package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
- package/dist/designer/assets/chart-Cn7MnNqA.js +1 -0
- package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
- package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
- package/dist/designer/assets/chart-panel-messages-Dz7PTIqT.js +1 -0
- package/dist/designer/assets/chart-summary-BD1qYB2R.js +1 -0
- package/dist/designer/assets/chart-trend-B8xr5LuD.js +1 -0
- package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
- package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
- package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
- package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
- package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
- package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
- package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
- package/dist/designer/assets/focus-trap-V2-6Fy9I.js +1 -0
- package/dist/designer/assets/{index-C_a9Cj8Y.js → index-D5bVbuzI.js} +318 -315
- package/dist/designer/assets/index-_JVFxt02.css +1 -0
- package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
- package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
- package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
- package/dist/designer/assets/src-CITpcm6b.js +1 -0
- package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
- package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
- package/dist/designer/index.html +29 -19
- package/dist/export-pdf.d.ts +164 -0
- package/dist/export-pdf.js +346 -15
- package/dist/export.js +25 -10
- package/dist/gantt/SvGridGantt.svelte +2890 -0
- package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
- package/dist/gantt/gantt-config.d.ts +73 -0
- package/dist/gantt/gantt-config.js +1 -0
- package/dist/gantt/gantt-critical-path.d.ts +67 -0
- package/dist/gantt/gantt-critical-path.js +207 -0
- package/dist/gantt/gantt-model.d.ts +257 -0
- package/dist/gantt/gantt-model.js +651 -0
- package/dist/gantt/gantt-resources.d.ts +59 -0
- package/dist/gantt/gantt-resources.js +71 -0
- package/dist/gantt/gantt.d.ts +7 -0
- package/dist/gantt/gantt.js +36 -0
- package/dist/gantt/index.d.ts +15 -0
- package/dist/gantt/index.js +22 -0
- package/dist/gantt/timeline-arrows.d.ts +102 -0
- package/dist/gantt/timeline-arrows.js +181 -0
- package/dist/import.d.ts +2 -2
- package/dist/import.js +1 -1
- package/dist/index.d.ts +93 -3
- package/dist/index.js +107 -3
- package/dist/install.js +13 -1
- package/dist/license-core.d.ts +17 -1
- package/dist/license-core.js +16 -5
- package/dist/license.d.ts +18 -2
- package/dist/license.js +71 -6
- package/dist/node/studio.js +203 -9
- package/dist/pivot-chart.d.ts +25 -1
- package/dist/pivot-chart.js +25 -69
- package/dist/pivot-enable.js +2 -2
- package/dist/scheduler-dependencies.d.ts +35 -0
- package/dist/scheduler-dependencies.js +36 -1
- package/dist/scheduler.js +2 -2
- package/dist/selection-bar.js +2 -2
- package/dist/server/SvGroupCell.svelte +213 -0
- package/dist/server/SvGroupCell.svelte.d.ts +62 -0
- package/dist/server/SvRowGroupPanel.svelte +322 -0
- package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
- package/dist/server/enable.d.ts +17 -0
- package/dist/server/enable.js +49 -0
- package/dist/server/index.d.ts +22 -0
- package/dist/server/index.js +24 -0
- package/dist/server/messages.d.ts +45 -0
- package/dist/server/messages.js +35 -0
- package/dist/server/server-group-model.d.ts +104 -0
- package/dist/server/server-group-model.js +272 -0
- package/dist/server/server-pivot.d.ts +46 -0
- package/dist/server/server-pivot.js +57 -0
- package/dist/server/server-row-model.d.ts +547 -0
- package/dist/server/server-row-model.js +1551 -0
- package/dist/server/server-selection.d.ts +99 -0
- package/dist/server/server-selection.js +215 -0
- package/dist/server/svgrid-adapter.d.ts +155 -0
- package/dist/server/svgrid-adapter.js +202 -0
- package/dist/sheet/address.d.ts +43 -0
- package/dist/sheet/address.js +81 -0
- package/dist/sheet/ast.d.ts +138 -0
- package/dist/sheet/ast.js +70 -0
- package/dist/sheet/auto-filter.d.ts +120 -0
- package/dist/sheet/auto-filter.js +295 -0
- package/dist/sheet/autocomplete.d.ts +51 -0
- package/dist/sheet/autocomplete.js +248 -0
- package/dist/sheet/cell-images.d.ts +39 -0
- package/dist/sheet/cell-images.js +90 -0
- package/dist/sheet/cell-types.d.ts +58 -0
- package/dist/sheet/cell-types.js +96 -0
- package/dist/sheet/coerce.d.ts +71 -0
- package/dist/sheet/coerce.js +289 -0
- package/dist/sheet/commands.d.ts +89 -0
- package/dist/sheet/commands.js +230 -0
- package/dist/sheet/comments.d.ts +63 -0
- package/dist/sheet/comments.js +131 -0
- package/dist/sheet/conditional-formats.d.ts +167 -0
- package/dist/sheet/conditional-formats.js +341 -0
- package/dist/sheet/csv.d.ts +32 -0
- package/dist/sheet/csv.js +158 -0
- package/dist/sheet/culture.d.ts +87 -0
- package/dist/sheet/culture.js +247 -0
- package/dist/sheet/delta.d.ts +102 -0
- package/dist/sheet/delta.js +203 -0
- package/dist/sheet/deps.d.ts +104 -0
- package/dist/sheet/deps.js +274 -0
- package/dist/sheet/document.d.ts +252 -0
- package/dist/sheet/document.js +465 -0
- package/dist/sheet/edit-keys.d.ts +14 -0
- package/dist/sheet/edit-keys.js +40 -0
- package/dist/sheet/engine.d.ts +67 -0
- package/dist/sheet/engine.js +19 -0
- package/dist/sheet/entry.d.ts +35 -0
- package/dist/sheet/entry.js +226 -0
- package/dist/sheet/error-check.d.ts +49 -0
- package/dist/sheet/error-check.js +83 -0
- package/dist/sheet/evaluate-steps.d.ts +48 -0
- package/dist/sheet/evaluate-steps.js +196 -0
- package/dist/sheet/evaluate.d.ts +128 -0
- package/dist/sheet/evaluate.js +1049 -0
- package/dist/sheet/filter-files.d.ts +37 -0
- package/dist/sheet/filter-files.js +465 -0
- package/dist/sheet/find-replace.d.ts +62 -0
- package/dist/sheet/find-replace.js +152 -0
- package/dist/sheet/format-store.d.ts +114 -0
- package/dist/sheet/format-store.js +243 -0
- package/dist/sheet/freeze.d.ts +54 -0
- package/dist/sheet/freeze.js +58 -0
- package/dist/sheet/from-rows.d.ts +14 -0
- package/dist/sheet/from-rows.js +50 -0
- package/dist/sheet/function-catalog.d.ts +10 -0
- package/dist/sheet/function-catalog.js +385 -0
- package/dist/sheet/functions.d.ts +28 -0
- package/dist/sheet/functions.js +634 -0
- package/dist/sheet/goal-seek.d.ts +73 -0
- package/dist/sheet/goal-seek.js +153 -0
- package/dist/sheet/index.d.ts +80 -0
- package/dist/sheet/index.js +80 -0
- package/dist/sheet/links.d.ts +57 -0
- package/dist/sheet/links.js +174 -0
- package/dist/sheet/merges.d.ts +71 -0
- package/dist/sheet/merges.js +137 -0
- package/dist/sheet/messages.d.ts +778 -0
- package/dist/sheet/messages.js +806 -0
- package/dist/sheet/names.d.ts +31 -0
- package/dist/sheet/names.js +87 -0
- package/dist/sheet/navigate.d.ts +52 -0
- package/dist/sheet/navigate.js +135 -0
- package/dist/sheet/number-format.d.ts +110 -0
- package/dist/sheet/number-format.js +905 -0
- package/dist/sheet/objects.d.ts +106 -0
- package/dist/sheet/objects.js +149 -0
- package/dist/sheet/ods-document.d.ts +44 -0
- package/dist/sheet/ods-document.js +1275 -0
- package/dist/sheet/open-file.d.ts +26 -0
- package/dist/sheet/open-file.js +56 -0
- package/dist/sheet/outline.d.ts +82 -0
- package/dist/sheet/outline.js +228 -0
- package/dist/sheet/packs/array.d.ts +16 -0
- package/dist/sheet/packs/array.js +172 -0
- package/dist/sheet/packs/database.d.ts +42 -0
- package/dist/sheet/packs/database.js +142 -0
- package/dist/sheet/packs/engineering.d.ts +2 -0
- package/dist/sheet/packs/engineering.js +296 -0
- package/dist/sheet/packs/financial.d.ts +5 -0
- package/dist/sheet/packs/financial.js +220 -0
- package/dist/sheet/packs/math-stats.d.ts +2 -0
- package/dist/sheet/packs/math-stats.js +372 -0
- package/dist/sheet/packs/special.d.ts +44 -0
- package/dist/sheet/packs/special.js +187 -0
- package/dist/sheet/packs/stats-dist.d.ts +2 -0
- package/dist/sheet/packs/stats-dist.js +785 -0
- package/dist/sheet/packs/text-date.d.ts +2 -0
- package/dist/sheet/packs/text-date.js +332 -0
- package/dist/sheet/packs/trig.d.ts +2 -0
- package/dist/sheet/packs/trig.js +73 -0
- package/dist/sheet/page-setup.d.ts +73 -0
- package/dist/sheet/page-setup.js +73 -0
- package/dist/sheet/palette.d.ts +19 -0
- package/dist/sheet/palette.js +84 -0
- package/dist/sheet/parse.d.ts +13 -0
- package/dist/sheet/parse.js +140 -0
- package/dist/sheet/paste-special.d.ts +186 -0
- package/dist/sheet/paste-special.js +601 -0
- package/dist/sheet/pivot-range.d.ts +160 -0
- package/dist/sheet/pivot-range.js +333 -0
- package/dist/sheet/presence.d.ts +73 -0
- package/dist/sheet/presence.js +79 -0
- package/dist/sheet/print.d.ts +104 -0
- package/dist/sheet/print.js +169 -0
- package/dist/sheet/protection.d.ts +75 -0
- package/dist/sheet/protection.js +101 -0
- package/dist/sheet/rects.d.ts +45 -0
- package/dist/sheet/rects.js +112 -0
- package/dist/sheet/refs.d.ts +130 -0
- package/dist/sheet/refs.js +413 -0
- package/dist/sheet/ribbon-icons.d.ts +31 -0
- package/dist/sheet/ribbon-icons.js +536 -0
- package/dist/sheet/ribbon.d.ts +173 -0
- package/dist/sheet/ribbon.js +1054 -0
- package/dist/sheet/sheet-kinds.d.ts +74 -0
- package/dist/sheet/sheet-kinds.js +91 -0
- package/dist/sheet/shortcuts.d.ts +174 -0
- package/dist/sheet/shortcuts.js +674 -0
- package/dist/sheet/sort.d.ts +51 -0
- package/dist/sheet/sort.js +107 -0
- package/dist/sheet/sparklines.d.ts +111 -0
- package/dist/sheet/sparklines.js +204 -0
- package/dist/sheet/structure.d.ts +87 -0
- package/dist/sheet/structure.js +166 -0
- package/dist/sheet/table-styles.d.ts +74 -0
- package/dist/sheet/table-styles.js +152 -0
- package/dist/sheet/tables.d.ts +89 -0
- package/dist/sheet/tables.js +178 -0
- package/dist/sheet/tokenize.d.ts +71 -0
- package/dist/sheet/tokenize.js +355 -0
- package/dist/sheet/transforms.d.ts +76 -0
- package/dist/sheet/transforms.js +154 -0
- package/dist/sheet/validation.d.ts +108 -0
- package/dist/sheet/validation.js +278 -0
- package/dist/sheet/workbook.d.ts +217 -0
- package/dist/sheet/workbook.js +1120 -0
- package/dist/sheet/xls-cfb.d.ts +32 -0
- package/dist/sheet/xls-cfb.js +290 -0
- package/dist/sheet/xls-document.d.ts +10 -0
- package/dist/sheet/xls-document.js +1243 -0
- package/dist/sheet/xls-formula.d.ts +40 -0
- package/dist/sheet/xls-formula.js +569 -0
- package/dist/sheet/xlsx-document.d.ts +108 -0
- package/dist/sheet/xlsx-document.js +1849 -0
- package/dist/sheet/xlsx-drawing.d.ts +99 -0
- package/dist/sheet/xlsx-drawing.js +420 -0
- package/dist/sheet-enable.d.ts +6 -0
- package/dist/sheet-enable.js +45 -0
- package/dist/sheet-text.d.ts +7 -0
- package/dist/sheet-text.js +29 -0
- package/dist/sources/auth-supabase.js +2 -2
- package/dist/sources/realtime-supabase.js +2 -2
- package/dist/sources/rest.d.ts +6 -2
- package/dist/sources/rest.js +42 -7
- package/dist/sources/supabase.d.ts +10 -1
- package/dist/sources/supabase.js +73 -40
- package/dist/studio/emit-project.js +14 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio/project.d.ts +11 -2
- package/dist/studio/project.js +5 -2
- package/dist/studio/ui-components.generated.js +170 -3
- package/dist/sveltekit/in-memory.d.ts +2 -2
- package/dist/sveltekit/in-memory.js +125 -4
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/query-plan.d.ts +50 -4
- package/dist/sveltekit/query-plan.js +25 -3
- package/dist/sveltekit/sql-source.d.ts +6 -0
- package/dist/sveltekit/sql-source.js +34 -6
- package/dist/sveltekit/sql.d.ts +31 -0
- package/dist/sveltekit/sql.js +135 -25
- package/dist/sveltekit/transport.d.ts +10 -2
- package/dist/sveltekit/transport.js +30 -6
- package/dist/upgrade-prompt.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/watermark.js +35 -2
- package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
- package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
- package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
- package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
- package/dist/wc/SvField-B_kurx_P.js +149 -0
- package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
- package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
- package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
- package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
- package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
- package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
- package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
- package/dist/wc/SvModal-DrfOYYyX.js +189 -0
- package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
- package/dist/wc/chart-6OXlybVT.js +4341 -0
- package/dist/wc/chart-export-CeSADGfi.js +135 -0
- package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
- package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
- package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
- package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
- package/dist/wc/client-DQIzjDBG.js +3064 -0
- package/dist/wc/column-resize-DUPgynvc.js +101 -0
- package/dist/wc/date-format-DS-z_h9v.js +1130 -0
- package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
- package/dist/wc/editor-contract-peBDxir_.js +22 -0
- package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
- package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
- package/dist/wc/list-option-DrRyivWp.js +82 -0
- package/dist/wc/popover-WsGlvtQv.js +69 -0
- package/dist/wc/positioning-BiKLfu1E.js +112 -0
- package/dist/wc/react.d.ts +41 -0
- package/dist/wc/react.js +90 -0
- package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
- package/dist/wc/row-resize-limG_N1z.js +111 -0
- package/dist/wc/sv-sheet-element.d.ts +97 -0
- package/dist/wc/sv-sheet-element.js +44213 -0
- package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
- package/dist/wc/validate-BqDthkNr.js +88 -0
- package/dist/wc/vue.d.ts +31 -0
- package/dist/wc/vue.js +75 -0
- package/package.json +36 -6
- package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
- package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
- package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
- package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
- package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
- package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
- package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
- package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
- package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
- package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
- package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
- package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
- package/dist/designer/assets/chart-DXFganU8.js +0 -1
- package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
- package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
- package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
- package/dist/designer/assets/easing-zaypKrr0.js +0 -3
- package/dist/designer/assets/index-k2stW6L8.css +0 -1
- package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
- package/dist/designer/assets/src-D9p27OGC.js +0 -58
- package/dist/designer/assets/validate-h-aslRV9.js +0 -1
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel number-format strings, compiled once and applied many times.
|
|
3
|
+
*
|
|
4
|
+
* The grammar is small but full of traps, so this file is explicit about the
|
|
5
|
+
* ones that bite:
|
|
6
|
+
*
|
|
7
|
+
* - A pattern has up to four sections separated by `;`:
|
|
8
|
+
* positive;negative;zero;text. With two, the second covers negatives AND
|
|
9
|
+
* zero. With one, it covers everything. A negative rendered by section two
|
|
10
|
+
* uses its ABSOLUTE value, which is why `0.00;(0.00)` shows `(1.50)` and
|
|
11
|
+
* not `(-1.50)`.
|
|
12
|
+
* - `,` means two different things. Between placeholders it is a thousands
|
|
13
|
+
* separator; immediately before the decimal point (or at the end) it scales
|
|
14
|
+
* the number down by a thousand per comma.
|
|
15
|
+
* - `%` scales UP by 100 and prints a literal percent.
|
|
16
|
+
* - `0` prints a digit or a zero, `#` prints a digit or nothing, `?` prints a
|
|
17
|
+
* digit or a space. They can be mixed: `#,##0.0#` keeps one decimal always
|
|
18
|
+
* and a second only when it is there.
|
|
19
|
+
* - Anything in quotes, after a backslash, or in the set of always-literal
|
|
20
|
+
* characters is emitted as-is.
|
|
21
|
+
* - `[Red]` and friends set a colour rather than printing.
|
|
22
|
+
* - `@` is the text placeholder and only appears in the fourth section.
|
|
23
|
+
*
|
|
24
|
+
* Dates are detected rather than configured: a pattern containing date tokens
|
|
25
|
+
* outside quotes is a date pattern. `m` is minutes when it follows an hour
|
|
26
|
+
* token and months otherwise, which is the one piece of context the token
|
|
27
|
+
* scanner has to carry.
|
|
28
|
+
*/
|
|
29
|
+
const COLORS = {
|
|
30
|
+
BLACK: '#000000', BLUE: '#0000ff', CYAN: '#00ffff', GREEN: '#008000',
|
|
31
|
+
MAGENTA: '#ff00ff', RED: '#ff0000', WHITE: '#ffffff', YELLOW: '#ffff00',
|
|
32
|
+
};
|
|
33
|
+
/** Split on `;` at the top level, ignoring separators inside quotes, brackets
|
|
34
|
+
* or escapes. A naive `pattern.split(';')` breaks `"a;b"` and `[<100]`. */
|
|
35
|
+
function splitSections(pattern) {
|
|
36
|
+
const out = [];
|
|
37
|
+
let current = '';
|
|
38
|
+
let inQuote = false;
|
|
39
|
+
let inBracket = false;
|
|
40
|
+
for (let i = 0; i < pattern.length; i += 1) {
|
|
41
|
+
const ch = pattern[i];
|
|
42
|
+
if (ch === '\\') {
|
|
43
|
+
current += ch + (pattern[i + 1] ?? '');
|
|
44
|
+
i += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (ch === '"') {
|
|
48
|
+
inQuote = !inQuote;
|
|
49
|
+
current += ch;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!inQuote && ch === '[') {
|
|
53
|
+
inBracket = true;
|
|
54
|
+
current += ch;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!inQuote && ch === ']') {
|
|
58
|
+
inBracket = false;
|
|
59
|
+
current += ch;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === ';' && !inQuote && !inBracket) {
|
|
63
|
+
out.push(current);
|
|
64
|
+
current = '';
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
current += ch;
|
|
68
|
+
}
|
|
69
|
+
out.push(current);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
const CONDITION = /^(<=|>=|<>|<|>|=)\s*(-?\d+(?:\.\d+)?)$/;
|
|
73
|
+
function passes(condition, n) {
|
|
74
|
+
switch (condition.op) {
|
|
75
|
+
case '<': return n < condition.value;
|
|
76
|
+
case '<=': return n <= condition.value;
|
|
77
|
+
case '>': return n > condition.value;
|
|
78
|
+
case '>=': return n >= condition.value;
|
|
79
|
+
case '=': return n === condition.value;
|
|
80
|
+
case '<>': return n !== condition.value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function parseSection(raw) {
|
|
84
|
+
let color;
|
|
85
|
+
let condition;
|
|
86
|
+
let body = '';
|
|
87
|
+
// Walk rather than regex-replace: a pattern like `"[" @ "]"` has brackets
|
|
88
|
+
// INSIDE quoted literals, and a global /\[[^\]]*\]/ happily eats the text
|
|
89
|
+
// between them.
|
|
90
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
91
|
+
const ch = raw[i];
|
|
92
|
+
if (ch === '\\') {
|
|
93
|
+
body += ch + (raw[i + 1] ?? '');
|
|
94
|
+
i += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (ch === '"') {
|
|
98
|
+
const end = raw.indexOf('"', i + 1);
|
|
99
|
+
const stop = end < 0 ? raw.length : end;
|
|
100
|
+
body += raw.slice(i, stop + 1);
|
|
101
|
+
i = stop;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (ch === '[') {
|
|
105
|
+
const end = raw.indexOf(']', i + 1);
|
|
106
|
+
if (end < 0) {
|
|
107
|
+
body += ch;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const inner = raw.slice(i + 1, end).trim().toUpperCase();
|
|
111
|
+
// An elapsed-time token is part of the pattern rather than a
|
|
112
|
+
// directive about it, so it stays in the body for the renderer.
|
|
113
|
+
if (ELAPSED_TOKEN.test(raw.slice(i))) {
|
|
114
|
+
body += raw.slice(i, end + 1);
|
|
115
|
+
i = end;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const named = COLORS[inner];
|
|
119
|
+
if (named)
|
|
120
|
+
color = named;
|
|
121
|
+
const cond = CONDITION.exec(inner);
|
|
122
|
+
if (cond)
|
|
123
|
+
condition = { op: cond[1], value: Number(cond[2]) };
|
|
124
|
+
// Anything else ([$-409] locale tags and the like) is dropped rather
|
|
125
|
+
// than printed.
|
|
126
|
+
i = end;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
body += ch;
|
|
130
|
+
}
|
|
131
|
+
return condition ? { raw, color, condition, body } : { raw, color, body };
|
|
132
|
+
}
|
|
133
|
+
const DATE_TOKEN = /^(yyyy|yy|mmmmm|mmmm|mmm|mm|m|dddd|ddd|dd|d|hh|h|ss|s|AM\/PM|am\/pm|A\/P|a\/p)/;
|
|
134
|
+
/**
|
|
135
|
+
* Excel's elapsed-time tokens: the hours, minutes or seconds a duration
|
|
136
|
+
* holds in TOTAL rather than the clock's reading of it, which is what a
|
|
137
|
+
* timesheet needs. `[h]:mm` over 1.5 days is 36:00, not 12:00. Only h, m
|
|
138
|
+
* and s go in these brackets, so a colour (`[Red]`) or a condition
|
|
139
|
+
* (`[>100]`) never matches.
|
|
140
|
+
*/
|
|
141
|
+
const ELAPSED_TOKEN = /^\[(hh?|mm?|ss?)\]/;
|
|
142
|
+
/** Does this section describe a date? Scans outside quotes and escapes. */
|
|
143
|
+
function looksLikeDate(body) {
|
|
144
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
145
|
+
const ch = body[i];
|
|
146
|
+
if (ch === '\\') {
|
|
147
|
+
i += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (ch === '"') {
|
|
151
|
+
const end = body.indexOf('"', i + 1);
|
|
152
|
+
i = end < 0 ? body.length : end;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (ELAPSED_TOKEN.test(body.slice(i)))
|
|
156
|
+
return true;
|
|
157
|
+
if (ch === '[') {
|
|
158
|
+
// Any other bracket is a colour or a condition, and says nothing
|
|
159
|
+
// about whether this is a date.
|
|
160
|
+
const end = body.indexOf(']', i + 1);
|
|
161
|
+
i = end < 0 ? body.length : end;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (DATE_TOKEN.test(body.slice(i))) {
|
|
165
|
+
// `m` alone is ambiguous with the thousands/number grammar only in
|
|
166
|
+
// theory: a numeric pattern never contains a bare letter outside quotes.
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
|
|
173
|
+
'July', 'August', 'September', 'October', 'November', 'December'];
|
|
174
|
+
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
175
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
176
|
+
function toDate(value) {
|
|
177
|
+
if (value instanceof Date)
|
|
178
|
+
return value;
|
|
179
|
+
if (typeof value === 'number') {
|
|
180
|
+
// Excel serial day, with the usual 1899-12-30 epoch. That is correct for
|
|
181
|
+
// every date from 1900-03-01 on, which is every date anyone stores. Excel
|
|
182
|
+
// itself is a day out below serial 61 because it counts a 1900-02-29 that
|
|
183
|
+
// never existed; matching that bug exactly would break real dates.
|
|
184
|
+
return new Date(Date.UTC(1899, 11, 30) + value * 86400000);
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
187
|
+
const d = new Date(value);
|
|
188
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/** The meridiem token a pattern carries, which puts `h` on a 12-hour clock. */
|
|
193
|
+
function meridiemOf(body) {
|
|
194
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
195
|
+
if (body[i] === '\\') {
|
|
196
|
+
i += 1;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (body[i] === '"') {
|
|
200
|
+
const end = body.indexOf('"', i + 1);
|
|
201
|
+
i = end < 0 ? body.length : end;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const m = /^(AM\/PM|am\/pm|A\/P|a\/p)/.exec(body.slice(i));
|
|
205
|
+
if (m)
|
|
206
|
+
return m[1];
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The finest unit a time pattern shows, in milliseconds, or null for a
|
|
212
|
+
* pattern that shows no time. Excel ROUNDS to it: 23:59:40 under `hh:mm`
|
|
213
|
+
* reads 00:00, and the date rolls with it where the pattern shows one.
|
|
214
|
+
*/
|
|
215
|
+
function finestUnit(body) {
|
|
216
|
+
let unit = null;
|
|
217
|
+
let sawHour = false;
|
|
218
|
+
const finer = (ms) => { unit = unit === null ? ms : Math.min(unit, ms); };
|
|
219
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
220
|
+
if (body[i] === '\\') {
|
|
221
|
+
i += 1;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (body[i] === '"') {
|
|
225
|
+
const end = body.indexOf('"', i + 1);
|
|
226
|
+
i = end < 0 ? body.length : end;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const fraction = /^\.(0+)/.exec(body.slice(i));
|
|
230
|
+
if (fraction && unit !== null) {
|
|
231
|
+
finer(Math.pow(10, 3 - fraction[1].length));
|
|
232
|
+
i += fraction[0].length - 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const m = DATE_TOKEN.exec(body.slice(i));
|
|
236
|
+
if (!m)
|
|
237
|
+
continue;
|
|
238
|
+
const token = m[1];
|
|
239
|
+
i += token.length - 1;
|
|
240
|
+
if (token === 'h' || token === 'hh') {
|
|
241
|
+
sawHour = true;
|
|
242
|
+
finer(3600000);
|
|
243
|
+
}
|
|
244
|
+
else if (token === 's' || token === 'ss')
|
|
245
|
+
finer(1000);
|
|
246
|
+
else if ((token === 'm' || token === 'mm') && sawHour)
|
|
247
|
+
finer(60000);
|
|
248
|
+
}
|
|
249
|
+
return unit;
|
|
250
|
+
}
|
|
251
|
+
function renderDate(body, date, serial) {
|
|
252
|
+
const unit = finestUnit(body);
|
|
253
|
+
if (unit !== null && unit > 0)
|
|
254
|
+
date = new Date(Math.round(date.getTime() / unit) * unit);
|
|
255
|
+
const meridiem = meridiemOf(body);
|
|
256
|
+
const hourOf = (d) => (meridiem ? ((d.getUTCHours() + 11) % 12) + 1 : d.getUTCHours());
|
|
257
|
+
let out = '';
|
|
258
|
+
let sawHour = false;
|
|
259
|
+
let sawSecond = false;
|
|
260
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
261
|
+
const ch = body[i];
|
|
262
|
+
if (ch === '\\') {
|
|
263
|
+
out += body[i + 1] ?? '';
|
|
264
|
+
i += 1;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const elapsed = ELAPSED_TOKEN.exec(body.slice(i));
|
|
268
|
+
if (elapsed) {
|
|
269
|
+
const token = elapsed[1];
|
|
270
|
+
i += elapsed[0].length - 1;
|
|
271
|
+
// Without a serial there is no duration to total, so the clock's own
|
|
272
|
+
// reading is the best answer; with one, the whole of it counts.
|
|
273
|
+
const days = serial ?? 0;
|
|
274
|
+
const total = token.startsWith('h') ? days * 24 : token.startsWith('m') ? days * 1440 : days * 86400;
|
|
275
|
+
const whole = Math.floor(Math.abs(total) + 1e-9) * (total < 0 ? -1 : 1);
|
|
276
|
+
out += token.length === 2 ? pad2(whole) : String(whole);
|
|
277
|
+
if (token.startsWith('h'))
|
|
278
|
+
sawHour = true;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (ch === '"') {
|
|
282
|
+
const end = body.indexOf('"', i + 1);
|
|
283
|
+
out += body.slice(i + 1, end < 0 ? body.length : end);
|
|
284
|
+
i = end < 0 ? body.length : end;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const fraction = sawSecond ? /^\.(0+)/.exec(body.slice(i)) : null;
|
|
288
|
+
if (fraction) {
|
|
289
|
+
const digits = fraction[1].length;
|
|
290
|
+
out += `.${String(date.getUTCMilliseconds()).padStart(3, '0').slice(0, digits).padEnd(digits, '0')}`;
|
|
291
|
+
i += fraction[0].length - 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const m = DATE_TOKEN.exec(body.slice(i));
|
|
295
|
+
if (!m) {
|
|
296
|
+
out += ch;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const token = m[1];
|
|
300
|
+
i += token.length - 1;
|
|
301
|
+
if (token === 's' || token === 'ss')
|
|
302
|
+
sawSecond = true;
|
|
303
|
+
switch (token) {
|
|
304
|
+
case 'yyyy':
|
|
305
|
+
out += date.getUTCFullYear();
|
|
306
|
+
break;
|
|
307
|
+
case 'yy':
|
|
308
|
+
out += pad2(date.getUTCFullYear() % 100);
|
|
309
|
+
break;
|
|
310
|
+
case 'mmmm':
|
|
311
|
+
out += MONTHS[date.getUTCMonth()];
|
|
312
|
+
break;
|
|
313
|
+
case 'mmm':
|
|
314
|
+
out += MONTHS[date.getUTCMonth()].slice(0, 3);
|
|
315
|
+
break;
|
|
316
|
+
case 'dddd':
|
|
317
|
+
out += DAYS[date.getUTCDay()];
|
|
318
|
+
break;
|
|
319
|
+
case 'ddd':
|
|
320
|
+
out += DAYS[date.getUTCDay()].slice(0, 3);
|
|
321
|
+
break;
|
|
322
|
+
case 'dd':
|
|
323
|
+
out += pad2(date.getUTCDate());
|
|
324
|
+
break;
|
|
325
|
+
case 'd':
|
|
326
|
+
out += date.getUTCDate();
|
|
327
|
+
break;
|
|
328
|
+
case 'hh':
|
|
329
|
+
out += pad2(hourOf(date));
|
|
330
|
+
sawHour = true;
|
|
331
|
+
break;
|
|
332
|
+
case 'h':
|
|
333
|
+
out += hourOf(date);
|
|
334
|
+
sawHour = true;
|
|
335
|
+
break;
|
|
336
|
+
case 'ss':
|
|
337
|
+
out += pad2(date.getUTCSeconds());
|
|
338
|
+
break;
|
|
339
|
+
case 's':
|
|
340
|
+
out += date.getUTCSeconds();
|
|
341
|
+
break;
|
|
342
|
+
// Excel prints what the token spells: AM/PM in full, A/P as the one
|
|
343
|
+
// letter, and each in the case it was written in.
|
|
344
|
+
case 'AM/PM':
|
|
345
|
+
out += date.getUTCHours() < 12 ? 'AM' : 'PM';
|
|
346
|
+
break;
|
|
347
|
+
case 'am/pm':
|
|
348
|
+
out += date.getUTCHours() < 12 ? 'am' : 'pm';
|
|
349
|
+
break;
|
|
350
|
+
case 'A/P':
|
|
351
|
+
out += date.getUTCHours() < 12 ? 'A' : 'P';
|
|
352
|
+
break;
|
|
353
|
+
case 'a/p':
|
|
354
|
+
out += date.getUTCHours() < 12 ? 'a' : 'p';
|
|
355
|
+
break;
|
|
356
|
+
case 'mmmmm':
|
|
357
|
+
out += MONTHS[date.getUTCMonth()].slice(0, 1);
|
|
358
|
+
break;
|
|
359
|
+
// `m` and `mm` are MINUTES after an hour token, MONTHS otherwise. This
|
|
360
|
+
// is the one piece of context the scanner carries, and getting it wrong
|
|
361
|
+
// turns 09:05 into 09:09.
|
|
362
|
+
case 'mm':
|
|
363
|
+
out += sawHour ? pad2(date.getUTCMinutes()) : pad2(date.getUTCMonth() + 1);
|
|
364
|
+
break;
|
|
365
|
+
case 'm':
|
|
366
|
+
out += sawHour ? date.getUTCMinutes() : date.getUTCMonth() + 1;
|
|
367
|
+
break;
|
|
368
|
+
default: out += token;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
const FRACTION = /^(?:([#0?]+)\s+)?([#0?]+)\/([#0?]+|\d+)$/;
|
|
374
|
+
/** The plan for a fraction section, or null when it is not one. */
|
|
375
|
+
function planFraction(body) {
|
|
376
|
+
// Only the bare shape reads as a fraction; anything with quoted text or an
|
|
377
|
+
// escape around it stays on the ordinary numeric path, where it always was.
|
|
378
|
+
const m = FRACTION.exec(body.trim());
|
|
379
|
+
if (!m)
|
|
380
|
+
return null;
|
|
381
|
+
const den = m[3];
|
|
382
|
+
return { int: m[1] ?? null, num: m[2], den, denLiteral: /^\d+$/.test(den) ? Number(den) : null };
|
|
383
|
+
}
|
|
384
|
+
/** The closest numerator and denominator, with the denominator capped. */
|
|
385
|
+
function bestFraction(value, maxDen) {
|
|
386
|
+
// Stern-Brocot: take the mediant of the two bounding fractions until the
|
|
387
|
+
// denominator would pass the cap. Exact for anything that fits, and it
|
|
388
|
+
// cannot run longer than the cap.
|
|
389
|
+
let loNum = 0;
|
|
390
|
+
let loDen = 1;
|
|
391
|
+
let hiNum = 1;
|
|
392
|
+
let hiDen = 0;
|
|
393
|
+
let bestNum = Math.round(value);
|
|
394
|
+
let bestDen = 1;
|
|
395
|
+
let bestErr = Math.abs(value - bestNum);
|
|
396
|
+
for (let i = 0; i < 64; i += 1) {
|
|
397
|
+
const num = loNum + hiNum;
|
|
398
|
+
const den = loDen + hiDen;
|
|
399
|
+
if (den > maxDen)
|
|
400
|
+
break;
|
|
401
|
+
const mediant = num / den;
|
|
402
|
+
const err = Math.abs(value - mediant);
|
|
403
|
+
if (err < bestErr - 1e-12) {
|
|
404
|
+
bestErr = err;
|
|
405
|
+
bestNum = num;
|
|
406
|
+
bestDen = den;
|
|
407
|
+
}
|
|
408
|
+
if (err < 1e-12)
|
|
409
|
+
break;
|
|
410
|
+
if (mediant < value) {
|
|
411
|
+
loNum = num;
|
|
412
|
+
loDen = den;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
hiNum = num;
|
|
416
|
+
hiDen = den;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return { num: bestNum, den: bestDen };
|
|
420
|
+
}
|
|
421
|
+
/** A placeholder group as Excel pads it: `0` a zero, `?` a space, `#` nothing. */
|
|
422
|
+
function padHolders(text, holders) {
|
|
423
|
+
if (text.length >= holders.length)
|
|
424
|
+
return text;
|
|
425
|
+
const filler = holders.includes('0') ? '0' : holders.includes('?') ? ' ' : '';
|
|
426
|
+
return filler ? filler.repeat(holders.length - text.length) + text : text;
|
|
427
|
+
}
|
|
428
|
+
/** One number through a fraction pattern. */
|
|
429
|
+
function renderFraction(plan, value) {
|
|
430
|
+
const sign = value < 0 ? '-' : '';
|
|
431
|
+
const magnitude = Math.abs(value);
|
|
432
|
+
const whole = plan.int ? Math.floor(magnitude + 1e-12) : 0;
|
|
433
|
+
const rest = magnitude - whole;
|
|
434
|
+
const maxDen = plan.denLiteral ?? Math.pow(10, plan.den.length) - 1;
|
|
435
|
+
const found = plan.denLiteral
|
|
436
|
+
? { num: Math.round(rest * plan.denLiteral), den: plan.denLiteral }
|
|
437
|
+
: bestFraction(rest, maxDen);
|
|
438
|
+
let num = found.num;
|
|
439
|
+
const den = found.den;
|
|
440
|
+
let carried = whole;
|
|
441
|
+
// Rounding up to a whole: 0.99 over `# ?/?` is 1, not 1/1.
|
|
442
|
+
if (den > 0 && num >= den) {
|
|
443
|
+
carried += Math.floor(num / den);
|
|
444
|
+
num %= den;
|
|
445
|
+
}
|
|
446
|
+
if (!plan.int) {
|
|
447
|
+
return `${sign}${padHolders(String(num + carried * den), plan.num)}/${padHolders(String(den), plan.den)}`;
|
|
448
|
+
}
|
|
449
|
+
const blanks = (holders) => (holders.includes('?') ? ' '.repeat(holders.length) : '');
|
|
450
|
+
const wholeText = carried === 0 && !plan.int.includes('0')
|
|
451
|
+
? ' '.repeat(plan.int.includes('?') ? plan.int.length : 0)
|
|
452
|
+
: padHolders(String(carried), plan.int);
|
|
453
|
+
// No remainder: Excel leaves the fraction's width blank rather than
|
|
454
|
+
// printing 0/1, so a column of them still lines up.
|
|
455
|
+
const fraction = num === 0
|
|
456
|
+
? `${blanks(plan.num)} ${blanks(plan.den)}`
|
|
457
|
+
: `${padHolders(String(num), plan.num)}/${padHolders(String(den), plan.den)}`;
|
|
458
|
+
return `${sign}${wholeText} ${fraction}`.replace(/\s+$/, '');
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Pull a numeric section apart into literal text and digit placeholders.
|
|
462
|
+
*
|
|
463
|
+
* Literal text before the first digit is the prefix and after the last is
|
|
464
|
+
* the suffix. Literal text BETWEEN integer digits, as in `000-00-0000` or
|
|
465
|
+
* `(###) ###-####`, turns the integer side into a mask that the digits are
|
|
466
|
+
* laid into from the right, which is how Excel's Special formats work.
|
|
467
|
+
*
|
|
468
|
+
* `_x` (Excel: leave the width of x) and `*x` (fill the cell with x) both
|
|
469
|
+
* become one space here: text has no cell width to leave or fill, and one
|
|
470
|
+
* space is what keeps `_($* #,##0.00_)` reading as `$ 1,234.00`.
|
|
471
|
+
*/
|
|
472
|
+
function planNumeric(body) {
|
|
473
|
+
let prefix = '';
|
|
474
|
+
let suffix = '';
|
|
475
|
+
let intPlaceholders = '';
|
|
476
|
+
let fracPlaceholders = '';
|
|
477
|
+
let useGrouping = false;
|
|
478
|
+
let percent = false;
|
|
479
|
+
let scientific = false;
|
|
480
|
+
let expPlaceholders = '';
|
|
481
|
+
let scale = 1;
|
|
482
|
+
let seenPlaceholder = false;
|
|
483
|
+
let afterDecimal = false;
|
|
484
|
+
// The integer side as tokens, literal text included, until the decimal
|
|
485
|
+
// point. `pending` holds literal text after the last integer digit, which
|
|
486
|
+
// is part of the mask only if another digit follows it.
|
|
487
|
+
const mask = [];
|
|
488
|
+
let pending = '';
|
|
489
|
+
let maskHasLiteral = false;
|
|
490
|
+
const literal = (text) => {
|
|
491
|
+
if (!seenPlaceholder)
|
|
492
|
+
prefix += text;
|
|
493
|
+
else if (afterDecimal || scientific)
|
|
494
|
+
suffix += text;
|
|
495
|
+
else
|
|
496
|
+
pending += text;
|
|
497
|
+
};
|
|
498
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
499
|
+
const ch = body[i];
|
|
500
|
+
if (ch === '\\') {
|
|
501
|
+
literal(body[i + 1] ?? '');
|
|
502
|
+
i += 1;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (ch === '_' || ch === '*') {
|
|
506
|
+
literal(' ');
|
|
507
|
+
i += 1;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (ch === '"') {
|
|
511
|
+
const end = body.indexOf('"', i + 1);
|
|
512
|
+
literal(body.slice(i + 1, end < 0 ? body.length : end));
|
|
513
|
+
i = end < 0 ? body.length : end;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (ch === '0' || ch === '#' || ch === '?') {
|
|
517
|
+
seenPlaceholder = true;
|
|
518
|
+
// After an E+ the placeholders size the EXPONENT, not the mantissa.
|
|
519
|
+
// Counting them as decimals turns 0.00E+00 into four decimal places.
|
|
520
|
+
if (scientific)
|
|
521
|
+
expPlaceholders += ch;
|
|
522
|
+
else if (afterDecimal)
|
|
523
|
+
fracPlaceholders += ch;
|
|
524
|
+
else {
|
|
525
|
+
intPlaceholders += ch;
|
|
526
|
+
if (pending) {
|
|
527
|
+
mask.push({ kind: 'lit', text: pending });
|
|
528
|
+
maskHasLiteral = true;
|
|
529
|
+
pending = '';
|
|
530
|
+
}
|
|
531
|
+
mask.push({ kind: 'digit', ch });
|
|
532
|
+
}
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (ch === '.') {
|
|
536
|
+
afterDecimal = true;
|
|
537
|
+
seenPlaceholder = true;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (ch === ',') {
|
|
541
|
+
// A comma between placeholders groups; one trailing (before the decimal
|
|
542
|
+
// point or at the end of the pattern) scales by a thousand.
|
|
543
|
+
const rest = body.slice(i + 1);
|
|
544
|
+
if (/^[0#?]/.test(rest))
|
|
545
|
+
useGrouping = true;
|
|
546
|
+
else
|
|
547
|
+
scale *= 1000;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (ch === '%') {
|
|
551
|
+
percent = true;
|
|
552
|
+
literal('%');
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if ((ch === 'E' || ch === 'e') && /^[+-]/.test(body.slice(i + 1))) {
|
|
556
|
+
scientific = true;
|
|
557
|
+
i += 1;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
literal(ch);
|
|
561
|
+
}
|
|
562
|
+
// Literal text after the last integer digit belongs to the suffix, before
|
|
563
|
+
// whatever the fraction and exponent added.
|
|
564
|
+
if (pending)
|
|
565
|
+
suffix = pending + suffix;
|
|
566
|
+
// A `?` on the integer side pads with a space, which the mask renderer
|
|
567
|
+
// does and the plain path (digits, grouping) does not.
|
|
568
|
+
const needsMask = maskHasLiteral || intPlaceholders.includes('?');
|
|
569
|
+
return {
|
|
570
|
+
prefix, suffix, intPlaceholders, intMask: needsMask ? mask : null,
|
|
571
|
+
fracPlaceholders, expPlaceholders,
|
|
572
|
+
useGrouping, scale, percent, scientific,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
/** Lay digits into a mask from the right: `000-00-0000` on 123456789 is
|
|
576
|
+
* `123-45-6789`; digits left over go in front, missing ones follow the
|
|
577
|
+
* placeholder's rule, so `###-####` on 1234 is `-1234` as Excel gives. */
|
|
578
|
+
function renderMask(mask, digits) {
|
|
579
|
+
let out = '';
|
|
580
|
+
let at = digits.length;
|
|
581
|
+
for (let i = mask.length - 1; i >= 0; i -= 1) {
|
|
582
|
+
const token = mask[i];
|
|
583
|
+
if (token.kind === 'lit') {
|
|
584
|
+
out = token.text + out;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (at > 0) {
|
|
588
|
+
at -= 1;
|
|
589
|
+
out = digits[at] + out;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (token.ch === '0')
|
|
593
|
+
out = `0${out}`;
|
|
594
|
+
else if (token.ch === '?')
|
|
595
|
+
out = ` ${out}`;
|
|
596
|
+
}
|
|
597
|
+
return digits.slice(0, at) + out;
|
|
598
|
+
}
|
|
599
|
+
function group(intText, mark) {
|
|
600
|
+
return intText.replace(/\B(?=(\d{3})+(?!\d))/g, mark);
|
|
601
|
+
}
|
|
602
|
+
function renderNumeric(plan, value, marks) {
|
|
603
|
+
let n = value;
|
|
604
|
+
if (plan.percent)
|
|
605
|
+
n *= 100;
|
|
606
|
+
if (plan.scale !== 1)
|
|
607
|
+
n /= plan.scale;
|
|
608
|
+
if (plan.scientific) {
|
|
609
|
+
// The integer placeholders set the STEP of the exponent, which is what
|
|
610
|
+
// makes `##0.0E+0` engineering notation: three of them means the
|
|
611
|
+
// exponent moves in threes and the mantissa carries up to three integer
|
|
612
|
+
// digits, so 12345 reads 12.3E+3 rather than 1.2E+4. The everyday
|
|
613
|
+
// `0.00E+00` has one, and is the ordinary scientific form.
|
|
614
|
+
const step = Math.max(1, plan.intPlaceholders.length);
|
|
615
|
+
const decimals = plan.fracPlaceholders.length;
|
|
616
|
+
let exponent = n === 0 ? 0 : Math.floor(Math.log10(Math.abs(n)));
|
|
617
|
+
exponent = Math.floor(exponent / step) * step;
|
|
618
|
+
let mantissa = n / Math.pow(10, exponent);
|
|
619
|
+
// Rounding can carry the mantissa over its own width: 999.95 with one
|
|
620
|
+
// decimal is 1000.0, which is 1.0 a step further out.
|
|
621
|
+
if (Math.abs(Number(mantissa.toFixed(decimals))) >= Math.pow(10, step)) {
|
|
622
|
+
mantissa /= Math.pow(10, step);
|
|
623
|
+
exponent += step;
|
|
624
|
+
}
|
|
625
|
+
const sign = exponent < 0 ? '-' : '+';
|
|
626
|
+
const digits = String(Math.abs(exponent)).padStart(plan.expPlaceholders.length || 1, '0');
|
|
627
|
+
return `${plan.prefix}${mantissa.toFixed(decimals)}E${sign}${digits}${plan.suffix}`;
|
|
628
|
+
}
|
|
629
|
+
const decimals = plan.fracPlaceholders.length;
|
|
630
|
+
const fixed = Math.abs(n).toFixed(decimals);
|
|
631
|
+
const [rawInt = '0', rawFrac = ''] = fixed.split('.');
|
|
632
|
+
// Pad the integer side out to the number of `0` placeholders, and drop a
|
|
633
|
+
// leading zero entirely when the pattern is all `#`.
|
|
634
|
+
const minInt = plan.intPlaceholders.split('').filter((c) => c === '0').length;
|
|
635
|
+
let intText = rawInt;
|
|
636
|
+
if (plan.intMask) {
|
|
637
|
+
intText = renderMask(plan.intMask, rawInt === '0' && minInt === 0 ? '' : rawInt);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
if (intText.length < minInt)
|
|
641
|
+
intText = intText.padStart(minInt, '0');
|
|
642
|
+
if (minInt === 0 && intText === '0' && plan.intPlaceholders.length > 0)
|
|
643
|
+
intText = '';
|
|
644
|
+
if (plan.useGrouping && intText !== '')
|
|
645
|
+
intText = group(intText, marks.group);
|
|
646
|
+
}
|
|
647
|
+
// Trim optional decimals from the right: `0.0#` on 1.5 gives "1.5", not
|
|
648
|
+
// "1.50". A `0` placeholder is never trimmed, a `#` always may be, and a
|
|
649
|
+
// `?` becomes a space so columns line up.
|
|
650
|
+
let fracText = rawFrac;
|
|
651
|
+
for (let i = plan.fracPlaceholders.length - 1; i >= 0; i -= 1) {
|
|
652
|
+
const kind = plan.fracPlaceholders[i];
|
|
653
|
+
if (kind === '0')
|
|
654
|
+
break;
|
|
655
|
+
if (fracText[i] !== '0')
|
|
656
|
+
break;
|
|
657
|
+
fracText = kind === '?' ? `${fracText.slice(0, i)} ` : fracText.slice(0, i);
|
|
658
|
+
}
|
|
659
|
+
const body = fracText === '' ? intText : `${intText}${marks.decimal}${fracText}`;
|
|
660
|
+
// `#` on zero prints nothing, which is how "hide zeros" patterns work. The
|
|
661
|
+
// fallback below is only for a pattern with no digit placeholders at all.
|
|
662
|
+
return plan.prefix + body + plan.suffix;
|
|
663
|
+
}
|
|
664
|
+
/** A section with no digit placeholders is pure literal text: `"-"` as the
|
|
665
|
+
* zero section must print `-`, not `-0`. */
|
|
666
|
+
function hasPlaceholders(plan) {
|
|
667
|
+
return plan.intPlaceholders !== '' || plan.fracPlaceholders !== '';
|
|
668
|
+
}
|
|
669
|
+
/** The text section: `@` is the value, quotes and backslashes wrap
|
|
670
|
+
* literals, `_x` and `*x` are one space, as on the numeric side. */
|
|
671
|
+
function renderText(body, value) {
|
|
672
|
+
let out = '';
|
|
673
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
674
|
+
const ch = body[i];
|
|
675
|
+
if (ch === '\\') {
|
|
676
|
+
out += body[i + 1] ?? '';
|
|
677
|
+
i += 1;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (ch === '_' || ch === '*') {
|
|
681
|
+
out += ' ';
|
|
682
|
+
i += 1;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (ch === '"') {
|
|
686
|
+
const end = body.indexOf('"', i + 1);
|
|
687
|
+
out += body.slice(i + 1, end < 0 ? body.length : end);
|
|
688
|
+
i = end < 0 ? body.length : end;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
out += ch === '@' ? value : ch;
|
|
692
|
+
}
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
/** Excel's General: up to 11 significant digits, no trailing zeros. */
|
|
696
|
+
function general(value) {
|
|
697
|
+
if (typeof value === 'number') {
|
|
698
|
+
if (!Number.isFinite(value))
|
|
699
|
+
return String(value);
|
|
700
|
+
if (Number.isInteger(value))
|
|
701
|
+
return String(value);
|
|
702
|
+
return String(Number(value.toPrecision(11)));
|
|
703
|
+
}
|
|
704
|
+
if (typeof value === 'boolean')
|
|
705
|
+
return value ? 'TRUE' : 'FALSE';
|
|
706
|
+
return value === null || value === undefined ? '' : String(value);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Compile a format string. Compilation is cached per pattern, so a column of
|
|
710
|
+
* ten thousand cells sharing one format parses it once.
|
|
711
|
+
*/
|
|
712
|
+
const cache = new Map();
|
|
713
|
+
export const INVARIANT_MARKS = { decimal: '.', group: ',' };
|
|
714
|
+
export function compileNumberFormat(pattern, marks = INVARIANT_MARKS) {
|
|
715
|
+
// The marks join the cache key, so a sheet showing one locale does not
|
|
716
|
+
// serve its rendered numbers to a sheet showing another.
|
|
717
|
+
const key = marks === INVARIANT_MARKS ? pattern : `${pattern}\u0000${marks.decimal}${marks.group}`;
|
|
718
|
+
const hit = cache.get(key);
|
|
719
|
+
if (hit)
|
|
720
|
+
return hit;
|
|
721
|
+
const sections = splitSections(pattern).map(parseSection);
|
|
722
|
+
const positive = sections[0];
|
|
723
|
+
const negative = sections[1];
|
|
724
|
+
const zero = sections[2];
|
|
725
|
+
const text = sections[3];
|
|
726
|
+
const conditional = sections.some((sec) => sec.condition);
|
|
727
|
+
// Plans are built once here, not per value.
|
|
728
|
+
const plans = new Map();
|
|
729
|
+
const fractions = new Map();
|
|
730
|
+
const dateFlags = new Map();
|
|
731
|
+
for (const s of sections) {
|
|
732
|
+
if (!s)
|
|
733
|
+
continue;
|
|
734
|
+
const isDate = looksLikeDate(s.body);
|
|
735
|
+
dateFlags.set(s, isDate);
|
|
736
|
+
if (isDate)
|
|
737
|
+
continue;
|
|
738
|
+
const fraction = planFraction(s.body);
|
|
739
|
+
if (fraction)
|
|
740
|
+
fractions.set(s, fraction);
|
|
741
|
+
else
|
|
742
|
+
plans.set(s, planNumeric(s.body));
|
|
743
|
+
}
|
|
744
|
+
const compiled = {
|
|
745
|
+
pattern,
|
|
746
|
+
format(value) {
|
|
747
|
+
if (pattern.trim() === '' || pattern.toUpperCase() === 'GENERAL') {
|
|
748
|
+
return { text: general(value) };
|
|
749
|
+
}
|
|
750
|
+
if (typeof value === 'string' && value !== '') {
|
|
751
|
+
// Excel's FOURTH section is the one that governs text. With one, `@`
|
|
752
|
+
// puts the text where it stands and a section without an `@` shows
|
|
753
|
+
// only its own literals, which is what makes `;;;` hide a cell and
|
|
754
|
+
// `;;;"n/a"` show n/a whatever was typed. With no fourth section the
|
|
755
|
+
// text is shown as it is, even under a numeric pattern like `0.00`.
|
|
756
|
+
const section = text ?? (positive?.body.includes('@') ? positive : undefined);
|
|
757
|
+
if (!section)
|
|
758
|
+
return { text: value };
|
|
759
|
+
return { text: renderText(section.body, section.body.includes('@') ? value : ''), color: section.color };
|
|
760
|
+
}
|
|
761
|
+
if (value === null || value === undefined || value === '')
|
|
762
|
+
return { text: '' };
|
|
763
|
+
if (typeof value === 'boolean')
|
|
764
|
+
return { text: value ? 'TRUE' : 'FALSE' };
|
|
765
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
766
|
+
if (!Number.isFinite(n)) {
|
|
767
|
+
const d = toDate(value);
|
|
768
|
+
if (d && positive && dateFlags.get(positive)) {
|
|
769
|
+
return { text: renderDate(positive.body, d), color: positive.color };
|
|
770
|
+
}
|
|
771
|
+
return { text: String(value) };
|
|
772
|
+
}
|
|
773
|
+
// Pick the section. Two sections means the second covers zero too.
|
|
774
|
+
// With conditions, the first section whose condition the value passes
|
|
775
|
+
// wins and an unconditioned one is the fallback, as in Excel's
|
|
776
|
+
// `[<=9999999]###-####;(###) ###-####`.
|
|
777
|
+
let section;
|
|
778
|
+
if (conditional) {
|
|
779
|
+
section = sections.find((sec) => sec && (sec.condition ? passes(sec.condition, n) : true));
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
section = positive;
|
|
783
|
+
if (n < 0 && negative)
|
|
784
|
+
section = negative;
|
|
785
|
+
else if (n === 0 && zero)
|
|
786
|
+
section = zero;
|
|
787
|
+
}
|
|
788
|
+
if (!section)
|
|
789
|
+
return { text: general(value) };
|
|
790
|
+
if (dateFlags.get(section)) {
|
|
791
|
+
const d = toDate(value);
|
|
792
|
+
// `n` is the serial the value stands for, which is what an elapsed
|
|
793
|
+
// token totals: 1.5 is a day and a half, [h]:mm reads 36:00.
|
|
794
|
+
return d
|
|
795
|
+
? { text: renderDate(section.body, d, n), color: section.color }
|
|
796
|
+
: { text: general(value) };
|
|
797
|
+
}
|
|
798
|
+
const asFraction = fractions.get(section);
|
|
799
|
+
if (asFraction) {
|
|
800
|
+
// A negative with its own section is rendered from its magnitude,
|
|
801
|
+
// as everywhere else: the section supplies the sign.
|
|
802
|
+
const usingNegative = n < 0 && section === negative && !conditional;
|
|
803
|
+
return { text: renderFraction(asFraction, usingNegative ? Math.abs(n) : n), color: section.color };
|
|
804
|
+
}
|
|
805
|
+
const plan = plans.get(section);
|
|
806
|
+
if (!hasPlaceholders(plan)) {
|
|
807
|
+
// `@` is the text placeholder, and a NUMBER in a cell formatted as
|
|
808
|
+
// Text is still shown by Excel as the number rather than as the
|
|
809
|
+
// placeholder: `@` over 5 reads 5, not @.
|
|
810
|
+
if (section.body.includes('@')) {
|
|
811
|
+
return { text: renderText(section.body, general(value)), color: section.color };
|
|
812
|
+
}
|
|
813
|
+
return { text: plan.prefix + plan.suffix, color: section.color };
|
|
814
|
+
}
|
|
815
|
+
// A negative shown by its own section renders its ABSOLUTE value: the
|
|
816
|
+
// section supplies the sign, usually as parentheses or a literal minus.
|
|
817
|
+
const usingNegativeSection = n < 0 && section === negative && !conditional;
|
|
818
|
+
const magnitude = usingNegativeSection ? Math.abs(n) : n;
|
|
819
|
+
let out = renderNumeric(plan, magnitude, marks);
|
|
820
|
+
// With no negative section, put the sign back on.
|
|
821
|
+
if (n < 0 && !usingNegativeSection && !out.startsWith('-'))
|
|
822
|
+
out = `-${out}`;
|
|
823
|
+
return { text: out, color: section.color };
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
cache.set(key, compiled);
|
|
827
|
+
return compiled;
|
|
828
|
+
}
|
|
829
|
+
/** Format one value against a pattern. Convenience over compile-then-call. */
|
|
830
|
+
export function formatWithPattern(value, pattern) {
|
|
831
|
+
return compileNumberFormat(pattern).format(value).text;
|
|
832
|
+
}
|
|
833
|
+
/** The presets behind Ctrl+Shift+1 through 6 and the ribbon's $ % , buttons.
|
|
834
|
+
* Percent is Excel's Percent Style, no decimals: 0.425 reads 43%. */
|
|
835
|
+
export const FORMAT_PRESETS = {
|
|
836
|
+
general: 'General',
|
|
837
|
+
number: '#,##0.00',
|
|
838
|
+
time: 'h:mm AM/PM',
|
|
839
|
+
date: 'yyyy-mm-dd',
|
|
840
|
+
currency: '$#,##0.00;($#,##0.00)',
|
|
841
|
+
accounting: '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)',
|
|
842
|
+
percent: '0%',
|
|
843
|
+
scientific: '0.00E+00',
|
|
844
|
+
};
|
|
845
|
+
/** What the ribbon's Number Format combo applies for a category. Excel's
|
|
846
|
+
* "Percentage" there carries two decimals where its % button carries none;
|
|
847
|
+
* the other entries are the presets. */
|
|
848
|
+
export const FORMAT_CATEGORY_PATTERNS = {
|
|
849
|
+
...FORMAT_PRESETS,
|
|
850
|
+
percent: '0.00%',
|
|
851
|
+
};
|
|
852
|
+
/** Excel's Special category: fixed-shape numbers laid into a mask. */
|
|
853
|
+
export const SPECIAL_FORMATS = {
|
|
854
|
+
zip: { label: 'Zip Code', pattern: '00000' },
|
|
855
|
+
zip4: { label: 'Zip Code + 4', pattern: '00000-0000' },
|
|
856
|
+
phone: { label: 'Phone Number', pattern: '[<=9999999]###-####;(###) ###-####' },
|
|
857
|
+
ssn: { label: 'Social Security Number', pattern: '000-00-0000' },
|
|
858
|
+
};
|
|
859
|
+
/** The pattern Excel's Accounting category spells for a symbol and a
|
|
860
|
+
* number of decimals; an empty symbol is its "None". */
|
|
861
|
+
export function accountingPattern(symbol, decimals) {
|
|
862
|
+
const digits = `#,##0${decimals > 0 ? '.' + '0'.repeat(decimals) : ''}`;
|
|
863
|
+
const sym = symbol === '$' ? '$* ' : symbol ? `${JSON.stringify(symbol)}* ` : '* ';
|
|
864
|
+
return `_(${sym}${digits}_);_(${sym}(${digits});_(${sym}"-"${'?'.repeat(decimals)}_);_(@_)`;
|
|
865
|
+
}
|
|
866
|
+
/** The symbol and decimals an accounting pattern was built from, or null
|
|
867
|
+
* when the pattern is not one `accountingPattern` would spell. */
|
|
868
|
+
export function accountingParts(fmt) {
|
|
869
|
+
if (!fmt)
|
|
870
|
+
return null;
|
|
871
|
+
const m = /^_\((?:"([^"]*)"|\$)?\* #,##0(\.0+)?_\);/.exec(fmt);
|
|
872
|
+
if (!m)
|
|
873
|
+
return null;
|
|
874
|
+
const symbol = m[1] !== undefined ? m[1] : fmt.startsWith('_($') ? '$' : '';
|
|
875
|
+
return { symbol, decimals: m[2] ? m[2].length - 1 : 0 };
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Which of Excel's categories a pattern belongs to, with the decimals and
|
|
879
|
+
* separator it carries, so the ribbon's combo can say "Percentage" for a
|
|
880
|
+
* cell holding `0%` (a typed 12%) as well as for the combo's `0.00%`, and
|
|
881
|
+
* Format Cells can open on the right category. A pattern that is none of
|
|
882
|
+
* them is 'custom'.
|
|
883
|
+
*/
|
|
884
|
+
export function formatCategory(fmt) {
|
|
885
|
+
if (!fmt || fmt === 'General')
|
|
886
|
+
return { category: 'general', decimals: 2, thousands: true };
|
|
887
|
+
const decimals = fmt.match(/\.(0+)/)?.[1]?.length ?? 0;
|
|
888
|
+
if (accountingParts(fmt))
|
|
889
|
+
return { category: 'accounting', decimals, thousands: true };
|
|
890
|
+
if (Object.values(SPECIAL_FORMATS).some((sp) => sp.pattern === fmt))
|
|
891
|
+
return { category: 'special', decimals: 0, thousands: false };
|
|
892
|
+
if (/E\+/i.test(fmt))
|
|
893
|
+
return { category: 'scientific', decimals, thousands: false };
|
|
894
|
+
if (/%$/.test(fmt))
|
|
895
|
+
return { category: 'percent', decimals, thousands: false };
|
|
896
|
+
if (/^\$/.test(fmt))
|
|
897
|
+
return { category: 'currency', decimals, thousands: true };
|
|
898
|
+
if (/[hs]:|AM\/PM/i.test(fmt))
|
|
899
|
+
return { category: 'time', decimals: 0, thousands: false };
|
|
900
|
+
if (/y|d/i.test(fmt) && !/[#0]/.test(fmt))
|
|
901
|
+
return { category: 'date', decimals: 0, thousands: false };
|
|
902
|
+
if (/^#?,?#*0(\.0+)?$/.test(fmt))
|
|
903
|
+
return { category: 'number', decimals, thousands: fmt.includes(',') };
|
|
904
|
+
return { category: 'custom', decimals, thousands: false };
|
|
905
|
+
}
|