@pzerelles/headlessui-svelte 2.1.2-next.30 → 2.1.2-next.32

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 (115) hide show
  1. package/dist/button/Button.svelte +84 -54
  2. package/dist/button/Button.svelte.d.ts +2 -3
  3. package/dist/checkbox/Checkbox.svelte +173 -120
  4. package/dist/checkbox/Checkbox.svelte.d.ts +2 -3
  5. package/dist/close-button/CloseButton.svelte +12 -6
  6. package/dist/close-button/CloseButton.svelte.d.ts +7 -8
  7. package/dist/combobox/Combobox.svelte +50 -3
  8. package/dist/data-interactive/DataInteractive.svelte +55 -29
  9. package/dist/data-interactive/DataInteractive.svelte.d.ts +2 -3
  10. package/dist/description/Description.svelte +31 -21
  11. package/dist/description/Description.svelte.d.ts +1 -2
  12. package/dist/dialog/Dialog.svelte +320 -228
  13. package/dist/dialog/Dialog.svelte.d.ts +2 -3
  14. package/dist/dialog/DialogBackdrop.svelte +30 -13
  15. package/dist/dialog/DialogBackdrop.svelte.d.ts +2 -3
  16. package/dist/dialog/DialogPanel.svelte +49 -26
  17. package/dist/dialog/DialogPanel.svelte.d.ts +2 -3
  18. package/dist/dialog/DialogTitle.svelte +38 -23
  19. package/dist/dialog/DialogTitle.svelte.d.ts +2 -3
  20. package/dist/field/Field.svelte +47 -25
  21. package/dist/field/Field.svelte.d.ts +1 -2
  22. package/dist/fieldset/Fieldset.svelte +50 -29
  23. package/dist/fieldset/Fieldset.svelte.d.ts +1 -2
  24. package/dist/focus-trap/FocusTrap.svelte +419 -283
  25. package/dist/focus-trap/FocusTrap.svelte.d.ts +1 -2
  26. package/dist/input/Input.svelte +84 -53
  27. package/dist/input/Input.svelte.d.ts +2 -3
  28. package/dist/internal/FloatingProvider.svelte +14 -9
  29. package/dist/internal/FocusSentinel.svelte +16 -8
  30. package/dist/internal/ForcePortalRoot.svelte +7 -3
  31. package/dist/internal/FormFields.svelte +47 -34
  32. package/dist/internal/FormFieldsProvider.svelte +9 -5
  33. package/dist/internal/FormResolver.svelte +20 -15
  34. package/dist/internal/Hidden.svelte +50 -29
  35. package/dist/internal/Hidden.svelte.d.ts +1 -2
  36. package/dist/internal/MainTreeProvider.svelte +89 -36
  37. package/dist/internal/Portal.svelte +18 -14
  38. package/dist/internal/floating.svelte.d.ts +2 -3
  39. package/dist/internal/floating.svelte.js +0 -1
  40. package/dist/label/Label.svelte +93 -58
  41. package/dist/label/Label.svelte.d.ts +1 -2
  42. package/dist/legend/Legend.svelte +12 -3
  43. package/dist/listbox/Listbox.svelte +525 -387
  44. package/dist/listbox/Listbox.svelte.d.ts +2 -3
  45. package/dist/listbox/ListboxButton.svelte +173 -127
  46. package/dist/listbox/ListboxButton.svelte.d.ts +2 -3
  47. package/dist/listbox/ListboxOption.svelte +170 -129
  48. package/dist/listbox/ListboxOption.svelte.d.ts +2 -3
  49. package/dist/listbox/ListboxOptions.svelte +400 -304
  50. package/dist/listbox/ListboxOptions.svelte.d.ts +2 -3
  51. package/dist/listbox/ListboxSelectedOption.svelte +38 -15
  52. package/dist/listbox/ListboxSelectedOption.svelte.d.ts +1 -2
  53. package/dist/menu/Menu.svelte +77 -51
  54. package/dist/menu/Menu.svelte.d.ts +2 -4
  55. package/dist/menu/MenuButton.svelte +157 -117
  56. package/dist/menu/MenuButton.svelte.d.ts +2 -3
  57. package/dist/menu/MenuHeading.svelte +32 -14
  58. package/dist/menu/MenuHeading.svelte.d.ts +1 -2
  59. package/dist/menu/MenuItem.svelte +142 -107
  60. package/dist/menu/MenuItem.svelte.d.ts +2 -3
  61. package/dist/menu/MenuItems.svelte +301 -229
  62. package/dist/menu/MenuItems.svelte.d.ts +2 -3
  63. package/dist/menu/MenuSection.svelte +24 -9
  64. package/dist/menu/MenuSection.svelte.d.ts +1 -2
  65. package/dist/menu/MenuSeparator.svelte +17 -4
  66. package/dist/menu/MenuSeparator.svelte.d.ts +1 -2
  67. package/dist/popover/Popover.svelte +216 -150
  68. package/dist/popover/Popover.svelte.d.ts +2 -3
  69. package/dist/popover/PopoverBackdrop.svelte +67 -41
  70. package/dist/popover/PopoverBackdrop.svelte.d.ts +2 -3
  71. package/dist/popover/PopoverButton.svelte +292 -212
  72. package/dist/popover/PopoverButton.svelte.d.ts +2 -3
  73. package/dist/popover/PopoverGroup.svelte +62 -35
  74. package/dist/popover/PopoverGroup.svelte.d.ts +1 -2
  75. package/dist/popover/PopoverPanel.svelte +311 -229
  76. package/dist/popover/PopoverPanel.svelte.d.ts +2 -3
  77. package/dist/portal/InternalPortal.svelte +141 -85
  78. package/dist/portal/InternalPortal.svelte.d.ts +1 -2
  79. package/dist/portal/Portal.svelte +5 -2
  80. package/dist/portal/PortalGroup.svelte +30 -9
  81. package/dist/portal/PortalGroup.svelte.d.ts +1 -2
  82. package/dist/select/Select.svelte +98 -68
  83. package/dist/select/Select.svelte.d.ts +2 -3
  84. package/dist/switch/Switch.svelte +179 -132
  85. package/dist/switch/Switch.svelte.d.ts +2 -3
  86. package/dist/switch/SwitchGroup.svelte +44 -31
  87. package/dist/switch/SwitchGroup.svelte.d.ts +1 -2
  88. package/dist/tabs/Tab.svelte +194 -142
  89. package/dist/tabs/Tab.svelte.d.ts +2 -3
  90. package/dist/tabs/TabGroup.svelte +86 -56
  91. package/dist/tabs/TabGroup.svelte.d.ts +2 -3
  92. package/dist/tabs/TabList.svelte +31 -11
  93. package/dist/tabs/TabList.svelte.d.ts +2 -3
  94. package/dist/tabs/TabPanel.svelte +67 -42
  95. package/dist/tabs/TabPanel.svelte.d.ts +2 -3
  96. package/dist/tabs/TabPanels.svelte +18 -7
  97. package/dist/tabs/TabPanels.svelte.d.ts +2 -3
  98. package/dist/textarea/Textarea.svelte +84 -53
  99. package/dist/textarea/Textarea.svelte.d.ts +2 -3
  100. package/dist/transition/InternalTransitionChild.svelte +259 -170
  101. package/dist/transition/InternalTransitionChild.svelte.d.ts +2 -3
  102. package/dist/transition/Transition.svelte +96 -66
  103. package/dist/transition/Transition.svelte.d.ts +2 -3
  104. package/dist/transition/TransitionChild.svelte +31 -11
  105. package/dist/transition/TransitionChild.svelte.d.ts +2 -3
  106. package/dist/utils/DisabledProvider.svelte +7 -3
  107. package/dist/utils/ElementOrComponent.svelte +46 -23
  108. package/dist/utils/ElementOrComponent.svelte.d.ts +8 -10
  109. package/dist/utils/Generic.svelte +30 -19
  110. package/dist/utils/Generic.svelte.d.ts +6 -7
  111. package/dist/utils/StableCollection.svelte +54 -36
  112. package/dist/utils/floating-ui/svelte/components/FloatingNode.svelte +27 -12
  113. package/dist/utils/floating-ui/svelte/components/FloatingTree.svelte +88 -44
  114. package/dist/utils/types.d.ts +4 -5
  115. package/package.json +1 -1
