@tamagui/create-menu 2.7.7 → 3.0.0-beta.637.1

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 (113) hide show
  1. package/dist/cjs/createBaseMenu.cjs +1033 -1247
  2. package/dist/cjs/createBaseMenu.native.cjs +1217 -0
  3. package/dist/cjs/createBaseMenu.native.js +1155 -1387
  4. package/dist/cjs/createBaseMenu.native.js.map +1 -1
  5. package/dist/cjs/createNativeMenu/createNativeMenu.cjs +346 -377
  6. package/dist/cjs/createNativeMenu/createNativeMenu.native.cjs +439 -0
  7. package/dist/cjs/createNativeMenu/createNativeMenu.native.js +405 -439
  8. package/dist/cjs/createNativeMenu/createNativeMenu.native.js.map +1 -1
  9. package/dist/cjs/createNativeMenu/createNativeMenuTypes.cjs +10 -11
  10. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.cjs +19 -0
  11. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js +10 -10
  12. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js.map +1 -1
  13. package/dist/cjs/createNativeMenu/utils.cjs +76 -86
  14. package/dist/cjs/createNativeMenu/utils.native.cjs +107 -0
  15. package/dist/cjs/createNativeMenu/utils.native.js +82 -92
  16. package/dist/cjs/createNativeMenu/utils.native.js.map +1 -1
  17. package/dist/cjs/createNativeMenu/withNativeMenu.cjs +27 -35
  18. package/dist/cjs/createNativeMenu/withNativeMenu.native.cjs +42 -0
  19. package/dist/cjs/createNativeMenu/withNativeMenu.native.js +27 -34
  20. package/dist/cjs/createNativeMenu/withNativeMenu.native.js.map +1 -1
  21. package/dist/cjs/index.cjs +9 -20
  22. package/dist/cjs/index.native.cjs +23 -0
  23. package/dist/cjs/index.native.js +10 -20
  24. package/dist/cjs/index.native.js.map +1 -1
  25. package/dist/esm/createBaseMenu.mjs +1021 -1225
  26. package/dist/esm/createBaseMenu.mjs.map +1 -1
  27. package/dist/esm/createBaseMenu.native.js +1144 -1367
  28. package/dist/esm/createBaseMenu.native.js.map +1 -1
  29. package/dist/esm/createNativeMenu/createNativeMenu.mjs +332 -353
  30. package/dist/esm/createNativeMenu/createNativeMenu.mjs.map +1 -1
  31. package/dist/esm/createNativeMenu/createNativeMenu.native.js +392 -417
  32. package/dist/esm/createNativeMenu/createNativeMenu.native.js.map +1 -1
  33. package/dist/esm/createNativeMenu/createNativeMenuTypes.mjs +0 -2
  34. package/dist/esm/createNativeMenu/createNativeMenuTypes.native.js +0 -2
  35. package/dist/esm/createNativeMenu/utils.mjs +55 -57
  36. package/dist/esm/createNativeMenu/utils.mjs.map +1 -1
  37. package/dist/esm/createNativeMenu/utils.native.js +62 -65
  38. package/dist/esm/createNativeMenu/utils.native.js.map +1 -1
  39. package/dist/esm/createNativeMenu/withNativeMenu.mjs +14 -17
  40. package/dist/esm/createNativeMenu/withNativeMenu.mjs.map +1 -1
  41. package/dist/esm/createNativeMenu/withNativeMenu.native.js +15 -18
  42. package/dist/esm/createNativeMenu/withNativeMenu.native.js.map +1 -1
  43. package/dist/esm/index.js +5 -6
  44. package/dist/esm/index.mjs +5 -6
  45. package/dist/esm/index.native.js +5 -6
  46. package/dist/jsx/createBaseMenu.mjs +1021 -1225
  47. package/dist/jsx/createBaseMenu.mjs.map +1 -1
  48. package/dist/jsx/createBaseMenu.native.cjs +1217 -0
  49. package/dist/jsx/createBaseMenu.native.js +1155 -1387
  50. package/dist/jsx/createBaseMenu.native.js.map +1 -1
  51. package/dist/jsx/createNativeMenu/createNativeMenu.mjs +332 -353
  52. package/dist/jsx/createNativeMenu/createNativeMenu.mjs.map +1 -1
  53. package/dist/jsx/createNativeMenu/createNativeMenu.native.cjs +439 -0
  54. package/dist/jsx/createNativeMenu/createNativeMenu.native.js +405 -439
  55. package/dist/jsx/createNativeMenu/createNativeMenu.native.js.map +1 -1
  56. package/dist/jsx/createNativeMenu/createNativeMenuTypes.mjs +0 -2
  57. package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.cjs +19 -0
  58. package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js +10 -10
  59. package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js.map +1 -1
  60. package/dist/jsx/createNativeMenu/utils.mjs +55 -57
  61. package/dist/jsx/createNativeMenu/utils.mjs.map +1 -1
  62. package/dist/jsx/createNativeMenu/utils.native.cjs +107 -0
  63. package/dist/jsx/createNativeMenu/utils.native.js +82 -92
  64. package/dist/jsx/createNativeMenu/utils.native.js.map +1 -1
  65. package/dist/jsx/createNativeMenu/withNativeMenu.mjs +14 -17
  66. package/dist/jsx/createNativeMenu/withNativeMenu.mjs.map +1 -1
  67. package/dist/jsx/createNativeMenu/withNativeMenu.native.cjs +42 -0
  68. package/dist/jsx/createNativeMenu/withNativeMenu.native.js +27 -34
  69. package/dist/jsx/createNativeMenu/withNativeMenu.native.js.map +1 -1
  70. package/dist/jsx/index.js +5 -6
  71. package/dist/jsx/index.mjs +5 -6
  72. package/dist/jsx/index.native.cjs +23 -0
  73. package/dist/jsx/index.native.js +10 -20
  74. package/dist/jsx/index.native.js.map +1 -1
  75. package/package.json +23 -22
  76. package/src/createBaseMenu.tsx +211 -231
  77. package/src/createNativeMenu/createNativeMenuTypes.ts +0 -6
  78. package/src/createNativeMenu/utils.tsx +6 -6
  79. package/src/createNativeMenu/withNativeMenu.tsx +1 -2
  80. package/src/index.tsx +0 -1
  81. package/types/createBaseMenu.d.ts +38 -126
  82. package/types/createBaseMenu.d.ts.map +1 -1
  83. package/types/createNativeMenu/createNativeMenu.d.ts +1 -1
  84. package/types/createNativeMenu/createNativeMenu.d.ts.map +1 -1
  85. package/types/createNativeMenu/createNativeMenuTypes.d.ts +0 -6
  86. package/types/createNativeMenu/createNativeMenuTypes.d.ts.map +1 -1
  87. package/types/createNativeMenu/utils.d.ts +5 -5
  88. package/types/createNativeMenu/utils.d.ts.map +1 -1
  89. package/types/createNativeMenu/withNativeMenu.d.ts.map +1 -1
  90. package/types/index.d.ts +0 -1
  91. package/types/index.d.ts.map +1 -1
  92. package/dist/cjs/MenuPredefined.cjs +0 -191
  93. package/dist/cjs/MenuPredefined.native.js +0 -194
  94. package/dist/cjs/MenuPredefined.native.js.map +0 -1
  95. package/dist/esm/MenuPredefined.mjs +0 -166
  96. package/dist/esm/MenuPredefined.mjs.map +0 -1
  97. package/dist/esm/MenuPredefined.native.js +0 -166
  98. package/dist/esm/MenuPredefined.native.js.map +0 -1
  99. package/dist/esm/createNativeMenu/createNativeMenuTypes.mjs.map +0 -1
  100. package/dist/esm/createNativeMenu/createNativeMenuTypes.native.js.map +0 -1
  101. package/dist/esm/index.js.map +0 -1
  102. package/dist/esm/index.mjs.map +0 -1
  103. package/dist/esm/index.native.js.map +0 -1
  104. package/dist/jsx/MenuPredefined.mjs +0 -166
  105. package/dist/jsx/MenuPredefined.mjs.map +0 -1
  106. package/dist/jsx/MenuPredefined.native.js +0 -194
  107. package/dist/jsx/MenuPredefined.native.js.map +0 -1
  108. package/dist/jsx/createNativeMenu/createNativeMenuTypes.mjs.map +0 -1
  109. package/dist/jsx/index.js.map +0 -1
  110. package/dist/jsx/index.mjs.map +0 -1
  111. package/src/MenuPredefined.tsx +0 -204
  112. package/types/MenuPredefined.d.ts +0 -31
  113. package/types/MenuPredefined.d.ts.map +0 -1
@@ -1,3 +1,4 @@
1
+
1
2
  var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -5,43 +6,36 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
5
6
  var __getProtoOf = Object.getPrototypeOf;
6
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
8
  var __export = (target, all) => {
8
- for (var name in all) __defProp(target, name, {
9
- get: all[name],
10
- enumerable: true
11
- });
9
+ for (var name in all) __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
12
13
  };
13
14
  var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") {
15
- for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
- get: () => from[key],
17
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
- });
19
- }
20
- return to;
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
17
+ get: () => from[key],
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ return to;
21
22
  };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
- // If the importer is in node compatibility mode or this is not an ESM
24
- // file that has been converted to a CommonJS file using a Babel-
25
- // compatible transform (i.e. "__esModule" has not been set), then set
26
- // "default" to the CommonJS "module.exports" for node compatibility.
27
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
28
- value: mod,
29
- enumerable: true
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
30
26
  }) : target, mod));
31
- var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
32
- value: true
33
- }), mod);
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
28
  var createBaseMenu_exports = {};
35
- __export(createBaseMenu_exports, {
36
- createBaseMenu: () => createBaseMenu
37
- });
29
+ __export(createBaseMenu_exports, { createBaseMenu: () => createBaseMenu });
38
30
  module.exports = __toCommonJS(createBaseMenu_exports);
31
+ var import_core = require("@tamagui/core");
39
32
  var import_animate = require("@tamagui/animate");
40
33
  var import_animate_presence = require("@tamagui/animate-presence");
41
34
  var import_collection = require("@tamagui/collection");
42
35
  var import_dismissable = require("@tamagui/dismissable");
43
36
  var import_focus_guard = require("@tamagui/focus-guard");
44
37
  var import_focus_scope = require("@tamagui/focus-scope");
38
+ var import_image = require("@tamagui/image");
45
39
  var PopperPrimitive = __toESM(require("@tamagui/popper"), 1);
46
40
  var import_portal = require("@tamagui/portal");
47
41
  var import_remove_scroll = require("@tamagui/remove-scroll");
@@ -51,1253 +45,1045 @@ var import_use_direction = require("@tamagui/use-direction");
51
45
  var import_web = require("@tamagui/web");
52
46
  var React = __toESM(require("react"), 1);
53
47
  var import_react = require("react");
54
- var import_MenuPredefined = require("./MenuPredefined.cjs");
55
48
  var import_jsx_runtime = require("react/jsx-runtime");
56
49
  function whenMouse(handler) {
57
- return event => event.pointerType === "mouse" ? handler(event) : void 0;
50
+ return (event) => event.pointerType === "mouse" ? handler(event) : void 0;
58
51
  }
59
52
  const SELECTION_KEYS = ["Enter", " "];
60
- const FIRST_KEYS = ["ArrowDown", "PageUp", "Home"];
61
- const LAST_KEYS = ["ArrowUp", "PageDown", "End"];
53
+ const FIRST_KEYS = [
54
+ "ArrowDown",
55
+ "PageUp",
56
+ "Home"
57
+ ];
58
+ const LAST_KEYS = [
59
+ "ArrowUp",
60
+ "PageDown",
61
+ "End"
62
+ ];
62
63
  const FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS];
63
64
  const SUB_OPEN_KEYS = {
64
- ltr: [...SELECTION_KEYS, "ArrowRight"],
65
- rtl: [...SELECTION_KEYS, "ArrowLeft"]
65
+ ltr: [...SELECTION_KEYS, "ArrowRight"],
66
+ rtl: [...SELECTION_KEYS, "ArrowLeft"]
66
67
  };
67
68
  const SUB_CLOSE_KEYS = {
68
- ltr: ["ArrowLeft"],
69
- rtl: ["ArrowRight"]
69
+ ltr: ["ArrowLeft"],
70
+ rtl: ["ArrowRight"]
70
71
  };
71
72
  const MENU_NAME = "Menu";
72
73
  const [Collection, useCollection] = (0, import_collection.createCollection)(MENU_NAME);
73
- const {
74
- Provider: MenuProvider,
75
- useStyledContext: useMenuContext
76
- } = (0, import_web.createStyledContext)();
77
- const {
78
- Provider: MenuRootProvider,
79
- useStyledContext: useMenuRootContext
80
- } = (0, import_web.createStyledContext)();
74
+ const { Provider: MenuProvider, useStyledContext: useMenuContext } = (0, import_web.createStyledContext)();
75
+ const { Provider: MenuRootProvider, useStyledContext: useMenuRootContext } = (0, import_web.createStyledContext)();
81
76
  const MENU_CONTEXT = "MenuContext";
