svelora 3.0.1 → 3.0.2
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/Accordion/Accordion.svelte +66 -97
- package/dist/Alert/Alert.svelte +39 -64
- package/dist/Alert/Alert.svelte.d.ts +1 -1
- package/dist/Avatar/Avatar.svelte +35 -75
- package/dist/AvatarGroup/AvatarGroup.svelte +38 -55
- package/dist/Badge/Badge.svelte +28 -50
- package/dist/Banner/Banner.svelte +46 -41
- package/dist/Banner/Banner.svelte.d.ts +1 -1
- package/dist/Breadcrumb/Breadcrumb.svelte +32 -26
- package/dist/Button/Button.svelte +70 -138
- package/dist/Calendar/Calendar.svelte +94 -157
- package/dist/Calendar/Calendar.svelte.d.ts +1 -1
- package/dist/Card/Card.svelte +18 -31
- package/dist/Carousel/Carousel.svelte +118 -173
- package/dist/Checkbox/Checkbox.svelte +52 -97
- package/dist/CheckboxGroup/CheckboxGroup.svelte +62 -107
- package/dist/CheckboxGroup/CheckboxGroup.svelte.d.ts +1 -1
- package/dist/Chip/Chip.svelte +22 -34
- package/dist/CodeBlock/CodeBlock.svelte +42 -59
- package/dist/Collapsible/Collapsible.svelte +22 -38
- package/dist/Collapsible/Collapsible.svelte.d.ts +1 -1
- package/dist/Collapsible/CollapsibleTestWrapper.svelte +2 -5
- package/dist/Collapsible/CollapsibleTestWrapper.svelte.d.ts +1 -1
- package/dist/Command/Command.svelte +40 -77
- package/dist/Command/Command.svelte.d.ts +1 -1
- package/dist/Command/CommandTestWrapper.svelte +2 -10
- package/dist/Command/CommandTestWrapper.svelte.d.ts +1 -1
- package/dist/Container/Container.svelte +11 -14
- package/dist/ContextMenu/ContextMenu.svelte +51 -114
- package/dist/ContextMenu/ContextMenu.svelte.d.ts +1 -1
- package/dist/Drawer/Drawer.svelte +72 -110
- package/dist/Drawer/DrawerTriggerTestWrapper.svelte +1 -2
- package/dist/DropdownMenu/DropdownMenu.svelte +63 -124
- package/dist/DropdownMenu/DropdownMenu.svelte.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuTriggerTestWrapper.svelte +2 -5
- package/dist/Editor/Editor.svelte +441 -576
- package/dist/Editor/Editor.svelte.d.ts +1 -1
- package/dist/Editor/EditorUrlPrompt.svelte +40 -53
- package/dist/Editor/SlashPopup.svelte +12 -24
- package/dist/Empty/Empty.svelte +32 -63
- package/dist/FieldGroup/FieldGroup.svelte +23 -38
- package/dist/FileUpload/FileUpload.svelte +242 -320
- package/dist/FileUpload/FileUpload.svelte.d.ts +1 -1
- package/dist/Fonts/Fonts.svelte +15 -37
- package/dist/Form/Form.svelte +112 -170
- package/dist/FormField/FormField.svelte +102 -135
- package/dist/Icon/Icon.svelte +7 -32
- package/dist/Input/Input.svelte +71 -141
- package/dist/Input/Input.svelte.d.ts +2 -2
- package/dist/Kbd/Kbd.svelte +18 -34
- package/dist/Link/Link.svelte +129 -196
- package/dist/LocaleButton/LocaleButton.svelte +165 -0
- package/dist/LocaleButton/LocaleButton.svelte.d.ts +5 -0
- package/dist/LocaleButton/index.d.ts +2 -0
- package/dist/LocaleButton/index.js +1 -0
- package/dist/LocaleButton/locale-button.types.d.ts +182 -0
- package/dist/LocaleButton/locale-button.types.js +1 -0
- package/dist/LocaleButton/locale-button.variants.d.ts +61 -0
- package/dist/LocaleButton/locale-button.variants.js +34 -0
- package/dist/Modal/Modal.svelte +52 -106
- package/dist/Modal/ModalTriggerTestWrapper.svelte +1 -2
- package/dist/Pagination/Pagination.svelte +48 -92
- package/dist/Pagination/pagination.variants.d.ts +1 -1
- package/dist/PinInput/PinInput.svelte +57 -111
- package/dist/PinInput/PinInput.svelte.d.ts +1 -1
- package/dist/Popover/Popover.svelte +28 -61
- package/dist/Popover/Popover.svelte.d.ts +1 -1
- package/dist/Progress/Progress.svelte +75 -94
- package/dist/RadioGroup/RadioGroup.svelte +54 -99
- package/dist/RadioGroup/RadioGroup.svelte.d.ts +1 -1
- package/dist/Select/Select.svelte +112 -269
- package/dist/Select/Select.svelte.d.ts +1 -1
- package/dist/SelectMenu/SelectMenu.svelte +211 -409
- package/dist/SelectMenu/SelectMenu.svelte.d.ts +1 -1
- package/dist/SelectMenu/SelectMenuFormFieldTestWrapper.svelte +3 -6
- package/dist/Separator/Separator.svelte +29 -44
- package/dist/Skeleton/Skeleton.svelte +11 -23
- package/dist/Slideover/Slideover.svelte +52 -106
- package/dist/Slideover/SlideoverTriggerTestWrapper.svelte +1 -2
- package/dist/Slider/Slider.svelte +48 -84
- package/dist/Slider/Slider.svelte.d.ts +1 -1
- package/dist/Stepper/Stepper.svelte +139 -132
- package/dist/Stepper/Stepper.svelte.d.ts +1 -1
- package/dist/Switch/Switch.svelte +62 -98
- package/dist/Table/Table.svelte +232 -283
- package/dist/Table/table.variants.d.ts +1 -1
- package/dist/Tabs/Tabs.svelte +96 -129
- package/dist/Tabs/Tabs.svelte.d.ts +1 -1
- package/dist/Textarea/Textarea.svelte +90 -173
- package/dist/Textarea/Textarea.svelte.d.ts +1 -1
- package/dist/ThemeModeButton/ThemeModeButton.svelte +16 -38
- package/dist/Timeline/Timeline.svelte +75 -54
- package/dist/Toast/Toaster.svelte +8 -25
- package/dist/Tooltip/Tooltip.svelte +34 -66
- package/dist/Tooltip/Tooltip.svelte.d.ts +1 -1
- package/dist/Tooltip/TooltipTestWrapper.svelte +2 -5
- package/dist/User/User.svelte +33 -49
- package/dist/docs/navigation.js +6 -0
- package/dist/hooks/HookContextProbe.svelte +2 -4
- package/dist/hooks/HookContextProvider.svelte +8 -6
- package/dist/hooks/HookEmitProbe.svelte +8 -11
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/svelora-docs.data.json +4 -2
- package/package.json +16 -8
|
@@ -1,414 +1,216 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
|
|
3
|
-
SelectMenuItem,
|
|
4
|
-
SelectMenuItemType,
|
|
5
|
-
SelectMenuProps
|
|
6
|
-
} from './select-menu.types.js'
|
|
7
|
-
|
|
8
|
-
export type Props = SelectMenuProps
|
|
9
|
-
|
|
10
|
-
const CREATE_ITEM_VALUE = '@@svelora/select-menu/create-item'
|
|
1
|
+
<script lang="ts" module>const CREATE_ITEM_VALUE = "@@svelora/select-menu/create-item";
|
|
2
|
+
export {};
|
|
11
3
|
</script>
|
|
12
4
|
|
|
13
|
-
<script lang="ts">
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
if ('type' in i) continue
|
|
223
|
-
if (i.value.toLowerCase() === query || (i.label ?? i.value).toLowerCase() === query) {
|
|
224
|
-
return true
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return false
|
|
228
|
-
})
|
|
229
|
-
const showCreateItem = $derived.by(() => {
|
|
230
|
-
if (!createItem) return false
|
|
231
|
-
if (!trimmedSearch) return false
|
|
232
|
-
const mode = createItem === true ? 'lazy' : createItem
|
|
233
|
-
if (mode === 'always') return true
|
|
234
|
-
return !exactMatchExists
|
|
235
|
-
})
|
|
236
|
-
const resolvedCreateLabel = $derived(
|
|
237
|
-
typeof createItemLabel === 'function' ? createItemLabel(trimmedSearch) : createItemLabel
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
function findItemByCaseInsensitive(query: string): SelectMenuItem | undefined {
|
|
241
|
-
const q = query.toLowerCase()
|
|
242
|
-
for (const it of itemsMap.values()) {
|
|
243
|
-
if (it.value.toLowerCase() === q || (it.label ?? it.value).toLowerCase() === q) {
|
|
244
|
-
return it
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return undefined
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function selectValue(val: string) {
|
|
251
|
-
if (multiple) {
|
|
252
|
-
if (!selectedValues.includes(val)) {
|
|
253
|
-
value = [...selectedValues, val]
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
value = val
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function handleCreate() {
|
|
261
|
-
if (!showCreateItem) return
|
|
262
|
-
const newValue = trimmedSearch
|
|
263
|
-
if (!newValue) return
|
|
264
|
-
|
|
265
|
-
const existing = findItemByCaseInsensitive(newValue)
|
|
266
|
-
if (existing) {
|
|
267
|
-
selectValue(existing.value)
|
|
268
|
-
} else {
|
|
269
|
-
createdItems = [...createdItems, { value: newValue, label: newValue }]
|
|
270
|
-
selectValue(newValue)
|
|
271
|
-
onCreate?.(newValue)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
emit.onChange()
|
|
275
|
-
resetSearch()
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ---- Leading / trailing ----
|
|
279
|
-
const displayAvatar = $derived(multiple ? avatar : (singleSelectedItem?.avatar ?? avatar))
|
|
280
|
-
const displayIcon = $derived(
|
|
281
|
-
multiple ? (leadingIcon ?? icon) : (singleSelectedItem?.icon ?? leadingIcon ?? icon)
|
|
282
|
-
)
|
|
283
|
-
const isLeading = $derived(!!leadingSlot || !!displayAvatar || !!displayIcon)
|
|
284
|
-
const leadingIconName = $derived(
|
|
285
|
-
loading && isLeading ? loadingIcon : !displayAvatar ? displayIcon : undefined
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
// ---- Trailing icon ----
|
|
289
|
-
const trailingIconName = $derived(loading && !isLeading ? loadingIcon : trailingIcon)
|
|
290
|
-
const trailingIconClass = $derived(
|
|
291
|
-
`${loading && !isLeading ? 'animate-spin' : 'transition-transform'} ${open && !loading ? 'rotate-180' : ''}`
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
// ---- Variant slots ----
|
|
295
|
-
const variantSlots = $derived(
|
|
296
|
-
selectMenuVariants({
|
|
297
|
-
variant,
|
|
298
|
-
color: resolvedColor,
|
|
299
|
-
size: resolvedSize,
|
|
300
|
-
leading: isLeading,
|
|
301
|
-
trailing: true,
|
|
302
|
-
loading,
|
|
303
|
-
highlight: resolvedHighlight,
|
|
304
|
-
side,
|
|
305
|
-
transition
|
|
306
|
-
})
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
// ---- Trigger classes ----
|
|
310
|
-
const rootClass = $derived(
|
|
311
|
-
variantSlots.root({
|
|
312
|
-
class: [config.slots.root, fieldGroupClass?.root, className, ui?.root]
|
|
313
|
-
})
|
|
314
|
-
)
|
|
315
|
-
const baseClass = $derived(
|
|
316
|
-
variantSlots.base({
|
|
317
|
-
class: [config.slots.base, fieldGroupClass?.base, ui?.base]
|
|
318
|
-
})
|
|
319
|
-
)
|
|
320
|
-
const leadingClass = $derived(
|
|
321
|
-
variantSlots.leading({ class: [config.slots.leading, ui?.leading] })
|
|
322
|
-
)
|
|
323
|
-
const leadingIconStyleClass = $derived(
|
|
324
|
-
variantSlots.leadingIcon({ class: [config.slots.leadingIcon, ui?.leadingIcon] })
|
|
325
|
-
)
|
|
326
|
-
const leadingAvatarClass = $derived(
|
|
327
|
-
variantSlots.leadingAvatar({ class: [config.slots.leadingAvatar, ui?.leadingAvatar] })
|
|
328
|
-
)
|
|
329
|
-
const leadingAvatarSizeClass = $derived(variantSlots.leadingAvatarSize() as AvatarSize)
|
|
330
|
-
const trailingStyleClass = $derived(
|
|
331
|
-
variantSlots.trailing({ class: [config.slots.trailing, ui?.trailing] })
|
|
332
|
-
)
|
|
333
|
-
const trailingIconBaseClass = $derived(
|
|
334
|
-
variantSlots.trailingIcon({ class: [config.slots.trailingIcon, ui?.trailingIcon] })
|
|
335
|
-
)
|
|
336
|
-
const valueClass = $derived(variantSlots.value({ class: [config.slots.value, ui?.value] }))
|
|
337
|
-
const placeholderClass = $derived(
|
|
338
|
-
variantSlots.placeholder({ class: [config.slots.placeholder, ui?.placeholder] })
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
// ---- Content classes ----
|
|
342
|
-
const contentClass = $derived(
|
|
343
|
-
variantSlots.content({ class: [config.slots.content, ui?.content] })
|
|
344
|
-
)
|
|
345
|
-
const inputClass = $derived(variantSlots.input({ class: [config.slots.input, ui?.input] }))
|
|
346
|
-
const viewportClass = $derived(
|
|
347
|
-
variantSlots.viewport({ class: [config.slots.viewport, ui?.viewport] })
|
|
348
|
-
)
|
|
349
|
-
const groupLabelClass = $derived(
|
|
350
|
-
variantSlots.groupLabel({ class: [config.slots.groupLabel, ui?.groupLabel] })
|
|
351
|
-
)
|
|
352
|
-
const separatorClass = $derived(
|
|
353
|
-
variantSlots.separator({ class: [config.slots.separator, ui?.separator] })
|
|
354
|
-
)
|
|
355
|
-
const emptyClass = $derived(variantSlots.empty({ class: [config.slots.empty, ui?.empty] }))
|
|
356
|
-
const createItemClass = $derived(
|
|
357
|
-
variantSlots.createItem({ class: [config.slots.createItem, ui?.createItem] })
|
|
358
|
-
)
|
|
359
|
-
const createItemIconClass = $derived(
|
|
360
|
-
variantSlots.createItemIcon({ class: [config.slots.createItemIcon, ui?.createItemIcon] })
|
|
361
|
-
)
|
|
362
|
-
const createItemLabelClass = $derived(
|
|
363
|
-
variantSlots.createItemLabel({
|
|
364
|
-
class: [config.slots.createItemLabel, ui?.createItemLabel]
|
|
365
|
-
})
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
// ---- Item classes ----
|
|
369
|
-
const itemClass = $derived(variantSlots.item({ class: [config.slots.item, ui?.item] }))
|
|
370
|
-
const itemIconClass = $derived(
|
|
371
|
-
variantSlots.itemIcon({ class: [config.slots.itemIcon, ui?.itemIcon] })
|
|
372
|
-
)
|
|
373
|
-
const itemAvatarClass = $derived(
|
|
374
|
-
variantSlots.itemAvatar({ class: [config.slots.itemAvatar, ui?.itemAvatar] })
|
|
375
|
-
)
|
|
376
|
-
const itemAvatarSizeClass = $derived(variantSlots.itemAvatarSize() as AvatarSize)
|
|
377
|
-
const itemLabelClass = $derived(
|
|
378
|
-
variantSlots.itemLabel({ class: [config.slots.itemLabel, ui?.itemLabel] })
|
|
379
|
-
)
|
|
380
|
-
const itemDescriptionClass = $derived(
|
|
381
|
-
variantSlots.itemDescription({
|
|
382
|
-
class: [config.slots.itemDescription, ui?.itemDescription]
|
|
383
|
-
})
|
|
384
|
-
)
|
|
385
|
-
const itemIndicatorClass = $derived(
|
|
386
|
-
variantSlots.itemIndicator({ class: [config.slots.itemIndicator, ui?.itemIndicator] })
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
// ---- Type guards ----
|
|
390
|
-
function isSelectItem(item: SelectMenuItemType): item is SelectMenuItem {
|
|
391
|
-
return !('type' in item)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function isSeparator(item: SelectMenuItemType): item is { type: 'separator' } {
|
|
395
|
-
return 'type' in item && item.type === 'separator'
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function isLabel(item: SelectMenuItemType): item is { type: 'label'; label: string } {
|
|
399
|
-
return 'type' in item && item.type === 'label'
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// ---- Event handlers (Nuxt UI v4 pattern) ----
|
|
403
|
-
function onUpdateOpen(val: boolean) {
|
|
404
|
-
if (!val) {
|
|
405
|
-
resetSearch()
|
|
406
|
-
emit.onBlur()
|
|
407
|
-
} else {
|
|
408
|
-
emit.onFocus()
|
|
409
|
-
}
|
|
410
|
-
onOpenChange?.(val)
|
|
411
|
-
}
|
|
5
|
+
<script lang="ts">import { Combobox } from "bits-ui";
|
|
6
|
+
import { getContext } from "svelte";
|
|
7
|
+
import Avatar from "../Avatar/Avatar.svelte";
|
|
8
|
+
import { getComponentConfig, iconsDefaults } from "../config.js";
|
|
9
|
+
import { fieldGroupVariantWithRoot } from "../FieldGroup/field-group.variants.js";
|
|
10
|
+
import { useDebounce } from "../hooks/useDebounce.svelte.js";
|
|
11
|
+
import { useFormField, useFormFieldEmit } from "../hooks/useFormField.svelte.js";
|
|
12
|
+
import Icon from "../Icon/Icon.svelte";
|
|
13
|
+
import { selectMenuDefaults, selectMenuVariants } from "./select-menu.variants.js";
|
|
14
|
+
const config = getComponentConfig("selectMenu", selectMenuDefaults);
|
|
15
|
+
const icons = getComponentConfig("icons", iconsDefaults);
|
|
16
|
+
let { ref = $bindable(null), value = $bindable(), open = $bindable(false), onOpenChange, items = [], placeholder, searchPlaceholder = "Search...", name, required = false, disabled = false, multiple = false, separator = ", ", ui, id, color = config.defaultVariants.color, variant = config.defaultVariants.variant, size, highlight, loading = false, loadingIcon = icons.loading, icon, leadingIcon, trailingIcon = icons.chevronDown, selectedIcon = icons.check, avatar, filterFields = ["label", "value"], ignoreFilter = false, emptyText = "No results found.", createItem = false, createItemLabel = (value) => `Create "${value}"`, createItemIcon, onCreate, transition = config.defaultVariants.transition ?? true, portal = true, side = config.defaultVariants.side ?? "bottom", sideOffset = 8, align = "start", alignOffset = 0, avoidCollisions = true, collisionBoundary, collisionPadding = 8, onEscapeKeydown, onInteractOutside, forceMount, class: className, leadingSlot, trailingSlot, item: itemSlot, itemLeading, itemLabel: itemLabelSlot, itemTrailing, selected: selectedSlot, empty: emptySlot, content: contentSlot, ...restProps } = $props();
|
|
17
|
+
// ---- Form context ----
|
|
18
|
+
const formFieldContext = useFormField();
|
|
19
|
+
const emit = useFormFieldEmit();
|
|
20
|
+
const fieldGroupContext = getContext("fieldGroup");
|
|
21
|
+
const hasError = $derived(formFieldContext?.error !== undefined && formFieldContext?.error !== false);
|
|
22
|
+
const resolvedSize = $derived(size ?? formFieldContext?.size ?? fieldGroupContext?.size ?? config.defaultVariants.size);
|
|
23
|
+
const resolvedColor = $derived(hasError ? "error" : color);
|
|
24
|
+
const resolvedHighlight = $derived(highlight ?? hasError);
|
|
25
|
+
const fieldGroupClass = $derived(fieldGroupContext ? fieldGroupVariantWithRoot.fieldGroup[fieldGroupContext.orientation ?? "horizontal"] : undefined);
|
|
26
|
+
const resolvedId = $derived(id ?? formFieldContext?.ariaId);
|
|
27
|
+
const resolvedName = $derived(name ?? formFieldContext?.name);
|
|
28
|
+
// ---- ARIA ----
|
|
29
|
+
const ariaDescribedBy = $derived(!formFieldContext ? undefined : hasError ? `${formFieldContext.ariaId}-error` : `${formFieldContext.ariaId}-description ${formFieldContext.ariaId}-help`);
|
|
30
|
+
// ---- Created items (internal state for createItem) ----
|
|
31
|
+
let createdItems = $state([]);
|
|
32
|
+
const combinedItems = $derived.by(() => {
|
|
33
|
+
const propValues = new Set(items.filter((i) => !("type" in i)).map((i) => i.value));
|
|
34
|
+
const extras = createdItems.filter((c) => !propValues.has(c.value));
|
|
35
|
+
return [...items, ...extras];
|
|
36
|
+
});
|
|
37
|
+
// ---- Items lookup (O(1) via Map) ----
|
|
38
|
+
const itemsMap = $derived(new Map(combinedItems.filter((i) => !("type" in i)).map((i) => [i.value, i])));
|
|
39
|
+
// ---- Selection (single + multiple) ----
|
|
40
|
+
const selectedValues = $derived(multiple ? Array.isArray(value) ? value : [] : typeof value === "string" && value !== "" ? [value] : []);
|
|
41
|
+
const selectedItems = $derived(selectedValues.map((v) => itemsMap.get(v)).filter((i) => i !== undefined));
|
|
42
|
+
const hasSelection = $derived(selectedValues.length > 0);
|
|
43
|
+
const singleSelectedItem = $derived(multiple ? undefined : selectedItems[0]);
|
|
44
|
+
const displayLabel = $derived(multiple ? selectedItems.map((i) => i.label ?? i.value).join(separator) : singleSelectedItem?.label ?? singleSelectedItem?.value ?? "");
|
|
45
|
+
function removeValue(val) {
|
|
46
|
+
if (!multiple) return;
|
|
47
|
+
value = selectedValues.filter((v) => v !== val);
|
|
48
|
+
emit.onChange();
|
|
49
|
+
}
|
|
50
|
+
function clearSelection() {
|
|
51
|
+
if (!multiple) return;
|
|
52
|
+
value = [];
|
|
53
|
+
emit.onChange();
|
|
54
|
+
}
|
|
55
|
+
// ---- Search & filtering ----
|
|
56
|
+
let searchTerm = $state("");
|
|
57
|
+
let debouncedSearch = $state("");
|
|
58
|
+
const searchDebounce = useDebounce({ delay: 200 });
|
|
59
|
+
function setSearch(term) {
|
|
60
|
+
searchTerm = term;
|
|
61
|
+
searchDebounce.run(() => {
|
|
62
|
+
debouncedSearch = term;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function resetSearch() {
|
|
66
|
+
searchDebounce.cancel();
|
|
67
|
+
searchTerm = "";
|
|
68
|
+
debouncedSearch = "";
|
|
69
|
+
}
|
|
70
|
+
const filteredItems = $derived(ignoreFilter || !debouncedSearch.trim() ? combinedItems : combinedItems.filter((item) => {
|
|
71
|
+
if ("type" in item) return true;
|
|
72
|
+
const query = debouncedSearch.toLowerCase();
|
|
73
|
+
return filterFields.some((field) => {
|
|
74
|
+
const val = item[field];
|
|
75
|
+
return typeof val === "string" && val.toLowerCase().includes(query);
|
|
76
|
+
});
|
|
77
|
+
}));
|
|
78
|
+
const hasFilteredSelectItems = $derived(filteredItems.some((item) => !("type" in item)));
|
|
79
|
+
// ---- Create item ----
|
|
80
|
+
const trimmedSearch = $derived(searchTerm.trim());
|
|
81
|
+
const exactMatchExists = $derived.by(() => {
|
|
82
|
+
if (!trimmedSearch) return false;
|
|
83
|
+
const query = trimmedSearch.toLowerCase();
|
|
84
|
+
for (const i of combinedItems) {
|
|
85
|
+
if ("type" in i) continue;
|
|
86
|
+
if (i.value.toLowerCase() === query || (i.label ?? i.value).toLowerCase() === query) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
const showCreateItem = $derived.by(() => {
|
|
93
|
+
if (!createItem) return false;
|
|
94
|
+
if (!trimmedSearch) return false;
|
|
95
|
+
const mode = createItem === true ? "lazy" : createItem;
|
|
96
|
+
if (mode === "always") return true;
|
|
97
|
+
return !exactMatchExists;
|
|
98
|
+
});
|
|
99
|
+
const resolvedCreateLabel = $derived(typeof createItemLabel === "function" ? createItemLabel(trimmedSearch) : createItemLabel);
|
|
100
|
+
function findItemByCaseInsensitive(query) {
|
|
101
|
+
const q = query.toLowerCase();
|
|
102
|
+
for (const it of itemsMap.values()) {
|
|
103
|
+
if (it.value.toLowerCase() === q || (it.label ?? it.value).toLowerCase() === q) {
|
|
104
|
+
return it;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
function selectValue(val) {
|
|
110
|
+
if (multiple) {
|
|
111
|
+
if (!selectedValues.includes(val)) {
|
|
112
|
+
value = [...selectedValues, val];
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
value = val;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function handleCreate() {
|
|
119
|
+
if (!showCreateItem) return;
|
|
120
|
+
const newValue = trimmedSearch;
|
|
121
|
+
if (!newValue) return;
|
|
122
|
+
const existing = findItemByCaseInsensitive(newValue);
|
|
123
|
+
if (existing) {
|
|
124
|
+
selectValue(existing.value);
|
|
125
|
+
} else {
|
|
126
|
+
createdItems = [...createdItems, {
|
|
127
|
+
value: newValue,
|
|
128
|
+
label: newValue
|
|
129
|
+
}];
|
|
130
|
+
selectValue(newValue);
|
|
131
|
+
onCreate?.(newValue);
|
|
132
|
+
}
|
|
133
|
+
emit.onChange();
|
|
134
|
+
resetSearch();
|
|
135
|
+
}
|
|
136
|
+
// ---- Leading / trailing ----
|
|
137
|
+
const displayAvatar = $derived(multiple ? avatar : singleSelectedItem?.avatar ?? avatar);
|
|
138
|
+
const displayIcon = $derived(multiple ? leadingIcon ?? icon : singleSelectedItem?.icon ?? leadingIcon ?? icon);
|
|
139
|
+
const isLeading = $derived(!!leadingSlot || !!displayAvatar || !!displayIcon);
|
|
140
|
+
const leadingIconName = $derived(loading && isLeading ? loadingIcon : !displayAvatar ? displayIcon : undefined);
|
|
141
|
+
// ---- Trailing icon ----
|
|
142
|
+
const trailingIconName = $derived(loading && !isLeading ? loadingIcon : trailingIcon);
|
|
143
|
+
const trailingIconClass = $derived(`${loading && !isLeading ? "animate-spin" : "transition-transform"} ${open && !loading ? "rotate-180" : ""}`);
|
|
144
|
+
// ---- Variant slots ----
|
|
145
|
+
const variantSlots = $derived(selectMenuVariants({
|
|
146
|
+
variant,
|
|
147
|
+
color: resolvedColor,
|
|
148
|
+
size: resolvedSize,
|
|
149
|
+
leading: isLeading,
|
|
150
|
+
trailing: true,
|
|
151
|
+
loading,
|
|
152
|
+
highlight: resolvedHighlight,
|
|
153
|
+
side,
|
|
154
|
+
transition
|
|
155
|
+
}));
|
|
156
|
+
// ---- Trigger classes ----
|
|
157
|
+
const rootClass = $derived(variantSlots.root({ class: [
|
|
158
|
+
config.slots.root,
|
|
159
|
+
fieldGroupClass?.root,
|
|
160
|
+
className,
|
|
161
|
+
ui?.root
|
|
162
|
+
] }));
|
|
163
|
+
const baseClass = $derived(variantSlots.base({ class: [
|
|
164
|
+
config.slots.base,
|
|
165
|
+
fieldGroupClass?.base,
|
|
166
|
+
ui?.base
|
|
167
|
+
] }));
|
|
168
|
+
const leadingClass = $derived(variantSlots.leading({ class: [config.slots.leading, ui?.leading] }));
|
|
169
|
+
const leadingIconStyleClass = $derived(variantSlots.leadingIcon({ class: [config.slots.leadingIcon, ui?.leadingIcon] }));
|
|
170
|
+
const leadingAvatarClass = $derived(variantSlots.leadingAvatar({ class: [config.slots.leadingAvatar, ui?.leadingAvatar] }));
|
|
171
|
+
const leadingAvatarSizeClass = $derived(variantSlots.leadingAvatarSize());
|
|
172
|
+
const trailingStyleClass = $derived(variantSlots.trailing({ class: [config.slots.trailing, ui?.trailing] }));
|
|
173
|
+
const trailingIconBaseClass = $derived(variantSlots.trailingIcon({ class: [config.slots.trailingIcon, ui?.trailingIcon] }));
|
|
174
|
+
const valueClass = $derived(variantSlots.value({ class: [config.slots.value, ui?.value] }));
|
|
175
|
+
const placeholderClass = $derived(variantSlots.placeholder({ class: [config.slots.placeholder, ui?.placeholder] }));
|
|
176
|
+
// ---- Content classes ----
|
|
177
|
+
const contentClass = $derived(variantSlots.content({ class: [config.slots.content, ui?.content] }));
|
|
178
|
+
const inputClass = $derived(variantSlots.input({ class: [config.slots.input, ui?.input] }));
|
|
179
|
+
const viewportClass = $derived(variantSlots.viewport({ class: [config.slots.viewport, ui?.viewport] }));
|
|
180
|
+
const groupLabelClass = $derived(variantSlots.groupLabel({ class: [config.slots.groupLabel, ui?.groupLabel] }));
|
|
181
|
+
const separatorClass = $derived(variantSlots.separator({ class: [config.slots.separator, ui?.separator] }));
|
|
182
|
+
const emptyClass = $derived(variantSlots.empty({ class: [config.slots.empty, ui?.empty] }));
|
|
183
|
+
const createItemClass = $derived(variantSlots.createItem({ class: [config.slots.createItem, ui?.createItem] }));
|
|
184
|
+
const createItemIconClass = $derived(variantSlots.createItemIcon({ class: [config.slots.createItemIcon, ui?.createItemIcon] }));
|
|
185
|
+
const createItemLabelClass = $derived(variantSlots.createItemLabel({ class: [config.slots.createItemLabel, ui?.createItemLabel] }));
|
|
186
|
+
// ---- Item classes ----
|
|
187
|
+
const itemClass = $derived(variantSlots.item({ class: [config.slots.item, ui?.item] }));
|
|
188
|
+
const itemIconClass = $derived(variantSlots.itemIcon({ class: [config.slots.itemIcon, ui?.itemIcon] }));
|
|
189
|
+
const itemAvatarClass = $derived(variantSlots.itemAvatar({ class: [config.slots.itemAvatar, ui?.itemAvatar] }));
|
|
190
|
+
const itemAvatarSizeClass = $derived(variantSlots.itemAvatarSize());
|
|
191
|
+
const itemLabelClass = $derived(variantSlots.itemLabel({ class: [config.slots.itemLabel, ui?.itemLabel] }));
|
|
192
|
+
const itemDescriptionClass = $derived(variantSlots.itemDescription({ class: [config.slots.itemDescription, ui?.itemDescription] }));
|
|
193
|
+
const itemIndicatorClass = $derived(variantSlots.itemIndicator({ class: [config.slots.itemIndicator, ui?.itemIndicator] }));
|
|
194
|
+
// ---- Type guards ----
|
|
195
|
+
function isSelectItem(item) {
|
|
196
|
+
return !("type" in item);
|
|
197
|
+
}
|
|
198
|
+
function isSeparator(item) {
|
|
199
|
+
return "type" in item && item.type === "separator";
|
|
200
|
+
}
|
|
201
|
+
function isLabel(item) {
|
|
202
|
+
return "type" in item && item.type === "label";
|
|
203
|
+
}
|
|
204
|
+
// ---- Event handlers (Nuxt UI v4 pattern) ----
|
|
205
|
+
function onUpdateOpen(val) {
|
|
206
|
+
if (!val) {
|
|
207
|
+
resetSearch();
|
|
208
|
+
emit.onBlur();
|
|
209
|
+
} else {
|
|
210
|
+
emit.onFocus();
|
|
211
|
+
}
|
|
212
|
+
onOpenChange?.(val);
|
|
213
|
+
}
|
|
412
214
|
</script>
|
|
413
215
|
|
|
414
216
|
{#snippet renderItem(item: SelectMenuItem, index: number)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SelectMenuProps } from './select-menu.types.js';
|
|
2
2
|
export type Props = SelectMenuProps;
|
|
3
|
-
declare const SelectMenu: import("svelte").Component<SelectMenuProps, {}, "
|
|
3
|
+
declare const SelectMenu: import("svelte").Component<SelectMenuProps, {}, "open" | "ref" | "value">;
|
|
4
4
|
type SelectMenu = ReturnType<typeof SelectMenu>;
|
|
5
5
|
export default SelectMenu;
|