@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.
Files changed (59) hide show
  1. package/dist/button/Button.svelte +84 -54
  2. package/dist/checkbox/Checkbox.svelte +174 -120
  3. package/dist/close-button/CloseButton.svelte +12 -6
  4. package/dist/combobox/Combobox.svelte +50 -3
  5. package/dist/data-interactive/DataInteractive.svelte +57 -29
  6. package/dist/description/Description.svelte +32 -21
  7. package/dist/dialog/Dialog.svelte +69 -34
  8. package/dist/dialog/DialogBackdrop.svelte +29 -12
  9. package/dist/dialog/DialogPanel.svelte +49 -26
  10. package/dist/dialog/DialogTitle.svelte +38 -23
  11. package/dist/dialog/InternalDialog.svelte +263 -202
  12. package/dist/field/Field.svelte +49 -26
  13. package/dist/fieldset/Fieldset.svelte +50 -29
  14. package/dist/focus-trap/FocusTrap.svelte +436 -290
  15. package/dist/input/Input.svelte +85 -53
  16. package/dist/internal/FocusSentinel.svelte +16 -8
  17. package/dist/internal/ForcePortalRoot.svelte +7 -3
  18. package/dist/internal/FormFields.svelte +31 -20
  19. package/dist/internal/FormResolver.svelte +20 -15
  20. package/dist/internal/Hidden.svelte +44 -27
  21. package/dist/internal/Hidden.svelte.d.ts +2 -5
  22. package/dist/internal/HiddenFeatures.d.ts +5 -0
  23. package/dist/internal/HiddenFeatures.js +9 -0
  24. package/dist/internal/HoistFormFields.svelte +7 -4
  25. package/dist/internal/MainTreeProvider.svelte +89 -36
  26. package/dist/internal/Portal.svelte +18 -14
  27. package/dist/label/Label.svelte +91 -57
  28. package/dist/legend/Legend.svelte +18 -3
  29. package/dist/listbox/Listbox.svelte +600 -409
  30. package/dist/listbox/ListboxButton.svelte +176 -127
  31. package/dist/listbox/ListboxOption.svelte +166 -125
  32. package/dist/listbox/ListboxOptions.svelte +340 -244
  33. package/dist/listbox/ListboxSelectedOption.svelte +38 -15
  34. package/dist/menu/Menu.svelte +307 -218
  35. package/dist/menu/MenuButton.svelte +157 -115
  36. package/dist/menu/MenuHeading.svelte +34 -14
  37. package/dist/menu/MenuItem.svelte +145 -107
  38. package/dist/menu/MenuItems.svelte +298 -224
  39. package/dist/menu/MenuSection.svelte +26 -9
  40. package/dist/menu/MenuSeparator.svelte +20 -4
  41. package/dist/portal/InternalPortal.svelte +141 -85
  42. package/dist/portal/Portal.svelte +5 -2
  43. package/dist/portal/PortalGroup.svelte +30 -9
  44. package/dist/switch/Switch.svelte +179 -122
  45. package/dist/switch/Switch.svelte.d.ts +4 -4
  46. package/dist/switch/SwitchGroup.svelte +44 -31
  47. package/dist/tabs/Tab.svelte +195 -143
  48. package/dist/tabs/TabGroup.svelte +292 -205
  49. package/dist/tabs/TabList.svelte +31 -11
  50. package/dist/tabs/TabPanel.svelte +68 -43
  51. package/dist/tabs/TabPanels.svelte +18 -7
  52. package/dist/textarea/Textarea.svelte +83 -53
  53. package/dist/transition/InternalTransitionChild.svelte +259 -170
  54. package/dist/transition/Transition.svelte +96 -66
  55. package/dist/transition/TransitionChild.svelte +31 -11
  56. package/dist/utils/ElementOrComponent.svelte +44 -23
  57. package/dist/utils/Generic.svelte +29 -17
  58. package/dist/utils/StableCollection.svelte +54 -36
  59. package/package.json +10 -10
@@ -1,127 +1,169 @@
1
- <script lang="ts" module>import { tick } from "svelte";
2
- const DEFAULT_BUTTON_TAG = "button";
3
- </script>
1
+ <script lang="ts" module>
2
+ import { tick, type Snippet } from "svelte"
3
+ import type { Props, ElementType } from "../utils/types.js"
4
4
 
