@svgrid/grid 2.2.33 → 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/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -139
- package/dist/GridMenus.svelte +705 -705
- 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/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 -3490
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +266 -266
- package/dist/SvGroupCell.svelte +116 -116
- 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/SvMultiSelect.svelte +289 -289
- 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 -211
- 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 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +235 -235
- package/dist/cdn/{GridMenus-B7XhhqdN.js → GridMenus-BpVcKyUr.js} +4 -4
- package/dist/cdn/{GridMenus-BxmIvCJh.js → GridMenus-CeXL5IW4.js} +153 -150
- package/dist/cdn/{SvDateTimePicker-ClqtVIj3.js → SvDateTimePicker-DSUhIta8.js} +165 -165
- package/dist/cdn/{SvDateTimePicker-o94wfSUa.js → SvDateTimePicker-h9kPyszT.js} +572 -555
- package/dist/cdn/{SvGridChart-Cr_90pPD.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-ICbxiUvK.js → SvGridChartPanel-e5_nqNP9.js} +20 -12
- package/dist/cdn/{SvGridChartView-Bx42PMvf.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-CUOdm7YA.js → SvGridDropdown-CtdbHcIS.js} +6 -5
- package/dist/cdn/{chart-T1usDN0o.js → chart-i4XcXyHJ.js} +12 -10
- package/dist/cdn/{disclose-version-DljhZohK.js → disclose-version-Dr7rEVv-.js} +989 -1081
- package/dist/cdn/{export-format-JT5pWVT8.js → export-format-Cv1Dll6k.js} +66 -245
- package/dist/cdn/{src-erjBRYfX.js → src-DbeN1ReZ.js} +9221 -8925
- package/dist/cdn/{src-ktbQVOk-.js → src-DmEQFW5_.js} +9021 -8809
- 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/columns.js +9 -2
- 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/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -139
- package/src/GridMenus.svelte +705 -705
- 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/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 -3680
- package/src/SvGrid.css +2814 -2804
- package/src/SvGrid.svelte +3490 -3490
- package/src/SvGrid.types.ts +1708 -1708
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +266 -266
- package/src/SvGroupCell.svelte +116 -116
- 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/SvMultiSelect.svelte +289 -289
- 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 -211
- 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 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +235 -235
- 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 -805
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- 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 -2295
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/columns.ts +7 -2
- 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 -224
- 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 -606
- package/src/editor-contract.ts +164 -164
- package/src/editor-registry.grid.test.ts +144 -144
- 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 -601
- 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.ts +270 -270
- 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.ts +567 -567
- package/src/merge-objects.ts +48 -48
- 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-resize.ts +210 -210
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +491 -491
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- 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/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 -705
- package/src/svgrid.charting.test.ts +534 -534
- 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 -168
- 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
|
@@ -1,413 +1,413 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-Side Row Model (SSRM) controller. A single, documented datasource
|
|
3
|
-
* contract for grids whose data lives on the server - the "I have a million
|
|
4
|
-
* rows in a database" case. The consumer implements ONE async `getRows`
|
|
5
|
-
* function; this controller owns the request lifecycle (sort, filter, page),
|
|
6
|
-
* de-dupes/races, and pushes results back through `onChange`.
|
|
7
|
-
*
|
|
8
|
-
* It's headless and framework-agnostic on purpose: wire `setSort` /
|
|
9
|
-
* `setFilter` / `setPage` to the grid's controlled callbacks, and render the
|
|
10
|
-
* grid from the `{ rows, total, loading }` it hands you. See the demo.
|
|
11
|
-
*
|
|
12
|
-
* The write side (`createRow` / `updateRow` / `deleteRow`) is optional: a
|
|
13
|
-
* source that only implements `getRows` stays a pure read model, and the
|
|
14
|
-
* matching controller methods throw a clear error if called. Mutations are
|
|
15
|
-
* non-optimistic for now - the grid reflects a change only after the
|
|
16
|
-
* follow-up re-fetch of the current page lands.
|
|
17
|
-
*/
|
|
18
|
-
export type ServerSortModel = Array<{ id: string; desc: boolean }>
|
|
19
|
-
|
|
20
|
-
export type ServerFilterModel = {
|
|
21
|
-
/** Free-text global search. */
|
|
22
|
-
global?: string
|
|
23
|
-
/**
|
|
24
|
-
* Per-column filters, keyed by column id. `value` (+ `valueTo`) carry the
|
|
25
|
-
* operator-style filter; `selectedValues` carries a facet/checklist
|
|
26
|
-
* selection (set-filter). Either or both may be present.
|
|
27
|
-
*/
|
|
28
|
-
columns?: Record<
|
|
29
|
-
string,
|
|
30
|
-
{ operator: string; value: string; valueTo?: string; selectedValues?: string[] }
|
|
31
|
-
>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** A value column to roll up per group. */
|
|
35
|
-
export type ServerAggregation = { col: string; fn: 'sum' | 'avg' | 'min' | 'max' | 'count' }
|
|
36
|
-
|
|
37
|
-
export type ServerRequest = {
|
|
38
|
-
/** Zero-based index of the first row wanted (inclusive). */
|
|
39
|
-
startRow: number
|
|
40
|
-
/** Index just past the last row wanted (exclusive). */
|
|
41
|
-
endRow: number
|
|
42
|
-
pageIndex: number
|
|
43
|
-
pageSize: number
|
|
44
|
-
sortModel: ServerSortModel
|
|
45
|
-
filterModel: ServerFilterModel
|
|
46
|
-
/**
|
|
47
|
-
* Server-side grouping / tree: the columns being grouped on, outer to inner.
|
|
48
|
-
* Omitted / empty for a flat request.
|
|
49
|
-
*/
|
|
50
|
-
groupBy?: string[]
|
|
51
|
-
/**
|
|
52
|
-
* The path of group keys the grid is expanding, e.g. `['Germany', 'Berlin']`.
|
|
53
|
-
* Empty (`[]`) asks for the top level. When `groupKeys.length < groupBy.length`
|
|
54
|
-
* the server returns **group rows** (one per distinct key at this level,
|
|
55
|
-
* carrying the group key + aggregates); when they are equal it returns the
|
|
56
|
-
* **leaf rows** under that path.
|
|
57
|
-
*/
|
|
58
|
-
groupKeys?: string[]
|
|
59
|
-
/** Value columns to aggregate per group. */
|
|
60
|
-
aggregations?: ServerAggregation[]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* A group row in the server-side group/tree model - one distinct key at a
|
|
65
|
-
* level, with its rolled-up aggregates. The grid renders it with an expander;
|
|
66
|
-
* expanding it fetches its children through the same `getRows`.
|
|
67
|
-
*/
|
|
68
|
-
export type ServerGroupRow<TData> = {
|
|
69
|
-
kind: 'group'
|
|
70
|
-
/** Stable id (the group path). */
|
|
71
|
-
id: string
|
|
72
|
-
/** Group keys from the root to this node, e.g. `['Germany', 'Berlin']`. */
|
|
73
|
-
path: string[]
|
|
74
|
-
/** The column this group is on (the `groupBy` entry for this level). */
|
|
75
|
-
field: string
|
|
76
|
-
/** This group's key value. */
|
|
77
|
-
key: string
|
|
78
|
-
/** Zero-based depth (0 = top level). */
|
|
79
|
-
level: number
|
|
80
|
-
expanded: boolean
|
|
81
|
-
loading: boolean
|
|
82
|
-
/** Aggregate values keyed by column id, read from the group's response row. */
|
|
83
|
-
aggregates: Record<string, unknown>
|
|
84
|
-
/** The raw response row for this group (key + aggregates), for cell rendering. */
|
|
85
|
-
data: TData
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type ServerLeafRow<TData> = {
|
|
89
|
-
kind: 'leaf'
|
|
90
|
-
id: string
|
|
91
|
-
level: number
|
|
92
|
-
data: TData
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* A "load more" affordance emitted at the end of a group whose children are
|
|
97
|
-
* only partially loaded (intra-group paging). Trigger `loadMoreChildren(path)`
|
|
98
|
-
* to fetch the next block.
|
|
99
|
-
*/
|
|
100
|
-
export type ServerMoreRow = {
|
|
101
|
-
kind: 'more'
|
|
102
|
-
id: string
|
|
103
|
-
level: number
|
|
104
|
-
/** Path of the parent group whose children to load more of. */
|
|
105
|
-
path: string[]
|
|
106
|
-
/** How many children remain unloaded. */
|
|
107
|
-
remaining: number
|
|
108
|
-
loading: boolean
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* A subtotal / footer row emitted after an expanded group's children when
|
|
113
|
-
* `groupFooters` is on. Carries the group's aggregates a second time so a
|
|
114
|
-
* "Total" line sits under the detail.
|
|
115
|
-
*/
|
|
116
|
-
export type ServerFooterRow<TData> = {
|
|
117
|
-
kind: 'footer'
|
|
118
|
-
id: string
|
|
119
|
-
level: number
|
|
120
|
-
path: string[]
|
|
121
|
-
/** The group this footer totals (its key). */
|
|
122
|
-
key: string
|
|
123
|
-
aggregates: Record<string, unknown>
|
|
124
|
-
/** The group's response row (key + aggregates), so value columns show totals. */
|
|
125
|
-
data: TData
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** A placeholder row shown while a block of children is being fetched. */
|
|
129
|
-
export type ServerSkeletonRow = {
|
|
130
|
-
kind: 'skeleton'
|
|
131
|
-
id: string
|
|
132
|
-
level: number
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** A row in the flattened server-side group/tree display list. */
|
|
136
|
-
export type ServerDisplayRow<TData> =
|
|
137
|
-
| ServerGroupRow<TData>
|
|
138
|
-
| ServerLeafRow<TData>
|
|
139
|
-
| ServerMoreRow
|
|
140
|
-
| ServerFooterRow<TData>
|
|
141
|
-
| ServerSkeletonRow
|
|
142
|
-
|
|
143
|
-
export type ServerResult<TData> = {
|
|
144
|
-
rows: ReadonlyArray<TData>
|
|
145
|
-
/** Total row count after filtering (for the pager). */
|
|
146
|
-
rowCount: number
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export type ServerDataSource<TData> = {
|
|
150
|
-
getRows(request: ServerRequest): Promise<ServerResult<TData>>
|
|
151
|
-
/**
|
|
152
|
-
* Optional write side. Implement whichever your backend supports; the
|
|
153
|
-
* controller exposes matching `createRow` / `updateRow` / `deleteRow`
|
|
154
|
-
* methods that call through and then `refresh()` the current page.
|
|
155
|
-
* Calling a controller method whose source counterpart is missing throws.
|
|
156
|
-
*/
|
|
157
|
-
createRow?(input: Partial<TData>): Promise<TData>
|
|
158
|
-
updateRow?(id: string, patch: Partial<TData>): Promise<TData>
|
|
159
|
-
deleteRow?(id: string): Promise<void>
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export type ServerState<TData> = {
|
|
163
|
-
rows: ReadonlyArray<TData>
|
|
164
|
-
total: number
|
|
165
|
-
loading: boolean
|
|
166
|
-
/** True while a create / update / delete mutation is in flight. */
|
|
167
|
-
saving: boolean
|
|
168
|
-
error: unknown
|
|
169
|
-
pageIndex: number
|
|
170
|
-
pageSize: number
|
|
171
|
-
pageCount: number
|
|
172
|
-
sortModel: ServerSortModel
|
|
173
|
-
filterModel: ServerFilterModel
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export type ServerController<TData> = {
|
|
177
|
-
/** Re-fetch the current page (e.g. after a mutation). */
|
|
178
|
-
refresh(): void
|
|
179
|
-
setSort(sortModel: ServerSortModel): void
|
|
180
|
-
setFilter(filterModel: ServerFilterModel): void
|
|
181
|
-
setPage(pageIndex: number): void
|
|
182
|
-
setPageSize(pageSize: number): void
|
|
183
|
-
/**
|
|
184
|
-
* Create a row through the source, then refresh the current page. Resolves
|
|
185
|
-
* with the created row. Rejects if the source has no `createRow` (or if the
|
|
186
|
-
* create itself fails - the read state is left untouched on failure).
|
|
187
|
-
*/
|
|
188
|
-
createRow(input: Partial<TData>): Promise<TData>
|
|
189
|
-
/**
|
|
190
|
-
* Update a row by id through the source. Non-optimistic: refreshes the page.
|
|
191
|
-
* Optimistic (see `optimistic` + `getRowId` options): patches the local row
|
|
192
|
-
* immediately, then reconciles with the server result, rolling back on error.
|
|
193
|
-
*/
|
|
194
|
-
updateRow(id: string, patch: Partial<TData>): Promise<TData>
|
|
195
|
-
/**
|
|
196
|
-
* Delete a row by id through the source. Non-optimistic: refreshes the page.
|
|
197
|
-
* Optimistic: removes the local row immediately, restoring it on error.
|
|
198
|
-
*/
|
|
199
|
-
deleteRow(id: string): Promise<void>
|
|
200
|
-
getState(): ServerState<TData>
|
|
201
|
-
/** Stop accepting in-flight responses (call on unmount). */
|
|
202
|
-
dispose(): void
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export type ServerControllerOptions<TData> = {
|
|
206
|
-
pageSize?: number
|
|
207
|
-
/** Called whenever any of `rows` / `total` / `loading` / page changes. */
|
|
208
|
-
onChange: (state: ServerState<TData>) => void
|
|
209
|
-
/**
|
|
210
|
-
* Apply `updateRow` / `deleteRow` to the local rows immediately (before the
|
|
211
|
-
* server confirms) and roll back on error - so edits feel instant and no
|
|
212
|
-
* refetch is needed. Requires `getRowId` to locate rows; ignored without it.
|
|
213
|
-
* A subsequent `refresh()` reconciles ordering/filtering. Default false.
|
|
214
|
-
*/
|
|
215
|
-
optimistic?: boolean
|
|
216
|
-
/** Resolve a row's stable id, so optimistic update/delete can find it in `rows`. */
|
|
217
|
-
getRowId?: (row: TData) => string
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function createServerDataSource<TData>(
|
|
221
|
-
source: ServerDataSource<TData>,
|
|
222
|
-
options: ServerControllerOptions<TData>,
|
|
223
|
-
): ServerController<TData> {
|
|
224
|
-
const state: ServerState<TData> = {
|
|
225
|
-
rows: [],
|
|
226
|
-
total: 0,
|
|
227
|
-
loading: false,
|
|
228
|
-
saving: false,
|
|
229
|
-
error: null,
|
|
230
|
-
pageIndex: 0,
|
|
231
|
-
pageSize: options.pageSize ?? 50,
|
|
232
|
-
pageCount: 1,
|
|
233
|
-
sortModel: [],
|
|
234
|
-
filterModel: {},
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Monotonic request id: only the latest fetch is allowed to land, so a slow
|
|
238
|
-
// response for an old sort/filter can't clobber a newer one.
|
|
239
|
-
let requestSeq = 0
|
|
240
|
-
let disposed = false
|
|
241
|
-
|
|
242
|
-
const emit = () => {
|
|
243
|
-
state.pageCount = Math.max(1, Math.ceil(state.total / state.pageSize))
|
|
244
|
-
options.onChange({ ...state })
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async function fetchPage() {
|
|
248
|
-
if (disposed) return
|
|
249
|
-
const id = ++requestSeq
|
|
250
|
-
state.loading = true
|
|
251
|
-
state.error = null
|
|
252
|
-
emit()
|
|
253
|
-
const startRow = state.pageIndex * state.pageSize
|
|
254
|
-
try {
|
|
255
|
-
const result = await source.getRows({
|
|
256
|
-
startRow,
|
|
257
|
-
endRow: startRow + state.pageSize,
|
|
258
|
-
pageIndex: state.pageIndex,
|
|
259
|
-
pageSize: state.pageSize,
|
|
260
|
-
sortModel: state.sortModel,
|
|
261
|
-
filterModel: state.filterModel,
|
|
262
|
-
// Flat mode: no grouping. Group mode lives in createServerGroupModel.
|
|
263
|
-
groupBy: [],
|
|
264
|
-
groupKeys: [],
|
|
265
|
-
aggregations: [],
|
|
266
|
-
})
|
|
267
|
-
if (disposed || id !== requestSeq) return // stale
|
|
268
|
-
state.rows = result.rows
|
|
269
|
-
state.total = result.rowCount
|
|
270
|
-
state.loading = false
|
|
271
|
-
emit()
|
|
272
|
-
} catch (err) {
|
|
273
|
-
if (disposed || id !== requestSeq) return
|
|
274
|
-
state.rows = []
|
|
275
|
-
state.error = err
|
|
276
|
-
state.loading = false
|
|
277
|
-
emit()
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Shared mutation lifecycle: flip `saving`, run the write, refresh the
|
|
282
|
-
// current page on success, and always clear `saving`. A missing source
|
|
283
|
-
// method (or a disposed controller) rejects before anything is emitted.
|
|
284
|
-
function mutate<T>(name: string, thunk: (() => Promise<T>) | null): Promise<T> {
|
|
285
|
-
if (disposed) {
|
|
286
|
-
return Promise.reject(new Error('createServerDataSource: controller is disposed'))
|
|
287
|
-
}
|
|
288
|
-
if (!thunk) {
|
|
289
|
-
return Promise.reject(
|
|
290
|
-
new Error(`createServerDataSource: the datasource does not implement ${name}()`),
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
state.saving = true
|
|
294
|
-
emit()
|
|
295
|
-
return (async () => {
|
|
296
|
-
try {
|
|
297
|
-
const result = await thunk()
|
|
298
|
-
await fetchPage()
|
|
299
|
-
return result
|
|
300
|
-
} finally {
|
|
301
|
-
state.saving = false
|
|
302
|
-
emit()
|
|
303
|
-
}
|
|
304
|
-
})()
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const optimistic = !!options.optimistic && !!options.getRowId
|
|
308
|
-
const getRowId = options.getRowId
|
|
309
|
-
|
|
310
|
-
// Optimistic update: patch the local row, reconcile with the server result,
|
|
311
|
-
// roll back on error. Falls back to the plain refresh path when the row
|
|
312
|
-
// isn't on the current page (nothing local to update).
|
|
313
|
-
async function optimisticUpdate(
|
|
314
|
-
id: string,
|
|
315
|
-
patch: Partial<TData>,
|
|
316
|
-
fn: (id: string, patch: Partial<TData>) => Promise<TData>,
|
|
317
|
-
): Promise<TData> {
|
|
318
|
-
if (disposed) throw new Error('createServerDataSource: controller is disposed')
|
|
319
|
-
const prevRows = state.rows
|
|
320
|
-
const idx = prevRows.findIndex((r) => getRowId!(r) === id)
|
|
321
|
-
if (idx < 0) return mutate('updateRow', () => fn(id, patch))
|
|
322
|
-
|
|
323
|
-
state.rows = [...prevRows.slice(0, idx), { ...prevRows[idx]!, ...patch }, ...prevRows.slice(idx + 1)]
|
|
324
|
-
state.saving = true
|
|
325
|
-
emit()
|
|
326
|
-
try {
|
|
327
|
-
const result = await fn(id, patch)
|
|
328
|
-
state.rows = state.rows.map((r) => (getRowId!(r) === id ? result : r))
|
|
329
|
-
return result
|
|
330
|
-
} catch (err) {
|
|
331
|
-
state.rows = prevRows
|
|
332
|
-
throw err
|
|
333
|
-
} finally {
|
|
334
|
-
state.saving = false
|
|
335
|
-
emit()
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Optimistic delete: drop the local row + decrement total, restore on error.
|
|
340
|
-
async function optimisticDelete(
|
|
341
|
-
id: string,
|
|
342
|
-
fn: (id: string) => Promise<void>,
|
|
343
|
-
): Promise<void> {
|
|
344
|
-
if (disposed) throw new Error('createServerDataSource: controller is disposed')
|
|
345
|
-
const prevRows = state.rows
|
|
346
|
-
const prevTotal = state.total
|
|
347
|
-
const next = prevRows.filter((r) => getRowId!(r) !== id)
|
|
348
|
-
if (next.length === prevRows.length) return mutate('deleteRow', () => fn(id))
|
|
349
|
-
|
|
350
|
-
state.rows = next
|
|
351
|
-
state.total = Math.max(0, prevTotal - (prevRows.length - next.length))
|
|
352
|
-
state.saving = true
|
|
353
|
-
emit()
|
|
354
|
-
try {
|
|
355
|
-
await fn(id)
|
|
356
|
-
} catch (err) {
|
|
357
|
-
state.rows = prevRows
|
|
358
|
-
state.total = prevTotal
|
|
359
|
-
throw err
|
|
360
|
-
} finally {
|
|
361
|
-
state.saving = false
|
|
362
|
-
emit()
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return {
|
|
367
|
-
refresh: fetchPage,
|
|
368
|
-
createRow: (input) =>
|
|
369
|
-
mutate('createRow', source.createRow ? () => source.createRow!(input) : null),
|
|
370
|
-
updateRow: (id, patch) => {
|
|
371
|
-
if (!source.updateRow) return mutate('updateRow', null)
|
|
372
|
-
const fn = source.updateRow
|
|
373
|
-
return optimistic ? optimisticUpdate(id, patch, fn) : mutate('updateRow', () => fn(id, patch))
|
|
374
|
-
},
|
|
375
|
-
deleteRow: (id) => {
|
|
376
|
-
if (!source.deleteRow) return mutate('deleteRow', null)
|
|
377
|
-
const fn = source.deleteRow
|
|
378
|
-
return optimistic ? optimisticDelete(id, fn) : mutate('deleteRow', () => fn(id))
|
|
379
|
-
},
|
|
380
|
-
setSort(sortModel) {
|
|
381
|
-
state.sortModel = sortModel
|
|
382
|
-
state.pageIndex = 0
|
|
383
|
-
void fetchPage()
|
|
384
|
-
},
|
|
385
|
-
setFilter(filterModel) {
|
|
386
|
-
state.filterModel = filterModel
|
|
387
|
-
state.pageIndex = 0
|
|
388
|
-
void fetchPage()
|
|
389
|
-
},
|
|
390
|
-
setPage(pageIndex) {
|
|
391
|
-
const clamped = Math.max(0, pageIndex)
|
|
392
|
-
if (clamped === state.pageIndex) return
|
|
393
|
-
state.pageIndex = clamped
|
|
394
|
-
void fetchPage()
|
|
395
|
-
},
|
|
396
|
-
setPageSize(pageSize) {
|
|
397
|
-
state.pageSize = Math.max(1, pageSize)
|
|
398
|
-
state.pageIndex = 0
|
|
399
|
-
void fetchPage()
|
|
400
|
-
},
|
|
401
|
-
getState: () => ({ ...state }),
|
|
402
|
-
dispose() {
|
|
403
|
-
disposed = true
|
|
404
|
-
// An in-flight fetch's resolution short-circuits on `disposed`, so it
|
|
405
|
-
// never clears `loading`. Clear it here (and emit) so a disposed
|
|
406
|
-
// controller doesn't report a permanent loading state.
|
|
407
|
-
if (state.loading) {
|
|
408
|
-
state.loading = false
|
|
409
|
-
emit()
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
}
|
|
413
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Server-Side Row Model (SSRM) controller. A single, documented datasource
|
|
3
|
+
* contract for grids whose data lives on the server - the "I have a million
|
|
4
|
+
* rows in a database" case. The consumer implements ONE async `getRows`
|
|
5
|
+
* function; this controller owns the request lifecycle (sort, filter, page),
|
|
6
|
+
* de-dupes/races, and pushes results back through `onChange`.
|
|
7
|
+
*
|
|
8
|
+
* It's headless and framework-agnostic on purpose: wire `setSort` /
|
|
9
|
+
* `setFilter` / `setPage` to the grid's controlled callbacks, and render the
|
|
10
|
+
* grid from the `{ rows, total, loading }` it hands you. See the demo.
|
|
11
|
+
*
|
|
12
|
+
* The write side (`createRow` / `updateRow` / `deleteRow`) is optional: a
|
|
13
|
+
* source that only implements `getRows` stays a pure read model, and the
|
|
14
|
+
* matching controller methods throw a clear error if called. Mutations are
|
|
15
|
+
* non-optimistic for now - the grid reflects a change only after the
|
|
16
|
+
* follow-up re-fetch of the current page lands.
|
|
17
|
+
*/
|
|
18
|
+
export type ServerSortModel = Array<{ id: string; desc: boolean }>
|
|
19
|
+
|
|
20
|
+
export type ServerFilterModel = {
|
|
21
|
+
/** Free-text global search. */
|
|
22
|
+
global?: string
|
|
23
|
+
/**
|
|
24
|
+
* Per-column filters, keyed by column id. `value` (+ `valueTo`) carry the
|
|
25
|
+
* operator-style filter; `selectedValues` carries a facet/checklist
|
|
26
|
+
* selection (set-filter). Either or both may be present.
|
|
27
|
+
*/
|
|
28
|
+
columns?: Record<
|
|
29
|
+
string,
|
|
30
|
+
{ operator: string; value: string; valueTo?: string; selectedValues?: string[] }
|
|
31
|
+
>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A value column to roll up per group. */
|
|
35
|
+
export type ServerAggregation = { col: string; fn: 'sum' | 'avg' | 'min' | 'max' | 'count' }
|
|
36
|
+
|
|
37
|
+
export type ServerRequest = {
|
|
38
|
+
/** Zero-based index of the first row wanted (inclusive). */
|
|
39
|
+
startRow: number
|
|
40
|
+
/** Index just past the last row wanted (exclusive). */
|
|
41
|
+
endRow: number
|
|
42
|
+
pageIndex: number
|
|
43
|
+
pageSize: number
|
|
44
|
+
sortModel: ServerSortModel
|
|
45
|
+
filterModel: ServerFilterModel
|
|
46
|
+
/**
|
|
47
|
+
* Server-side grouping / tree: the columns being grouped on, outer to inner.
|
|
48
|
+
* Omitted / empty for a flat request.
|
|
49
|
+
*/
|
|
50
|
+
groupBy?: string[]
|
|
51
|
+
/**
|
|
52
|
+
* The path of group keys the grid is expanding, e.g. `['Germany', 'Berlin']`.
|
|
53
|
+
* Empty (`[]`) asks for the top level. When `groupKeys.length < groupBy.length`
|
|
54
|
+
* the server returns **group rows** (one per distinct key at this level,
|
|
55
|
+
* carrying the group key + aggregates); when they are equal it returns the
|
|
56
|
+
* **leaf rows** under that path.
|
|
57
|
+
*/
|
|
58
|
+
groupKeys?: string[]
|
|
59
|
+
/** Value columns to aggregate per group. */
|
|
60
|
+
aggregations?: ServerAggregation[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A group row in the server-side group/tree model - one distinct key at a
|
|
65
|
+
* level, with its rolled-up aggregates. The grid renders it with an expander;
|
|
66
|
+
* expanding it fetches its children through the same `getRows`.
|
|
67
|
+
*/
|
|
68
|
+
export type ServerGroupRow<TData> = {
|
|
69
|
+
kind: 'group'
|
|
70
|
+
/** Stable id (the group path). */
|
|
71
|
+
id: string
|
|
72
|
+
/** Group keys from the root to this node, e.g. `['Germany', 'Berlin']`. */
|
|
73
|
+
path: string[]
|
|
74
|
+
/** The column this group is on (the `groupBy` entry for this level). */
|
|
75
|
+
field: string
|
|
76
|
+
/** This group's key value. */
|
|
77
|
+
key: string
|
|
78
|
+
/** Zero-based depth (0 = top level). */
|
|
79
|
+
level: number
|
|
80
|
+
expanded: boolean
|
|
81
|
+
loading: boolean
|
|
82
|
+
/** Aggregate values keyed by column id, read from the group's response row. */
|
|
83
|
+
aggregates: Record<string, unknown>
|
|
84
|
+
/** The raw response row for this group (key + aggregates), for cell rendering. */
|
|
85
|
+
data: TData
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ServerLeafRow<TData> = {
|
|
89
|
+
kind: 'leaf'
|
|
90
|
+
id: string
|
|
91
|
+
level: number
|
|
92
|
+
data: TData
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A "load more" affordance emitted at the end of a group whose children are
|
|
97
|
+
* only partially loaded (intra-group paging). Trigger `loadMoreChildren(path)`
|
|
98
|
+
* to fetch the next block.
|
|
99
|
+
*/
|
|
100
|
+
export type ServerMoreRow = {
|
|
101
|
+
kind: 'more'
|
|
102
|
+
id: string
|
|
103
|
+
level: number
|
|
104
|
+
/** Path of the parent group whose children to load more of. */
|
|
105
|
+
path: string[]
|
|
106
|
+
/** How many children remain unloaded. */
|
|
107
|
+
remaining: number
|
|
108
|
+
loading: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A subtotal / footer row emitted after an expanded group's children when
|
|
113
|
+
* `groupFooters` is on. Carries the group's aggregates a second time so a
|
|
114
|
+
* "Total" line sits under the detail.
|
|
115
|
+
*/
|
|
116
|
+
export type ServerFooterRow<TData> = {
|
|
117
|
+
kind: 'footer'
|
|
118
|
+
id: string
|
|
119
|
+
level: number
|
|
120
|
+
path: string[]
|
|
121
|
+
/** The group this footer totals (its key). */
|
|
122
|
+
key: string
|
|
123
|
+
aggregates: Record<string, unknown>
|
|
124
|
+
/** The group's response row (key + aggregates), so value columns show totals. */
|
|
125
|
+
data: TData
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A placeholder row shown while a block of children is being fetched. */
|
|
129
|
+
export type ServerSkeletonRow = {
|
|
130
|
+
kind: 'skeleton'
|
|
131
|
+
id: string
|
|
132
|
+
level: number
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A row in the flattened server-side group/tree display list. */
|
|
136
|
+
export type ServerDisplayRow<TData> =
|
|
137
|
+
| ServerGroupRow<TData>
|
|
138
|
+
| ServerLeafRow<TData>
|
|
139
|
+
| ServerMoreRow
|
|
140
|
+
| ServerFooterRow<TData>
|
|
141
|
+
| ServerSkeletonRow
|
|
142
|
+
|
|
143
|
+
export type ServerResult<TData> = {
|
|
144
|
+
rows: ReadonlyArray<TData>
|
|
145
|
+
/** Total row count after filtering (for the pager). */
|
|
146
|
+
rowCount: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type ServerDataSource<TData> = {
|
|
150
|
+
getRows(request: ServerRequest): Promise<ServerResult<TData>>
|
|
151
|
+
/**
|
|
152
|
+
* Optional write side. Implement whichever your backend supports; the
|
|
153
|
+
* controller exposes matching `createRow` / `updateRow` / `deleteRow`
|
|
154
|
+
* methods that call through and then `refresh()` the current page.
|
|
155
|
+
* Calling a controller method whose source counterpart is missing throws.
|
|
156
|
+
*/
|
|
157
|
+
createRow?(input: Partial<TData>): Promise<TData>
|
|
158
|
+
updateRow?(id: string, patch: Partial<TData>): Promise<TData>
|
|
159
|
+
deleteRow?(id: string): Promise<void>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type ServerState<TData> = {
|
|
163
|
+
rows: ReadonlyArray<TData>
|
|
164
|
+
total: number
|
|
165
|
+
loading: boolean
|
|
166
|
+
/** True while a create / update / delete mutation is in flight. */
|
|
167
|
+
saving: boolean
|
|
168
|
+
error: unknown
|
|
169
|
+
pageIndex: number
|
|
170
|
+
pageSize: number
|
|
171
|
+
pageCount: number
|
|
172
|
+
sortModel: ServerSortModel
|
|
173
|
+
filterModel: ServerFilterModel
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type ServerController<TData> = {
|
|
177
|
+
/** Re-fetch the current page (e.g. after a mutation). */
|
|
178
|
+
refresh(): void
|
|
179
|
+
setSort(sortModel: ServerSortModel): void
|
|
180
|
+
setFilter(filterModel: ServerFilterModel): void
|
|
181
|
+
setPage(pageIndex: number): void
|
|
182
|
+
setPageSize(pageSize: number): void
|
|
183
|
+
/**
|
|
184
|
+
* Create a row through the source, then refresh the current page. Resolves
|
|
185
|
+
* with the created row. Rejects if the source has no `createRow` (or if the
|
|
186
|
+
* create itself fails - the read state is left untouched on failure).
|
|
187
|
+
*/
|
|
188
|
+
createRow(input: Partial<TData>): Promise<TData>
|
|
189
|
+
/**
|
|
190
|
+
* Update a row by id through the source. Non-optimistic: refreshes the page.
|
|
191
|
+
* Optimistic (see `optimistic` + `getRowId` options): patches the local row
|
|
192
|
+
* immediately, then reconciles with the server result, rolling back on error.
|
|
193
|
+
*/
|
|
194
|
+
updateRow(id: string, patch: Partial<TData>): Promise<TData>
|
|
195
|
+
/**
|
|
196
|
+
* Delete a row by id through the source. Non-optimistic: refreshes the page.
|
|
197
|
+
* Optimistic: removes the local row immediately, restoring it on error.
|
|
198
|
+
*/
|
|
199
|
+
deleteRow(id: string): Promise<void>
|
|
200
|
+
getState(): ServerState<TData>
|
|
201
|
+
/** Stop accepting in-flight responses (call on unmount). */
|
|
202
|
+
dispose(): void
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type ServerControllerOptions<TData> = {
|
|
206
|
+
pageSize?: number
|
|
207
|
+
/** Called whenever any of `rows` / `total` / `loading` / page changes. */
|
|
208
|
+
onChange: (state: ServerState<TData>) => void
|
|
209
|
+
/**
|
|
210
|
+
* Apply `updateRow` / `deleteRow` to the local rows immediately (before the
|
|
211
|
+
* server confirms) and roll back on error - so edits feel instant and no
|
|
212
|
+
* refetch is needed. Requires `getRowId` to locate rows; ignored without it.
|
|
213
|
+
* A subsequent `refresh()` reconciles ordering/filtering. Default false.
|
|
214
|
+
*/
|
|
215
|
+
optimistic?: boolean
|
|
216
|
+
/** Resolve a row's stable id, so optimistic update/delete can find it in `rows`. */
|
|
217
|
+
getRowId?: (row: TData) => string
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function createServerDataSource<TData>(
|
|
221
|
+
source: ServerDataSource<TData>,
|
|
222
|
+
options: ServerControllerOptions<TData>,
|
|
223
|
+
): ServerController<TData> {
|
|
224
|
+
const state: ServerState<TData> = {
|
|
225
|
+
rows: [],
|
|
226
|
+
total: 0,
|
|
227
|
+
loading: false,
|
|
228
|
+
saving: false,
|
|
229
|
+
error: null,
|
|
230
|
+
pageIndex: 0,
|
|
231
|
+
pageSize: options.pageSize ?? 50,
|
|
232
|
+
pageCount: 1,
|
|
233
|
+
sortModel: [],
|
|
234
|
+
filterModel: {},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Monotonic request id: only the latest fetch is allowed to land, so a slow
|
|
238
|
+
// response for an old sort/filter can't clobber a newer one.
|
|
239
|
+
let requestSeq = 0
|
|
240
|
+
let disposed = false
|
|
241
|
+
|
|
242
|
+
const emit = () => {
|
|
243
|
+
state.pageCount = Math.max(1, Math.ceil(state.total / state.pageSize))
|
|
244
|
+
options.onChange({ ...state })
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function fetchPage() {
|
|
248
|
+
if (disposed) return
|
|
249
|
+
const id = ++requestSeq
|
|
250
|
+
state.loading = true
|
|
251
|
+
state.error = null
|
|
252
|
+
emit()
|
|
253
|
+
const startRow = state.pageIndex * state.pageSize
|
|
254
|
+
try {
|
|
255
|
+
const result = await source.getRows({
|
|
256
|
+
startRow,
|
|
257
|
+
endRow: startRow + state.pageSize,
|
|
258
|
+
pageIndex: state.pageIndex,
|
|
259
|
+
pageSize: state.pageSize,
|
|
260
|
+
sortModel: state.sortModel,
|
|
261
|
+
filterModel: state.filterModel,
|
|
262
|
+
// Flat mode: no grouping. Group mode lives in createServerGroupModel.
|
|
263
|
+
groupBy: [],
|
|
264
|
+
groupKeys: [],
|
|
265
|
+
aggregations: [],
|
|
266
|
+
})
|
|
267
|
+
if (disposed || id !== requestSeq) return // stale
|
|
268
|
+
state.rows = result.rows
|
|
269
|
+
state.total = result.rowCount
|
|
270
|
+
state.loading = false
|
|
271
|
+
emit()
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (disposed || id !== requestSeq) return
|
|
274
|
+
state.rows = []
|
|
275
|
+
state.error = err
|
|
276
|
+
state.loading = false
|
|
277
|
+
emit()
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Shared mutation lifecycle: flip `saving`, run the write, refresh the
|
|
282
|
+
// current page on success, and always clear `saving`. A missing source
|
|
283
|
+
// method (or a disposed controller) rejects before anything is emitted.
|
|
284
|
+
function mutate<T>(name: string, thunk: (() => Promise<T>) | null): Promise<T> {
|
|
285
|
+
if (disposed) {
|
|
286
|
+
return Promise.reject(new Error('createServerDataSource: controller is disposed'))
|
|
287
|
+
}
|
|
288
|
+
if (!thunk) {
|
|
289
|
+
return Promise.reject(
|
|
290
|
+
new Error(`createServerDataSource: the datasource does not implement ${name}()`),
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
state.saving = true
|
|
294
|
+
emit()
|
|
295
|
+
return (async () => {
|
|
296
|
+
try {
|
|
297
|
+
const result = await thunk()
|
|
298
|
+
await fetchPage()
|
|
299
|
+
return result
|
|
300
|
+
} finally {
|
|
301
|
+
state.saving = false
|
|
302
|
+
emit()
|
|
303
|
+
}
|
|
304
|
+
})()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const optimistic = !!options.optimistic && !!options.getRowId
|
|
308
|
+
const getRowId = options.getRowId
|
|
309
|
+
|
|
310
|
+
// Optimistic update: patch the local row, reconcile with the server result,
|
|
311
|
+
// roll back on error. Falls back to the plain refresh path when the row
|
|
312
|
+
// isn't on the current page (nothing local to update).
|
|
313
|
+
async function optimisticUpdate(
|
|
314
|
+
id: string,
|
|
315
|
+
patch: Partial<TData>,
|
|
316
|
+
fn: (id: string, patch: Partial<TData>) => Promise<TData>,
|
|
317
|
+
): Promise<TData> {
|
|
318
|
+
if (disposed) throw new Error('createServerDataSource: controller is disposed')
|
|
319
|
+
const prevRows = state.rows
|
|
320
|
+
const idx = prevRows.findIndex((r) => getRowId!(r) === id)
|
|
321
|
+
if (idx < 0) return mutate('updateRow', () => fn(id, patch))
|
|
322
|
+
|
|
323
|
+
state.rows = [...prevRows.slice(0, idx), { ...prevRows[idx]!, ...patch }, ...prevRows.slice(idx + 1)]
|
|
324
|
+
state.saving = true
|
|
325
|
+
emit()
|
|
326
|
+
try {
|
|
327
|
+
const result = await fn(id, patch)
|
|
328
|
+
state.rows = state.rows.map((r) => (getRowId!(r) === id ? result : r))
|
|
329
|
+
return result
|
|
330
|
+
} catch (err) {
|
|
331
|
+
state.rows = prevRows
|
|
332
|
+
throw err
|
|
333
|
+
} finally {
|
|
334
|
+
state.saving = false
|
|
335
|
+
emit()
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Optimistic delete: drop the local row + decrement total, restore on error.
|
|
340
|
+
async function optimisticDelete(
|
|
341
|
+
id: string,
|
|
342
|
+
fn: (id: string) => Promise<void>,
|
|
343
|
+
): Promise<void> {
|
|
344
|
+
if (disposed) throw new Error('createServerDataSource: controller is disposed')
|
|
345
|
+
const prevRows = state.rows
|
|
346
|
+
const prevTotal = state.total
|
|
347
|
+
const next = prevRows.filter((r) => getRowId!(r) !== id)
|
|
348
|
+
if (next.length === prevRows.length) return mutate('deleteRow', () => fn(id))
|
|
349
|
+
|
|
350
|
+
state.rows = next
|
|
351
|
+
state.total = Math.max(0, prevTotal - (prevRows.length - next.length))
|
|
352
|
+
state.saving = true
|
|
353
|
+
emit()
|
|
354
|
+
try {
|
|
355
|
+
await fn(id)
|
|
356
|
+
} catch (err) {
|
|
357
|
+
state.rows = prevRows
|
|
358
|
+
state.total = prevTotal
|
|
359
|
+
throw err
|
|
360
|
+
} finally {
|
|
361
|
+
state.saving = false
|
|
362
|
+
emit()
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
refresh: fetchPage,
|
|
368
|
+
createRow: (input) =>
|
|
369
|
+
mutate('createRow', source.createRow ? () => source.createRow!(input) : null),
|
|
370
|
+
updateRow: (id, patch) => {
|
|
371
|
+
if (!source.updateRow) return mutate('updateRow', null)
|
|
372
|
+
const fn = source.updateRow
|
|
373
|
+
return optimistic ? optimisticUpdate(id, patch, fn) : mutate('updateRow', () => fn(id, patch))
|
|
374
|
+
},
|
|
375
|
+
deleteRow: (id) => {
|
|
376
|
+
if (!source.deleteRow) return mutate('deleteRow', null)
|
|
377
|
+
const fn = source.deleteRow
|
|
378
|
+
return optimistic ? optimisticDelete(id, fn) : mutate('deleteRow', () => fn(id))
|
|
379
|
+
},
|
|
380
|
+
setSort(sortModel) {
|
|
381
|
+
state.sortModel = sortModel
|
|
382
|
+
state.pageIndex = 0
|
|
383
|
+
void fetchPage()
|
|
384
|
+
},
|
|
385
|
+
setFilter(filterModel) {
|
|
386
|
+
state.filterModel = filterModel
|
|
387
|
+
state.pageIndex = 0
|
|
388
|
+
void fetchPage()
|
|
389
|
+
},
|
|
390
|
+
setPage(pageIndex) {
|
|
391
|
+
const clamped = Math.max(0, pageIndex)
|
|
392
|
+
if (clamped === state.pageIndex) return
|
|
393
|
+
state.pageIndex = clamped
|
|
394
|
+
void fetchPage()
|
|
395
|
+
},
|
|
396
|
+
setPageSize(pageSize) {
|
|
397
|
+
state.pageSize = Math.max(1, pageSize)
|
|
398
|
+
state.pageIndex = 0
|
|
399
|
+
void fetchPage()
|
|
400
|
+
},
|
|
401
|
+
getState: () => ({ ...state }),
|
|
402
|
+
dispose() {
|
|
403
|
+
disposed = true
|
|
404
|
+
// An in-flight fetch's resolution short-circuits on `disposed`, so it
|
|
405
|
+
// never clears `loading`. Clear it here (and emit) so a disposed
|
|
406
|
+
// controller doesn't report a permanent loading state.
|
|
407
|
+
if (state.loading) {
|
|
408
|
+
state.loading = false
|
|
409
|
+
emit()
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
}
|