@tamagui/create-menu 2.0.0-rc.3 → 2.0.0-rc.31

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 (129) hide show
  1. package/dist/cjs/MenuPredefined.cjs +1 -1
  2. package/dist/cjs/MenuPredefined.native.js +1 -1
  3. package/dist/cjs/createBaseMenu.cjs +90 -29
  4. package/dist/cjs/createBaseMenu.native.js +109 -34
  5. package/dist/cjs/createBaseMenu.native.js.map +1 -1
  6. package/dist/cjs/createNativeMenu/createNativeMenu.cjs +159 -128
  7. package/dist/cjs/createNativeMenu/createNativeMenu.native.js +258 -238
  8. package/dist/cjs/createNativeMenu/createNativeMenu.native.js.map +1 -1
  9. package/dist/cjs/createNativeMenu/withNativeMenu.cjs +2 -2
  10. package/dist/cjs/createNativeMenu/withNativeMenu.native.js +2 -2
  11. package/dist/cjs/createNativeMenu/withNativeMenu.native.js.map +1 -1
  12. package/dist/cjs/index.cjs +2 -1
  13. package/dist/cjs/index.native.js +2 -1
  14. package/dist/cjs/index.native.js.map +1 -1
  15. package/dist/esm/MenuPredefined.mjs +1 -1
  16. package/dist/esm/MenuPredefined.native.js +1 -1
  17. package/dist/esm/createBaseMenu.mjs +91 -30
  18. package/dist/esm/createBaseMenu.mjs.map +1 -1
  19. package/dist/esm/createBaseMenu.native.js +110 -35
  20. package/dist/esm/createBaseMenu.native.js.map +1 -1
  21. package/dist/esm/createNativeMenu/createNativeMenu.mjs +160 -129
  22. package/dist/esm/createNativeMenu/createNativeMenu.mjs.map +1 -1
  23. package/dist/esm/createNativeMenu/createNativeMenu.native.js +228 -208
  24. package/dist/esm/createNativeMenu/createNativeMenu.native.js.map +1 -1
  25. package/dist/esm/createNativeMenu/withNativeMenu.mjs +2 -2
  26. package/dist/esm/createNativeMenu/withNativeMenu.mjs.map +1 -1
  27. package/dist/esm/createNativeMenu/withNativeMenu.native.js +2 -2
  28. package/dist/esm/createNativeMenu/withNativeMenu.native.js.map +1 -1
  29. package/dist/esm/index.js +5 -6
  30. package/dist/esm/index.js.map +1 -6
  31. package/dist/esm/index.mjs +2 -1
  32. package/dist/esm/index.mjs.map +1 -1
  33. package/dist/esm/index.native.js +2 -1
  34. package/dist/esm/index.native.js.map +1 -1
  35. package/dist/jsx/MenuPredefined.mjs +1 -1
  36. package/dist/jsx/MenuPredefined.native.js +1 -1
  37. package/dist/jsx/createBaseMenu.mjs +91 -30
  38. package/dist/jsx/createBaseMenu.mjs.map +1 -1
  39. package/dist/jsx/createBaseMenu.native.js +109 -34
  40. package/dist/jsx/createBaseMenu.native.js.map +1 -1
  41. package/dist/jsx/createNativeMenu/createNativeMenu.mjs +160 -129
  42. package/dist/jsx/createNativeMenu/createNativeMenu.mjs.map +1 -1
  43. package/dist/jsx/createNativeMenu/createNativeMenu.native.js +258 -238
  44. package/dist/jsx/createNativeMenu/createNativeMenu.native.js.map +1 -1
  45. package/dist/jsx/createNativeMenu/withNativeMenu.mjs +2 -2
  46. package/dist/jsx/createNativeMenu/withNativeMenu.mjs.map +1 -1
  47. package/dist/jsx/createNativeMenu/withNativeMenu.native.js +2 -2
  48. package/dist/jsx/createNativeMenu/withNativeMenu.native.js.map +1 -1
  49. package/dist/jsx/index.js +5 -6
  50. package/dist/jsx/index.js.map +1 -6
  51. package/dist/jsx/index.mjs +2 -1
  52. package/dist/jsx/index.mjs.map +1 -1
  53. package/dist/jsx/index.native.js +2 -1
  54. package/dist/jsx/index.native.js.map +1 -1
  55. package/package.json +25 -27
  56. package/src/MenuPredefined.tsx +1 -1
  57. package/src/createBaseMenu.tsx +359 -271
  58. package/src/createNativeMenu/createNativeMenu.tsx +278 -219
  59. package/src/createNativeMenu/createNativeMenuTypes.ts +20 -20
  60. package/src/createNativeMenu/withNativeMenu.tsx +11 -4
  61. package/src/index.tsx +3 -5
  62. package/types/createBaseMenu.d.ts +121 -35
  63. package/types/createBaseMenu.d.ts.map +1 -1
  64. package/types/createNativeMenu/createNativeMenu.d.ts +21 -21
  65. package/types/createNativeMenu/createNativeMenu.d.ts.map +1 -1
  66. package/types/createNativeMenu/createNativeMenuTypes.d.ts +20 -20
  67. package/types/createNativeMenu/createNativeMenuTypes.d.ts.map +1 -1
  68. package/types/createNativeMenu/withNativeMenu.d.ts +3 -3
  69. package/types/createNativeMenu/withNativeMenu.d.ts.map +1 -1
  70. package/types/index.d.ts +3 -2
  71. package/types/index.d.ts.map +1 -1
  72. package/dist/cjs/MenuPredefined.js +0 -168
  73. package/dist/cjs/MenuPredefined.js.map +0 -6
  74. package/dist/cjs/createBaseMenu.js +0 -832
  75. package/dist/cjs/createBaseMenu.js.map +0 -6
  76. package/dist/cjs/createNativeMenu/createNativeMenu.js +0 -177
  77. package/dist/cjs/createNativeMenu/createNativeMenu.js.map +0 -6
  78. package/dist/cjs/createNativeMenu/createNativeMenuTypes.js +0 -14
  79. package/dist/cjs/createNativeMenu/createNativeMenuTypes.js.map +0 -6
  80. package/dist/cjs/createNativeMenu/index.cjs +0 -19
  81. package/dist/cjs/createNativeMenu/index.js +0 -16
  82. package/dist/cjs/createNativeMenu/index.js.map +0 -6
  83. package/dist/cjs/createNativeMenu/index.native.js +0 -22
  84. package/dist/cjs/createNativeMenu/index.native.js.map +0 -1
  85. package/dist/cjs/createNativeMenu/utils.js +0 -66
  86. package/dist/cjs/createNativeMenu/utils.js.map +0 -6
  87. package/dist/cjs/createNativeMenu/withNativeMenu.js +0 -30
  88. package/dist/cjs/createNativeMenu/withNativeMenu.js.map +0 -6
  89. package/dist/cjs/index.js +0 -23
  90. package/dist/cjs/index.js.map +0 -6
  91. package/dist/esm/MenuPredefined.js +0 -154
  92. package/dist/esm/MenuPredefined.js.map +0 -6
  93. package/dist/esm/createBaseMenu.js +0 -838
  94. package/dist/esm/createBaseMenu.js.map +0 -6
  95. package/dist/esm/createNativeMenu/createNativeMenu.js +0 -156
  96. package/dist/esm/createNativeMenu/createNativeMenu.js.map +0 -6
  97. package/dist/esm/createNativeMenu/createNativeMenuTypes.js +0 -1
  98. package/dist/esm/createNativeMenu/createNativeMenuTypes.js.map +0 -6
  99. package/dist/esm/createNativeMenu/index.js +0 -3
  100. package/dist/esm/createNativeMenu/index.js.map +0 -6
  101. package/dist/esm/createNativeMenu/index.mjs +0 -3
  102. package/dist/esm/createNativeMenu/index.mjs.map +0 -1
  103. package/dist/esm/createNativeMenu/index.native.js +0 -3
  104. package/dist/esm/createNativeMenu/index.native.js.map +0 -1
  105. package/dist/esm/createNativeMenu/utils.js +0 -47
  106. package/dist/esm/createNativeMenu/utils.js.map +0 -6
  107. package/dist/esm/createNativeMenu/withNativeMenu.js +0 -15
  108. package/dist/esm/createNativeMenu/withNativeMenu.js.map +0 -6
  109. package/dist/jsx/MenuPredefined.js +0 -154
  110. package/dist/jsx/MenuPredefined.js.map +0 -6
  111. package/dist/jsx/createBaseMenu.js +0 -838
  112. package/dist/jsx/createBaseMenu.js.map +0 -6
  113. package/dist/jsx/createNativeMenu/createNativeMenu.js +0 -156
  114. package/dist/jsx/createNativeMenu/createNativeMenu.js.map +0 -6
  115. package/dist/jsx/createNativeMenu/createNativeMenuTypes.js +0 -1
  116. package/dist/jsx/createNativeMenu/createNativeMenuTypes.js.map +0 -6
  117. package/dist/jsx/createNativeMenu/index.js +0 -3
  118. package/dist/jsx/createNativeMenu/index.js.map +0 -6
  119. package/dist/jsx/createNativeMenu/index.mjs +0 -3
  120. package/dist/jsx/createNativeMenu/index.mjs.map +0 -1
  121. package/dist/jsx/createNativeMenu/index.native.js +0 -22
  122. package/dist/jsx/createNativeMenu/index.native.js.map +0 -1
  123. package/dist/jsx/createNativeMenu/utils.js +0 -47
  124. package/dist/jsx/createNativeMenu/utils.js.map +0 -6
  125. package/dist/jsx/createNativeMenu/withNativeMenu.js +0 -15
  126. package/dist/jsx/createNativeMenu/withNativeMenu.js.map +0 -6
  127. package/src/createNativeMenu/index.tsx +0 -7
  128. package/types/createNativeMenu/index.d.ts +0 -4
  129. package/types/createNativeMenu/index.d.ts.map +0 -1
