@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/clipboard.ts
CHANGED
|
@@ -2,122 +2,20 @@
|
|
|
2
2
|
// that read/write controller state via the `ctx` handle; the reactive core
|
|
3
3
|
// ($state/$derived/$effect) stays in the controller.
|
|
4
4
|
import {
|
|
5
|
-
applyExcelFilter,
|
|
6
|
-
normalizeForFilter,
|
|
7
|
-
createColumnVirtualizer,
|
|
8
|
-
createCoreRowModel,
|
|
9
|
-
createExpandedRowModel,
|
|
10
|
-
createFilteredRowModel,
|
|
11
|
-
createGroupedRowModel,
|
|
12
|
-
createPaginatedRowModel,
|
|
13
|
-
createSvelteVirtualizer,
|
|
14
|
-
createSortedRowModel,
|
|
15
|
-
createSvGrid,
|
|
16
|
-
filterFns,
|
|
17
|
-
getGridCellA11yProps,
|
|
18
|
-
getGridCellDomId,
|
|
19
|
-
getGridHeaderA11yProps,
|
|
20
|
-
getGridRootA11yProps,
|
|
21
|
-
getGridRowA11yProps,
|
|
22
5
|
parseEditorValue,
|
|
23
|
-
normalizeEditorOptions,
|
|
24
|
-
sortFns,
|
|
25
|
-
tableFeatures,
|
|
26
|
-
rowSortingFeature,
|
|
27
|
-
columnFilteringFeature,
|
|
28
|
-
columnGroupingFeature,
|
|
29
|
-
type CellContext,
|
|
30
|
-
type EditorContext,
|
|
31
|
-
type CellEditorOption,
|
|
32
6
|
type CellEditorType,
|
|
33
|
-
type CellFormatter,
|
|
34
|
-
type CellFormatConfig,
|
|
35
|
-
type Column,
|
|
36
|
-
type ColumnDef,
|
|
37
|
-
type Row,
|
|
38
7
|
type RowData,
|
|
39
|
-
type SvGridApi,
|
|
40
8
|
type TableFeatures,
|
|
41
9
|
} from "./index";
|
|
42
10
|
import "./sv-grid-scrollbar";
|
|
43
|
-
import type { Snippet } from "svelte";
|
|
44
|
-
import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
|
|
45
|
-
import {
|
|
46
|
-
formatNumericWithConfig,
|
|
47
|
-
getDateFormatter,
|
|
48
|
-
resolveDatePattern,
|
|
49
|
-
} from "./cell-formatting";
|
|
50
|
-
import {
|
|
51
|
-
RenderSnippetConfig,
|
|
52
|
-
RenderComponentConfig,
|
|
53
|
-
} from "./render-component";
|
|
54
11
|
import { buildFillPattern } from "./fill-patterns";
|
|
55
|
-
import { buildSparkline, toSparklineValues } from "./sparkline";
|
|
56
|
-
import {
|
|
57
|
-
resolveCellFormat,
|
|
58
|
-
computeColumnStat,
|
|
59
|
-
formatsNeedingStats,
|
|
60
|
-
type ColumnStat,
|
|
61
|
-
type ResolvedCellFormat,
|
|
62
|
-
} from "./conditional-formatting";
|
|
63
|
-
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
64
|
-
import type {
|
|
65
|
-
Props,
|
|
66
|
-
SelectionPoint,
|
|
67
|
-
SelectionRange,
|
|
68
|
-
CellEditState,
|
|
69
|
-
FilterOperator,
|
|
70
|
-
FilterOption,
|
|
71
|
-
MenuPosition,
|
|
72
|
-
} from "./SvGrid.types";
|
|
73
12
|
import {
|
|
74
|
-
cfTextStyle,
|
|
75
|
-
fmtStat,
|
|
76
13
|
getCellKey,
|
|
77
|
-
resolveClassList,
|
|
78
|
-
toDateInputValue,
|
|
79
|
-
toDateTimeLocalInputValue,
|
|
80
|
-
getEditableInputValue,
|
|
81
|
-
getEditorInputType,
|
|
82
|
-
toValueArray,
|
|
83
|
-
getOptionLabel,
|
|
84
|
-
getOptionColor,
|
|
85
|
-
colorfulChipStyle,
|
|
86
|
-
getEditorClass,
|
|
87
|
-
asDate,
|
|
88
|
-
clampMenuX,
|
|
89
|
-
cssEscape,
|
|
90
|
-
rawToNumber,
|
|
91
|
-
formatFacetNumber,
|
|
92
|
-
formatFacetDate,
|
|
93
14
|
} from "./SvGrid.helpers";
|
|
94
|
-
import {
|
|
95
|
-
filterOperatorOptions,
|
|
96
|
-
fallbackOperatorOption,
|
|
97
|
-
TEXT_OPERATORS,
|
|
98
|
-
NUMBER_OPERATORS,
|
|
99
|
-
DATE_OPERATORS,
|
|
100
|
-
CHECKBOX_OPERATORS,
|
|
101
|
-
operatorOption,
|
|
102
|
-
operatorsForColumn,
|
|
103
|
-
defaultOperatorFor,
|
|
104
|
-
operatorLabelFor,
|
|
105
|
-
} from "./filter-operators";
|
|
106
|
-
import {
|
|
107
|
-
type FacetBucket,
|
|
108
|
-
isBucketableColumn,
|
|
109
|
-
buildBuckets,
|
|
110
|
-
isInBucket,
|
|
111
|
-
} from "./facet-buckets";
|
|
112
15
|
import {
|
|
113
16
|
getColumnBaseValue,
|
|
114
17
|
isGroupRow,
|
|
115
18
|
toolPanelHeaderLabel,
|
|
116
|
-
formatSummaryNumeric,
|
|
117
|
-
getColumnAlign,
|
|
118
|
-
getPinnedCellValue,
|
|
119
|
-
getColumnAccessorValue,
|
|
120
|
-
columnDefMatchesId,
|
|
121
19
|
} from "./cell-values";
|
|
122
20
|
|
|
123
21
|
export function createClipboard<
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
broadcastChannelTransport,
|
|
4
|
-
createCollaboration,
|
|
5
|
-
type CollabMessage,
|
|
6
|
-
type CollabTransport,
|
|
7
|
-
} from './collaboration'
|
|
8
|
-
|
|
9
|
-
/** An in-memory bus so two controllers can talk in a test. */
|
|
10
|
-
function makeBus() {
|
|
11
|
-
const handlers = new Set<(m: CollabMessage) => void>()
|
|
12
|
-
const transportFor = (): CollabTransport => ({
|
|
13
|
-
post: (msg) => {
|
|
14
|
-
// deliver async-ish to every subscriber except none (real channels echo
|
|
15
|
-
// to other tabs, not self; our controllers ignore their own id anyway).
|
|
16
|
-
for (const h of [...handlers]) h(msg)
|
|
17
|
-
},
|
|
18
|
-
subscribe: (h) => {
|
|
19
|
-
handlers.add(h)
|
|
20
|
-
return () => handlers.delete(h)
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
return { transportFor }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const userA = { id: 'a', name: 'Ada', color: '#f00' }
|
|
27
|
-
const userB = { id: 'b', name: 'Bob', color: '#00f' }
|
|
28
|
-
|
|
29
|
-
describe('createCollaboration', () => {
|
|
30
|
-
it('peers see each other after hello', () => {
|
|
31
|
-
const bus = makeBus()
|
|
32
|
-
const a = createCollaboration({ user: userA, transport: bus.transportFor() })
|
|
33
|
-
const b = createCollaboration({ user: userB, transport: bus.transportFor() })
|
|
34
|
-
// A is announced; B's hello reaches A; A replies presence.
|
|
35
|
-
expect(a.peers().map((p) => p.id)).toEqual(['b'])
|
|
36
|
-
expect(b.peers().map((p) => p.id)).toEqual(['a'])
|
|
37
|
-
a.dispose()
|
|
38
|
-
b.dispose()
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('closes the transport channel on dispose (#82)', () => {
|
|
42
|
-
// The controller now forwards dispose to the transport...
|
|
43
|
-
const disposeSpy = vi.fn()
|
|
44
|
-
const collab = createCollaboration({
|
|
45
|
-
user: userA,
|
|
46
|
-
transport: { post: vi.fn(), subscribe: () => () => {}, dispose: disposeSpy },
|
|
47
|
-
})
|
|
48
|
-
collab.dispose()
|
|
49
|
-
expect(disposeSpy).toHaveBeenCalledTimes(1)
|
|
50
|
-
|
|
51
|
-
// ...and broadcastChannelTransport.dispose() closes the BroadcastChannel.
|
|
52
|
-
const close = vi.fn()
|
|
53
|
-
class FakeBroadcastChannel {
|
|
54
|
-
postMessage = vi.fn()
|
|
55
|
-
addEventListener = vi.fn()
|
|
56
|
-
removeEventListener = vi.fn()
|
|
57
|
-
close = close
|
|
58
|
-
constructor(public name: string) {}
|
|
59
|
-
}
|
|
60
|
-
const Orig = (globalThis as { BroadcastChannel?: unknown }).BroadcastChannel
|
|
61
|
-
;(globalThis as { BroadcastChannel?: unknown }).BroadcastChannel = FakeBroadcastChannel
|
|
62
|
-
try {
|
|
63
|
-
broadcastChannelTransport('room').dispose?.()
|
|
64
|
-
expect(close).toHaveBeenCalledTimes(1)
|
|
65
|
-
} finally {
|
|
66
|
-
;(globalThis as { BroadcastChannel?: unknown }).BroadcastChannel = Orig
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('setCell propagates a remote cursor', () => {
|
|
71
|
-
const bus = makeBus()
|
|
72
|
-
const onPeers = vi.fn()
|
|
73
|
-
const a = createCollaboration({ user: userA, transport: bus.transportFor(), onPeersChange: onPeers })
|
|
74
|
-
const b = createCollaboration({ user: userB, transport: bus.transportFor() })
|
|
75
|
-
b.setCell({ rowId: 'r1', columnId: 'salary' })
|
|
76
|
-
const peerB = a.peers().find((p) => p.id === 'b')
|
|
77
|
-
expect(peerB?.cell).toEqual({ rowId: 'r1', columnId: 'salary' })
|
|
78
|
-
a.dispose()
|
|
79
|
-
b.dispose()
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('sendEdit fires onRemoteEdit on the other peer only', () => {
|
|
83
|
-
const bus = makeBus()
|
|
84
|
-
const aEdits = vi.fn()
|
|
85
|
-
const bEdits = vi.fn()
|
|
86
|
-
const a = createCollaboration({ user: userA, transport: bus.transportFor(), onRemoteEdit: aEdits })
|
|
87
|
-
const b = createCollaboration({ user: userB, transport: bus.transportFor(), onRemoteEdit: bEdits })
|
|
88
|
-
a.sendEdit('r2', 'name', 'Grace')
|
|
89
|
-
expect(bEdits).toHaveBeenCalledWith(expect.objectContaining({ rowId: 'r2', columnId: 'name', value: 'Grace' }))
|
|
90
|
-
expect(aEdits).not.toHaveBeenCalled() // not echoed to self
|
|
91
|
-
a.dispose()
|
|
92
|
-
b.dispose()
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('bye removes a peer', () => {
|
|
96
|
-
const bus = makeBus()
|
|
97
|
-
const a = createCollaboration({ user: userA, transport: bus.transportFor() })
|
|
98
|
-
const b = createCollaboration({ user: userB, transport: bus.transportFor() })
|
|
99
|
-
expect(a.peers()).toHaveLength(1)
|
|
100
|
-
b.dispose()
|
|
101
|
-
expect(a.peers()).toHaveLength(0)
|
|
102
|
-
a.dispose()
|
|
103
|
-
})
|
|
104
|
-
})
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
broadcastChannelTransport,
|
|
4
|
+
createCollaboration,
|
|
5
|
+
type CollabMessage,
|
|
6
|
+
type CollabTransport,
|
|
7
|
+
} from './collaboration'
|
|
8
|
+
|
|
9
|
+
/** An in-memory bus so two controllers can talk in a test. */
|
|
10
|
+
function makeBus() {
|
|
11
|
+
const handlers = new Set<(m: CollabMessage) => void>()
|
|
12
|
+
const transportFor = (): CollabTransport => ({
|
|
13
|
+
post: (msg) => {
|
|
14
|
+
// deliver async-ish to every subscriber except none (real channels echo
|
|
15
|
+
// to other tabs, not self; our controllers ignore their own id anyway).
|
|
16
|
+
for (const h of [...handlers]) h(msg)
|
|
17
|
+
},
|
|
18
|
+
subscribe: (h) => {
|
|
19
|
+
handlers.add(h)
|
|
20
|
+
return () => handlers.delete(h)
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
return { transportFor }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const userA = { id: 'a', name: 'Ada', color: '#f00' }
|
|
27
|
+
const userB = { id: 'b', name: 'Bob', color: '#00f' }
|
|
28
|
+
|
|
29
|
+
describe('createCollaboration', () => {
|
|
30
|
+
it('peers see each other after hello', () => {
|
|
31
|
+
const bus = makeBus()
|
|
32
|
+
const a = createCollaboration({ user: userA, transport: bus.transportFor() })
|
|
33
|
+
const b = createCollaboration({ user: userB, transport: bus.transportFor() })
|
|
34
|
+
// A is announced; B's hello reaches A; A replies presence.
|
|
35
|
+
expect(a.peers().map((p) => p.id)).toEqual(['b'])
|
|
36
|
+
expect(b.peers().map((p) => p.id)).toEqual(['a'])
|
|
37
|
+
a.dispose()
|
|
38
|
+
b.dispose()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('closes the transport channel on dispose (#82)', () => {
|
|
42
|
+
// The controller now forwards dispose to the transport...
|
|
43
|
+
const disposeSpy = vi.fn()
|
|
44
|
+
const collab = createCollaboration({
|
|
45
|
+
user: userA,
|
|
46
|
+
transport: { post: vi.fn(), subscribe: () => () => {}, dispose: disposeSpy },
|
|
47
|
+
})
|
|
48
|
+
collab.dispose()
|
|
49
|
+
expect(disposeSpy).toHaveBeenCalledTimes(1)
|
|
50
|
+
|
|
51
|
+
// ...and broadcastChannelTransport.dispose() closes the BroadcastChannel.
|
|
52
|
+
const close = vi.fn()
|
|
53
|
+
class FakeBroadcastChannel {
|
|
54
|
+
postMessage = vi.fn()
|
|
55
|
+
addEventListener = vi.fn()
|
|
56
|
+
removeEventListener = vi.fn()
|
|
57
|
+
close = close
|
|
58
|
+
constructor(public name: string) {}
|
|
59
|
+
}
|
|
60
|
+
const Orig = (globalThis as { BroadcastChannel?: unknown }).BroadcastChannel
|
|
61
|
+
;(globalThis as { BroadcastChannel?: unknown }).BroadcastChannel = FakeBroadcastChannel
|
|
62
|
+
try {
|
|
63
|
+
broadcastChannelTransport('room').dispose?.()
|
|
64
|
+
expect(close).toHaveBeenCalledTimes(1)
|
|
65
|
+
} finally {
|
|
66
|
+
;(globalThis as { BroadcastChannel?: unknown }).BroadcastChannel = Orig
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('setCell propagates a remote cursor', () => {
|
|
71
|
+
const bus = makeBus()
|
|
72
|
+
const onPeers = vi.fn()
|
|
73
|
+
const a = createCollaboration({ user: userA, transport: bus.transportFor(), onPeersChange: onPeers })
|
|
74
|
+
const b = createCollaboration({ user: userB, transport: bus.transportFor() })
|
|
75
|
+
b.setCell({ rowId: 'r1', columnId: 'salary' })
|
|
76
|
+
const peerB = a.peers().find((p) => p.id === 'b')
|
|
77
|
+
expect(peerB?.cell).toEqual({ rowId: 'r1', columnId: 'salary' })
|
|
78
|
+
a.dispose()
|
|
79
|
+
b.dispose()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('sendEdit fires onRemoteEdit on the other peer only', () => {
|
|
83
|
+
const bus = makeBus()
|
|
84
|
+
const aEdits = vi.fn()
|
|
85
|
+
const bEdits = vi.fn()
|
|
86
|
+
const a = createCollaboration({ user: userA, transport: bus.transportFor(), onRemoteEdit: aEdits })
|
|
87
|
+
const b = createCollaboration({ user: userB, transport: bus.transportFor(), onRemoteEdit: bEdits })
|
|
88
|
+
a.sendEdit('r2', 'name', 'Grace')
|
|
89
|
+
expect(bEdits).toHaveBeenCalledWith(expect.objectContaining({ rowId: 'r2', columnId: 'name', value: 'Grace' }))
|
|
90
|
+
expect(aEdits).not.toHaveBeenCalled() // not echoed to self
|
|
91
|
+
a.dispose()
|
|
92
|
+
b.dispose()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('bye removes a peer', () => {
|
|
96
|
+
const bus = makeBus()
|
|
97
|
+
const a = createCollaboration({ user: userA, transport: bus.transportFor() })
|
|
98
|
+
const b = createCollaboration({ user: userB, transport: bus.transportFor() })
|
|
99
|
+
expect(a.peers()).toHaveLength(1)
|
|
100
|
+
b.dispose()
|
|
101
|
+
expect(a.peers()).toHaveLength(0)
|
|
102
|
+
a.dispose()
|
|
103
|
+
})
|
|
104
|
+
})
|
package/src/collaboration.ts
CHANGED
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Real-time collaboration. Presence (who else is here + where their cursor
|
|
3
|
-
* is) and live edits (a change in one client lands in every other), over a
|
|
4
|
-
* pluggable transport. The transport is the only thing tied to infrastructure
|
|
5
|
-
* - swap `broadcastChannelTransport` (same-browser tabs, zero backend) for a
|
|
6
|
-
* WebSocket / WebRTC / CRDT adapter and the controller is unchanged.
|
|
7
|
-
*
|
|
8
|
-
* This is also the natural "multiple agents on one grid" substrate: an AI
|
|
9
|
-
* agent is just another peer posting `edit` messages.
|
|
10
|
-
*/
|
|
11
|
-
export type CollabUser = {
|
|
12
|
-
id: string
|
|
13
|
-
name: string
|
|
14
|
-
/** A CSS color for this user's cursor / avatar. */
|
|
15
|
-
color: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type CollabCell = { rowId: string; columnId: string }
|
|
19
|
-
|
|
20
|
-
export type CollabPresence = CollabUser & {
|
|
21
|
-
cell: CollabCell | null
|
|
22
|
-
/** Last time we heard from this peer (ms). Used to prune the gone. */
|
|
23
|
-
ts: number
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type CollabMessage =
|
|
27
|
-
| { kind: 'hello'; user: CollabUser }
|
|
28
|
-
| { kind: 'presence'; user: CollabUser; cell: CollabCell | null }
|
|
29
|
-
| { kind: 'edit'; user: CollabUser; rowId: string; columnId: string; value: unknown }
|
|
30
|
-
| { kind: 'bye'; userId: string }
|
|
31
|
-
|
|
32
|
-
export type CollabTransport = {
|
|
33
|
-
post(msg: CollabMessage): void
|
|
34
|
-
subscribe(handler: (msg: CollabMessage) => void): () => void
|
|
35
|
-
/** Release any underlying resource (e.g. a BroadcastChannel). Optional. */
|
|
36
|
-
dispose?(): void
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type Collaboration = {
|
|
40
|
-
/** Broadcast where this user's cursor is (or null when it leaves). */
|
|
41
|
-
setCell(cell: CollabCell | null): void
|
|
42
|
-
/** Broadcast a cell edit to every peer. */
|
|
43
|
-
sendEdit(rowId: string, columnId: string, value: unknown): void
|
|
44
|
-
/** The peers currently present (excludes self). */
|
|
45
|
-
peers(): CollabPresence[]
|
|
46
|
-
dispose(): void
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type CollaborationOptions = {
|
|
50
|
-
user: CollabUser
|
|
51
|
-
transport: CollabTransport
|
|
52
|
-
/** Fired (with self excluded) whenever the peer set or a cursor changes. */
|
|
53
|
-
onPeersChange?: (peers: CollabPresence[]) => void
|
|
54
|
-
/** Fired when another user edits a cell - apply it to your data. */
|
|
55
|
-
onRemoteEdit?: (edit: {
|
|
56
|
-
rowId: string
|
|
57
|
-
columnId: string
|
|
58
|
-
value: unknown
|
|
59
|
-
user: CollabUser
|
|
60
|
-
}) => void
|
|
61
|
-
/** Drop peers we haven't heard from in this many ms. Default 15000. */
|
|
62
|
-
peerTimeoutMs?: number
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** BroadcastChannel transport - live across tabs of the same browser, no
|
|
66
|
-
* backend. No-ops where BroadcastChannel is unavailable (SSR / old env). */
|
|
67
|
-
export function broadcastChannelTransport(name: string): CollabTransport {
|
|
68
|
-
const available = typeof BroadcastChannel !== 'undefined'
|
|
69
|
-
const channel = available ? new BroadcastChannel(name) : null
|
|
70
|
-
return {
|
|
71
|
-
post(msg) {
|
|
72
|
-
channel?.postMessage(msg)
|
|
73
|
-
},
|
|
74
|
-
subscribe(handler) {
|
|
75
|
-
if (!channel) return () => {}
|
|
76
|
-
const listener = (e: MessageEvent) => handler(e.data as CollabMessage)
|
|
77
|
-
channel.addEventListener('message', listener)
|
|
78
|
-
return () => channel.removeEventListener('message', listener)
|
|
79
|
-
},
|
|
80
|
-
dispose() {
|
|
81
|
-
channel?.close()
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function createCollaboration(options: CollaborationOptions): Collaboration {
|
|
87
|
-
const { user, transport } = options
|
|
88
|
-
const timeout = options.peerTimeoutMs ?? 15_000
|
|
89
|
-
const peers = new Map<string, CollabPresence>()
|
|
90
|
-
let ownCell: CollabCell | null = null
|
|
91
|
-
let disposed = false
|
|
92
|
-
|
|
93
|
-
const list = () => [...peers.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
94
|
-
const notify = () => options.onPeersChange?.(list())
|
|
95
|
-
|
|
96
|
-
function upsert(u: CollabUser, cell: CollabCell | null) {
|
|
97
|
-
if (u.id === user.id) return
|
|
98
|
-
peers.set(u.id, { ...u, cell, ts: Date.now() })
|
|
99
|
-
notify()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const unsubscribe = transport.subscribe((msg) => {
|
|
103
|
-
if (disposed) return
|
|
104
|
-
switch (msg.kind) {
|
|
105
|
-
case 'hello':
|
|
106
|
-
upsert(msg.user, null)
|
|
107
|
-
// Let the newcomer know we're here (and where our cursor is).
|
|
108
|
-
transport.post({ kind: 'presence', user, cell: ownCell })
|
|
109
|
-
break
|
|
110
|
-
case 'presence':
|
|
111
|
-
upsert(msg.user, msg.cell)
|
|
112
|
-
break
|
|
113
|
-
case 'edit':
|
|
114
|
-
upsert(msg.user, { rowId: msg.rowId, columnId: msg.columnId })
|
|
115
|
-
if (msg.user.id !== user.id) {
|
|
116
|
-
options.onRemoteEdit?.({
|
|
117
|
-
rowId: msg.rowId,
|
|
118
|
-
columnId: msg.columnId,
|
|
119
|
-
value: msg.value,
|
|
120
|
-
user: msg.user,
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
break
|
|
124
|
-
case 'bye':
|
|
125
|
-
if (peers.delete(msg.userId)) notify()
|
|
126
|
-
break
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
// Prune peers that went silent (closed tab without a `bye`).
|
|
131
|
-
const pruneTimer =
|
|
132
|
-
typeof setInterval !== 'undefined'
|
|
133
|
-
? setInterval(() => {
|
|
134
|
-
const now = Date.now()
|
|
135
|
-
let changed = false
|
|
136
|
-
for (const [id, p] of peers) {
|
|
137
|
-
if (now - p.ts > timeout) {
|
|
138
|
-
peers.delete(id)
|
|
139
|
-
changed = true
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (changed) notify()
|
|
143
|
-
}, Math.max(1000, timeout / 3))
|
|
144
|
-
: null
|
|
145
|
-
|
|
146
|
-
// Announce ourselves.
|
|
147
|
-
transport.post({ kind: 'hello', user })
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
setCell(cell) {
|
|
151
|
-
ownCell = cell
|
|
152
|
-
transport.post({ kind: 'presence', user, cell })
|
|
153
|
-
},
|
|
154
|
-
sendEdit(rowId, columnId, value) {
|
|
155
|
-
transport.post({ kind: 'edit', user, rowId, columnId, value })
|
|
156
|
-
},
|
|
157
|
-
peers: list,
|
|
158
|
-
dispose() {
|
|
159
|
-
if (disposed) return
|
|
160
|
-
disposed = true
|
|
161
|
-
transport.post({ kind: 'bye', userId: user.id })
|
|
162
|
-
unsubscribe()
|
|
163
|
-
if (pruneTimer) clearInterval(pruneTimer)
|
|
164
|
-
transport.dispose?.() // release the channel (BroadcastChannel etc.)
|
|
165
|
-
},
|
|
166
|
-
}
|
|
167
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Real-time collaboration. Presence (who else is here + where their cursor
|
|
3
|
+
* is) and live edits (a change in one client lands in every other), over a
|
|
4
|
+
* pluggable transport. The transport is the only thing tied to infrastructure
|
|
5
|
+
* - swap `broadcastChannelTransport` (same-browser tabs, zero backend) for a
|
|
6
|
+
* WebSocket / WebRTC / CRDT adapter and the controller is unchanged.
|
|
7
|
+
*
|
|
8
|
+
* This is also the natural "multiple agents on one grid" substrate: an AI
|
|
9
|
+
* agent is just another peer posting `edit` messages.
|
|
10
|
+
*/
|
|
11
|
+
export type CollabUser = {
|
|
12
|
+
id: string
|
|
13
|
+
name: string
|
|
14
|
+
/** A CSS color for this user's cursor / avatar. */
|
|
15
|
+
color: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type CollabCell = { rowId: string; columnId: string }
|
|
19
|
+
|
|
20
|
+
export type CollabPresence = CollabUser & {
|
|
21
|
+
cell: CollabCell | null
|
|
22
|
+
/** Last time we heard from this peer (ms). Used to prune the gone. */
|
|
23
|
+
ts: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type CollabMessage =
|
|
27
|
+
| { kind: 'hello'; user: CollabUser }
|
|
28
|
+
| { kind: 'presence'; user: CollabUser; cell: CollabCell | null }
|
|
29
|
+
| { kind: 'edit'; user: CollabUser; rowId: string; columnId: string; value: unknown }
|
|
30
|
+
| { kind: 'bye'; userId: string }
|
|
31
|
+
|
|
32
|
+
export type CollabTransport = {
|
|
33
|
+
post(msg: CollabMessage): void
|
|
34
|
+
subscribe(handler: (msg: CollabMessage) => void): () => void
|
|
35
|
+
/** Release any underlying resource (e.g. a BroadcastChannel). Optional. */
|
|
36
|
+
dispose?(): void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type Collaboration = {
|
|
40
|
+
/** Broadcast where this user's cursor is (or null when it leaves). */
|
|
41
|
+
setCell(cell: CollabCell | null): void
|
|
42
|
+
/** Broadcast a cell edit to every peer. */
|
|
43
|
+
sendEdit(rowId: string, columnId: string, value: unknown): void
|
|
44
|
+
/** The peers currently present (excludes self). */
|
|
45
|
+
peers(): CollabPresence[]
|
|
46
|
+
dispose(): void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type CollaborationOptions = {
|
|
50
|
+
user: CollabUser
|
|
51
|
+
transport: CollabTransport
|
|
52
|
+
/** Fired (with self excluded) whenever the peer set or a cursor changes. */
|
|
53
|
+
onPeersChange?: (peers: CollabPresence[]) => void
|
|
54
|
+
/** Fired when another user edits a cell - apply it to your data. */
|
|
55
|
+
onRemoteEdit?: (edit: {
|
|
56
|
+
rowId: string
|
|
57
|
+
columnId: string
|
|
58
|
+
value: unknown
|
|
59
|
+
user: CollabUser
|
|
60
|
+
}) => void
|
|
61
|
+
/** Drop peers we haven't heard from in this many ms. Default 15000. */
|
|
62
|
+
peerTimeoutMs?: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** BroadcastChannel transport - live across tabs of the same browser, no
|
|
66
|
+
* backend. No-ops where BroadcastChannel is unavailable (SSR / old env). */
|
|
67
|
+
export function broadcastChannelTransport(name: string): CollabTransport {
|
|
68
|
+
const available = typeof BroadcastChannel !== 'undefined'
|
|
69
|
+
const channel = available ? new BroadcastChannel(name) : null
|
|
70
|
+
return {
|
|
71
|
+
post(msg) {
|
|
72
|
+
channel?.postMessage(msg)
|
|
73
|
+
},
|
|
74
|
+
subscribe(handler) {
|
|
75
|
+
if (!channel) return () => {}
|
|
76
|
+
const listener = (e: MessageEvent) => handler(e.data as CollabMessage)
|
|
77
|
+
channel.addEventListener('message', listener)
|
|
78
|
+
return () => channel.removeEventListener('message', listener)
|
|
79
|
+
},
|
|
80
|
+
dispose() {
|
|
81
|
+
channel?.close()
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createCollaboration(options: CollaborationOptions): Collaboration {
|
|
87
|
+
const { user, transport } = options
|
|
88
|
+
const timeout = options.peerTimeoutMs ?? 15_000
|
|
89
|
+
const peers = new Map<string, CollabPresence>()
|
|
90
|
+
let ownCell: CollabCell | null = null
|
|
91
|
+
let disposed = false
|
|
92
|
+
|
|
93
|
+
const list = () => [...peers.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
94
|
+
const notify = () => options.onPeersChange?.(list())
|
|
95
|
+
|
|
96
|
+
function upsert(u: CollabUser, cell: CollabCell | null) {
|
|
97
|
+
if (u.id === user.id) return
|
|
98
|
+
peers.set(u.id, { ...u, cell, ts: Date.now() })
|
|
99
|
+
notify()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const unsubscribe = transport.subscribe((msg) => {
|
|
103
|
+
if (disposed) return
|
|
104
|
+
switch (msg.kind) {
|
|
105
|
+
case 'hello':
|
|
106
|
+
upsert(msg.user, null)
|
|
107
|
+
// Let the newcomer know we're here (and where our cursor is).
|
|
108
|
+
transport.post({ kind: 'presence', user, cell: ownCell })
|
|
109
|
+
break
|
|
110
|
+
case 'presence':
|
|
111
|
+
upsert(msg.user, msg.cell)
|
|
112
|
+
break
|
|
113
|
+
case 'edit':
|
|
114
|
+
upsert(msg.user, { rowId: msg.rowId, columnId: msg.columnId })
|
|
115
|
+
if (msg.user.id !== user.id) {
|
|
116
|
+
options.onRemoteEdit?.({
|
|
117
|
+
rowId: msg.rowId,
|
|
118
|
+
columnId: msg.columnId,
|
|
119
|
+
value: msg.value,
|
|
120
|
+
user: msg.user,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
break
|
|
124
|
+
case 'bye':
|
|
125
|
+
if (peers.delete(msg.userId)) notify()
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Prune peers that went silent (closed tab without a `bye`).
|
|
131
|
+
const pruneTimer =
|
|
132
|
+
typeof setInterval !== 'undefined'
|
|
133
|
+
? setInterval(() => {
|
|
134
|
+
const now = Date.now()
|
|
135
|
+
let changed = false
|
|
136
|
+
for (const [id, p] of peers) {
|
|
137
|
+
if (now - p.ts > timeout) {
|
|
138
|
+
peers.delete(id)
|
|
139
|
+
changed = true
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (changed) notify()
|
|
143
|
+
}, Math.max(1000, timeout / 3))
|
|
144
|
+
: null
|
|
145
|
+
|
|
146
|
+
// Announce ourselves.
|
|
147
|
+
transport.post({ kind: 'hello', user })
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
setCell(cell) {
|
|
151
|
+
ownCell = cell
|
|
152
|
+
transport.post({ kind: 'presence', user, cell })
|
|
153
|
+
},
|
|
154
|
+
sendEdit(rowId, columnId, value) {
|
|
155
|
+
transport.post({ kind: 'edit', user, rowId, columnId, value })
|
|
156
|
+
},
|
|
157
|
+
peers: list,
|
|
158
|
+
dispose() {
|
|
159
|
+
if (disposed) return
|
|
160
|
+
disposed = true
|
|
161
|
+
transport.post({ kind: 'bye', userId: user.id })
|
|
162
|
+
unsubscribe()
|
|
163
|
+
if (pruneTimer) clearInterval(pruneTimer)
|
|
164
|
+
transport.dispose?.() // release the channel (BroadcastChannel etc.)
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
}
|