82
- function createBaseMenu({
83
- Item: _Item = import_MenuPredefined.MenuPredefined.MenuItem,
84
- Title: _Title = import_MenuPredefined.MenuPredefined.Title,
85
- SubTitle: _SubTitle = import_MenuPredefined.MenuPredefined.SubTitle,
86
- Image: _Image = import_MenuPredefined.MenuPredefined.MenuImage,
87
- Icon: _Icon = import_MenuPredefined.MenuPredefined.MenuIcon,
88
- Indicator: _Indicator = import_MenuPredefined.MenuPredefined.MenuIndicator,
89
- Separator: _Separator = import_MenuPredefined.MenuPredefined.MenuSeparator,
90
- MenuGroup: _MenuGroup = import_MenuPredefined.MenuPredefined.MenuGroup,
91
- Label: _Label = import_MenuPredefined.MenuPredefined.MenuLabel
92
- }) {
93
- const MenuComp = props => {
94
- const direction = (0, import_use_direction.useDirection)(props.dir);
95
- const defaultPlacement = direction === "rtl" ? "bottom-end" : "bottom-start";
96
- const {
97
- scope = MENU_CONTEXT,
98
- open = false,
99
- children,
100
- dir,
101
- onOpenChange,
102
- modal = true,
103
- allowFlip = {
104
- padding: 10
105
- },
106
- stayInFrame = {
107
- padding: 10
108
- },
109
- placement = defaultPlacement,
110
- resize = true,
111
- offset = 10,
112
- ...rest
113
- } = props;
114
- const [content, setContent] = React.useState(null);
115
- const isUsingKeyboardRef = React.useRef(false);
116
- const handleOpenChange = (0, import_use_callback_ref.useCallbackRef)(onOpenChange);
117
- if (import_web.isWeb) {
118
- React.useEffect(() => {
119
- const handleKeyDown = () => {
120
- isUsingKeyboardRef.current = true;
121
- document.addEventListener("pointerdown", handlePointer, {
122
- capture: true,
123
- once: true
124
- });
125
- document.addEventListener("pointermove", handlePointer, {
126
- capture: true,
127
- once: true
128
- });
129
- };
130
- const handlePointer = () => isUsingKeyboardRef.current = false;
131
- document.addEventListener("keydown", handleKeyDown, {
132
- capture: true
133
- });
134
- return () => {
135
- document.removeEventListener("keydown", handleKeyDown, {
136
- capture: true
137
- });
138
- document.removeEventListener("pointerdown", handlePointer, {
139
- capture: true
140
- });
141
- document.removeEventListener("pointermove", handlePointer, {
142
- capture: true
143
- });
144
- };
145
- }, []);
146
- }
147
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(PopperPrimitive.Popper, {
148
- scope,
149
- open,
150
- placement,
151
- allowFlip,
152
- stayInFrame,
153
- resize,
154
- offset,
155
- ...rest,
156
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuProvider, {
157
- scope,
158
- open,
159
- onOpenChange: handleOpenChange,
160
- content,
161
- onContentChange: setContent,
162
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuRootProvider, {
163
- scope,
164
- open,
165
- onClose: React.useCallback(() => handleOpenChange(false), [handleOpenChange]),
166
- isUsingKeyboardRef,
167
- dir: direction,
168
- modal,
169
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuSubProvider, {
170
- scope,
171
- children
172
- })
173
- })
174
- })
175
- });
176
- };
177
- const RepropagateMenuAndMenuRootProvider = props => {
178
- const {
179
- scope = MENU_CONTEXT,
180
- menuContext,
181
- rootContext,
182
- popperContext,
183
- menuSubContext,
184
- children
185
- } = props;
186
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(PopperPrimitive.PopperProvider, {
187
- ...popperContext,
188
- scope,
189
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuProvider, {
190
- scope,
191
- ...menuContext,
192
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuRootProvider, {
193
- scope,
194
- ...rootContext,
195
- children: menuSubContext ? /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuSubProvider, {
196
- scope,
197
- ...menuSubContext,
198
- children
199
- }) : children
200
- })
201
- })
202
- });
203
- };
204
- MenuComp.displayName = MENU_NAME;
205
- const ANCHOR_NAME = "MenuAnchor";
206
- const MenuAnchor = props => {
207
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(PopperPrimitive.PopperAnchor, {
208
- scope: MENU_CONTEXT,
209
- ...props
210
- });
211
- };
212
- MenuAnchor.displayName = ANCHOR_NAME;
213
- const PORTAL_NAME = "MenuPortal";
214
- const {
215
- Provider: PortalProvider,
216
- useStyledContext: usePortalContext
217
- } = (0, import_web.createStyledContext)(void 0, "Portal");
218
- const MenuPortal = props => {
219
- const {
220
- scope = MENU_CONTEXT,
221
- forceMount,
222
- zIndex,
223
- children
224
- } = props;
225
- const menuContext = useMenuContext(scope);
226
- const rootContext = useMenuRootContext(scope);
227
- const popperContext = PopperPrimitive.usePopperContext(scope);
228
- const menuSubContext = useMenuSubContext(scope);
229
- const themeName = (0, import_web.useThemeName)();
230
- const themedChildren = /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_web.Theme, {
231
- forceClassName: true,
232
- name: themeName,
233
- children
234
- });
235
- const content = (0, import_portal.needsPortalRepropagation)() ? /* @__PURE__ */(0, import_jsx_runtime.jsx)(RepropagateMenuAndMenuRootProvider, {
236
- menuContext,
237
- rootContext,
238
- popperContext,
239
- menuSubContext,
240
- scope,
241
- children: themedChildren
242
- }) : themedChildren;
243
- const isPresent = forceMount || rootContext.open && menuContext.open;
244
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_animate.Animate, {
245
- type: "presence",
246
- present: isPresent,
247
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_portal.Portal, {
248
- stackZIndex: true,
249
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {
250
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(PortalProvider, {
251
- scope,
252
- forceMount,
253
- children: /* @__PURE__ */(0, import_jsx_runtime.jsxs)(import_web.View, {
254
- zIndex: zIndex || 100,
255
- inset: 0,
256
- position: "absolute",
257
- children: [!!menuContext.open && !import_web.isWeb && /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_web.View, {
258
- inset: 0,
259
- position: "absolute",
260
- onPress: () => menuContext.onOpenChange(!menuContext.open)
261
- }), content]
262
- })
263
- })
264
- })
265
- })
266
- });
267
- };
268
- MenuPortal.displayName = PORTAL_NAME;
269
- const CONTENT_NAME = "MenuContent";
270
- const {
271
- Provider: MenuContentProvider,
272
- useStyledContext: useMenuContentContext
273
- } = (0, import_web.createStyledContext)();
274
- const MenuContentFrame = (0, import_web.styled)(PopperPrimitive.PopperContentFrame, {
275
- name: CONTENT_NAME
276
- });
277
- const MenuContent = MenuContentFrame.styleable((props, forwardedRef) => {
278
- const scope = props.scope || MENU_CONTEXT;
279
- const portalContext = usePortalContext(scope);
280
- const {
281
- forceMount = portalContext.forceMount,
282
- ...contentProps
283
- } = props;
284
- const rootContext = useMenuRootContext(scope);
285
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(Collection.Provider, {
286
- scope,
287
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(Collection.Slot, {
288
- scope,
289
- children: rootContext.modal ? /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuRootContentModal, {
290
- ...contentProps,
291
- ref: forwardedRef
292
- }) : /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuRootContentNonModal, {
293
- ...contentProps,
294
- ref: forwardedRef
295
- })
296
- })
297
- });
298
- });
299
- const MenuRootContentModal = React.forwardRef((props, forwardedRef) => {
300
- const scope = props.scope || MENU_CONTEXT;
301
- const context = useMenuContext(scope);
302
- const ref = React.useRef(null);
303
- const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
304
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuContentImpl, {
305
- ...props,
306
- scope,
307
- ref: composedRefs,
308
- trapFocus: context.open,
309
- disableOutsidePointerEvents: context.open,
310
- disableOutsideScroll: false,
311
- onFocusOutside: (0, import_web.composeEventHandlers)(props.onFocusOutside, event => event.preventDefault(), {
312
- checkDefaultPrevented: false
313
- }),
314
- onDismiss: () => context.onOpenChange(false)
315
- });
316
- });
317
- const MenuRootContentNonModal = React.forwardRef((props, forwardedRef) => {
318
- const scope = props.scope || MENU_CONTEXT;
319
- const context = useMenuContext(scope);
320
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuContentImpl, {
321
- ...props,
322
- scope,
323
- ref: forwardedRef,
324
- trapFocus: false,
325
- disableOutsidePointerEvents: false,
326
- disableOutsideScroll: false,
327
- onDismiss: () => context.onOpenChange(false)
328
- });
329
- });
330
- const MenuContentImpl = React.forwardRef((props, forwardedRef) => {
331
- const {
332
- scope = MENU_CONTEXT,
333
- loop = false,
334
- trapFocus,
335
- onOpenAutoFocus,
336
- onCloseAutoFocus,
337
- disableOutsidePointerEvents,
338
- onEntryFocus,
339
- onEscapeKeyDown,
340
- onPointerDownOutside,
341
- onFocusOutside,
342
- onInteractOutside,
343
- onDismiss,
344
- disableOutsideScroll,
345
- disableDismissOnScroll = false,
346
- unstyled = process.env.TAMAGUI_HEADLESS === "1",
347
- ...contentProps
348
- } = props;
349
- const context = useMenuContext(scope);
350
- const rootContext = useMenuRootContext(scope);
351
- const popperContext = PopperPrimitive.usePopperContext(scope);
352
- const getItems = useCollection(scope);
353
- const [currentItemId, setCurrentItemId] = React.useState(null);
354
- const contentRef = React.useRef(null);
355
- const focusableContentRef = React.useRef(null);
356
- const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, contentRef, context.onContentChange);
357
- const timerRef = React.useRef(0);
358
- const searchRef = React.useRef("");
359
- const pointerGraceTimerRef = React.useRef(0);
360
- const pointerGraceIntentRef = React.useRef(null);
361
- const pointerDirRef = React.useRef("right");
362
- const lastPointerXRef = React.useRef(0);
363
- const handleTypeaheadSearch = key => {
364
- const search = searchRef.current + key;
365
- const items = getItems().filter(item => !item.disabled);
366
- const currentItem = document.activeElement;
367
- const currentMatch = items.find(item => item.ref.current === currentItem)?.textValue;
368
- const values = items.map(item => item.textValue);
369
- const nextMatch = getNextMatch(values, search, currentMatch);
370
- const newItem = items.find(item => item.textValue === nextMatch)?.ref.current;
371
- (function updateSearch(value) {
372
- searchRef.current = value;
373
- clearTimeout(timerRef.current);
374
- if (value !== "") timerRef.current = setTimeout(() => updateSearch(""), 1e3);
375
- })(search);
376
- if (newItem) {
377
- setTimeout(() => newItem.focus());
378
- }
379
- };
380
- React.useEffect(() => {
381
- return () => clearTimeout(timerRef.current);
382
- }, []);
383
- React.useEffect(() => {
384
- if (!import_web.isWeb || !context.open) return;
385
- const frame = requestAnimationFrame(() => {
386
- const container = contentRef.current;
387
- const el = container?.querySelector("[data-tamagui-menu-content]");
388
- if (el) focusableContentRef.current = el;
389
- });
390
- return () => cancelAnimationFrame(frame);
391
- }, [context.open]);
392
- React.useEffect(() => {
393
- if (!import_web.isWeb || disableDismissOnScroll || !context.open) return;
394
- const handleScroll = event => {
395
- const scrolled = event.target;
396
- if (!scrolled) return;
397
- const content2 = contentRef.current;
398
- if (content2?.contains(scrolled)) return;
399
- const reference = popperContext.refs?.reference?.current;
400
- const anchor = reference instanceof Element ? reference : reference?.contextElement ?? null;
401
- if (anchor && !scrolled.contains(anchor)) return;
402
- onDismiss?.();
403
- };
404
- window.addEventListener("scroll", handleScroll, {
405
- capture: true,
406
- passive: true
407
- });
408
- return () => {
409
- window.removeEventListener("scroll", handleScroll, {
410
- capture: true
411
- });
412
- };
413
- }, [disableDismissOnScroll, context.open, onDismiss, popperContext.refs]);
414
- if (import_web.isWeb) {
415
- (0, import_focus_guard.useFocusGuards)();
416
- }
417
- const isPointerMovingToSubmenu = React.useCallback(event => {
418
- const isMovingTowards = pointerDirRef.current === pointerGraceIntentRef.current?.side;
419
- const inArea = isPointerInGraceArea(event, pointerGraceIntentRef.current?.area);
420
- return isMovingTowards && inArea;
421
- }, []);
422
- const content = /* @__PURE__ */(0, import_jsx_runtime.jsx)(PopperPrimitive.PopperContent, {
423
- role: "menu",
424
- tabIndex: -1,
425
- unstyled,
426
- ...(!unstyled && {
427
- backgroundColor: "$background",
428
- borderWidth: 1,
429
- borderColor: "$borderColor",
430
- outlineWidth: 0,
431
- minWidth: 180
432
- }),
433
- "aria-orientation": "vertical",
434
- "data-state": getOpenState(context.open),
435
- "data-tamagui-menu-content": "",
436
- dir: rootContext.dir,
437
- scope: scope || MENU_CONTEXT,
438
- ...contentProps,
439
- ref: composedRefs,
440
- className: contentProps.transition ? void 0 : contentProps.className,
441
- ...(import_web.isWeb ? {
442
- onKeyDown: (0, import_web.composeEventHandlers)(contentProps.onKeyDown, event => {
443
- const target = event.target;
444
- const isKeyDownInside = target.closest("[data-tamagui-menu-content]") === event.currentTarget;
445
- const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
446
- const isCharacterKey = event.key.length === 1;
447
- if (isKeyDownInside) {
448
- if (event.key === "Tab") event.preventDefault();
449
- if (!isModifierKey && isCharacterKey) handleTypeaheadSearch(event.key);
450
- }
451
- const isOnContentFrame = event.target.hasAttribute("data-tamagui-menu-content");
452
- if (!isKeyDownInside || !isOnContentFrame) return;
453
- if (!FIRST_LAST_KEYS.includes(event.key)) return;
454
- event.preventDefault();
455
- const items = getItems().filter(item => !item.disabled);
456
- const candidateNodes = items.map(item => item.ref.current);
457
- if (LAST_KEYS.includes(event.key)) candidateNodes.reverse();
458
- focusFirst(candidateNodes, {
459
- focusVisible: true
460
- });
461
- }),
462
- // TODO
463
- // @ts-ignore
464
- onBlur: (0, import_web.composeEventHandlers)(props.onBlur, event => {
465
- if (!event.currentTarget?.contains(event.target)) {
466
- clearTimeout(timerRef.current);
467
- searchRef.current = "";
468
- }
469
- }),
470
- // TODO
471
- onPointerMove: (0, import_web.composeEventHandlers)(props.onPointerMove, event => {
472
- if (event.pointerType !== "mouse") return;
473
- const target = event.target;
474
- const pointerXHasChanged = lastPointerXRef.current !== event.clientX;
475
- if (event.currentTarget?.contains(target) && pointerXHasChanged) {
476
- const newDir = event.clientX > lastPointerXRef.current ? "right" : "left";
477
- pointerDirRef.current = newDir;
478
- lastPointerXRef.current = event.clientX;
479
- }
480
- })
481
- } : {})
482
- });
483
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuContentProvider, {
484
- scope,
485
- searchRef,
486
- onItemEnter: React.useCallback(event => {
487
- if (isPointerMovingToSubmenu(event)) event.preventDefault();
488
- }, [isPointerMovingToSubmenu]),
489
- onItemLeave: React.useCallback(event => {
490
- if (isPointerMovingToSubmenu(event)) return;
491
- focusableContentRef.current?.focus();
492
- setCurrentItemId(null);
493
- }, [isPointerMovingToSubmenu]),
494
- onTriggerLeave: React.useCallback(event => {
495
- if (isPointerMovingToSubmenu(event)) event.preventDefault();
496
- }, [isPointerMovingToSubmenu]),
497
- pointerGraceTimerRef,
498
- onPointerGraceIntentChange: React.useCallback(intent => {
499
- pointerGraceIntentRef.current = intent;
500
- }, []),
501
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_remove_scroll.RemoveScroll, {
502
- enabled: disableOutsideScroll,
503
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_focus_scope.FocusScope, {
504
- asChild: false,
505
- trapped: trapFocus,
506
- onMountAutoFocus: (0, import_web.composeEventHandlers)(onOpenAutoFocus, event => {
507
- event.preventDefault();
508
- const content2 = document.querySelector("[data-tamagui-menu-content]");
509
- content2?.focus({
510
- preventScroll: true
511
- });
512
- }),
513
- onUnmountAutoFocus: onCloseAutoFocus,
514
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_dismissable.Dismissable, {
515
- disableOutsidePointerEvents,
516
- onEscapeKeyDown,
517
- onPointerDownOutside,
518
- onFocusOutside,
519
- onInteractOutside,
520
- onDismiss,
521
- asChild: true,
522
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_roving_focus.RovingFocusGroup, {
523
- asChild: true,
524
- __scopeRovingFocusGroup: scope || MENU_CONTEXT,
525
- dir: rootContext.dir,
526
- orientation: "vertical",
527
- loop,
528
- currentTabStopId: currentItemId,
529
- onCurrentTabStopIdChange: setCurrentItemId,
530
- onEntryFocus: (0, import_web.composeEventHandlers)(onEntryFocus, event => {
531
- if (!rootContext.isUsingKeyboardRef.current) {
532
- event.preventDefault();
533
- }
534
- }),
535
- children: content
536
- })
537
- })
538
- })
539
- })
540
- });
541
- });
542
- MenuContent.displayName = CONTENT_NAME;
543
- const ITEM_NAME = "MenuItem";
544
- const ITEM_SELECT = "menu.itemSelect";
545
- const MenuItem = _Item.styleable((props, forwardedRef) => {
546
- const {
547
- disabled = false,
548
- onSelect,
549
- preventCloseOnSelect,
550
- children,
551
- scope = MENU_CONTEXT,
552
- // filter out native-only props that shouldn't reach the DOM
553
- // @ts-ignore
554
- destructive,
555
- // @ts-ignore
556
- hidden,
557
- // @ts-ignore
558
- androidIconName,
559
- // @ts-ignore
560
- iosIconName,
561
- ...itemProps
562
- } = props;
563
- const ref = React.useRef(null);
564
- const rootContext = useMenuRootContext(scope);
565
- const contentContext = useMenuContentContext(scope);
566
- const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
567
- const isPointerDownRef = React.useRef(false);
568
- const handleSelect = () => {
569
- const menuItem = ref.current;
570
- if (!disabled && menuItem) {
571
- if (import_web.isWeb) {
572
- const menuItemEl = menuItem;
573
- const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
574
- bubbles: true,
575
- cancelable: true
576
- });
577
- menuItemEl.addEventListener(ITEM_SELECT, event => onSelect?.(event), {
578
- once: true
579
- });
580
- (0, import_dismissable.dispatchDiscreteCustomEvent)(menuItemEl, itemSelectEvent);
581
- if (itemSelectEvent.defaultPrevented || preventCloseOnSelect) {
582
- isPointerDownRef.current = false;
583
- } else {
584
- rootContext.onClose();
585
- }
586
- } else {
587
- onSelect?.({
588
- target: menuItem
589
- });
590
- isPointerDownRef.current = false;
591
- if (!preventCloseOnSelect) {
592
- rootContext.onClose();
593
- }
594
- }
595
- }
596
- };
597
- const content = typeof children === "string" ? /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_web.Text, {
598
- children
599
- }) : children;
600
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuItemImpl, {
601
- outlineStyle: "none",
602
- ...itemProps,
603
- scope,
604
- ref: composedRefs,
605
- disabled,
606
- onPress: (0, import_web.composeEventHandlers)(props.onPress, handleSelect),
607
- onPointerDown: event => {
608
- props.onPointerDown?.(event);
609
- isPointerDownRef.current = true;
610
- },
611
- onPointerUp: (0, import_web.composeEventHandlers)(props.onPointerUp, event => {
612
- if (import_web.isWeb) {
613
- if (!isPointerDownRef.current) event.currentTarget?.click();
614
- }
615
- }),
616
- ...(import_web.isWeb ? {
617
- onKeyDown: (0, import_web.composeEventHandlers)(props.onKeyDown, event => {
618
- const isTypingAhead = contentContext.searchRef.current !== "";
619
- if (disabled || isTypingAhead && event.key === " ") return;
620
- if (SELECTION_KEYS.includes(event.key)) {
621
- event.currentTarget?.click();
622
- event.preventDefault();
623
- }
624
- })
625
- } : {}),
626
- children: content
627
- });
628
- });
629
- const MenuItemImpl = React.forwardRef((props, forwardedRef) => {
630
- const {
631
- scope = MENU_CONTEXT,
632
- disabled = false,
633
- textValue,
634
- unstyled = process.env.TAMAGUI_HEADLESS === "1",
635
- ...itemProps
636
- } = props;
637
- const contentContext = useMenuContentContext(scope);
638
- const ref = React.useRef(null);
639
- const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
640
- const [isFocused, setIsFocused] = React.useState(false);
641
- const [textContent, setTextContent] = React.useState("");
642
- if (import_web.isWeb) {
643
- React.useEffect(() => {
644
- const menuItem = ref.current;
645
- if (menuItem) {
646
- setTextContent((menuItem.textContent ?? "").trim());
647
- }
648
- }, [itemProps.children]);
649
- }
650
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(Collection.ItemSlot, {
651
- scope,
652
- disabled,
653
- textValue: textValue ?? textContent,
654
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_roving_focus.RovingFocusGroup.Item, {
655
- asChild: true,
656
- __scopeRovingFocusGroup: scope,
657
- focusable: !disabled,
658
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Item, {
659
- unstyled,
660
- componentName: ITEM_NAME,
661
- role: "menuitem",
662
- "data-highlighted": isFocused ? "" : void 0,
663
- "aria-disabled": disabled || void 0,
664
- "data-disabled": disabled ? "" : void 0,
665
- ...itemProps,
666
- ref: composedRefs,
667
- onPointerMove: (0, import_web.composeEventHandlers)(props.onPointerMove, event => {
668
- if (event.pointerType !== "mouse") return;
669
- if (disabled) {
670
- contentContext.onItemLeave(event);
671
- } else {
672
- contentContext.onItemEnter(event);
673
- if (!event.defaultPrevented) {
674
- const item = event.currentTarget;
675
- item.focus({
676
- preventScroll: true,
677
- focusVisible: false
678
- });
679
- }
680
- }
681
- }),
682
- onPointerLeave: (0, import_web.composeEventHandlers)(props.onPointerLeave, event => {
683
- contentContext.onItemLeave(event);
684
- }),
685
- onFocus: (0, import_web.composeEventHandlers)(props.onFocus, () => setIsFocused(true)),
686
- onBlur: (0, import_web.composeEventHandlers)(props.onBlur, () => setIsFocused(false))
687
- })
688
- })
689
- });
690
- });
691
- MenuItem.displayName = ITEM_NAME;
692
- const ITEM_TITLE_NAME = "MenuItemTitle";
693
- const MenuItemTitle = _Title.styleable((props, forwardedRef) => {
694
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Title, {
695
- ...props,
696
- ref: forwardedRef
697
- });
698
- });
699
- MenuItemTitle.displayName = ITEM_TITLE_NAME;
700
- const ITEM_SUB_TITLE_NAME = "MenuItemSubTitle";
701
- const MenuItemSubTitle = _SubTitle.styleable((props, forwardedRef) => {
702
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_SubTitle, {
703
- ...props,
704
- ref: forwardedRef
705
- });
706
- });
707
- MenuItemSubTitle.displayName = ITEM_SUB_TITLE_NAME;
708
- const ITEM_IMAGE = "MenuItemImage";
709
- const MenuItemImage = React.forwardRef((props, forwardedRef) => {
710
- const {
711
- // @ts-ignore - native menu ios config
712
- ios,
713
- // @ts-ignore
714
- androidIconName,
715
- // @ts-ignore
716
- iosIconName,
717
- ...rest
718
- } = props;
719
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Image, {
720
- ...rest,
721
- ref: forwardedRef
722
- });
723
- });
724
- MenuItemImage.displayName = ITEM_IMAGE;
725
- const ITEM_ICON = "MenuItemIcon";
726
- const MenuItemIcon = _Icon.styleable((props, forwardedRef) => {
727
- const {
728
- // @ts-ignore
729
- ios,
730
- // @ts-ignore
731
- android,
732
- // @ts-ignore
733
- androidIconName,
734
- // @ts-ignore
735
- iosIconName,
736
- ...rest
737
- } = props;
738
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Icon, {
739
- ...rest,
740
- ref: forwardedRef
741
- });
742
- });
743
- MenuItemIcon.displayName = ITEM_ICON;
744
- const CHECKBOX_ITEM_NAME = "MenuCheckboxItem";
745
- const MenuCheckboxItem = _Item.styleable((props, forwardedRef) => {
746
- const {
747
- checked = false,
748
- onCheckedChange,
749
- scope = MENU_CONTEXT,
750
- // filter out native-only props
751
- // @ts-ignore - native menu value state
752
- value,
753
- // @ts-ignore - native menu value change handler
754
- onValueChange,
755
- ...checkboxItemProps
756
- } = props;
757
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(ItemIndicatorProvider, {
758
- scope,
759
- checked,
760
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuItem, {
761
- componentName: CHECKBOX_ITEM_NAME,
762
- role: import_web.isWeb ? "menuitemcheckbox" : "menuitem",
763
- "aria-checked": isIndeterminate(checked) ? "mixed" : checked,
764
- ...checkboxItemProps,
765
- scope,
766
- ref: forwardedRef,
767
- "data-state": getCheckedState(checked),
768
- onSelect: (0, import_web.composeEventHandlers)(checkboxItemProps.onSelect, () => onCheckedChange?.(isIndeterminate(checked) ? true : !checked), {
769
- checkDefaultPrevented: false
770
- })
771
- })
772
- });
773
- });
774
- MenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME;
775
- const RADIO_GROUP_NAME = "MenuRadioGroup";
776
- const {
777
- Provider: RadioGroupProvider,
778
- useStyledContext: useRadioGroupContext
779
- } = (0, import_web.createStyledContext)();
780
- const MenuRadioGroup = _MenuGroup.styleable((props, forwardedRef) => {
781
- const {
782
- value,
783
- onValueChange,
784
- scope = MENU_CONTEXT,
785
- ...groupProps
786
- } = props;
787
- const handleValueChange = (0, import_use_callback_ref.useCallbackRef)(onValueChange);
788
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(RadioGroupProvider, {
789
- scope,
790
- value,
791
- onValueChange: handleValueChange,
792
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(_MenuGroup, {
793
- componentName: RADIO_GROUP_NAME,
794
- ...groupProps,
795
- ref: forwardedRef
796
- })
797
- });
798
- });
799
- MenuRadioGroup.displayName = RADIO_GROUP_NAME;
800
- const RADIO_ITEM_NAME = "MenuRadioItem";
801
- const MenuRadioItem = _Item.styleable((props, forwardedRef) => {
802
- const {
803
- value,
804
- scope = MENU_CONTEXT,
805
- ...radioItemProps
806
- } = props;
807
- const context = useRadioGroupContext(scope);
808
- const checked = value === context.value;
809
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(ItemIndicatorProvider, {
810
- scope,
811
- checked,
812
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuItem, {
813
- componentName: RADIO_ITEM_NAME,
814
- ...radioItemProps,
815
- scope,
816
- "aria-checked": checked,
817
- ref: forwardedRef,
818
- role: import_web.isWeb ? "menuitemradio" : "menuitem",
819
- "data-state": getCheckedState(checked),
820
- onSelect: (0, import_web.composeEventHandlers)(radioItemProps.onSelect, () => context.onValueChange?.(value), {
821
- checkDefaultPrevented: false
822
- })
823
- })
824
- });
825
- });
826
- MenuRadioItem.displayName = RADIO_ITEM_NAME;
827
- const ITEM_INDICATOR_NAME = "MenuItemIndicator";
828
- const {
829
- Provider: ItemIndicatorProvider,
830
- useStyledContext: useItemIndicatorContext
831
- } = (0, import_web.createStyledContext)();
832
- const MenuItemIndicator = _Indicator.styleable((props, forwardedRef) => {
833
- const {
834
- scope = MENU_CONTEXT,
835
- forceMount,
836
- ...itemIndicatorProps
837
- } = props;
838
- const indicatorContext = useItemIndicatorContext(scope);
839
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_animate_presence.AnimatePresence, {
840
- children: forceMount || isIndeterminate(indicatorContext.checked) || indicatorContext.checked === true ? /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Indicator, {
841
- componentName: ITEM_INDICATOR_NAME,
842
- render: "span",
843
- ...itemIndicatorProps,
844
- ref: forwardedRef,
845
- "data-state": getCheckedState(indicatorContext.checked)
846
- }) : null
847
- });
848
- });
849
- MenuItemIndicator.displayName = ITEM_INDICATOR_NAME;
850
- const MenuArrow = React.forwardRef(function MenuArrow2(props, forwardedRef) {
851
- const {
852
- scope = MENU_CONTEXT,
853
- unstyled = process.env.TAMAGUI_HEADLESS === "1",
854
- ...rest
855
- } = props;
856
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(PopperPrimitive.PopperArrow, {
857
- scope,
858
- componentName: "PopperArrow",
859
- unstyled,
860
- ...(!unstyled && {
861
- backgroundColor: "$background"
862
- }),
863
- ...rest,
864
- ref: forwardedRef
865
- });
866
- });
867
- const SUB_NAME = "MenuSub";
868
- const {
869
- Provider: MenuSubProvider,
870
- useStyledContext: useMenuSubContext
871
- } = (0, import_web.createStyledContext)();
872
- const MenuSub = props => {
873
- const isTouchDevice = (0, import_web.useIsTouchDevice)();
874
- const {
875
- scope = MENU_CONTEXT
876
- } = props;
877
- const rootContext = useMenuRootContext(scope);
878
- const parentPopperContext = PopperPrimitive.usePopperContext(scope);
879
- const parentSide = parentPopperContext.placement?.split("-")[0];
880
- const isNestedSubmenu = parentSide === "left" || parentSide === "right";
881
- const defaultPlacement = isTouchDevice ? "bottom" : isNestedSubmenu ? `${parentSide}-start` : rootContext.dir === "rtl" ? "left-start" : "right-start";
882
- const {
883
- children,
884
- open = false,
885
- onOpenChange,
886
- allowFlip: allowFlipProp = {
887
- padding: 10
888
- },
889
- stayInFrame = {
890
- padding: 10
891
- },
892
- placement = defaultPlacement,
893
- ...rest
894
- } = props;
895
- const allowFlip = React.useMemo(() => {
896
- if (!isNestedSubmenu || typeof allowFlipProp === "boolean") return allowFlipProp;
897
- if (allowFlipProp.fallbackPlacements) return allowFlipProp;
898
- const side = placement.split("-")[0];
899
- const align = placement.split("-")[1] || "start";
900
- const otherAlign = align === "start" ? "end" : "start";
901
- if (side === "left" || side === "right") {
902
- const oppositeSide = side === "right" ? "left" : "right";
903
- return {
904
- ...(typeof allowFlipProp === "object" ? allowFlipProp : {}),
905
- fallbackPlacements: [`${side}-${otherAlign}`, `${oppositeSide}-${align}`, `${oppositeSide}-${otherAlign}`]
906
- };
907
- }
908
- return allowFlipProp;
909
- }, [isNestedSubmenu, allowFlipProp, placement]);
910
- const parentMenuContext = useMenuContext(scope);
911
- const [trigger, setTrigger] = React.useState(null);
912
- const [content, setContent] = React.useState(null);
913
- const handleOpenChange = (0, import_use_callback_ref.useCallbackRef)(onOpenChange);
914
- React.useEffect(() => {
915
- if (parentMenuContext.open === false) handleOpenChange(false);
916
- return () => handleOpenChange(false);
917
- }, [parentMenuContext.open, handleOpenChange]);
918
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(PopperPrimitive.Popper, {
919
- open,
920
- placement,
921
- allowFlip,
922
- stayInFrame,
923
- ...rest,
924
- scope,
925
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuProvider, {
926
- scope,
927
- open,
928
- onOpenChange: handleOpenChange,
929
- content,
930
- onContentChange: setContent,
931
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuSubProvider, {
932
- scope,
933
- contentId: (0, import_react.useId)(),
934
- triggerId: (0, import_react.useId)(),
935
- trigger,
936
- onTriggerChange: setTrigger,
937
- children
938
- })
939
- })
940
- });
941
- };
942
- MenuSub.displayName = SUB_NAME;
943
- const SUB_TRIGGER_NAME = "MenuSubTrigger";
944
- const MenuSubTrigger = React.forwardRef((props, forwardedRef) => {
945
- const scope = props.scope || MENU_CONTEXT;
946
- const context = useMenuContext(scope);
947
- const rootContext = useMenuRootContext(scope);
948
- const subContext = useMenuSubContext(scope);
949
- const contentContext = useMenuContentContext(scope);
950
- const popperContext = PopperPrimitive.usePopperContext(scope);
951
- const openTimerRef = React.useRef(null);
952
- const {
953
- pointerGraceTimerRef,
954
- onPointerGraceIntentChange
955
- } = contentContext;
956
- const effectiveDir = rootContext.dir;
957
- const clearOpenTimer = React.useCallback(() => {
958
- if (openTimerRef.current) window.clearTimeout(openTimerRef.current);
959
- openTimerRef.current = null;
960
- }, []);
961
- React.useEffect(() => clearOpenTimer, [clearOpenTimer]);
962
- React.useEffect(() => {
963
- const pointerGraceTimer = pointerGraceTimerRef.current;
964
- return () => {
965
- window.clearTimeout(pointerGraceTimer);
966
- onPointerGraceIntentChange(null);
967
- };
968
- }, [pointerGraceTimerRef, onPointerGraceIntentChange]);
969
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuAnchor, {
970
- componentName: SUB_TRIGGER_NAME,
971
- asChild: "except-style",
972
- scope,
973
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuItemImpl, {
974
- id: subContext.triggerId,
975
- "aria-haspopup": "menu",
976
- "aria-expanded": context.open,
977
- "aria-controls": subContext.contentId,
978
- "data-state": getOpenState(context.open),
979
- outlineStyle: "none",
980
- ...props,
981
- ref: (0, import_web.composeRefs)(forwardedRef, subContext.onTriggerChange),
982
- onPress: event => {
983
- props.onPress?.(event);
984
- if (props.disabled || event.defaultPrevented) return;
985
- if (import_web.isWeb) {
986
- event.currentTarget.focus();
987
- }
988
- if (!context.open) context.onOpenChange(true);
989
- },
990
- onPointerMove: (0, import_web.composeEventHandlers)(props.onPointerMove,
991
- // @ts-ignore
992
- whenMouse(event => {
993
- contentContext.onItemEnter(event);
994
- if (event.defaultPrevented) return;
995
- if (!props.disabled && !context.open && !openTimerRef.current) {
996
- contentContext.onPointerGraceIntentChange(null);
997
- openTimerRef.current = window.setTimeout(() => {
998
- context.onOpenChange(true);
999
- clearOpenTimer();
1000
- }, 100);
1001
- }
1002
- })),
1003
- onPointerLeave: (0, import_web.composeEventHandlers)(props.onPointerLeave, eventIn => {
1004
- const event = eventIn;
1005
- clearOpenTimer();
1006
- const contentRect = context.content?.getBoundingClientRect();
1007
- if (contentRect) {
1008
- const contentEl = context.content;
1009
- const sideEl = contentEl?.dataset?.side ? contentEl : contentEl?.querySelector("[data-side]");
1010
- const side = sideEl?.dataset?.side || "right";
1011
- const rightSide = side === "right";
1012
- const bleed = rightSide ? -5 : 5;
1013
- const contentNearEdge = contentRect[rightSide ? "left" : "right"];
1014
- const contentFarEdge = contentRect[rightSide ? "right" : "left"];
1015
- const polygon = {
1016
- area: [
1017
- // Apply a bleed on clientX to ensure that our exit point is
1018
- // consistently within polygon bounds
1019
- {
1020
- x: event.clientX + bleed,
1021
- y: event.clientY
1022
- }, {
1023
- x: contentNearEdge,
1024
- y: contentRect.top
1025
- }, {
1026
- x: contentFarEdge,
1027
- y: contentRect.top
1028
- }, {
1029
- x: contentFarEdge,
1030
- y: contentRect.bottom
1031
- }, {
1032
- x: contentNearEdge,
1033
- y: contentRect.bottom
1034
- }],
1035
- side
1036
- };
1037
- contentContext.onPointerGraceIntentChange(polygon);
1038
- window.clearTimeout(pointerGraceTimerRef.current);
1039
- pointerGraceTimerRef.current = window.setTimeout(() => contentContext.onPointerGraceIntentChange(null), 300);
1040
- } else if (import_web.isWeb && subContext.trigger) {
1041
- const triggerEl = subContext.trigger;
1042
- const triggerRect = triggerEl?.getBoundingClientRect();
1043
- if (triggerRect) {
1044
- const placementSide = popperContext.placement?.split("-")[0];
1045
- const side = placementSide === "left" || placementSide === "right" ? placementSide : rootContext.dir === "rtl" ? "left" : "right";
1046
- const rightSide = side === "right";
1047
- const bleed = rightSide ? -5 : 5;
1048
- const nearEdge = rightSide ? triggerRect.right + 4 : triggerRect.left - 4;
1049
- const farEdge = rightSide ? nearEdge + 200 : nearEdge - 200;
1050
- const polygon = {
1051
- area: [{
1052
- x: event.clientX + bleed,
1053
- y: event.clientY
1054
- }, {
1055
- x: nearEdge,
1056
- y: triggerRect.top - 50
1057
- }, {
1058
- x: farEdge,
1059
- y: triggerRect.top - 50
1060
- }, {
1061
- x: farEdge,
1062
- y: triggerRect.bottom + 50
1063
- }, {
1064
- x: nearEdge,
1065
- y: triggerRect.bottom + 50
1066
- }],
1067
- side
1068
- };
1069
- contentContext.onPointerGraceIntentChange(polygon);
1070
- window.clearTimeout(pointerGraceTimerRef.current);
1071
- pointerGraceTimerRef.current = window.setTimeout(() => contentContext.onPointerGraceIntentChange(null), 300);
1072
- }
1073
- } else {
1074
- contentContext.onTriggerLeave(event);
1075
- if (event.defaultPrevented) return;
1076
- contentContext.onPointerGraceIntentChange(null);
1077
- }
1078
- }),
1079
- ...(import_web.isWeb ? {
1080
- onKeyDown: (0, import_web.composeEventHandlers)(props.onKeyDown, event => {
1081
- const isTypingAhead = contentContext.searchRef.current !== "";
1082
- if (props.disabled || isTypingAhead && event.key === " ") return;
1083
- const willOpen = SUB_OPEN_KEYS[effectiveDir].includes(event.key);
1084
- if (willOpen) {
1085
- if (context.open && context.content) {
1086
- const contentEl = context.content;
1087
- const firstItem = contentEl.querySelector?.('[role="menuitem"]:not([data-disabled])');
1088
- if (firstItem) {
1089
- firstItem.focus({
1090
- focusVisible: true
1091
- });
1092
- event.preventDefault();
1093
- return;
1094
- }
1095
- }
1096
- const triggerEl = event.currentTarget;
1097
- popperContext.refs?.setReference(triggerEl);
1098
- context.onOpenChange(true);
1099
- requestAnimationFrame(() => {
1100
- popperContext.update?.();
1101
- });
1102
- context.content?.focus({
1103
- focusVisible: true
1104
- });
1105
- event.preventDefault();
1106
- }
1107
- })
1108
- } : null)
1109
- })
1110
- });
1111
- });
1112
- MenuSubTrigger.displayName = SUB_TRIGGER_NAME;
1113
- const SUB_CONTENT_NAME = "MenuSubContent";
1114
- const MenuSubContentFrame = (0, import_web.styled)(PopperPrimitive.PopperContentFrame, {
1115
- name: SUB_CONTENT_NAME
1116
- });
1117
- const MenuSubContent = MenuSubContentFrame.styleable((props, forwardedRef) => {
1118
- const scope = props.scope || MENU_CONTEXT;
1119
- const portalContext = usePortalContext(scope);
1120
- const {
1121
- forceMount = portalContext.forceMount,
1122
- ...subContentProps
1123
- } = props;
1124
- const context = useMenuContext(scope);
1125
- const rootContext = useMenuRootContext(scope);
1126
- const subContext = useMenuSubContext(scope);
1127
- const popperContext = PopperPrimitive.usePopperContext(scope);
1128
- const ref = React.useRef(null);
1129
- const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
1130
- const placementSide = popperContext.placement?.split("-")[0];
1131
- const dataSide = placementSide === "left" || placementSide === "right" ? placementSide : rootContext.dir === "rtl" ? "left" : "right";
1132
- const effectiveDir = rootContext.dir;
1133
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(Collection.Provider, {
1134
- scope,
1135
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(Collection.Slot, {
1136
- scope,
1137
- children: /* @__PURE__ */(0, import_jsx_runtime.jsx)(MenuContentImpl, {
1138
- id: subContext.contentId,
1139
- "aria-labelledby": subContext.triggerId,
1140
- ...subContentProps,
1141
- ref: composedRefs,
1142
- "data-side": dataSide,
1143
- disableOutsidePointerEvents: false,
1144
- disableOutsideScroll: false,
1145
- trapFocus: false,
1146
- onOpenAutoFocus: event => {
1147
- if (rootContext.isUsingKeyboardRef.current) {
1148
- const root = ref.current;
1149
- const content = root?.querySelector?.("[data-tamagui-menu-content]");
1150
- (content || root)?.focus({
1151
- preventScroll: true
1152
- });
1153
- }
1154
- event.preventDefault();
1155
- },
1156
- onCloseAutoFocus: event => event.preventDefault(),
1157
- onFocusOutside: (0, import_web.composeEventHandlers)(props.onFocusOutside, event => {
1158
- if (event.target !== subContext.trigger) context.onOpenChange(false);
1159
- }),
1160
- onEscapeKeyDown: (0, import_web.composeEventHandlers)(props.onEscapeKeyDown, event => {
1161
- context.onOpenChange(false);
1162
- subContext.trigger?.focus({
1163
- focusVisible: true
1164
- });
1165
- event.preventDefault();
1166
- }),
1167
- ...(import_web.isWeb ? {
1168
- onKeyDown: (0, import_web.composeEventHandlers)(props.onKeyDown, event => {
1169
- const isKeyDownInside = event.currentTarget.contains(event.target);
1170
- const isCloseKey = SUB_CLOSE_KEYS[effectiveDir].includes(event.key);
1171
- if (isKeyDownInside && isCloseKey) {
1172
- context.onOpenChange(false);
1173
- subContext.trigger?.focus({
1174
- focusVisible: true
1175
- });
1176
- event.preventDefault();
1177
- }
1178
- })
1179
- } : null)
1180
- })
1181
- })
1182
- });
1183
- });
1184
- MenuSubContent.displayName = SUB_CONTENT_NAME;
1185
- const Anchor = MenuAnchor;
1186
- const Portal = MenuPortal;
1187
- const Content = MenuContent;
1188
- const Group = _MenuGroup.styleable((props, ref) => {
1189
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_MenuGroup, {
1190
- ...props,
1191
- ref
1192
- });
1193
- });
1194
- Group.displayName = "MenuGroup";
1195
- const Label = _Label.styleable((props, ref) => {
1196
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Label, {
1197
- ...props,
1198
- ref
1199
- });
1200
- });
1201
- Label.displayName = "MenuLabel";
1202
- const Item = MenuItem;
1203
- const CheckboxItem = MenuCheckboxItem;
1204
- const RadioGroup = MenuRadioGroup;
1205
- const RadioItem = MenuRadioItem;
1206
- const ItemIndicator = MenuItemIndicator;
1207
- const Separator = _Separator.styleable((props, ref) => {
1208
- return /* @__PURE__ */(0, import_jsx_runtime.jsx)(_Separator, {
1209
- ...props,
1210
- ref
1211
- });
1212
- });
1213
- Separator.displayName = "MenuSeparator";
1214
- const Arrow = MenuArrow;
1215
- const Sub = MenuSub;
1216
- const SubTrigger = MenuSubTrigger;
1217
- const SubContent = MenuSubContent;
1218
- const ItemTitle = MenuItemTitle;
1219
- const ItemSubtitle = MenuItemSubTitle;
1220
- const ItemImage = MenuItemImage;
1221
- const ItemIcon = MenuItemIcon;
1222
- const Menu = (0, import_web.withStaticProperties)(MenuComp, {
1223
- Anchor,
1224
- Portal,
1225
- Content,
1226
- Group,
1227
- Label,
1228
- Item,
1229
- CheckboxItem,
1230
- RadioGroup,
1231
- RadioItem,
1232
- ItemIndicator,
1233
- Separator,
1234
- Arrow,
1235
- Sub,
1236
- SubTrigger,
1237
- SubContent,
1238
- ItemTitle,
1239
- ItemSubtitle,
1240
- ItemImage,
1241
- ItemIcon
1242
- });
1243
- return {
1244
- Menu
1245
- };
77
+ const MenuImageFrame = (0, import_web.styled)(import_image.Image, { displayName: "MenuItemImage" });
78
+ function createBaseMenu() {
79
+ const MenuComp = (props) => {
80
+ const direction = (0, import_use_direction.useDirection)(props.dir);
81
+ const defaultPlacement = direction === "rtl" ? "bottom-end" : "bottom-start";
82
+ const { scope = MENU_CONTEXT, open = false, children, dir, onOpenChange, modal = true, allowFlip = { padding: 10 }, stayInFrame = { padding: 10 }, placement = defaultPlacement, resize = true, offset = 10, ...rest } = props;
83
+ const [content, setContent] = React.useState(null);
84
+ const isUsingKeyboardRef = React.useRef(false);
85
+ const handleOpenChange = (0, import_use_callback_ref.useCallbackRef)(onOpenChange);
86
+ if (import_web.isWeb) {
87
+ React.useEffect(() => {
88
+ const handleKeyDown = () => {
89
+ isUsingKeyboardRef.current = true;
90
+ document.addEventListener("pointerdown", handlePointer, {
91
+ capture: true,
92
+ once: true
93
+ });
94
+ document.addEventListener("pointermove", handlePointer, {
95
+ capture: true,
96
+ once: true
97
+ });
98
+ };
99
+ const handlePointer = () => isUsingKeyboardRef.current = false;
100
+ document.addEventListener("keydown", handleKeyDown, { capture: true });
101
+ return () => {
102
+ document.removeEventListener("keydown", handleKeyDown, { capture: true });
103
+ document.removeEventListener("pointerdown", handlePointer, { capture: true });
104
+ document.removeEventListener("pointermove", handlePointer, { capture: true });
105
+ };
106
+ }, []);
107
+ }
108
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.Popper, {
109
+ scope,
110
+ open,
111
+ placement,
112
+ allowFlip,
113
+ stayInFrame,
114
+ resize,
115
+ offset,
116
+ ...rest,
117
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuProvider, {
118
+ scope,
119
+ open,
120
+ onOpenChange: handleOpenChange,
121
+ content,
122
+ onContentChange: setContent,
123
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRootProvider, {
124
+ scope,
125
+ open,
126
+ onClose: React.useCallback(() => handleOpenChange(false), [handleOpenChange]),
127
+ isUsingKeyboardRef,
128
+ dir: direction,
129
+ modal,
130
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuSubProvider, {
131
+ scope,
132
+ children
133
+ })
134
+ })
135
+ })
136
+ });
137
+ };
138
+ const RepropagateMenuAndMenuRootProvider = (props) => {
139
+ const { scope = MENU_CONTEXT, menuContext, rootContext, popperContext, menuSubContext, children } = props;
140
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.PopperProvider, {
141
+ ...popperContext,
142
+ scope,
143
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuProvider, {
144
+ scope,
145
+ ...menuContext,
146
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRootProvider, {
147
+ scope,
148
+ ...rootContext,
149
+ children: menuSubContext ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuSubProvider, {
150
+ scope,
151
+ ...menuSubContext,
152
+ children
153
+ }) : children
154
+ })
155
+ })
156
+ });
157
+ };
158
+ MenuComp.displayName = MENU_NAME;
159
+ const ANCHOR_NAME = "MenuAnchor";
160
+ const MenuAnchor = (props) => {
161
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.PopperAnchor, {
162
+ scope: MENU_CONTEXT,
163
+ ...props
164
+ });
165
+ };
166
+ MenuAnchor.displayName = ANCHOR_NAME;
167
+ const PORTAL_NAME = "MenuPortal";
168
+ const { Provider: PortalProvider, useStyledContext: usePortalContext } = (0, import_web.createStyledContext)(void 0, "Portal");
169
+ const MenuPortal = (props) => {
170
+ const { scope = MENU_CONTEXT, forceMount, zIndex, children } = props;
171
+ const menuContext = useMenuContext(scope);
172
+ const rootContext = useMenuRootContext(scope);
173
+ const popperContext = PopperPrimitive.usePopperContext(scope);
174
+ const menuSubContext = useMenuSubContext(scope);
175
+ const themeName = (0, import_web.useThemeName)();
176
+ const themedChildren = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.Theme, {
177
+ forceClassName: true,
178
+ name: themeName,
179
+ children
180
+ });
181
+ const content = (0, import_portal.needsPortalRepropagation)() ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RepropagateMenuAndMenuRootProvider, {
182
+ menuContext,
183
+ rootContext,
184
+ popperContext,
185
+ menuSubContext,
186
+ scope,
187
+ children: themedChildren
188
+ }) : themedChildren;
189
+ const isPresent = forceMount || rootContext.open && menuContext.open;
190
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_animate.Animate, {
191
+ type: "presence",
192
+ present: isPresent,
193
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, {
194
+ stackZIndex: true,
195
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PortalProvider, {
196
+ scope,
197
+ forceMount,
198
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_web.View, {
199
+ zIndex: zIndex || 100,
200
+ inset: 0,
201
+ position: "absolute",
202
+ children: [!!menuContext.open && !import_web.isWeb && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
203
+ inset: 0,
204
+ position: "absolute",
205
+ onPress: () => menuContext.onOpenChange(!menuContext.open)
206
+ }), content]
207
+ })
208
+ }) })
209
+ })
210
+ });
211
+ };
212
+ MenuPortal.displayName = PORTAL_NAME;
213
+ const CONTENT_NAME = "MenuContent";
214
+ const { Provider: MenuContentProvider, useStyledContext: useMenuContentContext } = (0, import_web.createStyledContext)();
215
+ const MenuContentFrame = (0, import_web.styled)(PopperPrimitive.PopperContentFrame, { displayName: CONTENT_NAME });
216
+ const MenuContent = (0, import_core.createStyledHOC)(MenuContentFrame, (props, forwardedRef) => {
217
+ const scope = props.scope || MENU_CONTEXT;
218
+ const portalContext = usePortalContext(scope);
219
+ const { forceMount = portalContext.forceMount, ...contentProps } = props;
220
+ const rootContext = useMenuRootContext(scope);
221
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Provider, {
222
+ scope,
223
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Slot, {
224
+ scope,
225
+ children: rootContext.modal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRootContentModal, {
226
+ ...contentProps,
227
+ ref: forwardedRef
228
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRootContentNonModal, {
229
+ ...contentProps,
230
+ ref: forwardedRef
231
+ })
232
+ })
233
+ });
234
+ });
235
+ const MenuRootContentModal = (0, import_core.createRefComponent)((props, forwardedRef) => {
236
+ const scope = props.scope || MENU_CONTEXT;
237
+ const context = useMenuContext(scope);
238
+ const ref = React.useRef(null);
239
+ const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
240
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuContentImpl, {
241
+ ...props,
242
+ scope,
243
+ ref: composedRefs,
244
+ trapFocus: context.open,
245
+ disableOutsidePointerEvents: context.open,
246
+ disableOutsideScroll: false,
247
+ onFocusOutside: (0, import_web.composeEventHandlers)(props.onFocusOutside, (event) => event.cancel(), { checkDefaultPrevented: false }),
248
+ onDismiss: () => context.onOpenChange(false)
249
+ });
250
+ });
251
+ const MenuRootContentNonModal = (0, import_core.createRefComponent)((props, forwardedRef) => {
252
+ const scope = props.scope || MENU_CONTEXT;
253
+ const context = useMenuContext(scope);
254
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuContentImpl, {
255
+ ...props,
256
+ scope,
257
+ ref: forwardedRef,
258
+ trapFocus: false,
259
+ disableOutsidePointerEvents: false,
260
+ disableOutsideScroll: false,
261
+ onDismiss: () => context.onOpenChange(false)
262
+ });
263
+ });
264
+ const MenuContentImpl = (0, import_core.createRefComponent)((props, forwardedRef) => {
265
+ const { scope = MENU_CONTEXT, loop = false, trapFocus, onOpenAutoFocus, onCloseAutoFocus, disableOutsidePointerEvents, onEntryFocus, onEscapeKeyDown, onPointerDownOutside, onFocusOutside, onInteractOutside, onDismiss, disableOutsideScroll, disableDismissOnScroll = false, ...contentProps } = props;
266
+ const context = useMenuContext(scope);
267
+ const rootContext = useMenuRootContext(scope);
268
+ const popperContext = PopperPrimitive.usePopperContext(scope);
269
+ const getItems = useCollection(scope);
270
+ const [currentItemId, setCurrentItemId] = React.useState(null);
271
+ const contentRef = React.useRef(null);
272
+ const focusableContentRef = React.useRef(null);
273
+ const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, contentRef, context.onContentChange);
274
+ const timerRef = React.useRef(0);
275
+ const searchRef = React.useRef("");
276
+ const pointerGraceTimerRef = React.useRef(0);
277
+ const pointerGraceIntentRef = React.useRef(null);
278
+ const pointerDirRef = React.useRef("right");
279
+ const lastPointerXRef = React.useRef(0);
280
+ const handleTypeaheadSearch = (key) => {
281
+ const search = searchRef.current + key;
282
+ const items = getItems().filter((item) => !item.disabled);
283
+ const currentItem = document.activeElement;
284
+ const currentMatch = items.find((item) => item.ref.current === currentItem)?.textValue;
285
+ const values = items.map((item) => item.textValue);
286
+ const nextMatch = getNextMatch(values, search, currentMatch);
287
+ const newItem = items.find((item) => item.textValue === nextMatch)?.ref.current;
288
+ (function updateSearch(value) {
289
+ searchRef.current = value;
290
+ clearTimeout(timerRef.current);
291
+ if (value !== "") timerRef.current = setTimeout(() => updateSearch(""), 1e3);
292
+ })(search);
293
+ if (newItem) {
294
+ setTimeout(() => newItem.focus());
295
+ }
296
+ };
297
+ React.useEffect(() => {
298
+ return () => clearTimeout(timerRef.current);
299
+ }, []);
300
+ React.useEffect(() => {
301
+ if (!import_web.isWeb || !context.open) return;
302
+ const frame = requestAnimationFrame(() => {
303
+ const container = contentRef.current;
304
+ const el = container?.querySelector("[data-tamagui-menu-content]");
305
+ if (el) focusableContentRef.current = el;
306
+ });
307
+ return () => cancelAnimationFrame(frame);
308
+ }, [context.open]);
309
+ React.useEffect(() => {
310
+ if (!import_web.isWeb || disableDismissOnScroll || !context.open) return;
311
+ const handleScroll = (event) => {
312
+ const scrolled = event.target;
313
+ if (!scrolled) return;
314
+ const content2 = contentRef.current;
315
+ if (content2?.contains(scrolled)) return;
316
+ const reference = popperContext.refs?.reference?.current;
317
+ const anchor = reference instanceof Element ? reference : reference?.contextElement ?? null;
318
+ if (anchor && !scrolled.contains(anchor)) return;
319
+ context.onOpenChange(false);
320
+ };
321
+ window.addEventListener("scroll", handleScroll, {
322
+ capture: true,
323
+ passive: true
324
+ });
325
+ return () => {
326
+ window.removeEventListener("scroll", handleScroll, { capture: true });
327
+ };
328
+ }, [
329
+ disableDismissOnScroll,
330
+ context.open,
331
+ context.onOpenChange,
332
+ popperContext.refs
333
+ ]);
334
+ if (import_web.isWeb) {
335
+ (0, import_focus_guard.useFocusGuards)();
336
+ }
337
+ const isPointerMovingToSubmenu = React.useCallback((event) => {
338
+ const isMovingTowards = pointerDirRef.current === pointerGraceIntentRef.current?.side;
339
+ const inArea = isPointerInGraceArea(event, pointerGraceIntentRef.current?.area);
340
+ return isMovingTowards && inArea;
341
+ }, []);
342
+ const content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.PopperContent, {
343
+ role: "menu",
344
+ tabIndex: -1,
345
+ "aria-orientation": "vertical",
346
+ "data-state": getOpenState(context.open),
347
+ "data-tamagui-menu-content": "",
348
+ dir: rootContext.dir,
349
+ scope: scope || MENU_CONTEXT,
350
+ ...contentProps,
351
+ ref: composedRefs,
352
+ className: contentProps.transition ? void 0 : contentProps.className,
353
+ ...import_web.isWeb ? {
354
+ onKeyDown: (0, import_web.composeEventHandlers)(contentProps.onKeyDown, (event) => {
355
+ const target = event.target;
356
+ const isKeyDownInside = target.closest("[data-tamagui-menu-content]") === event.currentTarget;
357
+ const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
358
+ const isCharacterKey = event.key.length === 1;
359
+ if (isKeyDownInside) {
360
+ if (event.key === "Tab") event.preventDefault();
361
+ if (!isModifierKey && isCharacterKey) handleTypeaheadSearch(event.key);
362
+ }
363
+ const isOnContentFrame = event.target.hasAttribute("data-tamagui-menu-content");
364
+ if (!isKeyDownInside || !isOnContentFrame) return;
365
+ if (!FIRST_LAST_KEYS.includes(event.key)) return;
366
+ event.preventDefault();
367
+ const items = getItems().filter((item) => !item.disabled);
368
+ const candidateNodes = items.map((item) => item.ref.current);
369
+ if (LAST_KEYS.includes(event.key)) candidateNodes.reverse();
370
+ focusFirst(candidateNodes, { focusVisible: true });
371
+ }),
372
+ onBlur: (0, import_web.composeEventHandlers)(props.onBlur, (event) => {
373
+ if (!event.currentTarget?.contains(event.target)) {
374
+ clearTimeout(timerRef.current);
375
+ searchRef.current = "";
376
+ }
377
+ }),
378
+ onPointerMove: (0, import_web.composeEventHandlers)(props.onPointerMove, (event) => {
379
+ if (event.pointerType !== "mouse") return;
380
+ const target = event.target;
381
+ const pointerXHasChanged = lastPointerXRef.current !== event.clientX;
382
+ if (event.currentTarget?.contains(target) && pointerXHasChanged) {
383
+ const newDir = event.clientX > lastPointerXRef.current ? "right" : "left";
384
+ pointerDirRef.current = newDir;
385
+ lastPointerXRef.current = event.clientX;
386
+ }
387
+ })
388
+ } : {}
389
+ });
390
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuContentProvider, {
391
+ scope,
392
+ searchRef,
393
+ onItemEnter: React.useCallback((event) => {
394
+ if (isPointerMovingToSubmenu(event)) event.preventDefault();
395
+ }, [isPointerMovingToSubmenu]),
396
+ onItemLeave: React.useCallback((event) => {
397
+ if (isPointerMovingToSubmenu(event)) return;
398
+ focusableContentRef.current?.focus();
399
+ setCurrentItemId(null);
400
+ }, [isPointerMovingToSubmenu]),
401
+ onTriggerLeave: React.useCallback((event) => {
402
+ if (isPointerMovingToSubmenu(event)) event.preventDefault();
403
+ }, [isPointerMovingToSubmenu]),
404
+ pointerGraceTimerRef,
405
+ onPointerGraceIntentChange: React.useCallback((intent) => {
406
+ pointerGraceIntentRef.current = intent;
407
+ }, []),
408
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_remove_scroll.RemoveScroll, {
409
+ enabled: disableOutsideScroll,
410
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_focus_scope.FocusScope, {
411
+ asChild: false,
412
+ trapped: trapFocus,
413
+ onMountAutoFocus: (0, import_web.composeEventHandlers)(onOpenAutoFocus, (event) => {
414
+ event.cancel();
415
+ const content2 = document.querySelector("[data-tamagui-menu-content]");
416
+ content2?.focus({ preventScroll: true });
417
+ }),
418
+ onUnmountAutoFocus: onCloseAutoFocus,
419
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dismissable.Dismissable, {
420
+ disableOutsidePointerEvents,
421
+ onEscapeKeyDown,
422
+ onPointerDownOutside,
423
+ onFocusOutside,
424
+ onInteractOutside,
425
+ onDismiss,
426
+ asChild: true,
427
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_roving_focus.RovingFocusGroup, {
428
+ asChild: true,
429
+ __scopeRovingFocusGroup: scope || MENU_CONTEXT,
430
+ dir: rootContext.dir,
431
+ orientation: "vertical",
432
+ loop,
433
+ currentTabStopId: currentItemId,
434
+ onCurrentTabStopIdChange: setCurrentItemId,
435
+ onEntryFocus: (0, import_web.composeEventHandlers)(onEntryFocus, (event) => {
436
+ if (!rootContext.isUsingKeyboardRef.current) {
437
+ event.cancel();
438
+ }
439
+ }),
440
+ children: content
441
+ })
442
+ })
443
+ })
444
+ })
445
+ });
446
+ });
447
+ MenuContent.displayName = CONTENT_NAME;
448
+ const ITEM_NAME = "MenuItem";
449
+ const ITEM_SELECT = "menu.itemSelect";
450
+ const MenuItem = (0, import_core.createStyledHOC)(import_web.View, (props, forwardedRef) => {
451
+ const { disabled = false, onSelect, preventCloseOnSelect, children, scope = MENU_CONTEXT, destructive, hidden, androidIconName, ...itemProps } = props;
452
+ const ref = React.useRef(null);
453
+ const rootContext = useMenuRootContext(scope);
454
+ const contentContext = useMenuContentContext(scope);
455
+ const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
456
+ const isPointerDownRef = React.useRef(false);
457
+ const handleSelect = () => {
458
+ const menuItem = ref.current;
459
+ if (!disabled && menuItem) {
460
+ if (import_web.isWeb) {
461
+ const menuItemEl = menuItem;
462
+ const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
463
+ bubbles: true,
464
+ cancelable: true
465
+ });
466
+ menuItemEl.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), { once: true });
467
+ (0, import_dismissable.dispatchDiscreteCustomEvent)(menuItemEl, itemSelectEvent);
468
+ if (itemSelectEvent.defaultPrevented || preventCloseOnSelect) {
469
+ isPointerDownRef.current = false;
470
+ } else {
471
+ rootContext.onClose();
472
+ }
473
+ } else {
474
+ onSelect?.({ target: menuItem });
475
+ isPointerDownRef.current = false;
476
+ if (!preventCloseOnSelect) {
477
+ rootContext.onClose();
478
+ }
479
+ }
480
+ }
481
+ };
482
+ const content = typeof children === "string" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.Text, { children }) : children;
483
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItemImpl, {
484
+ ...itemProps,
485
+ scope,
486
+ ref: composedRefs,
487
+ disabled,
488
+ onPress: (0, import_web.composeEventHandlers)(props.onPress, handleSelect),
489
+ onPointerDown: (event) => {
490
+ props.onPointerDown?.(event);
491
+ isPointerDownRef.current = true;
492
+ },
493
+ onPointerUp: (0, import_web.composeEventHandlers)(props.onPointerUp, (event) => {
494
+ if (import_web.isWeb) {
495
+ if (!isPointerDownRef.current) event.currentTarget?.click();
496
+ }
497
+ }),
498
+ ...import_web.isWeb ? { onKeyDown: (0, import_web.composeEventHandlers)(props.onKeyDown, (event) => {
499
+ const isTypingAhead = contentContext.searchRef.current !== "";
500
+ if (disabled || isTypingAhead && event.key === " ") return;
501
+ if (SELECTION_KEYS.includes(event.key)) {
502
+ event.currentTarget?.click();
503
+ event.preventDefault();
504
+ }
505
+ }) } : {},
506
+ children: content
507
+ });
508
+ });
509
+ const MenuItemImpl = (0, import_core.createRefComponent)((props, forwardedRef) => {
510
+ const { scope = MENU_CONTEXT, className, disabled = false, textValue, ...itemProps } = props;
511
+ const contentContext = useMenuContentContext(scope);
512
+ const ref = React.useRef(null);
513
+ const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
514
+ const [isFocused, setIsFocused] = React.useState(false);
515
+ const [textContent, setTextContent] = React.useState("");
516
+ if (import_web.isWeb) {
517
+ React.useEffect(() => {
518
+ const menuItem = ref.current;
519
+ if (menuItem) {
520
+ setTextContent((menuItem.textContent ?? "").trim());
521
+ }
522
+ }, [itemProps.children]);
523
+ }
524
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.ItemSlot, {
525
+ scope,
526
+ disabled,
527
+ textValue: textValue ?? textContent,
528
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_roving_focus.RovingFocusGroup.Item, {
529
+ asChild: true,
530
+ __scopeRovingFocusGroup: scope,
531
+ tabIndex: disabled ? -1 : 0,
532
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
533
+ className: `is_${ITEM_NAME} ${className || ""}`.trim(),
534
+ role: "menuitem",
535
+ "data-highlighted": isFocused ? "" : void 0,
536
+ "aria-disabled": disabled || void 0,
537
+ "data-disabled": disabled ? "" : void 0,
538
+ ...itemProps,
539
+ ref: composedRefs,
540
+ onPointerMove: (0, import_web.composeEventHandlers)(props.onPointerMove, (event) => {
541
+ if (event.pointerType !== "mouse") return;
542
+ if (disabled) {
543
+ contentContext.onItemLeave(event);
544
+ } else {
545
+ contentContext.onItemEnter(event);
546
+ if (!event.defaultPrevented) {
547
+ const item = event.currentTarget;
548
+ item.focus({
549
+ preventScroll: true,
550
+ focusVisible: false
551
+ });
552
+ }
553
+ }
554
+ }),
555
+ onPointerLeave: (0, import_web.composeEventHandlers)(props.onPointerLeave, (event) => {
556
+ contentContext.onItemLeave(event);
557
+ }),
558
+ onFocus: (0, import_web.composeEventHandlers)(props.onFocus, () => setIsFocused(true)),
559
+ onBlur: (0, import_web.composeEventHandlers)(props.onBlur, () => setIsFocused(false))
560
+ })
561
+ })
562
+ });
563
+ });
564
+ MenuItem.displayName = ITEM_NAME;
565
+ const ITEM_TITLE_NAME = "MenuItemTitle";
566
+ const MenuItemTitle = (0, import_core.createStyledHOC)(import_web.Text, (props, forwardedRef) => {
567
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.Text, {
568
+ ...props,
569
+ ref: forwardedRef
570
+ });
571
+ });
572
+ MenuItemTitle.displayName = ITEM_TITLE_NAME;
573
+ const ITEM_SUB_TITLE_NAME = "MenuItemSubTitle";
574
+ const MenuItemSubTitle = (0, import_core.createStyledHOC)(import_web.Text, (props, forwardedRef) => {
575
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.Text, {
576
+ ...props,
577
+ ref: forwardedRef
578
+ });
579
+ });
580
+ MenuItemSubTitle.displayName = ITEM_SUB_TITLE_NAME;
581
+ const ITEM_IMAGE = "MenuItemImage";
582
+ const MenuItemImage = (0, import_core.createRefComponent)((props, forwardedRef) => {
583
+ const { ios, androidIconName, ...rest } = props;
584
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuImageFrame, {
585
+ ...rest,
586
+ ref: forwardedRef
587
+ });
588
+ });
589
+ MenuItemImage.displayName = ITEM_IMAGE;
590
+ const ITEM_ICON = "MenuItemIcon";
591
+ const MenuItemIcon = (0, import_core.createStyledHOC)(import_web.View, (props, forwardedRef) => {
592
+ const { ios, android, androidIconName, ...rest } = props;
593
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
594
+ ...rest,
595
+ ref: forwardedRef
596
+ });
597
+ });
598
+ MenuItemIcon.displayName = ITEM_ICON;
599
+ const CHECKBOX_ITEM_NAME = "MenuCheckboxItem";
600
+ const MenuCheckboxItem = (0, import_core.createStyledHOC)(import_web.View, (props, forwardedRef) => {
601
+ const { checked = false, onCheckedChange, scope = MENU_CONTEXT, value, onValueChange, className, ...checkboxItemProps } = props;
602
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemIndicatorProvider, {
603
+ scope,
604
+ checked,
605
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
606
+ className: `is_${CHECKBOX_ITEM_NAME} ${className || ""}`.trim(),
607
+ role: import_web.isWeb ? "menuitemcheckbox" : "menuitem",
608
+ "aria-checked": isIndeterminate(checked) ? "mixed" : checked,
609
+ ...checkboxItemProps,
610
+ scope,
611
+ ref: forwardedRef,
612
+ "data-state": getCheckedState(checked),
613
+ onSelect: (0, import_web.composeEventHandlers)(checkboxItemProps.onSelect, () => onCheckedChange?.(isIndeterminate(checked) ? true : !checked), { checkDefaultPrevented: false })
614
+ })
615
+ });
616
+ });
617
+ MenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME;
618
+ const RADIO_GROUP_NAME = "MenuRadioGroup";
619
+ const { Provider: RadioGroupProvider, useStyledContext: useRadioGroupContext } = (0, import_web.createStyledContext)();
620
+ const MenuRadioGroup = (0, import_core.createStyledHOC)(import_web.View, (props, forwardedRef) => {
621
+ const { value, onValueChange, scope = MENU_CONTEXT, className, ...groupProps } = props;
622
+ const handleValueChange = (0, import_use_callback_ref.useCallbackRef)(onValueChange);
623
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadioGroupProvider, {
624
+ scope,
625
+ value,
626
+ onValueChange: handleValueChange,
627
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
628
+ className: `is_${RADIO_GROUP_NAME} ${className || ""}`.trim(),
629
+ ...groupProps,
630
+ ref: forwardedRef
631
+ })
632
+ });
633
+ });
634
+ MenuRadioGroup.displayName = RADIO_GROUP_NAME;
635
+ const RADIO_ITEM_NAME = "MenuRadioItem";
636
+ const MenuRadioItem = (0, import_core.createStyledHOC)(import_web.View, (props, forwardedRef) => {
637
+ const { value, scope = MENU_CONTEXT, className, ...radioItemProps } = props;
638
+ const context = useRadioGroupContext(scope);
639
+ const checked = value === context.value;
640
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemIndicatorProvider, {
641
+ scope,
642
+ checked,
643
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItem, {
644
+ className: `is_${RADIO_ITEM_NAME} ${className || ""}`.trim(),
645
+ ...radioItemProps,
646
+ scope,
647
+ "aria-checked": checked,
648
+ ref: forwardedRef,
649
+ role: import_web.isWeb ? "menuitemradio" : "menuitem",
650
+ "data-state": getCheckedState(checked),
651
+ onSelect: (0, import_web.composeEventHandlers)(radioItemProps.onSelect, () => context.onValueChange?.(value), { checkDefaultPrevented: false })
652
+ })
653
+ });
654
+ });
655
+ MenuRadioItem.displayName = RADIO_ITEM_NAME;
656
+ const ITEM_INDICATOR_NAME = "MenuItemIndicator";
657
+ const { Provider: ItemIndicatorProvider, useStyledContext: useItemIndicatorContext } = (0, import_web.createStyledContext)();
658
+ const MenuItemIndicator = (0, import_core.createStyledHOC)(import_web.View, (props, forwardedRef) => {
659
+ const { scope = MENU_CONTEXT, className, forceMount, ...itemIndicatorProps } = props;
660
+ const indicatorContext = useItemIndicatorContext(scope);
661
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_animate_presence.AnimatePresence, { children: forceMount || isIndeterminate(indicatorContext.checked) || indicatorContext.checked === true ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
662
+ className: `is_${ITEM_INDICATOR_NAME} ${className || ""}`.trim(),
663
+ render: "span",
664
+ ...itemIndicatorProps,
665
+ ref: forwardedRef,
666
+ "data-state": getCheckedState(indicatorContext.checked)
667
+ }) : null });
668
+ });
669
+ MenuItemIndicator.displayName = ITEM_INDICATOR_NAME;
670
+ const MenuArrow = (0, import_core.createRefComponent)(function MenuArrow2(props, forwardedRef) {
671
+ const { scope = MENU_CONTEXT, className, ...rest } = props;
672
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.PopperArrow, {
673
+ scope,
674
+ className: `is_PopperArrow ${className || ""}`.trim(),
675
+ ...rest,
676
+ ref: forwardedRef
677
+ });
678
+ });
679
+ const SUB_NAME = "MenuSub";
680
+ const { Provider: MenuSubProvider, useStyledContext: useMenuSubContext } = (0, import_web.createStyledContext)();
681
+ const MenuSub = (props) => {
682
+ const isTouchDevice = (0, import_web.useIsTouchDevice)();
683
+ const { scope = MENU_CONTEXT } = props;
684
+ const rootContext = useMenuRootContext(scope);
685
+ const parentPopperContext = PopperPrimitive.usePopperContext(scope);
686
+ const parentSide = parentPopperContext.placement?.split("-")[0];
687
+ const isNestedSubmenu = parentSide === "left" || parentSide === "right";
688
+ const defaultPlacement = isTouchDevice ? "bottom" : isNestedSubmenu ? `${parentSide}-start` : rootContext.dir === "rtl" ? "left-start" : "right-start";
689
+ const { children, open = false, onOpenChange, allowFlip: allowFlipProp = { padding: 10 }, stayInFrame = { padding: 10 }, placement = defaultPlacement, ...rest } = props;
690
+ const allowFlip = React.useMemo(() => {
691
+ if (!isNestedSubmenu || typeof allowFlipProp === "boolean") return allowFlipProp;
692
+ if (allowFlipProp.fallbackPlacements) return allowFlipProp;
693
+ const side = placement.split("-")[0];
694
+ const align = placement.split("-")[1] || "start";
695
+ const otherAlign = align === "start" ? "end" : "start";
696
+ if (side === "left" || side === "right") {
697
+ const oppositeSide = side === "right" ? "left" : "right";
698
+ return {
699
+ ...typeof allowFlipProp === "object" ? allowFlipProp : {},
700
+ fallbackPlacements: [
701
+ `${side}-${otherAlign}`,
702
+ `${oppositeSide}-${align}`,
703
+ `${oppositeSide}-${otherAlign}`
704
+ ]
705
+ };
706
+ }
707
+ return allowFlipProp;
708
+ }, [
709
+ isNestedSubmenu,
710
+ allowFlipProp,
711
+ placement
712
+ ]);
713
+ const parentMenuContext = useMenuContext(scope);
714
+ const [trigger, setTrigger] = React.useState(null);
715
+ const [content, setContent] = React.useState(null);
716
+ const handleOpenChange = (0, import_use_callback_ref.useCallbackRef)(onOpenChange);
717
+ React.useEffect(() => {
718
+ if (parentMenuContext.open === false) handleOpenChange(false);
719
+ return () => handleOpenChange(false);
720
+ }, [parentMenuContext.open, handleOpenChange]);
721
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.Popper, {
722
+ open,
723
+ placement,
724
+ allowFlip,
725
+ stayInFrame,
726
+ ...rest,
727
+ scope,
728
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuProvider, {
729
+ scope,
730
+ open,
731
+ onOpenChange: handleOpenChange,
732
+ content,
733
+ onContentChange: setContent,
734
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuSubProvider, {
735
+ scope,
736
+ contentId: (0, import_react.useId)(),
737
+ triggerId: (0, import_react.useId)(),
738
+ trigger,
739
+ onTriggerChange: setTrigger,
740
+ children
741
+ })
742
+ })
743
+ });
744
+ };
745
+ MenuSub.displayName = SUB_NAME;
746
+ const SUB_TRIGGER_NAME = "MenuSubTrigger";
747
+ const MenuSubTrigger = (0, import_core.createRefComponent)((props, forwardedRef) => {
748
+ const scope = props.scope || MENU_CONTEXT;
749
+ const context = useMenuContext(scope);
750
+ const rootContext = useMenuRootContext(scope);
751
+ const subContext = useMenuSubContext(scope);
752
+ const contentContext = useMenuContentContext(scope);
753
+ const popperContext = PopperPrimitive.usePopperContext(scope);
754
+ const openTimerRef = React.useRef(null);
755
+ const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext;
756
+ const effectiveDir = rootContext.dir;
757
+ const clearOpenTimer = React.useCallback(() => {
758
+ if (openTimerRef.current) window.clearTimeout(openTimerRef.current);
759
+ openTimerRef.current = null;
760
+ }, []);
761
+ React.useEffect(() => clearOpenTimer, [clearOpenTimer]);
762
+ React.useEffect(() => {
763
+ const pointerGraceTimer = pointerGraceTimerRef.current;
764
+ return () => {
765
+ window.clearTimeout(pointerGraceTimer);
766
+ onPointerGraceIntentChange(null);
767
+ };
768
+ }, [pointerGraceTimerRef, onPointerGraceIntentChange]);
769
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuAnchor, {
770
+ className: `is_${SUB_TRIGGER_NAME}`,
771
+ asChild: "except-style",
772
+ scope,
773
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuItemImpl, {
774
+ id: subContext.triggerId,
775
+ "aria-haspopup": "menu",
776
+ "aria-expanded": context.open,
777
+ "aria-controls": subContext.contentId,
778
+ "data-state": getOpenState(context.open),
779
+ ...props,
780
+ ref: (0, import_web.composeRefs)(forwardedRef, subContext.onTriggerChange),
781
+ onPress: (event) => {
782
+ props.onPress?.(event);
783
+ if (props.disabled || event.defaultPrevented) return;
784
+ if (import_web.isWeb) {
785
+ event.currentTarget.focus();
786
+ }
787
+ if (!context.open) context.onOpenChange(true);
788
+ },
789
+ onPointerMove: (0, import_web.composeEventHandlers)(props.onPointerMove, whenMouse((event) => {
790
+ contentContext.onItemEnter(event);
791
+ if (event.defaultPrevented) return;
792
+ if (!props.disabled && !context.open && !openTimerRef.current) {
793
+ contentContext.onPointerGraceIntentChange(null);
794
+ openTimerRef.current = window.setTimeout(() => {
795
+ context.onOpenChange(true);
796
+ clearOpenTimer();
797
+ }, 100);
798
+ }
799
+ })),
800
+ onPointerLeave: (0, import_web.composeEventHandlers)(props.onPointerLeave, (eventIn) => {
801
+ const event = eventIn;
802
+ clearOpenTimer();
803
+ const contentRect = context.content?.getBoundingClientRect();
804
+ if (contentRect) {
805
+ const contentEl = context.content;
806
+ const sideEl = contentEl?.dataset?.side ? contentEl : contentEl?.querySelector("[data-side]");
807
+ const side = sideEl?.dataset?.side || "right";
808
+ const rightSide = side === "right";
809
+ const bleed = rightSide ? -5 : 5;
810
+ const contentNearEdge = contentRect[rightSide ? "left" : "right"];
811
+ const contentFarEdge = contentRect[rightSide ? "right" : "left"];
812
+ const polygon = {
813
+ area: [
814
+ {
815
+ x: event.clientX + bleed,
816
+ y: event.clientY
817
+ },
818
+ {
819
+ x: contentNearEdge,
820
+ y: contentRect.top
821
+ },
822
+ {
823
+ x: contentFarEdge,
824
+ y: contentRect.top
825
+ },
826
+ {
827
+ x: contentFarEdge,
828
+ y: contentRect.bottom
829
+ },
830
+ {
831
+ x: contentNearEdge,
832
+ y: contentRect.bottom
833
+ }
834
+ ],
835
+ side
836
+ };
837
+ contentContext.onPointerGraceIntentChange(polygon);
838
+ window.clearTimeout(pointerGraceTimerRef.current);
839
+ pointerGraceTimerRef.current = window.setTimeout(() => contentContext.onPointerGraceIntentChange(null), 300);
840
+ } else if (import_web.isWeb && subContext.trigger) {
841
+ const triggerEl = subContext.trigger;
842
+ const triggerRect = triggerEl?.getBoundingClientRect();
843
+ if (triggerRect) {
844
+ const placementSide = popperContext.placement?.split("-")[0];
845
+ const side = placementSide === "left" || placementSide === "right" ? placementSide : rootContext.dir === "rtl" ? "left" : "right";
846
+ const rightSide = side === "right";
847
+ const bleed = rightSide ? -5 : 5;
848
+ const nearEdge = rightSide ? triggerRect.right + 4 : triggerRect.left - 4;
849
+ const farEdge = rightSide ? nearEdge + 200 : nearEdge - 200;
850
+ const polygon = {
851
+ area: [
852
+ {
853
+ x: event.clientX + bleed,
854
+ y: event.clientY
855
+ },
856
+ {
857
+ x: nearEdge,
858
+ y: triggerRect.top - 50
859
+ },
860
+ {
861
+ x: farEdge,
862
+ y: triggerRect.top - 50
863
+ },
864
+ {
865
+ x: farEdge,
866
+ y: triggerRect.bottom + 50
867
+ },
868
+ {
869
+ x: nearEdge,
870
+ y: triggerRect.bottom + 50
871
+ }
872
+ ],
873
+ side
874
+ };
875
+ contentContext.onPointerGraceIntentChange(polygon);
876
+ window.clearTimeout(pointerGraceTimerRef.current);
877
+ pointerGraceTimerRef.current = window.setTimeout(() => contentContext.onPointerGraceIntentChange(null), 300);
878
+ }
879
+ } else {
880
+ contentContext.onTriggerLeave(event);
881
+ if (event.defaultPrevented) return;
882
+ contentContext.onPointerGraceIntentChange(null);
883
+ }
884
+ }),
885
+ ...import_web.isWeb ? { onKeyDown: (0, import_web.composeEventHandlers)(props.onKeyDown, (event) => {
886
+ const isTypingAhead = contentContext.searchRef.current !== "";
887
+ if (props.disabled || isTypingAhead && event.key === " ") return;
888
+ const willOpen = SUB_OPEN_KEYS[effectiveDir].includes(event.key);
889
+ if (willOpen) {
890
+ if (context.open && context.content) {
891
+ const contentEl = context.content;
892
+ const firstItem = contentEl.querySelector?.("[role=\"menuitem\"]:not([data-disabled])");
893
+ if (firstItem) {
894
+ firstItem.focus({ focusVisible: true });
895
+ event.preventDefault();
896
+ return;
897
+ }
898
+ }
899
+ const triggerEl = event.currentTarget;
900
+ popperContext.refs?.setReference(triggerEl);
901
+ context.onOpenChange(true);
902
+ requestAnimationFrame(() => {
903
+ popperContext.update?.();
904
+ });
905
+ context.content?.focus({ focusVisible: true });
906
+ event.preventDefault();
907
+ }
908
+ }) } : null
909
+ })
910
+ });
911
+ });
912
+ MenuSubTrigger.displayName = SUB_TRIGGER_NAME;
913
+ const SUB_CONTENT_NAME = "MenuSubContent";
914
+ const MenuSubContentFrame = (0, import_web.styled)(PopperPrimitive.PopperContentFrame, { displayName: SUB_CONTENT_NAME });
915
+ const MenuSubContent = (0, import_core.createStyledHOC)(MenuSubContentFrame, (props, forwardedRef) => {
916
+ const scope = props.scope || MENU_CONTEXT;
917
+ const portalContext = usePortalContext(scope);
918
+ const { forceMount = portalContext.forceMount, ...subContentProps } = props;
919
+ const context = useMenuContext(scope);
920
+ const rootContext = useMenuRootContext(scope);
921
+ const subContext = useMenuSubContext(scope);
922
+ const popperContext = PopperPrimitive.usePopperContext(scope);
923
+ const ref = React.useRef(null);
924
+ const composedRefs = (0, import_web.useComposedRefs)(forwardedRef, ref);
925
+ const placementSide = popperContext.placement?.split("-")[0];
926
+ const dataSide = placementSide === "left" || placementSide === "right" ? placementSide : rootContext.dir === "rtl" ? "left" : "right";
927
+ const effectiveDir = rootContext.dir;
928
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Provider, {
929
+ scope,
930
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Slot, {
931
+ scope,
932
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuContentImpl, {
933
+ id: subContext.contentId,
934
+ "aria-labelledby": subContext.triggerId,
935
+ ...subContentProps,
936
+ ref: composedRefs,
937
+ "data-side": dataSide,
938
+ disableOutsidePointerEvents: false,
939
+ disableOutsideScroll: false,
940
+ trapFocus: false,
941
+ onOpenAutoFocus: (event) => {
942
+ if (rootContext.isUsingKeyboardRef.current) {
943
+ const root = ref.current;
944
+ const content = root?.querySelector?.("[data-tamagui-menu-content]");
945
+ (content || root)?.focus({ preventScroll: true });
946
+ }
947
+ event.cancel();
948
+ },
949
+ onCloseAutoFocus: (event) => event.cancel(),
950
+ onFocusOutside: (0, import_web.composeEventHandlers)(props.onFocusOutside, (event) => {
951
+ if (event.event?.target !== subContext.trigger) context.onOpenChange(false);
952
+ }),
953
+ onEscapeKeyDown: (0, import_web.composeEventHandlers)(props.onEscapeKeyDown, (event) => {
954
+ context.onOpenChange(false);
955
+ subContext.trigger?.focus({ focusVisible: true });
956
+ event.cancel();
957
+ event.event?.preventDefault?.();
958
+ }),
959
+ ...import_web.isWeb ? { onKeyDown: (0, import_web.composeEventHandlers)(props.onKeyDown, (event) => {
960
+ const isKeyDownInside = event.currentTarget.contains(event.target);
961
+ const isCloseKey = SUB_CLOSE_KEYS[effectiveDir].includes(event.key);
962
+ if (isKeyDownInside && isCloseKey) {
963
+ context.onOpenChange(false);
964
+ subContext.trigger?.focus({ focusVisible: true });
965
+ event.preventDefault();
966
+ }
967
+ }) } : null
968
+ })
969
+ })
970
+ });
971
+ });
972
+ MenuSubContent.displayName = SUB_CONTENT_NAME;
973
+ const Anchor = MenuAnchor;
974
+ const Portal = MenuPortal;
975
+ const Content = MenuContent;
976
+ const Group = (0, import_core.createStyledHOC)(import_web.View, (props, ref) => {
977
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
978
+ role: "group",
979
+ ...props,
980
+ ref
981
+ });
982
+ });
983
+ Group.displayName = "MenuGroup";
984
+ const Label = (0, import_core.createStyledHOC)(import_web.Text, (props, ref) => {
985
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.Text, {
986
+ ...props,
987
+ ref
988
+ });
989
+ });
990
+ Label.displayName = "MenuLabel";
991
+ const Item = MenuItem;
992
+ const CheckboxItem = MenuCheckboxItem;
993
+ const RadioGroup = MenuRadioGroup;
994
+ const RadioItem = MenuRadioItem;
995
+ const ItemIndicator = MenuItemIndicator;
996
+ const Separator = (0, import_core.createStyledHOC)(import_web.View, (props, ref) => {
997
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_web.View, {
998
+ role: "separator",
999
+ "aria-orientation": "horizontal",
1000
+ ...props,
1001
+ ref
1002
+ });
1003
+ });
1004
+ Separator.displayName = "MenuSeparator";
1005
+ const Arrow = MenuArrow;
1006
+ const Sub = MenuSub;
1007
+ const SubTrigger = MenuSubTrigger;
1008
+ const SubContent = MenuSubContent;
1009
+ const ItemTitle = MenuItemTitle;
1010
+ const ItemSubtitle = MenuItemSubTitle;
1011
+ const ItemImage = MenuItemImage;
1012
+ const ItemIcon = MenuItemIcon;
1013
+ const Menu = (0, import_web.withStaticProperties)(MenuComp, {
1014
+ Anchor,
1015
+ Portal,
1016
+ Content,
1017
+ Group,
1018
+ Label,
1019
+ Item,
1020
+ CheckboxItem,
1021
+ RadioGroup,
1022
+ RadioItem,
1023
+ ItemIndicator,
1024
+ Separator,
1025
+ Arrow,
1026
+ Sub,
1027
+ SubTrigger,
1028
+ SubContent,
1029
+ ItemTitle,
1030
+ ItemSubtitle,
1031
+ ItemImage,
1032
+ ItemIcon
1033
+ });
1034
+ return { Menu };
1246
1035
  }
