@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/dist/SvTree.svelte
CHANGED
|
@@ -1,444 +1,444 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
export type { TreeNode } from './createTree.svelte'
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
/**
|
|
7
|
-
* SvTree - a WAI-ARIA tree view. Expand/collapse, single-select highlight,
|
|
8
|
-
* cascading tri-state checkboxes, keyboard, and - for large or remote data -
|
|
9
|
-
* fixed-row `virtual` windowing (scales to tens of thousands of nodes) and
|
|
10
|
-
* `loadChildren` lazy-loading. Parity: Smart `smart-tree`.
|
|
11
|
-
*
|
|
12
|
-
* The behavior (flattening, cascade math, keyboard, ARIA) lives in the headless
|
|
13
|
-
* `createTree` core; this component renders it, and owns the DOM concerns
|
|
14
|
-
* (windowing, focus, and merging lazily-loaded children into the node tree).
|
|
15
|
-
*/
|
|
16
|
-
import { tick, untrack } from 'svelte'
|
|
17
|
-
import { type EditorDir } from './editor-contract'
|
|
18
|
-
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
19
|
-
import { createJsScroller } from './js-scroller.svelte'
|
|
20
|
-
import { createTree, sortTreeNodes, type TreeNode, type TreeDropPosition } from './createTree.svelte'
|
|
21
|
-
|
|
22
|
-
type Props = {
|
|
23
|
-
nodes: ReadonlyArray<TreeNode>
|
|
24
|
-
/** Selected node id (single-select highlight). */
|
|
25
|
-
selected?: string | null
|
|
26
|
-
onSelect?: (id: string) => void
|
|
27
|
-
/** Initially/controlled expanded ids. */
|
|
28
|
-
expandedIds?: string[]
|
|
29
|
-
onToggle?: (id: string, expanded: boolean) => void
|
|
30
|
-
/** Show cascading checkboxes; `checked` is the set of checked ids. */
|
|
31
|
-
checkable?: boolean
|
|
32
|
-
checked?: string[]
|
|
33
|
-
onCheck?: (ids: string[]) => void
|
|
34
|
-
ariaLabel?: string
|
|
35
|
-
/** Text direction (rtl mirrors indentation + flips Left/Right keys). */
|
|
36
|
-
dir?: EditorDir
|
|
37
|
-
/** Window rows (render only visible) - for large trees. Needs `height`. */
|
|
38
|
-
virtual?: boolean
|
|
39
|
-
/** Row height in px - a number, or a per-node function for variable
|
|
40
|
-
* heights. Must match the rendered row height. Default 30. */
|
|
41
|
-
rowHeight?: number | ((node: TreeNode, index: number) => number)
|
|
42
|
-
/** Scroll-viewport height in px. Enables scrolling (required for `virtual`). */
|
|
43
|
-
height?: number
|
|
44
|
-
/** Load a lazy node's children on first expand (node has `lazy: true`). */
|
|
45
|
-
loadChildren?: (node: TreeNode) => Promise<TreeNode[]>
|
|
46
|
-
/** Filter query - show only matching nodes + ancestors, auto-expanded. */
|
|
47
|
-
filter?: string
|
|
48
|
-
/** Render a built-in search box that drives the filter. */
|
|
49
|
-
searchable?: boolean
|
|
50
|
-
searchPlaceholder?: string
|
|
51
|
-
/** Placeholder row shown under a lazy node while its children load
|
|
52
|
-
* (localization). */
|
|
53
|
-
loadingText?: string
|
|
54
|
-
/** Accessible name for the search box's clear button (localization). */
|
|
55
|
-
clearSearchLabel?: string
|
|
56
|
-
/** Sort siblings by label ('asc'/'desc') or a custom comparator. */
|
|
57
|
-
sort?: 'asc' | 'desc' | ((a: TreeNode, b: TreeNode) => number)
|
|
58
|
-
/** Allow inline rename (double-click or F2). Fires onRename. */
|
|
59
|
-
editable?: boolean
|
|
60
|
-
onRename?: (id: string, label: string) => void
|
|
61
|
-
/** Allow drag-drop reordering; fires onMove(dragId, targetId, position).
|
|
62
|
-
* Apply it with the exported `moveTreeNode(nodes, ...)` helper. */
|
|
63
|
-
reorderable?: boolean
|
|
64
|
-
onMove?: (dragId: string, targetId: string, position: TreeDropPosition) => void
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let {
|
|
68
|
-
nodes,
|
|
69
|
-
selected = null,
|
|
70
|
-
onSelect,
|
|
71
|
-
expandedIds,
|
|
72
|
-
onToggle,
|
|
73
|
-
checkable = false,
|
|
74
|
-
checked = [],
|
|
75
|
-
onCheck,
|
|
76
|
-
ariaLabel,
|
|
77
|
-
dir,
|
|
78
|
-
virtual = false,
|
|
79
|
-
rowHeight = 30,
|
|
80
|
-
height,
|
|
81
|
-
loadChildren,
|
|
82
|
-
filter,
|
|
83
|
-
searchable = false,
|
|
84
|
-
searchPlaceholder = 'Search...',
|
|
85
|
-
loadingText = 'Loading...',
|
|
86
|
-
clearSearchLabel = 'Clear search',
|
|
87
|
-
sort,
|
|
88
|
-
editable = false,
|
|
89
|
-
onRename,
|
|
90
|
-
reorderable = false,
|
|
91
|
-
onMove,
|
|
92
|
-
}: Props = $props()
|
|
93
|
-
|
|
94
|
-
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
95
|
-
let query = $state('')
|
|
96
|
-
const effectiveFilter = $derived(filter ?? (searchable ? query : undefined))
|
|
97
|
-
|
|
98
|
-
// Sort siblings (recursively) before anything else.
|
|
99
|
-
const sortedNodes = $derived.by(() => {
|
|
100
|
-
if (!sort) return nodes
|
|
101
|
-
const cmp = typeof sort === 'function' ? sort : (a: TreeNode, b: TreeNode) => (sort === 'asc' ? 1 : -1) * a.label.localeCompare(b.label)
|
|
102
|
-
return sortTreeNodes(nodes, cmp)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
// --- Lazy loading: merge loaded children into the (controlled) node tree -----
|
|
106
|
-
let loaded = $state(new Map<string, TreeNode[]>())
|
|
107
|
-
let loadingIds = $state(new Set<string>())
|
|
108
|
-
|
|
109
|
-
function inject(list: ReadonlyArray<TreeNode>): TreeNode[] {
|
|
110
|
-
return list.map((n) => {
|
|
111
|
-
if (n.lazy && loaded.has(n.id)) return { ...n, children: inject(loaded.get(n.id)!), lazy: false }
|
|
112
|
-
if (n.children?.length) return { ...n, children: inject(n.children) }
|
|
113
|
-
return n
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
const effectiveNodes = $derived(inject(sortedNodes))
|
|
117
|
-
|
|
118
|
-
// --- Inline rename ----------------------------------------------------------
|
|
119
|
-
let editingId = $state<string | null>(null)
|
|
120
|
-
let draftLabel = $state('')
|
|
121
|
-
function startEdit(node: TreeNode) {
|
|
122
|
-
if (!editable || node.disabled) return
|
|
123
|
-
editingId = node.id
|
|
124
|
-
draftLabel = node.label
|
|
125
|
-
tick().then(() => treeEl?.querySelector<HTMLInputElement>('.sv-tree__edit')?.select())
|
|
126
|
-
}
|
|
127
|
-
function commitEdit() {
|
|
128
|
-
if (editingId == null) return
|
|
129
|
-
const id = editingId
|
|
130
|
-
const label = draftLabel.trim()
|
|
131
|
-
editingId = null
|
|
132
|
-
if (label) onRename?.(id, label)
|
|
133
|
-
}
|
|
134
|
-
function cancelEdit() { editingId = null }
|
|
135
|
-
|
|
136
|
-
// --- Drag-drop reorder ------------------------------------------------------
|
|
137
|
-
let dragId = $state<string | null>(null)
|
|
138
|
-
let dropId = $state<string | null>(null)
|
|
139
|
-
let dropPos = $state<TreeDropPosition | null>(null)
|
|
140
|
-
function computeDropPos(e: DragEvent, node: TreeNode): TreeDropPosition {
|
|
141
|
-
const el = e.currentTarget as HTMLElement
|
|
142
|
-
const r = el.getBoundingClientRect()
|
|
143
|
-
const y = (e.clientY - r.top) / r.height
|
|
144
|
-
const canNest = !!node.children?.length || !!node.lazy
|
|
145
|
-
if (y < 0.3) return 'before'
|
|
146
|
-
if (y > 0.7 || !canNest) return y > 0.5 ? 'after' : (canNest ? 'inside' : 'after')
|
|
147
|
-
return 'inside'
|
|
148
|
-
}
|
|
149
|
-
function onRowDragStart(e: DragEvent, node: TreeNode) {
|
|
150
|
-
dragId = node.id
|
|
151
|
-
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move'
|
|
152
|
-
}
|
|
153
|
-
function onRowDragOver(e: DragEvent, node: TreeNode) {
|
|
154
|
-
if (!dragId || dragId === node.id) return
|
|
155
|
-
e.preventDefault()
|
|
156
|
-
dropId = node.id
|
|
157
|
-
dropPos = computeDropPos(e, node)
|
|
158
|
-
}
|
|
159
|
-
function onRowDrop(e: DragEvent, node: TreeNode) {
|
|
160
|
-
e.preventDefault()
|
|
161
|
-
if (dragId && dragId !== node.id && dropPos) onMove?.(dragId, node.id, dropPos)
|
|
162
|
-
dragId = dropId = dropPos = null
|
|
163
|
-
}
|
|
164
|
-
function endDrag() { dragId = dropId = dropPos = null }
|
|
165
|
-
|
|
166
|
-
function findNode(list: ReadonlyArray<TreeNode>, id: string): TreeNode | null {
|
|
167
|
-
for (const n of list) {
|
|
168
|
-
if (n.id === id) return n
|
|
169
|
-
if (n.children) { const f = findNode(n.children, id); if (f) return f }
|
|
170
|
-
}
|
|
171
|
-
return null
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function handleToggle(id: string, expanded: boolean) {
|
|
175
|
-
onToggle?.(id, expanded)
|
|
176
|
-
if (!expanded || !loadChildren) return
|
|
177
|
-
// Search the merged tree, not the raw prop: a nested lazy node (revealed by
|
|
178
|
-
// a parent's earlier load) lives inside `loaded`, never in `nodes`.
|
|
179
|
-
const node = findNode(effectiveNodes, id)
|
|
180
|
-
if (!node?.lazy || loaded.has(id) || loadingIds.has(id)) return
|
|
181
|
-
loadingIds = new Set(loadingIds).add(id)
|
|
182
|
-
// A placeholder row shows immediately while the promise resolves.
|
|
183
|
-
loaded = new Map(loaded).set(id, [{ id: `${id}__loading`, label: loadingText, disabled: true }])
|
|
184
|
-
try {
|
|
185
|
-
const kids = await loadChildren(node)
|
|
186
|
-
loaded = new Map(loaded).set(id, kids)
|
|
187
|
-
} catch {
|
|
188
|
-
loaded = new Map(loaded).set(id, [])
|
|
189
|
-
} finally {
|
|
190
|
-
const next = new Set(loadingIds); next.delete(id); loadingIds = next
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const tree = createTree({
|
|
195
|
-
nodes: () => effectiveNodes,
|
|
196
|
-
selected: () => selected,
|
|
197
|
-
onSelect: (id) => onSelect?.(id),
|
|
198
|
-
expandedIds: () => expandedIds,
|
|
199
|
-
onToggle: handleToggle,
|
|
200
|
-
checkable: () => checkable,
|
|
201
|
-
checked: () => checked,
|
|
202
|
-
onCheck: (ids) => onCheck?.(ids),
|
|
203
|
-
ariaLabel: () => ariaLabel,
|
|
204
|
-
dir: () => dir,
|
|
205
|
-
filter: () => effectiveFilter,
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
// --- Virtualization ---------------------------------------------------------
|
|
209
|
-
// Windowing runs on the shared `createSvelteVirtualizer` (cumulative offsets +
|
|
210
|
-
// binary search + variable-size support). Rows stay keyed by node id (NOT slot
|
|
211
|
-
// recycling): a tree row carries per-row identity - an in-progress inline
|
|
212
|
-
// rename, an active drag, roving focus - that must stay pinned to its node
|
|
213
|
-
// while scrolling. The engine only supplies the window + each row's offset.
|
|
214
|
-
const scrollable = $derived(virtual || height != null)
|
|
215
|
-
let treeEl = $state<HTMLDivElement | null>(null)
|
|
216
|
-
let viewportH = $state(0)
|
|
217
|
-
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 30)
|
|
218
|
-
const uniform = $derived(typeof rowHeight === 'number')
|
|
219
|
-
const rowSize = (i: number) => {
|
|
220
|
-
const r = tree.rows[i]
|
|
221
|
-
return r ? (typeof rowHeight === 'function' ? rowHeight(r.node, i) : rowHeight) : rowHeightBase
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => rowSize(i))
|
|
225
|
-
// Generous overscan buffers fast flings; effective viewport never 0 (see SvListBox).
|
|
226
|
-
const OVERSCAN = 20
|
|
227
|
-
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, height ?? 320))
|
|
228
|
-
// Seed with the initial (untracked) values so the first paint renders a full window.
|
|
229
|
-
const virtualizer = createSvelteVirtualizer(
|
|
230
|
-
untrack(() => ({ count: tree.rows.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
231
|
-
)
|
|
232
|
-
// untrack keeps the virtualizer's internal `version` write out of these
|
|
233
|
-
// effects' dependencies, so they re-run only on their real inputs.
|
|
234
|
-
$effect(() => {
|
|
235
|
-
const count = tree.rows.length
|
|
236
|
-
const es = estimateSize
|
|
237
|
-
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
238
|
-
})
|
|
239
|
-
$effect(() => {
|
|
240
|
-
const vh = effViewport
|
|
241
|
-
untrack(() => virtualizer.setViewportHeight(vh))
|
|
242
|
-
})
|
|
243
|
-
const totalSize = $derived.by(() => { virtualizer.version; return virtualizer.getTotalSize() })
|
|
244
|
-
const visibleRows = $derived.by(() => {
|
|
245
|
-
if (!virtual) return tree.rows
|
|
246
|
-
virtualizer.version
|
|
247
|
-
const st = virtualizer.getState()
|
|
248
|
-
return tree.rows.slice(st.startIndex, st.endIndex + 1)
|
|
249
|
-
})
|
|
250
|
-
const rowTop = (i: number) => virtualizer.getOffsetForIndex(i)
|
|
251
|
-
const rowHeightPx = (i: number) => (virtual ? virtualizer.getSizeForIndex(i) : rowSize(i))
|
|
252
|
-
|
|
253
|
-
// JS-driven scroll for virtual mode (see js-scroller): no native scroll, so a
|
|
254
|
-
// fast thumb-drag never flashes an empty tree.
|
|
255
|
-
const scroller = createJsScroller({
|
|
256
|
-
virtualizer,
|
|
257
|
-
totalSize: () => totalSize,
|
|
258
|
-
viewport: () => effViewport,
|
|
259
|
-
lineStep: () => rowHeightBase,
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
// Follow the core's roving focus row; scroll it into view first when windowed.
|
|
263
|
-
let lastFocusTick = 0
|
|
264
|
-
$effect(() => {
|
|
265
|
-
if (tree.focusTick === lastFocusTick) return
|
|
266
|
-
lastFocusTick = tree.focusTick
|
|
267
|
-
const i = tree.activeIndex
|
|
268
|
-
if (virtual) untrack(() => scroller.ensureIndexVisible(i))
|
|
269
|
-
tick().then(() => treeEl?.querySelector<HTMLElement>(`[data-row="${i}"]`)?.focus())
|
|
270
|
-
})
|
|
271
|
-
</script>
|
|
272
|
-
|
|
273
|
-
<div class="sv-tree-host" class:has-search={searchable} dir={resolvedDir}>
|
|
274
|
-
{#if searchable}
|
|
275
|
-
<div class="sv-tree__search">
|
|
276
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>
|
|
277
|
-
<input class="sv-tree__search-input" type="text" placeholder={searchPlaceholder} bind:value={query} aria-label={searchPlaceholder} />
|
|
278
|
-
{#if query}<button type="button" class="sv-tree__search-clear" aria-label={clearSearchLabel} onclick={() => (query = '')}>×</button>{/if}
|
|
279
|
-
</div>
|
|
280
|
-
{/if}
|
|
281
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
282
|
-
<div
|
|
283
|
-
bind:this={treeEl}
|
|
284
|
-
class="sv-tree"
|
|
285
|
-
class:sv-tree--scroll={scrollable && !virtual}
|
|
286
|
-
class:is-virtual={virtual}
|
|
287
|
-
dir={resolvedDir}
|
|
288
|
-
style:height={height != null ? `${height}px` : undefined}
|
|
289
|
-
onwheel={virtual ? scroller.onWheel : undefined}
|
|
290
|
-
onpointerdown={virtual ? scroller.onContentDown : undefined}
|
|
291
|
-
onpointermove={virtual ? scroller.onContentMove : undefined}
|
|
292
|
-
onpointerup={virtual ? scroller.onContentUp : undefined}
|
|
293
|
-
onpointercancel={virtual ? scroller.onContentUp : undefined}
|
|
294
|
-
bind:clientHeight={viewportH}
|
|
295
|
-
{...tree.treeProps()}
|
|
296
|
-
>
|
|
297
|
-
{#each visibleRows as item (item.node.id)}
|
|
298
|
-
{@render treeRow(item)}
|
|
299
|
-
{/each}
|
|
300
|
-
{#if virtual && scroller.maxOffset > 0}
|
|
301
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
302
|
-
<div class="sv-tree__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
303
|
-
<div class="sv-tree__thumb" class:is-dragging={scroller.dragging} style:height={`${scroller.thumbH}px`} style:transform={`translateY(${scroller.thumbTop}px)`} onpointerdown={scroller.onThumbDown} onpointermove={scroller.onThumbMove} onpointerup={scroller.onThumbUp} onpointercancel={scroller.onThumbUp}></div>
|
|
304
|
-
</div>
|
|
305
|
-
{/if}
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
|
|
309
|
-
{#snippet treeRow(item: (typeof tree.rows)[number])}
|
|
310
|
-
{@const cs = checkable ? tree.checkStateOf(item.node) : 'unchecked'}
|
|
311
|
-
{@const isLoading = loadingIds.has(item.node.id)}
|
|
312
|
-
{@const rowProps = tree.itemProps(item)}
|
|
313
|
-
<div
|
|
314
|
-
class="sv-tree__row"
|
|
315
|
-
class:is-selected={item.node.id === selected}
|
|
316
|
-
class:is-disabled={item.node.disabled}
|
|
317
|
-
class:is-dragging={dragId === item.node.id}
|
|
318
|
-
class:drop-before={dropId === item.node.id && dropPos === 'before'}
|
|
319
|
-
class:drop-after={dropId === item.node.id && dropPos === 'after'}
|
|
320
|
-
class:drop-inside={dropId === item.node.id && dropPos === 'inside'}
|
|
321
|
-
style:height={`${rowHeightPx(item.index)}px`}
|
|
322
|
-
style:padding-inline-start={`${item.depth * 18 + 6}px`}
|
|
323
|
-
style:transform={virtual ? `translateY(${rowTop(item.index) - scroller.scrollOffset}px)` : undefined}
|
|
324
|
-
draggable={reorderable && editingId !== item.node.id && !item.node.disabled}
|
|
325
|
-
ondragstart={reorderable ? (e) => onRowDragStart(e, item.node) : undefined}
|
|
326
|
-
ondragover={reorderable ? (e) => onRowDragOver(e, item.node) : undefined}
|
|
327
|
-
ondragleave={reorderable ? () => { if (dropId === item.node.id) { dropId = null; dropPos = null } } : undefined}
|
|
328
|
-
ondrop={reorderable ? (e) => onRowDrop(e, item.node) : undefined}
|
|
329
|
-
ondragend={reorderable ? endDrag : undefined}
|
|
330
|
-
ondblclick={editable ? () => startEdit(item.node) : undefined}
|
|
331
|
-
{...rowProps}
|
|
332
|
-
onkeydown={(e) => {
|
|
333
|
-
rowProps.onkeydown(e)
|
|
334
|
-
if (editable && e.key === 'F2') { e.preventDefault(); startEdit(item.node) }
|
|
335
|
-
}}
|
|
336
|
-
>
|
|
337
|
-
<button
|
|
338
|
-
type="button"
|
|
339
|
-
class="sv-tree__twist"
|
|
340
|
-
class:is-open={item.open}
|
|
341
|
-
class:is-leaf={!item.hasChildren}
|
|
342
|
-
class:is-loading={isLoading}
|
|
343
|
-
tabindex="-1"
|
|
344
|
-
aria-hidden="true"
|
|
345
|
-
onclick={(e) => { e.stopPropagation(); tree.toggleExpand(item.node) }}
|
|
346
|
-
>
|
|
347
|
-
{#if isLoading}
|
|
348
|
-
<svg class="sv-tree__spin" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-6.2-8.6" /></svg>
|
|
349
|
-
{:else if item.hasChildren}
|
|
350
|
-
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
|
351
|
-
{/if}
|
|
352
|
-
</button>
|
|
353
|
-
{#if checkable}
|
|
354
|
-
<button type="button" class="sv-tree__check is-{cs}" tabindex="-1" aria-hidden="true" onclick={(e) => { e.stopPropagation(); tree.toggleCheck(item.node) }}>
|
|
355
|
-
{#if cs === 'checked'}<svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-6.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
356
|
-
{:else if cs === 'indeterminate'}<svg viewBox="0 0 16 16"><path d="M4 8h8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>{/if}
|
|
357
|
-
</button>
|
|
358
|
-
{/if}
|
|
359
|
-
{#if editingId === item.node.id}
|
|
360
|
-
<!-- svelte-ignore a11y_autofocus -->
|
|
361
|
-
<input
|
|
362
|
-
class="sv-tree__edit"
|
|
363
|
-
bind:value={draftLabel}
|
|
364
|
-
onclick={(e) => e.stopPropagation()}
|
|
365
|
-
onkeydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); commitEdit() } else if (e.key === 'Escape') { e.preventDefault(); cancelEdit() } }}
|
|
366
|
-
onblur={commitEdit}
|
|
367
|
-
/>
|
|
368
|
-
{:else}
|
|
369
|
-
<span class="sv-tree__label">{item.node.label}</span>
|
|
370
|
-
{/if}
|
|
371
|
-
</div>
|
|
372
|
-
{/snippet}
|
|
373
|
-
|
|
374
|
-
<style>
|
|
375
|
-
.sv-tree-host { display: contents; }
|
|
376
|
-
.sv-tree-host.has-search { display: inline-flex; flex-direction: column; gap: 6px; }
|
|
377
|
-
.sv-tree__search {
|
|
378
|
-
display: flex; align-items: center; gap: 7px; width: 260px; box-sizing: border-box; padding: 0 10px; height: 32px;
|
|
379
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-muted, #64748b);
|
|
380
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
381
|
-
}
|
|
382
|
-
.sv-tree__search:focus-within { border-color: var(--sg-accent, #2563eb); box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent, #2563eb) 22%, transparent); }
|
|
383
|
-
.sv-tree__search-input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: var(--sg-fg, #0f172a); font: inherit; font-size: 13px; }
|
|
384
|
-
.sv-tree__search-clear { background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; font-size: 16px; line-height: 1; padding: 0 2px; }
|
|
385
|
-
.sv-tree {
|
|
386
|
-
--_accent: var(--sg-accent, #2563eb);
|
|
387
|
-
width: 260px; padding: 4px; user-select: none;
|
|
388
|
-
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
389
|
-
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 8px);
|
|
390
|
-
font-size: 13px;
|
|
391
|
-
}
|
|
392
|
-
/* Non-virtual (height-capped) tree uses native scroll. */
|
|
393
|
-
.sv-tree--scroll { overflow-y: auto; overscroll-behavior: contain; overflow-anchor: none; }
|
|
394
|
-
/* Virtual tree: JS-driven, no native scroll - rows are moved by `scrollOffset`. */
|
|
395
|
-
.sv-tree.is-virtual { position: relative; overflow: hidden; touch-action: none; }
|
|
396
|
-
.sv-tree.is-virtual .sv-tree__row { position: absolute; inset-inline: 4px; top: 4px; }
|
|
397
|
-
.sv-tree__sb { position: absolute; top: 4px; right: 2px; bottom: 4px; width: 10px; z-index: 2; }
|
|
398
|
-
.sv-tree__thumb {
|
|
399
|
-
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px; border-radius: 4px;
|
|
400
|
-
background: color-mix(in srgb, currentColor 26%, transparent); cursor: default; touch-action: none;
|
|
401
|
-
}
|
|
402
|
-
.sv-tree__thumb:hover, .sv-tree__thumb.is-dragging { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
403
|
-
.sv-tree__row {
|
|
404
|
-
display: flex; align-items: center; gap: 4px; box-sizing: border-box; padding-inline-end: 8px;
|
|
405
|
-
border-radius: 6px; cursor: pointer; outline: none;
|
|
406
|
-
}
|
|
407
|
-
.sv-tree__row:hover:not(.is-disabled) { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
408
|
-
.sv-tree__row.is-selected { background: color-mix(in srgb, var(--_accent) 15%, transparent); color: var(--_accent); font-weight: 600; }
|
|
409
|
-
.sv-tree__row.is-disabled { opacity: 0.45; cursor: not-allowed; }
|
|
410
|
-
.sv-tree__row:focus-visible { box-shadow: inset 0 0 0 2px var(--sg-focus-ring, var(--_accent)); }
|
|
411
|
-
.sv-tree__twist {
|
|
412
|
-
display: grid; place-items: center; width: 18px; height: 18px; flex: none;
|
|
413
|
-
background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; transition: transform 0.12s;
|
|
414
|
-
}
|
|
415
|
-
.sv-tree__twist.is-open { transform: rotate(90deg); }
|
|
416
|
-
.sv-tree[dir='rtl'] .sv-tree__twist:not(.is-open) svg { transform: scaleX(-1); }
|
|
417
|
-
.sv-tree__twist.is-leaf { visibility: hidden; }
|
|
418
|
-
.sv-tree__twist.is-loading { transform: none; }
|
|
419
|
-
.sv-tree__spin { animation: sv-tree-spin 0.7s linear infinite; }
|
|
420
|
-
@keyframes sv-tree-spin { to { transform: rotate(360deg); } }
|
|
421
|
-
@media (prefers-reduced-motion: reduce) { .sv-tree__spin { animation: none; } }
|
|
422
|
-
.sv-tree__check {
|
|
423
|
-
display: grid; place-items: center; width: 16px; height: 16px; flex: none; padding: 0;
|
|
424
|
-
border: 1.5px solid var(--sg-border, #cbd5e1); border-radius: 4px; background: var(--sg-input-bg, #fff);
|
|
425
|
-
color: var(--sg-on-accent, #fff); cursor: pointer;
|
|
426
|
-
}
|
|
427
|
-
.sv-tree__check.is-checked, .sv-tree__check.is-indeterminate { background: var(--_accent); border-color: var(--_accent); }
|
|
428
|
-
.sv-tree__check svg { width: 100%; height: 100%; }
|
|
429
|
-
.sv-tree__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
430
|
-
.sv-tree__edit {
|
|
431
|
-
flex: 1; min-width: 0; height: 22px; padding: 0 6px; margin: 0; font: inherit; font-size: 13px;
|
|
432
|
-
border: 1px solid var(--_accent); border-radius: 5px; background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); outline: none;
|
|
433
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent);
|
|
434
|
-
}
|
|
435
|
-
/* Drag-drop feedback */
|
|
436
|
-
.sv-tree__row.is-dragging { opacity: 0.4; }
|
|
437
|
-
.sv-tree__row.drop-inside { background: color-mix(in srgb, var(--_accent) 18%, transparent); box-shadow: inset 0 0 0 1px var(--_accent); }
|
|
438
|
-
.sv-tree__row.drop-before, .sv-tree__row.drop-after { position: relative; }
|
|
439
|
-
.sv-tree__row.drop-before::before, .sv-tree__row.drop-after::after {
|
|
440
|
-
content: ''; position: absolute; inset-inline: 4px; height: 2px; background: var(--_accent); border-radius: 2px; z-index: 1;
|
|
441
|
-
}
|
|
442
|
-
.sv-tree__row.drop-before::before { top: -1px; }
|
|
443
|
-
.sv-tree__row.drop-after::after { bottom: -1px; }
|
|
444
|
-
</style>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type { TreeNode } from './createTree.svelte'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
/**
|
|
7
|
+
* SvTree - a WAI-ARIA tree view. Expand/collapse, single-select highlight,
|
|
8
|
+
* cascading tri-state checkboxes, keyboard, and - for large or remote data -
|
|
9
|
+
* fixed-row `virtual` windowing (scales to tens of thousands of nodes) and
|
|
10
|
+
* `loadChildren` lazy-loading. Parity: Smart `smart-tree`.
|
|
11
|
+
*
|
|
12
|
+
* The behavior (flattening, cascade math, keyboard, ARIA) lives in the headless
|
|
13
|
+
* `createTree` core; this component renders it, and owns the DOM concerns
|
|
14
|
+
* (windowing, focus, and merging lazily-loaded children into the node tree).
|
|
15
|
+
*/
|
|
16
|
+
import { tick, untrack } from 'svelte'
|
|
17
|
+
import { type EditorDir } from './editor-contract'
|
|
18
|
+
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
19
|
+
import { createJsScroller } from './js-scroller.svelte'
|
|
20
|
+
import { createTree, sortTreeNodes, type TreeNode, type TreeDropPosition } from './createTree.svelte'
|
|
21
|
+
|
|
22
|
+
type Props = {
|
|
23
|
+
nodes: ReadonlyArray<TreeNode>
|
|
24
|
+
/** Selected node id (single-select highlight). */
|
|
25
|
+
selected?: string | null
|
|
26
|
+
onSelect?: (id: string) => void
|
|
27
|
+
/** Initially/controlled expanded ids. */
|
|
28
|
+
expandedIds?: string[]
|
|
29
|
+
onToggle?: (id: string, expanded: boolean) => void
|
|
30
|
+
/** Show cascading checkboxes; `checked` is the set of checked ids. */
|
|
31
|
+
checkable?: boolean
|
|
32
|
+
checked?: string[]
|
|
33
|
+
onCheck?: (ids: string[]) => void
|
|
34
|
+
ariaLabel?: string
|
|
35
|
+
/** Text direction (rtl mirrors indentation + flips Left/Right keys). */
|
|
36
|
+
dir?: EditorDir
|
|
37
|
+
/** Window rows (render only visible) - for large trees. Needs `height`. */
|
|
38
|
+
virtual?: boolean
|
|
39
|
+
/** Row height in px - a number, or a per-node function for variable
|
|
40
|
+
* heights. Must match the rendered row height. Default 30. */
|
|
41
|
+
rowHeight?: number | ((node: TreeNode, index: number) => number)
|
|
42
|
+
/** Scroll-viewport height in px. Enables scrolling (required for `virtual`). */
|
|
43
|
+
height?: number
|
|
44
|
+
/** Load a lazy node's children on first expand (node has `lazy: true`). */
|
|
45
|
+
loadChildren?: (node: TreeNode) => Promise<TreeNode[]>
|
|
46
|
+
/** Filter query - show only matching nodes + ancestors, auto-expanded. */
|
|
47
|
+
filter?: string
|
|
48
|
+
/** Render a built-in search box that drives the filter. */
|
|
49
|
+
searchable?: boolean
|
|
50
|
+
searchPlaceholder?: string
|
|
51
|
+
/** Placeholder row shown under a lazy node while its children load
|
|
52
|
+
* (localization). */
|
|
53
|
+
loadingText?: string
|
|
54
|
+
/** Accessible name for the search box's clear button (localization). */
|
|
55
|
+
clearSearchLabel?: string
|
|
56
|
+
/** Sort siblings by label ('asc'/'desc') or a custom comparator. */
|
|
57
|
+
sort?: 'asc' | 'desc' | ((a: TreeNode, b: TreeNode) => number)
|
|
58
|
+
/** Allow inline rename (double-click or F2). Fires onRename. */
|
|
59
|
+
editable?: boolean
|
|
60
|
+
onRename?: (id: string, label: string) => void
|
|
61
|
+
/** Allow drag-drop reordering; fires onMove(dragId, targetId, position).
|
|
62
|
+
* Apply it with the exported `moveTreeNode(nodes, ...)` helper. */
|
|
63
|
+
reorderable?: boolean
|
|
64
|
+
onMove?: (dragId: string, targetId: string, position: TreeDropPosition) => void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let {
|
|
68
|
+
nodes,
|
|
69
|
+
selected = null,
|
|
70
|
+
onSelect,
|
|
71
|
+
expandedIds,
|
|
72
|
+
onToggle,
|
|
73
|
+
checkable = false,
|
|
74
|
+
checked = [],
|
|
75
|
+
onCheck,
|
|
76
|
+
ariaLabel,
|
|
77
|
+
dir,
|
|
78
|
+
virtual = false,
|
|
79
|
+
rowHeight = 30,
|
|
80
|
+
height,
|
|
81
|
+
loadChildren,
|
|
82
|
+
filter,
|
|
83
|
+
searchable = false,
|
|
84
|
+
searchPlaceholder = 'Search...',
|
|
85
|
+
loadingText = 'Loading...',
|
|
86
|
+
clearSearchLabel = 'Clear search',
|
|
87
|
+
sort,
|
|
88
|
+
editable = false,
|
|
89
|
+
onRename,
|
|
90
|
+
reorderable = false,
|
|
91
|
+
onMove,
|
|
92
|
+
}: Props = $props()
|
|
93
|
+
|
|
94
|
+
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
95
|
+
let query = $state('')
|
|
96
|
+
const effectiveFilter = $derived(filter ?? (searchable ? query : undefined))
|
|
97
|
+
|
|
98
|
+
// Sort siblings (recursively) before anything else.
|
|
99
|
+
const sortedNodes = $derived.by(() => {
|
|
100
|
+
if (!sort) return nodes
|
|
101
|
+
const cmp = typeof sort === 'function' ? sort : (a: TreeNode, b: TreeNode) => (sort === 'asc' ? 1 : -1) * a.label.localeCompare(b.label)
|
|
102
|
+
return sortTreeNodes(nodes, cmp)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// --- Lazy loading: merge loaded children into the (controlled) node tree -----
|
|
106
|
+
let loaded = $state(new Map<string, TreeNode[]>())
|
|
107
|
+
let loadingIds = $state(new Set<string>())
|
|
108
|
+
|
|
109
|
+
function inject(list: ReadonlyArray<TreeNode>): TreeNode[] {
|
|
110
|
+
return list.map((n) => {
|
|
111
|
+
if (n.lazy && loaded.has(n.id)) return { ...n, children: inject(loaded.get(n.id)!), lazy: false }
|
|
112
|
+
if (n.children?.length) return { ...n, children: inject(n.children) }
|
|
113
|
+
return n
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
const effectiveNodes = $derived(inject(sortedNodes))
|
|
117
|
+
|
|
118
|
+
// --- Inline rename ----------------------------------------------------------
|
|
119
|
+
let editingId = $state<string | null>(null)
|
|
120
|
+
let draftLabel = $state('')
|
|
121
|
+
function startEdit(node: TreeNode) {
|
|
122
|
+
if (!editable || node.disabled) return
|
|
123
|
+
editingId = node.id
|
|
124
|
+
draftLabel = node.label
|
|
125
|
+
tick().then(() => treeEl?.querySelector<HTMLInputElement>('.sv-tree__edit')?.select())
|
|
126
|
+
}
|
|
127
|
+
function commitEdit() {
|
|
128
|
+
if (editingId == null) return
|
|
129
|
+
const id = editingId
|
|
130
|
+
const label = draftLabel.trim()
|
|
131
|
+
editingId = null
|
|
132
|
+
if (label) onRename?.(id, label)
|
|
133
|
+
}
|
|
134
|
+
function cancelEdit() { editingId = null }
|
|
135
|
+
|
|
136
|
+
// --- Drag-drop reorder ------------------------------------------------------
|
|
137
|
+
let dragId = $state<string | null>(null)
|
|
138
|
+
let dropId = $state<string | null>(null)
|
|
139
|
+
let dropPos = $state<TreeDropPosition | null>(null)
|
|
140
|
+
function computeDropPos(e: DragEvent, node: TreeNode): TreeDropPosition {
|
|
141
|
+
const el = e.currentTarget as HTMLElement
|
|
142
|
+
const r = el.getBoundingClientRect()
|
|
143
|
+
const y = (e.clientY - r.top) / r.height
|
|
144
|
+
const canNest = !!node.children?.length || !!node.lazy
|
|
145
|
+
if (y < 0.3) return 'before'
|
|
146
|
+
if (y > 0.7 || !canNest) return y > 0.5 ? 'after' : (canNest ? 'inside' : 'after')
|
|
147
|
+
return 'inside'
|
|
148
|
+
}
|
|
149
|
+
function onRowDragStart(e: DragEvent, node: TreeNode) {
|
|
150
|
+
dragId = node.id
|
|
151
|
+
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move'
|
|
152
|
+
}
|
|
153
|
+
function onRowDragOver(e: DragEvent, node: TreeNode) {
|
|
154
|
+
if (!dragId || dragId === node.id) return
|
|
155
|
+
e.preventDefault()
|
|
156
|
+
dropId = node.id
|
|
157
|
+
dropPos = computeDropPos(e, node)
|
|
158
|
+
}
|
|
159
|
+
function onRowDrop(e: DragEvent, node: TreeNode) {
|
|
160
|
+
e.preventDefault()
|
|
161
|
+
if (dragId && dragId !== node.id && dropPos) onMove?.(dragId, node.id, dropPos)
|
|
162
|
+
dragId = dropId = dropPos = null
|
|
163
|
+
}
|
|
164
|
+
function endDrag() { dragId = dropId = dropPos = null }
|
|
165
|
+
|
|
166
|
+
function findNode(list: ReadonlyArray<TreeNode>, id: string): TreeNode | null {
|
|
167
|
+
for (const n of list) {
|
|
168
|
+
if (n.id === id) return n
|
|
169
|
+
if (n.children) { const f = findNode(n.children, id); if (f) return f }
|
|
170
|
+
}
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function handleToggle(id: string, expanded: boolean) {
|
|
175
|
+
onToggle?.(id, expanded)
|
|
176
|
+
if (!expanded || !loadChildren) return
|
|
177
|
+
// Search the merged tree, not the raw prop: a nested lazy node (revealed by
|
|
178
|
+
// a parent's earlier load) lives inside `loaded`, never in `nodes`.
|
|
179
|
+
const node = findNode(effectiveNodes, id)
|
|
180
|
+
if (!node?.lazy || loaded.has(id) || loadingIds.has(id)) return
|
|
181
|
+
loadingIds = new Set(loadingIds).add(id)
|
|
182
|
+
// A placeholder row shows immediately while the promise resolves.
|
|
183
|
+
loaded = new Map(loaded).set(id, [{ id: `${id}__loading`, label: loadingText, disabled: true }])
|
|
184
|
+
try {
|
|
185
|
+
const kids = await loadChildren(node)
|
|
186
|
+
loaded = new Map(loaded).set(id, kids)
|
|
187
|
+
} catch {
|
|
188
|
+
loaded = new Map(loaded).set(id, [])
|
|
189
|
+
} finally {
|
|
190
|
+
const next = new Set(loadingIds); next.delete(id); loadingIds = next
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const tree = createTree({
|
|
195
|
+
nodes: () => effectiveNodes,
|
|
196
|
+
selected: () => selected,
|
|
197
|
+
onSelect: (id) => onSelect?.(id),
|
|
198
|
+
expandedIds: () => expandedIds,
|
|
199
|
+
onToggle: handleToggle,
|
|
200
|
+
checkable: () => checkable,
|
|
201
|
+
checked: () => checked,
|
|
202
|
+
onCheck: (ids) => onCheck?.(ids),
|
|
203
|
+
ariaLabel: () => ariaLabel,
|
|
204
|
+
dir: () => dir,
|
|
205
|
+
filter: () => effectiveFilter,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// --- Virtualization ---------------------------------------------------------
|
|
209
|
+
// Windowing runs on the shared `createSvelteVirtualizer` (cumulative offsets +
|
|
210
|
+
// binary search + variable-size support). Rows stay keyed by node id (NOT slot
|
|
211
|
+
// recycling): a tree row carries per-row identity - an in-progress inline
|
|
212
|
+
// rename, an active drag, roving focus - that must stay pinned to its node
|
|
213
|
+
// while scrolling. The engine only supplies the window + each row's offset.
|
|
214
|
+
const scrollable = $derived(virtual || height != null)
|
|
215
|
+
let treeEl = $state<HTMLDivElement | null>(null)
|
|
216
|
+
let viewportH = $state(0)
|
|
217
|
+
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 30)
|
|
218
|
+
const uniform = $derived(typeof rowHeight === 'number')
|
|
219
|
+
const rowSize = (i: number) => {
|
|
220
|
+
const r = tree.rows[i]
|
|
221
|
+
return r ? (typeof rowHeight === 'function' ? rowHeight(r.node, i) : rowHeight) : rowHeightBase
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => rowSize(i))
|
|
225
|
+
// Generous overscan buffers fast flings; effective viewport never 0 (see SvListBox).
|
|
226
|
+
const OVERSCAN = 20
|
|
227
|
+
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, height ?? 320))
|
|
228
|
+
// Seed with the initial (untracked) values so the first paint renders a full window.
|
|
229
|
+
const virtualizer = createSvelteVirtualizer(
|
|
230
|
+
untrack(() => ({ count: tree.rows.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
231
|
+
)
|
|
232
|
+
// untrack keeps the virtualizer's internal `version` write out of these
|
|
233
|
+
// effects' dependencies, so they re-run only on their real inputs.
|
|
234
|
+
$effect(() => {
|
|
235
|
+
const count = tree.rows.length
|
|
236
|
+
const es = estimateSize
|
|
237
|
+
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
238
|
+
})
|
|
239
|
+
$effect(() => {
|
|
240
|
+
const vh = effViewport
|
|
241
|
+
untrack(() => virtualizer.setViewportHeight(vh))
|
|
242
|
+
})
|
|
243
|
+
const totalSize = $derived.by(() => { virtualizer.version; return virtualizer.getTotalSize() })
|
|
244
|
+
const visibleRows = $derived.by(() => {
|
|
245
|
+
if (!virtual) return tree.rows
|
|
246
|
+
virtualizer.version
|
|
247
|
+
const st = virtualizer.getState()
|
|
248
|
+
return tree.rows.slice(st.startIndex, st.endIndex + 1)
|
|
249
|
+
})
|
|
250
|
+
const rowTop = (i: number) => virtualizer.getOffsetForIndex(i)
|
|
251
|
+
const rowHeightPx = (i: number) => (virtual ? virtualizer.getSizeForIndex(i) : rowSize(i))
|
|
252
|
+
|
|
253
|
+
// JS-driven scroll for virtual mode (see js-scroller): no native scroll, so a
|
|
254
|
+
// fast thumb-drag never flashes an empty tree.
|
|
255
|
+
const scroller = createJsScroller({
|
|
256
|
+
virtualizer,
|
|
257
|
+
totalSize: () => totalSize,
|
|
258
|
+
viewport: () => effViewport,
|
|
259
|
+
lineStep: () => rowHeightBase,
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// Follow the core's roving focus row; scroll it into view first when windowed.
|
|
263
|
+
let lastFocusTick = 0
|
|
264
|
+
$effect(() => {
|
|
265
|
+
if (tree.focusTick === lastFocusTick) return
|
|
266
|
+
lastFocusTick = tree.focusTick
|
|
267
|
+
const i = tree.activeIndex
|
|
268
|
+
if (virtual) untrack(() => scroller.ensureIndexVisible(i))
|
|
269
|
+
tick().then(() => treeEl?.querySelector<HTMLElement>(`[data-row="${i}"]`)?.focus())
|
|
270
|
+
})
|
|
271
|
+
</script>
|
|
272
|
+
|
|
273
|
+
<div class="sv-tree-host" class:has-search={searchable} dir={resolvedDir}>
|
|
274
|
+
{#if searchable}
|
|
275
|
+
<div class="sv-tree__search">
|
|
276
|
+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>
|
|
277
|
+
<input class="sv-tree__search-input" type="text" placeholder={searchPlaceholder} bind:value={query} aria-label={searchPlaceholder} />
|
|
278
|
+
{#if query}<button type="button" class="sv-tree__search-clear" aria-label={clearSearchLabel} onclick={() => (query = '')}>×</button>{/if}
|
|
279
|
+
</div>
|
|
280
|
+
{/if}
|
|
281
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
282
|
+
<div
|
|
283
|
+
bind:this={treeEl}
|
|
284
|
+
class="sv-tree"
|
|
285
|
+
class:sv-tree--scroll={scrollable && !virtual}
|
|
286
|
+
class:is-virtual={virtual}
|
|
287
|
+
dir={resolvedDir}
|
|
288
|
+
style:height={height != null ? `${height}px` : undefined}
|
|
289
|
+
onwheel={virtual ? scroller.onWheel : undefined}
|
|
290
|
+
onpointerdown={virtual ? scroller.onContentDown : undefined}
|
|
291
|
+
onpointermove={virtual ? scroller.onContentMove : undefined}
|
|
292
|
+
onpointerup={virtual ? scroller.onContentUp : undefined}
|
|
293
|
+
onpointercancel={virtual ? scroller.onContentUp : undefined}
|
|
294
|
+
bind:clientHeight={viewportH}
|
|
295
|
+
{...tree.treeProps()}
|
|
296
|
+
>
|
|
297
|
+
{#each visibleRows as item (item.node.id)}
|
|
298
|
+
{@render treeRow(item)}
|
|
299
|
+
{/each}
|
|
300
|
+
{#if virtual && scroller.maxOffset > 0}
|
|
301
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
302
|
+
<div class="sv-tree__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
303
|
+
<div class="sv-tree__thumb" class:is-dragging={scroller.dragging} style:height={`${scroller.thumbH}px`} style:transform={`translateY(${scroller.thumbTop}px)`} onpointerdown={scroller.onThumbDown} onpointermove={scroller.onThumbMove} onpointerup={scroller.onThumbUp} onpointercancel={scroller.onThumbUp}></div>
|
|
304
|
+
</div>
|
|
305
|
+
{/if}
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
{#snippet treeRow(item: (typeof tree.rows)[number])}
|
|
310
|
+
{@const cs = checkable ? tree.checkStateOf(item.node) : 'unchecked'}
|
|
311
|
+
{@const isLoading = loadingIds.has(item.node.id)}
|
|
312
|
+
{@const rowProps = tree.itemProps(item)}
|
|
313
|
+
<div
|
|
314
|
+
class="sv-tree__row"
|
|
315
|
+
class:is-selected={item.node.id === selected}
|
|
316
|
+
class:is-disabled={item.node.disabled}
|
|
317
|
+
class:is-dragging={dragId === item.node.id}
|
|
318
|
+
class:drop-before={dropId === item.node.id && dropPos === 'before'}
|
|
319
|
+
class:drop-after={dropId === item.node.id && dropPos === 'after'}
|
|
320
|
+
class:drop-inside={dropId === item.node.id && dropPos === 'inside'}
|
|
321
|
+
style:height={`${rowHeightPx(item.index)}px`}
|
|
322
|
+
style:padding-inline-start={`${item.depth * 18 + 6}px`}
|
|
323
|
+
style:transform={virtual ? `translateY(${rowTop(item.index) - scroller.scrollOffset}px)` : undefined}
|
|
324
|
+
draggable={reorderable && editingId !== item.node.id && !item.node.disabled}
|
|
325
|
+
ondragstart={reorderable ? (e) => onRowDragStart(e, item.node) : undefined}
|
|
326
|
+
ondragover={reorderable ? (e) => onRowDragOver(e, item.node) : undefined}
|
|
327
|
+
ondragleave={reorderable ? () => { if (dropId === item.node.id) { dropId = null; dropPos = null } } : undefined}
|
|
328
|
+
ondrop={reorderable ? (e) => onRowDrop(e, item.node) : undefined}
|
|
329
|
+
ondragend={reorderable ? endDrag : undefined}
|
|
330
|
+
ondblclick={editable ? () => startEdit(item.node) : undefined}
|
|
331
|
+
{...rowProps}
|
|
332
|
+
onkeydown={(e) => {
|
|
333
|
+
rowProps.onkeydown(e)
|
|
334
|
+
if (editable && e.key === 'F2') { e.preventDefault(); startEdit(item.node) }
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
<button
|
|
338
|
+
type="button"
|
|
339
|
+
class="sv-tree__twist"
|
|
340
|
+
class:is-open={item.open}
|
|
341
|
+
class:is-leaf={!item.hasChildren}
|
|
342
|
+
class:is-loading={isLoading}
|
|
343
|
+
tabindex="-1"
|
|
344
|
+
aria-hidden="true"
|
|
345
|
+
onclick={(e) => { e.stopPropagation(); tree.toggleExpand(item.node) }}
|
|
346
|
+
>
|
|
347
|
+
{#if isLoading}
|
|
348
|
+
<svg class="sv-tree__spin" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-6.2-8.6" /></svg>
|
|
349
|
+
{:else if item.hasChildren}
|
|
350
|
+
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
|
351
|
+
{/if}
|
|
352
|
+
</button>
|
|
353
|
+
{#if checkable}
|
|
354
|
+
<button type="button" class="sv-tree__check is-{cs}" tabindex="-1" aria-hidden="true" onclick={(e) => { e.stopPropagation(); tree.toggleCheck(item.node) }}>
|
|
355
|
+
{#if cs === 'checked'}<svg viewBox="0 0 16 16"><path d="M3.5 8.5l3 3 6-6.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
356
|
+
{:else if cs === 'indeterminate'}<svg viewBox="0 0 16 16"><path d="M4 8h8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>{/if}
|
|
357
|
+
</button>
|
|
358
|
+
{/if}
|
|
359
|
+
{#if editingId === item.node.id}
|
|
360
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
361
|
+
<input
|
|
362
|
+
class="sv-tree__edit"
|
|
363
|
+
bind:value={draftLabel}
|
|
364
|
+
onclick={(e) => e.stopPropagation()}
|
|
365
|
+
onkeydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); commitEdit() } else if (e.key === 'Escape') { e.preventDefault(); cancelEdit() } }}
|
|
366
|
+
onblur={commitEdit}
|
|
367
|
+
/>
|
|
368
|
+
{:else}
|
|
369
|
+
<span class="sv-tree__label">{item.node.label}</span>
|
|
370
|
+
{/if}
|
|
371
|
+
</div>
|
|
372
|
+
{/snippet}
|
|
373
|
+
|
|
374
|
+
<style>
|
|
375
|
+
.sv-tree-host { display: contents; }
|
|
376
|
+
.sv-tree-host.has-search { display: inline-flex; flex-direction: column; gap: 6px; }
|
|
377
|
+
.sv-tree__search {
|
|
378
|
+
display: flex; align-items: center; gap: 7px; width: 260px; box-sizing: border-box; padding: 0 10px; height: 32px;
|
|
379
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-muted, #64748b);
|
|
380
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
381
|
+
}
|
|
382
|
+
.sv-tree__search:focus-within { border-color: var(--sg-accent, #2563eb); box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent, #2563eb) 22%, transparent); }
|
|
383
|
+
.sv-tree__search-input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: var(--sg-fg, #0f172a); font: inherit; font-size: 13px; }
|
|
384
|
+
.sv-tree__search-clear { background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; font-size: 16px; line-height: 1; padding: 0 2px; }
|
|
385
|
+
.sv-tree {
|
|
386
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
387
|
+
width: 260px; padding: 4px; user-select: none;
|
|
388
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
389
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 8px);
|
|
390
|
+
font-size: 13px;
|
|
391
|
+
}
|
|
392
|
+
/* Non-virtual (height-capped) tree uses native scroll. */
|
|
393
|
+
.sv-tree--scroll { overflow-y: auto; overscroll-behavior: contain; overflow-anchor: none; }
|
|
394
|
+
/* Virtual tree: JS-driven, no native scroll - rows are moved by `scrollOffset`. */
|
|
395
|
+
.sv-tree.is-virtual { position: relative; overflow: hidden; touch-action: none; }
|
|
396
|
+
.sv-tree.is-virtual .sv-tree__row { position: absolute; inset-inline: 4px; top: 4px; }
|
|
397
|
+
.sv-tree__sb { position: absolute; top: 4px; right: 2px; bottom: 4px; width: 10px; z-index: 2; }
|
|
398
|
+
.sv-tree__thumb {
|
|
399
|
+
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px; border-radius: 4px;
|
|
400
|
+
background: color-mix(in srgb, currentColor 26%, transparent); cursor: default; touch-action: none;
|
|
401
|
+
}
|
|
402
|
+
.sv-tree__thumb:hover, .sv-tree__thumb.is-dragging { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
403
|
+
.sv-tree__row {
|
|
404
|
+
display: flex; align-items: center; gap: 4px; box-sizing: border-box; padding-inline-end: 8px;
|
|
405
|
+
border-radius: 6px; cursor: pointer; outline: none;
|
|
406
|
+
}
|
|
407
|
+
.sv-tree__row:hover:not(.is-disabled) { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
408
|
+
.sv-tree__row.is-selected { background: color-mix(in srgb, var(--_accent) 15%, transparent); color: var(--_accent); font-weight: 600; }
|
|
409
|
+
.sv-tree__row.is-disabled { opacity: 0.45; cursor: not-allowed; }
|
|
410
|
+
.sv-tree__row:focus-visible { box-shadow: inset 0 0 0 2px var(--sg-focus-ring, var(--_accent)); }
|
|
411
|
+
.sv-tree__twist {
|
|
412
|
+
display: grid; place-items: center; width: 18px; height: 18px; flex: none;
|
|
413
|
+
background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; transition: transform 0.12s;
|
|
414
|
+
}
|
|
415
|
+
.sv-tree__twist.is-open { transform: rotate(90deg); }
|
|
416
|
+
.sv-tree[dir='rtl'] .sv-tree__twist:not(.is-open) svg { transform: scaleX(-1); }
|
|
417
|
+
.sv-tree__twist.is-leaf { visibility: hidden; }
|
|
418
|
+
.sv-tree__twist.is-loading { transform: none; }
|
|
419
|
+
.sv-tree__spin { animation: sv-tree-spin 0.7s linear infinite; }
|
|
420
|
+
@keyframes sv-tree-spin { to { transform: rotate(360deg); } }
|
|
421
|
+
@media (prefers-reduced-motion: reduce) { .sv-tree__spin { animation: none; } }
|
|
422
|
+
.sv-tree__check {
|
|
423
|
+
display: grid; place-items: center; width: 16px; height: 16px; flex: none; padding: 0;
|
|
424
|
+
border: 1.5px solid var(--sg-border, #cbd5e1); border-radius: 4px; background: var(--sg-input-bg, #fff);
|
|
425
|
+
color: var(--sg-on-accent, #fff); cursor: pointer;
|
|
426
|
+
}
|
|
427
|
+
.sv-tree__check.is-checked, .sv-tree__check.is-indeterminate { background: var(--_accent); border-color: var(--_accent); }
|
|
428
|
+
.sv-tree__check svg { width: 100%; height: 100%; }
|
|
429
|
+
.sv-tree__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
430
|
+
.sv-tree__edit {
|
|
431
|
+
flex: 1; min-width: 0; height: 22px; padding: 0 6px; margin: 0; font: inherit; font-size: 13px;
|
|
432
|
+
border: 1px solid var(--_accent); border-radius: 5px; background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); outline: none;
|
|
433
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent);
|
|
434
|
+
}
|
|
435
|
+
/* Drag-drop feedback */
|
|
436
|
+
.sv-tree__row.is-dragging { opacity: 0.4; }
|
|
437
|
+
.sv-tree__row.drop-inside { background: color-mix(in srgb, var(--_accent) 18%, transparent); box-shadow: inset 0 0 0 1px var(--_accent); }
|
|
438
|
+
.sv-tree__row.drop-before, .sv-tree__row.drop-after { position: relative; }
|
|
439
|
+
.sv-tree__row.drop-before::before, .sv-tree__row.drop-after::after {
|
|
440
|
+
content: ''; position: absolute; inset-inline: 4px; height: 2px; background: var(--_accent); border-radius: 2px; z-index: 1;
|
|
441
|
+
}
|
|
442
|
+
.sv-tree__row.drop-before::before { top: -1px; }
|
|
443
|
+
.sv-tree__row.drop-after::after { bottom: -1px; }
|
|
444
|
+
</style>
|