@tamagui/create-menu 1.0.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/MenuPredefined.cjs +189 -0
  3. package/dist/cjs/MenuPredefined.js +168 -0
  4. package/dist/cjs/MenuPredefined.js.map +6 -0
  5. package/dist/cjs/MenuPredefined.native.js +192 -0
  6. package/dist/cjs/MenuPredefined.native.js.map +1 -0
  7. package/dist/cjs/createBaseMenu.cjs +1147 -0
  8. package/dist/cjs/createBaseMenu.js +884 -0
  9. package/dist/cjs/createBaseMenu.js.map +6 -0
  10. package/dist/cjs/createBaseMenu.native.js +1346 -0
  11. package/dist/cjs/createBaseMenu.native.js.map +1 -0
  12. package/dist/cjs/createNativeMenu/createNativeMenu.cjs +242 -0
  13. package/dist/cjs/createNativeMenu/createNativeMenu.js +184 -0
  14. package/dist/cjs/createNativeMenu/createNativeMenu.js.map +6 -0
  15. package/dist/cjs/createNativeMenu/createNativeMenu.native.js +307 -0
  16. package/dist/cjs/createNativeMenu/createNativeMenu.native.js.map +1 -0
  17. package/dist/cjs/createNativeMenu/createNativeMenuTypes.cjs +16 -0
  18. package/dist/cjs/createNativeMenu/createNativeMenuTypes.js +14 -0
  19. package/dist/cjs/createNativeMenu/createNativeMenuTypes.js.map +6 -0
  20. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js +19 -0
  21. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js.map +1 -0
  22. package/dist/cjs/createNativeMenu/utils.cjs +68 -0
  23. package/dist/cjs/createNativeMenu/utils.js +66 -0
  24. package/dist/cjs/createNativeMenu/utils.js.map +6 -0
  25. package/dist/cjs/createNativeMenu/utils.native.js +94 -0
  26. package/dist/cjs/createNativeMenu/utils.native.js.map +1 -0
  27. package/dist/cjs/createNativeMenu/withNativeMenu.cjs +37 -0
  28. package/dist/cjs/createNativeMenu/withNativeMenu.js +30 -0
  29. package/dist/cjs/createNativeMenu/withNativeMenu.js.map +6 -0
  30. package/dist/cjs/createNativeMenu/withNativeMenu.native.js +43 -0
  31. package/dist/cjs/createNativeMenu/withNativeMenu.native.js.map +1 -0
  32. package/dist/cjs/index.cjs +30 -0
  33. package/dist/cjs/index.js +24 -0
  34. package/dist/cjs/index.js.map +6 -0
  35. package/dist/cjs/index.native.js +33 -0
  36. package/dist/cjs/index.native.js.map +1 -0
  37. package/dist/esm/MenuPredefined.js +154 -0
  38. package/dist/esm/MenuPredefined.js.map +6 -0
  39. package/dist/esm/MenuPredefined.mjs +166 -0
  40. package/dist/esm/MenuPredefined.mjs.map +1 -0
  41. package/dist/esm/MenuPredefined.native.js +166 -0
  42. package/dist/esm/MenuPredefined.native.js.map +1 -0
  43. package/dist/esm/createBaseMenu.js +892 -0
  44. package/dist/esm/createBaseMenu.js.map +6 -0
  45. package/dist/esm/createBaseMenu.mjs +1113 -0
  46. package/dist/esm/createBaseMenu.mjs.map +1 -0
  47. package/dist/esm/createBaseMenu.native.js +1309 -0
  48. package/dist/esm/createBaseMenu.native.js.map +1 -0
  49. package/dist/esm/createNativeMenu/createNativeMenu.js +163 -0
  50. package/dist/esm/createNativeMenu/createNativeMenu.js.map +6 -0
  51. package/dist/esm/createNativeMenu/createNativeMenu.mjs +208 -0
  52. package/dist/esm/createNativeMenu/createNativeMenu.mjs.map +1 -0
  53. package/dist/esm/createNativeMenu/createNativeMenu.native.js +270 -0
  54. package/dist/esm/createNativeMenu/createNativeMenu.native.js.map +1 -0
  55. package/dist/esm/createNativeMenu/createNativeMenuTypes.js +1 -0
  56. package/dist/esm/createNativeMenu/createNativeMenuTypes.js.map +6 -0
  57. package/dist/esm/createNativeMenu/createNativeMenuTypes.mjs +2 -0
  58. package/dist/esm/createNativeMenu/createNativeMenuTypes.mjs.map +1 -0
  59. package/dist/esm/createNativeMenu/createNativeMenuTypes.native.js +2 -0
  60. package/dist/esm/createNativeMenu/createNativeMenuTypes.native.js.map +1 -0
  61. package/dist/esm/createNativeMenu/utils.js +47 -0
  62. package/dist/esm/createNativeMenu/utils.js.map +6 -0
  63. package/dist/esm/createNativeMenu/utils.mjs +29 -0
  64. package/dist/esm/createNativeMenu/utils.mjs.map +1 -0
  65. package/dist/esm/createNativeMenu/utils.native.js +52 -0
  66. package/dist/esm/createNativeMenu/utils.native.js.map +1 -0
  67. package/dist/esm/createNativeMenu/withNativeMenu.js +15 -0
  68. package/dist/esm/createNativeMenu/withNativeMenu.js.map +6 -0
  69. package/dist/esm/createNativeMenu/withNativeMenu.mjs +14 -0
  70. package/dist/esm/createNativeMenu/withNativeMenu.mjs.map +1 -0
  71. package/dist/esm/createNativeMenu/withNativeMenu.native.js +17 -0
  72. package/dist/esm/createNativeMenu/withNativeMenu.native.js.map +1 -0
  73. package/dist/esm/index.js +8 -0
  74. package/dist/esm/index.js.map +6 -0
  75. package/dist/esm/index.mjs +6 -0
  76. package/dist/esm/index.mjs.map +1 -0
  77. package/dist/esm/index.native.js +6 -0
  78. package/dist/esm/index.native.js.map +1 -0
  79. package/dist/jsx/MenuPredefined.js +154 -0
  80. package/dist/jsx/MenuPredefined.js.map +6 -0
  81. package/dist/jsx/MenuPredefined.mjs +166 -0
  82. package/dist/jsx/MenuPredefined.mjs.map +1 -0
  83. package/dist/jsx/MenuPredefined.native.js +192 -0
  84. package/dist/jsx/MenuPredefined.native.js.map +1 -0
  85. package/dist/jsx/createBaseMenu.js +892 -0
  86. package/dist/jsx/createBaseMenu.js.map +6 -0
  87. package/dist/jsx/createBaseMenu.mjs +1113 -0
  88. package/dist/jsx/createBaseMenu.mjs.map +1 -0
  89. package/dist/jsx/createBaseMenu.native.js +1346 -0
  90. package/dist/jsx/createBaseMenu.native.js.map +1 -0
  91. package/dist/jsx/createNativeMenu/createNativeMenu.js +163 -0
  92. package/dist/jsx/createNativeMenu/createNativeMenu.js.map +6 -0
  93. package/dist/jsx/createNativeMenu/createNativeMenu.mjs +208 -0
  94. package/dist/jsx/createNativeMenu/createNativeMenu.mjs.map +1 -0
  95. package/dist/jsx/createNativeMenu/createNativeMenu.native.js +307 -0
  96. package/dist/jsx/createNativeMenu/createNativeMenu.native.js.map +1 -0
  97. package/dist/jsx/createNativeMenu/createNativeMenuTypes.js +1 -0
  98. package/dist/jsx/createNativeMenu/createNativeMenuTypes.js.map +6 -0
  99. package/dist/jsx/createNativeMenu/createNativeMenuTypes.mjs +2 -0
  100. package/dist/jsx/createNativeMenu/createNativeMenuTypes.mjs.map +1 -0
  101. package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js +19 -0
  102. package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js.map +1 -0
  103. package/dist/jsx/createNativeMenu/utils.js +47 -0
  104. package/dist/jsx/createNativeMenu/utils.js.map +6 -0
  105. package/dist/jsx/createNativeMenu/utils.mjs +29 -0
  106. package/dist/jsx/createNativeMenu/utils.mjs.map +1 -0
  107. package/dist/jsx/createNativeMenu/utils.native.js +94 -0
  108. package/dist/jsx/createNativeMenu/utils.native.js.map +1 -0
  109. package/dist/jsx/createNativeMenu/withNativeMenu.js +15 -0
  110. package/dist/jsx/createNativeMenu/withNativeMenu.js.map +6 -0
  111. package/dist/jsx/createNativeMenu/withNativeMenu.mjs +14 -0
  112. package/dist/jsx/createNativeMenu/withNativeMenu.mjs.map +1 -0
  113. package/dist/jsx/createNativeMenu/withNativeMenu.native.js +43 -0
  114. package/dist/jsx/createNativeMenu/withNativeMenu.native.js.map +1 -0
  115. package/dist/jsx/index.js +8 -0
  116. package/dist/jsx/index.js.map +6 -0
  117. package/dist/jsx/index.mjs +6 -0
  118. package/dist/jsx/index.mjs.map +1 -0
  119. package/dist/jsx/index.native.js +33 -0
  120. package/dist/jsx/index.native.js.map +1 -0
  121. package/package.json +69 -0
  122. package/src/MenuPredefined.tsx +204 -0
  123. package/src/createBaseMenu.tsx +2031 -0
  124. package/src/createNativeMenu/createNativeMenu.tsx +400 -0
  125. package/src/createNativeMenu/createNativeMenuTypes.ts +214 -0
  126. package/src/createNativeMenu/utils.tsx +150 -0
  127. package/src/createNativeMenu/withNativeMenu.tsx +39 -0
  128. package/src/index.tsx +5 -0
  129. package/types/MenuPredefined.d.ts +31 -0
  130. package/types/MenuPredefined.d.ts.map +1 -0
  131. package/types/createBaseMenu.d.ts +298 -0
  132. package/types/createBaseMenu.d.ts.map +1 -0
  133. package/types/createNativeMenu/createNativeMenu.d.ts +42 -0
  134. package/types/createNativeMenu/createNativeMenu.d.ts.map +1 -0
  135. package/types/createNativeMenu/createNativeMenuTypes.d.ts +188 -0
  136. package/types/createNativeMenu/createNativeMenuTypes.d.ts.map +1 -0
  137. package/types/createNativeMenu/index.d.ts +4 -0
  138. package/types/createNativeMenu/utils.d.ts +38 -0
  139. package/types/createNativeMenu/utils.d.ts.map +1 -0
  140. package/types/createNativeMenu/withNativeMenu.d.ts +9 -0
  141. package/types/createNativeMenu/withNativeMenu.d.ts.map +1 -0
  142. package/types/index.d.ts +6 -0
  143. package/types/index.d.ts.map +1 -0