1247
1036
  function getOpenState(open) {
1248
- return open ? "open" : "closed";
1037
+ return open ? "open" : "closed";
1249
1038
  }
1250
1039
  function isIndeterminate(checked) {
1251
- return checked === "indeterminate";
1040
+ return checked === "indeterminate";
1252
1041
  }
1253
1042
  function getCheckedState(checked) {
1254
- return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
1043
+ return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
1255
1044
  }
1256
1045
  function focusFirst(candidates, options) {
1257
- const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
1258
- for (const candidate of candidates) {
1259
- if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
1260
- candidate.focus({
1261
- preventScroll: true,
1262
- focusVisible: options?.focusVisible
1263
- });
1264
- if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
1265
- }
1046
+ const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
1047
+ for (const candidate of candidates) {
1048
+ if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
1049
+ candidate.focus({
1050
+ preventScroll: true,
1051
+ focusVisible: options?.focusVisible
1052
+ });
1053
+ if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
1054
+ }
1266
1055
  }
1267
1056
  function wrapArray(array, startIndex) {
1268
- return array.map((_, index) => array[(startIndex + index) % array.length]);
1057
+ return array.map((_, index) => array[(startIndex + index) % array.length]);
1269
1058
  }
1270
1059
  function getNextMatch(values, search, currentMatch) {
1271
- const isRepeated = search.length > 1 && Array.from(search).every(char => char === search[0]);
1272
- const normalizedSearch = isRepeated ? search[0] : search;
1273
- const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
1274
- let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
1275
- const excludeCurrentMatch = normalizedSearch.length === 1;
1276
- if (excludeCurrentMatch) wrappedValues = wrappedValues.filter(v => v !== currentMatch);
1277
- const nextMatch = wrappedValues.find(value => value.toLowerCase().startsWith(normalizedSearch.toLowerCase()));
1278
- return nextMatch !== currentMatch ? nextMatch : void 0;
1060
+ const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
1061
+ const normalizedSearch = isRepeated ? search[0] : search;
1062
+ const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
1063
+ let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
1064
+ const excludeCurrentMatch = normalizedSearch.length === 1;
1065
+ if (excludeCurrentMatch) wrappedValues = wrappedValues.filter((v) => v !== currentMatch);
1066
+ const nextMatch = wrappedValues.find((value) => value.toLowerCase().startsWith(normalizedSearch.toLowerCase()));
1067
+ return nextMatch !== currentMatch ? nextMatch : void 0;
1279
1068
  }
