@pzerelles/headlessui-svelte 2.1.2-next.31 → 2.1.2-next.33

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