@svgrid/enterprise 3.0.2 → 3.1.0
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-BSjv6Qec.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
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* `x:num`, and Sheets' `data-sheets-*` attributes all come through, so a
|
|
21
21
|
* block copied from either arrives with its values, formulas and formats.
|
|
22
22
|
*/
|
|
23
|
-
import { translateFormula } from './refs.js';
|
|
23
|
+
import { translateFormula, transposeFormula } from './refs.js';
|
|
24
24
|
import { parseFormula } from './parse.js';
|
|
25
25
|
import { visit } from './ast.js';
|
|
26
26
|
import { entryToStyle } from './format-store.js';
|
|
@@ -466,7 +466,10 @@ function arithmetic(existing, incoming, op) {
|
|
|
466
466
|
* them so) needs to become A1; it then needs no offset, being relative to
|
|
467
467
|
* wherever it lands.
|
|
468
468
|
*/
|
|
469
|
-
export function resolvePasteCell(source, existing, opts, offset = { rows: 0, cols: 0 }, at
|
|
469
|
+
export function resolvePasteCell(source, existing, opts, offset = { rows: 0, cols: 0 }, at,
|
|
470
|
+
/** Where the cell came from and lands, for a transposed paste: its
|
|
471
|
+
* relative references are turned rather than shifted. */
|
|
472
|
+
turn) {
|
|
470
473
|
if (!source)
|
|
471
474
|
return { kind: 'skip' };
|
|
472
475
|
const what = opts.what ?? 'all';
|
|
@@ -484,7 +487,9 @@ export function resolvePasteCell(source, existing, opts, offset = { rows: 0, col
|
|
|
484
487
|
const value = wantsFormula && source.formula
|
|
485
488
|
? at && isR1C1(source.formula)
|
|
486
489
|
? r1c1ToA1(source.formula, at.row, at.col)
|
|
487
|
-
:
|
|
490
|
+
: turn
|
|
491
|
+
? String(transposeFormula(source.formula, turn.source, turn.dest))
|
|
492
|
+
: String(translateFormula(source.formula, offset.rows, offset.cols))
|
|
488
493
|
: source.text;
|
|
489
494
|
if (what === 'values' || what === 'formulas')
|
|
490
495
|
return { kind: 'value', value };
|
|
@@ -573,7 +578,8 @@ export function planPaste(grid, destination, opts = {}, origin, fill) {
|
|
|
573
578
|
for (let tr = 0; tr < repeats.rows; tr += 1) {
|
|
574
579
|
for (let tc = 0; tc < repeats.cols; tc += 1) {
|
|
575
580
|
const corner = { row: destination.row + tr * height, col: destination.col + tc * width };
|
|
576
|
-
// A transposed paste
|
|
581
|
+
// A transposed paste turns each formula's references instead of
|
|
582
|
+
// shifting them (see `transposeFormula`); the offset is for the rest.
|
|
577
583
|
const offset = origin && !opts.transpose
|
|
578
584
|
? { rows: corner.row - origin.row, cols: corner.col - origin.col }
|
|
579
585
|
: { rows: 0, cols: 0 };
|
|
@@ -583,7 +589,10 @@ export function planPaste(grid, destination, opts = {}, origin, fill) {
|
|
|
583
589
|
const source = row[c];
|
|
584
590
|
if (!source)
|
|
585
591
|
continue;
|
|
586
|
-
|
|
592
|
+
const dest = { row: corner.row + r, col: corner.col + c };
|
|
593
|
+
// In the turned grid, (r, c) came from (c, r) of the block.
|
|
594
|
+
const turn = opts.transpose && origin ? { source: { row: origin.row + c, col: origin.col + r }, dest } : undefined;
|
|
595
|
+
out.push({ ...dest, source, offset, ...(turn ? { turn } : {}) });
|
|
587
596
|
}
|
|
588
597
|
}
|
|
589
598
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A PivotTable over a range of cells: Excel's Insert > PivotTable, on the
|
|
3
|
+
* pivot engine the grid already has (`pivot.ts`).
|
|
4
|
+
*
|
|
5
|
+
* The definition is what the sheet keeps: the source range (its first row
|
|
6
|
+
* is the field names), where the result is written, and the row fields,
|
|
7
|
+
* column fields and measures. The RESULT is plain cells, written into the
|
|
8
|
+
* sheet like any other block, so every other part of the shell works on it
|
|
9
|
+
* without knowing it came from a pivot: it can be formatted, filtered,
|
|
10
|
+
* charted, printed and saved to an .xlsx. Refresh rebuilds the block from
|
|
11
|
+
* the source, which is what a pivot over live cells owes the user.
|
|
12
|
+
*
|
|
13
|
+
* Everything here is pure: it reads cells through the callbacks it is
|
|
14
|
+
* given, and returns the block of text to write.
|
|
15
|
+
*/
|
|
16
|
+
import type { Rect } from './format-store.js';
|
|
17
|
+
import type { StructuralEdit } from './refs.js';
|
|
18
|
+
import type { CellValue } from './ast.js';
|
|
19
|
+
/** The aggregations Excel's Summarize Values By offers, as this sheet has them. */
|
|
20
|
+
export declare const SHEET_PIVOT_AGGS: readonly ["sum", "avg", "count", "countDistinct", "min", "max"];
|
|
21
|
+
export type SheetPivotAgg = (typeof SHEET_PIVOT_AGGS)[number];
|
|
22
|
+
export type SheetPivotValue = {
|
|
23
|
+
/** A field name: one of the source's header cells. */
|
|
24
|
+
field: string;
|
|
25
|
+
agg: SheetPivotAgg;
|
|
26
|
+
label?: string;
|
|
27
|
+
};
|
|
28
|
+
export type SheetPivot = {
|
|
29
|
+
id: string;
|
|
30
|
+
/** The source block, its first row the field names. */
|
|
31
|
+
source: Rect;
|
|
32
|
+
/** The top-left cell of the written block, on the same sheet. */
|
|
33
|
+
target: {
|
|
34
|
+
row: number;
|
|
35
|
+
col: number;
|
|
36
|
+
};
|
|
37
|
+
/** Row fields, outermost first. */
|
|
38
|
+
rows: string[];
|
|
39
|
+
/** Column fields, outermost first. */
|
|
40
|
+
cols: string[];
|
|
41
|
+
values: SheetPivotValue[];
|
|
42
|
+
/**
|
|
43
|
+
* Excel's Filters area: a field, and the one value the whole pivot is
|
|
44
|
+
* narrowed to. An empty value is "(All)", which narrows nothing.
|
|
45
|
+
*
|
|
46
|
+
* It is written above the block, a line per filter, so the reader can
|
|
47
|
+
* see what the numbers are of; typing another value there and refreshing
|
|
48
|
+
* is the fast way to change it.
|
|
49
|
+
*/
|
|
50
|
+
filters?: SheetPivotFilter[];
|
|
51
|
+
grandTotalRow?: boolean;
|
|
52
|
+
grandTotalCol?: boolean;
|
|
53
|
+
rowSubtotals?: boolean;
|
|
54
|
+
/** The block the last refresh wrote, so the next one can clear it. */
|
|
55
|
+
written?: Rect;
|
|
56
|
+
};
|
|
57
|
+
export type SheetPivotFilter = {
|
|
58
|
+
field: string;
|
|
59
|
+
/** The value to keep, as text. Empty means every value. */
|
|
60
|
+
value: string;
|
|
61
|
+
};
|
|
62
|
+
/** An id no other pivot of this session carries. */
|
|
63
|
+
export declare const pivotId: () => string;
|
|
64
|
+
/** A copy safe to hand out or save. */
|
|
65
|
+
export declare function copyPivot(pivot: SheetPivot): SheetPivot;
|
|
66
|
+
/**
|
|
67
|
+
* The pivot after an insert or delete: the source moves with its cells and
|
|
68
|
+
* the target with its own. Null when the source is gone, or the cell the
|
|
69
|
+
* result is written at is.
|
|
70
|
+
*/
|
|
71
|
+
export declare function shiftPivot(pivot: SheetPivot, edit: StructuralEdit): SheetPivot | null;
|
|
72
|
+
/** The pivots of a sheet after an insert or delete, the gone ones dropped. */
|
|
73
|
+
export declare function shiftPivots(pivots: ReadonlyArray<SheetPivot>, edit: StructuralEdit): SheetPivot[];
|
|
74
|
+
/**
|
|
75
|
+
* The field names of a source range: its first row, with a blank header
|
|
76
|
+
* named after its column the way Excel names an unnamed one.
|
|
77
|
+
*/
|
|
78
|
+
export declare function pivotFields(source: Rect, textAt: (row: number, col: number) => string): string[];
|
|
79
|
+
/** The source's body as records keyed by field name, ready for the engine. */
|
|
80
|
+
export declare function pivotRecords(source: Rect, valueAt: (row: number, col: number) => CellValue, textAt: (row: number, col: number) => string): Array<Record<string, unknown>>;
|
|
81
|
+
/**
|
|
82
|
+
* The column tree flattened for a sheet: one header row per level of the
|
|
83
|
+
* tree, and the leaf ids in the order they are written. A header sits over
|
|
84
|
+
* the leaves it covers, and its own cell is the leftmost of them, the way
|
|
85
|
+
* Excel writes a merged group header without the merge.
|
|
86
|
+
*/
|
|
87
|
+
export declare function flattenPivotColumns(columns: ReadonlyArray<unknown>): {
|
|
88
|
+
headerRows: string[][];
|
|
89
|
+
leaves: Array<{
|
|
90
|
+
id: string;
|
|
91
|
+
header: string;
|
|
92
|
+
}>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The records a pivot's filters leave.
|
|
96
|
+
*
|
|
97
|
+
* Excel's report filter narrows the whole pivot, not one part of it, so it
|
|
98
|
+
* applies before anything is grouped: the totals, the subtotals and the
|
|
99
|
+
* drill-down all see the same rows, which is the only way the numbers on
|
|
100
|
+
* the page add up.
|
|
101
|
+
*/
|
|
102
|
+
export declare function filteredRecords(pivot: SheetPivot, records: ReadonlyArray<Record<string, unknown>>): Array<Record<string, unknown>>;
|
|
103
|
+
/** The values a field carries, each once, in the order they appear. */
|
|
104
|
+
export declare function pivotFieldValues(field: string, source: Rect, valueAt: (row: number, col: number) => CellValue, textAt: (row: number, col: number) => string): string[];
|
|
105
|
+
/**
|
|
106
|
+
* The block of text a pivot writes: the column headers, then one line per
|
|
107
|
+
* pivot row, the row label indented by its depth the way Excel indents a
|
|
108
|
+
* group under its parent.
|
|
109
|
+
*/
|
|
110
|
+
export declare function pivotBlock(pivot: SheetPivot, valueAt: (row: number, col: number) => CellValue, textAt: (row: number, col: number) => string): string[][];
|
|
111
|
+
/**
|
|
112
|
+
* The block, and what each of its lines and columns stands for.
|
|
113
|
+
*
|
|
114
|
+
* `pivotBlock` writes the cells; drilling into one needs to know which
|
|
115
|
+
* field values pin it, which is the same walk. Both come from here so they
|
|
116
|
+
* cannot drift: a cell drawn from one layout and read from another would
|
|
117
|
+
* open the wrong rows.
|
|
118
|
+
*/
|
|
119
|
+
export type PivotLayout = {
|
|
120
|
+
cells: string[][];
|
|
121
|
+
/** How many lines of `cells` are column headers. */
|
|
122
|
+
headerCount: number;
|
|
123
|
+
/**
|
|
124
|
+
* The row field values that pin each line, outermost first, aligned to
|
|
125
|
+
* `cells`. Null for a header line; shorter than the pivot's row fields
|
|
126
|
+
* for a group, a subtotal or the grand total, which pin fewer of them.
|
|
127
|
+
*/
|
|
128
|
+
rowPaths: Array<string[] | null>;
|
|
129
|
+
/** The same for each column of `cells`. Null for the label column. */
|
|
130
|
+
colPaths: Array<string[] | null>;
|
|
131
|
+
};
|
|
132
|
+
export declare function pivotLayout(pivot: SheetPivot, valueAt: (row: number, col: number) => CellValue, textAt: (row: number, col: number) => string): PivotLayout;
|
|
133
|
+
/** What a filter with no value chosen shows, and what clears one. */
|
|
134
|
+
export declare const ALL_VALUES = "(All)";
|
|
135
|
+
/** The source rows behind one cell of a written pivot. */
|
|
136
|
+
export type PivotDrill = {
|
|
137
|
+
/** The row and column field values that pin the cell, outermost first. */
|
|
138
|
+
rowPath: string[];
|
|
139
|
+
colPath: string[];
|
|
140
|
+
/** The records behind it, in the source's own order. */
|
|
141
|
+
records: Array<Record<string, unknown>>;
|
|
142
|
+
/** The source's fields, in its own order. */
|
|
143
|
+
fields: string[];
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Excel's "show details": the rows behind the number in a pivot cell.
|
|
147
|
+
*
|
|
148
|
+
* `row` and `col` are a cell on the sheet, not a place in the block. Null
|
|
149
|
+
* when the cell is outside the pivot, or is a header, or is the row label
|
|
150
|
+
* column, none of which stand for a number.
|
|
151
|
+
*/
|
|
152
|
+
export declare function pivotDrill(pivot: SheetPivot, row: number, col: number, valueAt: (row: number, col: number) => CellValue, textAt: (row: number, col: number) => string): PivotDrill | null;
|
|
153
|
+
/** The rectangle a block written at the target covers. */
|
|
154
|
+
export declare function pivotWrittenRect(pivot: SheetPivot, block: ReadonlyArray<ReadonlyArray<string>>): Rect;
|
|
155
|
+
/**
|
|
156
|
+
* Where Excel's Create PivotTable dialog opens: the source is the selected
|
|
157
|
+
* block, and the result lands two columns past it, which keeps it clear of
|
|
158
|
+
* the data it reads.
|
|
159
|
+
*/
|
|
160
|
+
export declare function pivotFromRange(source: Rect, fields: ReadonlyArray<string>): SheetPivot;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { shiftRect, lineShift } from './rects.js';
|
|
2
|
+
import { colToLetters } from './address.js';
|
|
3
|
+
import { createPivotModel } from '../pivot.js';
|
|
4
|
+
/** The aggregations Excel's Summarize Values By offers, as this sheet has them. */
|
|
5
|
+
export const SHEET_PIVOT_AGGS = ['sum', 'avg', 'count', 'countDistinct', 'min', 'max'];
|
|
6
|
+
let nextPivotId = 1;
|
|
7
|
+
/** An id no other pivot of this session carries. */
|
|
8
|
+
export const pivotId = () => `pivot-${Date.now().toString(36)}-${(nextPivotId += 1).toString(36)}`;
|
|
9
|
+
/** A copy safe to hand out or save. */
|
|
10
|
+
export function copyPivot(pivot) {
|
|
11
|
+
return {
|
|
12
|
+
...pivot,
|
|
13
|
+
source: [...pivot.source],
|
|
14
|
+
target: { ...pivot.target },
|
|
15
|
+
rows: [...pivot.rows],
|
|
16
|
+
cols: [...pivot.cols],
|
|
17
|
+
values: pivot.values.map((v) => ({ ...v })),
|
|
18
|
+
...(pivot.filters?.length ? { filters: pivot.filters.map((f) => ({ ...f })) } : {}),
|
|
19
|
+
...(pivot.written ? { written: [...pivot.written] } : {}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The pivot after an insert or delete: the source moves with its cells and
|
|
24
|
+
* the target with its own. Null when the source is gone, or the cell the
|
|
25
|
+
* result is written at is.
|
|
26
|
+
*/
|
|
27
|
+
export function shiftPivot(pivot, edit) {
|
|
28
|
+
const source = shiftRect(pivot.source, edit);
|
|
29
|
+
if (!source)
|
|
30
|
+
return null;
|
|
31
|
+
const shift = lineShift(edit);
|
|
32
|
+
const rows = edit.kind === 'insertRows' || edit.kind === 'deleteRows';
|
|
33
|
+
const at = rows ? shift(pivot.target.row) : shift(pivot.target.col);
|
|
34
|
+
if (at === null)
|
|
35
|
+
return null;
|
|
36
|
+
const out = copyPivot(pivot);
|
|
37
|
+
out.source = source;
|
|
38
|
+
out.target = rows ? { ...out.target, row: at } : { ...out.target, col: at };
|
|
39
|
+
if (out.written) {
|
|
40
|
+
const written = shiftRect(out.written, edit);
|
|
41
|
+
if (written)
|
|
42
|
+
out.written = written;
|
|
43
|
+
else
|
|
44
|
+
delete out.written;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/** The pivots of a sheet after an insert or delete, the gone ones dropped. */
|
|
49
|
+
export function shiftPivots(pivots, edit) {
|
|
50
|
+
return pivots.map((p) => shiftPivot(p, edit)).filter((p) => p !== null);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The field names of a source range: its first row, with a blank header
|
|
54
|
+
* named after its column the way Excel names an unnamed one.
|
|
55
|
+
*/
|
|
56
|
+
export function pivotFields(source, textAt) {
|
|
57
|
+
const [r1, c1, , c2] = source;
|
|
58
|
+
const out = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (let c = c1; c <= c2; c += 1) {
|
|
61
|
+
let name = textAt(r1, c).trim() || `Column ${colToLetters(c)}`;
|
|
62
|
+
// Two columns with one name would collapse into one field.
|
|
63
|
+
while (seen.has(name))
|
|
64
|
+
name = `${name} (${colToLetters(c)})`;
|
|
65
|
+
seen.add(name);
|
|
66
|
+
out.push(name);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/** The source's body as records keyed by field name, ready for the engine. */
|
|
71
|
+
export function pivotRecords(source, valueAt, textAt) {
|
|
72
|
+
const [r1, c1, r2, c2] = source;
|
|
73
|
+
const fields = pivotFields(source, textAt);
|
|
74
|
+
const out = [];
|
|
75
|
+
for (let r = r1 + 1; r <= r2; r += 1) {
|
|
76
|
+
const record = {};
|
|
77
|
+
let any = false;
|
|
78
|
+
for (let c = c1; c <= c2; c += 1) {
|
|
79
|
+
const value = valueAt(r, c);
|
|
80
|
+
record[fields[c - c1]] = value === '' ? null : value;
|
|
81
|
+
if (value !== '')
|
|
82
|
+
any = true;
|
|
83
|
+
}
|
|
84
|
+
// A wholly empty row is a gap in the block, not a record.
|
|
85
|
+
if (any)
|
|
86
|
+
out.push(record);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The column tree flattened for a sheet: one header row per level of the
|
|
92
|
+
* tree, and the leaf ids in the order they are written. A header sits over
|
|
93
|
+
* the leaves it covers, and its own cell is the leftmost of them, the way
|
|
94
|
+
* Excel writes a merged group header without the merge.
|
|
95
|
+
*/
|
|
96
|
+
export function flattenPivotColumns(columns) {
|
|
97
|
+
const nodes = columns;
|
|
98
|
+
const depthOf = (list) => list.reduce((deep, node) => Math.max(deep, node.columns?.length ? 1 + depthOf(node.columns) : 1), 0);
|
|
99
|
+
const depth = depthOf(nodes);
|
|
100
|
+
const headerRows = Array.from({ length: depth }, () => []);
|
|
101
|
+
const leaves = [];
|
|
102
|
+
const walk = (list, level) => {
|
|
103
|
+
for (const node of list) {
|
|
104
|
+
const label = node.header === undefined || node.header === null ? '' : String(node.header);
|
|
105
|
+
if (node.columns?.length) {
|
|
106
|
+
const start = leaves.length;
|
|
107
|
+
walk(node.columns, level + 1);
|
|
108
|
+
for (let i = start; i < leaves.length; i += 1)
|
|
109
|
+
headerRows[level][i] = i === start ? label : '';
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
leaves.push({ id: String(node.id ?? ''), header: label });
|
|
113
|
+
const at = leaves.length - 1;
|
|
114
|
+
for (let l = level; l < depth - 1; l += 1)
|
|
115
|
+
headerRows[l][at] = '';
|
|
116
|
+
headerRows[depth - 1][at] = label;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
walk(nodes, 0);
|
|
121
|
+
for (const row of headerRows) {
|
|
122
|
+
for (let i = 0; i < leaves.length; i += 1)
|
|
123
|
+
row[i] = row[i] ?? '';
|
|
124
|
+
}
|
|
125
|
+
return { headerRows, leaves };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The records a pivot's filters leave.
|
|
129
|
+
*
|
|
130
|
+
* Excel's report filter narrows the whole pivot, not one part of it, so it
|
|
131
|
+
* applies before anything is grouped: the totals, the subtotals and the
|
|
132
|
+
* drill-down all see the same rows, which is the only way the numbers on
|
|
133
|
+
* the page add up.
|
|
134
|
+
*/
|
|
135
|
+
export function filteredRecords(pivot, records) {
|
|
136
|
+
const active = (pivot.filters ?? []).filter((f) => f.field && f.value !== '');
|
|
137
|
+
if (!active.length)
|
|
138
|
+
return [...records];
|
|
139
|
+
return records.filter((record) => active.every((f) => String(record[f.field] ?? '') === f.value));
|
|
140
|
+
}
|
|
141
|
+
/** The values a field carries, each once, in the order they appear. */
|
|
142
|
+
export function pivotFieldValues(field, source, valueAt, textAt) {
|
|
143
|
+
const seen = [];
|
|
144
|
+
for (const record of pivotRecords(source, valueAt, textAt)) {
|
|
145
|
+
const value = String(record[field] ?? '');
|
|
146
|
+
if (value !== '' && !seen.includes(value))
|
|
147
|
+
seen.push(value);
|
|
148
|
+
}
|
|
149
|
+
return seen;
|
|
150
|
+
}
|
|
151
|
+
/** A number without the float noise a sum of decimals leaves behind. */
|
|
152
|
+
function cellText(value) {
|
|
153
|
+
if (value === null || value === undefined || value === '')
|
|
154
|
+
return '';
|
|
155
|
+
if (typeof value === 'number') {
|
|
156
|
+
if (!Number.isFinite(value))
|
|
157
|
+
return '';
|
|
158
|
+
return String(Number(value.toPrecision(12)));
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === 'boolean')
|
|
161
|
+
return value ? 'TRUE' : 'FALSE';
|
|
162
|
+
return String(value);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The block of text a pivot writes: the column headers, then one line per
|
|
166
|
+
* pivot row, the row label indented by its depth the way Excel indents a
|
|
167
|
+
* group under its parent.
|
|
168
|
+
*/
|
|
169
|
+
export function pivotBlock(pivot, valueAt, textAt) {
|
|
170
|
+
return pivotLayout(pivot, valueAt, textAt).cells;
|
|
171
|
+
}
|
|
172
|
+
export function pivotLayout(pivot, valueAt, textAt) {
|
|
173
|
+
const empty = { cells: [], headerCount: 0, rowPaths: [], colPaths: [] };
|
|
174
|
+
if (!pivot.values.length)
|
|
175
|
+
return empty;
|
|
176
|
+
const records = filteredRecords(pivot, pivotRecords(pivot.source, valueAt, textAt));
|
|
177
|
+
const result = createPivotModel(records, {
|
|
178
|
+
rows: pivot.rows,
|
|
179
|
+
cols: pivot.cols,
|
|
180
|
+
values: pivot.values.map((v) => ({
|
|
181
|
+
field: v.field,
|
|
182
|
+
agg: v.agg,
|
|
183
|
+
...(v.label ? { label: v.label } : {}),
|
|
184
|
+
})),
|
|
185
|
+
grandTotalRow: pivot.grandTotalRow !== false,
|
|
186
|
+
// With no column field the grand total column would repeat the only
|
|
187
|
+
// measure column, which is not what Excel shows.
|
|
188
|
+
grandTotalCol: pivot.cols.length ? pivot.grandTotalCol !== false : false,
|
|
189
|
+
rowSubtotals: pivot.rowSubtotals !== false,
|
|
190
|
+
});
|
|
191
|
+
const { headerRows, leaves } = flattenPivotColumns(result.columns);
|
|
192
|
+
// The engine's first column is the row-axis label; it is the block's own
|
|
193
|
+
// first column here rather than one of the leaves.
|
|
194
|
+
const label = leaves[0]?.id === '__pivotRowHeader' ? leaves.shift() : undefined;
|
|
195
|
+
const headerLabel = label?.header ?? '';
|
|
196
|
+
const dropped = label ? 1 : 0;
|
|
197
|
+
const out = headerRows.map((row, i) => [
|
|
198
|
+
i === headerRows.length - 1 ? headerLabel : '',
|
|
199
|
+
...row.slice(dropped),
|
|
200
|
+
]);
|
|
201
|
+
// With one measure, the bottom header row is that measure's name under
|
|
202
|
+
// every column, which says nothing the dialog has not. Drop it and let
|
|
203
|
+
// the column values carry the corner label, as Excel's compact form does.
|
|
204
|
+
if (pivot.values.length === 1 && out.length > 1) {
|
|
205
|
+
const measures = out.pop();
|
|
206
|
+
out[out.length - 1][0] = measures[0] ?? '';
|
|
207
|
+
}
|
|
208
|
+
// Excel writes the Filters area above the block, one line per filter and
|
|
209
|
+
// a blank line under them, so the reader sees what the numbers are of
|
|
210
|
+
// before reading a number.
|
|
211
|
+
const filters = (pivot.filters ?? []).filter((f) => f.field);
|
|
212
|
+
if (filters.length) {
|
|
213
|
+
const width = out[0]?.length ?? 2;
|
|
214
|
+
const pad = (line) => {
|
|
215
|
+
while (line.length < Math.max(width, 2))
|
|
216
|
+
line.push('');
|
|
217
|
+
return line;
|
|
218
|
+
};
|
|
219
|
+
out.unshift(...filters.map((f) => pad([f.field, f.value === '' ? ALL_VALUES : f.value])), pad([]));
|
|
220
|
+
}
|
|
221
|
+
const headerCount = out.length;
|
|
222
|
+
const rowPaths = Array.from({ length: headerCount }, () => null);
|
|
223
|
+
// The row axis is a walk down a tree, and the model hands its rows out in
|
|
224
|
+
// that order: a group before what is under it. Keeping the labels seen at
|
|
225
|
+
// each depth is enough to know the path of the line being written, and
|
|
226
|
+
// the label IS the value as the model grouped it, since it groups on the
|
|
227
|
+
// text too.
|
|
228
|
+
const stack = [];
|
|
229
|
+
for (const row of result.rows) {
|
|
230
|
+
const indent = ' '.repeat(Math.max(0, row.__pivotDepth - 1));
|
|
231
|
+
out.push([`${indent}${row.__pivotLabel}`, ...leaves.map((leaf) => cellText(row[leaf.id]))]);
|
|
232
|
+
const depth = Math.max(0, row.__pivotDepth);
|
|
233
|
+
if (row.__pivotKind === 'grandTotal') {
|
|
234
|
+
rowPaths.push([]);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
stack.length = depth;
|
|
238
|
+
if (depth > 0)
|
|
239
|
+
stack[depth - 1] = row.__pivotLabel;
|
|
240
|
+
rowPaths.push([...stack]);
|
|
241
|
+
}
|
|
242
|
+
// A column's path is the headers above it, with a group's label repeated
|
|
243
|
+
// under it: `flattenPivotColumns` writes a group once, over its leftmost
|
|
244
|
+
// leaf, the way Excel writes one.
|
|
245
|
+
const levels = Math.min(pivot.cols.length, headerRows.length);
|
|
246
|
+
const colPaths = [null];
|
|
247
|
+
leaves.forEach((leaf, i) => {
|
|
248
|
+
// The grand total column reads every record, and a sheet with no column
|
|
249
|
+
// field has one column per measure, which is the same.
|
|
250
|
+
if (leaf.id.includes('__total__m') || leaf.id.startsWith('pv__all__m')) {
|
|
251
|
+
colPaths.push([]);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const path = [];
|
|
255
|
+
for (let level = 0; level < levels; level += 1) {
|
|
256
|
+
let value = '';
|
|
257
|
+
for (let j = 0; j <= i + dropped; j += 1) {
|
|
258
|
+
const cell = headerRows[level]?.[j];
|
|
259
|
+
if (cell)
|
|
260
|
+
value = cell;
|
|
261
|
+
}
|
|
262
|
+
path.push(value);
|
|
263
|
+
}
|
|
264
|
+
colPaths.push(path);
|
|
265
|
+
});
|
|
266
|
+
return { cells: out, headerCount, rowPaths, colPaths };
|
|
267
|
+
}
|
|
268
|
+
/** What a filter with no value chosen shows, and what clears one. */
|
|
269
|
+
export const ALL_VALUES = '(All)';
|
|
270
|
+
/**
|
|
271
|
+
* Excel's "show details": the rows behind the number in a pivot cell.
|
|
272
|
+
*
|
|
273
|
+
* `row` and `col` are a cell on the sheet, not a place in the block. Null
|
|
274
|
+
* when the cell is outside the pivot, or is a header, or is the row label
|
|
275
|
+
* column, none of which stand for a number.
|
|
276
|
+
*/
|
|
277
|
+
export function pivotDrill(pivot, row, col, valueAt, textAt) {
|
|
278
|
+
const layout = pivotLayout(pivot, valueAt, textAt);
|
|
279
|
+
if (!layout.cells.length)
|
|
280
|
+
return null;
|
|
281
|
+
const line = row - pivot.target.row;
|
|
282
|
+
const column = col - pivot.target.col;
|
|
283
|
+
if (line < 0 || line >= layout.cells.length)
|
|
284
|
+
return null;
|
|
285
|
+
if (column < 0 || column >= (layout.cells[line]?.length ?? 0))
|
|
286
|
+
return null;
|
|
287
|
+
const rowPath = layout.rowPaths[line];
|
|
288
|
+
const colPath = layout.colPaths[column];
|
|
289
|
+
if (!rowPath || !colPath)
|
|
290
|
+
return null;
|
|
291
|
+
const records = filteredRecords(pivot, pivotRecords(pivot.source, valueAt, textAt));
|
|
292
|
+
const matches = records.filter((record) => {
|
|
293
|
+
for (let i = 0; i < rowPath.length; i += 1) {
|
|
294
|
+
const field = pivot.rows[i];
|
|
295
|
+
if (field === undefined)
|
|
296
|
+
return false;
|
|
297
|
+
if (String(record[field] ?? '') !== rowPath[i])
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
for (let i = 0; i < colPath.length; i += 1) {
|
|
301
|
+
const field = pivot.cols[i];
|
|
302
|
+
if (field === undefined)
|
|
303
|
+
return false;
|
|
304
|
+
if (String(record[field] ?? '') !== colPath[i])
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
308
|
+
});
|
|
309
|
+
return { rowPath: [...rowPath], colPath: [...colPath], records: matches, fields: pivotFields(pivot.source, textAt) };
|
|
310
|
+
}
|
|
311
|
+
/** The rectangle a block written at the target covers. */
|
|
312
|
+
export function pivotWrittenRect(pivot, block) {
|
|
313
|
+
const height = Math.max(1, block.length);
|
|
314
|
+
const width = Math.max(1, block.reduce((w, row) => Math.max(w, row.length), 0));
|
|
315
|
+
const { row, col } = pivot.target;
|
|
316
|
+
return [row, col, row + height - 1, col + width - 1];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Where Excel's Create PivotTable dialog opens: the source is the selected
|
|
320
|
+
* block, and the result lands two columns past it, which keeps it clear of
|
|
321
|
+
* the data it reads.
|
|
322
|
+
*/
|
|
323
|
+
export function pivotFromRange(source, fields) {
|
|
324
|
+
const [r1, , , c2] = source;
|
|
325
|
+
return {
|
|
326
|
+
id: pivotId(),
|
|
327
|
+
source: [...source],
|
|
328
|
+
target: { row: r1, col: c2 + 2 },
|
|
329
|
+
rows: fields.length ? [fields[0]] : [],
|
|
330
|
+
cols: [],
|
|
331
|
+
values: fields.length > 1 ? [{ field: fields[fields.length - 1], agg: 'sum' }] : [],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presence: where the other people are.
|
|
3
|
+
*
|
|
4
|
+
* The delta stream carries what everyone TYPES; this carries where everyone
|
|
5
|
+
* IS, which is the other half of two people on one sheet. Seeing a
|
|
6
|
+
* colleague's box move is what stops two people typing into the same cell,
|
|
7
|
+
* and it is what makes a shared sheet feel shared rather than merely
|
|
8
|
+
* synchronised.
|
|
9
|
+
*
|
|
10
|
+
* Presence is not part of the document. It is never saved, never in
|
|
11
|
+
* `getState()` and never in the .xlsx: a cursor belongs to a session, and a
|
|
12
|
+
* file that remembered where someone's cursor was a week ago would be
|
|
13
|
+
* remembering nothing worth keeping. It is passed to the shell as a prop and
|
|
14
|
+
* drawn over the cells.
|
|
15
|
+
*/
|
|
16
|
+
import type { Rect } from './rects.js';
|
|
17
|
+
export type SheetPresence = {
|
|
18
|
+
/** Whoever this is, stable for as long as they are here. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** What to put on the tag. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Their colour, which the box and the tag are drawn in. Left out means
|
|
23
|
+
* one picked from the id, so a set of peers is never all one colour. */
|
|
24
|
+
colour?: string;
|
|
25
|
+
/** The sheet they are looking at. */
|
|
26
|
+
sheet: string;
|
|
27
|
+
/** Their selection, `[minRow, minCol, maxRow, maxCol]`. */
|
|
28
|
+
rect: Rect;
|
|
29
|
+
/** The cell the cursor is in, which is where the tag goes. Defaults to
|
|
30
|
+
* the top-left of the selection. */
|
|
31
|
+
active?: {
|
|
32
|
+
row: number;
|
|
33
|
+
col: number;
|
|
34
|
+
};
|
|
35
|
+
/** When they were last heard from, in ms. Absent counts as now. */
|
|
36
|
+
at?: number;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Eight colours that read as different people rather than as a heat map,
|
|
40
|
+
* stay legible as a thin box over a white sheet and over a dark one, and
|
|
41
|
+
* each carry a white name at 11px at 4.5:1 or better, so every tag wears
|
|
42
|
+
* the same white ink. The first set had five a shade too light for that.
|
|
43
|
+
*/
|
|
44
|
+
export declare const PRESENCE_COLOURS: ReadonlyArray<string>;
|
|
45
|
+
/** A colour for an id: the same person gets the same one every session. */
|
|
46
|
+
export declare function presenceColour(id: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* The ink for a name tag on a person's colour: black or white, whichever
|
|
49
|
+
* contrasts more. White on every colour was the first version, and white
|
|
50
|
+
* on the first set's green, orange or yellow was a little over 3:1, under
|
|
51
|
+
* the 4.5:1 an 11px name needs. The built-in set is dark enough now, but a
|
|
52
|
+
* collaborator's own colour can be anything, so the tag still asks.
|
|
53
|
+
* A colour that is not a hex triplet gets white, the way it always did.
|
|
54
|
+
*/
|
|
55
|
+
export declare function presenceInk(colour: string): '#000' | '#fff';
|
|
56
|
+
/** Two initials for the tag when the box is too small for a name. */
|
|
57
|
+
export declare function presenceInitials(name: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* The peers still here: the newest entry per person, anyone quiet for
|
|
60
|
+
* longer than `timeoutMs` dropped.
|
|
61
|
+
*
|
|
62
|
+
* A network gives no "goodbye" when a tab is closed, so the only honest
|
|
63
|
+
* answer to "is this person still here" is how long ago they last said
|
|
64
|
+
* anything. Fifteen seconds is the grid's own answer, and this keeps to it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function livePresence(list: ReadonlyArray<SheetPresence>, now?: number, timeoutMs?: number): SheetPresence[];
|
|
67
|
+
/** The peers on one sheet, in a stable order so the boxes do not swap. */
|
|
68
|
+
export declare function presenceOnSheet(list: ReadonlyArray<SheetPresence>, sheet: string): SheetPresence[];
|
|
69
|
+
/** Where the tag goes: the cell named, or the top-left of the selection. */
|
|
70
|
+
export declare function presenceAnchor(person: SheetPresence): {
|
|
71
|
+
row: number;
|
|
72
|
+
col: number;
|
|
73
|
+
};
|