@svgrid/grid 2.2.32 → 2.4.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/README.md +199 -199
- package/dist/DockNodeView.svelte +1 -1
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -179
- package/dist/GridMenus.svelte +705 -782
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +275 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +160 -160
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +374 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.css +2814 -2804
- package/dist/SvGrid.svelte +3490 -3513
- package/dist/SvGridChart.svelte +1716 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +266 -268
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvKbd.svelte +1 -1
- package/dist/SvList.svelte +1 -1
- package/dist/SvListBox.svelte +326 -326
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMenubar.svelte +1 -1
- package/dist/SvMultiSelect.svelte +289 -290
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichText.svelte +211 -207
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +112 -112
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -204
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +235 -237
- package/dist/cdn/{GridMenus-C6ixSGWW.js → GridMenus-BpVcKyUr.js} +5 -7
- package/dist/cdn/{GridMenus-Cyk3qpJ7.js → GridMenus-CeXL5IW4.js} +153 -152
- package/dist/cdn/{SvDateTimePicker-ClqtVIj3.js → SvDateTimePicker-DSUhIta8.js} +165 -165
- package/dist/cdn/{SvDateTimePicker-CdqaP61H.js → SvDateTimePicker-h9kPyszT.js} +572 -555
- package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Bs2GIR2Q.js} +28 -21
- package/dist/cdn/{SvGridChart-BhnFdLd0.js → SvGridChart-CwhFz7GV.js} +2 -2
- package/dist/cdn/{SvGridChartPanel-Cmx9DHAS.js → SvGridChartPanel-1WXuStzM.js} +2 -2
- package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-e5_nqNP9.js} +22 -14
- package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-SmPW10dI.js} +3 -3
- package/dist/cdn/{SvGridChartView-B40DTJSX.js → SvGridChartView-eSPuJE6g.js} +2 -2
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-B1ZcLpgs.js} +1 -1
- package/dist/cdn/{SvGridDropdown-Dh-2ej8j.js → SvGridDropdown-CtdbHcIS.js} +6 -5
- package/dist/cdn/{chart-T1usDN0o.js → chart-i4XcXyHJ.js} +12 -10
- package/dist/cdn/disclose-version-Dr7rEVv-.js +2866 -0
- package/dist/cdn/{export-format-JT5pWVT8.js → export-format-Cv1Dll6k.js} +66 -245
- package/dist/cdn/{src-CgD-wiuS.js → src-DbeN1ReZ.js} +9223 -8944
- package/dist/cdn/{src-BgHjWF0Q.js → src-DmEQFW5_.js} +9023 -8828
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +9 -9
- package/dist/chart-export.js +8 -8
- package/dist/chart.d.ts +2 -0
- package/dist/chart.js +1 -4
- package/dist/columns.js +9 -2
- package/dist/createMenu.svelte.js +0 -1
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.js +1 -1
- package/dist/export-format.js +3 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/_svelte_metadata.json +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/_metadata.json +178 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js +3843 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js +23 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js +1925 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js +206 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js +20 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js +264 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js +698 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/package.json +3 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js +135 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js +57 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_attachments.js +2 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js +46691 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js +239 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_events.js +2 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js +6 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_client.js +7 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js +6 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js +7 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js +7 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js +7 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_server.js +6 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js +85 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js +561 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js +26 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js +124 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_server.js +2 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js +91 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js +242 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js +497 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js +4929 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js +192 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js +10 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js.map +1 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js +140 -0
- package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js.map +1 -0
- package/dist/selection.js +0 -1
- package/package.json +11 -10
- package/src/DockNodeView.svelte +1 -1
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -179
- package/src/GridMenus.svelte +705 -782
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +275 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +160 -160
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +374 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +3680 -3685
- package/src/SvGrid.css +2814 -2804
- package/src/SvGrid.svelte +3490 -3513
- package/src/SvGrid.types.ts +1708 -1709
- package/src/SvGridChart.svelte +1716 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +266 -268
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvKbd.svelte +1 -1
- package/src/SvList.svelte +1 -1
- package/src/SvListBox.svelte +326 -326
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMenubar.svelte +1 -1
- package/src/SvMultiSelect.svelte +289 -290
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichText.svelte +211 -207
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +112 -112
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -204
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +235 -237
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1391 -1391
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +805 -910
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.test.ts +1 -1
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -590
- package/src/cell-values.ts +118 -118
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2295 -2296
- package/src/clipboard.ts +0 -102
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/columns.ts +7 -117
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1261 -1261
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -225
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +949 -949
- package/src/editing.ts +606 -709
- package/src/editor-contract.ts +164 -164
- package/src/editor-registry.grid.test.ts +144 -146
- package/src/editor-registry.ts +122 -122
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -598
- package/src/features.ts +0 -90
- package/src/filter-operators.ts +157 -157
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/index.ts +724 -724
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.coverage.test.ts +1 -1
- package/src/keyboard-handlers.ts +270 -390
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +172 -172
- package/src/menus.test.ts +0 -1
- package/src/menus.ts +567 -679
- package/src/merge-objects.ts +48 -48
- package/src/new-features.test.ts +3 -1
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/_svelte_metadata.json +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/_metadata.json +178 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js +3843 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js +23 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js +1925 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js +206 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js +20 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js +264 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js +698 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/package.json +3 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js +135 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js +57 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_attachments.js +2 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js +46691 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js +239 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_events.js +2 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js +6 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_client.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js +6 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_server.js +6 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js +85 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js +561 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js +26 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js +124 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_server.js +2 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js +91 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js +242 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js +497 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js +4929 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js +192 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js +10 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js +140 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js.map +1 -0
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/row-drag.test.ts +1 -1
- package/src/row-resize.test.ts +1 -2
- package/src/row-resize.ts +210 -210
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- package/src/scroll-sync.test.ts +4 -7
- package/src/scroll-sync.ts +0 -122
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +491 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/server-group-model.test.ts +2 -2
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +0 -112
- package/src/svgrid-wrapper.types.ts +526 -526
- package/src/svgrid.async-editor-options.test.ts +271 -271
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +705 -706
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.column-reorder.test.ts +0 -8
- package/src/svgrid.comments-autocomplete.test.ts +123 -123
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +322 -322
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.footer-pagesize.svelte.test.ts +86 -0
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +257 -257
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -169
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
- package/dist/cdn/disclose-version-DoD9AFD_.js +0 -2958
package/src/cell-values.ts
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
// Pure column/cell value access + display-formatting helpers. They operate
|
|
2
|
-
// only on their Column/Row/data arguments (plus the numeric formatter), so
|
|
3
|
-
// they hold no grid state and live outside the controller.
|
|
4
|
-
import type {
|
|
5
|
-
CellFormatConfig,
|
|
6
|
-
Column,
|
|
7
|
-
ColumnDef,
|
|
8
|
-
Row,
|
|
9
|
-
RowData,
|
|
10
|
-
TableFeatures,
|
|
11
|
-
} from "./index";
|
|
12
|
-
import { formatNumericWithConfig } from "./cell-formatting";
|
|
13
|
-
|
|
14
|
-
export function getColumnBaseValue<TData extends RowData>(row: Row<TData>, column: Column<TData>) {
|
|
15
|
-
const def = column.columnDef;
|
|
16
|
-
if (def.fieldFn) return def.fieldFn(row.original);
|
|
17
|
-
if (def.field) return row.original[def.field];
|
|
18
|
-
return row.getCellValueByColumnId(column.id);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function isGroupRow<TData extends RowData>(row: Row<TData>) {
|
|
22
|
-
// Tree rows are expandable too, but they are real data rows - rendering one as
|
|
23
|
-
// a full-width banner would swallow its cells. Only grouping's synthetic rows
|
|
24
|
-
// are banners.
|
|
25
|
-
if ((row as { __treeRow?: boolean }).__treeRow) return false;
|
|
26
|
-
return typeof row.getCanExpand === "function" && row.getCanExpand();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function toolPanelHeaderLabel<TData extends RowData>(column: Column<TData>): string {
|
|
30
|
-
const h = column.columnDef.header;
|
|
31
|
-
return typeof h === "string" ? h : column.id;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Apply the column's `format` config to a raw numeric summary value
|
|
35
|
-
* (sum / avg / etc.). Mirrors the currency/number/percent branches in
|
|
36
|
-
* `formatCellValue` so a totaled salary column shows "$1,234,567" in
|
|
37
|
-
* the footer instead of "1234567". Per-row `formatter` functions are
|
|
38
|
-
* intentionally NOT invoked here - they may close over row state. */
|
|
39
|
-
export function formatSummaryNumeric<TData extends RowData>(column: Column<TData>, value: number): string {
|
|
40
|
-
const formatConfig = column.columnDef.format as
|
|
41
|
-
| CellFormatConfig
|
|
42
|
-
| undefined;
|
|
43
|
-
if (
|
|
44
|
-
formatConfig &&
|
|
45
|
-
(formatConfig.type === "number" ||
|
|
46
|
-
formatConfig.type === "currency" ||
|
|
47
|
-
formatConfig.type === "percent")
|
|
48
|
-
) {
|
|
49
|
-
return formatNumericWithConfig(value, {
|
|
50
|
-
type: formatConfig.type,
|
|
51
|
-
locales: formatConfig.locales,
|
|
52
|
-
currency:
|
|
53
|
-
formatConfig.type === "currency"
|
|
54
|
-
? (formatConfig.currency ?? "USD")
|
|
55
|
-
: undefined,
|
|
56
|
-
valueIsPercentPoints:
|
|
57
|
-
formatConfig.type === "percent"
|
|
58
|
-
? formatConfig.valueIsPercentPoints
|
|
59
|
-
: undefined,
|
|
60
|
-
options: formatConfig.options,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return String(value);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Effective horizontal alignment for a column. Honors the explicit
|
|
68
|
-
* `align` prop on the ColumnDef; otherwise picks a sensible default
|
|
69
|
-
* based on `editorType`:
|
|
70
|
-
* number / date / datetime → 'right'
|
|
71
|
-
* checkbox → 'center'
|
|
72
|
-
* everything else → 'left'
|
|
73
|
-
*/
|
|
74
|
-
export function getColumnAlign<TData extends RowData>(column: Column<TData>): "left" | "center" | "right" {
|
|
75
|
-
const explicit = column.columnDef.align;
|
|
76
|
-
if (explicit) return explicit;
|
|
77
|
-
const editorType = column.columnDef.editorType;
|
|
78
|
-
if (
|
|
79
|
-
editorType === "number" ||
|
|
80
|
-
editorType === "date" ||
|
|
81
|
-
editorType === "datetime"
|
|
82
|
-
) {
|
|
83
|
-
return "right";
|
|
84
|
-
}
|
|
85
|
-
if (editorType === "checkbox") return "center";
|
|
86
|
-
return "left";
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Read a cell value from a PINNED row. Pinned rows aren't bound to a
|
|
91
|
-
* TanStack Row<TData>, so we resolve the value directly off the raw
|
|
92
|
-
* TData via `fieldFn` or `field`. Mirrors `getColumnBaseValue`'s
|
|
93
|
-
* lookup path minus the Row indirection.
|
|
94
|
-
*/
|
|
95
|
-
export function getPinnedCellValue<TData extends RowData>(
|
|
96
|
-
rowData: TData,
|
|
97
|
-
column: Column<TData>,
|
|
98
|
-
): unknown {
|
|
99
|
-
const def = column.columnDef;
|
|
100
|
-
if (def.fieldFn) return def.fieldFn(rowData);
|
|
101
|
-
const field = def.field as string | undefined;
|
|
102
|
-
if (!field) return undefined;
|
|
103
|
-
return (rowData as unknown as Record<string, unknown>)[field];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function getColumnAccessorValue<TData extends RowData>(rowData: TData, column: Column<TData>) {
|
|
107
|
-
const def = column.columnDef;
|
|
108
|
-
if (def.fieldFn) return def.fieldFn(rowData);
|
|
109
|
-
if (def.field) return rowData[def.field];
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function columnDefMatchesId<TFeatures extends TableFeatures, TData extends RowData>(
|
|
114
|
-
def: ColumnDef<TFeatures, TData>,
|
|
115
|
-
columnId: string,
|
|
116
|
-
): boolean {
|
|
117
|
-
return (def.id ?? def.field ?? null) === columnId;
|
|
118
|
-
}
|
|
1
|
+
// Pure column/cell value access + display-formatting helpers. They operate
|
|
2
|
+
// only on their Column/Row/data arguments (plus the numeric formatter), so
|
|
3
|
+
// they hold no grid state and live outside the controller.
|
|
4
|
+
import type {
|
|
5
|
+
CellFormatConfig,
|
|
6
|
+
Column,
|
|
7
|
+
ColumnDef,
|
|
8
|
+
Row,
|
|
9
|
+
RowData,
|
|
10
|
+
TableFeatures,
|
|
11
|
+
} from "./index";
|
|
12
|
+
import { formatNumericWithConfig } from "./cell-formatting";
|
|
13
|
+
|
|
14
|
+
export function getColumnBaseValue<TData extends RowData>(row: Row<TData>, column: Column<TData>) {
|
|
15
|
+
const def = column.columnDef;
|
|
16
|
+
if (def.fieldFn) return def.fieldFn(row.original);
|
|
17
|
+
if (def.field) return row.original[def.field];
|
|
18
|
+
return row.getCellValueByColumnId(column.id);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isGroupRow<TData extends RowData>(row: Row<TData>) {
|
|
22
|
+
// Tree rows are expandable too, but they are real data rows - rendering one as
|
|
23
|
+
// a full-width banner would swallow its cells. Only grouping's synthetic rows
|
|
24
|
+
// are banners.
|
|
25
|
+
if ((row as { __treeRow?: boolean }).__treeRow) return false;
|
|
26
|
+
return typeof row.getCanExpand === "function" && row.getCanExpand();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function toolPanelHeaderLabel<TData extends RowData>(column: Column<TData>): string {
|
|
30
|
+
const h = column.columnDef.header;
|
|
31
|
+
return typeof h === "string" ? h : column.id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Apply the column's `format` config to a raw numeric summary value
|
|
35
|
+
* (sum / avg / etc.). Mirrors the currency/number/percent branches in
|
|
36
|
+
* `formatCellValue` so a totaled salary column shows "$1,234,567" in
|
|
37
|
+
* the footer instead of "1234567". Per-row `formatter` functions are
|
|
38
|
+
* intentionally NOT invoked here - they may close over row state. */
|
|
39
|
+
export function formatSummaryNumeric<TData extends RowData>(column: Column<TData>, value: number): string {
|
|
40
|
+
const formatConfig = column.columnDef.format as
|
|
41
|
+
| CellFormatConfig
|
|
42
|
+
| undefined;
|
|
43
|
+
if (
|
|
44
|
+
formatConfig &&
|
|
45
|
+
(formatConfig.type === "number" ||
|
|
46
|
+
formatConfig.type === "currency" ||
|
|
47
|
+
formatConfig.type === "percent")
|
|
48
|
+
) {
|
|
49
|
+
return formatNumericWithConfig(value, {
|
|
50
|
+
type: formatConfig.type,
|
|
51
|
+
locales: formatConfig.locales,
|
|
52
|
+
currency:
|
|
53
|
+
formatConfig.type === "currency"
|
|
54
|
+
? (formatConfig.currency ?? "USD")
|
|
55
|
+
: undefined,
|
|
56
|
+
valueIsPercentPoints:
|
|
57
|
+
formatConfig.type === "percent"
|
|
58
|
+
? formatConfig.valueIsPercentPoints
|
|
59
|
+
: undefined,
|
|
60
|
+
options: formatConfig.options,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Effective horizontal alignment for a column. Honors the explicit
|
|
68
|
+
* `align` prop on the ColumnDef; otherwise picks a sensible default
|
|
69
|
+
* based on `editorType`:
|
|
70
|
+
* number / date / datetime → 'right'
|
|
71
|
+
* checkbox → 'center'
|
|
72
|
+
* everything else → 'left'
|
|
73
|
+
*/
|
|
74
|
+
export function getColumnAlign<TData extends RowData>(column: Column<TData>): "left" | "center" | "right" {
|
|
75
|
+
const explicit = column.columnDef.align;
|
|
76
|
+
if (explicit) return explicit;
|
|
77
|
+
const editorType = column.columnDef.editorType;
|
|
78
|
+
if (
|
|
79
|
+
editorType === "number" ||
|
|
80
|
+
editorType === "date" ||
|
|
81
|
+
editorType === "datetime"
|
|
82
|
+
) {
|
|
83
|
+
return "right";
|
|
84
|
+
}
|
|
85
|
+
if (editorType === "checkbox") return "center";
|
|
86
|
+
return "left";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Read a cell value from a PINNED row. Pinned rows aren't bound to a
|
|
91
|
+
* TanStack Row<TData>, so we resolve the value directly off the raw
|
|
92
|
+
* TData via `fieldFn` or `field`. Mirrors `getColumnBaseValue`'s
|
|
93
|
+
* lookup path minus the Row indirection.
|
|
94
|
+
*/
|
|
95
|
+
export function getPinnedCellValue<TData extends RowData>(
|
|
96
|
+
rowData: TData,
|
|
97
|
+
column: Column<TData>,
|
|
98
|
+
): unknown {
|
|
99
|
+
const def = column.columnDef;
|
|
100
|
+
if (def.fieldFn) return def.fieldFn(rowData);
|
|
101
|
+
const field = def.field as string | undefined;
|
|
102
|
+
if (!field) return undefined;
|
|
103
|
+
return (rowData as unknown as Record<string, unknown>)[field];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getColumnAccessorValue<TData extends RowData>(rowData: TData, column: Column<TData>) {
|
|
107
|
+
const def = column.columnDef;
|
|
108
|
+
if (def.fieldFn) return def.fieldFn(rowData);
|
|
109
|
+
if (def.field) return rowData[def.field];
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function columnDefMatchesId<TFeatures extends TableFeatures, TData extends RowData>(
|
|
114
|
+
def: ColumnDef<TFeatures, TData>,
|
|
115
|
+
columnId: string,
|
|
116
|
+
): boolean {
|
|
117
|
+
return (def.id ?? def.field ?? null) === columnId;
|
|
118
|
+
}
|
package/src/chart-export.ts
CHANGED
|
@@ -1,201 +1,201 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Export a rendered `SvGridChart` to a standalone SVG file or a PNG image.
|
|
3
|
-
*
|
|
4
|
-
* The on-screen chart styles its axes / labels / gridlines with CSS classes
|
|
5
|
-
* and `--sg-*` theme variables, which don't survive a raw serialization. So
|
|
6
|
-
* we clone the SVG, read the live computed colors, and inline a small `<style>`
|
|
7
|
-
* block with concrete values - the export looks like what's on screen.
|
|
8
|
-
*
|
|
9
|
-
* Pass the chart's `<svg class="sv-grid-chart-svg">` element (or any ancestor
|
|
10
|
-
* that contains it).
|
|
11
|
-
*/
|
|
12
|
-
import type { ChartSpec } from './chart'
|
|
13
|
-
|
|
14
|
-
function resolveSvg(el: SVGSVGElement | HTMLElement): SVGSVGElement | null {
|
|
15
|
-
if (el instanceof SVGSVGElement) return el
|
|
16
|
-
return el.querySelector('svg.sv-grid-chart-svg') ?? el.querySelector('svg')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function cssVar(el: Element, name: string, fallback: string): string {
|
|
20
|
-
const v = getComputedStyle(el).getPropertyValue(name).trim()
|
|
21
|
-
return v || fallback
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type ChartExportOptions = {
|
|
25
|
-
/** Background color of the exported image. Default: the grid background. */
|
|
26
|
-
background?: string
|
|
27
|
-
/** PNG pixel scale (1 = viewBox size). Default 2 (retina). */
|
|
28
|
-
scale?: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Serialize the chart SVG to a standalone, self-styled SVG string. */
|
|
32
|
-
export function chartToSvgString(
|
|
33
|
-
source: SVGSVGElement | HTMLElement,
|
|
34
|
-
options: ChartExportOptions = {},
|
|
35
|
-
): string {
|
|
36
|
-
const svg = resolveSvg(source)
|
|
37
|
-
if (!svg) throw new Error('chartToSvgString: no <svg> found')
|
|
38
|
-
|
|
39
|
-
const fg = cssVar(svg, '--sg-fg', '#0f172a')
|
|
40
|
-
const muted = cssVar(svg, '--sg-muted', '#64748b')
|
|
41
|
-
const border = cssVar(svg, '--sg-border', '#e2e8f0')
|
|
42
|
-
const bg = options.background ?? cssVar(svg, '--sg-bg', '#ffffff')
|
|
43
|
-
|
|
44
|
-
const vb = svg.viewBox.baseVal
|
|
45
|
-
const w = vb && vb.width ? vb.width : svg.clientWidth || 520
|
|
46
|
-
const h = vb && vb.height ? vb.height : svg.clientHeight || 300
|
|
47
|
-
|
|
48
|
-
const clone = svg.cloneNode(true) as SVGSVGElement
|
|
49
|
-
clone.setAttribute('width', String(w))
|
|
50
|
-
clone.setAttribute('height', String(h))
|
|
51
|
-
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
|
52
|
-
|
|
53
|
-
// Inline computed paint/text styles so the export is what's on screen -
|
|
54
|
-
// dynamic SVG presentation attributes (a line's `stroke={color}`) and scoped
|
|
55
|
-
// component CSS don't survive a raw clone + XMLSerializer. Walk the live tree
|
|
56
|
-
// and its clone (1:1 here, before we prune hit layers) and copy the resolved
|
|
57
|
-
// style, so line paths etc. export with their real stroke instead of nothing.
|
|
58
|
-
const PAINT_PROPS = [
|
|
59
|
-
'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity',
|
|
60
|
-
'stroke-dasharray', 'stroke-linecap', 'stroke-linejoin', 'opacity',
|
|
61
|
-
'font-size', 'font-family', 'font-weight', 'text-anchor',
|
|
62
|
-
]
|
|
63
|
-
const liveNodes = svg.querySelectorAll('*')
|
|
64
|
-
const cloneNodes = clone.querySelectorAll('*')
|
|
65
|
-
for (let i = 0; i < liveNodes.length; i += 1) {
|
|
66
|
-
const el = cloneNodes[i] as (SVGElement & { style: CSSStyleDeclaration }) | undefined
|
|
67
|
-
if (!el || !el.style) continue
|
|
68
|
-
const cs = getComputedStyle(liveNodes[i]!)
|
|
69
|
-
for (const prop of PAINT_PROPS) {
|
|
70
|
-
const v = cs.getPropertyValue(prop)
|
|
71
|
-
if (v && v !== 'normal') el.style.setProperty(prop, v)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Drop interaction-only hit layers.
|
|
76
|
-
clone.querySelectorAll('.sv-grid-chart-hit').forEach((n) => n.remove())
|
|
77
|
-
|
|
78
|
-
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
|
79
|
-
style.textContent = `
|
|
80
|
-
.sv-grid-chart-axis { fill: ${muted}; font-size: 10px; font-family: sans-serif; }
|
|
81
|
-
.sv-grid-chart-gridline { stroke: ${border}; stroke-width: 1; opacity: 0.6; }
|
|
82
|
-
.sv-grid-chart-gridline.is-zero { stroke: ${muted}; opacity: 0.9; }
|
|
83
|
-
.sv-grid-chart-datalabel { fill: ${fg}; font-size: 9.5px; font-weight: 600; font-family: sans-serif; }
|
|
84
|
-
.sv-grid-chart-datalabel.on-bar { fill: #fff; }
|
|
85
|
-
.sv-grid-chart-donut-total { fill: ${fg}; font-size: 16px; font-weight: 800; font-family: sans-serif; }
|
|
86
|
-
.sv-grid-chart-donut-label { fill: ${muted}; font-size: 10px; font-family: sans-serif; }
|
|
87
|
-
`
|
|
88
|
-
clone.insertBefore(style, clone.firstChild)
|
|
89
|
-
|
|
90
|
-
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
|
91
|
-
rect.setAttribute('x', '0')
|
|
92
|
-
rect.setAttribute('y', '0')
|
|
93
|
-
rect.setAttribute('width', String(w))
|
|
94
|
-
rect.setAttribute('height', String(h))
|
|
95
|
-
rect.setAttribute('fill', bg)
|
|
96
|
-
clone.insertBefore(rect, style.nextSibling)
|
|
97
|
-
|
|
98
|
-
return new XMLSerializer().serializeToString(clone)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function triggerDownload(blobUrl: string, filename: string) {
|
|
102
|
-
const a = document.createElement('a')
|
|
103
|
-
a.href = blobUrl
|
|
104
|
-
a.download = filename
|
|
105
|
-
document.body.appendChild(a)
|
|
106
|
-
a.click()
|
|
107
|
-
a.remove()
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Download the chart as an `.svg` file. */
|
|
111
|
-
export function downloadChartSvg(
|
|
112
|
-
source: SVGSVGElement | HTMLElement,
|
|
113
|
-
filename = 'chart.svg',
|
|
114
|
-
options: ChartExportOptions = {},
|
|
115
|
-
): void {
|
|
116
|
-
const svg = chartToSvgString(source, options)
|
|
117
|
-
const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }))
|
|
118
|
-
triggerDownload(url, filename)
|
|
119
|
-
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Rasterize the chart to a PNG `Blob`. */
|
|
123
|
-
export function chartToPngBlob(
|
|
124
|
-
source: SVGSVGElement | HTMLElement,
|
|
125
|
-
options: ChartExportOptions = {},
|
|
126
|
-
): Promise<Blob> {
|
|
127
|
-
const svgStr = chartToSvgString(source, options)
|
|
128
|
-
const scale = options.scale ?? 2
|
|
129
|
-
const svgEl = resolveSvg(source)!
|
|
130
|
-
const vb = svgEl.viewBox.baseVal
|
|
131
|
-
const w = (vb && vb.width ? vb.width : svgEl.clientWidth || 520) * scale
|
|
132
|
-
const h = (vb && vb.height ? vb.height : svgEl.clientHeight || 300) * scale
|
|
133
|
-
|
|
134
|
-
return new Promise((resolve, reject) => {
|
|
135
|
-
const url = URL.createObjectURL(new Blob([svgStr], { type: 'image/svg+xml' }))
|
|
136
|
-
const img = new Image()
|
|
137
|
-
img.onload = () => {
|
|
138
|
-
const canvas = document.createElement('canvas')
|
|
139
|
-
canvas.width = Math.round(w)
|
|
140
|
-
canvas.height = Math.round(h)
|
|
141
|
-
const ctx = canvas.getContext('2d')
|
|
142
|
-
if (!ctx) {
|
|
143
|
-
URL.revokeObjectURL(url)
|
|
144
|
-
reject(new Error('canvas 2d context unavailable'))
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
|
148
|
-
URL.revokeObjectURL(url)
|
|
149
|
-
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('toBlob failed'))), 'image/png')
|
|
150
|
-
}
|
|
151
|
-
img.onerror = () => {
|
|
152
|
-
URL.revokeObjectURL(url)
|
|
153
|
-
reject(new Error('failed to rasterize chart SVG'))
|
|
154
|
-
}
|
|
155
|
-
img.src = url
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Download the chart as a `.png` file. */
|
|
160
|
-
export async function downloadChartPng(
|
|
161
|
-
source: SVGSVGElement | HTMLElement,
|
|
162
|
-
filename = 'chart.png',
|
|
163
|
-
options: ChartExportOptions = {},
|
|
164
|
-
): Promise<void> {
|
|
165
|
-
const blob = await chartToPngBlob(source, options)
|
|
166
|
-
const url = URL.createObjectURL(blob)
|
|
167
|
-
triggerDownload(url, filename)
|
|
168
|
-
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const csvCell = (v: unknown): string => {
|
|
172
|
-
const s = v == null ? '' : String(v)
|
|
173
|
-
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Serialize a chart's data to CSV: one row per category, one column per series
|
|
178
|
-
* (`Category, <series 1>, ...`). Empty for exotic specs (sankey/treemap/gauge/
|
|
179
|
-
* calendar) that don't carry a rectangular categories x series grid.
|
|
180
|
-
*/
|
|
181
|
-
export function chartSpecToCsv(spec: ChartSpec): string {
|
|
182
|
-
const cats = spec.categories ?? []
|
|
183
|
-
const series = spec.series ?? []
|
|
184
|
-
if (!cats.length || !series.length) return ''
|
|
185
|
-
const header = ['Category', ...series.map((s) => s.label)].map(csvCell).join(',')
|
|
186
|
-
const rows = cats.map((cat, i) =>
|
|
187
|
-
[cat, ...series.map((s) => s.values[i] ?? '')].map(csvCell).join(','),
|
|
188
|
-
)
|
|
189
|
-
return [header, ...rows].join('\n')
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/** Download the chart's data as a `.csv` file. Returns false if there's no
|
|
193
|
-
* rectangular category/series data to export. */
|
|
194
|
-
export function downloadChartCsv(spec: ChartSpec, filename = 'chart.csv'): boolean {
|
|
195
|
-
const csv = chartSpecToCsv(spec)
|
|
196
|
-
if (!csv) return false
|
|
197
|
-
const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }))
|
|
198
|
-
triggerDownload(url, filename)
|
|
199
|
-
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
200
|
-
return true
|
|
201
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Export a rendered `SvGridChart` to a standalone SVG file or a PNG image.
|
|
3
|
+
*
|
|
4
|
+
* The on-screen chart styles its axes / labels / gridlines with CSS classes
|
|
5
|
+
* and `--sg-*` theme variables, which don't survive a raw serialization. So
|
|
6
|
+
* we clone the SVG, read the live computed colors, and inline a small `<style>`
|
|
7
|
+
* block with concrete values - the export looks like what's on screen.
|
|
8
|
+
*
|
|
9
|
+
* Pass the chart's `<svg class="sv-grid-chart-svg">` element (or any ancestor
|
|
10
|
+
* that contains it).
|
|
11
|
+
*/
|
|
12
|
+
import type { ChartSpec } from './chart'
|
|
13
|
+
|
|
14
|
+
function resolveSvg(el: SVGSVGElement | HTMLElement): SVGSVGElement | null {
|
|
15
|
+
if (el instanceof SVGSVGElement) return el
|
|
16
|
+
return el.querySelector('svg.sv-grid-chart-svg') ?? el.querySelector('svg')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cssVar(el: Element, name: string, fallback: string): string {
|
|
20
|
+
const v = getComputedStyle(el).getPropertyValue(name).trim()
|
|
21
|
+
return v || fallback
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ChartExportOptions = {
|
|
25
|
+
/** Background color of the exported image. Default: the grid background. */
|
|
26
|
+
background?: string
|
|
27
|
+
/** PNG pixel scale (1 = viewBox size). Default 2 (retina). */
|
|
28
|
+
scale?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Serialize the chart SVG to a standalone, self-styled SVG string. */
|
|
32
|
+
export function chartToSvgString(
|
|
33
|
+
source: SVGSVGElement | HTMLElement,
|
|
34
|
+
options: ChartExportOptions = {},
|
|
35
|
+
): string {
|
|
36
|
+
const svg = resolveSvg(source)
|
|
37
|
+
if (!svg) throw new Error('chartToSvgString: no <svg> found')
|
|
38
|
+
|
|
39
|
+
const fg = cssVar(svg, '--sg-fg', '#0f172a')
|
|
40
|
+
const muted = cssVar(svg, '--sg-muted', '#64748b')
|
|
41
|
+
const border = cssVar(svg, '--sg-border', '#e2e8f0')
|
|
42
|
+
const bg = options.background ?? cssVar(svg, '--sg-bg', '#ffffff')
|
|
43
|
+
|
|
44
|
+
const vb = svg.viewBox.baseVal
|
|
45
|
+
const w = vb && vb.width ? vb.width : svg.clientWidth || 520
|
|
46
|
+
const h = vb && vb.height ? vb.height : svg.clientHeight || 300
|
|
47
|
+
|
|
48
|
+
const clone = svg.cloneNode(true) as SVGSVGElement
|
|
49
|
+
clone.setAttribute('width', String(w))
|
|
50
|
+
clone.setAttribute('height', String(h))
|
|
51
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
|
52
|
+
|
|
53
|
+
// Inline computed paint/text styles so the export is what's on screen -
|
|
54
|
+
// dynamic SVG presentation attributes (a line's `stroke={color}`) and scoped
|
|
55
|
+
// component CSS don't survive a raw clone + XMLSerializer. Walk the live tree
|
|
56
|
+
// and its clone (1:1 here, before we prune hit layers) and copy the resolved
|
|
57
|
+
// style, so line paths etc. export with their real stroke instead of nothing.
|
|
58
|
+
const PAINT_PROPS = [
|
|
59
|
+
'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity',
|
|
60
|
+
'stroke-dasharray', 'stroke-linecap', 'stroke-linejoin', 'opacity',
|
|
61
|
+
'font-size', 'font-family', 'font-weight', 'text-anchor',
|
|
62
|
+
]
|
|
63
|
+
const liveNodes = svg.querySelectorAll('*')
|
|
64
|
+
const cloneNodes = clone.querySelectorAll('*')
|
|
65
|
+
for (let i = 0; i < liveNodes.length; i += 1) {
|
|
66
|
+
const el = cloneNodes[i] as (SVGElement & { style: CSSStyleDeclaration }) | undefined
|
|
67
|
+
if (!el || !el.style) continue
|
|
68
|
+
const cs = getComputedStyle(liveNodes[i]!)
|
|
69
|
+
for (const prop of PAINT_PROPS) {
|
|
70
|
+
const v = cs.getPropertyValue(prop)
|
|
71
|
+
if (v && v !== 'normal') el.style.setProperty(prop, v)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Drop interaction-only hit layers.
|
|
76
|
+
clone.querySelectorAll('.sv-grid-chart-hit').forEach((n) => n.remove())
|
|
77
|
+
|
|
78
|
+
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
|
79
|
+
style.textContent = `
|
|
80
|
+
.sv-grid-chart-axis { fill: ${muted}; font-size: 10px; font-family: sans-serif; }
|
|
81
|
+
.sv-grid-chart-gridline { stroke: ${border}; stroke-width: 1; opacity: 0.6; }
|
|
82
|
+
.sv-grid-chart-gridline.is-zero { stroke: ${muted}; opacity: 0.9; }
|
|
83
|
+
.sv-grid-chart-datalabel { fill: ${fg}; font-size: 9.5px; font-weight: 600; font-family: sans-serif; }
|
|
84
|
+
.sv-grid-chart-datalabel.on-bar { fill: #fff; }
|
|
85
|
+
.sv-grid-chart-donut-total { fill: ${fg}; font-size: 16px; font-weight: 800; font-family: sans-serif; }
|
|
86
|
+
.sv-grid-chart-donut-label { fill: ${muted}; font-size: 10px; font-family: sans-serif; }
|
|
87
|
+
`
|
|
88
|
+
clone.insertBefore(style, clone.firstChild)
|
|
89
|
+
|
|
90
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
|
91
|
+
rect.setAttribute('x', '0')
|
|
92
|
+
rect.setAttribute('y', '0')
|
|
93
|
+
rect.setAttribute('width', String(w))
|
|
94
|
+
rect.setAttribute('height', String(h))
|
|
95
|
+
rect.setAttribute('fill', bg)
|
|
96
|
+
clone.insertBefore(rect, style.nextSibling)
|
|
97
|
+
|
|
98
|
+
return new XMLSerializer().serializeToString(clone)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function triggerDownload(blobUrl: string, filename: string) {
|
|
102
|
+
const a = document.createElement('a')
|
|
103
|
+
a.href = blobUrl
|
|
104
|
+
a.download = filename
|
|
105
|
+
document.body.appendChild(a)
|
|
106
|
+
a.click()
|
|
107
|
+
a.remove()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Download the chart as an `.svg` file. */
|
|
111
|
+
export function downloadChartSvg(
|
|
112
|
+
source: SVGSVGElement | HTMLElement,
|
|
113
|
+
filename = 'chart.svg',
|
|
114
|
+
options: ChartExportOptions = {},
|
|
115
|
+
): void {
|
|
116
|
+
const svg = chartToSvgString(source, options)
|
|
117
|
+
const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }))
|
|
118
|
+
triggerDownload(url, filename)
|
|
119
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Rasterize the chart to a PNG `Blob`. */
|
|
123
|
+
export function chartToPngBlob(
|
|
124
|
+
source: SVGSVGElement | HTMLElement,
|
|
125
|
+
options: ChartExportOptions = {},
|
|
126
|
+
): Promise<Blob> {
|
|
127
|
+
const svgStr = chartToSvgString(source, options)
|
|
128
|
+
const scale = options.scale ?? 2
|
|
129
|
+
const svgEl = resolveSvg(source)!
|
|
130
|
+
const vb = svgEl.viewBox.baseVal
|
|
131
|
+
const w = (vb && vb.width ? vb.width : svgEl.clientWidth || 520) * scale
|
|
132
|
+
const h = (vb && vb.height ? vb.height : svgEl.clientHeight || 300) * scale
|
|
133
|
+
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const url = URL.createObjectURL(new Blob([svgStr], { type: 'image/svg+xml' }))
|
|
136
|
+
const img = new Image()
|
|
137
|
+
img.onload = () => {
|
|
138
|
+
const canvas = document.createElement('canvas')
|
|
139
|
+
canvas.width = Math.round(w)
|
|
140
|
+
canvas.height = Math.round(h)
|
|
141
|
+
const ctx = canvas.getContext('2d')
|
|
142
|
+
if (!ctx) {
|
|
143
|
+
URL.revokeObjectURL(url)
|
|
144
|
+
reject(new Error('canvas 2d context unavailable'))
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
|
148
|
+
URL.revokeObjectURL(url)
|
|
149
|
+
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('toBlob failed'))), 'image/png')
|
|
150
|
+
}
|
|
151
|
+
img.onerror = () => {
|
|
152
|
+
URL.revokeObjectURL(url)
|
|
153
|
+
reject(new Error('failed to rasterize chart SVG'))
|
|
154
|
+
}
|
|
155
|
+
img.src = url
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Download the chart as a `.png` file. */
|
|
160
|
+
export async function downloadChartPng(
|
|
161
|
+
source: SVGSVGElement | HTMLElement,
|
|
162
|
+
filename = 'chart.png',
|
|
163
|
+
options: ChartExportOptions = {},
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const blob = await chartToPngBlob(source, options)
|
|
166
|
+
const url = URL.createObjectURL(blob)
|
|
167
|
+
triggerDownload(url, filename)
|
|
168
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const csvCell = (v: unknown): string => {
|
|
172
|
+
const s = v == null ? '' : String(v)
|
|
173
|
+
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Serialize a chart's data to CSV: one row per category, one column per series
|
|
178
|
+
* (`Category, <series 1>, ...`). Empty for exotic specs (sankey/treemap/gauge/
|
|
179
|
+
* calendar) that don't carry a rectangular categories x series grid.
|
|
180
|
+
*/
|
|
181
|
+
export function chartSpecToCsv(spec: ChartSpec): string {
|
|
182
|
+
const cats = spec.categories ?? []
|
|
183
|
+
const series = spec.series ?? []
|
|
184
|
+
if (!cats.length || !series.length) return ''
|
|
185
|
+
const header = ['Category', ...series.map((s) => s.label)].map(csvCell).join(',')
|
|
186
|
+
const rows = cats.map((cat, i) =>
|
|
187
|
+
[cat, ...series.map((s) => s.values[i] ?? '')].map(csvCell).join(','),
|
|
188
|
+
)
|
|
189
|
+
return [header, ...rows].join('\n')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Download the chart's data as a `.csv` file. Returns false if there's no
|
|
193
|
+
* rectangular category/series data to export. */
|
|
194
|
+
export function downloadChartCsv(spec: ChartSpec, filename = 'chart.csv'): boolean {
|
|
195
|
+
const csv = chartSpecToCsv(spec)
|
|
196
|
+
if (!csv) return false
|
|
197
|
+
const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }))
|
|
198
|
+
triggerDownload(url, filename)
|
|
199
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
200
|
+
return true
|
|
201
|
+
}
|