@pzerelles/headlessui-svelte 2.1.2-next.3 → 2.1.2-next.4

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.
Files changed (59) hide show
  1. package/dist/button/Button.svelte +84 -54
  2. package/dist/checkbox/Checkbox.svelte +174 -120
  3. package/dist/close-button/CloseButton.svelte +12 -6
  4. package/dist/combobox/Combobox.svelte +50 -3
  5. package/dist/data-interactive/DataInteractive.svelte +57 -29
  6. package/dist/description/Description.svelte +32 -21
  7. package/dist/dialog/Dialog.svelte +69 -34
  8. package/dist/dialog/DialogBackdrop.svelte +29 -12
  9. package/dist/dialog/DialogPanel.svelte +49 -26
  10. package/dist/dialog/DialogTitle.svelte +38 -23
  11. package/dist/dialog/InternalDialog.svelte +263 -202
  12. package/dist/field/Field.svelte +49 -26
  13. package/dist/fieldset/Fieldset.svelte +50 -29
  14. package/dist/focus-trap/FocusTrap.svelte +436 -290
  15. package/dist/input/Input.svelte +85 -53
  16. package/dist/internal/FocusSentinel.svelte +16 -8
  17. package/dist/internal/ForcePortalRoot.svelte +7 -3
  18. package/dist/internal/FormFields.svelte +31 -20
  19. package/dist/internal/FormResolver.svelte +20 -15
  20. package/dist/internal/Hidden.svelte +44 -27
  21. package/dist/internal/Hidden.svelte.d.ts +2 -5
  22. package/dist/internal/HiddenFeatures.d.ts +5 -0
  23. package/dist/internal/HiddenFeatures.js +9 -0
  24. package/dist/internal/HoistFormFields.svelte +7 -4
  25. package/dist/internal/MainTreeProvider.svelte +89 -36
  26. package/dist/internal/Portal.svelte +18 -14
  27. package/dist/label/Label.svelte +91 -57
  28. package/dist/legend/Legend.svelte +18 -3
  29. package/dist/listbox/Listbox.svelte +600 -409
  30. package/dist/listbox/ListboxButton.svelte +176 -127
  31. package/dist/listbox/ListboxOption.svelte +166 -125
  32. package/dist/listbox/ListboxOptions.svelte +340 -244
  33. package/dist/listbox/ListboxSelectedOption.svelte +38 -15
  34. package/dist/menu/Menu.svelte +307 -218
  35. package/dist/menu/MenuButton.svelte +157 -115
  36. package/dist/menu/MenuHeading.svelte +34 -14
  37. package/dist/menu/MenuItem.svelte +145 -107
  38. package/dist/menu/MenuItems.svelte +298 -224
  39. package/dist/menu/MenuSection.svelte +26 -9
  40. package/dist/menu/MenuSeparator.svelte +20 -4
  41. package/dist/portal/InternalPortal.svelte +141 -85
  42. package/dist/portal/Portal.svelte +5 -2
  43. package/dist/portal/PortalGroup.svelte +30 -9
  44. package/dist/switch/Switch.svelte +179 -122
  45. package/dist/switch/Switch.svelte.d.ts +4 -4
  46. package/dist/switch/SwitchGroup.svelte +44 -31
  47. package/dist/tabs/Tab.svelte +195 -143
  48. package/dist/tabs/TabGroup.svelte +292 -205
  49. package/dist/tabs/TabList.svelte +31 -11
  50. package/dist/tabs/TabPanel.svelte +68 -43
  51. package/dist/tabs/TabPanels.svelte +18 -7
  52. package/dist/textarea/Textarea.svelte +83 -53
  53. package/dist/transition/InternalTransitionChild.svelte +259 -170
  54. package/dist/transition/Transition.svelte +96 -66
  55. package/dist/transition/TransitionChild.svelte +31 -11
  56. package/dist/utils/ElementOrComponent.svelte +44 -23
  57. package/dist/utils/Generic.svelte +29 -17
  58. package/dist/utils/StableCollection.svelte +54 -36
  59. package/package.json +10 -10
