@pzerelles/headlessui-svelte 2.1.2-next.3 → 2.1.2-next.4
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 +84 -54
- package/dist/checkbox/Checkbox.svelte +174 -120
- package/dist/close-button/CloseButton.svelte +12 -6
- package/dist/combobox/Combobox.svelte +50 -3
- package/dist/data-interactive/DataInteractive.svelte +57 -29
- package/dist/description/Description.svelte +32 -21
- package/dist/dialog/Dialog.svelte +69 -34
- package/dist/dialog/DialogBackdrop.svelte +29 -12
- package/dist/dialog/DialogPanel.svelte +49 -26
- package/dist/dialog/DialogTitle.svelte +38 -23
- package/dist/dialog/InternalDialog.svelte +263 -202
- package/dist/field/Field.svelte +49 -26
- package/dist/fieldset/Fieldset.svelte +50 -29
- package/dist/focus-trap/FocusTrap.svelte +436 -290
- package/dist/input/Input.svelte +85 -53
- package/dist/internal/FocusSentinel.svelte +16 -8
- package/dist/internal/ForcePortalRoot.svelte +7 -3
- package/dist/internal/FormFields.svelte +31 -20
- package/dist/internal/FormResolver.svelte +20 -15
- package/dist/internal/Hidden.svelte +44 -27
- package/dist/internal/Hidden.svelte.d.ts +2 -5
- package/dist/internal/HiddenFeatures.d.ts +5 -0
- package/dist/internal/HiddenFeatures.js +9 -0
- package/dist/internal/HoistFormFields.svelte +7 -4
- package/dist/internal/MainTreeProvider.svelte +89 -36
- package/dist/internal/Portal.svelte +18 -14
- package/dist/label/Label.svelte +91 -57
- package/dist/legend/Legend.svelte +18 -3
- package/dist/listbox/Listbox.svelte +600 -409
- package/dist/listbox/ListboxButton.svelte +176 -127
- package/dist/listbox/ListboxOption.svelte +166 -125
- package/dist/listbox/ListboxOptions.svelte +340 -244
- package/dist/listbox/ListboxSelectedOption.svelte +38 -15
- package/dist/menu/Menu.svelte +307 -218
- package/dist/menu/MenuButton.svelte +157 -115
- package/dist/menu/MenuHeading.svelte +34 -14
- package/dist/menu/MenuItem.svelte +145 -107
- package/dist/menu/MenuItems.svelte +298 -224
- package/dist/menu/MenuSection.svelte +26 -9
- package/dist/menu/MenuSeparator.svelte +20 -4
- package/dist/portal/InternalPortal.svelte +141 -85
- package/dist/portal/Portal.svelte +5 -2
- package/dist/portal/PortalGroup.svelte +30 -9
- package/dist/switch/Switch.svelte +179 -122
- package/dist/switch/Switch.svelte.d.ts +4 -4
- package/dist/switch/SwitchGroup.svelte +44 -31
- package/dist/tabs/Tab.svelte +195 -143
- package/dist/tabs/TabGroup.svelte +292 -205
- package/dist/tabs/TabList.svelte +31 -11
- package/dist/tabs/TabPanel.svelte +68 -43
- package/dist/tabs/TabPanels.svelte +18 -7
- package/dist/textarea/Textarea.svelte +83 -53
- package/dist/transition/InternalTransitionChild.svelte +259 -170
- package/dist/transition/Transition.svelte +96 -66
- package/dist/transition/TransitionChild.svelte +31 -11
- package/dist/utils/ElementOrComponent.svelte +44 -23
- package/dist/utils/Generic.svelte +29 -17
- package/dist/utils/StableCollection.svelte +54 -36
- package/package.json +10 -10
|
@@ -1,127 +1,169 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { tick, type Snippet } from "svelte"
|
|
3
|
+
import type { Props, ElementType } from "../utils/types.js"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { MenuStates, useMenuContext } from "./context.svelte.js";
|
|
14
|
-
import { untrack } from "svelte";
|
|
15
|
-
import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
16
|
-
const internalId = useId();
|
|
17
|
-
let {
|
|
18
|
-
ref = $bindable(),
|
|
19
|
-
id = `headlessui-menu-button-${internalId}`,
|
|
20
|
-
disabled = false,
|
|
21
|
-
autofocus = false,
|
|
22
|
-
...theirProps
|
|
23
|
-
} = $props();
|
|
24
|
-
const _state = useMenuContext("MenuButton");
|
|
25
|
-
const { setReference, getReferenceProps: getFloatingReferenceProps } = useFloating();
|
|
26
|
-
$effect(() => {
|
|
27
|
-
untrack(() => _state.setButtonElement(ref ? ref : null));
|
|
28
|
-
setReference(ref);
|
|
29
|
-
});
|
|
30
|
-
const handleKeyDown = async (event) => {
|
|
31
|
-
switch (event.key) {
|
|
32
|
-
case " ":
|
|
33
|
-
case "Enter":
|
|
34
|
-
case "ArrowDown":
|
|
35
|
-
event.preventDefault();
|
|
36
|
-
event.stopPropagation();
|
|
37
|
-
_state.openMenu();
|
|
38
|
-
await tick();
|
|
39
|
-
_state.goToItem({ focus: Focus.First });
|
|
40
|
-
break;
|
|
41
|
-
case "ArrowUp":
|
|
42
|
-
event.preventDefault();
|
|
43
|
-
event.stopPropagation();
|
|
44
|
-
_state.openMenu();
|
|
45
|
-
await tick();
|
|
46
|
-
_state.goToItem({ focus: Focus.Last });
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const handleKeyUp = (event) => {
|
|
51
|
-
switch (event.key) {
|
|
52
|
-
case " ":
|
|
53
|
-
event.preventDefault();
|
|
54
|
-
break;
|
|
5
|
+
const DEFAULT_BUTTON_TAG = "button" as const
|
|
6
|
+
type ButtonRenderPropArg = {
|
|
7
|
+
open: boolean
|
|
8
|
+
active: boolean
|
|
9
|
+
hover: boolean
|
|
10
|
+
focus: boolean
|
|
11
|
+
disabled: boolean
|
|
12
|
+
autofocus: boolean
|
|
55
13
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
68
|
-
const { isFocusVisible: focus, focusProps } = $derived(
|
|
69
|
-
useFocusRing({
|
|
70
|
-
get autofocus() {
|
|
71
|
-
return autofocus;
|
|
14
|
+
type ButtonPropsWeControl = "aria-controls" | "aria-expanded" | "aria-haspopup"
|
|
15
|
+
|
|
16
|
+
export type MenuButtonProps<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG> = Props<
|
|
17
|
+
TTag,
|
|
18
|
+
ButtonRenderPropArg,
|
|
19
|
+
ButtonPropsWeControl,
|
|
20
|
+
{
|
|
21
|
+
id?: string
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
autofocus?: boolean
|
|
24
|
+
type?: string
|
|
72
25
|
}
|
|
26
|
+
>
|
|
27
|
+
|
|
28
|
+
export type MenuButtonChildren = Snippet<[ButtonRenderPropArg]>
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_BUTTON_TAG">
|
|
32
|
+
import { useId } from "../hooks/use-id.js"
|
|
33
|
+
import { Focus } from "../utils/calculate-active-index.js"
|
|
34
|
+
import { useFocusRing } from "../hooks/use-focus-ring.svelte.js"
|
|
35
|
+
import { useActivePress } from "../hooks/use-active-press.svelte.js"
|
|
36
|
+
import { useResolveButtonType } from "../hooks/use-resolve-button-type.svelte.js"
|
|
37
|
+
import { useFloating } from "../internal/floating.svelte.js"
|
|
38
|
+
import { useHover } from "../hooks/use-hover.svelte.js"
|
|
39
|
+
import { mergeProps } from "../utils/render.js"
|
|
40
|
+
import { MenuStates, useMenuContext } from "./context.svelte.js"
|
|
41
|
+
import { untrack } from "svelte"
|
|
42
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte"
|
|
43
|
+
|
|
44
|
+
const internalId = useId()
|
|
45
|
+
let {
|
|
46
|
+
ref = $bindable(),
|
|
47
|
+
id = `headlessui-menu-button-${internalId}`,
|
|
48
|
+
disabled = false,
|
|
49
|
+
autofocus = false,
|
|
50
|
+
...theirProps
|
|
51
|
+
}: { as?: TTag } & MenuButtonProps<TTag> = $props()
|
|
52
|
+
const _state = useMenuContext("MenuButton")
|
|
53
|
+
const { setReference, getReferenceProps: getFloatingReferenceProps } = useFloating()
|
|
54
|
+
$effect(() => {
|
|
55
|
+
untrack(() => _state.setButtonElement(ref ? (ref as HTMLButtonElement) : null))
|
|
56
|
+
setReference(ref)
|
|
73
57
|
})
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
|
|
59
|
+
const handleKeyDown = async (event: KeyboardEvent) => {
|
|
60
|
+
switch (event.key) {
|
|
61
|
+
// Ref: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/#keyboard-interaction-13
|
|
62
|
+
|
|
63
|
+
case " ":
|
|
64
|
+
case "Enter":
|
|
65
|
+
case "ArrowDown":
|
|
66
|
+
event.preventDefault()
|
|
67
|
+
event.stopPropagation()
|
|
68
|
+
_state.openMenu()
|
|
69
|
+
await tick()
|
|
70
|
+
_state.goToItem({ focus: Focus.First })
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
case "ArrowUp":
|
|
74
|
+
event.preventDefault()
|
|
75
|
+
event.stopPropagation()
|
|
76
|
+
_state.openMenu()
|
|
77
|
+
await tick()
|
|
78
|
+
_state.goToItem({ focus: Focus.Last })
|
|
79
|
+
break
|
|
79
80
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const handleKeyUp = (event: KeyboardEvent) => {
|
|
84
|
+
switch (event.key) {
|
|
85
|
+
case " ":
|
|
86
|
+
// Required for firefox, event.preventDefault() in handleKeyDown for
|
|
87
|
+
// the Space key doesn't cancel the handleKeyUp, which in turn
|
|
88
|
+
// triggers a *click*.
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
break
|
|
86
91
|
}
|
|
87
|
-
})
|
|
88
|
-
);
|
|
89
|
-
const slot = $derived({
|
|
90
|
-
open: _state.menuState === MenuStates.Open,
|
|
91
|
-
active: active || _state.menuState === MenuStates.Open,
|
|
92
|
-
disabled,
|
|
93
|
-
hover,
|
|
94
|
-
focus,
|
|
95
|
-
autofocus: autofocus ?? false
|
|
96
|
-
});
|
|
97
|
-
const buttonType = useResolveButtonType({
|
|
98
|
-
get props() {
|
|
99
|
-
return { type: theirProps.type, as: theirProps.as };
|
|
100
|
-
},
|
|
101
|
-
get ref() {
|
|
102
|
-
return { current: _state.buttonElement };
|
|
103
92
|
}
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
|
|
94
|
+
const handleClick = async (event: MouseEvent) => {
|
|
95
|
+
//if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault()
|
|
96
|
+
if (disabled) return
|
|
97
|
+
if (_state.menuState === MenuStates.Open) {
|
|
98
|
+
_state.closeMenu()
|
|
99
|
+
await tick()
|
|
100
|
+
_state.buttonElement?.focus({ preventScroll: true })
|
|
101
|
+
} else {
|
|
102
|
+
event.preventDefault()
|
|
103
|
+
_state.openMenu()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { isFocusVisible: focus, focusProps } = $derived(
|
|
108
|
+
useFocusRing({
|
|
109
|
+
get autofocus() {
|
|
110
|
+
return autofocus
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
const { isHovered: hover, hoverProps } = $derived(
|
|
115
|
+
useHover({
|
|
116
|
+
get disabled() {
|
|
117
|
+
return disabled
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
const { pressed: active, pressProps } = $derived(
|
|
122
|
+
useActivePress({
|
|
123
|
+
get disabled() {
|
|
124
|
+
return disabled
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const slot = $derived({
|
|
130
|
+
open: _state.menuState === MenuStates.Open,
|
|
131
|
+
active: active || _state.menuState === MenuStates.Open,
|
|
132
|
+
disabled,
|
|
133
|
+
hover,
|
|
134
|
+
focus,
|
|
135
|
+
autofocus: autofocus ?? false,
|
|
136
|
+
} satisfies ButtonRenderPropArg)
|
|
137
|
+
|
|
138
|
+
const buttonType = useResolveButtonType({
|
|
139
|
+
get props() {
|
|
140
|
+
return { type: theirProps.type, as: theirProps.as }
|
|
119
141
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
get ref() {
|
|
143
|
+
return { current: _state.buttonElement }
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const ourProps = $derived(
|
|
148
|
+
mergeProps(
|
|
149
|
+
{
|
|
150
|
+
...getFloatingReferenceProps(),
|
|
151
|
+
id,
|
|
152
|
+
type: buttonType.type,
|
|
153
|
+
"aria-haspopup": "menu",
|
|
154
|
+
"aria-controls": _state.itemsElement?.id,
|
|
155
|
+
"aria-expanded": _state.menuState === MenuStates.Open,
|
|
156
|
+
disabled: disabled || undefined,
|
|
157
|
+
autofocus,
|
|
158
|
+
onkeydown: handleKeyDown,
|
|
159
|
+
onkeyup: handleKeyUp,
|
|
160
|
+
onclick: handleClick,
|
|
161
|
+
},
|
|
162
|
+
focusProps,
|
|
163
|
+
hoverProps,
|
|
164
|
+
pressProps
|
|
165
|
+
)
|
|
123
166
|
)
|
|
124
|
-
);
|
|
125
167
|
</script>
|
|
126
168
|
|
|
127
169
|
<ElementOrComponent {ourProps} {theirProps} {slot} defaultTag={DEFAULT_BUTTON_TAG} name="MenuButton" bind:ref />
|
|
@@ -1,19 +1,39 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { onMount, type Snippet } from "svelte"
|
|
3
|
+
import type { ElementType, Props } from "../utils/types.js"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_HEADING_TAG = "header" as const
|
|
6
|
+
type HeadingRenderPropArg = {}
|
|
7
|
+
type HeadingPropsWeControl = "role"
|
|
8
|
+
|
|
9
|
+
export type MenuHeadingProps<TTag extends ElementType = typeof DEFAULT_HEADING_TAG> = Props<
|
|
10
|
+
TTag,
|
|
11
|
+
HeadingRenderPropArg,
|
|
12
|
+
HeadingPropsWeControl,
|
|
13
|
+
{
|
|
14
|
+
id?: string
|
|
15
|
+
}
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
export type MenuHeadingChildren = Snippet<[HeadingRenderPropArg]>
|
|
3
19
|
</script>
|
|
4
20
|
|
|
5
|
-
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_HEADING_TAG">
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_HEADING_TAG">
|
|
22
|
+
import { useId } from "../hooks/use-id.js"
|
|
23
|
+
import { useLabelContext } from "../label/context.svelte.js"
|
|
24
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte"
|
|
25
|
+
|
|
26
|
+
const internalId = useId()
|
|
27
|
+
let {
|
|
28
|
+
ref = $bindable(),
|
|
29
|
+
id = `headlessui-menu-heading-${internalId}`,
|
|
30
|
+
...theirProps
|
|
31
|
+
}: { as?: TTag } & MenuHeadingProps<TTag> = $props()
|
|
32
|
+
|
|
33
|
+
const context = useLabelContext()
|
|
34
|
+
onMount(() => context.register(id))
|
|
35
|
+
|
|
36
|
+
const ourProps = $derived({ id, role: "presentation", ...context.props })
|
|
17
37
|
</script>
|
|
18
38
|
|
|
19
39
|
<ElementOrComponent {ourProps} {theirProps} defaultTag={DEFAULT_HEADING_TAG} name="MenuItem" bind:ref />
|
|
@@ -1,114 +1,152 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Props, ElementType, Children } from "../utils/types.js"
|
|
3
|
+
import { onMount, type Snippet } from "svelte"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import ElementOrComponent from "../utils/ElementOrComponent.svelte";
|
|
14
|
-
import { mergeProps } from "../utils/render.js";
|
|
15
|
-
import { useDescriptions } from "../description/context.svelte.js";
|
|
16
|
-
const internalId = useId();
|
|
17
|
-
let {
|
|
18
|
-
ref = $bindable(),
|
|
19
|
-
id = `headlessui-menu-item-${internalId}`,
|
|
20
|
-
disabled = false,
|
|
21
|
-
...theirProps
|
|
22
|
-
} = $props();
|
|
23
|
-
const _state = useMenuContext("MenuItem");
|
|
24
|
-
const active = $derived(_state.activeItemIndex !== null ? _state.items[_state.activeItemIndex].id === id : false);
|
|
25
|
-
$effect(() => {
|
|
26
|
-
_state.activeItemIndex;
|
|
27
|
-
if (_state.__demoMode) return;
|
|
28
|
-
if (_state.menuState !== MenuStates.Open) return;
|
|
29
|
-
if (!active) return;
|
|
30
|
-
if (_state.activationTrigger === ActivationTrigger.Pointer) return;
|
|
31
|
-
return disposables().requestAnimationFrame(() => {
|
|
32
|
-
;
|
|
33
|
-
ref?.scrollIntoView?.({ block: "nearest" });
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
const getTextValue = useTextValue({
|
|
37
|
-
get element() {
|
|
38
|
-
return ref || null;
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
const bag = $derived({
|
|
42
|
-
disabled,
|
|
43
|
-
domRef: { current: ref || null },
|
|
44
|
-
get textValue() {
|
|
45
|
-
return getTextValue();
|
|
5
|
+
const DEFAULT_ITEM_TAG = "svelte:fragment" as const
|
|
6
|
+
type ItemRenderPropArg = {
|
|
7
|
+
/** @deprecated use `focus` instead */
|
|
8
|
+
active: boolean
|
|
9
|
+
focus: boolean
|
|
10
|
+
disabled: boolean
|
|
11
|
+
close: () => void
|
|
12
|
+
props?: Record<string, any>
|
|
46
13
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
14
|
+
type ItemPropsWeControl = "aria-describedby" | "aria-disabled" | "aria-labelledby" | "role" | "tabIndex"
|
|
15
|
+
|
|
16
|
+
export type MenuItemProps<TTag extends ElementType = typeof DEFAULT_ITEM_TAG> = Props<
|
|
17
|
+
TTag,
|
|
18
|
+
ItemRenderPropArg,
|
|
19
|
+
ItemPropsWeControl | "children",
|
|
20
|
+
{
|
|
21
|
+
id?: string
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
children: Children<ItemRenderPropArg>
|
|
52
24
|
}
|
|
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
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
disabled
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
25
|
+
>
|
|
26
|
+
|
|
27
|
+
export type MenuItemChildren = Children<ItemRenderPropArg>
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_ITEM_TAG">
|
|
31
|
+
import { useId } from "../hooks/use-id.js"
|
|
32
|
+
import { ActivationTrigger, MenuStates, useMenuContext, type MenuItemDataRef } from "./context.svelte.js"
|
|
33
|
+
import { disposables } from "../utils/disposables.js"
|
|
34
|
+
import { useTextValue } from "../hooks/use-text-value.svelte.js"
|
|
35
|
+
import { restoreFocusIfNecessary } from "../utils/focus-management.js"
|
|
36
|
+
import { Focus } from "../utils/calculate-active-index.js"
|
|
37
|
+
import { useTrackedPointer } from "../hooks/use-tracked-pointer.js"
|
|
38
|
+
import { useLabels } from "../label/context.svelte.js"
|
|
39
|
+
import ElementOrComponent from "../utils/ElementOrComponent.svelte"
|
|
40
|
+
import { mergeProps } from "../utils/render.js"
|
|
41
|
+
import { useDescriptions } from "../description/context.svelte.js"
|
|
42
|
+
|
|
43
|
+
const internalId = useId()
|
|
44
|
+
let {
|
|
45
|
+
ref = $bindable(),
|
|
46
|
+
id = `headlessui-menu-item-${internalId}`,
|
|
47
|
+
disabled = false,
|
|
48
|
+
...theirProps
|
|
49
|
+
}: { as?: TTag } & MenuItemProps<TTag> = $props()
|
|
50
|
+
const _state = useMenuContext("MenuItem")
|
|
51
|
+
const active = $derived(_state.activeItemIndex !== null ? _state.items[_state.activeItemIndex].id === id : false)
|
|
52
|
+
|
|
53
|
+
$effect(() => {
|
|
54
|
+
/* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */
|
|
55
|
+
_state.activeItemIndex
|
|
56
|
+
if (_state.__demoMode) return
|
|
57
|
+
if (_state.menuState !== MenuStates.Open) return
|
|
58
|
+
if (!active) return
|
|
59
|
+
if (_state.activationTrigger === ActivationTrigger.Pointer) return
|
|
60
|
+
return disposables().requestAnimationFrame(() => {
|
|
61
|
+
;(ref as HTMLElement)?.scrollIntoView?.({ block: "nearest" })
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const getTextValue = useTextValue({
|
|
66
|
+
get element() {
|
|
67
|
+
return ref || null
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const bag: MenuItemDataRef["current"] = $derived({
|
|
72
|
+
disabled,
|
|
73
|
+
domRef: { current: ref || null },
|
|
74
|
+
get textValue() {
|
|
75
|
+
return getTextValue()
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
onMount(() => {
|
|
80
|
+
_state.registerItem(id, {
|
|
81
|
+
get current() {
|
|
82
|
+
return bag
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
return () => _state.unregisterItem(id)
|
|
110
86
|
})
|
|
111
|
-
|
|
87
|
+
|
|
88
|
+
const handleClick = (event: MouseEvent) => {
|
|
89
|
+
if (disabled) return event.preventDefault()
|
|
90
|
+
_state.closeMenu()
|
|
91
|
+
restoreFocusIfNecessary(_state.buttonElement)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleFocus = () => {
|
|
95
|
+
if (disabled) return _state.goToItem({ focus: Focus.Nothing })
|
|
96
|
+
_state.goToItem({ focus: Focus.Specific, id })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const pointer = useTrackedPointer()
|
|
100
|
+
|
|
101
|
+
const handleEnter = (evt: PointerEvent) => {
|
|
102
|
+
pointer.update(evt)
|
|
103
|
+
if (disabled) return
|
|
104
|
+
if (active) return
|
|
105
|
+
_state.goToItem({ focus: Focus.Specific, id, trigger: ActivationTrigger.Pointer })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const handleMove = (evt: PointerEvent) => {
|
|
109
|
+
if (!pointer.wasMoved(evt)) return
|
|
110
|
+
if (disabled) return
|
|
111
|
+
if (active) return
|
|
112
|
+
_state.goToItem({ focus: Focus.Specific, id, trigger: ActivationTrigger.Pointer })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const handleLeave = (evt: PointerEvent) => {
|
|
116
|
+
if (!pointer.wasMoved(evt)) return
|
|
117
|
+
if (disabled) return
|
|
118
|
+
if (!active) return
|
|
119
|
+
_state.goToItem({ focus: Focus.Nothing })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const labelledby = useLabels()
|
|
123
|
+
const describedby = useDescriptions()
|
|
124
|
+
|
|
125
|
+
const slot = $derived({
|
|
126
|
+
active,
|
|
127
|
+
focus: active,
|
|
128
|
+
disabled,
|
|
129
|
+
close: _state.closeMenu,
|
|
130
|
+
} satisfies ItemRenderPropArg)
|
|
131
|
+
const ourProps = $derived(
|
|
132
|
+
mergeProps({
|
|
133
|
+
id,
|
|
134
|
+
role: "menuitem",
|
|
135
|
+
tabindex: disabled === true ? undefined : -1,
|
|
136
|
+
"aria-disabled": disabled === true ? true : undefined,
|
|
137
|
+
"aria-labelledby": labelledby.value,
|
|
138
|
+
"aria-describedby": describedby.value,
|
|
139
|
+
disabled: undefined, // Never forward the `disabled` prop
|
|
140
|
+
onclick: handleClick,
|
|
141
|
+
onfocus: handleFocus,
|
|
142
|
+
onpointerenter: handleEnter,
|
|
143
|
+
onmouseenter: handleEnter,
|
|
144
|
+
onpointermove: handleMove,
|
|
145
|
+
onmousemove: handleMove,
|
|
146
|
+
onpointerleave: handleLeave,
|
|
147
|
+
onmouseleave: handleLeave,
|
|
148
|
+
})
|
|
149
|
+
)
|
|
112
150
|
</script>
|
|
113
151
|
|
|
114
152
|
<ElementOrComponent {ourProps} {theirProps} {slot} defaultTag={DEFAULT_ITEM_TAG} name="MenuItem" bind:ref />
|