@@ -1,194 +1,214 @@
1
1
  /**
2
- * createNativeMenu - provides native menu implementation for React Native
2
+ * createNativeMenu - native menu implementation for React Native
3
3
  *
4
- * On Web: Returns empty stub components (withNativeMenu will use the web components instead)
5
- * On Native: Uses Zeego for native menus (Credit to nandorojo/Zeego)
4
+ * Web: returns empty stub components (withNativeMenu uses the web components instead)
5
+ * Native: lazily resolves Zeego at render time so importing the package doesn't warn/error
6
6
  */
7
7
 
8
- import { getZeego } from '@tamagui/native'
8
+ import { getZeego, NativeMenuContext } from '@tamagui/native'
9
9
  import { isWeb, withStaticProperties, isIos } from '@tamagui/web'
10
10
  import type { FC } from 'react'
11
11
  import React from 'react'
12
12
  import type {
13
13
  ContextMenuPreviewProps,
14
- MenuArrowProps,
15
- MenuCheckboxItemProps,
16
- MenuContentProps,
17
- MenuGroupProps,
18
- MenuItemIconProps,
19
- MenuItemImageProps,
20
- MenuItemIndicatorProps,
21
- MenuItemProps,
22
- MenuItemSubtitleProps,
23
- MenuItemTitleProps,
24
- MenuLabelProps,
25
- MenuProps,
26
- MenuSeparatorProps,
27
- MenuSubContentProps,
28
- MenuSubProps,
29
- MenuSubTriggerProps,
14
+ NativeContextMenuAuxiliaryProps,
15
+ NativeMenuArrowProps,
16
+ NativeMenuCheckboxItemProps,
17
+ NativeMenuContentProps,
18
+ NativeMenuGroupProps,
19
+ NativeMenuItemIconProps,
20
+ NativeMenuItemImageProps,
21
+ NativeMenuItemIndicatorProps,
22
+ NativeMenuItemProps,
23
+ NativeMenuItemSubtitleProps,
24
+ NativeMenuItemTitleProps,
25
+ NativeMenuLabelProps,
26
+ NativeMenuProps,
27
+ NativeMenuSeparatorProps,
28
+ NativeMenuSubContentProps,
29
+ NativeMenuSubProps,
30
+ NativeMenuSubTriggerProps,
30
31
  MenuTriggerProps,
31
32
  } from './createNativeMenuTypes'
