@svgrid/grid 2.2.27 → 2.2.29
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 +179 -179
- package/dist/GridMenus.svelte +782 -734
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +101 -101
- 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 -250
- 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.controller.svelte.d.ts +14 -1
- package/dist/SvGrid.controller.svelte.js +112 -28
- package/dist/SvGrid.css +2704 -2723
- package/dist/SvGrid.svelte +3361 -3333
- package/dist/SvGrid.types.d.ts +20 -5
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +696 -696
- package/dist/SvGridSelect.svelte +259 -259
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +326 -287
- package/dist/SvListBox.svelte.d.ts +10 -1
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +281 -281
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -131
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +107 -107
- 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 +204 -204
- package/dist/SvTree.svelte +437 -437
- package/dist/SvTreeSelect.svelte +233 -233
- package/dist/cdn/GridMenus-Da_p7SKS.js +486 -0
- package/dist/cdn/GridMenus-Ds3OpzZT.js +490 -0
- package/dist/cdn/{SvDateTimePicker-B1sDc9Pz.js → SvDateTimePicker-B8GopjhC.js} +2 -2
- package/dist/cdn/{SvDateTimePicker-Bh0FTs1E.js → SvDateTimePicker-DB0aIEk8.js} +2 -2
- package/dist/cdn/{SvGridChart-Bgk4hDs8.js → SvGridChart-BcLCYvw9.js} +1 -1
- package/dist/cdn/{SvGridChart-BKQsO5fZ.js → SvGridChart-BhnFdLd0.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DkCc2Z7b.js → SvGridChartPanel-Cmx9DHAS.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DSkHIQDd.js → SvGridChartPanel-DvjwxLvK.js} +1 -1
- package/dist/cdn/{SvGridChartView-BrbVaELm.js → SvGridChartView-B40DTJSX.js} +1 -1
- package/dist/cdn/{SvGridChartView-i9rWvSKg.js → SvGridChartView-PwqI4s02.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D08sVmAy.js → SvGridDropdown-DjALc_4f.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CGxxopQ4.js → SvGridDropdown-hpnHvOGt.js} +1 -1
- package/dist/cdn/{src-cgs1wcUS.js → src-BLJfdyL0.js} +4034 -3889
- package/dist/cdn/{src-Cw8d9U8j.js → src-DYDCiC0D.js} +7053 -6908
- 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/column-groups.js +8 -10
- package/dist/column-id.d.ts +9 -0
- package/dist/column-id.js +25 -0
- package/dist/core.js +2 -1
- package/dist/createListbox.svelte.d.ts +15 -2
- package/dist/createListbox.svelte.js +27 -6
- package/dist/editing.d.ts +1 -0
- package/dist/editing.js +45 -10
- package/dist/filtering/excel-filters.d.ts +30 -10
- package/dist/filtering/excel-filters.js +96 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/menus.d.ts +2 -0
- package/dist/menus.js +25 -0
- package/dist/row-resize.js +40 -0
- package/dist/selection.js +1 -1
- package/package.json +10 -10
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +179 -179
- package/src/GridMenus.svelte +782 -734
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +101 -101
- 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 -250
- package/src/SvDrawer.test.ts +18 -1
- 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 +3335 -3239
- package/src/SvGrid.css +2704 -2723
- package/src/SvGrid.svelte +3361 -3333
- package/src/SvGrid.types.ts +1628 -1613
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +696 -696
- package/src/SvGridSelect.svelte +259 -259
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +326 -287
- 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 +281 -281
- package/src/SvNavPane.test.ts +15 -4
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -131
- package/src/SvPopover.svelte +197 -197
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +107 -107
- 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 +204 -204
- package/src/SvTree.svelte +437 -437
- package/src/SvTreeSelect.svelte +233 -233
- 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 +895 -895
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.test.ts +48 -0
- package/src/column-groups.ts +8 -10
- package/src/column-id.ts +30 -0
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1112 -1111
- 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 +39 -7
- package/src/createMenu.svelte.ts +225 -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 -859
- package/src/editing.ts +709 -675
- package/src/editor-contract.ts +157 -157
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.ts +157 -157
- package/src/filtering/excel-filters.test.ts +44 -1
- package/src/filtering/excel-filters.ts +91 -14
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/index.ts +715 -713
- package/src/js-scroller.svelte.ts +173 -173
- 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 +736 -700
- package/src/menus.ts +679 -652
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/row-resize.test.ts +100 -0
- package/src/row-resize.ts +36 -0
- 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 +600 -600
- 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 +489 -489
- 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 +524 -524
- package/src/svgrid.api-extensions.test.ts +8 -3
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.cell-dom-ids.test.ts +96 -0
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.conditional-stat-scope.test.ts +111 -0
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +308 -0
- package/src/svgrid.filter-menu-scroll.test.ts +3 -1
- package/src/svgrid.group-header-ids.test.ts +121 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +215 -215
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- 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/ember.css +71 -71
- package/dist/cdn/GridMenus-BzxEYNtW.js +0 -421
- package/dist/cdn/GridMenus-Dr-nGEXn.js +0 -417
package/dist/SvListBox.svelte
CHANGED
|
@@ -1,287 +1,326 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SvListBox - an inline single/multi-select list (WAI-ARIA listbox) with
|
|
4
|
-
* roving highlight + full keyboard. Parity: Smart `smart-list-box`. Controlled
|
|
5
|
-
* via `value` (scalar or array) + `onChange`. Scales to huge option sets with
|
|
6
|
-
* `virtual` - windowing over the shared `createSvelteVirtualizer` (slot-keyed
|
|
7
|
-
* DOM recycling), which also windows GROUPED lists and mixes row heights.
|
|
8
|
-
*
|
|
9
|
-
* Virtual mode uses a JS-DRIVEN scroller (like Smart): the option list does
|
|
10
|
-
* NOT use native overflow scroll - a single `scrollOffset` state positions the
|
|
11
|
-
* rows (`translateY`) AND drives the virtualizer window in the same reactive
|
|
12
|
-
* flush. Native scroll is composited off the main thread, so it paints the
|
|
13
|
-
* empty scrolled position a frame before JS can re-window (the "blank on a
|
|
14
|
-
* fast thumb-drag" flash); driving the offset in JS removes that async gap.
|
|
15
|
-
*/
|
|
16
|
-
import { untrack, type Snippet } from 'svelte'
|
|
17
|
-
import { groupOptions, normalizeOptions, flattenForVirtual, type ListOption, type IndexedOption, type RowHeight } from './list-option'
|
|
18
|
-
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
19
|
-
import { createJsScroller } from './js-scroller.svelte'
|
|
20
|
-
import { createListbox } from './createListbox.svelte'
|
|
21
|
-
import SvField from './SvField.svelte'
|
|
22
|
-
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
23
|
-
|
|
24
|
-
type Props = SvEditorProps & {
|
|
25
|
-
options: ReadonlyArray<ListOption>
|
|
26
|
-
value?: string | number |
|
|
27
|
-
onChange?: (value: any) => void
|
|
28
|
-
multiple?: boolean
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
$
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
$
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
virtualizer
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
{
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
style:transform={`translateY(${scroller.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{/
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
{/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
/*
|
|
259
|
-
.sv-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
/*
|
|
264
|
-
|
|
265
|
-
.sv-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
.sv-
|
|
271
|
-
|
|
272
|
-
.sv-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.sv-
|
|
278
|
-
.sv-
|
|
279
|
-
.sv-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvListBox - an inline single/multi-select list (WAI-ARIA listbox) with
|
|
4
|
+
* roving highlight + full keyboard. Parity: Smart `smart-list-box`. Controlled
|
|
5
|
+
* via `value` (scalar or array) + `onChange`. Scales to huge option sets with
|
|
6
|
+
* `virtual` - windowing over the shared `createSvelteVirtualizer` (slot-keyed
|
|
7
|
+
* DOM recycling), which also windows GROUPED lists and mixes row heights.
|
|
8
|
+
*
|
|
9
|
+
* Virtual mode uses a JS-DRIVEN scroller (like Smart): the option list does
|
|
10
|
+
* NOT use native overflow scroll - a single `scrollOffset` state positions the
|
|
11
|
+
* rows (`translateY`) AND drives the virtualizer window in the same reactive
|
|
12
|
+
* flush. Native scroll is composited off the main thread, so it paints the
|
|
13
|
+
* empty scrolled position a frame before JS can re-window (the "blank on a
|
|
14
|
+
* fast thumb-drag" flash); driving the offset in JS removes that async gap.
|
|
15
|
+
*/
|
|
16
|
+
import { untrack, type Snippet } from 'svelte'
|
|
17
|
+
import { groupOptions, normalizeOptions, flattenForVirtual, type ListOption, type IndexedOption, type RowHeight } from './list-option'
|
|
18
|
+
import { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
|
|
19
|
+
import { createJsScroller } from './js-scroller.svelte'
|
|
20
|
+
import { createListbox } from './createListbox.svelte'
|
|
21
|
+
import SvField from './SvField.svelte'
|
|
22
|
+
import { nextEditorId, type SvEditorProps } from './editor-contract'
|
|
23
|
+
|
|
24
|
+
type Props = SvEditorProps & {
|
|
25
|
+
options: ReadonlyArray<ListOption>
|
|
26
|
+
value?: string | number | ReadonlyArray<string | number> | ReadonlySet<string | number> | null
|
|
27
|
+
onChange?: (value: any) => void
|
|
28
|
+
multiple?: boolean
|
|
29
|
+
/**
|
|
30
|
+
* Checklist styling: each row draws a checkbox and the selected-row accent
|
|
31
|
+
* highlight is suppressed, because a list that starts fully selected (a
|
|
32
|
+
* filter checklist) would otherwise paint every row. State reads off the
|
|
33
|
+
* box; `aria-selected` still carries it for assistive tech.
|
|
34
|
+
*/
|
|
35
|
+
checkbox?: boolean
|
|
36
|
+
/** Stretch to the container width instead of the default 220px. */
|
|
37
|
+
block?: boolean
|
|
38
|
+
/** Visible height in rows before scrolling. */
|
|
39
|
+
rows?: number
|
|
40
|
+
/** Window the list (render only visible rows) for large option sets. */
|
|
41
|
+
virtual?: boolean
|
|
42
|
+
/** Row height in px - a number, or a per-option function for variable
|
|
43
|
+
* heights. Must match the rendered row height. Default 32. */
|
|
44
|
+
rowHeight?: RowHeight
|
|
45
|
+
/** Height (px) of a group heading row when virtualized + grouped. Default 28. */
|
|
46
|
+
groupHeaderHeight?: number
|
|
47
|
+
/** Custom per-option content. Receives the option. */
|
|
48
|
+
item?: Snippet<[ListOption]>
|
|
49
|
+
/** @deprecated Use `item`. */
|
|
50
|
+
itemTemplate?: Snippet<[ListOption]>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let {
|
|
54
|
+
options,
|
|
55
|
+
value = null,
|
|
56
|
+
onChange,
|
|
57
|
+
multiple = false,
|
|
58
|
+
checkbox = false,
|
|
59
|
+
block = false,
|
|
60
|
+
disabled = false,
|
|
61
|
+
size = 'md',
|
|
62
|
+
rows = 7,
|
|
63
|
+
virtual = false,
|
|
64
|
+
rowHeight = 32,
|
|
65
|
+
groupHeaderHeight = 28,
|
|
66
|
+
item,
|
|
67
|
+
itemTemplate,
|
|
68
|
+
ariaLabel,
|
|
69
|
+
name,
|
|
70
|
+
invalid = false,
|
|
71
|
+
required = false,
|
|
72
|
+
error,
|
|
73
|
+
label,
|
|
74
|
+
hint,
|
|
75
|
+
dir,
|
|
76
|
+
id,
|
|
77
|
+
}: Props = $props()
|
|
78
|
+
|
|
79
|
+
// Stable id anchors the label/hint/error wiring even when no `id` is passed.
|
|
80
|
+
const autoId = nextEditorId('sv-lb')
|
|
81
|
+
const uid = $derived(id ?? autoId)
|
|
82
|
+
|
|
83
|
+
// Tolerate primitive / partial options (e.g. setOptions([1, 2, 3])): normalize
|
|
84
|
+
// to { value, label } so labels render and keys are never undefined.
|
|
85
|
+
const norm = $derived(normalizeOptions(options))
|
|
86
|
+
|
|
87
|
+
// The styled listbox is just a renderer over the headless core.
|
|
88
|
+
const lb = createListbox({
|
|
89
|
+
options: () => norm,
|
|
90
|
+
value: () => value,
|
|
91
|
+
onChange: (v) => onChange?.(v),
|
|
92
|
+
multiple: () => multiple,
|
|
93
|
+
disabled: () => disabled,
|
|
94
|
+
ariaLabel: () => ariaLabel,
|
|
95
|
+
id: () => uid,
|
|
96
|
+
invalid: () => invalid,
|
|
97
|
+
required: () => required,
|
|
98
|
+
error: () => error,
|
|
99
|
+
hint: () => hint,
|
|
100
|
+
})
|
|
101
|
+
const isSel = (o: ListOption) => lb.isSelected(o.value)
|
|
102
|
+
|
|
103
|
+
const rowHeightBase = $derived(typeof rowHeight === 'number' ? rowHeight : 32)
|
|
104
|
+
const optHeight = (o: IndexedOption) => (typeof rowHeight === 'function' ? rowHeight(o, o.index) : rowHeight)
|
|
105
|
+
|
|
106
|
+
// --- Virtualization ---------------------------------------------------------
|
|
107
|
+
const useVirtual = $derived(virtual)
|
|
108
|
+
let listEl = $state<HTMLDivElement | null>(null)
|
|
109
|
+
let viewportH = $state(0)
|
|
110
|
+
|
|
111
|
+
const flat = $derived(flattenForVirtual(norm, { rowHeight, groupHeaderHeight }))
|
|
112
|
+
const uniform = $derived(!flat.hasGroups && typeof rowHeight === 'number')
|
|
113
|
+
const estimateSize = $derived(uniform ? rowHeightBase : (i: number) => flat.sizeAt(i))
|
|
114
|
+
|
|
115
|
+
const OVERSCAN = 6
|
|
116
|
+
// Effective viewport height: measured, or the computed `rows` height until the
|
|
117
|
+
// viewport element is measured. Never 0.
|
|
118
|
+
const effViewport = $derived(viewportH > 0 ? viewportH : Math.max(1, rows * rowHeightBase))
|
|
119
|
+
|
|
120
|
+
const virtualizer = createSvelteVirtualizer(
|
|
121
|
+
untrack(() => ({ count: flat.entries.length, estimateSize, overscan: OVERSCAN, viewportHeight: effViewport })),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
$effect(() => {
|
|
125
|
+
const count = flat.entries.length
|
|
126
|
+
const es = estimateSize
|
|
127
|
+
untrack(() => virtualizer.setOptions({ count, estimateSize: es }))
|
|
128
|
+
})
|
|
129
|
+
$effect(() => {
|
|
130
|
+
const vh = effViewport
|
|
131
|
+
untrack(() => virtualizer.setViewportHeight(vh))
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const vItems = $derived.by(() => {
|
|
135
|
+
virtualizer.version
|
|
136
|
+
return virtualizer.getVirtualItems()
|
|
137
|
+
})
|
|
138
|
+
const totalSize = $derived.by(() => {
|
|
139
|
+
virtualizer.version
|
|
140
|
+
return virtualizer.getTotalSize()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// JS-driven scroll: one offset positions the rows AND drives the virtualizer
|
|
144
|
+
// window in the same flush - no native scroll, so no async "blank flash".
|
|
145
|
+
const scroller = createJsScroller({
|
|
146
|
+
virtualizer,
|
|
147
|
+
totalSize: () => totalSize,
|
|
148
|
+
viewport: () => effViewport,
|
|
149
|
+
lineStep: () => rowHeightBase,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Keep the active option in view - via the scroller (virtual) or scrollIntoView
|
|
153
|
+
// (native non-virtual). Driven by roving keyboard nav.
|
|
154
|
+
$effect(() => {
|
|
155
|
+
const i = lb.activeIndex
|
|
156
|
+
if (useVirtual) {
|
|
157
|
+
const flatIdx = flat.optionFlatIndex[i]
|
|
158
|
+
if (flatIdx == null) return
|
|
159
|
+
untrack(() => scroller.ensureIndexVisible(flatIdx))
|
|
160
|
+
} else {
|
|
161
|
+
queueMicrotask(() => listEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir} {block}>
|
|
167
|
+
<!-- Div-based listbox (role comes from `lb.rootProps()`). -->
|
|
168
|
+
<div
|
|
169
|
+
bind:this={listEl}
|
|
170
|
+
class="sv-listbox sv-listbox--{size}"
|
|
171
|
+
class:is-invalid={invalid}
|
|
172
|
+
class:is-virtual={useVirtual}
|
|
173
|
+
class:is-checkbox={checkbox}
|
|
174
|
+
class:is-block={block}
|
|
175
|
+
style:--sv-rows={rows}
|
|
176
|
+
style:--sv-row-h={`${rowHeightBase}px`}
|
|
177
|
+
onwheel={useVirtual ? scroller.onWheel : undefined}
|
|
178
|
+
{...lb.rootProps()}
|
|
179
|
+
>
|
|
180
|
+
{#if useVirtual}
|
|
181
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
182
|
+
<div
|
|
183
|
+
class="sv-listbox__vp"
|
|
184
|
+
bind:clientHeight={viewportH}
|
|
185
|
+
onpointerdown={scroller.onContentDown}
|
|
186
|
+
onpointermove={scroller.onContentMove}
|
|
187
|
+
onpointerup={scroller.onContentUp}
|
|
188
|
+
onpointercancel={scroller.onContentUp}
|
|
189
|
+
>
|
|
190
|
+
{#each vItems as vi (vi.key)}
|
|
191
|
+
{@const entry = flat.entries[vi.index]}
|
|
192
|
+
{#if entry?.type === 'group'}
|
|
193
|
+
<div class="sv-listbox__group" role="presentation" style:height={`${vi.size}px`} style:transform={`translateY(${vi.start - scroller.scrollOffset}px)`}>{entry.label}</div>
|
|
194
|
+
{:else if entry}
|
|
195
|
+
{@render optionRow(entry.opt, entry.opt.index, vi.size, vi.start - scroller.scrollOffset)}
|
|
196
|
+
{/if}
|
|
197
|
+
{/each}
|
|
198
|
+
</div>
|
|
199
|
+
{#if scroller.maxOffset > 0}
|
|
200
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
201
|
+
<div class="sv-listbox__sb" onpointerdown={scroller.onTrackDown} aria-hidden="true">
|
|
202
|
+
<div
|
|
203
|
+
class="sv-listbox__thumb"
|
|
204
|
+
class:is-dragging={scroller.dragging}
|
|
205
|
+
style:height={`${scroller.thumbH}px`}
|
|
206
|
+
style:transform={`translateY(${scroller.thumbTop}px)`}
|
|
207
|
+
onpointerdown={scroller.onThumbDown}
|
|
208
|
+
onpointermove={scroller.onThumbMove}
|
|
209
|
+
onpointerup={scroller.onThumbUp}
|
|
210
|
+
onpointercancel={scroller.onThumbUp}
|
|
211
|
+
></div>
|
|
212
|
+
</div>
|
|
213
|
+
{/if}
|
|
214
|
+
{:else}
|
|
215
|
+
{#each groupOptions(norm) as g (g.group ?? ' ')}
|
|
216
|
+
{#if g.group != null}<div class="sv-listbox__group" role="presentation">{g.group}</div>{/if}
|
|
217
|
+
{#each g.options as opt (opt.index)}
|
|
218
|
+
{@render optionRow(opt, opt.index)}
|
|
219
|
+
{/each}
|
|
220
|
+
{/each}
|
|
221
|
+
{/if}
|
|
222
|
+
</div>
|
|
223
|
+
{#if name}{#each lb.selectedValues as v (v)}<input type="hidden" {name} value={v} />{/each}{/if}
|
|
224
|
+
</SvField>
|
|
225
|
+
|
|
226
|
+
{#snippet optionInner(opt: ListOption)}
|
|
227
|
+
{#if checkbox}
|
|
228
|
+
<span class="sv-listbox__box" class:is-on={isSel(opt)} aria-hidden="true"></span>
|
|
229
|
+
{:else if multiple}
|
|
230
|
+
<span class="sv-listbox__check" aria-hidden="true">{isSel(opt) ? '✓' : ''}</span>
|
|
231
|
+
{/if}
|
|
232
|
+
{#if item ?? itemTemplate}{@render (item ?? itemTemplate)!(opt)}{:else}<span class="sv-listbox__label">{opt.label}</span>{/if}
|
|
233
|
+
{/snippet}
|
|
234
|
+
|
|
235
|
+
{#snippet optionRow(opt: IndexedOption, index: number, h?: number, top?: number)}
|
|
236
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
237
|
+
<div
|
|
238
|
+
class="sv-listbox__opt"
|
|
239
|
+
class:is-selected={isSel(opt)}
|
|
240
|
+
class:is-active={lb.isActive(index)}
|
|
241
|
+
class:is-disabled={opt.disabled}
|
|
242
|
+
style:height={`${h ?? optHeight(opt)}px`}
|
|
243
|
+
style:transform={top == null ? undefined : `translateY(${top}px)`}
|
|
244
|
+
{...lb.optionProps(index)}
|
|
245
|
+
>
|
|
246
|
+
{@render optionInner(opt)}
|
|
247
|
+
</div>
|
|
248
|
+
{/snippet}
|
|
249
|
+
|
|
250
|
+
<style>
|
|
251
|
+
.sv-listbox {
|
|
252
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
253
|
+
margin: 0; list-style: none;
|
|
254
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
255
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
256
|
+
width: 220px; outline: none; --_fs: 13px;
|
|
257
|
+
}
|
|
258
|
+
/* Native scroll for the (fully rendered) non-virtual list. */
|
|
259
|
+
.sv-listbox:not(.is-virtual) {
|
|
260
|
+
padding: 4px; overflow-y: auto;
|
|
261
|
+
max-height: calc(var(--sv-rows, 7) * var(--sv-row-h, 32px) + 8px);
|
|
262
|
+
}
|
|
263
|
+
/* JS-driven scroller for the virtual list: fixed height, no native scroll -
|
|
264
|
+
the rows are absolutely positioned and moved by `scrollOffset`. */
|
|
265
|
+
.sv-listbox.is-virtual {
|
|
266
|
+
padding: 0; overflow: hidden; position: relative;
|
|
267
|
+
height: calc(var(--sv-rows, 7) * var(--sv-row-h, 32px) + 8px);
|
|
268
|
+
}
|
|
269
|
+
.sv-listbox.is-block { width: 100%; }
|
|
270
|
+
.sv-listbox--sm { --_fs: 12px; }
|
|
271
|
+
.sv-listbox--lg { --_fs: 15px; }
|
|
272
|
+
.sv-listbox:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
273
|
+
.sv-listbox.is-invalid { border-color: var(--sg-danger, #dc2626); }
|
|
274
|
+
.sv-listbox.is-invalid:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
|
|
275
|
+
|
|
276
|
+
/* The clipped viewport that holds the translated rows. */
|
|
277
|
+
.sv-listbox__vp { position: absolute; inset: 4px; overflow: hidden; touch-action: none; }
|
|
278
|
+
.sv-listbox.is-virtual .sv-listbox__opt,
|
|
279
|
+
.sv-listbox.is-virtual .sv-listbox__group { position: absolute; left: 0; right: 0; top: 0; }
|
|
280
|
+
|
|
281
|
+
/* Custom scrollbar (mouse affordance; keyboard nav handles a11y scrolling). */
|
|
282
|
+
.sv-listbox__sb { position: absolute; top: 4px; right: 2px; bottom: 4px; width: 10px; z-index: 2; }
|
|
283
|
+
.sv-listbox__thumb {
|
|
284
|
+
position: absolute; left: 2px; right: 2px; top: 0; min-height: 24px;
|
|
285
|
+
border-radius: 4px; background: color-mix(in srgb, currentColor 26%, transparent);
|
|
286
|
+
cursor: default; touch-action: none;
|
|
287
|
+
}
|
|
288
|
+
.sv-listbox__thumb:hover, .sv-listbox__thumb.is-dragging { background: color-mix(in srgb, currentColor 42%, transparent); }
|
|
289
|
+
|
|
290
|
+
.sv-listbox__opt {
|
|
291
|
+
display: flex; align-items: center; gap: 8px; box-sizing: border-box; flex: none; padding: 0 10px;
|
|
292
|
+
border-radius: 6px; cursor: pointer; font-size: var(--_fs, 13px);
|
|
293
|
+
}
|
|
294
|
+
.sv-listbox__opt.is-active { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
295
|
+
.sv-listbox__opt.is-selected { background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent); font-weight: 600; }
|
|
296
|
+
.sv-listbox__opt.is-selected.is-active { background: color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
297
|
+
.sv-listbox__opt.is-disabled { opacity: 0.4; cursor: not-allowed; }
|
|
298
|
+
.sv-listbox__group {
|
|
299
|
+
box-sizing: border-box; padding: 8px 10px 3px; font-size: 10.5px; font-weight: 700;
|
|
300
|
+
text-transform: uppercase; letter-spacing: 0.04em; color: var(--sg-muted, #94a3b8);
|
|
301
|
+
display: flex; align-items: flex-end;
|
|
302
|
+
}
|
|
303
|
+
.sv-listbox__check { width: 14px; text-align: center; color: var(--_accent); }
|
|
304
|
+
.sv-listbox__label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
305
|
+
|
|
306
|
+
/* ---- Checklist variant ---- */
|
|
307
|
+
.sv-listbox__box {
|
|
308
|
+
width: 14px; height: 14px; flex: none; box-sizing: border-box; position: relative;
|
|
309
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1));
|
|
310
|
+
border-radius: 3px; background: var(--sg-input-bg, #fff);
|
|
311
|
+
}
|
|
312
|
+
.sv-listbox__box.is-on { background: var(--_accent); border-color: var(--_accent); }
|
|
313
|
+
.sv-listbox__box.is-on::after {
|
|
314
|
+
content: ''; position: absolute; left: 4px; top: 1px; width: 3px; height: 7px;
|
|
315
|
+
border: solid var(--sg-input-bg, #fff); border-width: 0 2px 2px 0; transform: rotate(45deg);
|
|
316
|
+
}
|
|
317
|
+
/* The box carries the state, so selected rows keep neutral chrome - only the
|
|
318
|
+
roving highlight tints a row. */
|
|
319
|
+
.sv-listbox.is-checkbox .sv-listbox__opt.is-selected {
|
|
320
|
+
background: none; color: inherit; font-weight: inherit;
|
|
321
|
+
}
|
|
322
|
+
.sv-listbox.is-checkbox .sv-listbox__opt.is-active,
|
|
323
|
+
.sv-listbox.is-checkbox .sv-listbox__opt.is-selected.is-active {
|
|
324
|
+
background: var(--sg-row-hover-bg, #f1f5f9);
|
|
325
|
+
}
|
|
326
|
+
</style>
|