@pzerelles/headlessui-svelte 2.1.2-next.6 → 2.1.2-next.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button/Button.svelte +54 -84
- package/dist/checkbox/Checkbox.svelte +120 -174
- package/dist/close-button/CloseButton.svelte +6 -12
- package/dist/combobox/Combobox.svelte +3 -50
- package/dist/data-interactive/DataInteractive.svelte +29 -57
- package/dist/description/Description.svelte +21 -32
- package/dist/dialog/Dialog.svelte +34 -69
- package/dist/dialog/DialogBackdrop.svelte +12 -29
- package/dist/dialog/DialogPanel.svelte +26 -49
- package/dist/dialog/DialogTitle.svelte +23 -38
- package/dist/dialog/InternalDialog.svelte +202 -263
- package/dist/field/Field.svelte +26 -49
- package/dist/fieldset/Fieldset.svelte +29 -50
- package/dist/focus-trap/FocusTrap.svelte +283 -419
- package/dist/input/Input.svelte +53 -85
- package/dist/input/Input.svelte.d.ts +4 -6
- package/dist/internal/FocusSentinel.svelte +8 -16
- package/dist/internal/ForcePortalRoot.svelte +3 -7
- package/dist/internal/FormFields.svelte +20 -31
- package/dist/internal/FormResolver.svelte +15 -20
- package/dist/internal/Hidden.svelte +23 -44
- package/dist/internal/HoistFormFields.svelte +4 -7
- package/dist/internal/MainTreeProvider.svelte +36 -89
- package/dist/internal/Portal.svelte +14 -18
- package/dist/label/Label.svelte +57 -91
- package/dist/legend/Legend.svelte +3 -18
- package/dist/listbox/Listbox.svelte +396 -588
- package/dist/listbox/ListboxButton.svelte +127 -176
- package/dist/listbox/ListboxOption.svelte +125 -166
- package/dist/listbox/ListboxOptions.svelte +244 -340
- package/dist/listbox/ListboxSelectedOption.svelte +15 -38
- package/dist/menu/Menu.svelte +218 -307
- package/dist/menu/MenuButton.svelte +115 -157
- package/dist/menu/MenuHeading.svelte +14 -34
- package/dist/menu/MenuItem.svelte +107 -145
- package/dist/menu/MenuItems.svelte +224 -298
- package/dist/menu/MenuSection.svelte +9 -26
- package/dist/menu/MenuSeparator.svelte +4 -20
- package/dist/portal/InternalPortal.svelte +85 -141
- package/dist/portal/Portal.svelte +2 -5
- package/dist/portal/PortalGroup.svelte +9 -30
- package/dist/switch/Switch.svelte +132 -179
- package/dist/switch/SwitchGroup.svelte +31 -44
- package/dist/tabs/Tab.svelte +143 -195
- package/dist/tabs/TabGroup.svelte +205 -292
- package/dist/tabs/TabList.svelte +11 -31
- package/dist/tabs/TabPanel.svelte +43 -68
- package/dist/tabs/TabPanels.svelte +7 -18
- package/dist/textarea/Textarea.svelte +53 -83
- package/dist/textarea/Textarea.svelte.d.ts +14 -11
- package/dist/transition/InternalTransitionChild.svelte +170 -259
- package/dist/transition/Transition.svelte +66 -96
- package/dist/transition/TransitionChild.svelte +11 -31
- package/dist/utils/ElementOrComponent.svelte +23 -44
- package/dist/utils/Generic.svelte +17 -29
- package/dist/utils/StableCollection.svelte +36 -54
- package/package.json +1 -1
|
@@ -1,351 +1,255 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "../internal/floating.svelte.js"
|
|
10
|
-
|
|
11
|
-
const DEFAULT_OPTIONS_TAG = "div" as const
|
|
12
|
-
type OptionsRenderPropArg = {
|
|
13
|
-
open: boolean
|
|
14
|
-
}
|
|
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
|
-
|
|
38
|
-
export type ListboxOptionsChildren = Snippet<[OptionsRenderPropArg]>
|
|
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;
|
|
39
9
|
</script>
|
|
40
10
|
|
|
41
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG">
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Always enable `portal` functionality, when `anchor` is enabled
|
|
79
|
-
$effect(() => {
|
|
80
|
-
if (anchor) {
|
|
81
|
-
portal = true
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const data = useData("ListboxOptions")
|
|
86
|
-
const actions = useActions("ListboxOptions")
|
|
87
|
-
|
|
88
|
-
const ownerDocument = $derived(getOwnerDocument(data.optionsRef.current))
|
|
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 ref
|
|
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.buttonRef.current
|
|
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.buttonRef.current, data.optionsRef.current]
|
|
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.buttonRef.current
|
|
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, compareValue)
|
|
185
|
-
|
|
186
|
-
const selectedOptionIndex = () => {
|
|
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
|
|
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 Hidden from "../internal/Hidden.svelte";
|
|
28
|
+
import Portal from "../portal/Portal.svelte";
|
|
29
|
+
import { stateFromSlot } from "../utils/state.js";
|
|
30
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
31
|
+
const internalId = useId();
|
|
32
|
+
let {
|
|
33
|
+
as = DEFAULT_OPTIONS_TAG,
|
|
34
|
+
ref = $bindable(),
|
|
35
|
+
id = `headlessui-listbox-options-${internalId}`,
|
|
36
|
+
anchor: rawAnchor,
|
|
37
|
+
portal = false,
|
|
38
|
+
modal = true,
|
|
39
|
+
transition = false,
|
|
40
|
+
static: isStatic = false,
|
|
41
|
+
unmount = true,
|
|
42
|
+
...theirProps
|
|
43
|
+
} = $props();
|
|
44
|
+
const anchor = $derived(useResolvedAnchor(rawAnchor));
|
|
45
|
+
$effect(() => {
|
|
46
|
+
if (anchor) {
|
|
47
|
+
portal = true;
|
|
196
48
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
49
|
+
});
|
|
50
|
+
const data = useData("ListboxOptions");
|
|
51
|
+
const actions = useActions("ListboxOptions");
|
|
52
|
+
const ownerDocument = $derived(getOwnerDocument(data.optionsRef.current));
|
|
53
|
+
const usesOpenClosedState = useOpenClosed();
|
|
54
|
+
const show = $derived(
|
|
55
|
+
usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Open) === State.Open : data.listboxState === ListboxStates.Open
|
|
56
|
+
);
|
|
57
|
+
const _transition = useTransition({
|
|
58
|
+
get enabled() {
|
|
59
|
+
return transition;
|
|
60
|
+
},
|
|
61
|
+
get element() {
|
|
62
|
+
return ref;
|
|
63
|
+
},
|
|
64
|
+
get show() {
|
|
65
|
+
return show;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const { visible, data: transitionData } = $derived(_transition);
|
|
69
|
+
useOnDisappear({
|
|
70
|
+
get enabled() {
|
|
71
|
+
return visible;
|
|
72
|
+
},
|
|
73
|
+
get ref() {
|
|
74
|
+
return data.buttonRef.current;
|
|
75
|
+
},
|
|
76
|
+
get ondisappear() {
|
|
77
|
+
return actions.closeListbox;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const scrollLockEnabled = $derived(data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open);
|
|
81
|
+
useScrollLock({
|
|
82
|
+
get enabled() {
|
|
83
|
+
return scrollLockEnabled;
|
|
84
|
+
},
|
|
85
|
+
get ownerDocument() {
|
|
86
|
+
return ownerDocument;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const inertOthersEnabled = $derived(data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open);
|
|
90
|
+
useInertOthers({
|
|
91
|
+
get enabled() {
|
|
92
|
+
return inertOthersEnabled;
|
|
93
|
+
},
|
|
94
|
+
elements: {
|
|
95
|
+
get allowed() {
|
|
96
|
+
return [data.buttonRef.current, data.optionsRef.current];
|
|
210
97
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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.buttonRef.current?.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.buttonRef.current?.focus({ preventScroll: true })
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
case "Tab":
|
|
295
|
-
event.preventDefault()
|
|
296
|
-
event.stopPropagation()
|
|
297
|
-
actions.closeListbox()
|
|
298
|
-
focusFrom(data.buttonRef.current!, 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
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const didElementMoveEnabled = $derived(data.listboxState !== ListboxStates.Open);
|
|
101
|
+
const didButtonMove = useDidElementMove({
|
|
102
|
+
get enabled() {
|
|
103
|
+
return didElementMoveEnabled;
|
|
104
|
+
},
|
|
105
|
+
get element() {
|
|
106
|
+
return data.buttonRef.current;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const panelEnabled = $derived(didButtonMove.value ? false : visible);
|
|
110
|
+
const shouldFreeze = $derived(visible && data.listboxState === ListboxStates.Closed);
|
|
111
|
+
const frozenValue = useFrozenData({
|
|
112
|
+
get freeze() {
|
|
113
|
+
return shouldFreeze;
|
|
114
|
+
},
|
|
115
|
+
get data() {
|
|
116
|
+
return data.value;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const isSelected = (compareValue) => data.compare(frozenValue, compareValue);
|
|
120
|
+
const selectedOptionIndex = () => {
|
|
121
|
+
if (anchor == null) return null;
|
|
122
|
+
if (!anchor?.to?.includes("selection")) return null;
|
|
123
|
+
let idx = data.options.findIndex((option) => isSelected(option.dataRef.current.value));
|
|
124
|
+
if (idx === -1) idx = 0;
|
|
125
|
+
return idx;
|
|
126
|
+
};
|
|
127
|
+
const anchorOptions = $derived.by(() => {
|
|
128
|
+
if (anchor == null) return void 0;
|
|
129
|
+
if (selectedOptionIndex === null) return { ...anchor, inner: void 0 };
|
|
130
|
+
let elements = Array.from(data.listRef.current.values());
|
|
131
|
+
return {
|
|
132
|
+
...anchor,
|
|
133
|
+
inner: {
|
|
134
|
+
listRef: { current: elements },
|
|
135
|
+
index: selectedOptionIndex
|
|
307
136
|
}
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
const floatingPanel = useFloatingPanel({
|
|
140
|
+
get placement() {
|
|
141
|
+
return anchorOptions ?? null;
|
|
308
142
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
143
|
+
});
|
|
144
|
+
const { setFloating, style } = $derived(floatingPanel);
|
|
145
|
+
const getFloatingPanelProps = useFloatingPanelProps();
|
|
146
|
+
$effect(() => {
|
|
147
|
+
data.optionsRef.current = ref || null;
|
|
148
|
+
if (anchor) setFloating(ref);
|
|
149
|
+
});
|
|
150
|
+
const searchDisposables = useDisposables();
|
|
151
|
+
const { listboxState, optionsRef } = $derived(data);
|
|
152
|
+
$effect(() => {
|
|
153
|
+
let container = optionsRef.current;
|
|
154
|
+
if (!container) return;
|
|
155
|
+
if (listboxState !== ListboxStates.Open) return;
|
|
156
|
+
if (container === getOwnerDocument(container)?.activeElement) return;
|
|
157
|
+
container?.focus({ preventScroll: true });
|
|
158
|
+
});
|
|
159
|
+
const handleKeyDown = (event) => {
|
|
160
|
+
searchDisposables.dispose();
|
|
161
|
+
switch (event.key) {
|
|
162
|
+
case " ":
|
|
163
|
+
if (data.searchQuery !== "") {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
event.stopPropagation();
|
|
166
|
+
return actions.search(event.key);
|
|
167
|
+
}
|
|
168
|
+
case "Enter":
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
event.stopPropagation();
|
|
171
|
+
if (data.activeOptionIndex !== null) {
|
|
172
|
+
let { dataRef } = data.options[data.activeOptionIndex];
|
|
173
|
+
actions.onChange(dataRef.current.value);
|
|
174
|
+
}
|
|
175
|
+
if (data.mode === ValueMode.Single) {
|
|
176
|
+
actions.closeListbox();
|
|
177
|
+
data.buttonRef.current?.focus({ preventScroll: true });
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
case match(data.orientation, { vertical: "ArrowDown", horizontal: "ArrowRight" }):
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
return actions.goToOption(Focus.Next);
|
|
184
|
+
case match(data.orientation, { vertical: "ArrowUp", horizontal: "ArrowLeft" }):
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
event.stopPropagation();
|
|
187
|
+
return actions.goToOption(Focus.Previous);
|
|
188
|
+
case "Home":
|
|
189
|
+
case "PageUp":
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
event.stopPropagation();
|
|
192
|
+
return actions.goToOption(Focus.First);
|
|
193
|
+
case "End":
|
|
194
|
+
case "PageDown":
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
event.stopPropagation();
|
|
197
|
+
return actions.goToOption(Focus.Last);
|
|
198
|
+
case "Escape":
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
event.stopPropagation();
|
|
201
|
+
actions.closeListbox();
|
|
202
|
+
data.buttonRef.current?.focus({ preventScroll: true });
|
|
203
|
+
return;
|
|
204
|
+
case "Tab":
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
event.stopPropagation();
|
|
207
|
+
actions.closeListbox();
|
|
208
|
+
focusFrom(data.buttonRef.current, event.shiftKey ? FocusManagementFocus.Previous : FocusManagementFocus.Next);
|
|
209
|
+
break;
|
|
210
|
+
default:
|
|
211
|
+
if (event.key.length === 1) {
|
|
212
|
+
actions.search(event.key);
|
|
213
|
+
searchDisposables.setTimeout(() => actions.clearSearch(), 350);
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const labelledby = $derived(data.buttonRef.current?.id);
|
|
219
|
+
const slot = $derived({
|
|
220
|
+
open: data.listboxState === ListboxStates.Open
|
|
221
|
+
});
|
|
222
|
+
const buttonSize = useElementSize({
|
|
223
|
+
get element() {
|
|
224
|
+
return data.buttonRef.current;
|
|
225
|
+
},
|
|
226
|
+
unit: true
|
|
227
|
+
});
|
|
228
|
+
const ourProps = $derived(
|
|
229
|
+
mergeProps(anchor ? getFloatingPanelProps() : {}, {
|
|
230
|
+
id,
|
|
231
|
+
"aria-activedescendant": data.activeOptionIndex === null ? void 0 : data.options[data.activeOptionIndex]?.id,
|
|
232
|
+
"aria-multiselectable": data.mode === ValueMode.Multi ? true : void 0,
|
|
233
|
+
"aria-labelledby": labelledby,
|
|
234
|
+
"aria-orientation": data.orientation,
|
|
235
|
+
onkeydown: handleKeyDown,
|
|
236
|
+
role: "listbox",
|
|
237
|
+
// When the `Listbox` is closed, it should not be focusable. This allows us
|
|
238
|
+
// to skip focusing the `ListboxOptions` when pressing the tab key on an
|
|
239
|
+
// open `Listbox`, and go to the next focusable element.
|
|
240
|
+
tabIndex: data.listboxState === ListboxStates.Open ? 0 : void 0,
|
|
241
|
+
style: [theirProps.style, style, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
|
|
242
|
+
...transitionDataAttributes(transitionData),
|
|
243
|
+
...stateFromSlot(slot)
|
|
320
244
|
})
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
"aria-multiselectable": data.mode === ValueMode.Multi ? true : undefined,
|
|
327
|
-
"aria-labelledby": labelledby,
|
|
328
|
-
"aria-orientation": data.orientation,
|
|
329
|
-
onkeydown: handleKeyDown,
|
|
330
|
-
role: "listbox",
|
|
331
|
-
// When the `Listbox` is closed, it should not be focusable. This allows us
|
|
332
|
-
// to skip focusing the `ListboxOptions` when pressing the tab key on an
|
|
333
|
-
// open `Listbox`, and go to the next focusable element.
|
|
334
|
-
tabIndex: data.listboxState === ListboxStates.Open ? 0 : undefined,
|
|
335
|
-
style: [theirProps.style, style, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
|
|
336
|
-
...transitionDataAttributes(transitionData),
|
|
337
|
-
...stateFromSlot(slot),
|
|
338
|
-
})
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
const derivedData: ListboxDataContext = {
|
|
342
|
-
...data,
|
|
343
|
-
get isSelected() {
|
|
344
|
-
return data.mode === ValueMode.Multi ? data.isSelected : isSelected
|
|
345
|
-
},
|
|
245
|
+
);
|
|
246
|
+
const derivedData = {
|
|
247
|
+
...data,
|
|
248
|
+
get isSelected() {
|
|
249
|
+
return data.mode === ValueMode.Multi ? data.isSelected : isSelected;
|
|
346
250
|
}
|
|
347
|
-
|
|
348
|
-
|
|
251
|
+
};
|
|
252
|
+
setContext("ListboxDataContext", derivedData);
|
|
349
253
|
</script>
|
|
350
254
|
|
|
351
255
|
<Portal enabled={portal ? isStatic || visible : false}>
|
|
@@ -1,43 +1,20 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { ElementType, Props } from "../utils/types.js"
|
|
3
|
-
import type { Component } from "svelte"
|
|
4
|
-
|
|
5
|
-
const DEFAULT_SELECTED_OPTION_TAG = "svelte:fragment"
|
|
6
|
-
type SelectedOptionRenderPropArg = {}
|
|
7
|
-
type SelectedOptionPropsWeControl = never
|
|
8
|
-
|
|
9
|
-
export type ListboxSelectedOptionProps<TTag extends ElementType = typeof DEFAULT_SELECTED_OPTION_TAG> = Props<
|
|
10
|
-
TTag,
|
|
11
|
-
SelectedOptionRenderPropArg,
|
|
12
|
-
SelectedOptionPropsWeControl,
|
|
13
|
-
{
|
|
14
|
-
options: Component<any, any>
|
|
15
|
-
placeholder?: Component<any, any>
|
|
16
|
-
}
|
|
17
|
-
>
|
|
1
|
+
<script lang="ts" module>const DEFAULT_SELECTED_OPTION_TAG = "svelte:fragment";
|
|
18
2
|
</script>
|
|
19
3
|
|
|
20
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_SELECTED_OPTION_TAG">
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const shouldShowPlaceholder = $derived(
|
|
35
|
-
data.value === undefined ||
|
|
36
|
-
data.value === null ||
|
|
37
|
-
(data.mode === ValueMode.Multi && Array.isArray(data.value) && data.value.length === 0)
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
setContext("SelectedOptionContext", true)
|
|
4
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_SELECTED_OPTION_TAG">import { useData, ValueMode } from "./Listbox.svelte";
|
|
5
|
+
import { setContext } from "svelte";
|
|
6
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
7
|
+
let {
|
|
8
|
+
ref = $bindable(),
|
|
9
|
+
options,
|
|
10
|
+
placeholder,
|
|
11
|
+
...theirProps
|
|
12
|
+
} = $props();
|
|
13
|
+
const data = useData("ListboxSelectedOption");
|
|
14
|
+
const shouldShowPlaceholder = $derived(
|
|
15
|
+
data.value === void 0 || data.value === null || data.mode === ValueMode.Multi && Array.isArray(data.value) && data.value.length === 0
|
|
16
|
+
);
|
|
17
|
+
setContext("SelectedOptionContext", true);
|
|
41
18
|
</script>
|
|
42
19
|
|
|
43
20
|
{#snippet children()}
|