32
33
 
34
+ // zeego module shape (DropdownMenu / ContextMenu both share this)
35
+ type ZeegoMenuModule = {
36
+ Root: FC<Record<string, unknown>>
37
+ Trigger: FC<MenuTriggerProps>
38
+ Content: FC<NativeMenuContentProps>
39
+ Item: FC<NativeMenuItemProps>
40
+ ItemTitle: FC<NativeMenuItemTitleProps>
41
+ ItemSubtitle: FC<NativeMenuItemSubtitleProps>
42
+ ItemIcon: FC<NativeMenuItemIconProps>
43
+ ItemImage: FC<NativeMenuItemImageProps>
44
+ ItemIndicator: FC<NativeMenuItemIndicatorProps>
45
+ Group: FC<NativeMenuGroupProps>
46
+ Label: FC<NativeMenuLabelProps>
47
+ Separator: FC<NativeMenuSeparatorProps>
48
+ Sub: FC<NativeMenuSubProps>
49
+ SubTrigger: FC<NativeMenuSubTriggerProps>
50
+ SubContent: FC<NativeMenuSubContentProps>
51
+ CheckboxItem: FC<NativeMenuCheckboxItemProps>
52
+ Preview: FC<ContextMenuPreviewProps>
53
+ Auxiliary: FC<NativeContextMenuAuxiliaryProps>
54
+ }
55
+
56
+ // component types we recognize via displayName matching
57
+ type MappedComponentType =
58
+ | 'SubContent'
59
+ | 'SubTrigger'
60
+ | 'Content'
61
+ | 'Sub'
62
+ | 'Group'
63
+ | 'CheckboxItem'
64
+
65
+ const MAPPED_TYPES: MappedComponentType[] = [
66
+ 'SubContent',
67
+ 'SubTrigger',
68
+ 'Content',
69
+ 'Sub',
70
+ 'Group',
71
+ 'CheckboxItem',
72
+ ]
73
+
74
+ // types whose children get recursively transformed
75
+ const CONTAINER_TYPES: MappedComponentType[] = ['SubContent', 'Content', 'Sub', 'Group']
76
+
77
+ type ComponentMap = Pick<
78
+ ZeegoMenuModule,
79
+ 'SubContent' | 'Content' | 'Sub' | 'Group' | 'SubTrigger'
80
+ >
81
+
33
82
  export type NativeMenuComponents = {
34
- Menu: FC<MenuProps> & {
83
+ Menu: FC<NativeMenuProps> & {
35
84
  Trigger: FC<MenuTriggerProps>
36
- Content: FC<MenuContentProps>
37
- Item: FC<MenuItemProps>
38
- ItemTitle: FC<MenuItemTitleProps>
39
- ItemSubtitle: FC<MenuItemSubtitleProps>
40
- SubTrigger: FC<MenuSubTriggerProps>
41
- Group: FC<MenuGroupProps>
42
- ItemIcon: FC<MenuItemIconProps>
43
- Separator: FC<MenuSeparatorProps>
44
- CheckboxItem: FC<MenuCheckboxItemProps>
45
- ItemIndicator: FC<MenuItemIndicatorProps>
46
- ItemImage: FC<MenuItemImageProps>
47
- Label: FC<MenuLabelProps>
48
- Arrow: FC<MenuArrowProps>
49
- Sub: FC<MenuSubProps>
50
- SubContent: FC<MenuSubContentProps>
85
+ Content: FC<NativeMenuContentProps>
86
+ Item: FC<NativeMenuItemProps>
87
+ ItemTitle: FC<NativeMenuItemTitleProps>
88
+ ItemSubtitle: FC<NativeMenuItemSubtitleProps>
89
+ SubTrigger: FC<NativeMenuSubTriggerProps>
90
+ Group: FC<NativeMenuGroupProps>
91
+ ItemIcon: FC<NativeMenuItemIconProps>
92
+ Separator: FC<NativeMenuSeparatorProps>
93
+ CheckboxItem: FC<NativeMenuCheckboxItemProps>
94
+ ItemIndicator: FC<NativeMenuItemIndicatorProps>
95
+ ItemImage: FC<NativeMenuItemImageProps>
96
+ Label: FC<NativeMenuLabelProps>
97
+ Arrow: FC<NativeMenuArrowProps>
98
+ Sub: FC<NativeMenuSubProps>
99
+ SubContent: FC<NativeMenuSubContentProps>
51
100
  Preview: FC<ContextMenuPreviewProps>
52
101
  Portal: FC<{ children: React.ReactNode }>
53
102
  RadioGroup: FC<{ children: React.ReactNode }>
54
103
  RadioItem: FC<{ children: React.ReactNode }>
55
- Auxiliary: FC<any>
104
+ Auxiliary: FC<NativeContextMenuAuxiliaryProps>
105
+ }
106
+ }
107
+
108
+ // shared helpers (stateless, no need to recreate per call)
109
+
110
+ function getComponentType(displayName: string): MappedComponentType | null {
111
+ for (const type of MAPPED_TYPES) {
112
+ if (displayName === type || displayName.includes(`(${type})`)) {
113
+ return type
114
+ }
115
+ }
116
+ return null
117
+ }
118
+
119
+ function isItemLike(props: Record<string, unknown>, displayName: string): boolean {
120
+ if (getComponentType(displayName)) return false
121
+ return 'onSelect' in props || 'textValue' in props
122
+ }
123
+
124
+ function isPortalLike(displayName: string): boolean {
125
+ return displayName === 'Portal' || displayName.includes('Portal')
126
+ }
127
+
128
+ // stub used for web — never actually rendered, just needs to exist for withNativeMenu fallback
129
+ const emptyStub = (() => null) as FC<any>
130
+
131
+ function createWebStubs(): NativeMenuComponents {
132
+ return {
133
+ Menu: withStaticProperties(emptyStub as FC<NativeMenuProps>, {
134
+ Trigger: emptyStub as FC<MenuTriggerProps>,
135
+ Content: emptyStub as FC<NativeMenuContentProps>,
136
+ Item: emptyStub as FC<NativeMenuItemProps>,
137
+ ItemTitle: emptyStub as FC<NativeMenuItemTitleProps>,
138
+ ItemSubtitle: emptyStub as FC<NativeMenuItemSubtitleProps>,
139
+ SubTrigger: emptyStub as FC<NativeMenuSubTriggerProps>,
140
+ Group: emptyStub as FC<NativeMenuGroupProps>,
141
+ ItemIcon: emptyStub as FC<NativeMenuItemIconProps>,
142
+ Separator: emptyStub as FC<NativeMenuSeparatorProps>,
143
+ CheckboxItem: emptyStub as FC<NativeMenuCheckboxItemProps>,
144
+ ItemIndicator: emptyStub as FC<NativeMenuItemIndicatorProps>,
145
+ ItemImage: emptyStub as FC<NativeMenuItemImageProps>,
146
+ Label: emptyStub as FC<NativeMenuLabelProps>,
147
+ Arrow: emptyStub as FC<NativeMenuArrowProps>,
148
+ Sub: emptyStub as FC<NativeMenuSubProps>,
149
+ SubContent: emptyStub as FC<NativeMenuSubContentProps>,
150
+ Preview: emptyStub as FC<ContextMenuPreviewProps>,
151
+ Portal: emptyStub as FC<{ children: React.ReactNode }>,
152
+ RadioGroup: emptyStub as FC<{ children: React.ReactNode }>,
153
+ RadioItem: emptyStub as FC<{ children: React.ReactNode }>,
154
+ Auxiliary: emptyStub as FC<NativeContextMenuAuxiliaryProps>,
155
+ }),
56
156
  }
57
157
  }
58
158
 
59
159
  export const createNativeMenu = (
60
160
  MenuType: 'ContextMenu' | 'Menu'
61
161
  ): NativeMenuComponents => {
62
- // On web, return empty stubs - withNativeMenu will use the web components passed to it
63
162
  if (isWeb) {
64
- const Menu = {} as FC<MenuProps>
65
- const Trigger = {} as FC<MenuTriggerProps>
66
- const Content = {} as FC<MenuContentProps>
67
- const Preview = {} as FC<ContextMenuPreviewProps>
68
- const Item = {} as FC<MenuItemProps>
69
- const ItemIcon = {} as FC<MenuItemIconProps>
70
- const ItemImage = {} as FC<MenuItemImageProps>
71
- const SubTrigger = {} as FC<MenuSubTriggerProps>
72
- const ItemTitle = {} as FC<MenuItemTitleProps>
73
- const ItemSubtitle = {} as FC<MenuItemSubtitleProps>
74
- const Group = {} as FC<MenuGroupProps>
75
- const Separator = {} as FC<MenuSeparatorProps>
76
- const CheckboxItem = {} as FC<MenuCheckboxItemProps>
77
- const ItemIndicator = {} as FC<MenuItemIndicatorProps>
78
- const Label = {} as FC<MenuLabelProps>
79
- const Arrow = {} as FC<MenuArrowProps>
80
- const Sub = {} as FC<MenuSubProps>
81
- const SubContent = {} as FC<MenuSubContentProps>
82
- const Portal = {} as FC<{ children: React.ReactNode }>
83
- const RadioGroup = {} as FC<{ children: React.ReactNode }>
84
- const RadioItem = {} as FC<{ children: React.ReactNode }>
85
- const Auxiliary = {} as FC<any>
86
-
87
- return {
88
- Menu: withStaticProperties(Menu, {
89
- Trigger,
90
- Content,
91
- Item,
92
- ItemTitle,
93
- ItemSubtitle,
94
- SubTrigger,
95
- Group,
96
- ItemIcon,
97
- Separator,
98
- CheckboxItem,
99
- ItemIndicator,
100
- ItemImage,
101
- Label,
102
- Arrow,
103
- Sub,
104
- SubContent,
105
- Preview,
106
- Portal,
107
- RadioGroup,
108
- RadioItem,
109
- Auxiliary,
110
- }),
111
- }
163
+ return createWebStubs()
112
164
  }
113
165
 
114
166
  // ===========================================
115
- // Native implementation using Zeego
167
+ // native implementation lazily resolves zeego
116
168
  // ===========================================
117
169
 
118
- const zeego = getZeego()
119
- if (!zeego.isEnabled) {
120
- console.warn(
121
- `Warning: Must call import '@tamagui/native/setup-zeego' at your app entry point to use native menus`
122
- )
123
- return { Menu: {} as any }
124
- }
125
-
126
- const { DropdownMenu: ZeegoDropdownMenu, ContextMenu: ZeegoContextMenu } = zeego.state
127
-
128
170
  const isContextMenu = MenuType === 'ContextMenu'
129
- const ZeegoMenu = isContextMenu ? ZeegoContextMenu : ZeegoDropdownMenu
130
-
131
- // Map displayName patterns to Zeego components
132
- const COMPONENT_MAP: Record<string, any> = {
133
- SubContent: ZeegoMenu.SubContent,
134
- Content: ZeegoMenu.Content,
135
- Sub: ZeegoMenu.Sub,
136
- Group: ZeegoMenu.Group,
137
- SubTrigger: ZeegoMenu.SubTrigger,
138
- }
139
-
140
- // Components that need children transformation (containers)
141
- const CONTAINER_TYPES = ['SubContent', 'Content', 'Sub', 'Group']
142
-
143
- /**
144
- * Get component type from displayName (handles styled wrappers)
145
- */
146
- const getComponentType = (displayName: string): string | null => {
147
- // Check in specific order (SubContent before Content, SubTrigger before Trigger)
148
- for (const type of [
149
- 'SubContent',
150
- 'SubTrigger',
151
- 'Content',
152
- 'Sub',
153
- 'Group',
154
- 'CheckboxItem',
155
- ]) {
156
- if (displayName === type || displayName.includes(`(${type})`)) {
157
- return type
171
+ const isAndroid = !isIos && !isWeb
172
+
173
+ // cached after first successful resolve
174
+ let resolved: { menu: ZeegoMenuModule; componentMap: ComponentMap } | null = null
175
+ let warned = false
176
+
177
+ function resolve(): typeof resolved {
178
+ if (resolved) return resolved
179
+ const zeego = getZeego()
180
+ if (!zeego.isEnabled) {
181
+ if (!warned) {
182
+ warned = true
183
+ console.warn(
184
+ `Warning: Must call import '@tamagui/native/setup-zeego' at your app entry point to use native menus`
185
+ )
158
186
  }
187
+ return null
159
188
  }
160
- return null
161
- }
162
-
163
- /**
164
- * Check if component looks like a menu Item (has onSelect/textValue but isn't a special component)
165
- */
166
- const isItemLike = (props: Record<string, any>, displayName: string): boolean => {
167
- // If it matches a known component type, it's not a generic Item
168
- if (getComponentType(displayName)) {
169
- return false
189
+ const menu = (
190
+ isContextMenu ? zeego.state.ContextMenu : zeego.state.DropdownMenu
191
+ ) as ZeegoMenuModule
192
+ resolved = {
193
+ menu,
194
+ componentMap: {
195
+ SubContent: menu.SubContent,
196
+ Content: menu.Content,
197
+ Sub: menu.Sub,
198
+ Group: menu.Group,
199
+ SubTrigger: menu.SubTrigger,
200
+ },
170
201
  }
171
- return 'onSelect' in props || 'textValue' in props
202
+ return resolved
172
203
  }
173
204
 
174
- /**
175
- * Check if displayName matches Portal
176
- */
177
- const isPortal = (displayName: string): boolean => {
178
- return displayName === 'Portal' || displayName.includes('Portal')
179
- }
180
-
181
- /**
182
- * Transform children tree for Zeego compatibility:
183
- * - Flatten Portal wrappers
184
- * - Recurse into containers (Content, Sub, Group, SubContent)
185
- * - Convert styled Items to Zeego Items
186
- * - Reverse children on iOS only for DropdownMenu at Content/SubContent level
187
- */
188
- const transformForZeego = (
205
+ // transform children tree for zeego compatibility
206
+ function transformChildren(
207
+ menu: ZeegoMenuModule,
208
+ map: ComponentMap,
189
209
  children: React.ReactNode,
190
210
  shouldReverseOnIos = false
191
- ): React.ReactNode => {
211
+ ): React.ReactNode {
192
212
  const result: React.ReactNode[] = []
193
213
 
194
214
  React.Children.forEach(children, (child) => {
@@ -197,49 +217,67 @@ export const createNativeMenu = (
197
217
  return
198
218
  }
199
219
 
200
- const displayName = (child.type as any)?.displayName || ''
220
+ const displayName = (child.type as { displayName?: string })?.displayName || ''
201
221
  const props = child.props as Record<string, any>
202
222
 
203
- // Flatten Portal
204
- if (isPortal(displayName)) {
205
- const portalChildren = transformForZeego(props.children, false)
206
- React.Children.forEach(portalChildren, (c) => result.push(c))
223
+ // flatten portal wrappers
224
+ if (isPortalLike(displayName)) {
225
+ const inner = transformChildren(
226
+ menu,
227
+ map,
228
+ props.children as React.ReactNode,
229
+ false
230
+ )
231
+ React.Children.forEach(inner, (c) => result.push(c))
232
+ return
233
+ }
234
+
235
+ // flatten ScrollView (native passthrough — children need to be visible to zeego)
236
+ if (displayName.includes('ScrollView')) {
237
+ const inner = transformChildren(
238
+ menu,
239
+ map,
240
+ props.children as React.ReactNode,
241
+ false
242
+ )
243
+ React.Children.forEach(inner, (c) => result.push(c))
207
244
  return
208
245
  }
209
246
 
210
- // Handle known component types (containers, SubTrigger, CheckboxItem)
211
247
  const componentType = getComponentType(displayName)
212
248
 
213
- // normalizing checked/value props
249
+ // normalize checkbox checked/value props
214
250
  if (componentType === 'CheckboxItem') {
215
- const { checked, onCheckedChange, value, onValueChange, children, ...rest } =
216
- props
251
+ const {
252
+ checked,
253
+ onCheckedChange,
254
+ value,
255
+ onValueChange,
256
+ children: cbChildren,
257
+ ...rest
258
+ } = props as Record<string, any>
217
259
 
218
260
  const finalValue = value ?? (checked ? 'on' : 'off')
219
261
  const finalOnValueChange =
220
262
  onValueChange ??
221
263
  (onCheckedChange && ((v: string) => onCheckedChange(v === 'on')))
222
264
 
223
- const cleanChildren = React.Children.map(children, (child) => {
224
- if (!React.isValidElement(child)) return child
225
-
226
- const childDisplayName = (child.type as any)?.displayName || ''
227
- // If it's an ItemIndicator, remove it (return null) so we don't double render the checkmark
228
- if (childDisplayName.includes('ItemIndicator')) {
229
- return null
230
- }
231
- return child
265
+ const cleanChildren = React.Children.map(cbChildren, (c) => {
266
+ if (!React.isValidElement(c)) return c
267
+ const dn = (c.type as { displayName?: string })?.displayName || ''
268
+ if (dn.includes('ItemIndicator')) return null
269
+ return c
232
270
  })
233
271
 
234
272
  result.push(
235
273
  React.createElement(
236
- ZeegoMenu.CheckboxItem,
274
+ menu.CheckboxItem,
237
275
  {
238
276
  ...rest,
239
277
  key: child.key,
240
278
  value: finalValue,
241
279
  onValueChange: finalOnValueChange,
242
- },
280
+ } as any,
243
281
  cleanChildren
244
282
  )
245
283
  )
@@ -248,41 +286,43 @@ export const createNativeMenu = (
248
286
 
249
287
  if (componentType) {
250
288
  const { children: childChildren, ...restProps } = props
251
- const isContainer = CONTAINER_TYPES.includes(componentType)
252
- // Only reverse children of Content and SubContent (not Group or Sub)
253
- const shouldReverseChildren =
289
+ const isContainer = (CONTAINER_TYPES as string[]).includes(componentType)
290
+ const shouldReverse =
254
291
  componentType === 'Content' || componentType === 'SubContent'
255
292
  result.push(
256
293
  React.createElement(
257
- COMPONENT_MAP[componentType],
258
- { ...restProps, key: child.key },
294
+ map[componentType as keyof ComponentMap],
295
+ { ...restProps, key: child.key } as any,
259
296
  isContainer
260
- ? transformForZeego(childChildren, shouldReverseChildren)
297
+ ? transformChildren(
298
+ menu,
299
+ map,
300
+ childChildren as React.ReactNode,
301
+ shouldReverse
302
+ )
261
303
  : childChildren
262
304
  )
263
305
  )
264
306
  return
265
307
  }
266
308
 
267
- // Convert Item-like components to Zeego Items
309
+ // convert Item-like components to zeego Items
268
310
  if (isItemLike(props, displayName)) {
269
311
  const { children: itemChildren, ...itemProps } = props
270
312
  result.push(
271
313
  React.createElement(
272
- ZeegoMenu.Item,
273
- { ...itemProps, key: child.key },
314
+ menu.Item,
315
+ { ...itemProps, key: child.key } as any,
274
316
  itemChildren
275
317
  )
276
318
  )
277
319
  return
278
320
  }
279
321
 
280
- // Pass through everything else
281
322
  result.push(child)
282
323
  })
283
324
 
284
- // iOS DropdownMenu (not ContextMenu) displays menu items in reverse order
285
- // Only reverse for Menu component, not ContextMenu
325
+ // iOS DropdownMenu displays items in reverse order
286
326
  if (isIos && shouldReverseOnIos && !isContextMenu) {
287
327
  result.reverse()
288
328
  }
@@ -290,31 +330,39 @@ export const createNativeMenu = (
290
330
  return result
291
331
  }
292
332
 
293
- // ===========================================
294
- // Component definitions (typed wrappers around Zeego)
295
- // ===========================================
333
+ // lazy wrapper — resolves the zeego component on first render
334
+ function lazyZeego<P extends Record<string, any>>(
335
+ name: keyof ZeegoMenuModule,
336
+ displayName?: string
337
+ ): FC<P> {
338
+ const Comp: FC<P> = (props) => {
339
+ const z = resolve()
340
+ if (!z) return null
341
+ return React.createElement(z.menu[name] as FC<any>, props)
342
+ }
343
+ Comp.displayName = displayName || name
344
+ return Comp
345
+ }
346
+
347
+ const Trigger = lazyZeego<MenuTriggerProps>('Trigger')
348
+ const Content = lazyZeego<NativeMenuContentProps>('Content')
349
+ const Item = lazyZeego<NativeMenuItemProps>('Item')
350
+ const ItemTitle = lazyZeego<NativeMenuItemTitleProps>('ItemTitle')
351
+ const ItemSubtitle = lazyZeego<NativeMenuItemSubtitleProps>('ItemSubtitle')
352
+ const ItemIcon = lazyZeego<NativeMenuItemIconProps>('ItemIcon')
353
+ const ItemImage = lazyZeego<NativeMenuItemImageProps>('ItemImage')
354
+ const ItemIndicator = lazyZeego<NativeMenuItemIndicatorProps>('ItemIndicator')
355
+ const Group = lazyZeego<NativeMenuGroupProps>('Group')
356
+ const Label = lazyZeego<NativeMenuLabelProps>('Label')
357
+ const Separator = lazyZeego<NativeMenuSeparatorProps>('Separator')
358
+ const Sub = lazyZeego<NativeMenuSubProps>('Sub')
359
+ const SubTrigger = lazyZeego<NativeMenuSubTriggerProps>('SubTrigger')
360
+ const SubContent = lazyZeego<NativeMenuSubContentProps>('SubContent')
296
361
 
297
- // Direct Zeego pass-throughs with proper types
298
- const Trigger: FC<MenuTriggerProps> = ZeegoMenu.Trigger
299
- const Content: FC<MenuContentProps> = ZeegoMenu.Content
300
- const Item: FC<MenuItemProps> = ZeegoMenu.Item
301
- const ItemTitle: FC<MenuItemTitleProps> = ZeegoMenu.ItemTitle
302
- const ItemSubtitle: FC<MenuItemSubtitleProps> = ZeegoMenu.ItemSubtitle
303
- const ItemIcon: FC<MenuItemIconProps> = ZeegoMenu.ItemIcon
304
- const ItemImage: FC<MenuItemImageProps> = ZeegoMenu.ItemImage
305
- const ItemIndicator: FC<MenuItemIndicatorProps> = ZeegoMenu.ItemIndicator
306
- const Group: FC<MenuGroupProps> = ZeegoMenu.Group
307
- const Label: FC<MenuLabelProps> = ZeegoMenu.Label
308
- const Separator: FC<MenuSeparatorProps> = ZeegoMenu.Separator
309
- const Sub: FC<MenuSubProps> = ZeegoMenu.Sub
310
- const SubTrigger: FC<MenuSubTriggerProps> = ZeegoMenu.SubTrigger
311
- const SubContent: FC<MenuSubContentProps> = ZeegoMenu.SubContent
312
-
313
- // Custom components
314
362
  const Portal: FC<{ children: React.ReactNode }> = ({ children }) => <>{children}</>
315
363
  Portal.displayName = 'Portal'
316
364
 
317
- const Arrow: FC<MenuArrowProps> = () => null
365
+ const Arrow: FC<NativeMenuArrowProps> = () => null
318
366
  Arrow.displayName = 'Arrow'
319
367
 
320
368
  const RadioGroup: FC<{ children: React.ReactNode }> = ({ children }) => <>{children}</>
@@ -322,36 +370,47 @@ export const createNativeMenu = (
322
370
 
323
371
  const RadioItem: FC<{ children: React.ReactNode }> = ({ children }) => <>{children}</>
324
372
  RadioItem.displayName = `${MenuType}RadioItem`
325
- // CheckboxItem wrapper to normalize checked/value props
326
- const CheckboxItem: FC<MenuCheckboxItemProps> = (props) => null
373
+
374
+ const CheckboxItem: FC<NativeMenuCheckboxItemProps> = () => null
327
375
  CheckboxItem.displayName = 'CheckboxItem'
328
376
 
329
- // Context menu specific
330
377
  const Preview: FC<ContextMenuPreviewProps> = isContextMenu
331
- ? ZeegoContextMenu.Preview
378
+ ? lazyZeego<ContextMenuPreviewProps>('Preview', `${MenuType}Preview`)
332
379
  : () => null
333
380
  Preview.displayName = `${MenuType}Preview`
334
381
 
335
- const Auxiliary: FC<ContextMenuPreviewProps> = isContextMenu
336
- ? ZeegoContextMenu.Auxiliary
382
+ const Auxiliary: FC<NativeContextMenuAuxiliaryProps> = isContextMenu
383
+ ? lazyZeego<NativeContextMenuAuxiliaryProps>('Auxiliary', `${MenuType}Auxiliary`)
337
384
  : () => null
338
385
  Auxiliary.displayName = `${MenuType}Auxiliary`
339
386
 
340
- // Main Menu component
341
- const Menu: FC<MenuProps> = ({ children, onOpenChange, onOpenWillChange }) => {
387
+ // on Android, provide NativeMenuContext so components use Gesture.Manual()
388
+ // instead of Gesture.Tap() (which sends ACTION_CANCEL to MenuView)
389
+ const Menu: FC<NativeMenuProps> = ({ children, onOpenChange, onOpenWillChange }) => {
390
+ const z = resolve()
391
+ if (!z) return null
392
+
342
393
  const rootProps: Record<string, unknown> = { onOpenChange }
343
394
  if (isContextMenu && onOpenWillChange) {
344
395
  rootProps.onOpenWillChange = onOpenWillChange
345
396
  }
346
397
 
347
- return <ZeegoMenu.Root {...rootProps}>{transformForZeego(children)}</ZeegoMenu.Root>
398
+ const content = (
399
+ <z.menu.Root {...rootProps}>
400
+ {transformChildren(z.menu, z.componentMap, children)}
401
+ </z.menu.Root>
402
+ )
403
+
404
+ if (isAndroid) {
405
+ return (
406
+ <NativeMenuContext.Provider value={true}>{content}</NativeMenuContext.Provider>
407
+ )
408
+ }
409
+
410
+ return content
348
411
  }
349
412
  Menu.displayName = MenuType
350
413
 
351
- // ===========================================
352
- // Export
353
- // ===========================================
354
-
355
414
  return {
356
415
  Menu: withStaticProperties(Menu, {
357
416
  Trigger,