noph-ui 0.24.11 → 0.24.13
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/dist/autocomplete/AutoComplete.svelte +76 -41
- package/dist/autocomplete/AutoComplete.svelte.d.ts +1 -1
- package/dist/autocomplete/types.d.ts +1 -0
- package/dist/chip/InputChip.svelte +1 -1
- package/dist/menu/Menu.svelte +1 -1
- package/dist/select/Select.svelte +93 -64
- package/dist/select/VirtualList.svelte +7 -1
- package/dist/select/VirtualList.svelte.d.ts +4 -0
- package/dist/select/types.d.ts +1 -0
- package/dist/text-field/TextField.svelte +8 -7
- package/package.json +1 -1
|
@@ -16,9 +16,7 @@
|
|
|
16
16
|
clampMenuWidth = false,
|
|
17
17
|
children,
|
|
18
18
|
optionsFilter,
|
|
19
|
-
|
|
20
|
-
hidePopover = $bindable(),
|
|
21
|
-
onoptionselect = (option) => {
|
|
19
|
+
onoptionselect = (option: AutoCompleteOption) => {
|
|
22
20
|
value = option.label
|
|
23
21
|
finalPopulated = populated
|
|
24
22
|
menuElement?.hidePopover()
|
|
@@ -26,43 +24,41 @@
|
|
|
26
24
|
onkeydown,
|
|
27
25
|
onclick,
|
|
28
26
|
oninput,
|
|
27
|
+
virtualThreshold = 300,
|
|
29
28
|
...attributes
|
|
30
29
|
}: AutoCompleteProps = $props()
|
|
31
30
|
|
|
32
|
-
showPopover = () => {
|
|
33
|
-
menuElement?.showPopover()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
hidePopover = () => {
|
|
37
|
-
menuElement?.hidePopover()
|
|
38
|
-
}
|
|
39
31
|
const uid = $props.id()
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
const query = $derived(value ? value.toLocaleLowerCase() : '')
|
|
33
|
+
const filterFn = $derived(
|
|
34
|
+
optionsFilter ||
|
|
35
|
+
((option: AutoCompleteOption) => !query || option.label.toLocaleLowerCase().includes(query)),
|
|
36
|
+
)
|
|
37
|
+
const NO_INDEX = -1
|
|
38
|
+
let displayOptions = $derived(query === '' && !optionsFilter ? options : options.filter(filterFn))
|
|
39
|
+
let useVirtualList = $derived(displayOptions.length > virtualThreshold)
|
|
40
|
+
let widthProp = $derived(clampMenuWidth || useVirtualList ? 'width' : 'min-width')
|
|
45
41
|
let clientWidth = $state(0)
|
|
46
42
|
let menuElement = $state<HTMLDivElement>()
|
|
47
43
|
let menuOpen = $state(false)
|
|
48
44
|
let finalPopulated = $state(populated)
|
|
49
|
-
let activeIndex = $state(
|
|
45
|
+
let activeIndex = $state(NO_INDEX)
|
|
50
46
|
|
|
51
|
-
|
|
47
|
+
const setActive = (index: number) => {
|
|
52
48
|
if (index < 0 || index >= displayOptions.length) {
|
|
53
|
-
activeIndex =
|
|
49
|
+
activeIndex = NO_INDEX
|
|
54
50
|
return
|
|
55
51
|
}
|
|
56
52
|
activeIndex = index
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
|
|
55
|
+
const moveActive = (delta: number) => {
|
|
60
56
|
if (!displayOptions.length) {
|
|
61
|
-
activeIndex =
|
|
57
|
+
activeIndex = NO_INDEX
|
|
62
58
|
return
|
|
63
59
|
}
|
|
64
60
|
const next =
|
|
65
|
-
activeIndex ===
|
|
61
|
+
activeIndex === NO_INDEX
|
|
66
62
|
? delta > 0
|
|
67
63
|
? 0
|
|
68
64
|
: displayOptions.length - 1
|
|
@@ -70,9 +66,40 @@
|
|
|
70
66
|
setActive(next)
|
|
71
67
|
}
|
|
72
68
|
|
|
69
|
+
const selectOption = (option: AutoCompleteOption) => {
|
|
70
|
+
onoptionselect(option)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
73
|
$effect(() => {
|
|
74
74
|
if (activeIndex >= displayOptions.length) {
|
|
75
|
-
activeIndex =
|
|
75
|
+
activeIndex = NO_INDEX
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (!menuOpen || activeIndex < 0) return
|
|
79
|
+
const id = `${uid}-opt-${activeIndex}`
|
|
80
|
+
const optEl = document.getElementById(id)
|
|
81
|
+
if (optEl) {
|
|
82
|
+
optEl.scrollIntoView({ block: 'nearest' })
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
if (useVirtualList && menuElement) {
|
|
86
|
+
const viewport = menuElement.querySelector(
|
|
87
|
+
'svelte-virtual-list-viewport',
|
|
88
|
+
) as HTMLElement | null
|
|
89
|
+
if (!viewport) return
|
|
90
|
+
let rowHeight = 48
|
|
91
|
+
const firstRow = viewport.querySelector('[id^="' + uid + '-opt-"]') as HTMLElement | null
|
|
92
|
+
if (firstRow) {
|
|
93
|
+
rowHeight = firstRow.offsetHeight || rowHeight
|
|
94
|
+
}
|
|
95
|
+
const top = activeIndex * rowHeight
|
|
96
|
+
const bottom = top + rowHeight
|
|
97
|
+
const { scrollTop, clientHeight } = viewport
|
|
98
|
+
if (top < scrollTop) {
|
|
99
|
+
viewport.scrollTop = top
|
|
100
|
+
} else if (bottom > scrollTop + clientHeight) {
|
|
101
|
+
viewport.scrollTop = bottom - clientHeight
|
|
102
|
+
}
|
|
76
103
|
}
|
|
77
104
|
})
|
|
78
105
|
</script>
|
|
@@ -83,14 +110,15 @@
|
|
|
83
110
|
softFocus={index === activeIndex}
|
|
84
111
|
aria-selected={index === activeIndex}
|
|
85
112
|
role="option"
|
|
86
|
-
|
|
113
|
+
tabindex={-1}
|
|
114
|
+
onpointerdown={(e) => {
|
|
87
115
|
e.preventDefault()
|
|
88
116
|
}}
|
|
89
117
|
onmouseenter={() => setActive(index)}
|
|
90
118
|
onclick={(event) => {
|
|
91
119
|
event.preventDefault()
|
|
92
120
|
setActive(index)
|
|
93
|
-
|
|
121
|
+
selectOption(option)
|
|
94
122
|
}}
|
|
95
123
|
variant="button"
|
|
96
124
|
>{option.label}
|
|
@@ -110,46 +138,56 @@
|
|
|
110
138
|
aria-controls="listbox-{uid}"
|
|
111
139
|
aria-expanded={menuOpen}
|
|
112
140
|
aria-autocomplete="list"
|
|
113
|
-
aria-activedescendant={activeIndex >= 0 ? `${uid}-opt-${activeIndex}` : undefined}
|
|
141
|
+
aria-activedescendant={menuOpen && activeIndex >= 0 ? `${uid}-opt-${activeIndex}` : undefined}
|
|
114
142
|
aria-haspopup="listbox"
|
|
115
143
|
onclick={(event) => {
|
|
116
144
|
finalPopulated = true
|
|
117
|
-
showPopover()
|
|
145
|
+
menuElement?.showPopover()
|
|
118
146
|
onclick?.(event)
|
|
119
147
|
}}
|
|
120
148
|
oninput={(event) => {
|
|
121
|
-
showPopover()
|
|
122
|
-
activeIndex =
|
|
149
|
+
menuElement?.showPopover()
|
|
150
|
+
activeIndex = NO_INDEX
|
|
123
151
|
oninput?.(event)
|
|
124
152
|
}}
|
|
125
153
|
onkeydown={(event) => {
|
|
126
154
|
if (event.key === 'Tab') {
|
|
127
155
|
return
|
|
128
156
|
}
|
|
129
|
-
if (event.key === 'Escape') {
|
|
130
|
-
hidePopover()
|
|
131
|
-
activeIndex =
|
|
157
|
+
if (event.key === 'Escape' && menuOpen) {
|
|
158
|
+
menuElement?.hidePopover()
|
|
159
|
+
activeIndex = NO_INDEX
|
|
132
160
|
event.preventDefault()
|
|
133
161
|
return
|
|
134
162
|
}
|
|
135
163
|
if (event.key === 'ArrowDown') {
|
|
136
164
|
finalPopulated = true
|
|
137
|
-
showPopover()
|
|
165
|
+
menuElement?.showPopover()
|
|
138
166
|
moveActive(1)
|
|
139
167
|
event.preventDefault()
|
|
140
168
|
return
|
|
141
169
|
}
|
|
170
|
+
if (event.key === 'Home') {
|
|
171
|
+
setActive(0)
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
if (event.key === 'End') {
|
|
176
|
+
setActive(displayOptions.length - 1)
|
|
177
|
+
event.preventDefault()
|
|
178
|
+
return
|
|
179
|
+
}
|
|
142
180
|
if (event.key === 'ArrowUp') {
|
|
143
181
|
finalPopulated = true
|
|
144
|
-
showPopover()
|
|
182
|
+
menuElement?.showPopover()
|
|
145
183
|
moveActive(-1)
|
|
146
184
|
event.preventDefault()
|
|
147
185
|
return
|
|
148
186
|
}
|
|
149
|
-
if (event.key === 'Enter' && activeIndex >= 0) {
|
|
187
|
+
if (event.key === 'Enter' && menuOpen && activeIndex >= 0) {
|
|
150
188
|
const opt = displayOptions[activeIndex]
|
|
151
189
|
if (opt) {
|
|
152
|
-
|
|
190
|
+
selectOption(opt)
|
|
153
191
|
}
|
|
154
192
|
event.preventDefault()
|
|
155
193
|
return
|
|
@@ -163,9 +201,7 @@
|
|
|
163
201
|
</TextField>
|
|
164
202
|
<Menu
|
|
165
203
|
id="listbox-{uid}"
|
|
166
|
-
style="position-anchor:--{uid};{
|
|
167
|
-
? 'width'
|
|
168
|
-
: 'min-width'}:{clientWidth}px"
|
|
204
|
+
style="position-anchor:--{uid};{widthProp}:{clientWidth}px"
|
|
169
205
|
role="listbox"
|
|
170
206
|
class={[!displayOptions.length && 'np-auto-complete-empty']}
|
|
171
207
|
--np-menu-justify-self="none"
|
|
@@ -178,15 +214,14 @@
|
|
|
178
214
|
ontoggle={(e) => {
|
|
179
215
|
if (e.newState === 'closed') {
|
|
180
216
|
menuOpen = false
|
|
181
|
-
activeIndex =
|
|
217
|
+
activeIndex = NO_INDEX
|
|
182
218
|
if (!populated && finalPopulated && !value) {
|
|
183
219
|
finalPopulated = false
|
|
184
220
|
}
|
|
185
221
|
} else {
|
|
186
222
|
menuOpen = true
|
|
187
|
-
// Ensure activeIndex valid when opening
|
|
188
223
|
if (activeIndex >= displayOptions.length) {
|
|
189
|
-
activeIndex =
|
|
224
|
+
activeIndex = NO_INDEX
|
|
190
225
|
}
|
|
191
226
|
}
|
|
192
227
|
}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AutoCompleteProps } from './types.ts';
|
|
2
|
-
declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value" | "
|
|
2
|
+
declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value" | "reportValidity" | "checkValidity">;
|
|
3
3
|
type AutoComplete = ReturnType<typeof AutoComplete>;
|
|
4
4
|
export default AutoComplete;
|
package/dist/menu/Menu.svelte
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
reportValidity = $bindable(),
|
|
33
33
|
checkValidity = $bindable(),
|
|
34
34
|
multiple,
|
|
35
|
+
virtualThreshold = 300,
|
|
35
36
|
clampMenuWidth = false,
|
|
36
37
|
...attributes
|
|
37
38
|
}: SelectProps = $props()
|
|
@@ -45,17 +46,18 @@
|
|
|
45
46
|
value = options.find((option) => option.selected)?.value
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
|
|
50
|
+
let valueArray = $derived<unknown[]>(
|
|
51
|
+
Array.isArray(value) ? value : value === undefined || value === null ? [] : [value],
|
|
52
|
+
)
|
|
53
|
+
let selectedSet = $derived.by<Set<unknown>>(() => new Set(valueArray))
|
|
48
54
|
let selectedOption: SelectOption[] = $derived(
|
|
49
|
-
options
|
|
50
|
-
.filter(
|
|
51
|
-
(option) =>
|
|
52
|
-
option.selected ||
|
|
53
|
-
(Array.isArray(value) ? value.includes(option.value) : value === option.value),
|
|
54
|
-
)
|
|
55
|
-
.map((option) => ({ ...option, selected: true })),
|
|
55
|
+
options.filter((o) => selectedSet.has(o.value)).map((o) => ({ ...o, selected: true })),
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
-
let useVirtualList = $derived(options.length >
|
|
58
|
+
let useVirtualList = $derived(options.length > virtualThreshold)
|
|
59
|
+
|
|
60
|
+
let widthProp = $derived(clampMenuWidth || useVirtualList ? 'width' : 'min-width')
|
|
59
61
|
|
|
60
62
|
let errorTextRaw: string = $state(errorText)
|
|
61
63
|
let errorRaw = $state(error)
|
|
@@ -129,43 +131,70 @@
|
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
})
|
|
132
|
-
|
|
134
|
+
|
|
135
|
+
let cachedRowHeight = 0
|
|
136
|
+
const ensureRowHeight = () => {
|
|
137
|
+
if (!cachedRowHeight && menuElement) {
|
|
138
|
+
const viewport = menuElement.querySelector(
|
|
139
|
+
'svelte-virtual-list-viewport',
|
|
140
|
+
) as HTMLElement | null
|
|
141
|
+
if (viewport) {
|
|
142
|
+
const firstRow = viewport.querySelector('[id^="' + uid + '-opt-"]') as HTMLElement | null
|
|
143
|
+
cachedRowHeight = firstRow?.offsetHeight || 48
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!cachedRowHeight) cachedRowHeight = 48
|
|
147
|
+
return cachedRowHeight
|
|
148
|
+
}
|
|
149
|
+
const scrollOptionIntoView = (index: number) => {
|
|
150
|
+
if (!useVirtualList || !menuElement) return
|
|
151
|
+
const viewport = menuElement.querySelector('svelte-virtual-list-viewport') as HTMLElement | null
|
|
152
|
+
if (!viewport) return
|
|
153
|
+
const rowHeight = ensureRowHeight()
|
|
154
|
+
const top = index * rowHeight
|
|
155
|
+
const bottom = top + rowHeight
|
|
156
|
+
const { scrollTop, clientHeight } = viewport
|
|
157
|
+
if (top < scrollTop) viewport.scrollTop = top
|
|
158
|
+
else if (bottom > scrollTop + clientHeight) viewport.scrollTop = bottom - clientHeight
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const finalizeSelection = async () => {
|
|
162
|
+
await tick()
|
|
163
|
+
if (doValidity && checkValidity()) {
|
|
164
|
+
errorRaw = error
|
|
165
|
+
errorTextRaw = errorText
|
|
166
|
+
}
|
|
167
|
+
selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
168
|
+
}
|
|
169
|
+
const toggleValue = (option: SelectOption) => {
|
|
133
170
|
if (multiple) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} else {
|
|
139
|
-
selectedOption = [...selectedOption, option]
|
|
140
|
-
value = [...value, option.value]
|
|
141
|
-
}
|
|
171
|
+
let arr = Array.isArray(value) ? [...value] : []
|
|
172
|
+
const idx = arr.indexOf(option.value)
|
|
173
|
+
if (idx !== -1) {
|
|
174
|
+
arr.splice(idx, 1)
|
|
142
175
|
} else {
|
|
143
|
-
|
|
144
|
-
value = [option.value]
|
|
176
|
+
arr.push(option.value)
|
|
145
177
|
}
|
|
178
|
+
value = arr
|
|
146
179
|
} else {
|
|
147
|
-
selectedOption = [option]
|
|
148
180
|
value = option.value
|
|
149
|
-
menuElement?.hidePopover()
|
|
150
181
|
}
|
|
182
|
+
}
|
|
183
|
+
const handleOptionSelect = async (event: Event, option: SelectOption) => {
|
|
184
|
+
if (option.disabled) return
|
|
185
|
+
toggleValue(option)
|
|
186
|
+
if (!multiple) menuElement?.hidePopover()
|
|
151
187
|
event.preventDefault()
|
|
152
|
-
|
|
153
|
-
if (doValidity && checkValidity()) {
|
|
154
|
-
errorRaw = error
|
|
155
|
-
errorTextRaw = errorText
|
|
156
|
-
}
|
|
157
|
-
selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
158
|
-
})
|
|
188
|
+
await finalizeSelection()
|
|
159
189
|
}
|
|
160
190
|
|
|
161
191
|
const openMenuAndFocus = async (index: number) => {
|
|
162
|
-
if (!menuOpen)
|
|
163
|
-
menuElement?.showPopover()
|
|
164
|
-
}
|
|
192
|
+
if (!menuOpen) menuElement?.showPopover()
|
|
165
193
|
focusIndex = Math.min(Math.max(index, 0), options.length - 1)
|
|
166
194
|
await tick()
|
|
167
195
|
const el = document.getElementById(`${uid}-opt-${focusIndex}`)
|
|
168
|
-
|
|
196
|
+
if (el) el.focus()
|
|
197
|
+
else scrollOptionIntoView(focusIndex)
|
|
169
198
|
}
|
|
170
199
|
|
|
171
200
|
const moveFocus = (delta: number) => {
|
|
@@ -233,6 +262,23 @@
|
|
|
233
262
|
performTypeahead('')
|
|
234
263
|
}
|
|
235
264
|
}
|
|
265
|
+
|
|
266
|
+
const handleInvalid = (
|
|
267
|
+
event: Event & {
|
|
268
|
+
currentTarget: EventTarget & HTMLSelectElement
|
|
269
|
+
},
|
|
270
|
+
) => {
|
|
271
|
+
event.preventDefault()
|
|
272
|
+
const { currentTarget } = event
|
|
273
|
+
errorRaw = true
|
|
274
|
+
doValidity = true
|
|
275
|
+
if (errorText === '') {
|
|
276
|
+
errorTextRaw = currentTarget.validationMessage
|
|
277
|
+
}
|
|
278
|
+
if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
|
|
279
|
+
field?.focus()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
236
282
|
</script>
|
|
237
283
|
|
|
238
284
|
{#snippet arrows()}
|
|
@@ -324,7 +370,6 @@
|
|
|
324
370
|
}
|
|
325
371
|
return
|
|
326
372
|
}
|
|
327
|
-
// Printable character for typeahead
|
|
328
373
|
if (key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
329
374
|
performTypeahead(key)
|
|
330
375
|
return
|
|
@@ -379,18 +424,7 @@
|
|
|
379
424
|
multiple
|
|
380
425
|
{onchange}
|
|
381
426
|
{oninput}
|
|
382
|
-
oninvalid={
|
|
383
|
-
event.preventDefault()
|
|
384
|
-
const { currentTarget } = event
|
|
385
|
-
errorRaw = true
|
|
386
|
-
doValidity = true
|
|
387
|
-
if (errorText === '') {
|
|
388
|
-
errorTextRaw = currentTarget.validationMessage
|
|
389
|
-
}
|
|
390
|
-
if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
|
|
391
|
-
field?.focus()
|
|
392
|
-
}
|
|
393
|
-
}}
|
|
427
|
+
oninvalid={handleInvalid}
|
|
394
428
|
bind:value
|
|
395
429
|
bind:this={selectElement}
|
|
396
430
|
>
|
|
@@ -409,18 +443,7 @@
|
|
|
409
443
|
{form}
|
|
410
444
|
{onchange}
|
|
411
445
|
{oninput}
|
|
412
|
-
oninvalid={
|
|
413
|
-
event.preventDefault()
|
|
414
|
-
const { currentTarget } = event
|
|
415
|
-
errorRaw = true
|
|
416
|
-
doValidity = true
|
|
417
|
-
if (errorText === '') {
|
|
418
|
-
errorTextRaw = currentTarget.validationMessage
|
|
419
|
-
}
|
|
420
|
-
if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
|
|
421
|
-
field?.focus()
|
|
422
|
-
}
|
|
423
|
-
}}
|
|
446
|
+
oninvalid={handleInvalid}
|
|
424
447
|
bind:value
|
|
425
448
|
bind:this={selectElement}
|
|
426
449
|
>
|
|
@@ -544,9 +567,7 @@
|
|
|
544
567
|
|
|
545
568
|
<Menu
|
|
546
569
|
id="listbox-{uid}"
|
|
547
|
-
style=
|
|
548
|
-
? 'width'
|
|
549
|
-
: 'min-width'}:{clientWidth}px"
|
|
570
|
+
style={`position-anchor:--${uid};${widthProp}:${clientWidth}px`}
|
|
550
571
|
role="listbox"
|
|
551
572
|
aria-multiselectable={multiple}
|
|
552
573
|
--np-menu-justify-self="none"
|
|
@@ -572,9 +593,6 @@
|
|
|
572
593
|
}
|
|
573
594
|
if (idx < 0) idx = 0
|
|
574
595
|
focusIndex = idx
|
|
575
|
-
await tick()
|
|
576
|
-
const el = document.getElementById(`${uid}-opt-${focusIndex}`)
|
|
577
|
-
;(el as HTMLElement | null)?.focus?.()
|
|
578
596
|
} else {
|
|
579
597
|
menuOpen = false
|
|
580
598
|
focusIndex = -1
|
|
@@ -583,7 +601,18 @@
|
|
|
583
601
|
bind:element={menuElement}
|
|
584
602
|
>
|
|
585
603
|
{#if useVirtualList}
|
|
586
|
-
<VirtualList
|
|
604
|
+
<VirtualList
|
|
605
|
+
height="250px"
|
|
606
|
+
itemHeight={48}
|
|
607
|
+
items={options}
|
|
608
|
+
rendered={({ start, end }) => {
|
|
609
|
+
if (focusIndex >= start && focusIndex < end) {
|
|
610
|
+
const el = document.getElementById(`${uid}-opt-${focusIndex}`)
|
|
611
|
+
if (el) el.focus()
|
|
612
|
+
else scrollOptionIntoView(focusIndex)
|
|
613
|
+
}
|
|
614
|
+
}}
|
|
615
|
+
>
|
|
587
616
|
{#snippet row(option, index)}
|
|
588
617
|
{@render item(option, index)}
|
|
589
618
|
{/snippet}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
end?: number
|
|
11
11
|
row: Snippet<[T, number]>
|
|
12
12
|
overscan?: number
|
|
13
|
+
rendered?: (event: { start: number; end: number }) => void
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
let {
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
start = $bindable(0),
|
|
20
21
|
end = $bindable(0),
|
|
21
22
|
row,
|
|
22
|
-
overscan =
|
|
23
|
+
overscan = 0,
|
|
24
|
+
rendered,
|
|
23
25
|
}: VirtualListProps = $props()
|
|
24
26
|
|
|
25
27
|
let height_map: number[] = []
|
|
@@ -79,6 +81,8 @@
|
|
|
79
81
|
|
|
80
82
|
bottom = remaining * average_height
|
|
81
83
|
height_map.length = items.length
|
|
84
|
+
|
|
85
|
+
rendered?.({ start, end })
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
async function handle_scroll() {
|
|
@@ -151,6 +155,8 @@
|
|
|
151
155
|
const d = actual_height - expected_height
|
|
152
156
|
viewport.scrollTo(0, scrollTop + d)
|
|
153
157
|
}
|
|
158
|
+
await tick()
|
|
159
|
+
rendered?.({ start, end })
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
onMount(() => {
|
package/dist/select/types.d.ts
CHANGED
|
@@ -222,12 +222,11 @@
|
|
|
222
222
|
></textarea>
|
|
223
223
|
{:else}
|
|
224
224
|
<div class="input-wrapper">
|
|
225
|
-
{#if
|
|
226
|
-
<span class="
|
|
227
|
-
{
|
|
225
|
+
{#if suffixText}
|
|
226
|
+
<span class="suffix">
|
|
227
|
+
{suffixText}
|
|
228
228
|
</span>
|
|
229
229
|
{/if}
|
|
230
|
-
{@render children?.()}
|
|
231
230
|
<input
|
|
232
231
|
aria-describedby={supportingText || (errorTextRaw && errorRaw)
|
|
233
232
|
? `supporting-text-${uid}`
|
|
@@ -242,9 +241,10 @@
|
|
|
242
241
|
class="input"
|
|
243
242
|
aria-invalid={errorRaw}
|
|
244
243
|
/>
|
|
245
|
-
{
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
{@render children?.()}
|
|
245
|
+
{#if prefixText}
|
|
246
|
+
<span class="prefix">
|
|
247
|
+
{prefixText}
|
|
248
248
|
</span>
|
|
249
249
|
{/if}
|
|
250
250
|
</div>
|
|
@@ -444,6 +444,7 @@
|
|
|
444
444
|
flex-wrap: wrap;
|
|
445
445
|
align-items: baseline;
|
|
446
446
|
min-width: 0;
|
|
447
|
+
flex-direction: row-reverse;
|
|
447
448
|
}
|
|
448
449
|
|
|
449
450
|
.input-wrapper > * {
|