@pzerelles/headlessui-svelte 2.1.2-next.7 → 2.1.2-next.9
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/checkbox/Checkbox.svelte.d.ts +4 -6
- 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 +25 -49
- package/dist/fieldset/Fieldset.svelte +29 -50
- package/dist/focus-trap/FocusTrap.svelte +283 -419
- package/dist/input/Input.svelte +53 -84
- 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 +58 -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/ListboxButton.svelte.d.ts +4 -6
- 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 -84
- 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/dist/utils/id.d.ts +1 -1
- package/dist/utils/id.js +1 -1
- package/package.json +12 -12
- package/dist/internal/id.d.ts +0 -8
- package/dist/internal/id.js +0 -11
|
@@ -1,307 +1,233 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "../internal/floating.svelte.js"
|
|
10
|
-
|
|
11
|
-
const DEFAULT_ITEMS_TAG = "div" as const
|
|
12
|
-
type ItemsRenderPropArg = {
|
|
13
|
-
open: boolean
|
|
14
|
-
}
|
|
15
|
-
type ItemsPropsWeControl = "aria-activedescendant" | "aria-labelledby" | "role" | "tabIndex"
|
|
16
|
-
|
|
17
|
-
let ItemsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
|
|
18
|
-
|
|
19
|
-
export type MenuItemsProps<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG> = Props<
|
|
20
|
-
TTag,
|
|
21
|
-
ItemsRenderPropArg,
|
|
22
|
-
ItemsPropsWeControl,
|
|
23
|
-
{
|
|
24
|
-
id?: string
|
|
25
|
-
anchor?: AnchorProps
|
|
26
|
-
portal?: boolean
|
|
27
|
-
modal?: boolean
|
|
28
|
-
transition?: boolean
|
|
29
|
-
} & PropsForFeatures<typeof ItemsRenderFeatures>
|
|
30
|
-
>
|
|
31
|
-
|
|
32
|
-
export type MenuItemsChildren = Snippet<[ItemsRenderPropArg]>
|
|
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_ITEMS_TAG = "div";
|
|
8
|
+
let ItemsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static;
|
|
33
9
|
</script>
|
|
34
10
|
|
|
35
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_ITEMS_TAG">
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
get placement() {
|
|
69
|
-
return anchor
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
const { setFloating, style } = $derived(floatingPanel)
|
|
73
|
-
const getFloatingPanelProps = useFloatingPanelProps()
|
|
74
|
-
|
|
75
|
-
$effect(() => {
|
|
76
|
-
untrack(() => _state.setItemsElement(ref || null))
|
|
77
|
-
if (anchor) setFloating(ref)
|
|
78
|
-
})
|
|
79
|
-
const ownerDocument = $derived(getOwnerDocument(_state.itemsElement))
|
|
80
|
-
|
|
81
|
-
// Always enable `portal` functionality, when `anchor` is enabled
|
|
82
|
-
$effect(() => {
|
|
83
|
-
if (anchor) {
|
|
84
|
-
portal = true
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const usesOpenClosedState = useOpenClosed()
|
|
89
|
-
const show = $derived(
|
|
90
|
-
usesOpenClosedState !== null
|
|
91
|
-
? (usesOpenClosedState.value & State.Open) === State.Open
|
|
92
|
-
: _state.menuState === MenuStates.Open
|
|
93
|
-
)
|
|
94
|
-
const _transition = useTransition({
|
|
95
|
-
get enabled() {
|
|
96
|
-
return transition
|
|
97
|
-
},
|
|
98
|
-
get element() {
|
|
99
|
-
return ref
|
|
100
|
-
},
|
|
101
|
-
get show() {
|
|
102
|
-
return show
|
|
103
|
-
},
|
|
104
|
-
})
|
|
105
|
-
const { visible, data: transitionData } = $derived(_transition)
|
|
106
|
-
|
|
107
|
-
// Ensure we close the listbox as soon as the button becomes hidden
|
|
108
|
-
useOnDisappear({
|
|
109
|
-
get enabled() {
|
|
110
|
-
return visible
|
|
111
|
-
},
|
|
112
|
-
get ref() {
|
|
113
|
-
return _state.buttonElement
|
|
114
|
-
},
|
|
115
|
-
get ondisappear() {
|
|
116
|
-
return _state.closeMenu
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
// Enable scroll locking when the listbox is visible, and `modal` is enabled
|
|
121
|
-
const scrollLockEnabled = $derived(_state.__demoMode ? false : modal && _state.menuState === MenuStates.Open)
|
|
122
|
-
useScrollLock({
|
|
123
|
-
get enabled() {
|
|
124
|
-
return scrollLockEnabled
|
|
125
|
-
},
|
|
126
|
-
get ownerDocument() {
|
|
127
|
-
return ownerDocument
|
|
128
|
-
},
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// Mark other elements as inert when the listbox is visible, and `modal` is enabled
|
|
132
|
-
const inertOthersEnabled = $derived(_state.__demoMode ? false : modal && _state.menuState === MenuStates.Open)
|
|
133
|
-
useInertOthers({
|
|
134
|
-
get enabled() {
|
|
135
|
-
return inertOthersEnabled
|
|
136
|
-
},
|
|
137
|
-
elements: {
|
|
138
|
-
get allowed() {
|
|
139
|
-
return [_state.buttonElement, _state.itemsElement].filter(Boolean)
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// We keep track whether the button moved or not, we only check this when the menu state becomes
|
|
145
|
-
// closed. If the button moved, then we want to cancel pending transitions to prevent that the
|
|
146
|
-
// attached `MenuItems` is still transitioning while the button moved away.
|
|
147
|
-
//
|
|
148
|
-
// If we don't cancel these transitions then there will be a period where the `MenuItems` is
|
|
149
|
-
// visible and moving around because it is trying to re-position itself based on the new position.
|
|
150
|
-
//
|
|
151
|
-
// This can be solved by only transitioning the `opacity` instead of everything, but if you _do_
|
|
152
|
-
// want to transition the y-axis for example you will run into the same issue again.
|
|
153
|
-
const didElementMoveEnabled = $derived(_state.menuState !== MenuStates.Open)
|
|
154
|
-
const didButtonMove = useDidElementMove({
|
|
155
|
-
get enabled() {
|
|
156
|
-
return didElementMoveEnabled
|
|
157
|
-
},
|
|
158
|
-
get element() {
|
|
159
|
-
return _state.buttonElement
|
|
160
|
-
},
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
// Now that we know that the button did move or not, we can either disable the panel and all of
|
|
164
|
-
// its transitions, or rely on the `visible` state to hide the panel whenever necessary.
|
|
165
|
-
const panelEnabled = $derived(didButtonMove.value ? false : visible)
|
|
166
|
-
|
|
167
|
-
$effect(() => {
|
|
168
|
-
let container = _state.itemsElement
|
|
169
|
-
if (!container) return
|
|
170
|
-
if (_state.menuState !== MenuStates.Open) return
|
|
171
|
-
if (container === ownerDocument?.activeElement) return
|
|
172
|
-
|
|
173
|
-
container.focus({ preventScroll: true })
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
useTreeWalker({
|
|
177
|
-
get enabled() {
|
|
178
|
-
return _state.menuState === MenuStates.Open
|
|
179
|
-
},
|
|
180
|
-
get container() {
|
|
181
|
-
return _state.itemsElement
|
|
182
|
-
},
|
|
183
|
-
accept: (node) => {
|
|
184
|
-
if (node.getAttribute("role") === "menuitem") return NodeFilter.FILTER_REJECT
|
|
185
|
-
if (node.hasAttribute("role")) return NodeFilter.FILTER_SKIP
|
|
186
|
-
return NodeFilter.FILTER_ACCEPT
|
|
187
|
-
},
|
|
188
|
-
walk: (node) => {
|
|
189
|
-
node.setAttribute("role", "none")
|
|
190
|
-
},
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
const searchDisposables = useDisposables()
|
|
194
|
-
|
|
195
|
-
const handleKeyDown = async (event: KeyboardEvent) => {
|
|
196
|
-
searchDisposables.dispose()
|
|
197
|
-
|
|
198
|
-
switch (event.key) {
|
|
199
|
-
// Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menu/#keyboard-interaction-12
|
|
200
|
-
|
|
201
|
-
case " ":
|
|
202
|
-
if (_state.searchQuery !== "") {
|
|
203
|
-
event.preventDefault()
|
|
204
|
-
event.stopPropagation()
|
|
205
|
-
return _state.search(event.key)
|
|
206
|
-
}
|
|
207
|
-
// When in type ahead mode, fallthrough
|
|
208
|
-
case "Enter":
|
|
209
|
-
event.preventDefault()
|
|
210
|
-
event.stopPropagation()
|
|
211
|
-
_state.closeMenu()
|
|
212
|
-
if (_state.activeItemIndex !== null) {
|
|
213
|
-
let { dataRef } = _state.items[_state.activeItemIndex]
|
|
214
|
-
dataRef.current?.domRef.current?.click()
|
|
215
|
-
}
|
|
216
|
-
restoreFocusIfNecessary(_state.buttonElement)
|
|
217
|
-
break
|
|
218
|
-
|
|
219
|
-
case "ArrowDown":
|
|
220
|
-
event.preventDefault()
|
|
221
|
-
event.stopPropagation()
|
|
222
|
-
return _state.goToItem({ focus: Focus.Next })
|
|
223
|
-
|
|
224
|
-
case "ArrowUp":
|
|
225
|
-
event.preventDefault()
|
|
226
|
-
event.stopPropagation()
|
|
227
|
-
return _state.goToItem({ focus: Focus.Previous })
|
|
228
|
-
|
|
229
|
-
case "Home":
|
|
230
|
-
case "PageUp":
|
|
231
|
-
event.preventDefault()
|
|
232
|
-
event.stopPropagation()
|
|
233
|
-
return _state.goToItem({ focus: Focus.First })
|
|
234
|
-
|
|
235
|
-
case "End":
|
|
236
|
-
case "PageDown":
|
|
237
|
-
event.preventDefault()
|
|
238
|
-
event.stopPropagation()
|
|
239
|
-
return _state.goToItem({ focus: Focus.Last })
|
|
240
|
-
|
|
241
|
-
case "Escape":
|
|
242
|
-
event.preventDefault()
|
|
243
|
-
event.stopPropagation()
|
|
244
|
-
_state.closeMenu()
|
|
245
|
-
await tick()
|
|
246
|
-
_state.buttonElement?.focus({ preventScroll: true })
|
|
247
|
-
break
|
|
248
|
-
|
|
249
|
-
case "Tab":
|
|
250
|
-
event.preventDefault()
|
|
251
|
-
event.stopPropagation()
|
|
252
|
-
_state.closeMenu()
|
|
253
|
-
await tick()
|
|
254
|
-
focusFrom(_state.buttonElement!, event.shiftKey ? FocusManagementFocus.Previous : FocusManagementFocus.Next)
|
|
255
|
-
break
|
|
256
|
-
|
|
257
|
-
default:
|
|
258
|
-
if (event.key.length === 1) {
|
|
259
|
-
_state.search(event.key)
|
|
260
|
-
searchDisposables.setTimeout(() => _state.clearSearch(), 350)
|
|
261
|
-
}
|
|
262
|
-
break
|
|
263
|
-
}
|
|
11
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_ITEMS_TAG">import { useId } from "../hooks/use-id.js";
|
|
12
|
+
import { getOwnerDocument } from "../utils/owner.js";
|
|
13
|
+
import { State, useOpenClosed } from "../internal/open-closed.js";
|
|
14
|
+
import { transitionDataAttributes, useTransition } from "../hooks/use-transition.svelte.js";
|
|
15
|
+
import { useOnDisappear } from "../hooks/use-on-disappear.svelte.js";
|
|
16
|
+
import { useScrollLock } from "../hooks/use-scroll-lock.svelte.js";
|
|
17
|
+
import { useInertOthers } from "../hooks/use-inert-others.svelte.js";
|
|
18
|
+
import { useDidElementMove } from "../hooks/use-did-element-move.svelte.js";
|
|
19
|
+
import { useDisposables } from "../utils/disposables.js";
|
|
20
|
+
import { Focus } from "../utils/calculate-active-index.js";
|
|
21
|
+
import { focusFrom, Focus as FocusManagementFocus, restoreFocusIfNecessary } from "../utils/focus-management.js";
|
|
22
|
+
import { useElementSize } from "../hooks/use-element-size.svelte.js";
|
|
23
|
+
import { tick, untrack } from "svelte";
|
|
24
|
+
import Portal from "../portal/Portal.svelte";
|
|
25
|
+
import { MenuStates, useMenuContext } from "./context.svelte.js";
|
|
26
|
+
import { useTreeWalker } from "../hooks/use-tree-walker.svelte.js";
|
|
27
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
28
|
+
const internalId = useId();
|
|
29
|
+
let {
|
|
30
|
+
as = DEFAULT_ITEMS_TAG,
|
|
31
|
+
ref = $bindable(),
|
|
32
|
+
id = `headlessui-menu-items-${internalId}`,
|
|
33
|
+
anchor: rawAnchor,
|
|
34
|
+
portal = false,
|
|
35
|
+
modal = true,
|
|
36
|
+
transition = false,
|
|
37
|
+
...theirProps
|
|
38
|
+
} = $props();
|
|
39
|
+
const anchor = $derived(useResolvedAnchor(rawAnchor));
|
|
40
|
+
const _state = useMenuContext("MenuOptions");
|
|
41
|
+
const floatingPanel = useFloatingPanel({
|
|
42
|
+
get placement() {
|
|
43
|
+
return anchor;
|
|
264
44
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
45
|
+
});
|
|
46
|
+
const { setFloating, style } = $derived(floatingPanel);
|
|
47
|
+
const getFloatingPanelProps = useFloatingPanelProps();
|
|
48
|
+
$effect(() => {
|
|
49
|
+
untrack(() => _state.setItemsElement(ref || null));
|
|
50
|
+
if (anchor) setFloating(ref);
|
|
51
|
+
});
|
|
52
|
+
const ownerDocument = $derived(getOwnerDocument(_state.itemsElement));
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (anchor) {
|
|
55
|
+
portal = true;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const usesOpenClosedState = useOpenClosed();
|
|
59
|
+
const show = $derived(
|
|
60
|
+
usesOpenClosedState !== null ? (usesOpenClosedState.value & State.Open) === State.Open : _state.menuState === MenuStates.Open
|
|
61
|
+
);
|
|
62
|
+
const _transition = useTransition({
|
|
63
|
+
get enabled() {
|
|
64
|
+
return transition;
|
|
65
|
+
},
|
|
66
|
+
get element() {
|
|
67
|
+
return ref;
|
|
68
|
+
},
|
|
69
|
+
get show() {
|
|
70
|
+
return show;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const { visible, data: transitionData } = $derived(_transition);
|
|
74
|
+
useOnDisappear({
|
|
75
|
+
get enabled() {
|
|
76
|
+
return visible;
|
|
77
|
+
},
|
|
78
|
+
get ref() {
|
|
79
|
+
return _state.buttonElement;
|
|
80
|
+
},
|
|
81
|
+
get ondisappear() {
|
|
82
|
+
return _state.closeMenu;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const scrollLockEnabled = $derived(_state.__demoMode ? false : modal && _state.menuState === MenuStates.Open);
|
|
86
|
+
useScrollLock({
|
|
87
|
+
get enabled() {
|
|
88
|
+
return scrollLockEnabled;
|
|
89
|
+
},
|
|
90
|
+
get ownerDocument() {
|
|
91
|
+
return ownerDocument;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
const inertOthersEnabled = $derived(_state.__demoMode ? false : modal && _state.menuState === MenuStates.Open);
|
|
95
|
+
useInertOthers({
|
|
96
|
+
get enabled() {
|
|
97
|
+
return inertOthersEnabled;
|
|
98
|
+
},
|
|
99
|
+
elements: {
|
|
100
|
+
get allowed() {
|
|
101
|
+
return [_state.buttonElement, _state.itemsElement].filter(Boolean);
|
|
274
102
|
}
|
|
275
103
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
104
|
+
});
|
|
105
|
+
const didElementMoveEnabled = $derived(_state.menuState !== MenuStates.Open);
|
|
106
|
+
const didButtonMove = useDidElementMove({
|
|
107
|
+
get enabled() {
|
|
108
|
+
return didElementMoveEnabled;
|
|
109
|
+
},
|
|
110
|
+
get element() {
|
|
111
|
+
return _state.buttonElement;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const panelEnabled = $derived(didButtonMove.value ? false : visible);
|
|
115
|
+
$effect(() => {
|
|
116
|
+
let container = _state.itemsElement;
|
|
117
|
+
if (!container) return;
|
|
118
|
+
if (_state.menuState !== MenuStates.Open) return;
|
|
119
|
+
if (container === ownerDocument?.activeElement) return;
|
|
120
|
+
container.focus({ preventScroll: true });
|
|
121
|
+
});
|
|
122
|
+
useTreeWalker({
|
|
123
|
+
get enabled() {
|
|
124
|
+
return _state.menuState === MenuStates.Open;
|
|
125
|
+
},
|
|
126
|
+
get container() {
|
|
127
|
+
return _state.itemsElement;
|
|
128
|
+
},
|
|
129
|
+
accept: (node) => {
|
|
130
|
+
if (node.getAttribute("role") === "menuitem") return NodeFilter.FILTER_REJECT;
|
|
131
|
+
if (node.hasAttribute("role")) return NodeFilter.FILTER_SKIP;
|
|
132
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
133
|
+
},
|
|
134
|
+
walk: (node) => {
|
|
135
|
+
node.setAttribute("role", "none");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const searchDisposables = useDisposables();
|
|
139
|
+
const handleKeyDown = async (event) => {
|
|
140
|
+
searchDisposables.dispose();
|
|
141
|
+
switch (event.key) {
|
|
142
|
+
case " ":
|
|
143
|
+
if (_state.searchQuery !== "") {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
return _state.search(event.key);
|
|
147
|
+
}
|
|
148
|
+
case "Enter":
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
event.stopPropagation();
|
|
151
|
+
_state.closeMenu();
|
|
152
|
+
if (_state.activeItemIndex !== null) {
|
|
153
|
+
let { dataRef } = _state.items[_state.activeItemIndex];
|
|
154
|
+
dataRef.current?.domRef.current?.click();
|
|
155
|
+
}
|
|
156
|
+
restoreFocusIfNecessary(_state.buttonElement);
|
|
157
|
+
break;
|
|
158
|
+
case "ArrowDown":
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
return _state.goToItem({ focus: Focus.Next });
|
|
162
|
+
case "ArrowUp":
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
event.stopPropagation();
|
|
165
|
+
return _state.goToItem({ focus: Focus.Previous });
|
|
166
|
+
case "Home":
|
|
167
|
+
case "PageUp":
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
event.stopPropagation();
|
|
170
|
+
return _state.goToItem({ focus: Focus.First });
|
|
171
|
+
case "End":
|
|
172
|
+
case "PageDown":
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
return _state.goToItem({ focus: Focus.Last });
|
|
176
|
+
case "Escape":
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
event.stopPropagation();
|
|
179
|
+
_state.closeMenu();
|
|
180
|
+
await tick();
|
|
181
|
+
_state.buttonElement?.focus({ preventScroll: true });
|
|
182
|
+
break;
|
|
183
|
+
case "Tab":
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
event.stopPropagation();
|
|
186
|
+
_state.closeMenu();
|
|
187
|
+
await tick();
|
|
188
|
+
focusFrom(_state.buttonElement, event.shiftKey ? FocusManagementFocus.Previous : FocusManagementFocus.Next);
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
if (event.key.length === 1) {
|
|
192
|
+
_state.search(event.key);
|
|
193
|
+
searchDisposables.setTimeout(() => _state.clearSearch(), 350);
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const handleKeyUp = (event) => {
|
|
199
|
+
switch (event.key) {
|
|
200
|
+
case " ":
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const slot = $derived({
|
|
206
|
+
open: _state.menuState === MenuStates.Open
|
|
207
|
+
});
|
|
208
|
+
const buttonSize = useElementSize({
|
|
209
|
+
get element() {
|
|
210
|
+
return _state.buttonElement;
|
|
211
|
+
},
|
|
212
|
+
unit: true
|
|
213
|
+
});
|
|
214
|
+
const ourProps = $derived(
|
|
215
|
+
mergeProps(anchor ? getFloatingPanelProps() : {}, {
|
|
216
|
+
"aria-activedescendant": _state.activeItemIndex === null ? void 0 : _state.items[_state.activeItemIndex]?.id,
|
|
217
|
+
"aria-labelledby": _state.buttonElement?.id,
|
|
218
|
+
id,
|
|
219
|
+
onkeydown: handleKeyDown,
|
|
220
|
+
onkeyup: handleKeyUp,
|
|
221
|
+
role: "menu",
|
|
222
|
+
// When the `Menu` is closed, it should not be focusable. This allows us
|
|
223
|
+
// to skip focusing the `MenuItems` when pressing the tab key on an
|
|
224
|
+
// open `Menu`, and go to the next focusable element.
|
|
225
|
+
tabindex: _state.menuState === MenuStates.Open ? 0 : void 0,
|
|
226
|
+
ref,
|
|
227
|
+
style: [theirProps.style, style, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
|
|
228
|
+
...transitionDataAttributes(transitionData)
|
|
286
229
|
})
|
|
287
|
-
|
|
288
|
-
const ourProps = $derived(
|
|
289
|
-
mergeProps(anchor ? getFloatingPanelProps() : {}, {
|
|
290
|
-
"aria-activedescendant": _state.activeItemIndex === null ? undefined : _state.items[_state.activeItemIndex]?.id,
|
|
291
|
-
"aria-labelledby": _state.buttonElement?.id,
|
|
292
|
-
id,
|
|
293
|
-
onkeydown: handleKeyDown,
|
|
294
|
-
onkeyup: handleKeyUp,
|
|
295
|
-
role: "menu",
|
|
296
|
-
// When the `Menu` is closed, it should not be focusable. This allows us
|
|
297
|
-
// to skip focusing the `MenuItems` when pressing the tab key on an
|
|
298
|
-
// open `Menu`, and go to the next focusable element.
|
|
299
|
-
tabindex: _state.menuState === MenuStates.Open ? 0 : undefined,
|
|
300
|
-
ref,
|
|
301
|
-
style: [theirProps.style, style, `--button-width: ${buttonSize.width}`].filter(Boolean).join("; "),
|
|
302
|
-
...transitionDataAttributes(transitionData),
|
|
303
|
-
})
|
|
304
|
-
)
|
|
230
|
+
);
|
|
305
231
|
</script>
|
|
306
232
|
|
|
307
233
|
<Portal enabled={portal ? theirProps.static || visible : false}>
|
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from "svelte"
|
|
3
|
-
import type { ElementType, Props } from "../utils/types.js"
|
|
4
|
-
|
|
5
|
-
const DEFAULT_SECTION_TAG = "div" as const
|
|
6
|
-
type SectionRenderPropArg = {}
|
|
7
|
-
type SectionPropsWeControl = "role" | "aria-labelledby"
|
|
8
|
-
|
|
9
|
-
export type MenuSectionProps<TTag extends ElementType = typeof DEFAULT_SECTION_TAG> = Props<
|
|
10
|
-
TTag,
|
|
11
|
-
SectionRenderPropArg,
|
|
12
|
-
SectionPropsWeControl
|
|
13
|
-
>
|
|
14
|
-
|
|
15
|
-
export type MenuSectionChildren = Snippet<[SectionRenderPropArg]>
|
|
1
|
+
<script lang="ts" module>const DEFAULT_SECTION_TAG = "div";
|
|
16
2
|
</script>
|
|
17
3
|
|
|
18
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_SECTION_TAG">
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"aria-labelledby": labelledby,
|
|
27
|
-
role: "group",
|
|
28
|
-
})
|
|
4
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_SECTION_TAG">import { useLabels } from "../label/context.svelte.js";
|
|
5
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
6
|
+
const labelledby = useLabels();
|
|
7
|
+
let { ref = $bindable(), ...theirProps } = $props();
|
|
8
|
+
const ourProps = $derived({
|
|
9
|
+
"aria-labelledby": labelledby,
|
|
10
|
+
role: "group"
|
|
11
|
+
});
|
|
29
12
|
</script>
|
|
30
13
|
|
|
31
14
|
<ElementOrComponent {ourProps} {theirProps} defaultTag={DEFAULT_SECTION_TAG} name="MenuSection" bind:ref />
|
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from "svelte"
|
|
3
|
-
import type { ElementType, Props } from "../utils/types.js"
|
|
4
|
-
|
|
5
|
-
const DEFAULT_SEPARATOR_TAG = "div" as const
|
|
6
|
-
type SeparatorRenderPropArg = {}
|
|
7
|
-
type SeparatorPropsWeControl = "role"
|
|
8
|
-
|
|
9
|
-
export type MenuSeparatorProps<TTag extends ElementType = typeof DEFAULT_SEPARATOR_TAG> = Props<
|
|
10
|
-
TTag,
|
|
11
|
-
SeparatorRenderPropArg,
|
|
12
|
-
SeparatorPropsWeControl
|
|
13
|
-
>
|
|
14
|
-
|
|
15
|
-
export type MenuSeparatorChildren = Snippet<[SeparatorRenderPropArg]>
|
|
1
|
+
<script lang="ts" module>const DEFAULT_SEPARATOR_TAG = "div";
|
|
16
2
|
</script>
|
|
17
3
|
|
|
18
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_SEPARATOR_TAG">
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let { ref = $bindable(), ...theirProps }: { as?: TTag } & MenuSeparatorProps<TTag> = $props()
|
|
22
|
-
const ourProps = { role: "separator" }
|
|
4
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_SEPARATOR_TAG">import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
5
|
+
let { ref = $bindable(), ...theirProps } = $props();
|
|
6
|
+
const ourProps = { role: "separator" };
|
|
23
7
|
</script>
|
|
24
8
|
|
|
25
9
|
<ElementOrComponent {ourProps} {theirProps} defaultTag={DEFAULT_SEPARATOR_TAG} name="MenuSeparator" bind:ref />
|