@@ -1,445 +1,636 @@
1
- <script lang="ts" module>import { useByComparator } from "../hooks/use-by-comparator.js";
2
- import { useControllable } from "../hooks/use-controllable.svelte.js";
3
- import { useDisabled } from "../hooks/use-disabled.js";
4
- import { calculateActiveIndex, Focus } from "../utils/calculate-active-index.js";
5
- import { FocusableMode, isFocusableElement, sortByDomNode } from "../utils/focus-management.js";
6
- import { match } from "../utils/match.js";
7
- import { useRef } from "../utils/ref.svelte.js";
8
- import { getContext, setContext } from "svelte";
9
- let DEFAULT_LISTBOX_TAG = "svelte:fragment";
10
- export var ListboxStates = /* @__PURE__ */ ((ListboxStates2) => {
11
- ListboxStates2[ListboxStates2["Open"] = 0] = "Open";
12
- ListboxStates2[ListboxStates2["Closed"] = 1] = "Closed";
13
- return ListboxStates2;
14
- })(ListboxStates || {});
15
- export var ValueMode = /* @__PURE__ */ ((ValueMode2) => {
16
- ValueMode2[ValueMode2["Single"] = 0] = "Single";
17
- ValueMode2[ValueMode2["Multi"] = 1] = "Multi";
18
- return ValueMode2;
19
- })(ValueMode || {});
20
- export var ActivationTrigger = /* @__PURE__ */ ((ActivationTrigger2) => {
21
- ActivationTrigger2[ActivationTrigger2["Pointer"] = 0] = "Pointer";
22
- ActivationTrigger2[ActivationTrigger2["Other"] = 1] = "Other";
23
- return ActivationTrigger2;
24
- })(ActivationTrigger || {});
25
- export function useActions(component) {
26
- const context = getContext("ListboxActionsContext");
27
- if (!context) {
28
- let err = new Error(`<${component} /> is missing a parent <Listbox /> component.`);
29
- if (Error.captureStackTrace) Error.captureStackTrace(err, useActions);
30
- throw err;
1
+ <script lang="ts" module>
2
+ import { useByComparator, type ByComparator } from "../hooks/use-by-comparator.js"
3
+ import { useControllable } from "../hooks/use-controllable.svelte.js"
4
+ import { useDisabled } from "../hooks/use-disabled.js"
5
+ import { calculateActiveIndex, Focus } from "../utils/calculate-active-index.js"
6
+ import { FocusableMode, isFocusableElement, sortByDomNode } from "../utils/focus-management.js"
7
+ import { match } from "../utils/match.js"
8
+ import { useRef, type MutableRefObject } from "../utils/ref.svelte.js"
9
+ import type { ElementType, EnsureArray, Props } from "../utils/types.js"
10
+ import { getContext, setContext, type Snippet } from "svelte"
11
+
12
+ let DEFAULT_LISTBOX_TAG = "svelte:fragment"
13
+ type ListboxRenderPropArg<T> = {
14
+ open: boolean
15
+ disabled: boolean
16
+ invalid: boolean
17
+ value: T
18
+ }
19
+
20
+ export type ListboxProps<
21
+ TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG,
22
+ TType = string,
23
+ TActualType = TType extends (infer U)[] ? U : TType,
24
+ > = Props<
25
+ TTag,
26
+ ListboxRenderPropArg<TType>,
27
+ "value" | "defaultValue" | "onchange" | "by" | "disabled" | "horizontal" | "name" | "multiple",
28
+ {
29
+ value?: TType
30
+ defaultValue?: TType
31
+ onchange?: (value: TType) => void
32
+ by?: ByComparator<TActualType>
33
+ disabled?: boolean
34
+ invalid?: boolean
35
+ horizontal?: boolean
36
+ form?: string
37
+ name?: string
38
+ multiple?: boolean
39
+
40
+ __demoMode?: boolean
41
+ }
42
+ >
43
+
44
+ export type ListboxChildren<T> = Snippet<[ListboxRenderPropArg<T>]>
45
+
46
+ export enum ListboxStates {
47
+ Open,
48
+ Closed,
49
+ }
50
+
51
+ export enum ValueMode {
52
+ Single,
53
+ Multi,
54
+ }
55
+
56
+ export enum ActivationTrigger {
57
+ Pointer,
58
+ Other,
59
+ }
60
+
61
+ export type ListboxOptionDataRef<T> = MutableRefObject<{
62
+ textValue?: string
63
+ disabled: boolean
64
+ value: T
65
+ domRef: MutableRefObject<HTMLElement | null>
66
+ }>
67
+
68
+ interface StateDefinition<T> {
69
+ listboxState: ListboxStates
70
+
71
+ options: { id: string; dataRef: ListboxOptionDataRef<T> }[]
72
+ searchQuery: string
73
+ activeOptionIndex: number | null
74
+ activationTrigger: ActivationTrigger
75
+
76
+ __demoMode: boolean
77
+ }
78
+
79
+ type ListboxActionsContext = {
80
+ openListbox(): void
81
+ closeListbox(): void
82
+ registerOption(id: string, dataRef: ListboxOptionDataRef<unknown>): () => void
83
+ goToOption(focus: Focus.Specific, id: string, trigger?: ActivationTrigger): void
84
+ goToOption(focus: Focus, id?: string, trigger?: ActivationTrigger): void
85
+ selectOption(id: string): void
86
+ selectActiveOption(): void
87
+ onChange(value: unknown): void
88
+ search(query: string): void
89
+ clearSearch(): void
31
90
  }
32
- return context;
33
- }
34
- export function useData(component) {
35
- const context = getContext("ListboxData");
36
- if (context === null) {
37
- let err = new Error(`<${component} /> is missing a parent <Listbox /> component.`);
38
- if (Error.captureStackTrace) Error.captureStackTrace(err, useData);
39
- throw err;
91
+
92
+ export function useActions(component: string) {
93
+ const context = getContext<ListboxActionsContext>("ListboxActionsContext")
94
+ if (!context) {
95
+ let err = new Error(`<${component} /> is missing a parent <Listbox /> component.`)
96
+ if (Error.captureStackTrace) Error.captureStackTrace(err, useActions)
97
+ throw err
98
+ }
99
+ return context
100
+ }
101
+
102
+ export type ListboxDataContext = {
103
+ value: unknown
104
+ disabled: boolean
105
+ invalid: boolean
106
+ mode: ValueMode
107
+ orientation: "horizontal" | "vertical"
108
+ activeOptionIndex: number | null
109
+ compare(a: unknown, z: unknown): boolean
110
+ isSelected(value: unknown): boolean
111
+
112
+ optionsPropsRef: MutableRefObject<{
113
+ static: boolean
114
+ hold: boolean
115
+ }>
116
+
117
+ listRef: MutableRefObject<Map<string, HTMLElement | null>>
118
+
119
+ buttonRef: MutableRefObject<HTMLElement | null>
120
+ optionsRef: MutableRefObject<HTMLElement | null>
121
+ } & Omit<StateDefinition<unknown>, "dataRef">
122
+
123
+ export function useData(component: string) {
124
+ const context = getContext<ListboxDataContext>("ListboxData")
125
+ if (context === null) {
126
+ let err = new Error(`<${component} /> is missing a parent <Listbox /> component.`)
127
+ if (Error.captureStackTrace) Error.captureStackTrace(err, useData)
128
+ throw err
129
+ }
130
+ return context
40
131
  }
41
- return context;
42
- }
43
132
  </script>
44
133
 
45
- <script lang="ts" generics="TType, TActualType, TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG">import { disposables } from "../utils/disposables.js";
46
- import FormFields from "../internal/FormFields.svelte";
47
- import { createFloatingContext } from "../internal/floating.svelte.js";
48
- import { createOpenClosedContext, State } from "../internal/open-closed.js";
49
- import { useLabels } from "../label/context.svelte.js";
50
- import { useOutsideClick } from "../hooks/use-outside-click.svelte.js";
51
- import ElementOrComponent from "../utils/ElementOrComponent.svelte";
52
- function adjustOrderedState(state, adjustment = (i) => i) {
53
- let currentActiveOption = state.activeOptionIndex !== null ? state.options[state.activeOptionIndex] : null;
54
- let sortedOptions = sortByDomNode(
55
- adjustment(state.options.slice()),
56
- (option) => option.dataRef.current.domRef.current
57
- );
58
- let adjustedActiveOptionIndex = currentActiveOption ? sortedOptions.indexOf(currentActiveOption) : null;
59
- if (adjustedActiveOptionIndex === -1) {
60
- adjustedActiveOptionIndex = null;
134
+ <script lang="ts" generics="TType, TActualType, TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG">
135
+ import { disposables } from "../utils/disposables.js"
136
+ import FormFields from "../internal/FormFields.svelte"
137
+ import { createFloatingContext } from "../internal/floating.svelte.js"
138
+ import { createOpenClosedContext, State } from "../internal/open-closed.js"
139
+ import { useLabels } from "../label/context.svelte.js"
140
+ import { useOutsideClick } from "../hooks/use-outside-click.svelte.js"
141
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte"
142
+
143
+ function adjustOrderedState<T>(
144
+ state: StateDefinition<T>,
145
+ adjustment: (options: StateDefinition<T>["options"]) => StateDefinition<T>["options"] = (i) => i
146
+ ) {
147
+ let currentActiveOption = state.activeOptionIndex !== null ? state.options[state.activeOptionIndex] : null
148
+
149
+ let sortedOptions = sortByDomNode(
150
+ adjustment(state.options.slice()),
151
+ (option) => option.dataRef.current.domRef.current
152
+ )
153
+
154
+ // If we inserted an option before the current active option then the active option index
155
+ // would be wrong. To fix this, we will re-lookup the correct index.
156
+ let adjustedActiveOptionIndex = currentActiveOption ? sortedOptions.indexOf(currentActiveOption) : null
157
+
158
+ // Reset to `null` in case the currentActiveOption was removed.
159
+ if (adjustedActiveOptionIndex === -1) {
160
+ adjustedActiveOptionIndex = null
161
+ }
162
+
163
+ return {
164
+ options: sortedOptions,
165
+ activeOptionIndex: adjustedActiveOptionIndex,
166
+ }
167
+ }
168
+
169
+ const stateReducer = (initialState: StateDefinition<TActualType>) => {
170
+ let _state = $state(initialState)
171
+ return {
172
+ get listboxState() {
173
+ return _state.listboxState
174
+ },
175
+ get options() {
176
+ return _state.options
177
+ },
178
+ get searchQuery() {
179
+ return _state.searchQuery
180
+ },
181
+ get activeOptionIndex() {
182
+ return _state.activeOptionIndex
183
+ },
184
+ get activationTrigger() {
185
+ return _state.activationTrigger
186
+ },
187
+ get __demoMode() {
188
+ return _state.__demoMode
189
+ },
190
+ closeListbox() {
191
+ if (disabled) return _state
192
+ if (_state.listboxState === ListboxStates.Closed) return _state
193
+ _state.activeOptionIndex = null
194
+ _state.listboxState = ListboxStates.Closed
195
+ _state.__demoMode = false
196
+ return _state
197
+ },
198
+ openListbox() {
199
+ if (disabled) return _state
200
+ if (_state.listboxState === ListboxStates.Open) return _state
201
+
202
+ // Check if we have a selected value that we can make active
203
+ let activeOptionIndex = _state.activeOptionIndex
204
+ let optionIdx = _state.options.findIndex((option) => isSelected(option.dataRef.current.value))
205
+
206
+ if (optionIdx !== -1) {
207
+ activeOptionIndex = optionIdx
208
+ }
209
+ _state.listboxState = ListboxStates.Open
210
+ _state.activeOptionIndex = activeOptionIndex
211
+ _state.__demoMode = false
212
+ return _state
213
+ },
214
+ goToOption(
215
+ action:
216
+ | { focus: Focus.Specific; id: string; trigger?: ActivationTrigger }
217
+ | { focus: Exclude<Focus, Focus.Specific>; trigger?: ActivationTrigger }
218
+ ) {
219
+ if (disabled) return _state
220
+ if (_state.listboxState === ListboxStates.Closed) return _state
221
+
222
+ _state.searchQuery = ""
223
+ _state.activationTrigger = action.trigger ?? ActivationTrigger.Other
224
+ _state.__demoMode = false
225
+
226
+ // Optimization:
227
+ //
228
+ // There is no need to sort the DOM nodes if we know that we don't want to focus anything
229
+ if (action.focus === Focus.Nothing) {
230
+ _state.activeOptionIndex = null
231
+ return _state
232
+ }
233
+
234
+ // Optimization:
235
+ //
236
+ // There is no need to sort the DOM nodes if we know exactly where to go
237
+ if (action.focus === Focus.Specific) {
238
+ _state.activeOptionIndex = _state.options.findIndex((o) => o.id === action.id)
239
+ return _state
240
+ }
241
+
242
+ // Optimization:
243
+ //
244
+ // If the current DOM node and the previous DOM node are next to each other,
245
+ // or if the previous DOM node is already the first DOM node, then we don't
246
+ // have to sort all the DOM nodes.
247
+ else if (action.focus === Focus.Previous) {
248
+ let activeOptionIdx = _state.activeOptionIndex
249
+ if (activeOptionIdx !== null) {
250
+ let currentDom = _state.options[activeOptionIdx].dataRef.current.domRef
251
+ let previousOptionIndex = calculateActiveIndex(action, {
252
+ resolveItems: () => _state.options,
253
+ resolveActiveIndex: () => _state.activeOptionIndex,
254
+ resolveId: (option) => option.id,
255
+ resolveDisabled: (option) => option.dataRef.current.disabled ?? false,
256
+ })
257
+ if (previousOptionIndex !== null) {
258
+ let previousDom = _state.options[previousOptionIndex].dataRef.current.domRef
259
+ if (
260
+ // Next to each other
261
+ currentDom.current?.previousElementSibling === previousDom?.current ||
262
+ // Or already the first element
263
+ previousDom.current?.previousElementSibling === null
264
+ ) {
265
+ _state.activeOptionIndex = previousOptionIndex
266
+ return _state
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // Optimization:
273
+ //
274
+ // If the current DOM node and the next DOM node are next to each other, or
275
+ // if the next DOM node is already the last DOM node, then we don't have to
276
+ // sort all the DOM nodes.
277
+ else if (action.focus === Focus.Next) {
278
+ let activeOptionIdx = _state.activeOptionIndex
279
+ if (activeOptionIdx !== null) {
280
+ let currentDom = _state.options[activeOptionIdx].dataRef.current.domRef
281
+ let nextOptionIndex = calculateActiveIndex(action, {
282
+ resolveItems: () => _state.options,
283
+ resolveActiveIndex: () => _state.activeOptionIndex,
284
+ resolveId: (option) => option.id,
285
+ resolveDisabled: (option) => option.dataRef.current.disabled ?? false,
286
+ })
287
+ if (nextOptionIndex !== null) {
288
+ let nextDom = _state.options[nextOptionIndex].dataRef.current.domRef
289
+ if (
290
+ // Next to each other
291
+ currentDom.current?.nextElementSibling === nextDom.current ||
292
+ // Or already the last element
293
+ nextDom.current?.nextElementSibling === null
294
+ ) {
295
+ _state.activeOptionIndex = nextOptionIndex
296
+ return _state
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ // Slow path:
303
+ //
304
+ // Ensure all the options are correctly sorted according to DOM position
305
+ let adjustedState = adjustOrderedState(_state)
306
+ let activeOptionIndex = calculateActiveIndex(action, {
307
+ resolveItems: () => adjustedState.options,
308
+ resolveActiveIndex: () => adjustedState.activeOptionIndex,
309
+ resolveId: (option) => option.id,
310
+ resolveDisabled: (option) => option.dataRef.current.disabled ?? false,
311
+ })
312
+
313
+ _state.options = adjustedState.options
314
+ _state.activeOptionIndex = activeOptionIndex
315
+ return _state
316
+ },
317
+ search(value: string) {
318
+ if (disabled) return _state
319
+ if (_state.listboxState === ListboxStates.Closed) return _state
320
+
321
+ let wasAlreadySearching = _state.searchQuery !== ""
322
+ let offset = wasAlreadySearching ? 0 : 1
323
+
324
+ let searchQuery = _state.searchQuery + value.toLowerCase()
325
+
326
+ let reOrderedOptions =
327
+ _state.activeOptionIndex !== null
328
+ ? _state.options
329
+ .slice(_state.activeOptionIndex + offset)
330
+ .concat(_state.options.slice(0, _state.activeOptionIndex + offset))
331
+ : _state.options
332
+
333
+ let matchingOption = reOrderedOptions.find(
334
+ (option) => !option.dataRef.current.disabled && option.dataRef.current.textValue?.startsWith(searchQuery)
335
+ )
336
+
337
+ let matchIdx = matchingOption ? _state.options.indexOf(matchingOption) : -1
338
+
339
+ if (matchIdx === -1 || matchIdx === _state.activeOptionIndex) {
340
+ _state.searchQuery = searchQuery
341
+ } else {
342
+ _state.searchQuery = searchQuery
343
+ _state.activeOptionIndex = matchIdx
344
+ _state.activationTrigger = ActivationTrigger.Other
345
+ }
346
+ return _state
347
+ },
348
+ clearSearch() {
349
+ if (disabled) return _state
350
+ if (_state.listboxState === ListboxStates.Closed) return _state
351
+ if (_state.searchQuery === "") return _state
352
+ _state.searchQuery = ""
353
+ return _state
354
+ },
355
+ registerOption(action: { id: string; dataRef: ListboxOptionDataRef<TActualType> }) {
356
+ let option = { id: action.id, dataRef: action.dataRef }
357
+ let adjustedState = adjustOrderedState(_state, (options) => [...options, option])
358
+
359
+ // Check if we need to make the newly registered option active.
360
+ if (_state.activeOptionIndex === null) {
361
+ if (isSelected(action.dataRef.current.value as any)) {
362
+ adjustedState.activeOptionIndex = adjustedState.options.indexOf(option)
363
+ }
364
+ }
365
+
366
+ _state.options = adjustedState.options
367
+ _state.activeOptionIndex = adjustedState.activeOptionIndex
368
+ return _state
369
+ },
370
+ unregisterOption(action: { id: string }) {
371
+ let adjustedState = adjustOrderedState(_state, (options) => {
372
+ let idx = options.findIndex((a) => a.id === action.id)
373
+ if (idx !== -1) options.splice(idx, 1)
374
+ return options
375
+ })
376
+
377
+ _state.options = adjustedState.options
378
+ _state.activeOptionIndex = adjustedState.activeOptionIndex
379
+ _state.activationTrigger = ActivationTrigger.Other
380
+ return _state
381
+ },
382
+ }
61
383
  }
62
- return {
63
- options: sortedOptions,
64
- activeOptionIndex: adjustedActiveOptionIndex
65
- };
66
- }
67
- const stateReducer = (initialState) => {
68
- let _state2 = $state(initialState);
69
- return {
384
+
385
+ const listboxActionsContext: ListboxActionsContext | null = null
386
+ setContext("ListboxActionsContext", listboxActionsContext)
387
+
388
+ const listboxDataContext: ListboxDataContext | null = null
389
+ setContext("ListboxDataContext", listboxDataContext)
390
+
391
+ let {
392
+ ref = $bindable(),
393
+ as,
394
+ value: controlledValue,
395
+ defaultValue,
396
+ form,
397
+ name,
398
+ onchange: controlledOnChange,
399
+ by,
400
+ invalid = false,
401
+ disabled: ownDisabled = false,
402
+ horizontal = false,
403
+ multiple = false,
404
+ __demoMode = false,
405
+ ...theirProps
406
+ }: { as?: TTag } & ListboxProps<TTag, TType, TActualType> = $props()
407
+
408
+ const providedDisabled = useDisabled()
409
+ const disabled = $derived(providedDisabled.value || ownDisabled)
410
+
411
+ const orientation = horizontal ? "horizontal" : "vertical"
412
+ const controllable = useControllable<any>(
413
+ {
414
+ get controlledValue() {
415
+ return controlledValue
416
+ },
417
+ },
418
+ controlledOnChange,
419
+ defaultValue
420
+ )
421
+ const { value = multiple ? [] : undefined, onchange: theirOnChange } = $derived(controllable)
422
+
423
+ const _state = stateReducer({
424
+ listboxState: __demoMode ? ListboxStates.Open : ListboxStates.Closed,
425
+ options: [],
426
+ searchQuery: "",
427
+ activeOptionIndex: null,
428
+ activationTrigger: ActivationTrigger.Other,
429
+ optionsVisible: false,
430
+ __demoMode,
431
+ } as StateDefinition<TActualType>)
432
+
433
+ type _Data = ListboxDataContext
434
+
435
+ const optionsPropsRef = useRef<_Data["optionsPropsRef"]["current"]>({ static: false, hold: false })
436
+
437
+ const buttonRef = useRef<_Data["buttonRef"]["current"]>(null)
438
+ const optionsRef = useRef<_Data["optionsRef"]["current"]>(null)
439
+ const listRef = useRef<_Data["listRef"]["current"]>(new Map())
440
+
441
+ const compare = useByComparator(by)
442
+
443
+ const isSelected = (compareValue: TActualType): boolean =>
444
+ match(data.mode, {
445
+ [ValueMode.Multi]: () => {
446
+ return (value as EnsureArray<TType>).some((option) => compare(option, compareValue))
447
+ },
448
+ [ValueMode.Single]: () => {
449
+ return compare(value as TActualType, compareValue)
450
+ },
451
+ })
452
+
453
+ const data = {
70
454
  get listboxState() {
71
- return _state2.listboxState;
455
+ return _state.listboxState
72
456
  },
73
457
  get options() {
74
- return _state2.options;
458
+ return _state.options
75
459
  },
76
460
  get searchQuery() {
77
- return _state2.searchQuery;
461
+ return _state.searchQuery
78
462
  },
79
463
  get activeOptionIndex() {
80
- return _state2.activeOptionIndex;
464
+ return _state.activeOptionIndex
81
465
  },
82
466
  get activationTrigger() {
83
- return _state2.activationTrigger;
467
+ return _state.activationTrigger
84
468
  },
85
469
  get __demoMode() {
86
- return _state2.__demoMode;
470
+ return _state.__demoMode
87
471
  },
88
- closeListbox() {
89
- if (disabled) return _state2;
90
- if (_state2.listboxState === ListboxStates.Closed) return _state2;
91
- _state2.activeOptionIndex = null;
92
- _state2.listboxState = ListboxStates.Closed;
93
- _state2.__demoMode = false;
94
- return _state2;
472
+ get value() {
473
+ return value
95
474
  },
96
- openListbox() {
97
- if (disabled) return _state2;
98
- if (_state2.listboxState === ListboxStates.Open) return _state2;
99
- let activeOptionIndex = _state2.activeOptionIndex;
100
- let optionIdx = _state2.options.findIndex((option) => isSelected(option.dataRef.current.value));
101
- if (optionIdx !== -1) {
102
- activeOptionIndex = optionIdx;
103
- }
104
- _state2.listboxState = ListboxStates.Open;
105
- _state2.activeOptionIndex = activeOptionIndex;
106
- _state2.__demoMode = false;
107
- return _state2;
475
+ get disabled() {
476
+ return disabled
108
477
  },
109
- goToOption(action) {
110
- if (disabled) return _state2;
111
- if (_state2.listboxState === ListboxStates.Closed) return _state2;
112
- _state2.searchQuery = "";
113
- _state2.activationTrigger = action.trigger ?? ActivationTrigger.Other;
114
- _state2.__demoMode = false;
115
- if (action.focus === Focus.Nothing) {
116
- _state2.activeOptionIndex = null;
117
- return _state2;
118
- }
119
- if (action.focus === Focus.Specific) {
120
- _state2.activeOptionIndex = _state2.options.findIndex((o) => o.id === action.id);
121
- return _state2;
122
- } else if (action.focus === Focus.Previous) {
123
- let activeOptionIdx = _state2.activeOptionIndex;
124
- if (activeOptionIdx !== null) {
125
- let currentDom = _state2.options[activeOptionIdx].dataRef.current.domRef;
126
- let previousOptionIndex = calculateActiveIndex(action, {
127
- resolveItems: () => _state2.options,
128
- resolveActiveIndex: () => _state2.activeOptionIndex,
129
- resolveId: (option) => option.id,
130
- resolveDisabled: (option) => option.dataRef.current.disabled ?? false
131
- });
132
- if (previousOptionIndex !== null) {
133
- let previousDom = _state2.options[previousOptionIndex].dataRef.current.domRef;
134
- if (
135
- // Next to each other
136
- currentDom.current?.previousElementSibling === previousDom?.current || // Or already the first element
137
- previousDom.current?.previousElementSibling === null
138
- ) {
139
- _state2.activeOptionIndex = previousOptionIndex;
140
- return _state2;
141
- }
142
- }
143
- }
144
- } else if (action.focus === Focus.Next) {
145
- let activeOptionIdx = _state2.activeOptionIndex;
146
- if (activeOptionIdx !== null) {
147
- let currentDom = _state2.options[activeOptionIdx].dataRef.current.domRef;
148
- let nextOptionIndex = calculateActiveIndex(action, {
149
- resolveItems: () => _state2.options,
150
- resolveActiveIndex: () => _state2.activeOptionIndex,
151
- resolveId: (option) => option.id,
152
- resolveDisabled: (option) => option.dataRef.current.disabled ?? false
153
- });
154
- if (nextOptionIndex !== null) {
155
- let nextDom = _state2.options[nextOptionIndex].dataRef.current.domRef;
156
- if (
157
- // Next to each other
158
- currentDom.current?.nextElementSibling === nextDom.current || // Or already the last element
159
- nextDom.current?.nextElementSibling === null
160
- ) {
161
- _state2.activeOptionIndex = nextOptionIndex;
162
- return _state2;
163
- }
164
- }
165
- }
166
- }
167
- let adjustedState = adjustOrderedState(_state2);
168
- let activeOptionIndex = calculateActiveIndex(action, {
169
- resolveItems: () => adjustedState.options,
170
- resolveActiveIndex: () => adjustedState.activeOptionIndex,
171
- resolveId: (option) => option.id,
172
- resolveDisabled: (option) => option.dataRef.current.disabled ?? false
173
- });
174
- _state2.options = adjustedState.options;
175
- _state2.activeOptionIndex = activeOptionIndex;
176
- return _state2;
478
+ get invalid() {
479
+ return invalid
177
480
  },
178
- search(value2) {
179
- if (disabled) return _state2;
180
- if (_state2.listboxState === ListboxStates.Closed) return _state2;
181
- let wasAlreadySearching = _state2.searchQuery !== "";
182
- let offset = wasAlreadySearching ? 0 : 1;
183
- let searchQuery = _state2.searchQuery + value2.toLowerCase();
184
- let reOrderedOptions = _state2.activeOptionIndex !== null ? _state2.options.slice(_state2.activeOptionIndex + offset).concat(_state2.options.slice(0, _state2.activeOptionIndex + offset)) : _state2.options;
185
- let matchingOption = reOrderedOptions.find(
186
- (option) => !option.dataRef.current.disabled && option.dataRef.current.textValue?.startsWith(searchQuery)
187
- );
188
- let matchIdx = matchingOption ? _state2.options.indexOf(matchingOption) : -1;
189
- if (matchIdx === -1 || matchIdx === _state2.activeOptionIndex) {
190
- _state2.searchQuery = searchQuery;
191
- } else {
192
- _state2.searchQuery = searchQuery;
193
- _state2.activeOptionIndex = matchIdx;
194
- _state2.activationTrigger = ActivationTrigger.Other;
195
- }
196
- return _state2;
481
+ get mode() {
482
+ return multiple ? ValueMode.Multi : ValueMode.Single
197
483
  },
198
- clearSearch() {
199
- if (disabled) return _state2;
200
- if (_state2.listboxState === ListboxStates.Closed) return _state2;
201
- if (_state2.searchQuery === "") return _state2;
202
- _state2.searchQuery = "";
203
- return _state2;
484
+ get orientation() {
485
+ return orientation
204
486
  },
205
- registerOption(action) {
206
- let option = { id: action.id, dataRef: action.dataRef };
207
- let adjustedState = adjustOrderedState(_state2, (options) => [...options, option]);
208
- if (_state2.activeOptionIndex === null) {
209
- if (isSelected(action.dataRef.current.value)) {
210
- adjustedState.activeOptionIndex = adjustedState.options.indexOf(option);
211
- }
487
+ compare,
488
+ isSelected,
489
+ get optionsPropsRef() {
490
+ return optionsPropsRef
491
+ },
492
+ get buttonRef() {
493
+ return buttonRef
494
+ },
495
+ get optionsRef() {
496
+ return optionsRef
497
+ },
498
+ get listRef() {
499
+ return listRef
500
+ },
501
+ }
502
+ setContext<ListboxDataContext>("ListboxDataContext", data)
503
+ setContext("ListboxData", data)
504
+
505
+ // Handle outside click
506
+ const outsideClickEnabled = $derived(data.listboxState === ListboxStates.Open)
507
+ useOutsideClick({
508
+ get enabled() {
509
+ return outsideClickEnabled
510
+ },
511
+ get containers() {
512
+ return [data.buttonRef, data.optionsRef]
513
+ },
514
+ cb: (event, target) => {
515
+ _state.closeListbox()
516
+
517
+ if (!isFocusableElement(target, FocusableMode.Loose)) {
518
+ event.preventDefault()
519
+ data.buttonRef.current?.focus()
212
520
  }
213
- _state2.options = adjustedState.options;
214
- _state2.activeOptionIndex = adjustedState.activeOptionIndex;
215
- return _state2;
216
521
  },
217
- unregisterOption(action) {
218
- let adjustedState = adjustOrderedState(_state2, (options) => {
219
- let idx = options.findIndex((a) => a.id === action.id);
220
- if (idx !== -1) options.splice(idx, 1);
221
- return options;
222
- });
223
- _state2.options = adjustedState.options;
224
- _state2.activeOptionIndex = adjustedState.activeOptionIndex;
225
- _state2.activationTrigger = ActivationTrigger.Other;
226
- return _state2;
227
- }
228
- };
229
- };
230
- const listboxActionsContext = null;
231
- setContext("ListboxActionsContext", listboxActionsContext);
232
- const listboxDataContext = null;
233
- setContext("ListboxDataContext", listboxDataContext);
234
- let {
235
- ref = $bindable(),
236
- as,
237
- value: controlledValue,
238
- defaultValue,
239
- form,
240
- name,
241
- onchange: controlledOnChange,
242
- by,
243
- invalid = false,
244
- disabled: ownDisabled = false,
245
- horizontal = false,
246
- multiple = false,
247
- __demoMode = false,
248
- ...theirProps
249
- } = $props();
250
- const providedDisabled = useDisabled();
251
- const disabled = $derived(providedDisabled.value || ownDisabled);
252
- const orientation = horizontal ? "horizontal" : "vertical";
253
- const controllable = useControllable(
254
- {
255
- get controlledValue() {
256
- return controlledValue;
522
+ })
523
+
524
+ const slot = $derived({
525
+ open: _state.listboxState === ListboxStates.Open,
526
+ disabled,
527
+ invalid,
528
+ value,
529
+ } satisfies ListboxRenderPropArg<TType>)
530
+
531
+ const selectOption = (id: string) => {
532
+ let option = _state.options.find((item) => item.id === id)
533
+ if (!option) return
534
+
535
+ //onchange(option.dataRef.current.value)
536
+ }
537
+
538
+ const selectActiveOption = () => {
539
+ if (_state.activeOptionIndex !== null) {
540
+ let { dataRef, id } = _state.options[_state.activeOptionIndex]
541
+ //onchange(dataRef.current.value)
542
+
543
+ // It could happen that the `activeOptionIndex` stored in state is actually null,
544
+ // but we are getting the fallback ace2437tive option back instead.424323
545
+ _state.goToOption({ focus: Focus.Specific, id })
257
546
  }
258
- },
259
- controlledOnChange,
260
- defaultValue
261
- );
262
- const { value = multiple ? [] : void 0, onchange: theirOnChange } = $derived(controllable);
263
- const _state = stateReducer({
264
- listboxState: __demoMode ? ListboxStates.Open : ListboxStates.Closed,
265
- options: [],
266
- searchQuery: "",
267
- activeOptionIndex: null,
268
- activationTrigger: ActivationTrigger.Other,
269
- optionsVisible: false,
270
- __demoMode
271
- });
272
- const optionsPropsRef = useRef({ static: false, hold: false });
273
- const buttonRef = useRef(null);
274
- const optionsRef = useRef(null);
275
- const listRef = useRef(/* @__PURE__ */ new Map());
276
- const compare = useByComparator(by);
277
- const isSelected = (compareValue) => match(data.mode, {
278
- [ValueMode.Multi]: () => {
279
- return value.some((option) => compare(option, compareValue));
280
- },
281
- [ValueMode.Single]: () => {
282
- return compare(value, compareValue);
547
+ 7
283
548
  }
284
- });
285
- const data = {
286
- get listboxState() {
287
- return _state.listboxState;
288
- },
289
- get options() {
290
- return _state.options;
291
- },
292
- get searchQuery() {
293
- return _state.searchQuery;
294
- },
295
- get activeOptionIndex() {
296
- return _state.activeOptionIndex;
297
- },
298
- get activationTrigger() {
299
- return _state.activationTrigger;
300
- },
301
- get __demoMode() {
302
- return _state.__demoMode;
303
- },
304
- get value() {
305
- return value;
306
- },
307
- get disabled() {
308
- return disabled;
309
- },
310
- get invalid() {
311
- return invalid;
312
- },
313
- get mode() {
314
- return multiple ? ValueMode.Multi : ValueMode.Single;
315
- },
316
- get orientation() {
317
- return orientation;
318
- },
319
- compare,
320
- isSelected,
321
- get optionsPropsRef() {
322
- return optionsPropsRef;
323
- },
324
- get buttonRef() {
325
- return buttonRef;
326
- },
327
- get optionsRef() {
328
- return optionsRef;
329
- },
330
- get listRef() {
331
- return listRef;
549
+
550
+ const d = disposables()
551
+ const goToOption = (focus: Focus, id: string, trigger: ActivationTrigger) => {
552
+ d.dispose()
553
+ d.microTask(() => {
554
+ if (focus === Focus.Specific) {
555
+ return _state.goToOption({ focus: Focus.Specific, id: id!, trigger })
556
+ }
557
+
558
+ return _state.goToOption({ focus, trigger })
559
+ })
332
560
  }
333
- };
334
- setContext("ListboxDataContext", data);
335
- setContext("ListboxData", data);
336
- const outsideClickEnabled = $derived(data.listboxState === ListboxStates.Open);
337
- useOutsideClick({
338
- get enabled() {
339
- return outsideClickEnabled;
340
- },
341
- get containers() {
342
- return [data.buttonRef, data.optionsRef];
343
- },
344
- cb: (event, target) => {
345
- _state.closeListbox();
346
- if (!isFocusableElement(target, FocusableMode.Loose)) {
347
- event.preventDefault();
348
- data.buttonRef.current?.focus();
349
- }
561
+
562
+ const registerOption = (id: string, dataRef: ListboxOptionDataRef<TActualType>) => {
563
+ _state.registerOption({ id, dataRef })
564
+ return () => _state.unregisterOption({ id })
350
565
  }
351
- });
352
- const slot = $derived({
353
- open: _state.listboxState === ListboxStates.Open,
354
- disabled,
355
- invalid,
356
- value
357
- });
358
- const selectOption = (id) => {
359
- let option = _state.options.find((item) => item.id === id);
360
- if (!option) return;
361
- };
362
- const selectActiveOption = () => {
363
- if (_state.activeOptionIndex !== null) {
364
- let { dataRef, id } = _state.options[_state.activeOptionIndex];
365
- _state.goToOption({ focus: Focus.Specific, id });
566
+
567
+ const onChange = (value: unknown) => {
568
+ return match(data.mode, {
569
+ [ValueMode.Single]() {
570
+ return theirOnChange?.(value as TType)
571
+ },
572
+ [ValueMode.Multi]() {
573
+ let copy = (data.value as TActualType[]).slice()
574
+
575
+ let idx = copy.findIndex((item) => compare(item, value as TActualType))
576
+ if (idx === -1) {
577
+ copy.push(value as TActualType)
578
+ } else {
579
+ copy.splice(idx, 1)
580
+ }
581
+
582
+ return theirOnChange?.(copy as unknown as TType[])
583
+ },
584
+ })
366
585
  }
367
- 7;
368
- };
369
- const d = disposables();
370
- const goToOption = (focus, id, trigger) => {
371
- d.dispose();
372
- d.microTask(() => {
373
- if (focus === Focus.Specific) {
374
- return _state.goToOption({ focus: Focus.Specific, id, trigger });
375
- }
376
- return _state.goToOption({ focus, trigger });
377
- });
378
- };
379
- const registerOption = (id, dataRef) => {
380
- _state.registerOption({ id, dataRef });
381
- return () => _state.unregisterOption({ id });
382
- };
383
- const onChange = (value2) => {
384
- return match(data.mode, {
385
- [ValueMode.Single]() {
386
- return theirOnChange?.(value2);
586
+
587
+ setContext<ListboxActionsContext>("ListboxActionsContext", {
588
+ onChange,
589
+ registerOption,
590
+ goToOption,
591
+ closeListbox: _state.closeListbox,
592
+ openListbox: _state.openListbox,
593
+ selectActiveOption,
594
+ selectOption,
595
+ search: _state.search,
596
+ clearSearch: _state.clearSearch,
597
+ })
598
+
599
+ createFloatingContext()
600
+
601
+ const openClosed = $derived(
602
+ match(data.listboxState, {
603
+ [ListboxStates.Open]: State.Open,
604
+ [ListboxStates.Closed]: State.Closed,
605
+ })
606
+ )
607
+ createOpenClosedContext({
608
+ get value() {
609
+ return openClosed
387
610
  },
388
- [ValueMode.Multi]() {
389
- let copy = data.value.slice();
390
- let idx = copy.findIndex((item) => compare(item, value2));
391
- if (idx === -1) {
392
- copy.push(value2);
393
- } else {
394
- copy.splice(idx, 1);
395
- }
396
- return theirOnChange?.(copy);
397
- }
398
- });
399
- };
400
- setContext("ListboxActionsContext", {
401
- onChange,
402
- registerOption,
403
- goToOption,
404
- closeListbox: _state.closeListbox,
405
- openListbox: _state.openListbox,
406
- selectActiveOption,
407
- selectOption,
408
- search: _state.search,
409
- clearSearch: _state.clearSearch
410
- });
411
- createFloatingContext();
412
- const openClosed = $derived(
413
- match(data.listboxState, {
414
- [ListboxStates.Open]: State.Open,
415
- [ListboxStates.Closed]: State.Closed
416
611
  })
417
- );
418
- createOpenClosedContext({
419
- get value() {
420
- return openClosed;
421
- }
422
- });
423
- useLabels({
424
- inherit: true,
425
- props: {
426
- get htmlFor() {
427
- return data.buttonRef.current?.id;
428
- }
429
- },
430
- slot: {
431
- get open() {
432
- return data.listboxState === ListboxStates.Open;
612
+
613
+ useLabels({
614
+ inherit: true,
615
+ props: {
616
+ get htmlFor() {
617
+ return data.buttonRef.current?.id
618
+ },
433
619
  },
434
- get disabled() {
435
- return disabled;
436
- }
620
+ slot: {
621
+ get open() {
622
+ return data.listboxState === ListboxStates.Open
623
+ },
624
+ get disabled() {
625
+ return disabled
626
+ },
627
+ },
628
+ })
629
+
630
+ const reset = () => {
631
+ if (defaultValue === undefined) return
632
+ return theirOnChange?.(defaultValue)
437
633
  }
438
- });
439
- const reset = () => {
440
- if (defaultValue === void 0) return;
441
- return theirOnChange?.(defaultValue);
442
- };
443
634
  </script>
444
635
 
445
636
  {#if name && value}