@funstack/ui 0.1.0-alpha.6 → 0.1.0-alpha.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.mjs +1 -1
- package/dist/button/button.mjs.map +1 -1
- package/dist/combobox/combobox-clear.d.mts +6 -3
- package/dist/combobox/combobox-clear.d.mts.map +1 -1
- package/dist/combobox/combobox-clear.mjs +10 -5
- package/dist/combobox/combobox-clear.mjs.map +1 -1
- package/dist/combobox/combobox-trigger.d.mts +6 -2
- package/dist/combobox/combobox-trigger.d.mts.map +1 -1
- package/dist/combobox/combobox-trigger.mjs +10 -4
- package/dist/combobox/combobox-trigger.mjs.map +1 -1
- package/dist/dialog/dialog-close.d.mts +8 -3
- package/dist/dialog/dialog-close.d.mts.map +1 -1
- package/dist/dialog/dialog-close.mjs +11 -7
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-trigger.d.mts +8 -3
- package/dist/dialog/dialog-trigger.d.mts.map +1 -1
- package/dist/dialog/dialog-trigger.mjs +15 -8
- package/dist/dialog/dialog-trigger.mjs.map +1 -1
- package/dist/form/form-submit.d.mts +4 -1
- package/dist/form/form-submit.d.mts.map +1 -1
- package/dist/form/form-submit.mjs.map +1 -1
- package/dist/internal/anchor-position.mjs +24 -1
- package/dist/internal/anchor-position.mjs.map +1 -1
- package/dist/internal/use-action-trigger.mjs +9 -7
- package/dist/internal/use-action-trigger.mjs.map +1 -1
- package/dist/internal/use-popover-popup.mjs +22 -4
- package/dist/internal/use-popover-popup.mjs.map +1 -1
- package/dist/internal/use-popover-trigger.mjs +6 -5
- package/dist/internal/use-popover-trigger.mjs.map +1 -1
- package/dist/menu/index.d.mts +5 -1
- package/dist/menu/index.d.mts.map +1 -1
- package/dist/menu/index.mjs +5 -1
- package/dist/menu/index.mjs.map +1 -1
- package/dist/menu/menu-context-trigger.d.mts +46 -0
- package/dist/menu/menu-context-trigger.d.mts.map +1 -0
- package/dist/menu/menu-context-trigger.mjs +226 -0
- package/dist/menu/menu-context-trigger.mjs.map +1 -0
- package/dist/menu/menu-context.mjs +34 -1
- package/dist/menu/menu-context.mjs.map +1 -1
- package/dist/menu/menu-dom.mjs +44 -2
- package/dist/menu/menu-dom.mjs.map +1 -1
- package/dist/menu/menu-item.d.mts +6 -2
- package/dist/menu/menu-item.d.mts.map +1 -1
- package/dist/menu/menu-item.mjs +5 -3
- package/dist/menu/menu-item.mjs.map +1 -1
- package/dist/menu/menu-popup.d.mts +18 -4
- package/dist/menu/menu-popup.d.mts.map +1 -1
- package/dist/menu/menu-popup.mjs +116 -17
- package/dist/menu/menu-popup.mjs.map +1 -1
- package/dist/menu/menu-root.d.mts +5 -0
- package/dist/menu/menu-root.d.mts.map +1 -1
- package/dist/menu/menu-root.mjs +48 -6
- package/dist/menu/menu-root.mjs.map +1 -1
- package/dist/menu/menu-trigger.d.mts +6 -3
- package/dist/menu/menu-trigger.d.mts.map +1 -1
- package/dist/menu/menu-trigger.mjs +2 -2
- package/dist/menu/menu-trigger.mjs.map +1 -1
- package/dist/number-field/number-field-decrement.d.mts +4 -3
- package/dist/number-field/number-field-decrement.d.mts.map +1 -1
- package/dist/number-field/number-field-decrement.mjs +4 -3
- package/dist/number-field/number-field-decrement.mjs.map +1 -1
- package/dist/number-field/number-field-increment.d.mts +8 -3
- package/dist/number-field/number-field-increment.d.mts.map +1 -1
- package/dist/number-field/number-field-increment.mjs +8 -5
- package/dist/number-field/number-field-increment.mjs.map +1 -1
- package/dist/number-field/number-field-root.mjs +1 -1
- package/dist/number-field/number-field-root.mjs.map +1 -1
- package/dist/number-field/use-step-button.mjs +6 -1
- package/dist/number-field/use-step-button.mjs.map +1 -1
- package/dist/popover/popover-close.d.mts +9 -3
- package/dist/popover/popover-close.d.mts.map +1 -1
- package/dist/popover/popover-close.mjs +12 -7
- package/dist/popover/popover-close.mjs.map +1 -1
- package/dist/popover/popover-trigger.d.mts +8 -4
- package/dist/popover/popover-trigger.d.mts.map +1 -1
- package/dist/popover/popover-trigger.mjs +3 -3
- package/dist/popover/popover-trigger.mjs.map +1 -1
- package/dist/slider/slider-root.mjs +1 -1
- package/dist/slider/slider-root.mjs.map +1 -1
- package/dist/toast/toast-action.d.mts +4 -3
- package/dist/toast/toast-action.d.mts.map +1 -1
- package/dist/toast/toast-action.mjs +7 -5
- package/dist/toast/toast-action.mjs.map +1 -1
- package/dist/toggle/toggle.d.mts +4 -1
- package/dist/toggle/toggle.d.mts.map +1 -1
- package/dist/toggle/toggle.mjs +14 -8
- package/dist/toggle/toggle.mjs.map +1 -1
- package/dist/toggle-group/toggle-group-item.d.mts +6 -2
- package/dist/toggle-group/toggle-group-item.d.mts.map +1 -1
- package/dist/toggle-group/toggle-group-item.mjs +9 -4
- package/dist/toggle-group/toggle-group-item.mjs.map +1 -1
- package/package.json +1 -1
package/dist/menu/menu-popup.mjs
CHANGED
|
@@ -4,13 +4,13 @@ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
|
4
4
|
import { mergeRefs } from "../internal/merge-props.mjs";
|
|
5
5
|
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
6
6
|
import { useRender } from "../internal/use-render.mjs";
|
|
7
|
-
import { POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
|
|
7
|
+
import { FSUI_PRECEDENCE, MENU_POINT_FALLBACKS, MENU_POINT_HREF, MENU_POINT_RULES, MENU_POINT_X, MENU_POINT_Y, POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
|
|
8
8
|
import { usePopoverPopup } from "../internal/use-popover-popup.mjs";
|
|
9
|
-
import { MenuListContext, useMenuContext } from "./menu-context.mjs";
|
|
10
|
-
import { edgeItem, focusItem, hasModifier, isMenuItem, isRtl, menuItems, typeaheadText } from "./menu-dom.mjs";
|
|
9
|
+
import { MenuListContext, claimPress, isHandledBy, useMenuContext } from "./menu-context.mjs";
|
|
10
|
+
import { capturePointer, edgeItem, focusItem, hasModifier, isMenuItem, isRtl, menuItems, typeaheadText } from "./menu-dom.mjs";
|
|
11
11
|
import { scheduleHoverClose } from "./menu-hover.mjs";
|
|
12
|
-
import { Activity, useCallback, useMemo, useRef, useSyncExternalStore } from "react";
|
|
13
|
-
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
import { Activity, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
14
|
//#region src/menu/menu-popup.tsx
|
|
15
15
|
/** Engine capabilities never change while the page lives. */
|
|
16
16
|
const subscribeNever = () => () => {};
|
|
@@ -20,6 +20,57 @@ const isDev = process.env.NODE_ENV !== "production";
|
|
|
20
20
|
/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */
|
|
21
21
|
const FOCUSABLE_SELECTOR = "a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])";
|
|
22
22
|
/**
|
|
23
|
+
* The inline placement of a point open (context-menu.md 3.3): the point as two custom
|
|
24
|
+
* properties, and unless the stylesheet owns placement, `left` / `top` at it with the other two
|
|
25
|
+
* insets `auto` (longhands, so React never sees a shorthand next to its longhands),
|
|
26
|
+
* `position-area: none` (a consumer's anchored `position-area` would otherwise place the popup
|
|
27
|
+
* inside a grid area) and the library's fallbacks, spelled by generation parity (4.3).
|
|
28
|
+
*/
|
|
29
|
+
function pointStyle(point, stylesheetOwned) {
|
|
30
|
+
const vars = {
|
|
31
|
+
[MENU_POINT_X]: `${point.x}px`,
|
|
32
|
+
[MENU_POINT_Y]: `${point.y}px`
|
|
33
|
+
};
|
|
34
|
+
if (stylesheetOwned) return vars;
|
|
35
|
+
return {
|
|
36
|
+
...vars,
|
|
37
|
+
top: `var(${MENU_POINT_Y})`,
|
|
38
|
+
right: "auto",
|
|
39
|
+
bottom: "auto",
|
|
40
|
+
left: `var(${MENU_POINT_X})`,
|
|
41
|
+
positionArea: "none",
|
|
42
|
+
positionTryFallbacks: MENU_POINT_FALLBACKS[point.generation % 2]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The outside right- and middle-click close of a top-level menu (context-menu.md 5): a
|
|
47
|
+
* `contextmenu` outside the menu tree that this menu's ContextTrigger did not handle, or a
|
|
48
|
+
* middle-button `pointerdown` outside it, hides the root popup at once (`"dismiss"` through the
|
|
49
|
+
* `toggle` notification). Chromium's click-based light dismiss ignores those buttons. Returns the
|
|
50
|
+
* cleanup.
|
|
51
|
+
*/
|
|
52
|
+
function listenOutside(popup, menu) {
|
|
53
|
+
const doc = popup.ownerDocument;
|
|
54
|
+
const inside = (target) => target instanceof Node && popup.contains(target);
|
|
55
|
+
const close = () => {
|
|
56
|
+
if (popup.matches(":popover-open")) popup.hidePopover();
|
|
57
|
+
};
|
|
58
|
+
const onContextMenu = (event) => {
|
|
59
|
+
if (inside(event.target) || isHandledBy(event, menu)) return;
|
|
60
|
+
close();
|
|
61
|
+
};
|
|
62
|
+
const onPointerDown = (event) => {
|
|
63
|
+
if (event.button !== 1 || inside(event.target)) return;
|
|
64
|
+
close();
|
|
65
|
+
};
|
|
66
|
+
doc.addEventListener("contextmenu", onContextMenu);
|
|
67
|
+
doc.addEventListener("pointerdown", onPointerDown, true);
|
|
68
|
+
return () => {
|
|
69
|
+
doc.removeEventListener("contextmenu", onContextMenu);
|
|
70
|
+
doc.removeEventListener("pointerdown", onPointerDown, true);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
23
74
|
* The menu itself: `<div popover="auto" role="menu" aria-labelledby={trigger} tabindex="-1">`,
|
|
24
75
|
* placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):
|
|
25
76
|
* ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with
|
|
@@ -29,13 +80,20 @@ const FOCUSABLE_SELECTOR = "a[href], button, input, select, textarea, summary, [
|
|
|
29
80
|
*
|
|
30
81
|
* Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),
|
|
31
82
|
* `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on
|
|
32
|
-
* non-item space keeps focus on the current item), `
|
|
33
|
-
* `data-side` / `data-align` unless `side` is `null
|
|
83
|
+
* non-item space keeps focus on the current item), `onContextMenu` (no native menu over this
|
|
84
|
+
* one), `onPointerEnter` / `onPointerLeave`, `data-side` / `data-align` unless `side` is `null`
|
|
85
|
+
* or the menu is placed at a point, and `data-context` for an open from a `Menu.ContextTrigger`
|
|
86
|
+
* (placed at the pointer: `--menu-point-x` / `--menu-point-y` inline, and unless `side` is
|
|
87
|
+
* `null`, `left` / `top` at the point with the library's `@position-try` fallbacks, whose rules
|
|
88
|
+
* it renders as a hoisted `<style>`). A top-level menu closes at once on a right- or
|
|
89
|
+
* middle-click outside it.
|
|
34
90
|
*/
|
|
35
91
|
function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_focusgroup = false, transition, className, style, children, render, ...rest }) {
|
|
36
92
|
const menu = useMenuContext("Menu.Popup");
|
|
37
93
|
const { popover, submenu, loop } = menu;
|
|
38
|
-
const
|
|
94
|
+
const sideOption = sideProp === void 0 ? submenu ? "right" : "bottom" : sideProp;
|
|
95
|
+
const point = menu.placement.kind === "point" ? menu.placement : null;
|
|
96
|
+
const side = point !== null ? null : sideOption;
|
|
39
97
|
const nativeFocusgroup = useSyncExternalStore(subscribeNever, () => experimental_focusgroup && "focusGroup" in HTMLElement.prototype, () => false);
|
|
40
98
|
const typeahead = useRef({
|
|
41
99
|
text: "",
|
|
@@ -45,7 +103,13 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
45
103
|
const { text, at } = typeahead.current;
|
|
46
104
|
return text !== "" && Date.now() - at <= TYPEAHEAD_RESET_MS;
|
|
47
105
|
}, []);
|
|
48
|
-
const
|
|
106
|
+
const outside = useRef(null);
|
|
107
|
+
const stopOutside = useCallback(() => {
|
|
108
|
+
outside.current?.();
|
|
109
|
+
outside.current = null;
|
|
110
|
+
}, []);
|
|
111
|
+
useEffect(() => stopOutside, [stopOutside]);
|
|
112
|
+
const { setElement, visible, wrapContent, reshow } = usePopoverPopup(popover, {
|
|
49
113
|
part: "Menu.Popup",
|
|
50
114
|
initialFocus: void 0,
|
|
51
115
|
resolveInitialFocus: (popup) => {
|
|
@@ -58,11 +122,33 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
58
122
|
} : element;
|
|
59
123
|
},
|
|
60
124
|
keepMounted,
|
|
61
|
-
transition
|
|
125
|
+
transition,
|
|
126
|
+
source: () => point !== null ? point.source : popover.triggerRef.current,
|
|
127
|
+
onToggle: (event) => {
|
|
128
|
+
if (submenu) return;
|
|
129
|
+
const element = popover.popupRef.current;
|
|
130
|
+
if (event.newState === "open" && element !== null && element.matches(":popover-open")) outside.current ??= listenOutside(element, menu);
|
|
131
|
+
else if (event.newState === "closed") stopOutside();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
const shownKey = useRef(null);
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
const element = popover.popupRef.current;
|
|
137
|
+
if (element === null) return;
|
|
138
|
+
const key = point !== null ? point.generation : "anchor";
|
|
139
|
+
if (element.matches(":popover-open")) {
|
|
140
|
+
if (shownKey.current !== null && shownKey.current !== key && reshow()) capturePointer(element, point?.pointerId);
|
|
141
|
+
shownKey.current = key;
|
|
142
|
+
} else {
|
|
143
|
+
shownKey.current = null;
|
|
144
|
+
if (!visible && point !== null && !popover.pending && !popover.intended()) menu.resetPlacement(point.generation);
|
|
145
|
+
}
|
|
62
146
|
});
|
|
63
147
|
const devRef = useCallback((element) => {
|
|
64
148
|
if (isDev && element.tagName === "DIALOG") console.error("Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog focusing steps, which fight the menu's item focus.");
|
|
65
|
-
|
|
149
|
+
const onPointerDown = (event) => claimPress(event);
|
|
150
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
151
|
+
return () => element.removeEventListener("pointerdown", onPointerDown);
|
|
66
152
|
}, []);
|
|
67
153
|
const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);
|
|
68
154
|
const listContext = useMemo(() => ({
|
|
@@ -78,7 +164,8 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
78
164
|
open: popover.committed,
|
|
79
165
|
side,
|
|
80
166
|
align,
|
|
81
|
-
submenu
|
|
167
|
+
submenu,
|
|
168
|
+
context: point !== null
|
|
82
169
|
};
|
|
83
170
|
const runTypeahead = (popup, current, key) => {
|
|
84
171
|
const now = Date.now();
|
|
@@ -160,6 +247,9 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
160
247
|
onKeyDown,
|
|
161
248
|
onBlur,
|
|
162
249
|
onMouseDown,
|
|
250
|
+
onContextMenu: (event) => {
|
|
251
|
+
event.preventDefault();
|
|
252
|
+
},
|
|
163
253
|
...submenu && {
|
|
164
254
|
onPointerEnter: (event) => {
|
|
165
255
|
if (event.pointerType !== "touch") menu.timers.cancelClose();
|
|
@@ -171,8 +261,14 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
171
261
|
...side !== null && {
|
|
172
262
|
"data-side": side,
|
|
173
263
|
"data-align": align
|
|
174
|
-
}
|
|
264
|
+
},
|
|
265
|
+
"data-context": point !== null ? "" : void 0
|
|
175
266
|
};
|
|
267
|
+
const pointRules = point !== null && sideOption !== null ? /* @__PURE__ */ jsx("style", {
|
|
268
|
+
href: MENU_POINT_HREF,
|
|
269
|
+
precedence: FSUI_PRECEDENCE,
|
|
270
|
+
children: MENU_POINT_RULES
|
|
271
|
+
}) : null;
|
|
176
272
|
const content = (s) => {
|
|
177
273
|
let node;
|
|
178
274
|
if (keepMounted === true) node = /* @__PURE__ */ jsx(Activity, {
|
|
@@ -180,12 +276,12 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
180
276
|
children: wrapContent(resolveStateValue(children, s))
|
|
181
277
|
});
|
|
182
278
|
else node = visible ? wrapContent(resolveStateValue(children, s)) : null;
|
|
183
|
-
return /* @__PURE__ */
|
|
279
|
+
return /* @__PURE__ */ jsxs(MenuListContext, {
|
|
184
280
|
value: listContext,
|
|
185
|
-
children: /* @__PURE__ */ jsx(ToolbarContext, {
|
|
281
|
+
children: [pointRules, /* @__PURE__ */ jsx(ToolbarContext, {
|
|
186
282
|
value: null,
|
|
187
283
|
children: node
|
|
188
|
-
})
|
|
284
|
+
})]
|
|
189
285
|
});
|
|
190
286
|
};
|
|
191
287
|
const element = useRender({
|
|
@@ -193,7 +289,10 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
193
289
|
render,
|
|
194
290
|
state,
|
|
195
291
|
className,
|
|
196
|
-
style:
|
|
292
|
+
style: point !== null ? (s) => ({
|
|
293
|
+
...pointStyle(point, sideOption === null),
|
|
294
|
+
...resolveStateValue(style, s)
|
|
295
|
+
}) : side === null ? style : (s) => ({
|
|
197
296
|
positionArea: positionArea(side, align),
|
|
198
297
|
positionTryFallbacks: submenu ? "flip-inline" : POSITION_TRY_FALLBACKS,
|
|
199
298
|
...resolveStateValue(style, s)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-popup.mjs","names":[],"sources":["../../src/menu/menu-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { POSITION_TRY_FALLBACKS, positionArea } from \"../internal/anchor-position.js\";\nimport type { AnchorAlign, AnchorSide } from \"../internal/anchor-position.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { ToolbarContext } from \"../internal/toolbar-context.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { MenuListContext, useMenuContext } from \"./menu-context.js\";\nimport type { MenuListContextValue } from \"./menu-context.js\";\nimport {\n edgeItem,\n focusItem,\n hasModifier,\n isMenuItem,\n isRtl,\n menuItems,\n typeaheadText,\n} from \"./menu-dom.js\";\nimport { scheduleHoverClose } from \"./menu-hover.js\";\n\nexport type MenuSide = AnchorSide;\nexport type MenuAlign = AnchorAlign;\n\nexport interface MenuPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n readonly side: MenuSide | null;\n readonly align: MenuAlign;\n /** This popup belongs to a submenu. */\n readonly submenu: boolean;\n}\n\nexport interface MenuPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"role\"\n | \"aria-labelledby\"\n | \"onKeyDown\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onMouseDown\"\n | \"onPointerEnter\"\n | \"onPointerLeave\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * Placement against the Trigger through inline `position-area`, with `position-try-fallbacks`\n * flipping it when it does not fit. Default `\"bottom\"` for a top-level menu, `\"right\"` for a\n * submenu (whose fallback is `flip-inline` only). `null` emits no inline positioning (and no\n * `data-side` / `data-align`): the stylesheet owns placement.\n */\n side?: MenuSide | null;\n /** Alignment along the side. Default `\"start\"`. Ignored when `side` is `null`. */\n align?: MenuAlign;\n /**\n * `false` (default): the items are not rendered while the menu is closed; mounting them is part\n * of the opening transition, so items that suspend hold the open (and the Trigger pending)\n * until they are ready. `true`: they stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Experimental (Chromium 150+). Renders `focusgroup=\"menu wrap|nowrap\"` and, where the engine\n * implements `focusGroup`, lets the browser handle ArrowUp / ArrowDown / Home / End: items then\n * render no `tabindex` and the library's arrow-key handling steps aside. Typeahead, activation,\n * submenu keys and Tab stay in JS. No effect in other engines. Default `false`.\n */\n experimental_focusgroup?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popup element. Animates the opening\n * commit and every library-driven close (an item's activation, the Trigger, ArrowLeft, hover\n * leaving a submenu, controlled `open={false}`) in all engines; dismissals (Escape, a click\n * outside, Tab) animate with CSS only. Without it the part renders no `<ViewTransition>`.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, MenuPopupState>;\n style?: StateValue<CSSProperties | undefined, MenuPopupState>;\n children?: StateValue<ReactNode, MenuPopupState>;\n /** Should render a `<div>`; a `<dialog>` would run the dialog focusing steps. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuPopupState>;\n}\n\n/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\n\n/** Typeahead buffer lifetime after the last keystroke (design 5.1). */\nconst TYPEAHEAD_RESET_MS = 500;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */\nconst FOCUSABLE_SELECTOR =\n 'a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * The menu itself: `<div popover=\"auto\" role=\"menu\" aria-labelledby={trigger} tabindex=\"-1\">`,\n * placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):\n * ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with\n * `experimental_focusgroup` where supported), printable characters run typeahead, ArrowLeft\n * closes a submenu. The top-level popup closes the whole menu at once when focus leaves it (Tab).\n * Style it with `:popover-open`; it emits `data-side` / `data-align` but no `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),\n * `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on\n * non-item space keeps focus on the current item), `onPointerEnter` / `onPointerLeave`, and\n * `data-side` / `data-align` unless `side` is `null`.\n */\nexport function MenuPopup({\n side: sideProp,\n align = \"start\",\n keepMounted,\n experimental_focusgroup = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuPopupProps) {\n const menu = useMenuContext(\"Menu.Popup\");\n const { popover, submenu, loop } = menu;\n const side = sideProp === undefined ? (submenu ? \"right\" : \"bottom\") : sideProp;\n\n // As Tabs.List (#43): detected only with the flag, never during SSR or hydration.\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n\n const typeahead = useRef({ text: \"\", at: 0 });\n const typingAhead = useCallback(() => {\n const { text, at } = typeahead.current;\n return text !== \"\" && Date.now() - at <= TYPEAHEAD_RESET_MS;\n }, []);\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Menu.Popup\",\n initialFocus: undefined,\n // Design 6.1: the first (or last) enabled item by open cause, nothing for a hover open, the\n // popup itself when there are no items.\n // A pointer open focuses without the focus ring where the engine can be told so (H1).\n resolveInitialFocus: (popup) => {\n const { cause, pointer } = menu.takeOpenCause();\n if (cause === \"none\") return null;\n const element = edgeItem(popup, cause) ?? popup;\n return pointer ? { element, focusVisible: false } : element;\n },\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName === \"DIALOG\") {\n console.error(\n \"Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog \" +\n \"focusing steps, which fight the menu's item focus.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const listContext = useMemo<MenuListContextValue>(\n () => ({ menu, nativeFocusgroup, typingAhead }),\n [menu, nativeFocusgroup, typingAhead],\n );\n\n const state: MenuPopupState = { open: popover.committed, side, align, submenu };\n\n const runTypeahead = (popup: HTMLElement, current: HTMLElement | null, key: string): boolean => {\n const now = Date.now();\n const buffer = typeahead.current;\n if (buffer.text === \"\" || now - buffer.at > TYPEAHEAD_RESET_MS) buffer.text = \"\";\n if (key === \" \" && buffer.text === \"\") return false;\n buffer.text += key.toLowerCase();\n buffer.at = now;\n const items = menuItems(popup);\n if (items.length === 0) return true;\n // A repeated single character cycles through the items starting with it; a longer buffer\n // keeps the current item while it still matches (design 5.1).\n const repeated = [...buffer.text].every((c) => c === buffer.text[0]);\n const search = repeated ? buffer.text[0]! : buffer.text;\n const index = current === null ? -1 : items.indexOf(current);\n const start = repeated || index < 0 ? index + 1 : index;\n for (let i = 0; i < items.length; i++) {\n const item = items[(start + i) % items.length]!;\n if (typeaheadText(item).startsWith(search)) {\n if (item !== current) focusItem(item);\n break;\n }\n }\n return true;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || hasModifier(event)) return;\n const popup = event.currentTarget;\n const target = event.target as HTMLElement;\n const onItem = isMenuItem(target, popup);\n if (!onItem && target !== popup) return;\n const key = event.key;\n\n // ArrowLeft (ArrowRight in RTL) closes a submenu; focus returns to its SubmenuTrigger\n // through the popover hook's nested focus rule (design 6.3).\n if (submenu && key === (isRtl(popup) ? \"ArrowRight\" : \"ArrowLeft\")) {\n event.preventDefault();\n popover.requestClose(event.nativeEvent, \"activate\");\n return;\n }\n\n if (key === \"ArrowDown\" || key === \"ArrowUp\" || key === \"Home\" || key === \"End\") {\n if (!onItem) {\n // The popup itself has focus (no items at open, or focus landed on it some other way):\n // into the list from its edge (review M1).\n event.preventDefault();\n const items = menuItems(popup);\n const item = key === \"ArrowDown\" || key === \"Home\" ? items[0] : items.at(-1);\n if (item !== undefined) focusItem(item);\n return;\n }\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup) return;\n event.preventDefault();\n const items = menuItems(popup);\n const index = items.indexOf(target);\n const last = items.length - 1;\n let next: number;\n if (key === \"Home\") next = 0;\n else if (key === \"End\") next = last;\n else if (key === \"ArrowDown\") next = index >= last ? (loop ? 0 : last) : index + 1;\n else next = index <= 0 ? (loop ? last : 0) : index - 1;\n const item = items[next];\n if (item !== undefined && item !== target) focusItem(item);\n return;\n }\n\n if (key.length === 1 && runTypeahead(popup, onItem ? target : null, key)) {\n event.preventDefault();\n }\n };\n\n // Tab / Shift+Tab or any focus move out of the tree closes the whole stack at once (design\n // 2.2): submenus are DOM descendants of the top-level popup, the Trigger is outside on purpose.\n // A null `relatedTarget` (window blur, a removed element) closes nothing.\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n if (submenu) return;\n const popup = event.currentTarget;\n const next = event.relatedTarget;\n if (!(next instanceof Node) || popup.contains(next)) return;\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n\n // A press on non-item space (padding, a Separator, a GroupLabel) would focus the popup itself\n // (`tabindex=\"-1\"`) and take focus off the current item; keep it where it is (review M1, the\n // pointer-focus model of design 6.2). Focusable content of the consumer's (a text field) still\n // takes focus.\n const onMouseDown = (event: MouseEvent<HTMLElement>) => {\n if (event.button !== 0) return;\n const target = event.target as Element;\n const focusable = target.closest(FOCUSABLE_SELECTOR);\n if (focusable === null || focusable.getAttribute(\"role\") === \"menu\") event.preventDefault();\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n role: \"menu\",\n \"aria-labelledby\": menu.triggerId,\n // The popup itself takes focus as the last resort when a menu has no items.\n tabIndex: -1,\n ...(experimental_focusgroup && { focusgroup: `menu ${loop ? \"wrap\" : \"nowrap\"}` }),\n onKeyDown,\n onBlur,\n onMouseDown,\n ...(submenu && {\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") menu.timers.cancelClose();\n },\n onPointerLeave: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") scheduleHoverClose(menu, event.nativeEvent);\n },\n }),\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n };\n\n const content = (s: MenuPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n // Not part of an enclosing Toolbar: a Menu.Trigger or ToggleGroup in here sees no Toolbar.\n return (\n <MenuListContext value={listContext}>\n <ToolbarContext value={null}>{node}</ToolbarContext>\n </MenuListContext>\n );\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins.\n style:\n side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: submenu ? \"flip-inline\" : POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Menu.Popup\", transition, element);\n}\n\nMenuPopup.displayName = \"MenuPopup\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu-popup.mjs","names":[],"sources":["../../src/menu/menu-popup.tsx"],"sourcesContent":["\"use client\";\nimport {\n Activity,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport {\n FSUI_PRECEDENCE,\n MENU_POINT_FALLBACKS,\n MENU_POINT_HREF,\n MENU_POINT_RULES,\n MENU_POINT_X,\n MENU_POINT_Y,\n POSITION_TRY_FALLBACKS,\n positionArea,\n} from \"../internal/anchor-position.js\";\nimport type { AnchorAlign, AnchorSide } from \"../internal/anchor-position.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { ToolbarContext } from \"../internal/toolbar-context.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { claimPress, isHandledBy, MenuListContext, useMenuContext } from \"./menu-context.js\";\nimport type { MenuContextValue, MenuListContextValue, MenuPlacement } from \"./menu-context.js\";\nimport {\n capturePointer,\n edgeItem,\n focusItem,\n hasModifier,\n isMenuItem,\n isRtl,\n menuItems,\n typeaheadText,\n} from \"./menu-dom.js\";\nimport { scheduleHoverClose } from \"./menu-hover.js\";\n\nexport type MenuSide = AnchorSide;\nexport type MenuAlign = AnchorAlign;\n\nexport interface MenuPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n /** `null` with `side={null}`, and while the menu is placed at a point (`context`). */\n readonly side: MenuSide | null;\n readonly align: MenuAlign;\n /** This popup belongs to a submenu. */\n readonly submenu: boolean;\n /**\n * The current open came from a `Menu.ContextTrigger`: the popup is placed at the pointer, with\n * `--menu-point-x` / `--menu-point-y` set inline (`data-context`).\n */\n readonly context: boolean;\n}\n\nexport interface MenuPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"role\"\n | \"aria-labelledby\"\n | \"onKeyDown\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onMouseDown\"\n | \"onContextMenu\"\n | \"onPointerEnter\"\n | \"onPointerLeave\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * Placement against the Trigger through inline `position-area`, with `position-try-fallbacks`\n * flipping it when it does not fit. Default `\"bottom\"` for a top-level menu, `\"right\"` for a\n * submenu (whose fallback is `flip-inline` only). `null` emits no inline positioning (and no\n * `data-side` / `data-align`): the stylesheet owns placement. Ignored for an open from a\n * `Menu.ContextTrigger`, which is placed at the pointer; there `null` also opts out of the\n * inline point placement and the library's `@position-try` rules, leaving only\n * `--menu-point-x` / `--menu-point-y`.\n */\n side?: MenuSide | null;\n /** Alignment along the side. Default `\"start\"`. Ignored when `side` is `null`. */\n align?: MenuAlign;\n /**\n * `false` (default): the items are not rendered while the menu is closed; mounting them is part\n * of the opening transition, so items that suspend hold the open (and the Trigger pending)\n * until they are ready. `true`: they stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Experimental (Chromium 150+). Renders `focusgroup=\"menu wrap|nowrap\"` and, where the engine\n * implements `focusGroup`, lets the browser handle ArrowUp / ArrowDown / Home / End: items then\n * render no `tabindex` and the library's arrow-key handling steps aside. Typeahead, activation,\n * submenu keys and Tab stay in JS. No effect in other engines. Default `false`.\n */\n experimental_focusgroup?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popup element. Animates the opening\n * commit and every library-driven close (an item's activation, the Trigger, ArrowLeft, hover\n * leaving a submenu, controlled `open={false}`) in all engines; dismissals (Escape, a click\n * outside, Tab) animate with CSS only. Without it the part renders no `<ViewTransition>`.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, MenuPopupState>;\n style?: StateValue<CSSProperties | undefined, MenuPopupState>;\n children?: StateValue<ReactNode, MenuPopupState>;\n /** Should render a `<div>`; a `<dialog>` would run the dialog focusing steps. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuPopupState>;\n}\n\n/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\n\n/** Typeahead buffer lifetime after the last keystroke (design 5.1). */\nconst TYPEAHEAD_RESET_MS = 500;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */\nconst FOCUSABLE_SELECTOR =\n 'a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])';\n\ntype PointPlacement = Extract<MenuPlacement, { kind: \"point\" }>;\n\n/**\n * The inline placement of a point open (context-menu.md 3.3): the point as two custom\n * properties, and unless the stylesheet owns placement, `left` / `top` at it with the other two\n * insets `auto` (longhands, so React never sees a shorthand next to its longhands),\n * `position-area: none` (a consumer's anchored `position-area` would otherwise place the popup\n * inside a grid area) and the library's fallbacks, spelled by generation parity (4.3).\n */\nfunction pointStyle(point: PointPlacement, stylesheetOwned: boolean): CSSProperties {\n const vars = { [MENU_POINT_X]: `${point.x}px`, [MENU_POINT_Y]: `${point.y}px` };\n if (stylesheetOwned) return vars as CSSProperties;\n return {\n ...vars,\n top: `var(${MENU_POINT_Y})`,\n right: \"auto\",\n bottom: \"auto\",\n left: `var(${MENU_POINT_X})`,\n positionArea: \"none\",\n positionTryFallbacks: MENU_POINT_FALLBACKS[point.generation % 2],\n } as CSSProperties;\n}\n\n/**\n * The outside right- and middle-click close of a top-level menu (context-menu.md 5): a\n * `contextmenu` outside the menu tree that this menu's ContextTrigger did not handle, or a\n * middle-button `pointerdown` outside it, hides the root popup at once (`\"dismiss\"` through the\n * `toggle` notification). Chromium's click-based light dismiss ignores those buttons. Returns the\n * cleanup.\n */\nfunction listenOutside(popup: HTMLElement, menu: MenuContextValue): () => void {\n const doc = popup.ownerDocument;\n const inside = (target: EventTarget | null) => target instanceof Node && popup.contains(target);\n const close = () => {\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n // Bubble phase: the region's handler ran first and marked a move.\n const onContextMenu = (event: Event) => {\n if (inside(event.target) || isHandledBy(event, menu)) return;\n close();\n };\n const onPointerDown = (event: Event) => {\n if ((event as globalThis.PointerEvent).button !== 1 || inside(event.target)) return;\n close();\n };\n doc.addEventListener(\"contextmenu\", onContextMenu);\n doc.addEventListener(\"pointerdown\", onPointerDown, true);\n return () => {\n doc.removeEventListener(\"contextmenu\", onContextMenu);\n doc.removeEventListener(\"pointerdown\", onPointerDown, true);\n };\n}\n\n/**\n * The menu itself: `<div popover=\"auto\" role=\"menu\" aria-labelledby={trigger} tabindex=\"-1\">`,\n * placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):\n * ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with\n * `experimental_focusgroup` where supported), printable characters run typeahead, ArrowLeft\n * closes a submenu. The top-level popup closes the whole menu at once when focus leaves it (Tab).\n * Style it with `:popover-open`; it emits `data-side` / `data-align` but no `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),\n * `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on\n * non-item space keeps focus on the current item), `onContextMenu` (no native menu over this\n * one), `onPointerEnter` / `onPointerLeave`, `data-side` / `data-align` unless `side` is `null`\n * or the menu is placed at a point, and `data-context` for an open from a `Menu.ContextTrigger`\n * (placed at the pointer: `--menu-point-x` / `--menu-point-y` inline, and unless `side` is\n * `null`, `left` / `top` at the point with the library's `@position-try` fallbacks, whose rules\n * it renders as a hoisted `<style>`). A top-level menu closes at once on a right- or\n * middle-click outside it.\n */\nexport function MenuPopup({\n side: sideProp,\n align = \"start\",\n keepMounted,\n experimental_focusgroup = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuPopupProps) {\n const menu = useMenuContext(\"Menu.Popup\");\n const { popover, submenu, loop } = menu;\n const sideOption = sideProp === undefined ? (submenu ? \"right\" : \"bottom\") : sideProp;\n // A ContextTrigger's open is placed at the point; nothing is anchored (context-menu.md 3.3).\n const point = menu.placement.kind === \"point\" ? menu.placement : null;\n const side = point !== null ? null : sideOption;\n\n // As Tabs.List (#43): detected only with the flag, never during SSR or hydration.\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n\n const typeahead = useRef({ text: \"\", at: 0 });\n const typingAhead = useCallback(() => {\n const { text, at } = typeahead.current;\n return text !== \"\" && Date.now() - at <= TYPEAHEAD_RESET_MS;\n }, []);\n\n // The outside right- / middle-click listeners of a top-level menu, while it is shown.\n const outside = useRef<(() => void) | null>(null);\n const stopOutside = useCallback(() => {\n outside.current?.();\n outside.current = null;\n }, []);\n useEffect(() => stopOutside, [stopOutside]);\n\n const { setElement, visible, wrapContent, reshow } = usePopoverPopup(popover, {\n part: \"Menu.Popup\",\n initialFocus: undefined,\n // Design 6.1: the first (or last) enabled item by open cause, nothing for a hover open, the\n // popup itself when there are no items.\n // A pointer open focuses without the focus ring where the engine can be told so (H1).\n resolveInitialFocus: (popup) => {\n const { cause, pointer } = menu.takeOpenCause();\n if (cause === \"none\") return null;\n const element = edgeItem(popup, cause) ?? popup;\n return pointer ? { element, focusVisible: false } : element;\n },\n keepMounted,\n transition,\n // A point open nests through the region that opened it (context-menu.md 3.1).\n source: () => (point !== null ? point.source : popover.triggerRef.current),\n onToggle: (event) => {\n if (submenu) return;\n const element = popover.popupRef.current;\n if (event.newState === \"open\" && element !== null && element.matches(\":popover-open\")) {\n outside.current ??= listenOutside(element, menu);\n } else if (event.newState === \"closed\") {\n stopOutside();\n }\n },\n });\n\n // A move (context-menu.md 4.3): the popup is shown and the placement changed since it was\n // shown (a new point, or a right-click in the region while a Trigger open is shown). Re-show\n // it so the respelled fallbacks place it afresh, the source is the region and the first item\n // is focused again; then capture the still-pressed pointer again, against the Firefox / WebKit\n // `pointerup` light dismiss. Runs after the hook's apply effect (declared after it).\n // Once the popup is not visible (hidden, exit animation over) and no open is intended or in\n // flight, a point placement goes back to anchored for the next open that is not a\n // ContextTrigger's: after a point open that was shown and closed, and after one that was\n // rejected or refused (review L1).\n const shownKey = useRef<number | \"anchor\" | null>(null);\n useLayoutEffect(() => {\n const element = popover.popupRef.current;\n if (element === null) return;\n const key = point !== null ? point.generation : \"anchor\";\n if (element.matches(\":popover-open\")) {\n if (shownKey.current !== null && shownKey.current !== key && reshow()) {\n capturePointer(element, point?.pointerId);\n }\n shownKey.current = key;\n } else {\n shownKey.current = null;\n if (!visible && point !== null && !popover.pending && !popover.intended()) {\n menu.resetPlacement(point.generation);\n }\n }\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName === \"DIALOG\") {\n console.error(\n \"Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog \" +\n \"focusing steps, which fight the menu's item focus.\",\n );\n }\n // A press inside a menu is never a long-press of a Menu.ContextTrigger around it (review M1).\n // Native, so it runs before React's root listener dispatches to the region.\n const onPointerDown = (event: Event) => claimPress(event);\n element.addEventListener(\"pointerdown\", onPointerDown);\n return () => element.removeEventListener(\"pointerdown\", onPointerDown);\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const listContext = useMemo<MenuListContextValue>(\n () => ({ menu, nativeFocusgroup, typingAhead }),\n [menu, nativeFocusgroup, typingAhead],\n );\n\n const state: MenuPopupState = {\n open: popover.committed,\n side,\n align,\n submenu,\n context: point !== null,\n };\n\n const runTypeahead = (popup: HTMLElement, current: HTMLElement | null, key: string): boolean => {\n const now = Date.now();\n const buffer = typeahead.current;\n if (buffer.text === \"\" || now - buffer.at > TYPEAHEAD_RESET_MS) buffer.text = \"\";\n if (key === \" \" && buffer.text === \"\") return false;\n buffer.text += key.toLowerCase();\n buffer.at = now;\n const items = menuItems(popup);\n if (items.length === 0) return true;\n // A repeated single character cycles through the items starting with it; a longer buffer\n // keeps the current item while it still matches (design 5.1).\n const repeated = [...buffer.text].every((c) => c === buffer.text[0]);\n const search = repeated ? buffer.text[0]! : buffer.text;\n const index = current === null ? -1 : items.indexOf(current);\n const start = repeated || index < 0 ? index + 1 : index;\n for (let i = 0; i < items.length; i++) {\n const item = items[(start + i) % items.length]!;\n if (typeaheadText(item).startsWith(search)) {\n if (item !== current) focusItem(item);\n break;\n }\n }\n return true;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || hasModifier(event)) return;\n const popup = event.currentTarget;\n const target = event.target as HTMLElement;\n const onItem = isMenuItem(target, popup);\n if (!onItem && target !== popup) return;\n const key = event.key;\n\n // ArrowLeft (ArrowRight in RTL) closes a submenu; focus returns to its SubmenuTrigger\n // through the popover hook's nested focus rule (design 6.3).\n if (submenu && key === (isRtl(popup) ? \"ArrowRight\" : \"ArrowLeft\")) {\n event.preventDefault();\n popover.requestClose(event.nativeEvent, \"activate\");\n return;\n }\n\n if (key === \"ArrowDown\" || key === \"ArrowUp\" || key === \"Home\" || key === \"End\") {\n if (!onItem) {\n // The popup itself has focus (no items at open, or focus landed on it some other way):\n // into the list from its edge (review M1).\n event.preventDefault();\n const items = menuItems(popup);\n const item = key === \"ArrowDown\" || key === \"Home\" ? items[0] : items.at(-1);\n if (item !== undefined) focusItem(item);\n return;\n }\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup) return;\n event.preventDefault();\n const items = menuItems(popup);\n const index = items.indexOf(target);\n const last = items.length - 1;\n let next: number;\n if (key === \"Home\") next = 0;\n else if (key === \"End\") next = last;\n else if (key === \"ArrowDown\") next = index >= last ? (loop ? 0 : last) : index + 1;\n else next = index <= 0 ? (loop ? last : 0) : index - 1;\n const item = items[next];\n if (item !== undefined && item !== target) focusItem(item);\n return;\n }\n\n if (key.length === 1 && runTypeahead(popup, onItem ? target : null, key)) {\n event.preventDefault();\n }\n };\n\n // Tab / Shift+Tab or any focus move out of the tree closes the whole stack at once (design\n // 2.2): submenus are DOM descendants of the top-level popup, the Trigger is outside on purpose.\n // A null `relatedTarget` (window blur, a removed element) closes nothing.\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n if (submenu) return;\n const popup = event.currentTarget;\n const next = event.relatedTarget;\n if (!(next instanceof Node) || popup.contains(next)) return;\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n\n // A press on non-item space (padding, a Separator, a GroupLabel) would focus the popup itself\n // (`tabindex=\"-1\"`) and take focus off the current item; keep it where it is (review M1, the\n // pointer-focus model of design 6.2). Focusable content of the consumer's (a text field) still\n // takes focus.\n const onMouseDown = (event: MouseEvent<HTMLElement>) => {\n if (event.button !== 0) return;\n const target = event.target as Element;\n const focusable = target.closest(FOCUSABLE_SELECTOR);\n if (focusable === null || focusable.getAttribute(\"role\") === \"menu\") event.preventDefault();\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n role: \"menu\",\n \"aria-labelledby\": menu.triggerId,\n // The popup itself takes focus as the last resort when a menu has no items.\n tabIndex: -1,\n ...(experimental_focusgroup && { focusgroup: `menu ${loop ? \"wrap\" : \"nowrap\"}` }),\n onKeyDown,\n onBlur,\n onMouseDown,\n // No native menu over the custom one. The popup is a sibling of any ContextTrigger, so this\n // event never reaches a region as a move; an outer region sees it prevented and stays quiet.\n onContextMenu: (event: MouseEvent<HTMLElement>) => {\n event.preventDefault();\n },\n ...(submenu && {\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") menu.timers.cancelClose();\n },\n onPointerLeave: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") scheduleHoverClose(menu, event.nativeEvent);\n },\n }),\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n \"data-context\": point !== null ? \"\" : undefined,\n };\n\n // The shipped `@position-try` rules (conventions 12): hoisted into <head> and deduplicated by\n // React, rendered only by a point open that does not opt out with `side={null}`.\n const pointRules =\n point !== null && sideOption !== null ? (\n <style href={MENU_POINT_HREF} precedence={FSUI_PRECEDENCE}>\n {MENU_POINT_RULES}\n </style>\n ) : null;\n\n const content = (s: MenuPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n // Not part of an enclosing Toolbar: a Menu.Trigger or ToggleGroup in here sees no Toolbar.\n return (\n <MenuListContext value={listContext}>\n {pointRules}\n <ToolbarContext value={null}>{node}</ToolbarContext>\n </MenuListContext>\n );\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins.\n style:\n point !== null\n ? (s) => ({ ...pointStyle(point, sideOption === null), ...resolveStateValue(style, s) })\n : side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: submenu ? \"flip-inline\" : POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Menu.Popup\", transition, element);\n}\n\nMenuPopup.displayName = \"MenuPopup\";\n"],"mappings":"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"}
|
|
@@ -21,6 +21,11 @@ export interface MenuRootState {
|
|
|
21
21
|
readonly pending: boolean;
|
|
22
22
|
/** This Root is a submenu (rendered inside a `Menu.Popup`). */
|
|
23
23
|
readonly submenu: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The current open (optimistic) came from a `Menu.ContextTrigger`: the menu is placed at the
|
|
26
|
+
* pointer rather than against the Trigger.
|
|
27
|
+
*/
|
|
28
|
+
readonly context: boolean;
|
|
24
29
|
}
|
|
25
30
|
export interface MenuRootProps {
|
|
26
31
|
/** Controlled open state. `undefined` means uncontrolled. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-root.d.mts","names":[],"sources":["../../src/menu/menu-root.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"menu-root.d.mts","names":[],"sources":["../../src/menu/menu-root.tsx"],"mappings":";;;;iBAsBiB,8BAA8B;;;;;;;;;;WAUpC,QAAQ;;iBAGF;;WAEN;;WAEA;;WAEA;;;;;WAKA;;iBAGM;;EAEf;;EAEA;;;;;;;;EAQA,mBAAmB,sBAAsB;;EAEzC;;EAEA;;;;;EAKA;EACA,WAAW,WAAW,WAAW;;wBAenB,WACd,MACA,aACA,kBACA,MACA,OACA,YACA,YACC,gCAAa,IAAA;yBARA"}
|
package/dist/menu/menu-root.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
|
3
3
|
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
4
4
|
import { useDelayedPair } from "../internal/use-delayed-pair.mjs";
|
|
5
5
|
import { usePopoverRoot } from "../internal/use-popover-root.mjs";
|
|
6
|
-
import { MenuContext, MenuListContext } from "./menu-context.mjs";
|
|
7
|
-
import { use, useCallback, useMemo, useRef } from "react";
|
|
6
|
+
import { ANCHOR_PLACEMENT, MenuContext, MenuListContext, MenuRegionContext } from "./menu-context.mjs";
|
|
7
|
+
import { use, useCallback, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
9
9
|
//#region src/menu/menu-root.tsx
|
|
10
10
|
/**
|
|
@@ -27,10 +27,14 @@ function MenuRoot({ open, defaultOpen, openChangeAction, loop = true, delay, clo
|
|
|
27
27
|
const parentMenu = list !== null ? list.menu : null;
|
|
28
28
|
const submenu = parentMenu !== null;
|
|
29
29
|
useDevWarning(!submenu && (delay !== void 0 || closeDelay !== void 0), "Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a Menu.Popup); a top-level menu does not open on hover.");
|
|
30
|
-
|
|
30
|
+
const inRegion = use(MenuRegionContext);
|
|
31
|
+
useDevWarning(parent !== null && !submenu && !inRegion, "Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root goes inside the parent's Menu.Popup.");
|
|
31
32
|
const timers = useDelayedPair();
|
|
32
33
|
const nextCause = useRef(null);
|
|
33
34
|
const openCause = useRef(null);
|
|
35
|
+
const [placement, setPlacement] = useState(ANCHOR_PLACEMENT);
|
|
36
|
+
const placementGeneration = useRef(0);
|
|
37
|
+
const pointDispatch = useRef(false);
|
|
34
38
|
const popover = usePopoverRoot({
|
|
35
39
|
component: "menu",
|
|
36
40
|
part: "Menu.Root",
|
|
@@ -42,8 +46,39 @@ function MenuRoot({ open, defaultOpen, openChangeAction, loop = true, delay, clo
|
|
|
42
46
|
timers.cancel();
|
|
43
47
|
if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;
|
|
44
48
|
nextCause.current = null;
|
|
49
|
+
if (next && !pointDispatch.current) setPlacement(ANCHOR_PLACEMENT);
|
|
45
50
|
}
|
|
46
51
|
});
|
|
52
|
+
const { intended, requestOpen } = popover;
|
|
53
|
+
const openAt = useCallback((point, options) => {
|
|
54
|
+
const generation = ++placementGeneration.current;
|
|
55
|
+
setPlacement({
|
|
56
|
+
kind: "point",
|
|
57
|
+
x: point.x,
|
|
58
|
+
y: point.y,
|
|
59
|
+
generation,
|
|
60
|
+
source: options.source,
|
|
61
|
+
pointerId: options.pointerId
|
|
62
|
+
});
|
|
63
|
+
const focus = {
|
|
64
|
+
cause: "first",
|
|
65
|
+
pointer: options.pointer
|
|
66
|
+
};
|
|
67
|
+
if (intended()) {
|
|
68
|
+
openCause.current = focus;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
nextCause.current = focus;
|
|
72
|
+
pointDispatch.current = true;
|
|
73
|
+
try {
|
|
74
|
+
requestOpen(options.event, "activate");
|
|
75
|
+
} finally {
|
|
76
|
+
pointDispatch.current = false;
|
|
77
|
+
}
|
|
78
|
+
}, [intended, requestOpen]);
|
|
79
|
+
const resetPlacement = useCallback((generation) => {
|
|
80
|
+
setPlacement((current) => current.kind === "point" && current.generation === generation ? ANCHOR_PLACEMENT : current);
|
|
81
|
+
}, []);
|
|
47
82
|
const setOpenCause = useCallback((cause, pointer = false) => {
|
|
48
83
|
nextCause.current = {
|
|
49
84
|
cause,
|
|
@@ -71,7 +106,10 @@ function MenuRoot({ open, defaultOpen, openChangeAction, loop = true, delay, clo
|
|
|
71
106
|
triggerId: `${popover.ids.popup}-trigger`,
|
|
72
107
|
setOpenCause,
|
|
73
108
|
takeOpenCause,
|
|
74
|
-
timers
|
|
109
|
+
timers,
|
|
110
|
+
placement,
|
|
111
|
+
openAt,
|
|
112
|
+
resetPlacement
|
|
75
113
|
};
|
|
76
114
|
return self;
|
|
77
115
|
}, [
|
|
@@ -83,12 +121,16 @@ function MenuRoot({ open, defaultOpen, openChangeAction, loop = true, delay, clo
|
|
|
83
121
|
resolvedCloseDelay,
|
|
84
122
|
setOpenCause,
|
|
85
123
|
takeOpenCause,
|
|
86
|
-
timers
|
|
124
|
+
timers,
|
|
125
|
+
placement,
|
|
126
|
+
openAt,
|
|
127
|
+
resetPlacement
|
|
87
128
|
]);
|
|
88
129
|
const state = {
|
|
89
130
|
open: popover.optimistic,
|
|
90
131
|
pending: popover.pending,
|
|
91
|
-
submenu
|
|
132
|
+
submenu,
|
|
133
|
+
context: popover.optimistic && placement.kind === "point"
|
|
92
134
|
};
|
|
93
135
|
return /* @__PURE__ */ jsx(MenuContext, {
|
|
94
136
|
value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-root.mjs","names":[],"sources":["../../src/menu/menu-root.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useDelayedPair } from \"../internal/use-delayed-pair.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { MenuContext, MenuListContext } from \"./menu-context.js\";\nimport type { MenuContextValue, MenuOpenFocus } from \"./menu-context.js\";\n\n/** Details passed as the second argument of `Menu.Root`'s `openChangeAction`. */\nexport interface MenuOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: the Trigger or SubmenuTrigger (click, Enter / Space,\n * arrow keys), an item's activation (the close after its action), ArrowLeft in a submenu, or a\n * foreign invoker.\n * `\"dismiss\"`: Escape, a click outside, Tab or focus leaving the menu, the pointer leaving a\n * hover-opened submenu, the parent menu closing (a submenu when the whole stack closes), script.\n * `\"platform\"`: the menu was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n}\n\nexport interface MenuRootState {\n /** Optimistic open state. Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An open or close of this Root is in flight (its `openChangeAction`). */\n readonly pending: boolean;\n /** This Root is a submenu (rendered inside a `Menu.Popup`). */\n readonly submenu: boolean;\n}\n\nexport interface MenuRootProps {\n /** Controlled open state. `undefined` means uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the menu opens when it resolves and the items have rendered) and for the\n * closes the user asks a part for (Trigger toggle, an item's activation, ArrowLeft in a\n * submenu); a notification for dismissals (Escape, a click outside, Tab, the parent menu\n * closing). May be a Server Function. A `setState` placed after your own `await` needs\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, MenuOpenChangeDetails>;\n /** Arrow keys wrap from the last item to the first and back. Default `true`. */\n loop?: boolean;\n /** Submenu only: milliseconds from hovering the SubmenuTrigger to the open. Default 100. */\n delay?: number;\n /**\n * Submenu only: milliseconds from the pointer leaving the SubmenuTrigger and the submenu to the\n * close. Default 150.\n */\n closeDelay?: number;\n children?: StateValue<ReactNode, MenuRootState>;\n}\n\n/**\n * Owns the ids, the open state and the context of one menu level. Renders no DOM. A `Menu.Root`\n * rendered inside a `Menu.Popup` is a **submenu**: its Trigger is a `Menu.SubmenuTrigger` (an\n * item of the parent menu) and its Popup opens beside that item.\n *\n * A menu is always in library mode (design/components/menu.md 2.1): opening and the closes a\n * part asks for are transitions (pending Trigger, items render from committed state,\n * `showPopover()` / `hidePopover()` once the change has committed); dismissals are the\n * browser's and `openChangeAction(false)` is a notification.\n */\nconst DEFAULT_FOCUS: MenuOpenFocus = { cause: \"first\", pointer: false };\n\nexport function MenuRoot({\n open,\n defaultOpen,\n openChangeAction,\n loop = true,\n delay,\n closeDelay,\n children,\n}: MenuRootProps) {\n const parent = use(MenuContext);\n const list = use(MenuListContext);\n // A Root inside a Popup's content is a submenu (design 1); the enclosing menu is the Popup's.\n const parentMenu = list !== null ? list.menu : null;\n const submenu = parentMenu !== null;\n\n useDevWarning(\n !submenu && (delay !== undefined || closeDelay !== undefined),\n \"Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a \" +\n \"Menu.Popup); a top-level menu does not open on hover.\",\n );\n useDevWarning(\n parent !== null && !submenu,\n \"Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root \" +\n \"goes inside the parent's Menu.Popup.\",\n );\n\n const timers = useDelayedPair();\n // The cause recorded by the part about to dispatch an open, and the cause of the open being\n // shown (design 14, \"Open cause\").\n const nextCause = useRef<MenuOpenFocus | null>(null);\n const openCause = useRef<MenuOpenFocus | null>(null);\n\n const popover = usePopoverRoot<MenuOpenChangeDetails>({\n component: \"menu\",\n part: \"Menu.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n onDispatch: (next) => {\n // Any dispatch cancels hover intent (walkthrough 9).\n timers.cancel();\n if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;\n nextCause.current = null;\n },\n });\n\n const setOpenCause = useCallback((cause: MenuOpenFocus[\"cause\"], pointer = false) => {\n nextCause.current = { cause, pointer };\n }, []);\n const takeOpenCause = useCallback(() => {\n const cause = openCause.current ?? DEFAULT_FOCUS;\n openCause.current = null;\n return cause;\n }, []);\n\n const resolvedDelay = delay ?? 100;\n const resolvedCloseDelay = closeDelay ?? 150;\n // `popover` is memoised by `usePopoverRoot` (#48) and the other members are stable, so this\n // changes only with the open state, pending and the props listed below.\n const value = useMemo<MenuContextValue>(() => {\n const self: MenuContextValue = {\n popover,\n submenu,\n // The top-level menu: this one, or the parent's root (design 2.2).\n get root() {\n return parentMenu?.root ?? self;\n },\n parent: parentMenu,\n loop,\n delay: resolvedDelay,\n closeDelay: resolvedCloseDelay,\n triggerId: `${popover.ids.popup}-trigger`,\n setOpenCause,\n takeOpenCause,\n timers,\n };\n return self;\n }, [\n popover,\n submenu,\n parentMenu,\n loop,\n resolvedDelay,\n resolvedCloseDelay,\n setOpenCause,\n takeOpenCause,\n timers,\n ]);\n\n const state: MenuRootState = {\n open: popover.optimistic,\n pending: popover.pending,\n submenu,\n };\n\n return <MenuContext value={value}>{resolveStateValue(children, state)}</MenuContext>;\n}\n\nMenuRoot.displayName = \"MenuRoot\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqEA,MAAM,gBAA+B;CAAE,OAAO;CAAS,SAAS;AAAM;AAEtE,SAAgB,SAAS,EACvB,MACA,aACA,kBACA,OAAO,MACP,OACA,YACA,YACgB;CAChB,MAAM,SAAS,IAAI,WAAW;CAC9B,MAAM,OAAO,IAAI,eAAe;CAEhC,MAAM,aAAa,SAAS,OAAO,KAAK,OAAO;CAC/C,MAAM,UAAU,eAAe;CAE/B,cACE,CAAC,YAAY,UAAU,KAAA,KAAa,eAAe,KAAA,IACnD,yIAEF;CACA,cACE,WAAW,QAAQ,CAAC,SACpB,gIAEF;CAEA,MAAM,SAAS,eAAe;CAG9B,MAAM,YAAY,OAA6B,IAAI;CACnD,MAAM,YAAY,OAA6B,IAAI;CAEnD,MAAM,UAAU,eAAsC;EACpD,WAAW;EACX,MAAM;EACN,MAAM;EACN;EACA;EACA;EACA,aAAa,SAAS;GAEpB,OAAO,OAAO;GACd,IAAI,MAAM,UAAU,UAAU,UAAU,WAAW;GACnD,UAAU,UAAU;EACtB;CACF,CAAC;CAED,MAAM,eAAe,aAAa,OAA+B,UAAU,UAAU;EACnF,UAAU,UAAU;GAAE;GAAO;EAAQ;CACvC,GAAG,CAAC,CAAC;CACL,MAAM,gBAAgB,kBAAkB;EACtC,MAAM,QAAQ,UAAU,WAAW;EACnC,UAAU,UAAU;EACpB,OAAO;CACT,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,SAAS;CAC/B,MAAM,qBAAqB,cAAc;CAGzC,MAAM,QAAQ,cAAgC;EAC5C,MAAM,OAAyB;GAC7B;GACA;GAEA,IAAI,OAAO;IACT,OAAO,YAAY,QAAQ;GAC7B;GACA,QAAQ;GACR;GACA,OAAO;GACP,YAAY;GACZ,WAAW,GAAG,QAAQ,IAAI,MAAM;GAChC;GACA;GACA;EACF;EACA,OAAO;CACT,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,QAAuB;EAC3B,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,OAAO,oBAAC,aAAD;EAAoB;EAAQ,UAAA,kBAAkB,UAAU,KAAK;CAAe,CAAA;AACrF;AAEA,SAAS,cAAc"}
|
|
1
|
+
{"version":3,"file":"menu-root.mjs","names":[],"sources":["../../src/menu/menu-root.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useDelayedPair } from \"../internal/use-delayed-pair.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport {\n ANCHOR_PLACEMENT,\n MenuContext,\n MenuListContext,\n MenuRegionContext,\n} from \"./menu-context.js\";\nimport type {\n MenuContextValue,\n MenuOpenAtOptions,\n MenuOpenFocus,\n MenuPlacement,\n} from \"./menu-context.js\";\n\n/** Details passed as the second argument of `Menu.Root`'s `openChangeAction`. */\nexport interface MenuOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: the Trigger or SubmenuTrigger (click, Enter / Space,\n * arrow keys), an item's activation (the close after its action), ArrowLeft in a submenu, or a\n * foreign invoker.\n * `\"dismiss\"`: Escape, a click outside, Tab or focus leaving the menu, the pointer leaving a\n * hover-opened submenu, the parent menu closing (a submenu when the whole stack closes), script.\n * `\"platform\"`: the menu was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n}\n\nexport interface MenuRootState {\n /** Optimistic open state. Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An open or close of this Root is in flight (its `openChangeAction`). */\n readonly pending: boolean;\n /** This Root is a submenu (rendered inside a `Menu.Popup`). */\n readonly submenu: boolean;\n /**\n * The current open (optimistic) came from a `Menu.ContextTrigger`: the menu is placed at the\n * pointer rather than against the Trigger.\n */\n readonly context: boolean;\n}\n\nexport interface MenuRootProps {\n /** Controlled open state. `undefined` means uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the menu opens when it resolves and the items have rendered) and for the\n * closes the user asks a part for (Trigger toggle, an item's activation, ArrowLeft in a\n * submenu); a notification for dismissals (Escape, a click outside, Tab, the parent menu\n * closing). May be a Server Function. A `setState` placed after your own `await` needs\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, MenuOpenChangeDetails>;\n /** Arrow keys wrap from the last item to the first and back. Default `true`. */\n loop?: boolean;\n /** Submenu only: milliseconds from hovering the SubmenuTrigger to the open. Default 100. */\n delay?: number;\n /**\n * Submenu only: milliseconds from the pointer leaving the SubmenuTrigger and the submenu to the\n * close. Default 150.\n */\n closeDelay?: number;\n children?: StateValue<ReactNode, MenuRootState>;\n}\n\n/**\n * Owns the ids, the open state and the context of one menu level. Renders no DOM. A `Menu.Root`\n * rendered inside a `Menu.Popup` is a **submenu**: its Trigger is a `Menu.SubmenuTrigger` (an\n * item of the parent menu) and its Popup opens beside that item.\n *\n * A menu is always in library mode (design/components/menu.md 2.1): opening and the closes a\n * part asks for are transitions (pending Trigger, items render from committed state,\n * `showPopover()` / `hidePopover()` once the change has committed); dismissals are the\n * browser's and `openChangeAction(false)` is a notification.\n */\nconst DEFAULT_FOCUS: MenuOpenFocus = { cause: \"first\", pointer: false };\n\nexport function MenuRoot({\n open,\n defaultOpen,\n openChangeAction,\n loop = true,\n delay,\n closeDelay,\n children,\n}: MenuRootProps) {\n const parent = use(MenuContext);\n const list = use(MenuListContext);\n // A Root inside a Popup's content is a submenu (design 1); the enclosing menu is the Popup's.\n const parentMenu = list !== null ? list.menu : null;\n const submenu = parentMenu !== null;\n\n useDevWarning(\n !submenu && (delay !== undefined || closeDelay !== undefined),\n \"Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a \" +\n \"Menu.Popup); a top-level menu does not open on hover.\",\n );\n const inRegion = use(MenuRegionContext);\n useDevWarning(\n parent !== null && !submenu && !inRegion,\n \"Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root \" +\n \"goes inside the parent's Menu.Popup.\",\n );\n\n const timers = useDelayedPair();\n // The cause recorded by the part about to dispatch an open, and the cause of the open being\n // shown (design 14, \"Open cause\").\n const nextCause = useRef<MenuOpenFocus | null>(null);\n const openCause = useRef<MenuOpenFocus | null>(null);\n\n // Placement of the current open (context-menu.md 3.1). Written urgently, not in a transition:\n // while closed it renders nothing visible, and a move of an open menu (4.3) answers the\n // right-click at once, where a transition would be entangled with any pending async action\n // (an item's, the open's) and the menu would stay at the old point until it settled.\n const [placement, setPlacement] = useState<MenuPlacement>(ANCHOR_PLACEMENT);\n const placementGeneration = useRef(0);\n // Set by `openAt` around its `requestOpen`: the dispatch is a point open.\n const pointDispatch = useRef(false);\n\n const popover = usePopoverRoot<MenuOpenChangeDetails>({\n component: \"menu\",\n part: \"Menu.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n onDispatch: (next) => {\n // Any dispatch cancels hover intent (walkthrough 9).\n timers.cancel();\n if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;\n nextCause.current = null;\n // Every other open (Trigger, foreign invoker, mount reconciliation) is anchored.\n if (next && !pointDispatch.current) setPlacement(ANCHOR_PLACEMENT);\n },\n });\n\n const { intended, requestOpen } = popover;\n const openAt = useCallback(\n (point: { readonly x: number; readonly y: number }, options: MenuOpenAtOptions) => {\n const generation = ++placementGeneration.current;\n setPlacement({\n kind: \"point\",\n x: point.x,\n y: point.y,\n generation,\n source: options.source,\n pointerId: options.pointerId,\n });\n const focus: MenuOpenFocus = { cause: \"first\", pointer: options.pointer };\n if (intended()) {\n // A move, or a right-click while the open is still pending: no dispatch. The re-show\n // (or the open being shown) focuses the first item by this event's modality.\n openCause.current = focus;\n return;\n }\n nextCause.current = focus;\n pointDispatch.current = true;\n try {\n requestOpen(options.event, \"activate\");\n } finally {\n pointDispatch.current = false;\n }\n },\n [intended, requestOpen],\n );\n const resetPlacement = useCallback((generation: number) => {\n setPlacement((current) =>\n current.kind === \"point\" && current.generation === generation ? ANCHOR_PLACEMENT : current,\n );\n }, []);\n\n const setOpenCause = useCallback((cause: MenuOpenFocus[\"cause\"], pointer = false) => {\n nextCause.current = { cause, pointer };\n }, []);\n const takeOpenCause = useCallback(() => {\n const cause = openCause.current ?? DEFAULT_FOCUS;\n openCause.current = null;\n return cause;\n }, []);\n\n const resolvedDelay = delay ?? 100;\n const resolvedCloseDelay = closeDelay ?? 150;\n // `popover` is memoised by `usePopoverRoot` (#48) and the other members are stable, so this\n // changes only with the open state, pending and the props listed below.\n const value = useMemo<MenuContextValue>(() => {\n const self: MenuContextValue = {\n popover,\n submenu,\n // The top-level menu: this one, or the parent's root (design 2.2).\n get root() {\n return parentMenu?.root ?? self;\n },\n parent: parentMenu,\n loop,\n delay: resolvedDelay,\n closeDelay: resolvedCloseDelay,\n triggerId: `${popover.ids.popup}-trigger`,\n setOpenCause,\n takeOpenCause,\n timers,\n placement,\n openAt,\n resetPlacement,\n };\n return self;\n }, [\n popover,\n submenu,\n parentMenu,\n loop,\n resolvedDelay,\n resolvedCloseDelay,\n setOpenCause,\n takeOpenCause,\n timers,\n placement,\n openAt,\n resetPlacement,\n ]);\n\n const state: MenuRootState = {\n open: popover.optimistic,\n pending: popover.pending,\n submenu,\n context: popover.optimistic && placement.kind === \"point\",\n };\n\n return <MenuContext value={value}>{resolveStateValue(children, state)}</MenuContext>;\n}\n\nMenuRoot.displayName = \"MenuRoot\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoFA,MAAM,gBAA+B;CAAE,OAAO;CAAS,SAAS;AAAM;AAEtE,SAAgB,SAAS,EACvB,MACA,aACA,kBACA,OAAO,MACP,OACA,YACA,YACgB;CAChB,MAAM,SAAS,IAAI,WAAW;CAC9B,MAAM,OAAO,IAAI,eAAe;CAEhC,MAAM,aAAa,SAAS,OAAO,KAAK,OAAO;CAC/C,MAAM,UAAU,eAAe;CAE/B,cACE,CAAC,YAAY,UAAU,KAAA,KAAa,eAAe,KAAA,IACnD,yIAEF;CACA,MAAM,WAAW,IAAI,iBAAiB;CACtC,cACE,WAAW,QAAQ,CAAC,WAAW,CAAC,UAChC,gIAEF;CAEA,MAAM,SAAS,eAAe;CAG9B,MAAM,YAAY,OAA6B,IAAI;CACnD,MAAM,YAAY,OAA6B,IAAI;CAMnD,MAAM,CAAC,WAAW,gBAAgB,SAAwB,gBAAgB;CAC1E,MAAM,sBAAsB,OAAO,CAAC;CAEpC,MAAM,gBAAgB,OAAO,KAAK;CAElC,MAAM,UAAU,eAAsC;EACpD,WAAW;EACX,MAAM;EACN,MAAM;EACN;EACA;EACA;EACA,aAAa,SAAS;GAEpB,OAAO,OAAO;GACd,IAAI,MAAM,UAAU,UAAU,UAAU,WAAW;GACnD,UAAU,UAAU;GAEpB,IAAI,QAAQ,CAAC,cAAc,SAAS,aAAa,gBAAgB;EACnE;CACF,CAAC;CAED,MAAM,EAAE,UAAU,gBAAgB;CAClC,MAAM,SAAS,aACZ,OAAmD,YAA+B;EACjF,MAAM,aAAa,EAAE,oBAAoB;EACzC,aAAa;GACX,MAAM;GACN,GAAG,MAAM;GACT,GAAG,MAAM;GACT;GACA,QAAQ,QAAQ;GAChB,WAAW,QAAQ;EACrB,CAAC;EACD,MAAM,QAAuB;GAAE,OAAO;GAAS,SAAS,QAAQ;EAAQ;EACxE,IAAI,SAAS,GAAG;GAGd,UAAU,UAAU;GACpB;EACF;EACA,UAAU,UAAU;EACpB,cAAc,UAAU;EACxB,IAAI;GACF,YAAY,QAAQ,OAAO,UAAU;EACvC,UAAU;GACR,cAAc,UAAU;EAC1B;CACF,GACA,CAAC,UAAU,WAAW,CACxB;CACA,MAAM,iBAAiB,aAAa,eAAuB;EACzD,cAAc,YACZ,QAAQ,SAAS,WAAW,QAAQ,eAAe,aAAa,mBAAmB,OACrF;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,eAAe,aAAa,OAA+B,UAAU,UAAU;EACnF,UAAU,UAAU;GAAE;GAAO;EAAQ;CACvC,GAAG,CAAC,CAAC;CACL,MAAM,gBAAgB,kBAAkB;EACtC,MAAM,QAAQ,UAAU,WAAW;EACnC,UAAU,UAAU;EACpB,OAAO;CACT,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,SAAS;CAC/B,MAAM,qBAAqB,cAAc;CAGzC,MAAM,QAAQ,cAAgC;EAC5C,MAAM,OAAyB;GAC7B;GACA;GAEA,IAAI,OAAO;IACT,OAAO,YAAY,QAAQ;GAC7B;GACA,QAAQ;GACR;GACA,OAAO;GACP,YAAY;GACZ,WAAW,GAAG,QAAQ,IAAI,MAAM;GAChC;GACA;GACA;GACA;GACA;GACA;EACF;EACA,OAAO;CACT,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,QAAuB;EAC3B,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;EACA,SAAS,QAAQ,cAAc,UAAU,SAAS;CACpD;CAEA,OAAO,oBAAC,aAAD;EAAoB;EAAQ,UAAA,kBAAkB,UAAU,KAAK;CAAe,CAAA;AACrF;AAEA,SAAS,cAAc"}
|
|
@@ -10,7 +10,10 @@ export interface MenuTriggerState {
|
|
|
10
10
|
readonly disabled: boolean;
|
|
11
11
|
}
|
|
12
12
|
export interface MenuTriggerProps extends Omit<ComponentPropsWithRef<"button">, "onClick" | "onKeyDown" | "aria-expanded" | "aria-haspopup" | "className" | "style" | "children"> {
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The menu cannot be opened from this button. Native `disabled` on a `<button>`; with a link or
|
|
15
|
+
* component `render`, `aria-disabled="true"` instead (the element stays focusable).
|
|
16
|
+
*/
|
|
14
17
|
disabled?: boolean;
|
|
15
18
|
className?: StateValue<string | undefined, MenuTriggerState>;
|
|
16
19
|
style?: StateValue<CSSProperties | undefined, MenuTriggerState>;
|
|
@@ -25,8 +28,8 @@ export interface MenuTriggerProps extends Omit<ComponentPropsWithRef<"button">,
|
|
|
25
28
|
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a
|
|
26
29
|
* vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).
|
|
27
30
|
*
|
|
28
|
-
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `
|
|
29
|
-
* `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
31
|
+
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `aria-disabled`
|
|
32
|
+
* (link or component `render`), `commandfor`, `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
30
33
|
* `data-pending`, `data-disabled`.
|
|
31
34
|
*/
|
|
32
35
|
export declare function MenuTrigger({ disabled, className, style, children, render, ...rest }: MenuTriggerProps): ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-trigger.d.mts","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"mappings":";;;;iBAWiB;;WAEN;;WAEA;WACA;;iBAGM,yBAAyB,KACxC
|
|
1
|
+
{"version":3,"file":"menu-trigger.d.mts","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"mappings":";;;;iBAWiB;;WAEN;;WAEA;WACA;;iBAGM,yBAAyB,KACxC;;;;;EAOA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAwCvC,cACd,UACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAPH"}
|
|
@@ -33,8 +33,8 @@ function isPointerClick(event) {
|
|
|
33
33
|
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a
|
|
34
34
|
* vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).
|
|
35
35
|
*
|
|
36
|
-
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `
|
|
37
|
-
* `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
36
|
+
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `aria-disabled`
|
|
37
|
+
* (link or component `render`), `commandfor`, `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
38
38
|
* `data-pending`, `data-disabled`.
|
|
39
39
|
*/
|
|
40
40
|
function MenuTrigger({ disabled = false, className, style, children, render, ...rest }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-trigger.mjs","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { useToolbarContext } from \"../internal/toolbar-context.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverTrigger } from \"../internal/use-popover-trigger.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport type { MenuContextValue } from \"./menu-context.js\";\nimport { edgeItem, focusItem, hasModifier, isRtl } from \"./menu-dom.js\";\n\nexport interface MenuTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The Root's transition is pending (an open, a part-initiated close or a dismissal notification). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"aria-expanded\" | \"aria-haspopup\" | \"className\" | \"style\" | \"children\"\n> {\n
|
|
1
|
+
{"version":3,"file":"menu-trigger.mjs","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { useToolbarContext } from \"../internal/toolbar-context.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverTrigger } from \"../internal/use-popover-trigger.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport type { MenuContextValue } from \"./menu-context.js\";\nimport { edgeItem, focusItem, hasModifier, isRtl } from \"./menu-dom.js\";\n\nexport interface MenuTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The Root's transition is pending (an open, a part-initiated close or a dismissal notification). */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"aria-expanded\" | \"aria-haspopup\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * The menu cannot be opened from this button. Native `disabled` on a `<button>`; with a link or\n * component `render`, `aria-disabled=\"true\"` instead (the element stays focusable).\n */\n disabled?: boolean;\n className?: StateValue<string | undefined, MenuTriggerState>;\n style?: StateValue<CSSProperties | undefined, MenuTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, MenuTriggerState>;\n /** Should render a `<button>` (the command path before hydration). */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuTriggerState>;\n}\n\n/**\n * Opens with the first (`\"first\"`) or last (`\"last\"`) item focused, or focuses that item when the\n * menu is already open (a pre-hydration open, walkthrough 10). Shared with SubmenuTrigger.\n */\nexport function openMenuWithFocus(\n menu: MenuContextValue,\n event: Event,\n which: \"first\" | \"last\",\n): void {\n const { popover } = menu;\n if (popover.intended()) {\n const popup = popover.popupRef.current;\n if (popup !== null && popup.matches(\":popover-open\")) {\n const item = edgeItem(popup, which);\n if (item !== null) focusItem(item);\n }\n return;\n }\n menu.setOpenCause(which, isPointerClick(event));\n popover.requestOpen(event, \"activate\");\n}\n\n/** A click from a pointing device (`detail` counts clicks; keyboard and `click()` give 0). */\nexport function isPointerClick(event: Event): boolean {\n return event.type === \"click\" && (event as MouseEvent).detail > 0;\n}\n\n/**\n * The menu button: `<button type=\"button\" commandfor command=\"toggle-popover\"\n * aria-haspopup=\"menu\" aria-expanded>`. A click (Enter, Space) toggles the menu through the\n * Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a\n * vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `aria-disabled`\n * (link or component `render`), `commandfor`, `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,\n * `data-pending`, `data-disabled`.\n */\nexport function MenuTrigger({\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuTriggerProps) {\n const menu = useMenuContext(\"Menu.Trigger\");\n useDevWarning(\n menu.submenu,\n \"Menu.Trigger: rendered in a submenu. Use Menu.SubmenuTrigger, which is also an item of the \" +\n \"parent menu.\",\n );\n\n // Inside a vertical Toolbar, ArrowDown / ArrowUp are the Toolbar's: the trigger opens on the\n // inline-end arrow instead, as a submenu trigger does (design/components/toggle-group.md 7.4).\n const toolbar = useToolbarContext();\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (disabled || hasModifier(event)) return;\n let which: \"first\" | \"last\";\n if (toolbar?.orientation === \"vertical\") {\n const openKey = isRtl(event.currentTarget) ? \"ArrowLeft\" : \"ArrowRight\";\n if (event.key !== openKey) return;\n which = \"first\";\n } else {\n if (event.key !== \"ArrowDown\" && event.key !== \"ArrowUp\") return;\n which = event.key === \"ArrowDown\" ? \"first\" : \"last\";\n }\n // No page scroll, and an enclosing Toolbar sees `defaultPrevented`; Enter and Space stay the\n // native click.\n event.preventDefault();\n openMenuWithFocus(menu, event.nativeEvent, which);\n };\n\n const { state, libraryProps, ref } = usePopoverTrigger(menu.popover, {\n part: \"Menu.Trigger\",\n disabled,\n render,\n handlers: { onKeyDown },\n activate: (event) => {\n const native = event.nativeEvent;\n if (menu.popover.intended()) {\n menu.popover.requestClose(native, \"activate\");\n } else {\n menu.setOpenCause(\"first\", isPointerClick(native));\n menu.popover.requestOpen(native, \"activate\");\n }\n },\n });\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...libraryProps,\n id: menu.triggerId,\n \"aria-haspopup\": \"menu\",\n },\n ref,\n });\n}\n\nMenuTrigger.displayName = \"MenuTrigger\";\n"],"mappings":";;;;;;;;;;;;AAwCA,SAAgB,kBACd,MACA,OACA,OACM;CACN,MAAM,EAAE,YAAY;CACpB,IAAI,QAAQ,SAAS,GAAG;EACtB,MAAM,QAAQ,QAAQ,SAAS;EAC/B,IAAI,UAAU,QAAQ,MAAM,QAAQ,eAAe,GAAG;GACpD,MAAM,OAAO,SAAS,OAAO,KAAK;GAClC,IAAI,SAAS,MAAM,UAAU,IAAI;EACnC;EACA;CACF;CACA,KAAK,aAAa,OAAO,eAAe,KAAK,CAAC;CAC9C,QAAQ,YAAY,OAAO,UAAU;AACvC;;AAGA,SAAgB,eAAe,OAAuB;CACpD,OAAO,MAAM,SAAS,WAAY,MAAqB,SAAS;AAClE;;;;;;;;;;;AAYA,SAAgB,YAAY,EAC1B,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,OAAO,eAAe,cAAc;CAC1C,cACE,KAAK,SACL,yGAEF;CAIA,MAAM,UAAU,kBAAkB;CAClC,MAAM,aAAa,UAA4C;EAC7D,IAAI,YAAY,YAAY,KAAK,GAAG;EACpC,IAAI;EACJ,IAAI,SAAS,gBAAgB,YAAY;GACvC,MAAM,UAAU,MAAM,MAAM,aAAa,IAAI,cAAc;GAC3D,IAAI,MAAM,QAAQ,SAAS;GAC3B,QAAQ;EACV,OAAO;GACL,IAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,WAAW;GAC1D,QAAQ,MAAM,QAAQ,cAAc,UAAU;EAChD;EAGA,MAAM,eAAe;EACrB,kBAAkB,MAAM,MAAM,aAAa,KAAK;CAClD;CAEA,MAAM,EAAE,OAAO,cAAc,QAAQ,kBAAkB,KAAK,SAAS;EACnE,MAAM;EACN;EACA;EACA,UAAU,EAAE,UAAU;EACtB,WAAW,UAAU;GACnB,MAAM,SAAS,MAAM;GACrB,IAAI,KAAK,QAAQ,SAAS,GACxB,KAAK,QAAQ,aAAa,QAAQ,UAAU;QACvC;IACL,KAAK,aAAa,SAAS,eAAe,MAAM,CAAC;IACjD,KAAK,QAAQ,YAAY,QAAQ,UAAU;GAC7C;EACF;CACF,CAAC;CAED,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAG;GACH,IAAI,KAAK;GACT,iBAAiB;EACnB;EACA;CACF,CAAC;AACH;AAEA,YAAY,cAAc"}
|
|
@@ -3,10 +3,11 @@ import { NumberFieldDecrementProps } from "./number-field-increment.mjs";
|
|
|
3
3
|
/**
|
|
4
4
|
* Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and
|
|
5
5
|
* the default `aria-label` `"Decrease"`; one step at the press, then repeated while held, one
|
|
6
|
-
* commit at the release. Natively `disabled` when the next step would change nothing
|
|
6
|
+
* commit at the release. Natively `disabled` when the next step would change nothing
|
|
7
|
+
* (`aria-disabled` with a link or component `render`).
|
|
7
8
|
*
|
|
8
|
-
* Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `
|
|
9
|
-
* `onContextMenu`, `data-pending`, `data-disabled`.
|
|
9
|
+
* Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `aria-disabled` (link or component
|
|
10
|
+
* `render`), `onPointerDown`, `onClick`, `onContextMenu`, `data-pending`, `data-disabled`.
|
|
10
11
|
*/
|
|
11
12
|
export declare function NumberFieldDecrement(props: NumberFieldDecrementProps): import("react").ReactNode;
|
|
12
13
|
export declare namespace NumberFieldDecrement {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-field-decrement.d.mts","names":[],"sources":["../../src/number-field/number-field-decrement.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"number-field-decrement.d.mts","names":[],"sources":["../../src/number-field/number-field-decrement.tsx"],"mappings":";;;;;;;;;;;wBAegB,qBAAqB,OAAO,4CAAyB;yBAArD"}
|
|
@@ -4,10 +4,11 @@ import { useStepButtonElement } from "./number-field-increment.mjs";
|
|
|
4
4
|
/**
|
|
5
5
|
* Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and
|
|
6
6
|
* the default `aria-label` `"Decrease"`; one step at the press, then repeated while held, one
|
|
7
|
-
* commit at the release. Natively `disabled` when the next step would change nothing
|
|
7
|
+
* commit at the release. Natively `disabled` when the next step would change nothing
|
|
8
|
+
* (`aria-disabled` with a link or component `render`).
|
|
8
9
|
*
|
|
9
|
-
* Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `
|
|
10
|
-
* `onContextMenu`, `data-pending`, `data-disabled`.
|
|
10
|
+
* Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `aria-disabled` (link or component
|
|
11
|
+
* `render`), `onPointerDown`, `onClick`, `onContextMenu`, `data-pending`, `data-disabled`.
|
|
11
12
|
*/
|
|
12
13
|
function NumberFieldDecrement(props) {
|
|
13
14
|
return useStepButtonElement("decrement", "NumberField.Decrement", props);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-field-decrement.mjs","names":[],"sources":["../../src/number-field/number-field-decrement.tsx"],"sourcesContent":["\"use client\";\nimport { useStepButtonElement } from \"./number-field-increment.js\";\nimport type { NumberFieldDecrementProps } from \"./number-field-increment.js\";\n\nexport type { NumberFieldDecrementProps } from \"./number-field-increment.js\";\n\n/**\n * Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and\n * the default `aria-label` `\"Decrease\"`; one step at the press, then repeated while held, one\n * commit at the release. Natively `disabled` when the next step would change nothing.\n *\n * Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `onPointerDown`, `onClick
|
|
1
|
+
{"version":3,"file":"number-field-decrement.mjs","names":[],"sources":["../../src/number-field/number-field-decrement.tsx"],"sourcesContent":["\"use client\";\nimport { useStepButtonElement } from \"./number-field-increment.js\";\nimport type { NumberFieldDecrementProps } from \"./number-field-increment.js\";\n\nexport type { NumberFieldDecrementProps } from \"./number-field-increment.js\";\n\n/**\n * Steps the value down: `button[type=button][tabindex=-1]` with `aria-controls` → the input and\n * the default `aria-label` `\"Decrease\"`; one step at the press, then repeated while held, one\n * commit at the release. Natively `disabled` when the next step would change nothing\n * (`aria-disabled` with a link or component `render`).\n *\n * Owned keys: `type`, `tabIndex`, `aria-controls`, `disabled`, `aria-disabled` (link or component\n * `render`), `onPointerDown`, `onClick`, `onContextMenu`, `data-pending`, `data-disabled`.\n */\nexport function NumberFieldDecrement(props: NumberFieldDecrementProps) {\n return useStepButtonElement(\"decrement\", \"NumberField.Decrement\", props);\n}\n\nNumberFieldDecrement.displayName = \"NumberFieldDecrement\";\n"],"mappings":";;;;;;;;;;;;AAeA,SAAgB,qBAAqB,OAAkC;CACrE,OAAO,qBAAqB,aAAa,yBAAyB,KAAK;AACzE;AAEA,qBAAqB,cAAc"}
|