@tamagui/create-menu 2.7.7 → 3.0.0-beta.643.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
+ import { createStyledHOC, createRefComponent } from '@tamagui/core'
1
2
  import { Animate } from '@tamagui/animate'
2
3
  import { AnimatePresence as Presence } from '@tamagui/animate-presence'
3
4
  import { createCollection } from '@tamagui/collection'
@@ -7,6 +8,7 @@ import {
7
8
  } from '@tamagui/dismissable'
8
9
  import { useFocusGuards } from '@tamagui/focus-guard'
9
10
  import { FocusScope } from '@tamagui/focus-scope'
11
+ import { Image as TamaguiImage, type ImageProps } from '@tamagui/image'
10
12
  import type { PopperContentProps } from '@tamagui/popper'
11
13
  import * as PopperPrimitive from '@tamagui/popper'
12
14
  import { needsPortalRepropagation, Portal as PortalPrimitive } from '@tamagui/portal'
@@ -17,6 +19,7 @@ import { useCallbackRef } from '@tamagui/use-callback-ref'
17
19
  import { useDirection } from '@tamagui/use-direction'
18
20
  import type { TextProps } from '@tamagui/web'
19
21
  import {
22
+ type GetRef,
20
23
  type ViewProps,
21
24
  composeEventHandlers,
22
25
  composeRefs,
@@ -34,9 +37,6 @@ import {
34
37
  import type { TamaguiElement } from '@tamagui/web/types'
35
38
  import * as React from 'react'
36
39
  import { useId } from 'react'
37
- import type { Image, ImageProps } from 'react-native'
38
-
39
- import { MenuPredefined } from './MenuPredefined'
40
40
 
41
41
  type Direction = 'ltr' | 'rtl'
42
42
 
@@ -156,7 +156,7 @@ interface MenuRootContentTypeProps extends Omit<
156
156
 
157
157
  /* ---------------------------------------------------------------------------------------------- */
158
158
 
159
- type MenuContentImplElement = React.ElementRef<typeof PopperPrimitive.PopperContent>
159
+ type MenuContentImplElement = GetRef<typeof PopperPrimitive.PopperContent>
160
160
  type FocusScopeProps = React.ComponentPropsWithoutRef<typeof FocusScope>
161
161
  type DismissableLayerProps = React.ComponentPropsWithoutRef<typeof DismissableLayer>
162
162
  type MenuContentImplPrivateProps = {
@@ -175,18 +175,18 @@ type MenuContentImplPrivateProps = {
175
175
  * (default: false)
176
176
  */
177
177
  trapFocus?: FocusScopeProps['trapped']
178
-
178
+ }
179
+ interface MenuContentImplProps
180
+ extends MenuContentImplPrivateProps, Omit<PopperContentProps, 'dir' | 'onPlaced'> {
179
181
  /**
180
182
  * Whether to disable dismissing the menu when the user scrolls outside of it
181
183
  * (default: false, meaning scroll will dismiss on web)
182
184
  */
183
185
  disableDismissOnScroll?: boolean
184
- }
185
- interface MenuContentImplProps
186
- extends MenuContentImplPrivateProps, Omit<PopperContentProps, 'dir' | 'onPlaced'> {
186
+
187
187
  /**
188
188
  * Event handler called when auto-focusing on close.
189
- * Can be prevented.
189
+ * Can be canceled.
190
190
  */
191
191
  onCloseAutoFocus?: FocusScopeProps['onUnmountAutoFocus']
192
192
 
@@ -203,7 +203,7 @@ interface MenuContentImplProps
203
203
  onInteractOutside?: DismissableLayerProps['onInteractOutside']
204
204
  }
205
205
 
206
- type StyleableMenuContentProps = MenuContentImplProps & ViewProps
206
+ type StyledHOCMenuContentProps = MenuContentImplProps & ViewProps
207
207
 
208
208
  interface MenuGroupProps extends ViewProps {}
209
209
 
@@ -211,12 +211,11 @@ interface MenuGroupProps extends ViewProps {}
211
211
  * MenuLabel
212
212
  * -----------------------------------------------------------------------------------------------*/
213
213
 
214
- interface MenuLabelProps extends ViewProps {}
214
+ interface MenuLabelProps extends TextProps {}
215
215
 
216
216
  type MenuItemElement = MenuItemImplElement
217
217
  interface MenuItemProps extends Omit<MenuItemImplProps, 'onSelect'> {
218
218
  onSelect?: (event: Event) => void
219
- unstyled?: boolean
220
219
  /**
221
220
  * Prevents the menu from closing when this item is selected.
222
221
  * Useful for toggle items or multi-select scenarios.
@@ -229,7 +228,6 @@ type MenuItemImplElement = TamaguiElement
229
228
  interface MenuItemImplProps extends ViewProps {
230
229
  disabled?: boolean
231
230
  textValue?: string
232
- unstyled?: boolean
233
231
  }
234
232
 
235
233
  /* -------------------------------------------------------------------------------------------------
@@ -299,7 +297,6 @@ interface MenuItemIndicatorProps extends PrimitiveSpanProps {
299
297
  * MenuSeparator
300
298
  * -----------------------------------------------------------------------------------------------*/
301
299
 
302
- // type MenuSeparatorElement = React.ElementRef<typeof Stack>
303
300
  interface MenuSeparatorProps extends ViewProps {}
304
301
 
305
302
  /* -------------------------------------------------------------------------------------------------
@@ -308,9 +305,7 @@ interface MenuSeparatorProps extends ViewProps {}
308
305
 
309
306
  // type MenuArrowElement = React.ElementRef<typeof PopperPrimitive.PopperArrow>
310
307
  type PopperArrowProps = React.ComponentPropsWithoutRef<typeof PopperPrimitive.PopperArrow>
311
- interface MenuArrowProps extends PopperArrowProps {
312
- unstyled?: boolean
313
- }
308
+ type MenuArrowProps = PopperArrowProps
314
309
 
315
310
  /* -------------------------------------------------------------------------------------------------
316
311
  * MenuSub
@@ -375,29 +370,11 @@ const { Provider: MenuRootProvider, useStyledContext: useMenuRootContext } =
375
370
 
376
371
  const MENU_CONTEXT = 'MenuContext'
377
372
 
378
- export type CreateBaseMenuProps = {
379
- Item?: typeof MenuPredefined.MenuItem
380
- MenuGroup?: typeof MenuPredefined.MenuGroup
381
- Title?: typeof MenuPredefined.Title
382
- SubTitle?: typeof MenuPredefined.SubTitle
383
- Image?: React.ElementType
384
- Icon?: typeof MenuPredefined.MenuIcon
385
- Indicator?: typeof MenuPredefined.MenuIndicator
386
- Separator?: typeof MenuPredefined.MenuSeparator
387
- Label?: typeof MenuPredefined.MenuLabel
388
- }
373
+ const MenuImageFrame = styled(TamaguiImage, {
374
+ displayName: 'MenuItemImage',
375
+ })
389
376
 
390
- export function createBaseMenu({
391
- Item: _Item = MenuPredefined.MenuItem,
392
- Title: _Title = MenuPredefined.Title,
393
- SubTitle: _SubTitle = MenuPredefined.SubTitle,
394
- Image: _Image = MenuPredefined.MenuImage,
395
- Icon: _Icon = MenuPredefined.MenuIcon,
396
- Indicator: _Indicator = MenuPredefined.MenuIndicator,
397
- Separator: _Separator = MenuPredefined.MenuSeparator,
398
- MenuGroup: _MenuGroup = MenuPredefined.MenuGroup,
399
- Label: _Label = MenuPredefined.MenuLabel,
400
- }: CreateBaseMenuProps) {
377
+ export function createBaseMenu() {
401
378
  const MenuComp = (props: ScopedProps<MenuBaseProps>) => {
402
379
  const direction = useDirection(props.dir)
403
380
  // default placement: bottom-start for LTR, bottom-end for RTL
@@ -604,11 +581,12 @@ export function createBaseMenu({
604
581
  createStyledContext<MenuContentContextValue>()
605
582
 
606
583
  const MenuContentFrame = styled(PopperPrimitive.PopperContentFrame, {
607
- name: CONTENT_NAME,
584
+ displayName: CONTENT_NAME,
608
585
  })
609
586
 
610
- const MenuContent = MenuContentFrame.styleable<ScopedProps<MenuContentProps>>(
611
- (props, forwardedRef) => {
587
+ const MenuContent = createStyledHOC(
588
+ MenuContentFrame,
589
+ (props: ScopedProps<MenuContentProps>, forwardedRef) => {
612
590
  const scope = props.scope || MENU_CONTEXT
613
591
  const portalContext = usePortalContext(scope)
614
592
  const { forceMount = portalContext.forceMount, ...contentProps } = props
@@ -630,7 +608,7 @@ export function createBaseMenu({
630
608
 
631
609
  /* ---------------------------------------------------------------------------------------------- */
632
610
 
633
- const MenuRootContentModal = React.forwardRef<
611
+ const MenuRootContentModal = createRefComponent<
634
612
  MenuRootContentTypeElement,
635
613
  ScopedProps<MenuRootContentTypeProps>
636
614
  >((props, forwardedRef) => {
@@ -662,7 +640,7 @@ export function createBaseMenu({
662
640
  // We make sure we don't trigger our `onDismiss` in such case.
663
641
  onFocusOutside={composeEventHandlers(
664
642
  props.onFocusOutside,
665
- (event: Event) => event.preventDefault(),
643
+ (event) => event.cancel(),
666
644
  { checkDefaultPrevented: false }
667
645
  )}
668
646
  onDismiss={() => context.onOpenChange(false)}
@@ -670,7 +648,7 @@ export function createBaseMenu({
670
648
  )
671
649
  })
672
650
 
673
- const MenuRootContentNonModal = React.forwardRef<
651
+ const MenuRootContentNonModal = createRefComponent<
674
652
  MenuRootContentTypeElement,
675
653
  ScopedProps<MenuRootContentTypeProps>
676
654
  >((props, forwardedRef) => {
@@ -689,9 +667,9 @@ export function createBaseMenu({
689
667
  )
690
668
  })
691
669
 
692
- const MenuContentImpl = React.forwardRef<
670
+ const MenuContentImpl = createRefComponent<
693
671
  MenuContentImplElement,
694
- ScopedProps<StyleableMenuContentProps>
672
+ ScopedProps<StyledHOCMenuContentProps>
695
673
  >((props, forwardedRef) => {
696
674
  const {
697
675
  scope = MENU_CONTEXT,
@@ -708,7 +686,6 @@ export function createBaseMenu({
708
686
  onDismiss,
709
687
  disableOutsideScroll,
710
688
  disableDismissOnScroll = false,
711
- unstyled = process.env.TAMAGUI_HEADLESS === '1',
712
689
  ...contentProps
713
690
  } = props
714
691
 
@@ -805,13 +782,13 @@ export function createBaseMenu({
805
782
  // relative to the viewport. window/document scrolls have `document`
806
783
  // as the target, which contains the anchor, so those still dismiss.
807
784
  if (anchor && !scrolled.contains(anchor)) return
808
- onDismiss?.()
785
+ context.onOpenChange(false)
809
786
  }
810
787
  window.addEventListener('scroll', handleScroll, { capture: true, passive: true })
811
788
  return () => {
812
789
  window.removeEventListener('scroll', handleScroll, { capture: true })
813
790
  }
814
- }, [disableDismissOnScroll, context.open, onDismiss, popperContext.refs])
791
+ }, [disableDismissOnScroll, context.open, context.onOpenChange, popperContext.refs])
815
792
 
816
793
  // Make sure the whole tree has focus guards as our `MenuContent` may be
817
794
  // the last element in the DOM (beacuse of the `Portal`)
@@ -832,14 +809,6 @@ export function createBaseMenu({
832
809
  // tabIndex allows the content to be focusable so that onItemLeave can
833
810
  // focus the content frame and properly blur the previously focused item
834
811
  tabIndex={-1}
835
- unstyled={unstyled}
836
- {...(!unstyled && {
837
- backgroundColor: '$background',
838
- borderWidth: 1,
839
- borderColor: '$borderColor',
840
- outlineWidth: 0,
841
- minWidth: 180,
842
- })}
843
812
  aria-orientation="vertical"
844
813
  data-state={getOpenState(context.open)}
845
814
  data-tamagui-menu-content=""
@@ -945,7 +914,7 @@ export function createBaseMenu({
945
914
  onMountAutoFocus={composeEventHandlers(onOpenAutoFocus, (event) => {
946
915
  // when opening, explicitly focus the content area only and leave
947
916
  // `onEntryFocus` in control of focusing first item
948
- event.preventDefault()
917
+ event.cancel()
949
918
  // contentRef.current doesn't reliably point to the focusable DOM element
950
919
  // due to how refs propagate through Tamagui's styled component chain,
951
920
  // so we query for the element directly using the data attribute
@@ -977,7 +946,7 @@ export function createBaseMenu({
977
946
  // for keyboard users, focus first item for immediate navigation
978
947
  // for mouse users, prevent auto-focus to avoid showing focus style
979
948
  if (!rootContext.isUsingKeyboardRef.current) {
980
- event.preventDefault()
949
+ event.cancel()
981
950
  }
982
951
  })}
983
952
  >
@@ -999,119 +968,118 @@ export function createBaseMenu({
999
968
  const ITEM_NAME = 'MenuItem'
1000
969
  const ITEM_SELECT = 'menu.itemSelect'
1001
970
 
1002
- // use styleable so styled(Menu.Item, { focusStyle }) passes pseudo styles through correctly
1003
- const MenuItem = _Item.styleable<ScopedProps<MenuItemProps>>((props, forwardedRef) => {
1004
- const {
1005
- disabled = false,
1006
- onSelect,
1007
- preventCloseOnSelect,
1008
- children,
1009
- scope = MENU_CONTEXT,
1010
- // filter out native-only props that shouldn't reach the DOM
1011
- // @ts-ignore
1012
- destructive,
1013
- // @ts-ignore
1014
- hidden,
1015
- // @ts-ignore
1016
- androidIconName,
1017
- // @ts-ignore
1018
- iosIconName,
1019
- ...itemProps
1020
- } = props
1021
- const ref = React.useRef<TamaguiElement>(null)
1022
- const rootContext = useMenuRootContext(scope)
1023
- const contentContext = useMenuContentContext(scope)
1024
- const composedRefs = useComposedRefs(forwardedRef, ref)
1025
- const isPointerDownRef = React.useRef(false)
1026
-
1027
- const handleSelect = () => {
1028
- const menuItem = ref.current
1029
- if (!disabled && menuItem) {
1030
- if (isWeb) {
1031
- const menuItemEl = menuItem as HTMLElement
1032
- const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
1033
- bubbles: true,
1034
- cancelable: true,
1035
- })
1036
- menuItemEl.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), {
1037
- once: true,
1038
- })
1039
- dispatchDiscreteCustomEvent(menuItemEl, itemSelectEvent)
1040
- // close the menu unless preventCloseOnSelect is set or event.preventDefault() was called
1041
- if (itemSelectEvent.defaultPrevented || preventCloseOnSelect) {
1042
- isPointerDownRef.current = false
971
+ const MenuItem = createStyledHOC(
972
+ View,
973
+ (props: ScopedProps<MenuItemProps>, forwardedRef) => {
974
+ const {
975
+ disabled = false,
976
+ onSelect,
977
+ preventCloseOnSelect,
978
+ children,
979
+ scope = MENU_CONTEXT,
980
+ // filter out native-only props that shouldn't reach the DOM
981
+ // @ts-ignore
982
+ destructive,
983
+ // @ts-ignore
984
+ hidden,
985
+ // @ts-ignore
986
+ androidIconName,
987
+ ...itemProps
988
+ } = props
989
+ const ref = React.useRef<TamaguiElement>(null)
990
+ const rootContext = useMenuRootContext(scope)
991
+ const contentContext = useMenuContentContext(scope)
992
+ const composedRefs = useComposedRefs(forwardedRef, ref)
993
+ const isPointerDownRef = React.useRef(false)
994
+
995
+ const handleSelect = () => {
996
+ const menuItem = ref.current
997
+ if (!disabled && menuItem) {
998
+ if (isWeb) {
999
+ const menuItemEl = menuItem as HTMLElement
1000
+ const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
1001
+ bubbles: true,
1002
+ cancelable: true,
1003
+ })
1004
+ menuItemEl.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), {
1005
+ once: true,
1006
+ })
1007
+ dispatchDiscreteCustomEvent(menuItemEl, itemSelectEvent)
1008
+ // close the menu unless preventCloseOnSelect is set or event.preventDefault() was called
1009
+ if (itemSelectEvent.defaultPrevented || preventCloseOnSelect) {
1010
+ isPointerDownRef.current = false
1011
+ } else {
1012
+ rootContext.onClose()
1013
+ }
1043
1014
  } else {
1044
- rootContext.onClose()
1045
- }
1046
- } else {
1047
- // TODO: find a better way to handle this on native
1048
- onSelect?.({ target: menuItem } as unknown as Event)
1049
- isPointerDownRef.current = false
1050
- if (!preventCloseOnSelect) {
1051
- rootContext.onClose()
1015
+ // TODO: find a better way to handle this on native
1016
+ onSelect?.({ target: menuItem } as unknown as Event)
1017
+ isPointerDownRef.current = false
1018
+ if (!preventCloseOnSelect) {
1019
+ rootContext.onClose()
1020
+ }
1052
1021
  }
1053
1022
  }
1054
1023
  }
1055
- }
1056
1024
 
1057
- const content = typeof children === 'string' ? <Text>{children}</Text> : children
1025
+ const content = typeof children === 'string' ? <Text>{children}</Text> : children
1058
1026
 
1059
- return (
1060
- <MenuItemImpl
1061
- outlineStyle="none"
1062
- {...itemProps}
1063
- scope={scope}
1064
- // @ts-ignore
1065
- ref={composedRefs}
1066
- disabled={disabled}
1067
- onPress={composeEventHandlers(props.onPress, handleSelect)}
1068
- onPointerDown={(event) => {
1069
- props.onPointerDown?.(event)
1070
- isPointerDownRef.current = true
1071
- }}
1072
- onPointerUp={composeEventHandlers(props.onPointerUp, (event) => {
1073
- // Pointer down can move to a different menu item which should activate it on pointer up.
1074
- // We dispatch a click for selection to allow composition with click based triggers and to
1075
- // prevent Firefox from getting stuck in text selection mode when the menu closes.
1076
- if (isWeb) {
1077
- // @ts-ignore
1078
- if (!isPointerDownRef.current) event.currentTarget?.click()
1079
- }
1080
- })}
1081
- {...(isWeb
1082
- ? {
1083
- onKeyDown: composeEventHandlers(props.onKeyDown, (event) => {
1084
- const isTypingAhead = contentContext.searchRef.current !== ''
1085
- if (disabled || (isTypingAhead && event.key === ' ')) return
1086
- if (SELECTION_KEYS.includes(event.key)) {
1087
- // @ts-ignore
1088
- event.currentTarget?.click()
1089
- /**
1090
- * We prevent default browser behaviour for selection keys as they should trigger
1091
- * a selection only:
1092
- * - prevents space from scrolling the page.
1093
- * - if keydown causes focus to move, prevents keydown from firing on the new target.
1094
- */
1095
- event.preventDefault()
1096
- }
1097
- }),
1027
+ return (
1028
+ <MenuItemImpl
1029
+ {...itemProps}
1030
+ scope={scope}
1031
+ // @ts-ignore
1032
+ ref={composedRefs}
1033
+ disabled={disabled}
1034
+ onPress={composeEventHandlers(props.onPress, handleSelect)}
1035
+ onPointerDown={(event) => {
1036
+ props.onPointerDown?.(event)
1037
+ isPointerDownRef.current = true
1038
+ }}
1039
+ onPointerUp={composeEventHandlers(props.onPointerUp, (event) => {
1040
+ // Pointer down can move to a different menu item which should activate it on pointer up.
1041
+ // We dispatch a click for selection to allow composition with click based triggers and to
1042
+ // prevent Firefox from getting stuck in text selection mode when the menu closes.
1043
+ if (isWeb) {
1044
+ // @ts-ignore
1045
+ if (!isPointerDownRef.current) event.currentTarget?.click()
1098
1046
  }
1099
- : {})}
1100
- >
1101
- {content}
1102
- </MenuItemImpl>
1103
- )
1104
- })
1047
+ })}
1048
+ {...(isWeb
1049
+ ? {
1050
+ onKeyDown: composeEventHandlers(props.onKeyDown, (event) => {
1051
+ const isTypingAhead = contentContext.searchRef.current !== ''
1052
+ if (disabled || (isTypingAhead && event.key === ' ')) return
1053
+ if (SELECTION_KEYS.includes(event.key)) {
1054
+ // @ts-ignore
1055
+ event.currentTarget?.click()
1056
+ /**
1057
+ * We prevent default browser behaviour for selection keys as they should trigger
1058
+ * a selection only:
1059
+ * - prevents space from scrolling the page.
1060
+ * - if keydown causes focus to move, prevents keydown from firing on the new target.
1061
+ */
1062
+ event.preventDefault()
1063
+ }
1064
+ }),
1065
+ }
1066
+ : {})}
1067
+ >
1068
+ {content}
1069
+ </MenuItemImpl>
1070
+ )
1071
+ }
1072
+ )
1105
1073
 
1106
- const MenuItemImpl = React.forwardRef<
1074
+ const MenuItemImpl = createRefComponent<
1107
1075
  MenuItemImplElement,
1108
1076
  ScopedProps<MenuItemImplProps>
1109
1077
  >((props, forwardedRef) => {
1110
1078
  const {
1111
1079
  scope = MENU_CONTEXT,
1080
+ className,
1112
1081
  disabled = false,
1113
1082
  textValue,
1114
- unstyled = process.env.TAMAGUI_HEADLESS === '1',
1115
1083
  ...itemProps
1116
1084
  } = props
1117
1085
  const contentContext = useMenuContentContext(scope)
@@ -1140,11 +1108,10 @@ export function createBaseMenu({
1140
1108
  <RovingFocusGroup.Item
1141
1109
  asChild
1142
1110
  __scopeRovingFocusGroup={scope}
1143
- focusable={!disabled}
1111
+ tabIndex={disabled ? -1 : 0}
1144
1112
  >
1145
- <_Item
1146
- unstyled={unstyled}
1147
- componentName={ITEM_NAME}
1113
+ <View
1114
+ className={`is_${ITEM_NAME} ${className || ''}`.trim()}
1148
1115
  role="menuitem"
1149
1116
  data-highlighted={isFocused ? '' : undefined}
1150
1117
  aria-disabled={disabled || undefined}
@@ -1195,9 +1162,12 @@ export function createBaseMenu({
1195
1162
  * MenuItemTitle
1196
1163
  * -----------------------------------------------------------------------------------------------*/
1197
1164
  const ITEM_TITLE_NAME = 'MenuItemTitle'
1198
- const MenuItemTitle = _Title.styleable<MenuItemTitleProps>((props, forwardedRef) => {
1199
- return <_Title {...props} ref={forwardedRef} />
1200
- })
1165
+ const MenuItemTitle = createStyledHOC(
1166
+ Text,
1167
+ (props: MenuItemTitleProps, forwardedRef) => {
1168
+ return <Text {...props} ref={forwardedRef} />
1169
+ }
1170
+ )
1201
1171
 
1202
1172
  MenuItemTitle.displayName = ITEM_TITLE_NAME
1203
1173
  /* ---------------------------------------------------------------------------------------------- */
@@ -1206,9 +1176,10 @@ export function createBaseMenu({
1206
1176
  * MenuItemSubTitle
1207
1177
  * -----------------------------------------------------------------------------------------------*/
1208
1178
  const ITEM_SUB_TITLE_NAME = 'MenuItemSubTitle'
1209
- const MenuItemSubTitle = _SubTitle.styleable<MenuItemSubTitleProps>(
1210
- (props, forwardedRef) => {
1211
- return <_SubTitle {...props} ref={forwardedRef} />
1179
+ const MenuItemSubTitle = createStyledHOC(
1180
+ Text,
1181
+ (props: MenuItemSubTitleProps, forwardedRef) => {
1182
+ return <Text {...props} ref={forwardedRef} />
1212
1183
  }
1213
1184
  )
1214
1185
 
@@ -1220,19 +1191,19 @@ export function createBaseMenu({
1220
1191
  * MenuItemImage
1221
1192
  * -----------------------------------------------------------------------------------------------*/
1222
1193
  const ITEM_IMAGE = 'MenuItemImage'
1223
- const MenuItemImage = React.forwardRef<Image, ImageProps>((props, forwardedRef) => {
1224
- // filter out native-only props that shouldn't reach the DOM
1225
- const {
1226
- // @ts-ignore - native menu ios config
1227
- ios,
1228
- // @ts-ignore
1229
- androidIconName,
1230
- // @ts-ignore
1231
- iosIconName,
1232
- ...rest
1233
- } = props
1234
- return <_Image {...rest} ref={forwardedRef} />
1235
- })
1194
+ const MenuItemImage = createRefComponent<TamaguiElement, ImageProps>(
1195
+ (props, forwardedRef) => {
1196
+ // filter out native-only props that shouldn't reach the DOM
1197
+ const {
1198
+ // @ts-ignore - native menu ios config
1199
+ ios,
1200
+ // @ts-ignore
1201
+ androidIconName,
1202
+ ...rest
1203
+ } = props
1204
+ return <MenuImageFrame {...rest} ref={forwardedRef} />
1205
+ }
1206
+ )
1236
1207
 
1237
1208
  MenuItemImage.displayName = ITEM_IMAGE
1238
1209
 
@@ -1243,7 +1214,7 @@ export function createBaseMenu({
1243
1214
  * -----------------------------------------------------------------------------------------------*/
1244
1215
 
1245
1216
  const ITEM_ICON = 'MenuItemIcon'
1246
- const MenuItemIcon = _Icon.styleable<MenuItemIconProps>((props, forwardedRef) => {
1217
+ const MenuItemIcon = createStyledHOC(View, (props: MenuItemIconProps, forwardedRef) => {
1247
1218
  // filter out native-only props that shouldn't reach the DOM
1248
1219
  const {
1249
1220
  // @ts-ignore
@@ -1252,11 +1223,9 @@ export function createBaseMenu({
1252
1223
  android,
1253
1224
  // @ts-ignore
1254
1225
  androidIconName,
1255
- // @ts-ignore
1256
- iosIconName,
1257
1226
  ...rest
1258
1227
  } = props
1259
- return <_Icon {...rest} ref={forwardedRef} />
1228
+ return <View {...rest} ref={forwardedRef} />
1260
1229
  })
1261
1230
 
1262
1231
  MenuItemIcon.displayName = ITEM_ICON
@@ -1269,8 +1238,9 @@ export function createBaseMenu({
1269
1238
 
1270
1239
  const CHECKBOX_ITEM_NAME = 'MenuCheckboxItem'
1271
1240
 
1272
- const MenuCheckboxItem = _Item.styleable<ScopedProps<MenuCheckboxItemProps>>(
1273
- (props, forwardedRef) => {
1241
+ const MenuCheckboxItem = createStyledHOC(
1242
+ View,
1243
+ (props: ScopedProps<MenuCheckboxItemProps>, forwardedRef) => {
1274
1244
  const {
1275
1245
  checked = false,
1276
1246
  onCheckedChange,
@@ -1280,12 +1250,13 @@ export function createBaseMenu({
1280
1250
  value,
1281
1251
  // @ts-ignore - native menu value change handler
1282
1252
  onValueChange,
1253
+ className,
1283
1254
  ...checkboxItemProps
1284
1255
  } = props
1285
1256
  return (
1286
1257
  <ItemIndicatorProvider scope={scope} checked={checked}>
1287
1258
  <MenuItem
1288
- componentName={CHECKBOX_ITEM_NAME}
1259
+ className={`is_${CHECKBOX_ITEM_NAME} ${className || ''}`.trim()}
1289
1260
  role={(isWeb ? 'menuitemcheckbox' : 'menuitem') as 'menuitem'}
1290
1261
  aria-checked={isIndeterminate(checked) ? 'mixed' : checked}
1291
1262
  {...checkboxItemProps}
@@ -1313,14 +1284,21 @@ export function createBaseMenu({
1313
1284
  const { Provider: RadioGroupProvider, useStyledContext: useRadioGroupContext } =
1314
1285
  createStyledContext<MenuRadioGroupProps>()
1315
1286
 
1316
- const MenuRadioGroup = _MenuGroup.styleable<ScopedProps<MenuRadioGroupProps>>(
1317
- (props, forwardedRef) => {
1318
- const { value, onValueChange, scope = MENU_CONTEXT, ...groupProps } = props
1287
+ const MenuRadioGroup = createStyledHOC(
1288
+ View,
1289
+ (props: ScopedProps<MenuRadioGroupProps>, forwardedRef) => {
1290
+ const {
1291
+ value,
1292
+ onValueChange,
1293
+ scope = MENU_CONTEXT,
1294
+ className,
1295
+ ...groupProps
1296
+ } = props
1319
1297
  const handleValueChange = useCallbackRef(onValueChange)
1320
1298
  return (
1321
1299
  <RadioGroupProvider scope={scope} value={value} onValueChange={handleValueChange}>
1322
- <_MenuGroup
1323
- componentName={RADIO_GROUP_NAME}
1300
+ <View
1301
+ className={`is_${RADIO_GROUP_NAME} ${className || ''}`.trim()}
1324
1302
  {...groupProps}
1325
1303
  ref={forwardedRef}
1326
1304
  />
@@ -1336,15 +1314,16 @@ export function createBaseMenu({
1336
1314
 
1337
1315
  const RADIO_ITEM_NAME = 'MenuRadioItem'
1338
1316
 
1339
- const MenuRadioItem = _Item.styleable<ScopedProps<MenuRadioItemProps>>(
1340
- (props, forwardedRef) => {
1341
- const { value, scope = MENU_CONTEXT, ...radioItemProps } = props
1317
+ const MenuRadioItem = createStyledHOC(
1318
+ View,
1319
+ (props: ScopedProps<MenuRadioItemProps>, forwardedRef) => {
1320
+ const { value, scope = MENU_CONTEXT, className, ...radioItemProps } = props
1342
1321
  const context = useRadioGroupContext(scope)
1343
1322
  const checked = value === context.value
1344
1323
  return (
1345
1324
  <ItemIndicatorProvider scope={scope} checked={checked}>
1346
1325
  <MenuItem
1347
- componentName={RADIO_ITEM_NAME}
1326
+ className={`is_${RADIO_ITEM_NAME} ${className || ''}`.trim()}
1348
1327
  {...radioItemProps}
1349
1328
  scope={scope}
1350
1329
  aria-checked={checked}
@@ -1373,17 +1352,18 @@ export function createBaseMenu({
1373
1352
  const { Provider: ItemIndicatorProvider, useStyledContext: useItemIndicatorContext } =
1374
1353
  createStyledContext<CheckboxContextValue>()
1375
1354
 
1376
- const MenuItemIndicator = _Indicator.styleable<ScopedProps<MenuItemIndicatorProps>>(
1377
- (props, forwardedRef) => {
1378
- const { scope = MENU_CONTEXT, forceMount, ...itemIndicatorProps } = props
1355
+ const MenuItemIndicator = createStyledHOC(
1356
+ View,
1357
+ (props: ScopedProps<MenuItemIndicatorProps>, forwardedRef) => {
1358
+ const { scope = MENU_CONTEXT, className, forceMount, ...itemIndicatorProps } = props
1379
1359
  const indicatorContext = useItemIndicatorContext(scope)
1380
1360
  return (
1381
1361
  <Presence>
1382
1362
  {forceMount ||
1383
1363
  isIndeterminate(indicatorContext.checked) ||
1384
1364
  indicatorContext.checked === true ? (
1385
- <_Indicator
1386
- componentName={ITEM_INDICATOR_NAME}
1365
+ <View
1366
+ className={`is_${ITEM_INDICATOR_NAME} ${className || ''}`.trim()}
1387
1367
  render="span"
1388
1368
  {...itemIndicatorProps}
1389
1369
  ref={forwardedRef}
@@ -1401,22 +1381,14 @@ export function createBaseMenu({
1401
1381
  * MenuArrow
1402
1382
  * -----------------------------------------------------------------------------------------------*/
1403
1383
 
1404
- // TODO this was styleable but it cant flatten anyways so likely fine just need to check
1405
- const MenuArrow = React.forwardRef<TamaguiElement, MenuArrowProps>(
1384
+ // TODO this wrapped a styled component but it cant flatten anyways so likely fine just need to check
1385
+ const MenuArrow = createRefComponent<TamaguiElement, MenuArrowProps>(
1406
1386
  function MenuArrow(props, forwardedRef) {
1407
- const {
1408
- scope = MENU_CONTEXT,
1409
- unstyled = process.env.TAMAGUI_HEADLESS === '1',
1410
- ...rest
1411
- } = props
1387
+ const { scope = MENU_CONTEXT, className, ...rest } = props
1412
1388
  return (
1413
1389
  <PopperPrimitive.PopperArrow
1414
1390
  scope={scope}
1415
- componentName="PopperArrow"
1416
- unstyled={unstyled}
1417
- {...(!unstyled && {
1418
- backgroundColor: '$background',
1419
- })}
1391
+ className={`is_PopperArrow ${className || ''}`.trim()}
1420
1392
  {...rest}
1421
1393
  ref={forwardedRef}
1422
1394
  />
@@ -1534,7 +1506,7 @@ export function createBaseMenu({
1534
1506
 
1535
1507
  const SUB_TRIGGER_NAME = 'MenuSubTrigger'
1536
1508
 
1537
- const MenuSubTrigger = React.forwardRef<
1509
+ const MenuSubTrigger = createRefComponent<
1538
1510
  TamaguiElement,
1539
1511
  ScopedProps<MenuSubTriggerProps>
1540
1512
  >((props, forwardedRef) => {
@@ -1567,14 +1539,17 @@ export function createBaseMenu({
1567
1539
  }, [pointerGraceTimerRef, onPointerGraceIntentChange])
1568
1540
 
1569
1541
  return (
1570
- <MenuAnchor componentName={SUB_TRIGGER_NAME} asChild="except-style" scope={scope}>
1542
+ <MenuAnchor
1543
+ className={`is_${SUB_TRIGGER_NAME}`}
1544
+ asChild="except-style"
1545
+ scope={scope}
1546
+ >
1571
1547
  <MenuItemImpl
1572
1548
  id={subContext.triggerId}
1573
1549
  aria-haspopup="menu"
1574
1550
  aria-expanded={context.open}
1575
1551
  aria-controls={subContext.contentId}
1576
1552
  data-state={getOpenState(context.open)}
1577
- outlineStyle="none"
1578
1553
  {...props}
1579
1554
  ref={composeRefs(forwardedRef, subContext.onTriggerChange)}
1580
1555
  // This is redundant for mouse users but we cannot determine pointer type from
@@ -1776,11 +1751,12 @@ export function createBaseMenu({
1776
1751
  const SUB_CONTENT_NAME = 'MenuSubContent'
1777
1752
 
1778
1753
  const MenuSubContentFrame = styled(PopperPrimitive.PopperContentFrame, {
1779
- name: SUB_CONTENT_NAME,
1754
+ displayName: SUB_CONTENT_NAME,
1780
1755
  })
1781
1756
 
1782
- const MenuSubContent = MenuSubContentFrame.styleable<ScopedProps<MenuSubContentProps>>(
1783
- (props, forwardedRef) => {
1757
+ const MenuSubContent = createStyledHOC(
1758
+ MenuSubContentFrame,
1759
+ (props: ScopedProps<MenuSubContentProps>, forwardedRef) => {
1784
1760
  const scope = props.scope || MENU_CONTEXT
1785
1761
  const portalContext = usePortalContext(scope)
1786
1762
  const { forceMount = portalContext.forceMount, ...subContentProps } = props
@@ -1834,15 +1810,16 @@ export function createBaseMenu({
1834
1810
  ) as HTMLElement | null
1835
1811
  ;(content || root)?.focus({ preventScroll: true })
1836
1812
  }
1837
- event.preventDefault()
1813
+ event.cancel()
1838
1814
  }}
1839
1815
  // The menu might close because of focusing another menu item in the parent menu. We
1840
1816
  // don't want it to refocus the trigger in that case so we handle trigger focus ourselves.
1841
- onCloseAutoFocus={(event) => event.preventDefault()}
1817
+ onCloseAutoFocus={(event) => event.cancel()}
1842
1818
  onFocusOutside={composeEventHandlers(props.onFocusOutside, (event) => {
1843
1819
  // We prevent closing when the trigger is focused to avoid triggering a re-open animation
1844
1820
  // on pointer interaction.
1845
- if (event.target !== subContext.trigger) context.onOpenChange(false)
1821
+ if (event.event?.target !== subContext.trigger)
1822
+ context.onOpenChange(false)
1846
1823
  })}
1847
1824
  onEscapeKeyDown={composeEventHandlers(props.onEscapeKeyDown, (event) => {
1848
1825
  // close only this submenu, not the root menu
@@ -1850,8 +1827,11 @@ export function createBaseMenu({
1850
1827
  // return focus to the submenu trigger with focusVisible since this is keyboard navigation
1851
1828
  // @ts-ignore focusVisible is a newer API
1852
1829
  subContext.trigger?.focus({ focusVisible: true })
1853
- // ensure pressing escape in submenu doesn't escape full screen mode
1854
- event.preventDefault()
1830
+ // we close the submenu ourselves, so veto the layer's dismiss
1831
+ event.cancel()
1832
+ // and still ensure escape doesn't exit fullscreen (cancel no
1833
+ // longer touches the native event)
1834
+ event.event?.preventDefault?.()
1855
1835
  })}
1856
1836
  {...(isWeb
1857
1837
  ? {
@@ -1888,12 +1868,12 @@ export function createBaseMenu({
1888
1868
  const Anchor = MenuAnchor
1889
1869
  const Portal = MenuPortal
1890
1870
  const Content = MenuContent
1891
- const Group = _MenuGroup.styleable<MenuGroupProps>((props, ref) => {
1892
- return <_MenuGroup {...props} ref={ref} />
1871
+ const Group = createStyledHOC(View, (props: MenuGroupProps, ref) => {
1872
+ return <View role="group" {...props} ref={ref} />
1893
1873
  })
1894
1874
  Group.displayName = 'MenuGroup'
1895
- const Label = _Label.styleable<MenuLabelProps>((props, ref) => {
1896
- return <_Label {...props} ref={ref} />
1875
+ const Label = createStyledHOC(Text, (props: MenuLabelProps, ref) => {
1876
+ return <Text {...props} ref={ref} />
1897
1877
  })
1898
1878
  Label.displayName = 'MenuLabel'
1899
1879
  const Item = MenuItem
@@ -1901,8 +1881,8 @@ export function createBaseMenu({
1901
1881
  const RadioGroup = MenuRadioGroup
1902
1882
  const RadioItem = MenuRadioItem
1903
1883
  const ItemIndicator = MenuItemIndicator
1904
- const Separator = _Separator.styleable<MenuSeparatorProps>((props, ref) => {
1905
- return <_Separator {...props} ref={ref} />
1884
+ const Separator = createStyledHOC(View, (props: MenuSeparatorProps, ref) => {
1885
+ return <View role="separator" aria-orientation="horizontal" {...props} ref={ref} />
1906
1886
  })
1907
1887
  Separator.displayName = 'MenuSeparator'
1908
1888
  const Arrow = MenuArrow