@@ -0,0 +1,892 @@
1
+ import { Animate } from "@tamagui/animate";
2
+ import { AnimatePresence as Presence } from "@tamagui/animate-presence";
3
+ import { createCollection } from "@tamagui/collection";
4
+ import {
5
+ Dismissable as DismissableLayer,
6
+ dispatchDiscreteCustomEvent
7
+ } from "@tamagui/dismissable";
8
+ import { useFocusGuards } from "@tamagui/focus-guard";
9
+ import { FocusScope } from "@tamagui/focus-scope";
10
+ import * as PopperPrimitive from "@tamagui/popper";
11
+ import { needsPortalRepropagation, Portal as PortalPrimitive } from "@tamagui/portal";
12
+ import { RemoveScroll } from "@tamagui/remove-scroll";
13
+ import { RovingFocusGroup } from "@tamagui/roving-focus";
14
+ import { useCallbackRef } from "@tamagui/use-callback-ref";
15
+ import { useDirection } from "@tamagui/use-direction";
16
+ import {
17
+ composeEventHandlers,
18
+ composeRefs,
19
+ createStyledContext,
20
+ isWeb,
21
+ styled,
22
+ Text,
23
+ Theme,
24
+ useComposedRefs,
25
+ useIsTouchDevice,
26
+ useThemeName,
27
+ View,
28
+ withStaticProperties
29
+ } from "@tamagui/web";
30
+ import * as React from "react";
31
+ import { useId } from "react";
32
+ import { MenuPredefined } from "./MenuPredefined";
33
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
34
+ function whenMouse(handler) {
35
+ return (event) => event.pointerType === "mouse" ? handler(event) : void 0;
36
+ }
37
+ const SELECTION_KEYS = ["Enter", " "], FIRST_KEYS = ["ArrowDown", "PageUp", "Home"], LAST_KEYS = ["ArrowUp", "PageDown", "End"], FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS], SUB_OPEN_KEYS = {
38
+ ltr: [...SELECTION_KEYS, "ArrowRight"],
39
+ rtl: [...SELECTION_KEYS, "ArrowLeft"]
40
+ }, SUB_CLOSE_KEYS = {
41
+ ltr: ["ArrowLeft"],
42
+ rtl: ["ArrowRight"]
43
+ }, MENU_NAME = "Menu", [Collection, useCollection] = createCollection(MENU_NAME), { Provider: MenuProvider, useStyledContext: useMenuContext } = createStyledContext(), { Provider: MenuRootProvider, useStyledContext: useMenuRootContext } = createStyledContext(), MENU_CONTEXT = "MenuContext";
44
+ function createBaseMenu({
45
+ Item: _Item = MenuPredefined.MenuItem,
46
+ Title: _Title = MenuPredefined.Title,
47
+ SubTitle: _SubTitle = MenuPredefined.SubTitle,
48
+ Image: _Image = MenuPredefined.MenuImage,
49
+ Icon: _Icon = MenuPredefined.MenuIcon,
50
+ Indicator: _Indicator = MenuPredefined.MenuIndicator,
51
+ Separator: _Separator = MenuPredefined.MenuSeparator,
52
+ MenuGroup: _MenuGroup = MenuPredefined.MenuGroup,
53
+ Label: _Label = MenuPredefined.MenuLabel
54
+ }) {
55
+ const MenuComp = (props) => {
56
+ const direction = useDirection(props.dir), defaultPlacement = direction === "rtl" ? "bottom-end" : "bottom-start", {
57
+ scope = MENU_CONTEXT,
58
+ open = !1,
59
+ children,
60
+ dir,
61
+ onOpenChange,
62
+ modal = !0,
63
+ allowFlip = { padding: 10 },
64
+ stayInFrame = { padding: 10 },
65
+ placement = defaultPlacement,
66
+ resize = !0,
67
+ offset = 10,
68
+ ...rest
69
+ } = props, [content, setContent] = React.useState(null), isUsingKeyboardRef = React.useRef(!1), handleOpenChange = useCallbackRef(onOpenChange);
70
+ return isWeb && React.useEffect(() => {
71
+ const handleKeyDown = () => {
72
+ isUsingKeyboardRef.current = !0, document.addEventListener("pointerdown", handlePointer, {
73
+ capture: !0,
74
+ once: !0
75
+ }), document.addEventListener("pointermove", handlePointer, {
76
+ capture: !0,
77
+ once: !0
78
+ });
79
+ }, handlePointer = () => isUsingKeyboardRef.current = !1;
80
+ return document.addEventListener("keydown", handleKeyDown, { capture: !0 }), () => {
81
+ document.removeEventListener("keydown", handleKeyDown, { capture: !0 }), document.removeEventListener("pointerdown", handlePointer, { capture: !0 }), document.removeEventListener("pointermove", handlePointer, { capture: !0 });
82
+ };
83
+ }, []), /* @__PURE__ */ jsx(
84
+ PopperPrimitive.Popper,
85
+ {
86
+ scope,
87
+ open,
88
+ placement,
89
+ allowFlip,
90
+ stayInFrame,
91
+ resize,
92
+ offset,
93
+ ...rest,
94
+ children: /* @__PURE__ */ jsx(
95
+ MenuProvider,
96
+ {
97
+ scope,
98
+ open,
99
+ onOpenChange: handleOpenChange,
100
+ content,
101
+ onContentChange: setContent,
102
+ children: /* @__PURE__ */ jsx(
103
+ MenuRootProvider,
104
+ {
105
+ scope,
106
+ open,
107
+ onClose: React.useCallback(() => handleOpenChange(!1), [handleOpenChange]),
108
+ isUsingKeyboardRef,
109
+ dir: direction,
110
+ modal,
111
+ children: /* @__PURE__ */ jsx(MenuSubProvider, { scope, children })
112
+ }
113
+ )
114
+ }
115
+ )
116
+ }
117
+ );
118
+ }, RepropagateMenuAndMenuRootProvider = (props) => {
119
+ const {
120
+ scope = MENU_CONTEXT,
121
+ menuContext,
122
+ rootContext,
123
+ popperContext,
124
+ menuSubContext,
125
+ children
126
+ } = props;
127
+ return /* @__PURE__ */ jsx(PopperPrimitive.PopperProvider, { ...popperContext, scope, children: /* @__PURE__ */ jsx(MenuProvider, { scope, ...menuContext, children: /* @__PURE__ */ jsx(MenuRootProvider, { scope, ...rootContext, children: menuSubContext ? /* @__PURE__ */ jsx(MenuSubProvider, { scope, ...menuSubContext, children }) : children }) }) });
128
+ };
129
+ MenuComp.displayName = MENU_NAME;
130
+ const ANCHOR_NAME = "MenuAnchor", MenuAnchor = (props) => /* @__PURE__ */ jsx(PopperPrimitive.PopperAnchor, { scope: MENU_CONTEXT, ...props });
131
+ MenuAnchor.displayName = ANCHOR_NAME;
132
+ const PORTAL_NAME = "MenuPortal", { Provider: PortalProvider, useStyledContext: usePortalContext } = createStyledContext(void 0, "Portal"), MenuPortal = (props) => {
133
+ const { scope = MENU_CONTEXT, forceMount, zIndex, children } = props, menuContext = useMenuContext(scope), rootContext = useMenuRootContext(scope), popperContext = PopperPrimitive.usePopperContext(scope), menuSubContext = useMenuSubContext(scope), themeName = useThemeName(), themedChildren = /* @__PURE__ */ jsx(Theme, { forceClassName: !0, name: themeName, children }), content = needsPortalRepropagation() ? /* @__PURE__ */ jsx(
134
+ RepropagateMenuAndMenuRootProvider,
135
+ {
136
+ menuContext,
137
+ rootContext,
138
+ popperContext,
139
+ menuSubContext,
140
+ scope,
141
+ children: themedChildren
142
+ }
143
+ ) : themedChildren, isPresent = forceMount || rootContext.open && menuContext.open;
144
+ return /* @__PURE__ */ jsx(Animate, { type: "presence", present: isPresent, children: /* @__PURE__ */ jsx(PortalPrimitive, { stackZIndex: !0, children: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PortalProvider, { scope, forceMount, children: /* @__PURE__ */ jsxs(View, { zIndex: zIndex || 100, inset: 0, position: "absolute", children: [
145
+ !!menuContext.open && !isWeb && /* @__PURE__ */ jsx(
146
+ View,
147
+ {
148
+ inset: 0,
149
+ position: "absolute",
150
+ onPress: () => menuContext.onOpenChange(!menuContext.open)
151
+ }
152
+ ),
153
+ content
154
+ ] }) }) }) }) });
155
+ };
156
+ MenuPortal.displayName = PORTAL_NAME;
157
+ const CONTENT_NAME = "MenuContent", { Provider: MenuContentProvider, useStyledContext: useMenuContentContext } = createStyledContext(), MenuContent = styled(PopperPrimitive.PopperContentFrame, {
158
+ name: CONTENT_NAME
159
+ }).styleable(
160
+ (props, forwardedRef) => {
161
+ const scope = props.scope || MENU_CONTEXT, portalContext = usePortalContext(scope), { forceMount = portalContext.forceMount, ...contentProps } = props, rootContext = useMenuRootContext(scope);
162
+ return /* @__PURE__ */ jsx(Collection.Provider, { scope, children: /* @__PURE__ */ jsx(Collection.Slot, { scope, children: rootContext.modal ? /* @__PURE__ */ jsx(MenuRootContentModal, { ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ jsx(MenuRootContentNonModal, { ...contentProps, ref: forwardedRef }) }) });
163
+ }
164
+ ), MenuRootContentModal = React.forwardRef((props, forwardedRef) => {
165
+ const scope = props.scope || MENU_CONTEXT, context = useMenuContext(scope), ref = React.useRef(null), composedRefs = useComposedRefs(forwardedRef, ref);
166
+ return /* @__PURE__ */ jsx(
167
+ MenuContentImpl,
168
+ {
169
+ ...props,
170
+ scope,
171
+ ref: composedRefs,
172
+ trapFocus: context.open,
173
+ disableOutsidePointerEvents: context.open,
174
+ disableOutsideScroll: !1,
175
+ onFocusOutside: composeEventHandlers(
176
+ props.onFocusOutside,
177
+ (event) => event.preventDefault(),
178
+ { checkDefaultPrevented: !1 }
179
+ ),
180
+ onDismiss: () => context.onOpenChange(!1)
181
+ }
182
+ );
183
+ }), MenuRootContentNonModal = React.forwardRef((props, forwardedRef) => {
184
+ const scope = props.scope || MENU_CONTEXT, context = useMenuContext(scope);
185
+ return /* @__PURE__ */ jsx(
186
+ MenuContentImpl,
187
+ {
188
+ ...props,
189
+ scope,
190
+ ref: forwardedRef,
191
+ trapFocus: !1,
192
+ disableOutsidePointerEvents: !1,
193
+ disableOutsideScroll: !1,
194
+ onDismiss: () => context.onOpenChange(!1)
195
+ }
196
+ );
197
+ }), MenuContentImpl = React.forwardRef((props, forwardedRef) => {
198
+ const {
199
+ scope = MENU_CONTEXT,
200
+ loop = !1,
201
+ trapFocus,
202
+ onOpenAutoFocus,
203
+ onCloseAutoFocus,
204
+ disableOutsidePointerEvents,
205
+ onEntryFocus,
206
+ onEscapeKeyDown,
207
+ onPointerDownOutside,
208
+ onFocusOutside,
209
+ onInteractOutside,
210
+ onDismiss,
211
+ disableOutsideScroll,
212
+ disableDismissOnScroll = !1,
213
+ unstyled = process.env.TAMAGUI_HEADLESS === "1",
214
+ ...contentProps
215
+ } = props, context = useMenuContext(scope), rootContext = useMenuRootContext(scope), getItems = useCollection(scope), [currentItemId, setCurrentItemId] = React.useState(null), contentRef = React.useRef(null), focusableContentRef = React.useRef(null), composedRefs = useComposedRefs(
216
+ forwardedRef,
217
+ contentRef,
218
+ context.onContentChange
219
+ ), timerRef = React.useRef(0), searchRef = React.useRef(""), pointerGraceTimerRef = React.useRef(0), pointerGraceIntentRef = React.useRef(null), pointerDirRef = React.useRef("right"), lastPointerXRef = React.useRef(0), handleTypeaheadSearch = (key) => {
220
+ const search = searchRef.current + key, items = getItems().filter((item) => !item.disabled), currentItem = document.activeElement, currentMatch = items.find(
221
+ (item) => item.ref.current === currentItem
222
+ )?.textValue, values = items.map((item) => item.textValue), nextMatch = getNextMatch(values, search, currentMatch), newItem = items.find((item) => item.textValue === nextMatch)?.ref.current;
223
+ (function updateSearch(value) {
224
+ searchRef.current = value, clearTimeout(timerRef.current), value !== "" && (timerRef.current = setTimeout(() => updateSearch(""), 1e3));
225
+ })(search), newItem && setTimeout(() => newItem.focus());
226
+ };
227
+ React.useEffect(() => () => clearTimeout(timerRef.current), []), React.useEffect(() => {
228
+ if (!isWeb || !context.open) return;
229
+ const frame = requestAnimationFrame(() => {
230
+ const el = contentRef.current?.querySelector("[data-tamagui-menu-content]");
231
+ el && (focusableContentRef.current = el);
232
+ });
233
+ return () => cancelAnimationFrame(frame);
234
+ }, [context.open]), React.useEffect(() => {
235
+ if (!isWeb || disableDismissOnScroll || !context.open) return;
236
+ const handleScroll = (event) => {
237
+ const target = event.target;
238
+ contentRef.current?.contains(target) || onDismiss?.();
239
+ };
240
+ return window.addEventListener("scroll", handleScroll, { capture: !0, passive: !0 }), () => {
241
+ window.removeEventListener("scroll", handleScroll, { capture: !0 });
242
+ };
243
+ }, [disableDismissOnScroll, context.open, onDismiss]), isWeb && useFocusGuards();
244
+ const isPointerMovingToSubmenu = React.useCallback((event) => {
245
+ const isMovingTowards = pointerDirRef.current === pointerGraceIntentRef.current?.side, inArea = isPointerInGraceArea(event, pointerGraceIntentRef.current?.area);
246
+ return isMovingTowards && inArea;
247
+ }, []), content = /* @__PURE__ */ jsx(
248
+ PopperPrimitive.PopperContent,
249
+ {
250
+ role: "menu",
251
+ tabIndex: -1,
252
+ unstyled,
253
+ ...!unstyled && {
254
+ backgroundColor: "$background",
255
+ borderWidth: 1,
256
+ borderColor: "$borderColor",
257
+ outlineWidth: 0,
258
+ minWidth: 180
259
+ },
260
+ "aria-orientation": "vertical",
261
+ "data-state": getOpenState(context.open),
262
+ "data-tamagui-menu-content": "",
263
+ dir: rootContext.dir,
264
+ scope: scope || MENU_CONTEXT,
265
+ ...contentProps,
266
+ ref: composedRefs,
267
+ className: contentProps.transition ? void 0 : contentProps.className,
268
+ ...isWeb ? {
269
+ onKeyDown: composeEventHandlers(contentProps.onKeyDown, (event) => {
270
+ const isKeyDownInside = event.target.closest("[data-tamagui-menu-content]") === event.currentTarget, isModifierKey = event.ctrlKey || event.altKey || event.metaKey, isCharacterKey = event.key.length === 1;
271
+ isKeyDownInside && (event.key === "Tab" && event.preventDefault(), !isModifierKey && isCharacterKey && handleTypeaheadSearch(event.key));
272
+ const isOnContentFrame = event.target.hasAttribute(
273
+ "data-tamagui-menu-content"
274
+ );
275
+ if (!isKeyDownInside || !isOnContentFrame || !FIRST_LAST_KEYS.includes(event.key)) return;
276
+ event.preventDefault();
277
+ const candidateNodes = getItems().filter((item) => !item.disabled).map((item) => item.ref.current);
278
+ LAST_KEYS.includes(event.key) && candidateNodes.reverse(), focusFirst(candidateNodes, { focusVisible: !0 });
279
+ }),
280
+ // TODO
281
+ // @ts-ignore
282
+ onBlur: composeEventHandlers(props.onBlur, (event) => {
283
+ event.currentTarget?.contains(event.target) || (clearTimeout(timerRef.current), searchRef.current = "");
284
+ }),
285
+ // TODO
286
+ onPointerMove: composeEventHandlers(props.onPointerMove, (event) => {
287
+ if (event.pointerType !== "mouse") return;
288
+ const target = event.target, pointerXHasChanged = lastPointerXRef.current !== event.clientX;
289
+ if (event.currentTarget?.contains(target) && pointerXHasChanged) {
290
+ const newDir = event.clientX > lastPointerXRef.current ? "right" : "left";
291
+ pointerDirRef.current = newDir, lastPointerXRef.current = event.clientX;
292
+ }
293
+ })
294
+ } : {}
295
+ }
296
+ );
297
+ return /* @__PURE__ */ jsx(
298
+ MenuContentProvider,
299
+ {
300
+ scope,
301
+ searchRef,
302
+ onItemEnter: React.useCallback(
303
+ (event) => {
304
+ isPointerMovingToSubmenu(event) && event.preventDefault();
305
+ },
306
+ [isPointerMovingToSubmenu]
307
+ ),
308
+ onItemLeave: React.useCallback(
309
+ (event) => {
310
+ isPointerMovingToSubmenu(event) || (focusableContentRef.current?.focus(), setCurrentItemId(null));
311
+ },
312
+ [isPointerMovingToSubmenu]
313
+ ),
314
+ onTriggerLeave: React.useCallback(
315
+ (event) => {
316
+ isPointerMovingToSubmenu(event) && event.preventDefault();
317
+ },
318
+ [isPointerMovingToSubmenu]
319
+ ),
320
+ pointerGraceTimerRef,
321
+ onPointerGraceIntentChange: React.useCallback((intent) => {
322
+ pointerGraceIntentRef.current = intent;
323
+ }, []),
324
+ children: /* @__PURE__ */ jsx(RemoveScroll, { enabled: disableOutsideScroll, children: /* @__PURE__ */ jsx(
325
+ FocusScope,
326
+ {
327
+ asChild: !1,
328
+ trapped: trapFocus,
329
+ onMountAutoFocus: composeEventHandlers(onOpenAutoFocus, (event) => {
330
+ event.preventDefault(), document.querySelector(
331
+ "[data-tamagui-menu-content]"
332
+ )?.focus({ preventScroll: !0 });
333
+ }),
334
+ onUnmountAutoFocus: onCloseAutoFocus,
335
+ children: /* @__PURE__ */ jsx(
336
+ DismissableLayer,
337
+ {
338
+ disableOutsidePointerEvents,
339
+ onEscapeKeyDown,
340
+ onPointerDownOutside,
341
+ onFocusOutside,
342
+ onInteractOutside,
343
+ onDismiss,
344
+ asChild: !0,
345
+ children: /* @__PURE__ */ jsx(
346
+ RovingFocusGroup,
347
+ {
348
+ asChild: !0,
349
+ __scopeRovingFocusGroup: scope || MENU_CONTEXT,
350
+ dir: rootContext.dir,
351
+ orientation: "vertical",
352
+ loop,
353
+ currentTabStopId: currentItemId,
354
+ onCurrentTabStopIdChange: setCurrentItemId,
355
+ onEntryFocus: composeEventHandlers(onEntryFocus, (event) => {
356
+ rootContext.isUsingKeyboardRef.current || event.preventDefault();
357
+ }),
358
+ children: content
359
+ }
360
+ )
361
+ }
362
+ )
363
+ }
364
+ ) })
365
+ }
366
+ );
367
+ });
368
+ MenuContent.displayName = CONTENT_NAME;
369
+ const ITEM_NAME = "MenuItem", ITEM_SELECT = "menu.itemSelect", MenuItem = _Item.styleable((props, forwardedRef) => {
370
+ const {
371
+ disabled = !1,
372
+ onSelect,
373
+ preventCloseOnSelect,
374
+ children,
375
+ scope = MENU_CONTEXT,
376
+ // filter out native-only props that shouldn't reach the DOM
377
+ // @ts-ignore
378
+ destructive,
379
+ // @ts-ignore
380
+ hidden,
381
+ // @ts-ignore
382
+ androidIconName,
383
+ // @ts-ignore
384
+ iosIconName,
385
+ ...itemProps
386
+ } = props, ref = React.useRef(null), rootContext = useMenuRootContext(scope), contentContext = useMenuContentContext(scope), composedRefs = useComposedRefs(forwardedRef, ref), isPointerDownRef = React.useRef(!1), handleSelect = () => {
387
+ const menuItem = ref.current;
388
+ if (!disabled && menuItem)
389
+ if (isWeb) {
390
+ const menuItemEl = menuItem, itemSelectEvent = new CustomEvent(ITEM_SELECT, {
391
+ bubbles: !0,
392
+ cancelable: !0
393
+ });
394
+ menuItemEl.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), {
395
+ once: !0
396
+ }), dispatchDiscreteCustomEvent(menuItemEl, itemSelectEvent), itemSelectEvent.defaultPrevented || preventCloseOnSelect ? isPointerDownRef.current = !1 : rootContext.onClose();
397
+ } else
398
+ onSelect?.({ target: menuItem }), isPointerDownRef.current = !1, preventCloseOnSelect || rootContext.onClose();
399
+ }, content = typeof children == "string" ? /* @__PURE__ */ jsx(Text, { children }) : children;
400
+ return /* @__PURE__ */ jsx(
401
+ MenuItemImpl,
402
+ {
403
+ outlineStyle: "none",
404
+ ...itemProps,
405
+ scope,
406
+ ref: composedRefs,
407
+ disabled,
408
+ onPress: composeEventHandlers(props.onPress, handleSelect),
409
+ onPointerDown: (event) => {
410
+ props.onPointerDown?.(event), isPointerDownRef.current = !0;
411
+ },
412
+ onPointerUp: composeEventHandlers(props.onPointerUp, (event) => {
413
+ isWeb && (isPointerDownRef.current || event.currentTarget?.click());
414
+ }),
415
+ ...isWeb ? {
416
+ onKeyDown: composeEventHandlers(props.onKeyDown, (event) => {
417
+ const isTypingAhead = contentContext.searchRef.current !== "";
418
+ disabled || isTypingAhead && event.key === " " || SELECTION_KEYS.includes(event.key) && (event.currentTarget?.click(), event.preventDefault());
419
+ })
420
+ } : {},
421
+ children: content
422
+ }
423
+ );
424
+ }), MenuItemImpl = React.forwardRef((props, forwardedRef) => {
425
+ const {
426
+ scope = MENU_CONTEXT,
427
+ disabled = !1,
428
+ textValue,
429
+ unstyled = process.env.TAMAGUI_HEADLESS === "1",
430
+ ...itemProps
431
+ } = props, contentContext = useMenuContentContext(scope), ref = React.useRef(null), composedRefs = useComposedRefs(forwardedRef, ref), [isFocused, setIsFocused] = React.useState(!1), [textContent, setTextContent] = React.useState("");
432
+ return isWeb && React.useEffect(() => {
433
+ const menuItem = ref.current;
434
+ menuItem && setTextContent((menuItem.textContent ?? "").trim());
435
+ }, [itemProps.children]), /* @__PURE__ */ jsx(
436
+ Collection.ItemSlot,
437
+ {
438
+ scope,
439
+ disabled,
440
+ textValue: textValue ?? textContent,
441
+ children: /* @__PURE__ */ jsx(
442
+ RovingFocusGroup.Item,
443
+ {
444
+ asChild: !0,
445
+ __scopeRovingFocusGroup: scope,
446
+ focusable: !disabled,
447
+ children: /* @__PURE__ */ jsx(
448
+ _Item,
449
+ {
450
+ unstyled,
451
+ componentName: ITEM_NAME,
452
+ role: "menuitem",
453
+ "data-highlighted": isFocused ? "" : void 0,
454
+ "aria-disabled": disabled || void 0,
455
+ "data-disabled": disabled ? "" : void 0,
456
+ ...itemProps,
457
+ ref: composedRefs,
458
+ onPointerMove: composeEventHandlers(props.onPointerMove, (event) => {
459
+ event.pointerType === "mouse" && (disabled ? contentContext.onItemLeave(event) : (contentContext.onItemEnter(event), event.defaultPrevented || event.currentTarget.focus({ preventScroll: !0, focusVisible: !1 })));
460
+ }),
461
+ onPointerLeave: composeEventHandlers(props.onPointerLeave, (event) => {
462
+ contentContext.onItemLeave(event);
463
+ }),
464
+ onFocus: composeEventHandlers(props.onFocus, () => setIsFocused(!0)),
465
+ onBlur: composeEventHandlers(props.onBlur, () => setIsFocused(!1))
466
+ }
467
+ )
468
+ }
469
+ )
470
+ }
471
+ );
472
+ });
473
+ MenuItem.displayName = ITEM_NAME;
474
+ const ITEM_TITLE_NAME = "MenuItemTitle", MenuItemTitle = _Title.styleable((props, forwardedRef) => /* @__PURE__ */ jsx(_Title, { ...props, ref: forwardedRef }));
475
+ MenuItemTitle.displayName = ITEM_TITLE_NAME;
476
+ const ITEM_SUB_TITLE_NAME = "MenuItemSubTitle", MenuItemSubTitle = _SubTitle.styleable(
477
+ (props, forwardedRef) => /* @__PURE__ */ jsx(_SubTitle, { ...props, ref: forwardedRef })
478
+ );
479
+ MenuItemSubTitle.displayName = ITEM_SUB_TITLE_NAME;
480
+ const ITEM_IMAGE = "MenuItemImage", MenuItemImage = React.forwardRef((props, forwardedRef) => {
481
+ const {
482
+ // @ts-ignore - native menu ios config
483
+ ios,
484
+ // @ts-ignore
485
+ androidIconName,
486
+ // @ts-ignore
487
+ iosIconName,
488
+ ...rest
489
+ } = props;
490
+ return /* @__PURE__ */ jsx(_Image, { ...rest, ref: forwardedRef });
491
+ });
492
+ MenuItemImage.displayName = ITEM_IMAGE;
493
+ const ITEM_ICON = "MenuItemIcon", MenuItemIcon = _Icon.styleable((props, forwardedRef) => {
494
+ const {
495
+ // @ts-ignore
496
+ ios,
497
+ // @ts-ignore
498
+ android,
499
+ // @ts-ignore
500
+ androidIconName,
501
+ // @ts-ignore
502
+ iosIconName,
503
+ ...rest
504
+ } = props;
505
+ return /* @__PURE__ */ jsx(_Icon, { ...rest, ref: forwardedRef });
506
+ });
507
+ MenuItemIcon.displayName = ITEM_ICON;
508
+ const CHECKBOX_ITEM_NAME = "MenuCheckboxItem", MenuCheckboxItem = _Item.styleable(
509
+ (props, forwardedRef) => {
510
+ const {
511
+ checked = !1,
512
+ onCheckedChange,
513
+ scope = MENU_CONTEXT,
514
+ // filter out native-only props
515
+ // @ts-ignore - native menu value state
516
+ value,
517
+ // @ts-ignore - native menu value change handler
518
+ onValueChange,
519
+ ...checkboxItemProps
520
+ } = props;
521
+ return /* @__PURE__ */ jsx(ItemIndicatorProvider, { scope, checked, children: /* @__PURE__ */ jsx(
522
+ MenuItem,
523
+ {
524
+ componentName: CHECKBOX_ITEM_NAME,
525
+ role: isWeb ? "menuitemcheckbox" : "menuitem",
526
+ "aria-checked": isIndeterminate(checked) ? "mixed" : checked,
527
+ ...checkboxItemProps,
528
+ scope,
529
+ ref: forwardedRef,
530
+ "data-state": getCheckedState(checked),
531
+ onSelect: composeEventHandlers(
532
+ checkboxItemProps.onSelect,
533
+ () => onCheckedChange?.(isIndeterminate(checked) ? !0 : !checked),
534
+ { checkDefaultPrevented: !1 }
535
+ )
536
+ }
537
+ ) });
538
+ }
539
+ );
540
+ MenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME;
541
+ const RADIO_GROUP_NAME = "MenuRadioGroup", { Provider: RadioGroupProvider, useStyledContext: useRadioGroupContext } = createStyledContext(), MenuRadioGroup = _MenuGroup.styleable(
542
+ (props, forwardedRef) => {
543
+ const { value, onValueChange, scope = MENU_CONTEXT, ...groupProps } = props, handleValueChange = useCallbackRef(onValueChange);
544
+ return /* @__PURE__ */ jsx(RadioGroupProvider, { scope, value, onValueChange: handleValueChange, children: /* @__PURE__ */ jsx(
545
+ _MenuGroup,
546
+ {
547
+ componentName: RADIO_GROUP_NAME,
548
+ ...groupProps,
549
+ ref: forwardedRef
550
+ }
551
+ ) });
552
+ }
553
+ );
554
+ MenuRadioGroup.displayName = RADIO_GROUP_NAME;
555
+ const RADIO_ITEM_NAME = "MenuRadioItem", MenuRadioItem = _Item.styleable(
556
+ (props, forwardedRef) => {
557
+ const { value, scope = MENU_CONTEXT, ...radioItemProps } = props, context = useRadioGroupContext(scope), checked = value === context.value;
558
+ return /* @__PURE__ */ jsx(ItemIndicatorProvider, { scope, checked, children: /* @__PURE__ */ jsx(
559
+ MenuItem,
560
+ {
561
+ componentName: RADIO_ITEM_NAME,
562
+ ...radioItemProps,
563
+ scope,
564
+ "aria-checked": checked,
565
+ ref: forwardedRef,
566
+ role: isWeb ? "menuitemradio" : "menuitem",
567
+ "data-state": getCheckedState(checked),
568
+ onSelect: composeEventHandlers(
569
+ radioItemProps.onSelect,
570
+ () => context.onValueChange?.(value),
571
+ { checkDefaultPrevented: !1 }
572
+ )
573
+ }
574
+ ) });
575
+ }
576
+ );
577
+ MenuRadioItem.displayName = RADIO_ITEM_NAME;
578
+ const ITEM_INDICATOR_NAME = "MenuItemIndicator", { Provider: ItemIndicatorProvider, useStyledContext: useItemIndicatorContext } = createStyledContext(), MenuItemIndicator = _Indicator.styleable(
579
+ (props, forwardedRef) => {
580
+ const { scope = MENU_CONTEXT, forceMount, ...itemIndicatorProps } = props, indicatorContext = useItemIndicatorContext(scope);
581
+ return /* @__PURE__ */ jsx(Presence, { children: forceMount || isIndeterminate(indicatorContext.checked) || indicatorContext.checked === !0 ? /* @__PURE__ */ jsx(
582
+ _Indicator,
583
+ {
584
+ componentName: ITEM_INDICATOR_NAME,
585
+ render: "span",
586
+ ...itemIndicatorProps,
587
+ ref: forwardedRef,
588
+ "data-state": getCheckedState(indicatorContext.checked)
589
+ }
590
+ ) : null });
591
+ }
592
+ );
593
+ MenuItemIndicator.displayName = ITEM_INDICATOR_NAME;
594
+ const MenuArrow = React.forwardRef(
595
+ function(props, forwardedRef) {
596
+ const {
597
+ scope = MENU_CONTEXT,
598
+ unstyled = process.env.TAMAGUI_HEADLESS === "1",
599
+ ...rest
600
+ } = props;
601
+ return /* @__PURE__ */ jsx(
602
+ PopperPrimitive.PopperArrow,
603
+ {
604
+ scope,
605
+ componentName: "PopperArrow",
606
+ unstyled,
607
+ ...!unstyled && {
608
+ backgroundColor: "$background"
609
+ },
610
+ ...rest,
611
+ ref: forwardedRef
612
+ }
613
+ );
614
+ }
615
+ ), SUB_NAME = "MenuSub", { Provider: MenuSubProvider, useStyledContext: useMenuSubContext } = createStyledContext(), MenuSub = (props) => {
616
+ const isTouchDevice = useIsTouchDevice(), { scope = MENU_CONTEXT } = props, rootContext = useMenuRootContext(scope), parentSide = PopperPrimitive.usePopperContext(scope).placement?.split("-")[0], isNestedSubmenu = parentSide === "left" || parentSide === "right", defaultPlacement = isTouchDevice ? "bottom" : isNestedSubmenu ? `${parentSide}-start` : rootContext.dir === "rtl" ? "left-start" : "right-start", {
617
+ children,
618
+ open = !1,
619
+ onOpenChange,
620
+ allowFlip: allowFlipProp = { padding: 10 },
621
+ stayInFrame = { padding: 10 },
622
+ placement = defaultPlacement,
623
+ ...rest
624
+ } = props, allowFlip = React.useMemo(() => {
625
+ if (!isNestedSubmenu || typeof allowFlipProp == "boolean" || allowFlipProp.fallbackPlacements) return allowFlipProp;
626
+ const side = placement.split("-")[0], align = placement.split("-")[1] || "start", otherAlign = align === "start" ? "end" : "start";
627
+ if (side === "left" || side === "right") {
628
+ const oppositeSide = side === "right" ? "left" : "right";
629
+ return {
630
+ ...typeof allowFlipProp == "object" ? allowFlipProp : {},
631
+ fallbackPlacements: [
632
+ `${side}-${otherAlign}`,
633
+ `${oppositeSide}-${align}`,
634
+ `${oppositeSide}-${otherAlign}`
635
+ ]
636
+ };
637
+ }
638
+ return allowFlipProp;
639
+ }, [isNestedSubmenu, allowFlipProp, placement]), parentMenuContext = useMenuContext(scope), [trigger, setTrigger] = React.useState(null), [content, setContent] = React.useState(null), handleOpenChange = useCallbackRef(onOpenChange);
640
+ return React.useEffect(() => (parentMenuContext.open === !1 && handleOpenChange(!1), () => handleOpenChange(!1)), [parentMenuContext.open, handleOpenChange]), /* @__PURE__ */ jsx(
641
+ PopperPrimitive.Popper,
642
+ {
643
+ open,
644
+ placement,
645
+ allowFlip,
646
+ stayInFrame,
647
+ ...rest,
648
+ scope,
649
+ children: /* @__PURE__ */ jsx(
650
+ MenuProvider,
651
+ {
652
+ scope,
653
+ open,
654
+ onOpenChange: handleOpenChange,
655
+ content,
656
+ onContentChange: setContent,
657
+ children: /* @__PURE__ */ jsx(
658
+ MenuSubProvider,
659
+ {
660
+ scope,
661
+ contentId: useId(),
662
+ triggerId: useId(),
663
+ trigger,
664
+ onTriggerChange: setTrigger,
665
+ children
666
+ }
667
+ )
668
+ }
669
+ )
670
+ }
671
+ );
672
+ };
673
+ MenuSub.displayName = SUB_NAME;
674
+ const SUB_TRIGGER_NAME = "MenuSubTrigger", MenuSubTrigger = React.forwardRef((props, forwardedRef) => {
675
+ const scope = props.scope || MENU_CONTEXT, context = useMenuContext(scope), rootContext = useMenuRootContext(scope), subContext = useMenuSubContext(scope), contentContext = useMenuContentContext(scope), popperContext = PopperPrimitive.usePopperContext(scope), openTimerRef = React.useRef(null), { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext, effectiveDir = rootContext.dir, clearOpenTimer = React.useCallback(() => {
676
+ openTimerRef.current && window.clearTimeout(openTimerRef.current), openTimerRef.current = null;
677
+ }, []);
678
+ return React.useEffect(() => clearOpenTimer, [clearOpenTimer]), React.useEffect(() => {
679
+ const pointerGraceTimer = pointerGraceTimerRef.current;
680
+ return () => {
681
+ window.clearTimeout(pointerGraceTimer), onPointerGraceIntentChange(null);
682
+ };
683
+ }, [pointerGraceTimerRef, onPointerGraceIntentChange]), /* @__PURE__ */ jsx(MenuAnchor, { componentName: SUB_TRIGGER_NAME, asChild: "except-style", scope, children: /* @__PURE__ */ jsx(
684
+ MenuItemImpl,
685
+ {
686
+ id: subContext.triggerId,
687
+ "aria-haspopup": "menu",
688
+ "aria-expanded": context.open,
689
+ "aria-controls": subContext.contentId,
690
+ "data-state": getOpenState(context.open),
691
+ outlineStyle: "none",
692
+ ...props,
693
+ ref: composeRefs(forwardedRef, subContext.onTriggerChange),
694
+ onPress: (event) => {
695
+ props.onPress?.(event), !(props.disabled || event.defaultPrevented) && (isWeb && event.currentTarget.focus(), context.open || context.onOpenChange(!0));
696
+ },
697
+ onPointerMove: composeEventHandlers(
698
+ props.onPointerMove,
699
+ // @ts-ignore
700
+ whenMouse((event) => {
701
+ contentContext.onItemEnter(event), !event.defaultPrevented && !props.disabled && !context.open && !openTimerRef.current && (contentContext.onPointerGraceIntentChange(null), openTimerRef.current = window.setTimeout(() => {
702
+ context.onOpenChange(!0), clearOpenTimer();
703
+ }, 100));
704
+ })
705
+ ),
706
+ onPointerLeave: composeEventHandlers(props.onPointerLeave, (eventIn) => {
707
+ const event = eventIn;
708
+ clearOpenTimer();
709
+ const contentRect = context.content?.getBoundingClientRect();
710
+ if (contentRect) {
711
+ const contentEl = context.content, side = (contentEl?.dataset?.side ? contentEl : contentEl?.querySelector("[data-side]"))?.dataset?.side || "right", rightSide = side === "right", bleed = rightSide ? -5 : 5, contentNearEdge = contentRect[rightSide ? "left" : "right"], contentFarEdge = contentRect[rightSide ? "right" : "left"], polygon = {
712
+ area: [
713
+ // Apply a bleed on clientX to ensure that our exit point is
714
+ // consistently within polygon bounds
715
+ { x: event.clientX + bleed, y: event.clientY },
716
+ { x: contentNearEdge, y: contentRect.top },
717
+ { x: contentFarEdge, y: contentRect.top },
718
+ { x: contentFarEdge, y: contentRect.bottom },
719
+ { x: contentNearEdge, y: contentRect.bottom }
720
+ ],
721
+ side
722
+ };
723
+ contentContext.onPointerGraceIntentChange(polygon), window.clearTimeout(pointerGraceTimerRef.current), pointerGraceTimerRef.current = window.setTimeout(
724
+ () => contentContext.onPointerGraceIntentChange(null),
725
+ 300
726
+ );
727
+ } else if (isWeb && subContext.trigger) {
728
+ const triggerRect = subContext.trigger?.getBoundingClientRect();
729
+ if (triggerRect) {
730
+ const placementSide = popperContext.placement?.split("-")[0], side = placementSide === "left" || placementSide === "right" ? placementSide : rootContext.dir === "rtl" ? "left" : "right", rightSide = side === "right", bleed = rightSide ? -5 : 5, nearEdge = rightSide ? triggerRect.right + 4 : triggerRect.left - 4, farEdge = rightSide ? nearEdge + 200 : nearEdge - 200, polygon = {
731
+ area: [
732
+ { x: event.clientX + bleed, y: event.clientY },
733
+ { x: nearEdge, y: triggerRect.top - 50 },
734
+ { x: farEdge, y: triggerRect.top - 50 },
735
+ { x: farEdge, y: triggerRect.bottom + 50 },
736
+ { x: nearEdge, y: triggerRect.bottom + 50 }
737
+ ],
738
+ side
739
+ };
740
+ contentContext.onPointerGraceIntentChange(polygon), window.clearTimeout(pointerGraceTimerRef.current), pointerGraceTimerRef.current = window.setTimeout(
741
+ () => contentContext.onPointerGraceIntentChange(null),
742
+ 300
743
+ );
744
+ }
745
+ } else {
746
+ if (contentContext.onTriggerLeave(event), event.defaultPrevented) return;
747
+ contentContext.onPointerGraceIntentChange(null);
748
+ }
749
+ }),
750
+ ...isWeb ? {
751
+ onKeyDown: composeEventHandlers(props.onKeyDown, (event) => {
752
+ const isTypingAhead = contentContext.searchRef.current !== "";
753
+ if (props.disabled || isTypingAhead && event.key === " ") return;
754
+ if (SUB_OPEN_KEYS[effectiveDir].includes(event.key)) {
755
+ if (context.open && context.content) {
756
+ const firstItem = context.content.querySelector?.(
757
+ '[role="menuitem"]:not([data-disabled])'
758
+ );
759
+ if (firstItem) {
760
+ firstItem.focus({ focusVisible: !0 }), event.preventDefault();
761
+ return;
762
+ }
763
+ }
764
+ const triggerEl = event.currentTarget;
765
+ popperContext.refs?.setReference(triggerEl), context.onOpenChange(!0), requestAnimationFrame(() => {
766
+ popperContext.update?.();
767
+ }), context.content?.focus({ focusVisible: !0 }), event.preventDefault();
768
+ }
769
+ })
770
+ } : null
771
+ }
772
+ ) });
773
+ });
774
+ MenuSubTrigger.displayName = SUB_TRIGGER_NAME;
775
+ const SUB_CONTENT_NAME = "MenuSubContent", MenuSubContent = styled(PopperPrimitive.PopperContentFrame, {
776
+ name: SUB_CONTENT_NAME
777
+ }).styleable(
778
+ (props, forwardedRef) => {
779
+ const scope = props.scope || MENU_CONTEXT, portalContext = usePortalContext(scope), { forceMount = portalContext.forceMount, ...subContentProps } = props, context = useMenuContext(scope), rootContext = useMenuRootContext(scope), subContext = useMenuSubContext(scope), popperContext = PopperPrimitive.usePopperContext(scope), ref = React.useRef(null), composedRefs = useComposedRefs(forwardedRef, ref), placementSide = popperContext.placement?.split("-")[0], dataSide = placementSide === "left" || placementSide === "right" ? placementSide : rootContext.dir === "rtl" ? "left" : "right", effectiveDir = rootContext.dir;
780
+ return /* @__PURE__ */ jsx(Collection.Provider, { scope, children: /* @__PURE__ */ jsx(Collection.Slot, { scope, children: /* @__PURE__ */ jsx(
781
+ MenuContentImpl,
782
+ {
783
+ id: subContext.contentId,
784
+ "aria-labelledby": subContext.triggerId,
785
+ ...subContentProps,
786
+ ref: composedRefs,
787
+ "data-side": dataSide,
788
+ disableOutsidePointerEvents: !1,
789
+ disableOutsideScroll: !1,
790
+ trapFocus: !1,
791
+ onOpenAutoFocus: (event) => {
792
+ if (rootContext.isUsingKeyboardRef.current) {
793
+ const root = ref.current;
794
+ (root?.querySelector?.(
795
+ "[data-tamagui-menu-content]"
796
+ ) || root)?.focus({ preventScroll: !0 });
797
+ }
798
+ event.preventDefault();
799
+ },
800
+ onCloseAutoFocus: (event) => event.preventDefault(),
801
+ onFocusOutside: composeEventHandlers(props.onFocusOutside, (event) => {
802
+ event.target !== subContext.trigger && context.onOpenChange(!1);
803
+ }),
804
+ onEscapeKeyDown: composeEventHandlers(props.onEscapeKeyDown, (event) => {
805
+ context.onOpenChange(!1), subContext.trigger?.focus({ focusVisible: !0 }), event.preventDefault();
806
+ }),
807
+ ...isWeb ? {
808
+ onKeyDown: composeEventHandlers(props.onKeyDown, (event) => {
809
+ const isKeyDownInside = event.currentTarget.contains(
810
+ event.target
811
+ ), isCloseKey = SUB_CLOSE_KEYS[effectiveDir].includes(event.key);
812
+ isKeyDownInside && isCloseKey && (context.onOpenChange(!1), subContext.trigger?.focus({ focusVisible: !0 }), event.preventDefault());
813
+ })
814
+ } : null
815
+ }
816
+ ) }) });
817
+ }
818
+ );
819
+ MenuSubContent.displayName = SUB_CONTENT_NAME;
820
+ const Anchor = MenuAnchor, Portal = MenuPortal, Content = MenuContent, Group = _MenuGroup.styleable((props, ref) => /* @__PURE__ */ jsx(_MenuGroup, { ...props, ref }));
821
+ Group.displayName = "MenuGroup";
822
+ const Label = _Label.styleable((props, ref) => /* @__PURE__ */ jsx(_Label, { ...props, ref }));
823
+ Label.displayName = "MenuLabel";
824
+ const Item = MenuItem, CheckboxItem = MenuCheckboxItem, RadioGroup = MenuRadioGroup, RadioItem = MenuRadioItem, ItemIndicator = MenuItemIndicator, Separator = _Separator.styleable((props, ref) => /* @__PURE__ */ jsx(_Separator, { ...props, ref }));
825
+ return Separator.displayName = "MenuSeparator", {
826
+ Menu: withStaticProperties(MenuComp, {
827
+ Anchor,
828
+ Portal,
829
+ Content,
830
+ Group,
831
+ Label,
832
+ Item,
833
+ CheckboxItem,
834
+ RadioGroup,
835
+ RadioItem,
836
+ ItemIndicator,
837
+ Separator,
838
+ Arrow: MenuArrow,
839
+ Sub: MenuSub,
840
+ SubTrigger: MenuSubTrigger,
841
+ SubContent: MenuSubContent,
842
+ ItemTitle: MenuItemTitle,
843
+ ItemSubtitle: MenuItemSubTitle,
844
+ ItemImage: MenuItemImage,
845
+ ItemIcon: MenuItemIcon
846
+ })
847
+ };
848
+ }
849
+ function getOpenState(open) {
850
+ return open ? "open" : "closed";
851
+ }
852
+ function isIndeterminate(checked) {
853
+ return checked === "indeterminate";
854
+ }
855
+ function getCheckedState(checked) {
856
+ return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
857
+ }
858
+ function focusFirst(candidates, options) {
859
+ const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
860
+ for (const candidate of candidates)
861
+ if (candidate === PREVIOUSLY_FOCUSED_ELEMENT || (candidate.focus({ preventScroll: !0, focusVisible: options?.focusVisible }), document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT)) return;
862
+ }
863
+ function wrapArray(array, startIndex) {
864
+ return array.map((_, index) => array[(startIndex + index) % array.length]);
865
+ }
866
+ function getNextMatch(values, search, currentMatch) {
867
+ const normalizedSearch = search.length > 1 && Array.from(search).every((char) => char === search[0]) ? search[0] : search, currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
868
+ let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
869
+ normalizedSearch.length === 1 && (wrappedValues = wrappedValues.filter((v) => v !== currentMatch));
870
+ const nextMatch = wrappedValues.find(
871
+ (value) => value.toLowerCase().startsWith(normalizedSearch.toLowerCase())
872
+ );
873
+ return nextMatch !== currentMatch ? nextMatch : void 0;
874
+ }
875
+ function isPointInPolygon(point, polygon) {
876
+ const { x, y } = point;
877
+ let inside = !1;
878
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
879
+ const xi = polygon[i].x, yi = polygon[i].y, xj = polygon[j].x, yj = polygon[j].y;
880
+ yi > y != yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi && (inside = !inside);
881
+ }
882
+ return inside;
883
+ }
884
+ function isPointerInGraceArea(event, area) {
885
+ if (!area) return !1;
886
+ const cursorPos = { x: event.clientX, y: event.clientY };
887
+ return isPointInPolygon(cursorPos, area);
888
+ }
889
+ export {
890
+ createBaseMenu
891
+ };
892
+ //# sourceMappingURL=createBaseMenu.js.map