simplesvelte 2.2.16 → 2.2.18
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/Select.svelte +63 -74
- package/package.json +1 -1
package/dist/Select.svelte
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { clickOutside } from './utils.js'
|
|
3
3
|
import Input from './Input.svelte'
|
|
4
4
|
import Label from './Label.svelte'
|
|
5
|
-
import { tick } from 'svelte'
|
|
6
5
|
type Option = {
|
|
7
6
|
value: any
|
|
8
7
|
label: any
|
|
@@ -47,21 +46,15 @@
|
|
|
47
46
|
|
|
48
47
|
let detailsOpen = $state(false)
|
|
49
48
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
49
|
+
// Ensure value is properly typed for single/multiple mode
|
|
50
|
+
// Normalize on access rather than maintaining separate state
|
|
52
51
|
let normalizedValue = $derived.by(() => {
|
|
53
|
-
if (multiple
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// Sync normalized value back to value prop when it differs
|
|
62
|
-
$effect(() => {
|
|
63
|
-
if (normalizedValue !== value) {
|
|
64
|
-
value = normalizedValue
|
|
52
|
+
if (multiple) {
|
|
53
|
+
// For multiple mode, always work with arrays
|
|
54
|
+
return Array.isArray(value) ? value : value ? [value] : []
|
|
55
|
+
} else {
|
|
56
|
+
// For single mode, extract first value if array
|
|
57
|
+
return Array.isArray(value) ? (value.length > 0 ? value[0] : undefined) : value
|
|
65
58
|
}
|
|
66
59
|
})
|
|
67
60
|
|
|
@@ -79,25 +72,12 @@
|
|
|
79
72
|
return items.filter((item) => currentValue.includes(item.value))
|
|
80
73
|
})
|
|
81
74
|
|
|
82
|
-
//
|
|
83
|
-
function
|
|
84
|
-
if (!multiple) return itemValue === normalizedValue
|
|
85
|
-
const currentValue = normalizedValue
|
|
86
|
-
return Array.isArray(currentValue) && currentValue.includes(itemValue)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Toggle item selection in multi-select mode
|
|
90
|
-
async function toggleItemSelection(itemValue: any) {
|
|
75
|
+
// Remove specific item from multi-select
|
|
76
|
+
function toggleItemSelection(itemValue: any) {
|
|
91
77
|
if (!multiple) {
|
|
92
78
|
// Close dropdown and update filter immediately
|
|
93
|
-
|
|
79
|
+
filterInput = items.find((item) => item.value === itemValue)?.label || ''
|
|
94
80
|
detailsOpen = false
|
|
95
|
-
|
|
96
|
-
// Wait for DOM update so details closes properly
|
|
97
|
-
await tick()
|
|
98
|
-
|
|
99
|
-
// Update value and call onchange - these happen synchronously
|
|
100
|
-
// even though we're in an async function
|
|
101
81
|
value = itemValue
|
|
102
82
|
if (onchange) onchange(value)
|
|
103
83
|
return
|
|
@@ -126,22 +106,22 @@
|
|
|
126
106
|
// Clear all selections
|
|
127
107
|
function clearAll() {
|
|
128
108
|
value = multiple ? [] : null
|
|
129
|
-
|
|
109
|
+
filterInput = ''
|
|
130
110
|
detailsOpen = false
|
|
131
111
|
if (onchange) onchange(value)
|
|
132
112
|
}
|
|
133
113
|
|
|
134
|
-
|
|
114
|
+
// User's filter input - always editable
|
|
115
|
+
let filterInput = $state('')
|
|
135
116
|
|
|
136
|
-
//
|
|
137
|
-
$
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
} else {
|
|
142
|
-
filter = ''
|
|
143
|
-
}
|
|
117
|
+
// Display value in filter box for single-select when closed
|
|
118
|
+
let filter = $derived.by(() => {
|
|
119
|
+
// In single select mode when dropdown is closed, show selected item
|
|
120
|
+
if (!multiple && !detailsOpen && selectedItem) {
|
|
121
|
+
return selectedItem.label
|
|
144
122
|
}
|
|
123
|
+
// Otherwise use the user's filter input
|
|
124
|
+
return filterInput
|
|
145
125
|
})
|
|
146
126
|
|
|
147
127
|
let filteredItems = $derived.by(() => {
|
|
@@ -180,7 +160,6 @@
|
|
|
180
160
|
let searchEL: HTMLInputElement | undefined = $state(undefined)
|
|
181
161
|
|
|
182
162
|
// Virtual list implementation
|
|
183
|
-
let scrollContainer: HTMLDivElement | undefined = $state(undefined)
|
|
184
163
|
let scrollTop = $state(0)
|
|
185
164
|
const itemHeight = 40 // Approximate height of each item in pixels
|
|
186
165
|
const containerHeight = 320 // max-h-80 = 320px
|
|
@@ -216,35 +195,42 @@
|
|
|
216
195
|
}
|
|
217
196
|
|
|
218
197
|
// Scroll to selected item when dropdown opens
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
198
|
+
function scrollToSelected(node: HTMLDivElement) {
|
|
199
|
+
const unsubscribe = $effect.root(() => {
|
|
200
|
+
$effect(() => {
|
|
201
|
+
if (detailsOpen) {
|
|
202
|
+
// Find the index of the selected item in the flat list
|
|
203
|
+
let selectedIndex = -1
|
|
204
|
+
|
|
205
|
+
if (!multiple && selectedItem) {
|
|
206
|
+
// For single select, find the selected item
|
|
207
|
+
selectedIndex = flatList.findIndex(
|
|
208
|
+
(entry) => entry.type === 'option' && entry.item.value === selectedItem.value,
|
|
209
|
+
)
|
|
210
|
+
} else if (multiple && selectedItems.length > 0) {
|
|
211
|
+
// For multi select, find the first selected item
|
|
212
|
+
selectedIndex = flatList.findIndex(
|
|
213
|
+
(entry) =>
|
|
214
|
+
entry.type === 'option' && selectedItems.some((selected) => selected.value === entry.item.value),
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (selectedIndex >= 0) {
|
|
219
|
+
// Calculate scroll position to center the selected item
|
|
220
|
+
const targetScrollTop = Math.max(0, selectedIndex * itemHeight - containerHeight / 2 + itemHeight / 2)
|
|
221
|
+
// Set scroll position directly on the DOM node
|
|
222
|
+
node.scrollTop = targetScrollTop
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
})
|
|
239
227
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
// If no selected item, scrollTop stays at 0 (already set when dropdown was closed)
|
|
228
|
+
return {
|
|
229
|
+
destroy() {
|
|
230
|
+
unsubscribe()
|
|
231
|
+
},
|
|
246
232
|
}
|
|
247
|
-
}
|
|
233
|
+
}
|
|
248
234
|
|
|
249
235
|
const errorText = $derived.by(() => {
|
|
250
236
|
if (error) return error
|
|
@@ -276,7 +262,7 @@
|
|
|
276
262
|
class="select h-max min-h-10 w-full min-w-12 cursor-pointer !bg-none pr-1"
|
|
277
263
|
onclick={() => {
|
|
278
264
|
searchEL?.focus()
|
|
279
|
-
|
|
265
|
+
filterInput = ''
|
|
280
266
|
}}>
|
|
281
267
|
{#if multiple}
|
|
282
268
|
<!-- Multi-select display with chips -->
|
|
@@ -300,7 +286,7 @@
|
|
|
300
286
|
type="text"
|
|
301
287
|
class="h-full outline-0 {detailsOpen ? 'cursor-text' : 'cursor-pointer'}"
|
|
302
288
|
bind:this={searchEL}
|
|
303
|
-
bind:value={
|
|
289
|
+
bind:value={filterInput}
|
|
304
290
|
onclick={() => {
|
|
305
291
|
detailsOpen = true
|
|
306
292
|
}}
|
|
@@ -313,7 +299,8 @@
|
|
|
313
299
|
type="text"
|
|
314
300
|
class="h-full w-full outline-0 {detailsOpen ? 'cursor-text' : 'cursor-pointer'}"
|
|
315
301
|
bind:this={searchEL}
|
|
316
|
-
|
|
302
|
+
value={filter}
|
|
303
|
+
oninput={(e) => (filterInput = e.currentTarget.value)}
|
|
317
304
|
onclick={() => {
|
|
318
305
|
detailsOpen = true
|
|
319
306
|
}}
|
|
@@ -364,7 +351,7 @@
|
|
|
364
351
|
{/if}
|
|
365
352
|
|
|
366
353
|
{#if flatList.length > 0}
|
|
367
|
-
<div class="relative max-h-80 overflow-y-auto pr-2"
|
|
354
|
+
<div class="relative max-h-80 overflow-y-auto pr-2" use:scrollToSelected onscroll={handleScroll}>
|
|
368
355
|
<!-- Virtual spacer for items before visible range -->
|
|
369
356
|
{#if visibleItems.startIndex > 0}
|
|
370
357
|
<div style="height: {visibleItems.startIndex * itemHeight}px;"></div>
|
|
@@ -380,7 +367,9 @@
|
|
|
380
367
|
</li>
|
|
381
368
|
{:else}
|
|
382
369
|
{@const item = entry.item}
|
|
383
|
-
{@const isSelected =
|
|
370
|
+
{@const isSelected = multiple
|
|
371
|
+
? Array.isArray(normalizedValue) && normalizedValue.includes(item.value)
|
|
372
|
+
: item.value === normalizedValue}
|
|
384
373
|
<li style="height: {itemHeight}px;">
|
|
385
374
|
<button
|
|
386
375
|
class="flex h-full w-full items-center gap-2 {isSelected
|