5
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_BUTTON_TAG">import { useId } from "../hooks/use-id.js";
6
- import { Focus } from "../utils/calculate-active-index.js";
7
- import { useFocusRing } from "../hooks/use-focus-ring.svelte.js";
8
- import { useActivePress } from "../hooks/use-active-press.svelte.js";
9
- import { useResolveButtonType } from "../hooks/use-resolve-button-type.svelte.js";
10
- import { useFloating } from "../internal/floating.svelte.js";
11
- import { useHover } from "../hooks/use-hover.svelte.js";
12
- import { mergeProps } from "../utils/render.js";
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
- const handleClick = async (event) => {
58
- if (disabled) return;
59
- if (_state.menuState === MenuStates.Open) {
60
- _state.closeMenu();
61
- await tick();
62
- _state.buttonElement?.focus({ preventScroll: true });
63
- } else {
64
- event.preventDefault();
65
- _state.openMenu();
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 { isHovered: hover, hoverProps } = $derived(
76
- useHover({
77
- get disabled() {
78
- return disabled;
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 { pressed: active, pressProps } = $derived(
83
- useActivePress({
84
- get disabled() {
85
- return disabled;
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 ourProps = $derived(
106
- mergeProps(
107
- {
108
- ...getFloatingReferenceProps(),
109
- id,
110
- type: buttonType.type,
111
- "aria-haspopup": "menu",
112
- "aria-controls": _state.itemsElement?.id,
113
- "aria-expanded": _state.menuState === MenuStates.Open,
114
- disabled: disabled || void 0,
115
- autofocus,
116
- onkeydown: handleKeyDown,
117
- onkeyup: handleKeyUp,
118
- onclick: handleClick
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
- focusProps,
121
- hoverProps,
122
- pressProps
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>import { onMount } from "svelte";
2
- const DEFAULT_HEADING_TAG = "header";
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">import { useId } from "../hooks/use-id.js";
6
- import { useLabelContext } from "../label/context.svelte.js";
7
- import ElementOrComponent from "../utils/ElementOrComponent.svelte";
8
- const internalId = useId();
9
- let {
10
- ref = $bindable(),
11
- id = `headlessui-menu-heading-${internalId}`,
12
- ...theirProps
13
- } = $props();
14
- const context = useLabelContext();
15
- onMount(() => context.register(id));
16
- const ourProps = $derived({ id, role: "presentation", ...context.props });
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>import { onMount } from "svelte";
2
- const DEFAULT_ITEM_TAG = "svelte:fragment";
3
- </script>
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
- <script lang="ts" generics="TTag extends ElementType = typeof DEFAULT_ITEM_TAG">import { useId } from "../hooks/use-id.js";
6
- import { ActivationTrigger, MenuStates, useMenuContext } from "./context.svelte.js";
7
- import { disposables } from "../utils/disposables.js";
8
- import { useTextValue } from "../hooks/use-text-value.svelte.js";
9
- import { restoreFocusIfNecessary } from "../utils/focus-management.js";
10
- import { Focus } from "../utils/calculate-active-index.js";
11
- import { useTrackedPointer } from "../hooks/use-tracked-pointer.js";
12
- import { useLabels } from "../label/context.svelte.js";
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
- onMount(() => {
49
- _state.registerItem(id, {
50
- get current() {
51
- return bag;
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
- return () => _state.unregisterItem(id);
55
- });
56
- const handleClick = (event) => {
57
- if (disabled) return event.preventDefault();
58
- _state.closeMenu();
59
- restoreFocusIfNecessary(_state.buttonElement);
60
- };
61
- const handleFocus = () => {
62
- if (disabled) return _state.goToItem({ focus: Focus.Nothing });
63
- _state.goToItem({ focus: Focus.Specific, id });
64
- };
65
- const pointer = useTrackedPointer();
66
- const handleEnter = (evt) => {
67
- pointer.update(evt);
68
- if (disabled) return;
69
- if (active) return;
70
- _state.goToItem({ focus: Focus.Specific, id, trigger: ActivationTrigger.Pointer });
71
- };
72
- const handleMove = (evt) => {
73
- if (!pointer.wasMoved(evt)) return;
74
- if (disabled) return;
75
- if (active) return;
76
- _state.goToItem({ focus: Focus.Specific, id, trigger: ActivationTrigger.Pointer });
77
- };
78
- const handleLeave = (evt) => {
79
- if (!pointer.wasMoved(evt)) return;
80
- if (disabled) return;
81
- if (!active) return;
82
- _state.goToItem({ focus: Focus.Nothing });
83
- };
84
- const labelledby = useLabels();
85
- const describedby = useDescriptions();
86
- const slot = $derived({
87
- active,
88
- focus: active,
89
- disabled,
90
- close: _state.closeMenu
91
- });
92
- const ourProps = $derived(
93
- mergeProps({
94
- id,
95
- role: "menuitem",
96
- tabindex: disabled === true ? void 0 : -1,
97
- "aria-disabled": disabled === true ? true : void 0,
98
- "aria-labelledby": labelledby.value,
99
- "aria-describedby": describedby.value,
100
- disabled: void 0,
101
- // Never forward the `disabled` prop
102
- onclick: handleClick,
103
- onfocus: handleFocus,
104
- onpointerenter: handleEnter,
105
- onmouseenter: handleEnter,
106
- onpointermove: handleMove,
107
- onmousemove: handleMove,
108
- onpointerleave: handleLeave,
109
- onmouseleave: handleLeave
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 />