@svgrid/grid 2.2.35 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -178
- package/dist/GridMenus.svelte +705 -705
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +275 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +160 -160
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +374 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.css +2814 -2814
- package/dist/SvGrid.svelte +3490 -3490
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +266 -266
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +326 -326
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +289 -289
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +112 -112
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +235 -235
- package/dist/cdn/{GridMenus-oQYElQll.js → GridMenus-BpVcKyUr.js} +2 -2
- package/dist/cdn/{GridMenus-T-bZNprc.js → GridMenus-CeXL5IW4.js} +2 -2
- package/dist/cdn/{SvDateTimePicker-CHnUkWcH.js → SvDateTimePicker-DSUhIta8.js} +2 -2
- package/dist/cdn/{SvDateTimePicker-CRQH_U7E.js → SvDateTimePicker-h9kPyszT.js} +2 -2
- package/dist/cdn/{SvGridChart-BwVos976.js → SvGridChart-Bs2GIR2Q.js} +1 -1
- package/dist/cdn/{SvGridChart-BEJmNNx9.js → SvGridChart-CwhFz7GV.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-BOknOkrP.js → SvGridChartPanel-1WXuStzM.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-D2PjII4O.js → SvGridChartPanel-e5_nqNP9.js} +1 -1
- package/dist/cdn/{SvGridChartView-00LPUHL1.js → SvGridChartView-SmPW10dI.js} +1 -1
- package/dist/cdn/{SvGridChartView-BhUEJ5ki.js → SvGridChartView-eSPuJE6g.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-B1ZcLpgs.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CtdbHcIS.js} +1 -1
- package/dist/cdn/{src-Cm7C_WOZ.js → src-DbeN1ReZ.js} +24 -24
- package/dist/cdn/{src-72QTH8lh.js → src-DmEQFW5_.js} +23 -23
- package/dist/cdn/svgrid.js +5 -5
- package/dist/cdn/svgrid.svelte-external.js +5 -5
- package/dist/chart-export.js +8 -8
- 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/themes/index.js +28 -9
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -178
- package/src/GridMenus.svelte +705 -705
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +275 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +160 -160
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +374 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +3680 -3680
- package/src/SvGrid.css +2814 -2814
- package/src/SvGrid.svelte +3490 -3490
- package/src/SvGrid.types.ts +1708 -1708
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +266 -266
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +326 -326
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +289 -289
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +112 -112
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +235 -235
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1391 -1391
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +805 -805
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +118 -118
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2295 -2295
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1261 -1261
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +949 -949
- package/src/editing.ts +606 -606
- package/src/editor-contract.ts +164 -164
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +157 -157
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/index.ts +724 -724
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +172 -172
- package/src/menus.ts +567 -567
- package/src/merge-objects.ts +48 -48
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/_svelte_metadata.json +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/_metadata.json +178 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js +3843 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js +23 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js +1925 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js +206 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js +20 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js +264 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js +698 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/package.json +3 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js +135 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js +57 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_attachments.js +2 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js +46691 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js +239 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_events.js +2 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js +6 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_client.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js +6 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js +7 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_server.js +6 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js +85 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js +561 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js +26 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js +124 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_server.js +2 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js +91 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js +242 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js +497 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js +4929 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js +192 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js +10 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js.map +1 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js +140 -0
- package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js.map +1 -0
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/row-resize.ts +210 -210
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +491 -491
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +526 -526
- package/src/svgrid.async-editor-options.test.ts +271 -271
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +705 -705
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +123 -123
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +331 -322
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- 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 +278 -257
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
- package/themes/ag-alpine.css +2 -2
- package/themes/antd.css +4 -4
- package/themes/atlassian.css +1 -1
- package/themes/ember.css +2 -2
- package/themes/fluent.css +1 -1
- package/themes/github.css +1 -1
- package/themes/nord.css +2 -2
- package/themes/notion.css +2 -2
- package/themes/salesforce.css +1 -1
- package/themes/tailwind.css +1 -1
- package/themes/vercel.css +1 -1
|
@@ -1,374 +1,374 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SvDropDownList - a single-select dropdown (trigger button + portalled list,
|
|
4
|
-
* no typing). Parity: Smart `smart-drop-down-list`. Controlled via `value` +
|
|
5
|
-
* `onChange`. Popover escapes scroll clipping via popover.ts.
|
|
6
|
-
*
|
|
7
|
-
* One styled renderer over the headless `createDropdownList` core (state +
|
|
8
|
-
* keyboard + ARIA); only portal/measure/scroll render concerns live here.
|
|
9
|
-
*/
|
|
10
|
-
import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
|
|
11
|
-
import { startPanelResize } from './panel-resize'
|
|
12
|
-
import { createDismissableLayer } from './a11y/dismissable'
|
|
13
|
-
import { groupOptions, normalizeOptions, flattenForVirtual, type ListOption, type IndexedOption, type RowHeight } from './list-option'
|
|
14
|
-
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
15
|
-
import { createJsScroller } from './js-scroller.svelte'
|
|
16
|
-
import SvField from './SvField.svelte'
|
|
17
|
-
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
18
|
-
import { createDropdownList } from './createDropdownList.svelte'
|
|
19
|
-
import { untrack, type Snippet } from 'svelte'
|
|
20
|
-
|
|
21
|
-
type Props = SvEditorProps & {
|
|
22
|
-
options: ReadonlyArray<ListOption>
|
|
23
|
-
value?: string | number | null
|
|
24
|
-
onChange?: (value: string | number) => void
|
|
25
|
-
placeholder?: string
|
|
26
|
-
autoOpen?: boolean
|
|
27
|
-
/** Window the option list (render only visible) for large sets. */
|
|
28
|
-
virtual?: boolean
|
|
29
|
-
/** Option height in px - a number, or a per-option function for variable
|
|
30
|
-
* heights. Must match the rendered row height. Default 34. */
|
|
31
|
-
rowHeight?: RowHeight
|
|
32
|
-
/** Height (px) of a group heading row when virtualized + grouped. Default 28. */
|
|
33
|
-
groupHeaderHeight?: number
|
|
34
|
-
/** Max options shown before the panel scrolls (native-select behaviour). Default 8. */
|
|
35
|
-
maxRows?: number
|
|
36
|
-
/** Show a bottom drag grip so the user can resize the open panel's height
|
|
37
|
-
* (only while it opens downward). Off by default. */
|
|
38
|
-
resizable?: boolean
|
|
39
|
-
/** Custom render for each option (receives the ListOption). */
|
|
40
|
-
item?: Snippet<[ListOption]>
|
|
41
|
-
/** Content pinned above the option list. */
|
|
42
|
-
header?: Snippet
|
|
43
|
-
/** Content pinned below the option list. */
|
|
44
|
-
footer?: Snippet
|
|
45
|
-
/** Shown when there are no options. */
|
|
46
|
-
noData?: Snippet
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let {
|
|
50
|
-
options,
|
|
51
|
-
value = null,
|
|
52
|
-
onChange,
|
|
53
|
-
placeholder = 'Select…',
|
|
54
|
-
disabled = false,
|
|
55
|
-
readonly = false,
|
|
56
|
-
name,
|
|
57
|
-
size = 'md',
|
|
58
|
-
ariaLabel,
|
|
59
|
-
invalid = false,
|
|
60
|
-
required = false,
|
|
61
|
-
error,
|
|
62
|
-
label,
|
|
63
|
-
hint,
|
|
64
|
-
dir,
|
|
65
|
-
id,
|
|
66
|
-
autoOpen = false,
|
|
67
|
-
virtual = false,
|
|
68
|
-
rowHeight = 34,
|
|
69
|
-
groupHeaderHeight = 28,
|
|
70
|
-
maxRows = 8,
|
|
71
|
-
resizable = false,
|
|
72
|
-
item,
|
|
73
|
-
header,
|
|
74
|
-
footer,
|
|
75
|
-
noData,
|
|
76
|
-
}: Props = $props()
|
|
77
|
-
|
|
78
|
-
const autoId = nextEditorId('sv-ddl')
|
|
79
|
-
const uid = $derived(id ?? autoId)
|
|
80
|
-
|
|
81
|
-
// Tolerate primitive / partial options - normalize so labels render + keys are stable.
|
|
82
|
-
const norm = $derived(normalizeOptions(options))
|
|
83
|
-
|
|
84
|
-
// Virtualization windows the option list (grouped lists too, via the shared
|
|
85
|
-
// virtualizer + a flattened header/option model with per-row sizes).
|
|
86
|
-
const useVirtual = $derived(virtual)
|
|
87
|
-
let viewportH = $state(0)
|
|
88
|
-
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 34)
|
|
89
|
-
const flat = $derived(flattenForVirtual(norm, { rowHeight, groupHeaderHeight }))
|
|
90
|
-
const uniform = $derived(!flat.hasGroups && typeof rowHeight === 'number')
|
|
91
|
-
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => flat.sizeAt(i))
|
|
92
|
-
|
|
93
|
-
// Generous overscan buffers fast flings; effective viewport never 0 (see SvListBox).
|
|
94
|
-
const OVERSCAN = 20
|
|
95
|
-
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, maxRows * rowHeightBase))
|
|
96
|
-
|
|
97
|
-
// Seed with the initial (untracked) values so the first paint of the open panel
|
|
98
|
-
// renders a full window; the effects below keep it synced.
|
|
99
|
-
const virtualizer = createSvelteVirtualizer(
|
|
100
|
-
untrack(() => ({ count: flat.entries.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
101
|
-
)
|
|
102
|
-
// untrack keeps the virtualizer's internal `version` write out of these
|
|
103
|
-
// effects' dependencies, so they re-run only on their real inputs.
|
|
104
|
-
$effect(() => {
|
|
105
|
-
const count = flat.entries.length
|
|
106
|
-
const es = estimateSize
|
|
107
|
-
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
108
|
-
})
|
|
109
|
-
$effect(() => {
|
|
110
|
-
const vh = effViewport
|
|
111
|
-
untrack(() => virtualizer.setViewportHeight(vh))
|
|
112
|
-
})
|
|
113
|
-
const vItems = $derived.by(() => {
|
|
114
|
-
virtualizer.version
|
|
115
|
-
return virtualizer.getVirtualItems()
|
|
116
|
-
})
|
|
117
|
-
const totalSize = $derived.by(() => {
|
|
118
|
-
virtualizer.version
|
|
119
|
-
return virtualizer.getTotalSize()
|
|
120
|
-
})
|
|
121
|
-
// JS-driven scroll (see SvListBox / js-scroller): no native scroll, so a fast
|
|
122
|
-
// thumb-drag never flashes the empty panel.
|
|
123
|
-
const scroller = createJsScroller({
|
|
124
|
-
virtualizer,
|
|
125
|
-
totalSize: () => totalSize,
|
|
126
|
-
viewport: () => effViewport,
|
|
127
|
-
lineStep: () => rowHeightBase,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
let triggerEl = $state<HTMLButtonElement | null>(null)
|
|
131
|
-
let panelEl = $state<HTMLDivElement | null>(null)
|
|
132
|
-
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
133
|
-
// User-chosen panel height (px) once they drag the resize grip; null = auto.
|
|
134
|
-
let userHeight = $state<number | null>(null)
|
|
135
|
-
// Effective height cap: the user's drag if any (clamped to the viewport
|
|
136
|
-
// ceiling), otherwise the comfortable bounds-aware height.
|
|
137
|
-
const panelMaxH = $derived(
|
|
138
|
-
resizable && userHeight != null ? Math.min(userHeight, rect.availHeight) : rect.maxHeight,
|
|
139
|
-
)
|
|
140
|
-
const showGrip = $derived(resizable && !rect.openUpward)
|
|
141
|
-
|
|
142
|
-
// The virtual panel uses NO native scroll, so its rows are absolutely
|
|
143
|
-
// positioned and can't size the panel. Give it an explicit height (content up
|
|
144
|
-
// to `maxRows`, capped by the resized / bounds-aware ceiling) so the flex
|
|
145
|
-
// viewport (`.sv-ddl__vp`) has a definite height to fill.
|
|
146
|
-
const virtualPanelH = $derived(
|
|
147
|
-
Math.min(
|
|
148
|
-
Math.min(totalSize, Math.max(rowHeightBase, maxRows * rowHeightBase)) + 8,
|
|
149
|
-
rect.maxHeight || Number.POSITIVE_INFINITY,
|
|
150
|
-
resizable && userHeight != null ? panelMaxH : Number.POSITIVE_INFINITY,
|
|
151
|
-
),
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
const ddl = createDropdownList({
|
|
155
|
-
options: () => norm,
|
|
156
|
-
value: () => value,
|
|
157
|
-
onChange: (v) => onChange?.(v),
|
|
158
|
-
disabled: () => disabled,
|
|
159
|
-
readonly: () => readonly,
|
|
160
|
-
ariaLabel: () => ariaLabel,
|
|
161
|
-
focusTrigger: () => triggerEl?.focus(),
|
|
162
|
-
id: () => uid,
|
|
163
|
-
invalid: () => invalid,
|
|
164
|
-
required: () => required,
|
|
165
|
-
error: () => error,
|
|
166
|
-
hint: () => hint,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
const selected = $derived(ddl.selected)
|
|
170
|
-
|
|
171
|
-
function updatePos() {
|
|
172
|
-
if (!triggerEl) return
|
|
173
|
-
// When the user has resized, anchor to THAT height so the flip decision and
|
|
174
|
-
// upward top stay correct; otherwise size to the content up to `maxRows` items
|
|
175
|
-
// (native-select behaviour: no scrollbar until the list exceeds the cap). The
|
|
176
|
-
// +PANEL_CHROME budgets the panel's own padding (8) + border (2) so a list
|
|
177
|
-
// that exactly fills `maxRows` is not clipped by the border-box max-height.
|
|
178
|
-
const PANEL_CHROME = 10
|
|
179
|
-
const estimatedHeight =
|
|
180
|
-
resizable && userHeight != null ? userHeight : Math.min(norm.length, maxRows) * rowHeightBase + PANEL_CHROME
|
|
181
|
-
rect = anchoredRect(triggerEl.getBoundingClientRect(), { estimatedHeight })
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function startResize(e: PointerEvent) {
|
|
185
|
-
if (!panelEl) return
|
|
186
|
-
startPanelResize(e, {
|
|
187
|
-
startHeight: panelEl.clientHeight,
|
|
188
|
-
min: 96,
|
|
189
|
-
max: () => rect.availHeight,
|
|
190
|
-
onHeight: (h) => {
|
|
191
|
-
userHeight = h
|
|
192
|
-
updatePos()
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
$effect(() => {
|
|
198
|
-
if (!ddl.open) return
|
|
199
|
-
updatePos()
|
|
200
|
-
const rp = () => updatePos()
|
|
201
|
-
window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
|
|
202
|
-
// Outside-pointer dismissal via the shared layer stack (Escape stays with
|
|
203
|
-
// the core keyboard handler). Because this layer is pushed ON TOP, a
|
|
204
|
-
// dropdown opened inside a dialog/drawer closes itself first and never
|
|
205
|
-
// dismisses the dialog underneath it.
|
|
206
|
-
const layer = createDismissableLayer({
|
|
207
|
-
element: () => [triggerEl, panelEl],
|
|
208
|
-
onDismiss: () => ddl.close(),
|
|
209
|
-
closeOnEscape: false,
|
|
210
|
-
})
|
|
211
|
-
layer.activate()
|
|
212
|
-
return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); layer.release() }
|
|
213
|
-
})
|
|
214
|
-
// Keep the active option scrolled into view (render concern) - via the scroller
|
|
215
|
-
// (virtual) or scrollIntoView (native non-virtual).
|
|
216
|
-
$effect(() => {
|
|
217
|
-
if (!ddl.open) return
|
|
218
|
-
const i = ddl.activeIndex
|
|
219
|
-
if (useVirtual) {
|
|
220
|
-
const flatIdx = flat.optionFlatIndex[i]
|
|
221
|
-
if (flatIdx == null) return
|
|
222
|
-
untrack(() => scroller.ensureIndexVisible(flatIdx))
|
|
223
|
-
} else {
|
|
224
|
-
queueMicrotask(() => panelEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
225
|
-
}
|
|
226
|
-
})
|
|
227
|
-
function focusOpen(node: HTMLButtonElement) { if (autoOpen) { node.focus(); ddl.openPanel() } }
|
|
228
|
-
</script>
|
|
229
|
-
|
|
230
|
-
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
231
|
-
<button
|
|
232
|
-
bind:this={triggerEl}
|
|
233
|
-
class="sv-ddl sv-ddl--{size}"
|
|
234
|
-
class:is-open={ddl.open}
|
|
235
|
-
class:is-disabled={disabled}
|
|
236
|
-
class:is-invalid={invalid}
|
|
237
|
-
{...ddl.triggerProps()}
|
|
238
|
-
use:focusOpen
|
|
239
|
-
>
|
|
240
|
-
<span class="sv-ddl__value" class:is-placeholder={!selected}>{#if selected?.color}<span class="sv-ddl__swatch" style:background={selected.color}></span>{/if}{selected?.label ?? placeholder}</span>
|
|
241
|
-
<svg class="sv-ddl__chev" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
242
|
-
</button>
|
|
243
|
-
</SvField>
|
|
244
|
-
|
|
245
|
-
{#snippet optRow(opt: IndexedOption, h?: number, top?: number)}
|
|
246
|
-
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
247
|
-
<div class="sv-ddl__opt" style:height={h != null ? `${h}px` : undefined} style:transform={top == null ? undefined : `translateY(${top}px)`} class:is-active={ddl.isActive(opt.index)} class:is-selected={ddl.isSelected(opt)} class:is-disabled={opt.disabled} {...ddl.optionProps(opt.index)}>
|
|
248
|
-
{#if item}{@render item(opt)}{:else}{#if opt.color}<span class="sv-ddl__swatch" style:background={opt.color}></span>{/if}{opt.label}{/if}
|
|
249
|
-
</div>
|
|
250
|
-
{/snippet}
|
|
251
|
-
|
|
252
|
-
{#if ddl.open}
|
|
253
|
-
<div bind:this={panelEl} class="sv-ddl__panel" class:is-virtual={useVirtual} class:is-resizable={showGrip} use:portalToBody use:popIn={{ up: rect.openUpward }} style:--sv-row-h={`${rowHeightBase}px`} style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} style:max-height={`${panelMaxH}px`} style:height={useVirtual ? `${virtualPanelH}px` : resizable && userHeight != null ? `${panelMaxH}px` : undefined} onwheel={useVirtual ? scroller.onWheel : undefined} {...ddl.listboxProps()}>
|
|
254
|
-
{#if header}<div class="sv-ddl__header">{@render header()}</div>{/if}
|
|
255
|
-
{#if !norm.length}
|
|
256
|
-
{#if noData}{@render noData()}{:else}<div class="sv-ddl__empty">No options</div>{/if}
|
|
257
|
-
{:else if useVirtual}
|
|
258
|
-
<!-- JS-driven scroller: one clipped viewport holds the windowed rows (moved
|
|
259
|
-
by scrollOffset) + a custom scrollbar. No native scroll = no async gap
|
|
260
|
-
= no blank flash on a fast thumb-drag. -->
|
|
261
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
262
|
-
<div class="sv-ddl__vp" bind:clientHeight={viewportH} onpointerdown={scroller.onContentDown} onpointermove={scroller.onContentMove} onpointerup={scroller.onContentUp} onpointercancel={scroller.onContentUp}>
|
|
263
|
-
{#each vItems as vi (vi.key)}
|
|
264
|
-
{@const entry = flat.entries[vi.index]}
|
|
265
|
-
{#if entry?.type === 'group'}
|
|
266
|
-
<div class="sv-ddl__group-label sv-ddl__group-label--abs" aria-hidden="true" style:height={`${vi.size}px`} style:transform={`translateY(${vi.start - scroller.scrollOffset}px)`}>{entry.label}</div>
|
|
267
|
-
{:else if entry}
|
|
268
|
-
{@render optRow(entry.opt, vi.size, vi.start - scroller.scrollOffset)}
|
|
269
|
-
{/if}
|
|
270
|
-
{/each}
|
|
271
|
-
{#if scroller.maxOffset > 0}
|
|
272
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
273
|
-
<div class="sv-ddl__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
274
|
-
<div class="sv-ddl__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>
|
|
275
|
-
</div>
|
|
276
|
-
{/if}
|
|
277
|
-
</div>
|
|
278
|
-
{:else}
|
|
279
|
-
{#each groupOptions(norm) as g (g.group ?? '')}
|
|
280
|
-
{#if g.group != null}
|
|
281
|
-
<div class="sv-ddl__group" role="group" aria-label={g.group}>
|
|
282
|
-
<div class="sv-ddl__group-label" aria-hidden="true">{g.group}</div>
|
|
283
|
-
{#each g.options as opt (opt.index)}
|
|
284
|
-
{@render optRow(opt)}
|
|
285
|
-
{/each}
|
|
286
|
-
</div>
|
|
287
|
-
{:else}
|
|
288
|
-
{#each g.options as opt (opt.index)}
|
|
289
|
-
{@render optRow(opt)}
|
|
290
|
-
{/each}
|
|
291
|
-
{/if}
|
|
292
|
-
{/each}
|
|
293
|
-
{/if}
|
|
294
|
-
{#if footer}<div class="sv-ddl__footer">{@render footer()}</div>{/if}
|
|
295
|
-
{#if showGrip}
|
|
296
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
297
|
-
<div class="sv-ddl__grip" onpointerdown={startResize} title="Drag to resize" aria-hidden="true">
|
|
298
|
-
<span class="sv-ddl__grip-dots"></span>
|
|
299
|
-
</div>
|
|
300
|
-
{/if}
|
|
301
|
-
</div>
|
|
302
|
-
{/if}
|
|
303
|
-
{#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
|
|
304
|
-
|
|
305
|
-
<style>
|
|
306
|
-
.sv-ddl {
|
|
307
|
-
--_accent: var(--sg-accent, #2563eb);
|
|
308
|
-
display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; width: 200px;
|
|
309
|
-
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: start;
|
|
310
|
-
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
311
|
-
padding: 0 10px; cursor: pointer;
|
|
312
|
-
}
|
|
313
|
-
.sv-ddl--sm { height: 28px; font-size: 12px; }
|
|
314
|
-
.sv-ddl--md { height: 34px; font-size: 13px; }
|
|
315
|
-
.sv-ddl--lg { height: 40px; font-size: 15px; }
|
|
316
|
-
.sv-ddl.is-open, .sv-ddl:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); outline: none; }
|
|
317
|
-
.sv-ddl.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
318
|
-
.sv-ddl.is-invalid.is-open, .sv-ddl.is-invalid:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
319
|
-
.sv-ddl.is-disabled { opacity: 0.6; cursor: not-allowed; }
|
|
320
|
-
.sv-ddl__value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
321
|
-
.sv-ddl__value.is-placeholder { color: var(--sg-muted, #94a3b8); }
|
|
322
|
-
.sv-ddl__chev { color: var(--sg-muted, #64748b); flex: none; }
|
|
323
|
-
|
|
324
|
-
:global(.sv-ddl__panel) {
|
|
325
|
-
box-sizing: border-box; z-index: 2147483647; max-height: 288px; overflow-y: auto; padding: 4px;
|
|
326
|
-
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
327
|
-
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px; box-shadow: 0 16px 48px -12px rgba(15,23,42,0.35);
|
|
328
|
-
}
|
|
329
|
-
/* Virtual panel: a flex column (header / JS-driven viewport / footer / grip)
|
|
330
|
-
with NO native scroll - the viewport moves its rows by `scrollOffset`. */
|
|
331
|
-
:global(.sv-ddl__panel.is-virtual) { display: flex; flex-direction: column; overflow: hidden; }
|
|
332
|
-
:global(.sv-ddl__vp) { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; touch-action: none; }
|
|
333
|
-
:global(.sv-ddl__panel.is-virtual .sv-ddl__opt), :global(.sv-ddl__group-label--abs) { position: absolute; left: 0; right: 0; top: 0; }
|
|
334
|
-
:global(.sv-ddl__sb) { position: absolute; top: 0; right: 1px; bottom: 0; width: 10px; z-index: 2; }
|
|
335
|
-
:global(.sv-ddl__thumb) {
|
|
336
|
-
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px; border-radius: 4px;
|
|
337
|
-
background: color-mix(in srgb, currentColor 26%, transparent); cursor: default; touch-action: none;
|
|
338
|
-
}
|
|
339
|
-
:global(.sv-ddl__thumb:hover), :global(.sv-ddl__thumb.is-dragging) { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
340
|
-
:global(.sv-ddl__opt) { box-sizing: border-box; flex: none; display: flex; align-items: center; padding: 7px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
|
341
|
-
/* Color swatch shown before an option's label (option.color) - in both the
|
|
342
|
-
trigger value and the portalled panel options. */
|
|
343
|
-
:global(.sv-ddl__swatch) {
|
|
344
|
-
display: inline-block; flex: none; width: 10px; height: 10px; border-radius: 3px;
|
|
345
|
-
margin-inline-end: 8px; vertical-align: middle;
|
|
346
|
-
box-shadow: inset 0 0 0 1px color-mix(in srgb, #000 18%, transparent);
|
|
347
|
-
}
|
|
348
|
-
/* Bottom resize grip: a row of dots ("····") the user drags to resize the
|
|
349
|
-
panel. Sticky so it stays pinned at the panel's bottom while options scroll. */
|
|
350
|
-
:global(.sv-ddl__panel.is-resizable) { padding-bottom: 0; }
|
|
351
|
-
:global(.sv-ddl__grip) {
|
|
352
|
-
position: sticky; bottom: 0; z-index: 1; margin: 2px -4px -4px; height: 15px;
|
|
353
|
-
display: flex; align-items: center; justify-content: center; cursor: ns-resize;
|
|
354
|
-
background: var(--sg-bg, #fff); border-top: 1px solid var(--sg-border, #e2e8f0);
|
|
355
|
-
border-radius: 0 0 10px 10px; touch-action: none;
|
|
356
|
-
}
|
|
357
|
-
:global(.sv-ddl__grip-dots) {
|
|
358
|
-
width: 26px; height: 4px; color: var(--sg-muted, #94a3b8); opacity: 0.6;
|
|
359
|
-
background-image: radial-gradient(currentColor 1px, transparent 1.6px);
|
|
360
|
-
background-size: 6px 4px; background-position: center; background-repeat: repeat-x;
|
|
361
|
-
}
|
|
362
|
-
:global(.sv-ddl__grip:hover .sv-ddl__grip-dots) { opacity: 1; }
|
|
363
|
-
:global(.sv-ddl__opt.is-active) { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
364
|
-
:global(.sv-ddl__opt.is-selected) { color: var(--sg-accent, #2563eb); font-weight: 600; }
|
|
365
|
-
:global(.sv-ddl__opt.is-disabled) { opacity: 0.4; cursor: not-allowed; }
|
|
366
|
-
:global(.sv-ddl__group-label) {
|
|
367
|
-
box-sizing: border-box; padding: 8px 10px 3px; font-size: 10.5px; font-weight: 700;
|
|
368
|
-
text-transform: uppercase; letter-spacing: 0.04em; color: var(--sg-muted, #94a3b8);
|
|
369
|
-
display: flex; align-items: flex-end;
|
|
370
|
-
}
|
|
371
|
-
:global(.sv-ddl__empty) { padding: 12px 12px; font-size: 13px; color: var(--sg-muted, #64748b); text-align: center; }
|
|
372
|
-
:global(.sv-ddl__header) { padding: 8px 10px; border-bottom: 1px solid var(--sg-border, #e2e8f0); font-size: 12.5px; color: var(--sg-muted, #64748b); }
|
|
373
|
-
:global(.sv-ddl__footer) { padding: 8px 10px; border-top: 1px solid var(--sg-border, #e2e8f0); font-size: 12.5px; }
|
|
374
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvDropDownList - a single-select dropdown (trigger button + portalled list,
|
|
4
|
+
* no typing). Parity: Smart `smart-drop-down-list`. Controlled via `value` +
|
|
5
|
+
* `onChange`. Popover escapes scroll clipping via popover.ts.
|
|
6
|
+
*
|
|
7
|
+
* One styled renderer over the headless `createDropdownList` core (state +
|
|
8
|
+
* keyboard + ARIA); only portal/measure/scroll render concerns live here.
|
|
9
|
+
*/
|
|
10
|
+
import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
|
|
11
|
+
import { startPanelResize } from './panel-resize'
|
|
12
|
+
import { createDismissableLayer } from './a11y/dismissable'
|
|
13
|
+
import { groupOptions, normalizeOptions, flattenForVirtual, type ListOption, type IndexedOption, type RowHeight } from './list-option'
|
|
14
|
+
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
15
|
+
import { createJsScroller } from './js-scroller.svelte'
|
|
16
|
+
import SvField from './SvField.svelte'
|
|
17
|
+
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
18
|
+
import { createDropdownList } from './createDropdownList.svelte'
|
|
19
|
+
import { untrack, type Snippet } from 'svelte'
|
|
20
|
+
|
|
21
|
+
type Props = SvEditorProps & {
|
|
22
|
+
options: ReadonlyArray<ListOption>
|
|
23
|
+
value?: string | number | null
|
|
24
|
+
onChange?: (value: string | number) => void
|
|
25
|
+
placeholder?: string
|
|
26
|
+
autoOpen?: boolean
|
|
27
|
+
/** Window the option list (render only visible) for large sets. */
|
|
28
|
+
virtual?: boolean
|
|
29
|
+
/** Option height in px - a number, or a per-option function for variable
|
|
30
|
+
* heights. Must match the rendered row height. Default 34. */
|
|
31
|
+
rowHeight?: RowHeight
|
|
32
|
+
/** Height (px) of a group heading row when virtualized + grouped. Default 28. */
|
|
33
|
+
groupHeaderHeight?: number
|
|
34
|
+
/** Max options shown before the panel scrolls (native-select behaviour). Default 8. */
|
|
35
|
+
maxRows?: number
|
|
36
|
+
/** Show a bottom drag grip so the user can resize the open panel's height
|
|
37
|
+
* (only while it opens downward). Off by default. */
|
|
38
|
+
resizable?: boolean
|
|
39
|
+
/** Custom render for each option (receives the ListOption). */
|
|
40
|
+
item?: Snippet<[ListOption]>
|
|
41
|
+
/** Content pinned above the option list. */
|
|
42
|
+
header?: Snippet
|
|
43
|
+
/** Content pinned below the option list. */
|
|
44
|
+
footer?: Snippet
|
|
45
|
+
/** Shown when there are no options. */
|
|
46
|
+
noData?: Snippet
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
options,
|
|
51
|
+
value = null,
|
|
52
|
+
onChange,
|
|
53
|
+
placeholder = 'Select…',
|
|
54
|
+
disabled = false,
|
|
55
|
+
readonly = false,
|
|
56
|
+
name,
|
|
57
|
+
size = 'md',
|
|
58
|
+
ariaLabel,
|
|
59
|
+
invalid = false,
|
|
60
|
+
required = false,
|
|
61
|
+
error,
|
|
62
|
+
label,
|
|
63
|
+
hint,
|
|
64
|
+
dir,
|
|
65
|
+
id,
|
|
66
|
+
autoOpen = false,
|
|
67
|
+
virtual = false,
|
|
68
|
+
rowHeight = 34,
|
|
69
|
+
groupHeaderHeight = 28,
|
|
70
|
+
maxRows = 8,
|
|
71
|
+
resizable = false,
|
|
72
|
+
item,
|
|
73
|
+
header,
|
|
74
|
+
footer,
|
|
75
|
+
noData,
|
|
76
|
+
}: Props = $props()
|
|
77
|
+
|
|
78
|
+
const autoId = nextEditorId('sv-ddl')
|
|
79
|
+
const uid = $derived(id ?? autoId)
|
|
80
|
+
|
|
81
|
+
// Tolerate primitive / partial options - normalize so labels render + keys are stable.
|
|
82
|
+
const norm = $derived(normalizeOptions(options))
|
|
83
|
+
|
|
84
|
+
// Virtualization windows the option list (grouped lists too, via the shared
|
|
85
|
+
// virtualizer + a flattened header/option model with per-row sizes).
|
|
86
|
+
const useVirtual = $derived(virtual)
|
|
87
|
+
let viewportH = $state(0)
|
|
88
|
+
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 34)
|
|
89
|
+
const flat = $derived(flattenForVirtual(norm, { rowHeight, groupHeaderHeight }))
|
|
90
|
+
const uniform = $derived(!flat.hasGroups && typeof rowHeight === 'number')
|
|
91
|
+
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => flat.sizeAt(i))
|
|
92
|
+
|
|
93
|
+
// Generous overscan buffers fast flings; effective viewport never 0 (see SvListBox).
|
|
94
|
+
const OVERSCAN = 20
|
|
95
|
+
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, maxRows * rowHeightBase))
|
|
96
|
+
|
|
97
|
+
// Seed with the initial (untracked) values so the first paint of the open panel
|
|
98
|
+
// renders a full window; the effects below keep it synced.
|
|
99
|
+
const virtualizer = createSvelteVirtualizer(
|
|
100
|
+
untrack(() => ({ count: flat.entries.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
101
|
+
)
|
|
102
|
+
// untrack keeps the virtualizer's internal `version` write out of these
|
|
103
|
+
// effects' dependencies, so they re-run only on their real inputs.
|
|
104
|
+
$effect(() => {
|
|
105
|
+
const count = flat.entries.length
|
|
106
|
+
const es = estimateSize
|
|
107
|
+
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
108
|
+
})
|
|
109
|
+
$effect(() => {
|
|
110
|
+
const vh = effViewport
|
|
111
|
+
untrack(() => virtualizer.setViewportHeight(vh))
|
|
112
|
+
})
|
|
113
|
+
const vItems = $derived.by(() => {
|
|
114
|
+
virtualizer.version
|
|
115
|
+
return virtualizer.getVirtualItems()
|
|
116
|
+
})
|
|
117
|
+
const totalSize = $derived.by(() => {
|
|
118
|
+
virtualizer.version
|
|
119
|
+
return virtualizer.getTotalSize()
|
|
120
|
+
})
|
|
121
|
+
// JS-driven scroll (see SvListBox / js-scroller): no native scroll, so a fast
|
|
122
|
+
// thumb-drag never flashes the empty panel.
|
|
123
|
+
const scroller = createJsScroller({
|
|
124
|
+
virtualizer,
|
|
125
|
+
totalSize: () => totalSize,
|
|
126
|
+
viewport: () => effViewport,
|
|
127
|
+
lineStep: () => rowHeightBase,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
let triggerEl = $state<HTMLButtonElement | null>(null)
|
|
131
|
+
let panelEl = $state<HTMLDivElement | null>(null)
|
|
132
|
+
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
133
|
+
// User-chosen panel height (px) once they drag the resize grip; null = auto.
|
|
134
|
+
let userHeight = $state<number | null>(null)
|
|
135
|
+
// Effective height cap: the user's drag if any (clamped to the viewport
|
|
136
|
+
// ceiling), otherwise the comfortable bounds-aware height.
|
|
137
|
+
const panelMaxH = $derived(
|
|
138
|
+
resizable && userHeight != null ? Math.min(userHeight, rect.availHeight) : rect.maxHeight,
|
|
139
|
+
)
|
|
140
|
+
const showGrip = $derived(resizable && !rect.openUpward)
|
|
141
|
+
|
|
142
|
+
// The virtual panel uses NO native scroll, so its rows are absolutely
|
|
143
|
+
// positioned and can't size the panel. Give it an explicit height (content up
|
|
144
|
+
// to `maxRows`, capped by the resized / bounds-aware ceiling) so the flex
|
|
145
|
+
// viewport (`.sv-ddl__vp`) has a definite height to fill.
|
|
146
|
+
const virtualPanelH = $derived(
|
|
147
|
+
Math.min(
|
|
148
|
+
Math.min(totalSize, Math.max(rowHeightBase, maxRows * rowHeightBase)) + 8,
|
|
149
|
+
rect.maxHeight || Number.POSITIVE_INFINITY,
|
|
150
|
+
resizable && userHeight != null ? panelMaxH : Number.POSITIVE_INFINITY,
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
const ddl = createDropdownList({
|
|
155
|
+
options: () => norm,
|
|
156
|
+
value: () => value,
|
|
157
|
+
onChange: (v) => onChange?.(v),
|
|
158
|
+
disabled: () => disabled,
|
|
159
|
+
readonly: () => readonly,
|
|
160
|
+
ariaLabel: () => ariaLabel,
|
|
161
|
+
focusTrigger: () => triggerEl?.focus(),
|
|
162
|
+
id: () => uid,
|
|
163
|
+
invalid: () => invalid,
|
|
164
|
+
required: () => required,
|
|
165
|
+
error: () => error,
|
|
166
|
+
hint: () => hint,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const selected = $derived(ddl.selected)
|
|
170
|
+
|
|
171
|
+
function updatePos() {
|
|
172
|
+
if (!triggerEl) return
|
|
173
|
+
// When the user has resized, anchor to THAT height so the flip decision and
|
|
174
|
+
// upward top stay correct; otherwise size to the content up to `maxRows` items
|
|
175
|
+
// (native-select behaviour: no scrollbar until the list exceeds the cap). The
|
|
176
|
+
// +PANEL_CHROME budgets the panel's own padding (8) + border (2) so a list
|
|
177
|
+
// that exactly fills `maxRows` is not clipped by the border-box max-height.
|
|
178
|
+
const PANEL_CHROME = 10
|
|
179
|
+
const estimatedHeight =
|
|
180
|
+
resizable && userHeight != null ? userHeight : Math.min(norm.length, maxRows) * rowHeightBase + PANEL_CHROME
|
|
181
|
+
rect = anchoredRect(triggerEl.getBoundingClientRect(), { estimatedHeight })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function startResize(e: PointerEvent) {
|
|
185
|
+
if (!panelEl) return
|
|
186
|
+
startPanelResize(e, {
|
|
187
|
+
startHeight: panelEl.clientHeight,
|
|
188
|
+
min: 96,
|
|
189
|
+
max: () => rect.availHeight,
|
|
190
|
+
onHeight: (h) => {
|
|
191
|
+
userHeight = h
|
|
192
|
+
updatePos()
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
$effect(() => {
|
|
198
|
+
if (!ddl.open) return
|
|
199
|
+
updatePos()
|
|
200
|
+
const rp = () => updatePos()
|
|
201
|
+
window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
|
|
202
|
+
// Outside-pointer dismissal via the shared layer stack (Escape stays with
|
|
203
|
+
// the core keyboard handler). Because this layer is pushed ON TOP, a
|
|
204
|
+
// dropdown opened inside a dialog/drawer closes itself first and never
|
|
205
|
+
// dismisses the dialog underneath it.
|
|
206
|
+
const layer = createDismissableLayer({
|
|
207
|
+
element: () => [triggerEl, panelEl],
|
|
208
|
+
onDismiss: () => ddl.close(),
|
|
209
|
+
closeOnEscape: false,
|
|
210
|
+
})
|
|
211
|
+
layer.activate()
|
|
212
|
+
return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); layer.release() }
|
|
213
|
+
})
|
|
214
|
+
// Keep the active option scrolled into view (render concern) - via the scroller
|
|
215
|
+
// (virtual) or scrollIntoView (native non-virtual).
|
|
216
|
+
$effect(() => {
|
|
217
|
+
if (!ddl.open) return
|
|
218
|
+
const i = ddl.activeIndex
|
|
219
|
+
if (useVirtual) {
|
|
220
|
+
const flatIdx = flat.optionFlatIndex[i]
|
|
221
|
+
if (flatIdx == null) return
|
|
222
|
+
untrack(() => scroller.ensureIndexVisible(flatIdx))
|
|
223
|
+
} else {
|
|
224
|
+
queueMicrotask(() => panelEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
function focusOpen(node: HTMLButtonElement) { if (autoOpen) { node.focus(); ddl.openPanel() } }
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
231
|
+
<button
|
|
232
|
+
bind:this={triggerEl}
|
|
233
|
+
class="sv-ddl sv-ddl--{size}"
|
|
234
|
+
class:is-open={ddl.open}
|
|
235
|
+
class:is-disabled={disabled}
|
|
236
|
+
class:is-invalid={invalid}
|
|
237
|
+
{...ddl.triggerProps()}
|
|
238
|
+
use:focusOpen
|
|
239
|
+
>
|
|
240
|
+
<span class="sv-ddl__value" class:is-placeholder={!selected}>{#if selected?.color}<span class="sv-ddl__swatch" style:background={selected.color}></span>{/if}{selected?.label ?? placeholder}</span>
|
|
241
|
+
<svg class="sv-ddl__chev" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
242
|
+
</button>
|
|
243
|
+
</SvField>
|
|
244
|
+
|
|
245
|
+
{#snippet optRow(opt: IndexedOption, h?: number, top?: number)}
|
|
246
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
247
|
+
<div class="sv-ddl__opt" style:height={h != null ? `${h}px` : undefined} style:transform={top == null ? undefined : `translateY(${top}px)`} class:is-active={ddl.isActive(opt.index)} class:is-selected={ddl.isSelected(opt)} class:is-disabled={opt.disabled} {...ddl.optionProps(opt.index)}>
|
|
248
|
+
{#if item}{@render item(opt)}{:else}{#if opt.color}<span class="sv-ddl__swatch" style:background={opt.color}></span>{/if}{opt.label}{/if}
|
|
249
|
+
</div>
|
|
250
|
+
{/snippet}
|
|
251
|
+
|
|
252
|
+
{#if ddl.open}
|
|
253
|
+
<div bind:this={panelEl} class="sv-ddl__panel" class:is-virtual={useVirtual} class:is-resizable={showGrip} use:portalToBody use:popIn={{ up: rect.openUpward }} style:--sv-row-h={`${rowHeightBase}px`} style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} style:max-height={`${panelMaxH}px`} style:height={useVirtual ? `${virtualPanelH}px` : resizable && userHeight != null ? `${panelMaxH}px` : undefined} onwheel={useVirtual ? scroller.onWheel : undefined} {...ddl.listboxProps()}>
|
|
254
|
+
{#if header}<div class="sv-ddl__header">{@render header()}</div>{/if}
|
|
255
|
+
{#if !norm.length}
|
|
256
|
+
{#if noData}{@render noData()}{:else}<div class="sv-ddl__empty">No options</div>{/if}
|
|
257
|
+
{:else if useVirtual}
|
|
258
|
+
<!-- JS-driven scroller: one clipped viewport holds the windowed rows (moved
|
|
259
|
+
by scrollOffset) + a custom scrollbar. No native scroll = no async gap
|
|
260
|
+
= no blank flash on a fast thumb-drag. -->
|
|
261
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
262
|
+
<div class="sv-ddl__vp" bind:clientHeight={viewportH} onpointerdown={scroller.onContentDown} onpointermove={scroller.onContentMove} onpointerup={scroller.onContentUp} onpointercancel={scroller.onContentUp}>
|
|
263
|
+
{#each vItems as vi (vi.key)}
|
|
264
|
+
{@const entry = flat.entries[vi.index]}
|
|
265
|
+
{#if entry?.type === 'group'}
|
|
266
|
+
<div class="sv-ddl__group-label sv-ddl__group-label--abs" aria-hidden="true" style:height={`${vi.size}px`} style:transform={`translateY(${vi.start - scroller.scrollOffset}px)`}>{entry.label}</div>
|
|
267
|
+
{:else if entry}
|
|
268
|
+
{@render optRow(entry.opt, vi.size, vi.start - scroller.scrollOffset)}
|
|
269
|
+
{/if}
|
|
270
|
+
{/each}
|
|
271
|
+
{#if scroller.maxOffset > 0}
|
|
272
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
273
|
+
<div class="sv-ddl__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
274
|
+
<div class="sv-ddl__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>
|
|
275
|
+
</div>
|
|
276
|
+
{/if}
|
|
277
|
+
</div>
|
|
278
|
+
{:else}
|
|
279
|
+
{#each groupOptions(norm) as g (g.group ?? '')}
|
|
280
|
+
{#if g.group != null}
|
|
281
|
+
<div class="sv-ddl__group" role="group" aria-label={g.group}>
|
|
282
|
+
<div class="sv-ddl__group-label" aria-hidden="true">{g.group}</div>
|
|
283
|
+
{#each g.options as opt (opt.index)}
|
|
284
|
+
{@render optRow(opt)}
|
|
285
|
+
{/each}
|
|
286
|
+
</div>
|
|
287
|
+
{:else}
|
|
288
|
+
{#each g.options as opt (opt.index)}
|
|
289
|
+
{@render optRow(opt)}
|
|
290
|
+
{/each}
|
|
291
|
+
{/if}
|
|
292
|
+
{/each}
|
|
293
|
+
{/if}
|
|
294
|
+
{#if footer}<div class="sv-ddl__footer">{@render footer()}</div>{/if}
|
|
295
|
+
{#if showGrip}
|
|
296
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
297
|
+
<div class="sv-ddl__grip" onpointerdown={startResize} title="Drag to resize" aria-hidden="true">
|
|
298
|
+
<span class="sv-ddl__grip-dots"></span>
|
|
299
|
+
</div>
|
|
300
|
+
{/if}
|
|
301
|
+
</div>
|
|
302
|
+
{/if}
|
|
303
|
+
{#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
|
|
304
|
+
|
|
305
|
+
<style>
|
|
306
|
+
.sv-ddl {
|
|
307
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
308
|
+
display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; width: 200px;
|
|
309
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: start;
|
|
310
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
311
|
+
padding: 0 10px; cursor: pointer;
|
|
312
|
+
}
|
|
313
|
+
.sv-ddl--sm { height: 28px; font-size: 12px; }
|
|
314
|
+
.sv-ddl--md { height: 34px; font-size: 13px; }
|
|
315
|
+
.sv-ddl--lg { height: 40px; font-size: 15px; }
|
|
316
|
+
.sv-ddl.is-open, .sv-ddl:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); outline: none; }
|
|
317
|
+
.sv-ddl.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
318
|
+
.sv-ddl.is-invalid.is-open, .sv-ddl.is-invalid:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
319
|
+
.sv-ddl.is-disabled { opacity: 0.6; cursor: not-allowed; }
|
|
320
|
+
.sv-ddl__value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
321
|
+
.sv-ddl__value.is-placeholder { color: var(--sg-muted, #94a3b8); }
|
|
322
|
+
.sv-ddl__chev { color: var(--sg-muted, #64748b); flex: none; }
|
|
323
|
+
|
|
324
|
+
:global(.sv-ddl__panel) {
|
|
325
|
+
box-sizing: border-box; z-index: 2147483647; max-height: 288px; overflow-y: auto; padding: 4px;
|
|
326
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
327
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px; box-shadow: 0 16px 48px -12px rgba(15,23,42,0.35);
|
|
328
|
+
}
|
|
329
|
+
/* Virtual panel: a flex column (header / JS-driven viewport / footer / grip)
|
|
330
|
+
with NO native scroll - the viewport moves its rows by `scrollOffset`. */
|
|
331
|
+
:global(.sv-ddl__panel.is-virtual) { display: flex; flex-direction: column; overflow: hidden; }
|
|
332
|
+
:global(.sv-ddl__vp) { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; touch-action: none; }
|
|
333
|
+
:global(.sv-ddl__panel.is-virtual .sv-ddl__opt), :global(.sv-ddl__group-label--abs) { position: absolute; left: 0; right: 0; top: 0; }
|
|
334
|
+
:global(.sv-ddl__sb) { position: absolute; top: 0; right: 1px; bottom: 0; width: 10px; z-index: 2; }
|
|
335
|
+
:global(.sv-ddl__thumb) {
|
|
336
|
+
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px; border-radius: 4px;
|
|
337
|
+
background: color-mix(in srgb, currentColor 26%, transparent); cursor: default; touch-action: none;
|
|
338
|
+
}
|
|
339
|
+
:global(.sv-ddl__thumb:hover), :global(.sv-ddl__thumb.is-dragging) { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
340
|
+
:global(.sv-ddl__opt) { box-sizing: border-box; flex: none; display: flex; align-items: center; padding: 7px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
|
341
|
+
/* Color swatch shown before an option's label (option.color) - in both the
|
|
342
|
+
trigger value and the portalled panel options. */
|
|
343
|
+
:global(.sv-ddl__swatch) {
|
|
344
|
+
display: inline-block; flex: none; width: 10px; height: 10px; border-radius: 3px;
|
|
345
|
+
margin-inline-end: 8px; vertical-align: middle;
|
|
346
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, #000 18%, transparent);
|
|
347
|
+
}
|
|
348
|
+
/* Bottom resize grip: a row of dots ("····") the user drags to resize the
|
|
349
|
+
panel. Sticky so it stays pinned at the panel's bottom while options scroll. */
|
|
350
|
+
:global(.sv-ddl__panel.is-resizable) { padding-bottom: 0; }
|
|
351
|
+
:global(.sv-ddl__grip) {
|
|
352
|
+
position: sticky; bottom: 0; z-index: 1; margin: 2px -4px -4px; height: 15px;
|
|
353
|
+
display: flex; align-items: center; justify-content: center; cursor: ns-resize;
|
|
354
|
+
background: var(--sg-bg, #fff); border-top: 1px solid var(--sg-border, #e2e8f0);
|
|
355
|
+
border-radius: 0 0 10px 10px; touch-action: none;
|
|
356
|
+
}
|
|
357
|
+
:global(.sv-ddl__grip-dots) {
|
|
358
|
+
width: 26px; height: 4px; color: var(--sg-muted, #94a3b8); opacity: 0.6;
|
|
359
|
+
background-image: radial-gradient(currentColor 1px, transparent 1.6px);
|
|
360
|
+
background-size: 6px 4px; background-position: center; background-repeat: repeat-x;
|
|
361
|
+
}
|
|
362
|
+
:global(.sv-ddl__grip:hover .sv-ddl__grip-dots) { opacity: 1; }
|
|
363
|
+
:global(.sv-ddl__opt.is-active) { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
364
|
+
:global(.sv-ddl__opt.is-selected) { color: var(--sg-accent, #2563eb); font-weight: 600; }
|
|
365
|
+
:global(.sv-ddl__opt.is-disabled) { opacity: 0.4; cursor: not-allowed; }
|
|
366
|
+
:global(.sv-ddl__group-label) {
|
|
367
|
+
box-sizing: border-box; padding: 8px 10px 3px; font-size: 10.5px; font-weight: 700;
|
|
368
|
+
text-transform: uppercase; letter-spacing: 0.04em; color: var(--sg-muted, #94a3b8);
|
|
369
|
+
display: flex; align-items: flex-end;
|
|
370
|
+
}
|
|
371
|
+
:global(.sv-ddl__empty) { padding: 12px 12px; font-size: 13px; color: var(--sg-muted, #64748b); text-align: center; }
|
|
372
|
+
:global(.sv-ddl__header) { padding: 8px 10px; border-bottom: 1px solid var(--sg-border, #e2e8f0); font-size: 12.5px; color: var(--sg-muted, #64748b); }
|
|
373
|
+
:global(.sv-ddl__footer) { padding: 8px 10px; border-top: 1px solid var(--sg-border, #e2e8f0); font-size: 12.5px; }
|
|
374
|
+
</style>
|