1280
1069
  function isPointInPolygon(point, polygon) {
1281
- const {
1282
- x,
1283
- y
1284
- } = point;
1285
- let inside = false;
1286
- for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
1287
- const xi = polygon[i].x;
1288
- const yi = polygon[i].y;
1289
- const xj = polygon[j].x;
1290
- const yj = polygon[j].y;
1291
- const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
1292
- if (intersect) inside = !inside;
1293
- }
1294
- return inside;
1070
+ const { x, y } = point;
1071
+ let inside = false;
1072
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
1073
+ const xi = polygon[i].x;
1074
+ const yi = polygon[i].y;
1075
+ const xj = polygon[j].x;
1076
+ const yj = polygon[j].y;
1077
+ const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
1078
+ if (intersect) inside = !inside;
1079
+ }
1080
+ return inside;
1295
1081
  }
1296
1082
  function isPointerInGraceArea(event, area) {
1297
- if (!area) return false;
1298
- const cursorPos = {
1299
- x: event.clientX,
1300
- y: event.clientY
1301
- };
1302
- return isPointInPolygon(cursorPos, area);
1303
- }
1083
+ if (!area) return false;
1084
+ const cursorPos = {
1085
+ x: event.clientX,
1086
+ y: event.clientY
1087
+ };
1088
+ return isPointInPolygon(cursorPos, area);
1089
+ }