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

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