@@ -1,312 +1,408 @@
1
- <script lang="ts" module>import { mergeProps, RenderFeatures } from "../utils/render.js";
2
- import {
3
- useFloatingPanel,
4
- useFloatingPanelProps,
5
- useResolvedAnchor
6
- } from "../internal/floating.svelte.js";
7
- const DEFAULT_OPTIONS_TAG = "div";
8
- let OptionsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static;
9
- </script>
1
+ <script lang="ts" module>
2
+ import type { ElementType, Props } from "../utils/types.js"
3
+ import { mergeProps, RenderFeatures, type PropsForFeatures } from "../utils/render.js"
4
+ import {
5
+ useFloatingPanel,
6
+ useFloatingPanelProps,
7
+ useResolvedAnchor,
8
+ type AnchorPropsWithSelection,
9
+ } from "../internal/floating.svelte.js"
10
10
 
11
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG">import { useId } from "../hooks/use-id.js";
12
- import { ListboxStates, useActions, useData, ValueMode } from "./Listbox.svelte";
13
- import { getOwnerDocument } from "../utils/owner.js";
14
- import { State, useOpenClosed } from "../internal/open-closed.js";
15
- import { transitionDataAttributes, useTransition } from "../hooks/use-transition.svelte.js";
16
- import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js";
17
- import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js";
18
- import { useInertOthers } from "../hooks/use-inert-others.svelte.js";
19
- import { useDidElementMove } from "../hooks/use-did-element-move.svelte.js";
20
- import { useFrozenData } from "../internal/frozen.svelte.js";
21
- import { useDisposables } from "../utils/disposables.js";
22
- import { match } from "../utils/match.js";
23
- import { Focus } from "../utils/calculate-active-index.js";
24
- import { focusFrom, Focus as FocusManagementFocus } from "../utils/focus-management.js";
25
- import { useElementSize } from "../hooks/use-element-size.svelte.js";
26
- import { setContext } from "svelte";
27
- import Portal from "../portal/Portal.svelte";
28
- import ElementOrComponent from "../utils/ElementOrComponent.svelte";
29
- const internalId = useId();
30
- let {
31
- ref = $bindable(),
32
- id = `headlessui-listbox-options-${internalId}`,
33
- anchor: rawAnchor,
34
- portal = false,
35
- modal = true,
36
- transition = false,
37
- ...theirProps
38
- } = $props();
39
- const resolvedAnchor = useResolvedAnchor({
40
- get anchor() {
41
- return rawAnchor;
42
- }
43
- });
44
- const { anchor } = $derived(resolvedAnchor);
45
- let localOptionsElement = $state();
46
- $effect(() => {
47
- if (anchor) {
48
- portal = true;
49
- }
50
- });
51
- const data = useData("ListboxOptions");
52
- const actions = useActions("ListboxOptions");
53
- const ownerDocument = $derived(getOwnerDocument(data.optionsElement));
54
- const usesOpenClosedState = useOpenClosed();
55
- const show = $derived(
56
- usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Open) === State.Open : data.listboxState === ListboxStates.Open
57
- );
58
- const _transition = useTransition({
59
- get enabled() {
60
- return transition;
61
- },
62
- get element() {
63
- return localOptionsElement;
64
- },
65
- get show() {
66
- return show;
67
- }
68
- });
69
- const { visible, data: transitionData } = $derived(_transition);
70
- useOnDisappear({
71
- get enabled() {
72
- return visible;
73
- },
74
- get ref() {
75
- return data.buttonElement;
76
- },
77
- get ondisappear() {
78
- return actions.closeListbox;
11
+ const DEFAULT_OPTIONS_TAG = "div" as const
12
+ type OptionsRenderPropArg = {
13
+ open: boolean
79
14
  }
80
- });
81
- const scrollLockEnabled = $derived(data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open);
82
- useScrollLock({
83
- get enabled() {
84
- return scrollLockEnabled;
85
- },
86
- get ownerDocument() {
87
- return ownerDocument;
88
- }
89
- });
90
- const inertOthersEnabled = $derived(data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open);
91
- useInertOthers({
92
- get enabled() {
93
- return inertOthersEnabled;
94
- },
95
- elements: {
96
- get allowed() {
97
- return [data.buttonElement, data.optionsElement];
15
+ type OptionsPropsWeControl =
16
+ | "aria-activedescendant"
17
+ | "aria-labelledby"
18
+ | "aria-multiselectable"
19
+ | "aria-orientation"
20
+ | "role"
21
+ | "tabIndex"
22
+
23
+ let OptionsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
24
+
25
+ export type ListboxOptionsProps<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG> = Props<
26
+ TTag,
27
+ OptionsRenderPropArg,
28
+ OptionsPropsWeControl,
29
+ {
30
+ id?: string
31
+ anchor?: AnchorPropsWithSelection
32
+ portal?: boolean
33
+ modal?: boolean
34
+ transition?: boolean
35
+ } & PropsForFeatures<typeof OptionsRenderFeatures>
36
+ >
37
+ </script>
38
+
39
+ <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG">
40
+ import { useId } from "../hooks/use-id.js"
41
+ import { ListboxStates, useActions, useData, ValueMode, type ListboxDataContext } from "./Listbox.svelte"
42
+ import { getOwnerDocument } from "../utils/owner.js"
43
+ import { State, useOpenClosed } from "../internal/open-closed.js"
44
+ import { transitionDataAttributes, useTransition } from "../hooks/use-transition.svelte.js"
45
+ import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js"
46
+ import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js"
47
+ import { useInertOthers } from "../hooks/use-inert-others.svelte.js"
48
+ import { useDidElementMove } from "../hooks/use-did-element-move.svelte.js"
49
+ import { useFrozenData } from "../internal/frozen.svelte.js"
50
+ import { useDisposables } from "../utils/disposables.js"
51
+ import { match } from "../utils/match.js"
52
+ import { Focus } from "../utils/calculate-active-index.js"
53
+ import { focusFrom, Focus as FocusManagementFocus } from "../utils/focus-management.js"
54
+ import { useElementSize } from "../hooks/use-element-size.svelte.js"
55
+ import { setContext, type Snippet } from "svelte"
56
+ import Portal from "../portal/Portal.svelte"
57
+ import ElementOrComponent from "../utils/ElementOrComponent.svelte"
58
+
59
+ const internalId = useId()
60
+ let {
61
+ ref = $bindable(),
62
+ id = `headlessui-listbox-options-${internalId}`,
63
+ anchor: rawAnchor,
64
+ portal = false,
65
+ modal = true,
66
+ transition = false,
67
+ ...theirProps
68
+ }: { as?: TTag } & ListboxOptionsProps<TTag> = $props()
69
+ const resolvedAnchor = useResolvedAnchor({
70
+ get anchor() {
71
+ return rawAnchor
72
+ },
73
+ })
74
+ const { anchor } = $derived(resolvedAnchor)
75
+
76
+ let localOptionsElement = $state<HTMLElement | null>()
77
+
78
+ // Always enable `portal` functionality, when `anchor` is enabled
79
+ $effect(() => {
80
+ if (anchor) {
81
+ portal = true
98
82
  }
99
- }
100
- });
101
- const didElementMoveEnabled = $derived(data.listboxState !== ListboxStates.Open);
102
- const didButtonMove = useDidElementMove({
103
- get enabled() {
104
- return didElementMoveEnabled;
105
- },
106
- get element() {
107
- return data.buttonElement;
108
- }
109
- });
110
- const panelEnabled = $derived(didButtonMove.value ? false : visible);
111
- const shouldFreeze = $derived(visible && data.listboxState === ListboxStates.Closed);
112
- const frozenValue = useFrozenData({
113
- get freeze() {
114
- return shouldFreeze;
115
- },
116
- get data() {
117
- return data.value;
118
- }
119
- });
120
- const isSelected = (compareValue) => data.compare(frozenValue.data, compareValue);
121
- const selectedOptionIndex = $derived.by(() => {
122
- if (anchor == null) return null;
123
- if (!anchor?.to?.includes("selection")) return null;
124
- let idx = data.options.findIndex((option) => isSelected(option.dataRef.current.value));
125
- if (idx === -1) idx = 0;
126
- return idx;
127
- });
128
- const anchorOptions = $derived.by(() => {
129
- if (anchor == null) return void 0;
130
- if (selectedOptionIndex === null) return { ...anchor, inner: void 0 };
131
- let elements = Array.from(data.listElements.values());
132
- return {
133
- ...anchor,
134
- inner: {
135
- listRef: { current: elements },
136
- index: selectedOptionIndex
83
+ })
84
+
85
+ const data = useData("ListboxOptions")
86
+ const actions = useActions("ListboxOptions")
87
+
88
+ const ownerDocument = $derived(getOwnerDocument(data.optionsElement))
89
+
90
+ const usesOpenClosedState = useOpenClosed()
91
+ const show = $derived(
92
+ usesOpenClosedState !== null
93
+ ? (usesOpenClosedState.value & State.Open) === State.Open
94
+ : data.listboxState === ListboxStates.Open
95
+ )
96
+ const _transition = useTransition({
97
+ get enabled() {
98
+ return transition
99
+ },
100
+ get element() {
101
+ return localOptionsElement
102
+ },
103
+ get show() {
104
+ return show
105
+ },
106
+ })
107
+ const { visible, data: transitionData } = $derived(_transition)
108
+
109
+ // Ensure we close the listbox as soon as the button becomes hidden
110
+ useOnDisappear({
111
+ get enabled() {
112
+ return visible
113
+ },
114
+ get ref() {
115
+ return data.buttonElement
116
+ },
117
+ get ondisappear() {
118
+ return actions.closeListbox
119
+ },
120
+ })
121
+
122
+ // Enable scroll locking when the listbox is visible, and `modal` is enabled
123
+ const scrollLockEnabled = $derived(data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open)
124
+ useScrollLock({
125
+ get enabled() {
126
+ return scrollLockEnabled
127
+ },
128
+ get ownerDocument() {
129
+ return ownerDocument
130
+ },
131
+ })
132
+
133
+ // Mark other elements as inert when the listbox is visible, and `modal` is enabled
134
+ const inertOthersEnabled = $derived(data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open)
135
+ useInertOthers({
136
+ get enabled() {
137
+ return inertOthersEnabled
138
+ },
139
+ elements: {
140
+ get allowed() {
141
+ return [data.buttonElement, data.optionsElement]
142
+ },
143
+ },
144
+ })
145
+
146
+ // We keep track whether the button moved or not, we only check this when the menu state becomes
147
+ // closed. If the button moved, then we want to cancel pending transitions to prevent that the
148
+ // attached `MenuItems` is still transitioning while the button moved away.
149
+ //
150
+ // If we don't cancel these transitions then there will be a period where the `MenuItems` is
151
+ // visible and moving around because it is trying to re-position itself based on the new position.
152
+ //
153
+ // This can be solved by only transitioning the `opacity` instead of everything, but if you _do_
154
+ // want to transition the y-axis for example you will run into the same issue again.
155
+ const didElementMoveEnabled = $derived(data.listboxState !== ListboxStates.Open)
156
+ const didButtonMove = useDidElementMove({
157
+ get enabled() {
158
+ return didElementMoveEnabled
159
+ },
160
+ get element() {
161
+ return data.buttonElement
162
+ },
163
+ })
164
+
165
+ // Now that we know that the button did move or not, we can either disable the panel and all of
166
+ // its transitions, or rely on the `visible` state to hide the panel whenever necessary.
167
+ const panelEnabled = $derived(didButtonMove.value ? false : visible)
168
+
169
+ // We should freeze when the listbox is visible but "closed". This means that
170
+ // a transition is currently happening and the component is still visible (for
171
+ // the transition) but closed from a functionality perspective.
172
+ const shouldFreeze = $derived(visible && data.listboxState === ListboxStates.Closed)
173
+
174
+ // Frozen state, the selected value will only update visually when the user re-opens the <Listbox />
175
+ const frozenValue = useFrozenData({
176
+ get freeze() {
177
+ return shouldFreeze
178
+ },
179
+ get data() {
180
+ return data.value
181
+ },
182
+ })
183
+
184
+ const isSelected = (compareValue: unknown) => data.compare(frozenValue.data, compareValue)
185
+
186
+ const selectedOptionIndex = $derived.by(() => {
187
+ if (anchor == null) return null
188
+ if (!anchor?.to?.includes("selection")) return null
189
+
190
+ // Only compute the selected option index when using `selection` in the
191
+ // `anchor` prop.
192
+ let idx = data.options.findIndex((option) => isSelected(option.dataRef.current.value))
193
+ // Ensure that if no data is selected, we default to the first item.
194
+ if (idx === -1) idx = 0
195
+ return idx
196
+ })
197
+
198
+ const anchorOptions = $derived.by(() => {
199
+ if (anchor == null) return undefined
200
+ if (selectedOptionIndex === null) return { ...anchor, inner: undefined }
201
+
202
+ let elements = Array.from(data.listElements.values())
203
+
204
+ return {
205
+ ...anchor,
206
+ inner: {
207
+ listRef: { current: elements },
208
+ index: selectedOptionIndex,
209
+ },
210
+ }
211
+ })
212
+
213
+ const floatingPanel = useFloatingPanel({
214
+ get placement() {
215
+ return anchorOptions ?? null
216
+ },
217
+ })
218
+ const { setFloating, styles } = $derived(floatingPanel)
219
+ const getFloatingPanelProps = useFloatingPanelProps()
220
+
221
+ $effect(() => {
222
+ localOptionsElement = ref
223
+ data.optionsElement = ref ?? null
224
+ if (anchor) setFloating(ref ?? null)
225
+ })
226
+
227
+ const searchDisposables = useDisposables()
228
+
229
+ const { listboxState, optionsElement } = $derived(data)
230
+ $effect(() => {
231
+ if (!optionsElement) return
232
+ if (listboxState !== ListboxStates.Open) return
233
+ if (optionsElement === getOwnerDocument(optionsElement)?.activeElement) return
234
+
235
+ optionsElement?.focus({ preventScroll: true })
236
+ })
237
+
238
+ const handleKeyDown = (event: KeyboardEvent) => {
239
+ searchDisposables.dispose()
240
+
241
+ switch (event.key) {
242
+ // Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menu/#keyboard-interaction-12
243
+
244
+ case " ":
245
+ if (data.searchQuery !== "") {
246
+ event.preventDefault()
247
+ event.stopPropagation()
248
+ return actions.search(event.key)
249
+ }
250
+ // When in type ahead mode, fallthrough
251
+ case "Enter":
252
+ event.preventDefault()
253
+ event.stopPropagation()
254
+
255
+ if (data.activeOptionIndex !== null) {
256
+ let { dataRef } = data.options[data.activeOptionIndex]
257
+ actions.onChange(dataRef.current.value)
258
+ }
259
+ if (data.mode === ValueMode.Single) {
260
+ actions.closeListbox()
261
+ data.buttonElement?.focus({ preventScroll: true })
262
+ }
263
+ break
264
+
265
+ case match(data.orientation, { vertical: "ArrowDown", horizontal: "ArrowRight" }):
266
+ event.preventDefault()
267
+ event.stopPropagation()
268
+ return actions.goToOption(Focus.Next)
269
+
270
+ case match(data.orientation, { vertical: "ArrowUp", horizontal: "ArrowLeft" }):
271
+ event.preventDefault()
272
+ event.stopPropagation()
273
+ return actions.goToOption(Focus.Previous)
274
+
275
+ case "Home":
276
+ case "PageUp":
277
+ event.preventDefault()
278
+ event.stopPropagation()
279
+ return actions.goToOption(Focus.First)
280
+
281
+ case "End":
282
+ case "PageDown":
283
+ event.preventDefault()
284
+ event.stopPropagation()
285
+ return actions.goToOption(Focus.Last)
286
+
287
+ case "Escape":
288
+ event.preventDefault()
289
+ event.stopPropagation()
290
+ actions.closeListbox()
291
+ data.buttonElement?.focus({ preventScroll: true })
292
+ return
293
+
294
+ case "Tab":
295
+ event.preventDefault()
296
+ event.stopPropagation()
297
+ actions.closeListbox()
298
+ focusFrom(data.buttonElement, event.shiftKey ? FocusManagementFocus.Previous : FocusManagementFocus.Next)
299
+ break
300
+
301
+ default:
302
+ if (event.key.length === 1) {
303
+ actions.search(event.key)
304
+ searchDisposables.setTimeout(() => actions.clearSearch(), 350)
305
+ }
306
+ break
137
307
  }
138
- };
139
- });
140
- const floatingPanel = useFloatingPanel({
141
- get placement() {
142
- return anchorOptions ?? null;
143
- }
144
- });
145
- const { setFloating, styles } = $derived(floatingPanel);
146
- const getFloatingPanelProps = useFloatingPanelProps();
147
- $effect(() => {
148
- localOptionsElement = ref;
149
- data.optionsElement = ref ?? null;
150
- if (anchor) setFloating(ref ?? null);
151
- });
152
- const searchDisposables = useDisposables();
153
- const { listboxState, optionsElement } = $derived(data);
154
- $effect(() => {
155
- if (!optionsElement) return;
156
- if (listboxState !== ListboxStates.Open) return;
157
- if (optionsElement === getOwnerDocument(optionsElement)?.activeElement) return;
158
- optionsElement?.focus({ preventScroll: true });
159
- });
160
- const handleKeyDown = (event) => {
161
- searchDisposables.dispose();
162
- switch (event.key) {
163
- case " ":
164
- if (data.searchQuery !== "") {
165
- event.preventDefault();
166
- event.stopPropagation();
167
- return actions.search(event.key);
168
- }
169
- case "Enter":
170
- event.preventDefault();
171
- event.stopPropagation();
172
- if (data.activeOptionIndex !== null) {
173
- let { dataRef } = data.options[data.activeOptionIndex];
174
- actions.onChange(dataRef.current.value);
175
- }
176
- if (data.mode === ValueMode.Single) {
177
- actions.closeListbox();
178
- data.buttonElement?.focus({ preventScroll: true });
179
- }
180
- break;
181
- case match(data.orientation, { vertical: "ArrowDown", horizontal: "ArrowRight" }):
182
- event.preventDefault();
183
- event.stopPropagation();
184
- return actions.goToOption(Focus.Next);
185
- case match(data.orientation, { vertical: "ArrowUp", horizontal: "ArrowLeft" }):
186
- event.preventDefault();
187
- event.stopPropagation();
188
- return actions.goToOption(Focus.Previous);
189
- case "Home":
190
- case "PageUp":
191
- event.preventDefault();
192
- event.stopPropagation();
193
- return actions.goToOption(Focus.First);
194
- case "End":
195
- case "PageDown":
196
- event.preventDefault();
197
- event.stopPropagation();
198
- return actions.goToOption(Focus.Last);
199
- case "Escape":
200
- event.preventDefault();
201
- event.stopPropagation();
202
- actions.closeListbox();
203
- data.buttonElement?.focus({ preventScroll: true });
204
- return;
205
- case "Tab":
206
- event.preventDefault();
207
- event.stopPropagation();
208
- actions.closeListbox();
209
- focusFrom(data.buttonElement, event.shiftKey ? FocusManagementFocus.Previous : FocusManagementFocus.Next);
210
- break;
211
- default:
212
- if (event.key.length === 1) {
213
- actions.search(event.key);
214
- searchDisposables.setTimeout(() => actions.clearSearch(), 350);
215
- }
216
- break;
217
- }
218
- };
219
- const labelledby = $derived(data.buttonElement?.id);
220
- const slot = $derived({
221
- open: data.listboxState === ListboxStates.Open
222
- });
223
- const buttonSize = useElementSize({
224
- get element() {
225
- return data.buttonElement;
226
- },
227
- unit: true
228
- });
229
- $effect(() => {
230
- transitionData;
231
- });
232
- const ourProps = $derived({
233
- ...mergeProps(anchor ? getFloatingPanelProps() : {}, {
234
- id,
235
- "aria-activedescendant": data.activeOptionIndex === null ? void 0 : data.options[data.activeOptionIndex]?.id,
236
- "aria-multiselectable": data.mode === ValueMode.Multi ? true : void 0,
237
- "aria-labelledby": labelledby,
238
- "aria-orientation": data.orientation,
239
- role: "listbox",
240
- // When the `Listbox` is closed, it should not be focusable. This allows us
241
- // to skip focusing the `ListboxOptions` when pressing the tab key on an
242
- // open `Listbox`, and go to the next focusable element.
243
- tabindex: data.listboxState === ListboxStates.Open ? 0 : void 0,
244
- style: [theirProps.style, styles, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; ")
245
- }),
246
- ...transitionDataAttributes(transitionData)
247
- });
248
- const derivedData = $derived({
249
- ...data,
250
- get isSelected() {
251
- return data.mode === ValueMode.Multi ? data.isSelected : isSelected;
252
- }
253
- });
254
- setContext("ListboxDataContext", {
255
- get value() {
256
- return data.value;
257
- },
258
- get disabled() {
259
- return data.disabled;
260
- },
261
- get invalid() {
262
- return data.invalid;
263
- },
264
- get mode() {
265
- return data.mode;
266
- },
267
- get orientation() {
268
- return data.orientation;
269
- },
270
- get activeOptionIndex() {
271
- return data.activeOptionIndex;
272
- },
273
- get closeOnSelect() {
274
- return data.closeOnSelect;
275
- },
276
- get compare() {
277
- return data.compare;
278
- },
279
- get isSelected() {
280
- return data.mode === ValueMode.Multi ? data.isSelected : isSelected;
281
- },
282
- get optionsProps() {
283
- return data.optionsProps;
284
- },
285
- get listElements() {
286
- return data.listElements;
287
- },
288
- get buttonElement() {
289
- return data.buttonElement;
290
- },
291
- get optionsElement() {
292
- return data.optionsElement;
293
- },
294
- get listboxState() {
295
- return data.listboxState;
296
- },
297
- get options() {
298
- return data.options;
299
- },
300
- get searchQuery() {
301
- return data.searchQuery;
302
- },
303
- get activationTrigger() {
304
- return data.activationTrigger;
305
- },
306
- get __demoMode() {
307
- return data.__demoMode;
308
308
  }
309
- });
309
+
310
+ const labelledby = $derived(data.buttonElement?.id)
311
+ const slot = $derived({
312
+ open: data.listboxState === ListboxStates.Open,
313
+ } satisfies OptionsRenderPropArg)
314
+
315
+ const buttonSize = useElementSize({
316
+ get element() {
317
+ return data.buttonElement
318
+ },
319
+ unit: true,
320
+ })
321
+
322
+ $effect(() => {
323
+ transitionData
324
+ })
325
+
326
+ const ourProps = $derived({
327
+ ...mergeProps(anchor ? getFloatingPanelProps() : {}, {
328
+ id,
329
+ "aria-activedescendant": data.activeOptionIndex === null ? undefined : data.options[data.activeOptionIndex]?.id,
330
+ "aria-multiselectable": data.mode === ValueMode.Multi ? true : undefined,
331
+ "aria-labelledby": labelledby,
332
+ "aria-orientation": data.orientation,
333
+ role: "listbox",
334
+ // When the `Listbox` is closed, it should not be focusable. This allows us
335
+ // to skip focusing the `ListboxOptions` when pressing the tab key on an
336
+ // open `Listbox`, and go to the next focusable element.
337
+ tabindex: data.listboxState === ListboxStates.Open ? 0 : undefined,
338
+ style: [theirProps.style, styles, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
339
+ }),
340
+ ...transitionDataAttributes(transitionData),
341
+ })
342
+
343
+ const derivedData: ListboxDataContext = $derived({
344
+ ...data,
345
+ get isSelected() {
346
+ return data.mode === ValueMode.Multi ? data.isSelected : isSelected
347
+ },
348
+ })
349
+
350
+ setContext("ListboxDataContext", {
351
+ get value() {
352
+ return data.value
353
+ },
354
+ get disabled() {
355
+ return data.disabled
356
+ },
357
+ get invalid() {
358
+ return data.invalid
359
+ },
360
+ get mode() {
361
+ return data.mode
362
+ },
363
+ get orientation() {
364
+ return data.orientation
365
+ },
366
+ get activeOptionIndex() {
367
+ return data.activeOptionIndex
368
+ },
369
+ get closeOnSelect() {
370
+ return data.closeOnSelect
371
+ },
372
+ get compare() {
373
+ return data.compare
374
+ },
375
+ get isSelected() {
376
+ return data.mode === ValueMode.Multi ? data.isSelected : isSelected
377
+ },
378
+ get optionsProps() {
379
+ return data.optionsProps
380
+ },
381
+ get listElements() {
382
+ return data.listElements
383
+ },
384
+ get buttonElement() {
385
+ return data.buttonElement
386
+ },
387
+ get optionsElement() {
388
+ return data.optionsElement
389
+ },
390
+ get listboxState() {
391
+ return data.listboxState
392
+ },
393
+ get options() {
394
+ return data.options
395
+ },
396
+ get searchQuery() {
397
+ return data.searchQuery
398
+ },
399
+ get activationTrigger() {
400
+ return data.activationTrigger
401
+ },
402
+ get __demoMode() {
403
+ return data.__demoMode
404
+ },
405
+ })
310
406
  </script>
311
407
 
312
408
  <Portal enabled={portal ? theirProps.static || visible : false}>