@tamagui/create-menu 2.7.7 → 3.0.0-beta.1093.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 +1032 -1247
  2. package/dist/cjs/createBaseMenu.native.cjs +1207 -0
  3. package/dist/cjs/createBaseMenu.native.js +1149 -1391
  4. package/dist/cjs/createBaseMenu.native.js.map +1 -1
  5. package/dist/cjs/createNativeMenu/createNativeMenu.cjs +345 -377
  6. package/dist/cjs/createNativeMenu/createNativeMenu.native.cjs +438 -0
  7. package/dist/cjs/createNativeMenu/createNativeMenu.native.js +404 -439
  8. package/dist/cjs/createNativeMenu/createNativeMenu.native.js.map +1 -1
  9. package/dist/cjs/createNativeMenu/createNativeMenuTypes.cjs +9 -11
  10. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.cjs +18 -0
  11. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js +9 -10
  12. package/dist/cjs/createNativeMenu/createNativeMenuTypes.native.js.map +1 -1
  13. package/dist/cjs/createNativeMenu/utils.cjs +75 -86
  14. package/dist/cjs/createNativeMenu/utils.native.cjs +106 -0
  15. package/dist/cjs/createNativeMenu/utils.native.js +81 -92
  16. package/dist/cjs/createNativeMenu/utils.native.js.map +1 -1
  17. package/dist/cjs/createNativeMenu/withNativeMenu.cjs +30 -35
  18. package/dist/cjs/createNativeMenu/withNativeMenu.native.cjs +45 -0
  19. package/dist/cjs/createNativeMenu/withNativeMenu.native.js +30 -34
  20. package/dist/cjs/createNativeMenu/withNativeMenu.native.js.map +1 -1
  21. package/dist/cjs/index.cjs +8 -20
  22. package/dist/cjs/index.native.cjs +22 -0
  23. package/dist/cjs/index.native.js +9 -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 +1138 -1370
  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 +18 -17
  40. package/dist/esm/createNativeMenu/withNativeMenu.mjs.map +1 -1
  41. package/dist/esm/createNativeMenu/withNativeMenu.native.js +19 -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 +1207 -0
  49. package/dist/jsx/createBaseMenu.native.js +1149 -1391
  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 +438 -0
  54. package/dist/jsx/createNativeMenu/createNativeMenu.native.js +404 -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 +18 -0
  58. package/dist/jsx/createNativeMenu/createNativeMenuTypes.native.js +9 -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 +106 -0
  63. package/dist/jsx/createNativeMenu/utils.native.js +81 -92
  64. package/dist/jsx/createNativeMenu/utils.native.js.map +1 -1
  65. package/dist/jsx/createNativeMenu/withNativeMenu.mjs +18 -17
  66. package/dist/jsx/createNativeMenu/withNativeMenu.mjs.map +1 -1
  67. package/dist/jsx/createNativeMenu/withNativeMenu.native.cjs +45 -0
  68. package/dist/jsx/createNativeMenu/withNativeMenu.native.js +30 -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 +22 -0
  73. package/dist/jsx/index.native.js +9 -20
  74. package/dist/jsx/index.native.js.map +1 -1
  75. package/package.json +26 -23
  76. package/src/createBaseMenu.tsx +214 -232
  77. package/src/createNativeMenu/createNativeMenuTypes.ts +1 -7
  78. package/src/createNativeMenu/utils.tsx +6 -6
  79. package/src/createNativeMenu/withNativeMenu.tsx +8 -2
  80. package/src/index.tsx +0 -1
  81. package/types/createBaseMenu.d.ts +36 -124
  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 +1 -7
  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
@@ -535,8 +512,10 @@ export function createBaseMenu({
535
512
 
536
513
  const PORTAL_NAME = 'MenuPortal'
537
514
 
515
+ // Menu.Portal is optional, so Content and SubContent read this context with no
516
+ // provider above them: an undefined default makes that a crash
538
517
  const { Provider: PortalProvider, useStyledContext: usePortalContext } =
539
- createStyledContext<PortalContextValue>(undefined, 'Portal')
518
+ createStyledContext<PortalContextValue>({}, 'Portal')
540
519
 
541
520
  const MenuPortal = (props: ScopedProps<MenuPortalProps>) => {
542
521
  const { scope = MENU_CONTEXT, forceMount, zIndex, children } = props
@@ -604,11 +583,12 @@ export function createBaseMenu({
604
583
  createStyledContext<MenuContentContextValue>()
605
584
 
606
585
  const MenuContentFrame = styled(PopperPrimitive.PopperContentFrame, {
607
- name: CONTENT_NAME,
586
+ displayName: CONTENT_NAME,
608
587
  })
609
588
 
610
- const MenuContent = MenuContentFrame.styleable<ScopedProps<MenuContentProps>>(
611
- (props, forwardedRef) => {
589
+ const MenuContent = createStyledHOC(
590
+ MenuContentFrame,
591
+ (props: ScopedProps<MenuContentProps>, forwardedRef) => {
612
592
  const scope = props.scope || MENU_CONTEXT
613
593
  const portalContext = usePortalContext(scope)
614
594
  const { forceMount = portalContext.forceMount, ...contentProps } = props
@@ -630,7 +610,7 @@ export function createBaseMenu({
630
610
 
631
611
  /* ---------------------------------------------------------------------------------------------- */
632
612
 
633
- const MenuRootContentModal = React.forwardRef<
613
+ const MenuRootContentModal = createRefComponent<
634
614
  MenuRootContentTypeElement,
635
615
  ScopedProps<MenuRootContentTypeProps>
636
616
  >((props, forwardedRef) => {
@@ -662,7 +642,7 @@ export function createBaseMenu({
662
642
  // We make sure we don't trigger our `onDismiss` in such case.
663
643
  onFocusOutside={composeEventHandlers(
664
644
  props.onFocusOutside,
665
- (event: Event) => event.preventDefault(),
645
+ (event) => event.cancel(),
666
646
  { checkDefaultPrevented: false }
667
647
  )}
668
648
  onDismiss={() => context.onOpenChange(false)}
@@ -670,7 +650,7 @@ export function createBaseMenu({
670
650
  )
671
651
  })
672
652
 
673
- const MenuRootContentNonModal = React.forwardRef<
653
+ const MenuRootContentNonModal = createRefComponent<
674
654
  MenuRootContentTypeElement,
675
655
  ScopedProps<MenuRootContentTypeProps>
676
656
  >((props, forwardedRef) => {
@@ -689,9 +669,9 @@ export function createBaseMenu({
689
669
  )
690
670
  })
691
671
 
692
- const MenuContentImpl = React.forwardRef<
672
+ const MenuContentImpl = createRefComponent<
693
673
  MenuContentImplElement,
694
- ScopedProps<StyleableMenuContentProps>
674
+ ScopedProps<StyledHOCMenuContentProps>
695
675
  >((props, forwardedRef) => {
696
676
  const {
697
677
  scope = MENU_CONTEXT,
@@ -708,7 +688,6 @@ export function createBaseMenu({
708
688
  onDismiss,
709
689
  disableOutsideScroll,
710
690
  disableDismissOnScroll = false,
711
- unstyled = process.env.TAMAGUI_HEADLESS === '1',
712
691
  ...contentProps
713
692
  } = props
714
693
 
@@ -805,13 +784,13 @@ export function createBaseMenu({
805
784
  // relative to the viewport. window/document scrolls have `document`
806
785
  // as the target, which contains the anchor, so those still dismiss.
807
786
  if (anchor && !scrolled.contains(anchor)) return
808
- onDismiss?.()
787
+ context.onOpenChange(false)
809
788
  }
810
789
  window.addEventListener('scroll', handleScroll, { capture: true, passive: true })
811
790
  return () => {
812
791
  window.removeEventListener('scroll', handleScroll, { capture: true })
813
792
  }
814
- }, [disableDismissOnScroll, context.open, onDismiss, popperContext.refs])
793
+ }, [disableDismissOnScroll, context.open, context.onOpenChange, popperContext.refs])
815
794
 
816
795
  // Make sure the whole tree has focus guards as our `MenuContent` may be
817
796
  // the last element in the DOM (beacuse of the `Portal`)
@@ -832,14 +811,6 @@ export function createBaseMenu({
832
811
  // tabIndex allows the content to be focusable so that onItemLeave can
833
812
  // focus the content frame and properly blur the previously focused item
834
813
  tabIndex={-1}
835
- unstyled={unstyled}
836
- {...(!unstyled && {
837
- backgroundColor: '$background',
838
- borderWidth: 1,
839
- borderColor: '$borderColor',
840
- outlineWidth: 0,
841
- minWidth: 180,
842
- })}
843
814
  aria-orientation="vertical"
844
815
  data-state={getOpenState(context.open)}
845
816
  data-tamagui-menu-content=""
@@ -945,7 +916,7 @@ export function createBaseMenu({
945
916
  onMountAutoFocus={composeEventHandlers(onOpenAutoFocus, (event) => {
946
917
  // when opening, explicitly focus the content area only and leave
947
918
  // `onEntryFocus` in control of focusing first item
948
- event.preventDefault()
919
+ event.cancel()
949
920
  // contentRef.current doesn't reliably point to the focusable DOM element
950
921
  // due to how refs propagate through Tamagui's styled component chain,
951
922
  // so we query for the element directly using the data attribute
@@ -977,7 +948,7 @@ export function createBaseMenu({
977
948
  // for keyboard users, focus first item for immediate navigation
978
949
  // for mouse users, prevent auto-focus to avoid showing focus style
979
950
  if (!rootContext.isUsingKeyboardRef.current) {
980
- event.preventDefault()
951
+ event.cancel()
981
952
  }
982
953
  })}
983
954
  >
@@ -999,119 +970,118 @@ export function createBaseMenu({
999
970
  const ITEM_NAME = 'MenuItem'
1000
971
  const ITEM_SELECT = 'menu.itemSelect'
1001
972
 
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
973
+ const MenuItem = createStyledHOC(
974
+ View,
975
+ (props: ScopedProps<MenuItemProps>, forwardedRef) => {
976
+ const {
977
+ disabled = false,
978
+ onSelect,
979
+ preventCloseOnSelect,
980
+ children,
981
+ scope = MENU_CONTEXT,
982
+ // filter out native-only props that shouldn't reach the DOM
983
+ // @ts-ignore
984
+ destructive,
985
+ // @ts-ignore
986
+ hidden,
987
+ // @ts-ignore
988
+ androidIconName,
989
+ ...itemProps
990
+ } = props
991
+ const ref = React.useRef<TamaguiElement>(null)
992
+ const rootContext = useMenuRootContext(scope)
993
+ const contentContext = useMenuContentContext(scope)
994
+ const composedRefs = useComposedRefs(forwardedRef, ref)
995
+ const isPointerDownRef = React.useRef(false)
996
+
997
+ const handleSelect = () => {
998
+ const menuItem = ref.current
999
+ if (!disabled && menuItem) {
1000
+ if (isWeb) {
1001
+ const menuItemEl = menuItem as HTMLElement
1002
+ const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
1003
+ bubbles: true,
1004
+ cancelable: true,
1005
+ })
1006
+ menuItemEl.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), {
1007
+ once: true,
1008
+ })
1009
+ dispatchDiscreteCustomEvent(menuItemEl, itemSelectEvent)
1010
+ // close the menu unless preventCloseOnSelect is set or event.preventDefault() was called
1011
+ if (itemSelectEvent.defaultPrevented || preventCloseOnSelect) {
1012
+ isPointerDownRef.current = false
1013
+ } else {
1014
+ rootContext.onClose()
1015
+ }
1043
1016
  } 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()
1017
+ // TODO: find a better way to handle this on native
1018
+ onSelect?.({ target: menuItem } as unknown as Event)
1019
+ isPointerDownRef.current = false
1020
+ if (!preventCloseOnSelect) {
1021
+ rootContext.onClose()
1022
+ }
1052
1023
  }
1053
1024
  }
1054
1025
  }
1055
- }
1056
1026
 
1057
- const content = typeof children === 'string' ? <Text>{children}</Text> : children
1027
+ const content = typeof children === 'string' ? <Text>{children}</Text> : children
1058
1028
 
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
- }),
1029
+ return (
1030
+ <MenuItemImpl
1031
+ {...itemProps}
1032
+ scope={scope}
1033
+ // @ts-ignore
1034
+ ref={composedRefs}
1035
+ disabled={disabled}
1036
+ onPress={composeEventHandlers(props.onPress, handleSelect)}
1037
+ onPointerDown={(event) => {
1038
+ props.onPointerDown?.(event)
1039
+ isPointerDownRef.current = true
1040
+ }}
1041
+ onPointerUp={composeEventHandlers(props.onPointerUp, (event) => {
1042
+ // Pointer down can move to a different menu item which should activate it on pointer up.
1043
+ // We dispatch a click for selection to allow composition with click based triggers and to
1044
+ // prevent Firefox from getting stuck in text selection mode when the menu closes.
1045
+ if (isWeb) {
1046
+ // @ts-ignore
1047
+ if (!isPointerDownRef.current) event.currentTarget?.click()
1098
1048
  }
1099
- : {})}
1100
- >
1101
- {content}
1102
- </MenuItemImpl>
1103
- )
1104
- })
1049
+ })}
1050
+ {...(isWeb
1051
+ ? {
1052
+ onKeyDown: composeEventHandlers(props.onKeyDown, (event) => {
1053
+ const isTypingAhead = contentContext.searchRef.current !== ''
1054
+ if (disabled || (isTypingAhead && event.key === ' ')) return
1055
+ if (SELECTION_KEYS.includes(event.key)) {
1056
+ // @ts-ignore
1057
+ event.currentTarget?.click()
1058
+ /**
1059
+ * We prevent default browser behaviour for selection keys as they should trigger
1060
+ * a selection only:
1061
+ * - prevents space from scrolling the page.
1062
+ * - if keydown causes focus to move, prevents keydown from firing on the new target.
1063
+ */
1064
+ event.preventDefault()
1065
+ }
1066
+ }),
1067
+ }
1068
+ : {})}
1069
+ >
1070
+ {content}
1071
+ </MenuItemImpl>
1072
+ )
1073
+ }
1074
+ )
1105
1075
 
1106
- const MenuItemImpl = React.forwardRef<
1076
+ const MenuItemImpl = createRefComponent<
1107
1077
  MenuItemImplElement,
1108
1078
  ScopedProps<MenuItemImplProps>
1109
1079
  >((props, forwardedRef) => {
1110
1080
  const {
1111
1081
  scope = MENU_CONTEXT,
1082
+ className,
1112
1083
  disabled = false,
1113
1084
  textValue,
1114
- unstyled = process.env.TAMAGUI_HEADLESS === '1',
1115
1085
  ...itemProps
1116
1086
  } = props
1117
1087
  const contentContext = useMenuContentContext(scope)
@@ -1140,11 +1110,10 @@ export function createBaseMenu({
1140
1110
  <RovingFocusGroup.Item
1141
1111
  asChild
1142
1112
  __scopeRovingFocusGroup={scope}
1143
- focusable={!disabled}
1113
+ tabIndex={disabled ? -1 : 0}
1144
1114
  >
1145
- <_Item
1146
- unstyled={unstyled}
1147
- componentName={ITEM_NAME}
1115
+ <View
1116
+ className={`is_${ITEM_NAME} ${className || ''}`.trim()}
1148
1117
  role="menuitem"
1149
1118
  data-highlighted={isFocused ? '' : undefined}
1150
1119
  aria-disabled={disabled || undefined}
@@ -1195,9 +1164,12 @@ export function createBaseMenu({
1195
1164
  * MenuItemTitle
1196
1165
  * -----------------------------------------------------------------------------------------------*/
1197
1166
  const ITEM_TITLE_NAME = 'MenuItemTitle'
1198
- const MenuItemTitle = _Title.styleable<MenuItemTitleProps>((props, forwardedRef) => {
1199
- return <_Title {...props} ref={forwardedRef} />
1200
- })
1167
+ const MenuItemTitle = createStyledHOC(
1168
+ Text,
1169
+ (props: MenuItemTitleProps, forwardedRef) => {
1170
+ return <Text {...props} ref={forwardedRef} />
1171
+ }
1172
+ )
1201
1173
 
1202
1174
  MenuItemTitle.displayName = ITEM_TITLE_NAME
1203
1175
  /* ---------------------------------------------------------------------------------------------- */
@@ -1206,9 +1178,10 @@ export function createBaseMenu({
1206
1178
  * MenuItemSubTitle
1207
1179
  * -----------------------------------------------------------------------------------------------*/
1208
1180
  const ITEM_SUB_TITLE_NAME = 'MenuItemSubTitle'
1209
- const MenuItemSubTitle = _SubTitle.styleable<MenuItemSubTitleProps>(
1210
- (props, forwardedRef) => {
1211
- return <_SubTitle {...props} ref={forwardedRef} />
1181
+ const MenuItemSubTitle = createStyledHOC(
1182
+ Text,
1183
+ (props: MenuItemSubTitleProps, forwardedRef) => {
1184
+ return <Text {...props} ref={forwardedRef} />
1212
1185
  }
1213
1186
  )
1214
1187
 
@@ -1220,19 +1193,19 @@ export function createBaseMenu({
1220
1193
  * MenuItemImage
1221
1194
  * -----------------------------------------------------------------------------------------------*/
1222
1195
  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
- })
1196
+ const MenuItemImage = createRefComponent<TamaguiElement, ImageProps>(
1197
+ (props, forwardedRef) => {
1198
+ // filter out native-only props that shouldn't reach the DOM
1199
+ const {
1200
+ // @ts-ignore - native menu ios config
1201
+ ios,
1202
+ // @ts-ignore
1203
+ androidIconName,
1204
+ ...rest
1205
+ } = props
1206
+ return <MenuImageFrame {...rest} ref={forwardedRef} />
1207
+ }
1208
+ )
1236
1209
 
1237
1210
  MenuItemImage.displayName = ITEM_IMAGE
1238
1211
 
@@ -1243,7 +1216,7 @@ export function createBaseMenu({
1243
1216
  * -----------------------------------------------------------------------------------------------*/
1244
1217
 
1245
1218
  const ITEM_ICON = 'MenuItemIcon'
1246
- const MenuItemIcon = _Icon.styleable<MenuItemIconProps>((props, forwardedRef) => {
1219
+ const MenuItemIcon = createStyledHOC(View, (props: MenuItemIconProps, forwardedRef) => {
1247
1220
  // filter out native-only props that shouldn't reach the DOM
1248
1221
  const {
1249
1222
  // @ts-ignore
@@ -1252,11 +1225,9 @@ export function createBaseMenu({
1252
1225
  android,
1253
1226
  // @ts-ignore
1254
1227
  androidIconName,
1255
- // @ts-ignore
1256
- iosIconName,
1257
1228
  ...rest
1258
1229
  } = props
1259
- return <_Icon {...rest} ref={forwardedRef} />
1230
+ return <View {...rest} ref={forwardedRef} />
1260
1231
  })
1261
1232
 
1262
1233
  MenuItemIcon.displayName = ITEM_ICON
@@ -1269,8 +1240,9 @@ export function createBaseMenu({
1269
1240
 
1270
1241
  const CHECKBOX_ITEM_NAME = 'MenuCheckboxItem'
1271
1242
 
1272
- const MenuCheckboxItem = _Item.styleable<ScopedProps<MenuCheckboxItemProps>>(
1273
- (props, forwardedRef) => {
1243
+ const MenuCheckboxItem = createStyledHOC(
1244
+ View,
1245
+ (props: ScopedProps<MenuCheckboxItemProps>, forwardedRef) => {
1274
1246
  const {
1275
1247
  checked = false,
1276
1248
  onCheckedChange,
@@ -1280,12 +1252,13 @@ export function createBaseMenu({
1280
1252
  value,
1281
1253
  // @ts-ignore - native menu value change handler
1282
1254
  onValueChange,
1255
+ className,
1283
1256
  ...checkboxItemProps
1284
1257
  } = props
1285
1258
  return (
1286
1259
  <ItemIndicatorProvider scope={scope} checked={checked}>
1287
1260
  <MenuItem
1288
- componentName={CHECKBOX_ITEM_NAME}
1261
+ className={`is_${CHECKBOX_ITEM_NAME} ${className || ''}`.trim()}
1289
1262
  role={(isWeb ? 'menuitemcheckbox' : 'menuitem') as 'menuitem'}
1290
1263
  aria-checked={isIndeterminate(checked) ? 'mixed' : checked}
1291
1264
  {...checkboxItemProps}
@@ -1313,14 +1286,21 @@ export function createBaseMenu({
1313
1286
  const { Provider: RadioGroupProvider, useStyledContext: useRadioGroupContext } =
1314
1287
  createStyledContext<MenuRadioGroupProps>()
1315
1288
 
1316
- const MenuRadioGroup = _MenuGroup.styleable<ScopedProps<MenuRadioGroupProps>>(
1317
- (props, forwardedRef) => {
1318
- const { value, onValueChange, scope = MENU_CONTEXT, ...groupProps } = props
1289
+ const MenuRadioGroup = createStyledHOC(
1290
+ View,
1291
+ (props: ScopedProps<MenuRadioGroupProps>, forwardedRef) => {
1292
+ const {
1293
+ value,
1294
+ onValueChange,
1295
+ scope = MENU_CONTEXT,
1296
+ className,
1297
+ ...groupProps
1298
+ } = props
1319
1299
  const handleValueChange = useCallbackRef(onValueChange)
1320
1300
  return (
1321
1301
  <RadioGroupProvider scope={scope} value={value} onValueChange={handleValueChange}>
1322
- <_MenuGroup
1323
- componentName={RADIO_GROUP_NAME}
1302
+ <View
1303
+ className={`is_${RADIO_GROUP_NAME} ${className || ''}`.trim()}
1324
1304
  {...groupProps}
1325
1305
  ref={forwardedRef}
1326
1306
  />
@@ -1336,15 +1316,16 @@ export function createBaseMenu({
1336
1316
 
1337
1317
  const RADIO_ITEM_NAME = 'MenuRadioItem'
1338
1318
 
1339
- const MenuRadioItem = _Item.styleable<ScopedProps<MenuRadioItemProps>>(
1340
- (props, forwardedRef) => {
1341
- const { value, scope = MENU_CONTEXT, ...radioItemProps } = props
1319
+ const MenuRadioItem = createStyledHOC(
1320
+ View,
1321
+ (props: ScopedProps<MenuRadioItemProps>, forwardedRef) => {
1322
+ const { value, scope = MENU_CONTEXT, className, ...radioItemProps } = props
1342
1323
  const context = useRadioGroupContext(scope)
1343
1324
  const checked = value === context.value
1344
1325
  return (
1345
1326
  <ItemIndicatorProvider scope={scope} checked={checked}>
1346
1327
  <MenuItem
1347
- componentName={RADIO_ITEM_NAME}
1328
+ className={`is_${RADIO_ITEM_NAME} ${className || ''}`.trim()}
1348
1329
  {...radioItemProps}
1349
1330
  scope={scope}
1350
1331
  aria-checked={checked}
@@ -1373,17 +1354,18 @@ export function createBaseMenu({
1373
1354
  const { Provider: ItemIndicatorProvider, useStyledContext: useItemIndicatorContext } =
1374
1355
  createStyledContext<CheckboxContextValue>()
1375
1356
 
1376
- const MenuItemIndicator = _Indicator.styleable<ScopedProps<MenuItemIndicatorProps>>(
1377
- (props, forwardedRef) => {
1378
- const { scope = MENU_CONTEXT, forceMount, ...itemIndicatorProps } = props
1357
+ const MenuItemIndicator = createStyledHOC(
1358
+ View,
1359
+ (props: ScopedProps<MenuItemIndicatorProps>, forwardedRef) => {
1360
+ const { scope = MENU_CONTEXT, className, forceMount, ...itemIndicatorProps } = props
1379
1361
  const indicatorContext = useItemIndicatorContext(scope)
1380
1362
  return (
1381
1363
  <Presence>
1382
1364
  {forceMount ||
1383
1365
  isIndeterminate(indicatorContext.checked) ||
1384
1366
  indicatorContext.checked === true ? (
1385
- <_Indicator
1386
- componentName={ITEM_INDICATOR_NAME}
1367
+ <View
1368
+ className={`is_${ITEM_INDICATOR_NAME} ${className || ''}`.trim()}
1387
1369
  render="span"
1388
1370
  {...itemIndicatorProps}
1389
1371
  ref={forwardedRef}
@@ -1401,22 +1383,14 @@ export function createBaseMenu({
1401
1383
  * MenuArrow
1402
1384
  * -----------------------------------------------------------------------------------------------*/
1403
1385
 
1404
- // TODO this was styleable but it cant flatten anyways so likely fine just need to check
1405
- const MenuArrow = React.forwardRef<TamaguiElement, MenuArrowProps>(
1386
+ // TODO this wrapped a styled component but it cant flatten anyways so likely fine just need to check
1387
+ const MenuArrow = createRefComponent<TamaguiElement, MenuArrowProps>(
1406
1388
  function MenuArrow(props, forwardedRef) {
1407
- const {
1408
- scope = MENU_CONTEXT,
1409
- unstyled = process.env.TAMAGUI_HEADLESS === '1',
1410
- ...rest
1411
- } = props
1389
+ const { scope = MENU_CONTEXT, className, ...rest } = props
1412
1390
  return (
1413
1391
  <PopperPrimitive.PopperArrow
1414
1392
  scope={scope}
1415
- componentName="PopperArrow"
1416
- unstyled={unstyled}
1417
- {...(!unstyled && {
1418
- backgroundColor: '$background',
1419
- })}
1393
+ className={`is_PopperArrow ${className || ''}`.trim()}
1420
1394
  {...rest}
1421
1395
  ref={forwardedRef}
1422
1396
  />
@@ -1534,7 +1508,7 @@ export function createBaseMenu({
1534
1508
 
1535
1509
  const SUB_TRIGGER_NAME = 'MenuSubTrigger'
1536
1510
 
1537
- const MenuSubTrigger = React.forwardRef<
1511
+ const MenuSubTrigger = createRefComponent<
1538
1512
  TamaguiElement,
1539
1513
  ScopedProps<MenuSubTriggerProps>
1540
1514
  >((props, forwardedRef) => {
@@ -1567,14 +1541,17 @@ export function createBaseMenu({
1567
1541
  }, [pointerGraceTimerRef, onPointerGraceIntentChange])
1568
1542
 
1569
1543
  return (
1570
- <MenuAnchor componentName={SUB_TRIGGER_NAME} asChild="except-style" scope={scope}>
1544
+ <MenuAnchor
1545
+ className={`is_${SUB_TRIGGER_NAME}`}
1546
+ asChild="except-style"
1547
+ scope={scope}
1548
+ >
1571
1549
  <MenuItemImpl
1572
1550
  id={subContext.triggerId}
1573
1551
  aria-haspopup="menu"
1574
1552
  aria-expanded={context.open}
1575
1553
  aria-controls={subContext.contentId}
1576
1554
  data-state={getOpenState(context.open)}
1577
- outlineStyle="none"
1578
1555
  {...props}
1579
1556
  ref={composeRefs(forwardedRef, subContext.onTriggerChange)}
1580
1557
  // This is redundant for mouse users but we cannot determine pointer type from
@@ -1776,11 +1753,12 @@ export function createBaseMenu({
1776
1753
  const SUB_CONTENT_NAME = 'MenuSubContent'
1777
1754
 
1778
1755
  const MenuSubContentFrame = styled(PopperPrimitive.PopperContentFrame, {
1779
- name: SUB_CONTENT_NAME,
1756
+ displayName: SUB_CONTENT_NAME,
1780
1757
  })
1781
1758
 
1782
- const MenuSubContent = MenuSubContentFrame.styleable<ScopedProps<MenuSubContentProps>>(
1783
- (props, forwardedRef) => {
1759
+ const MenuSubContent = createStyledHOC(
1760
+ MenuSubContentFrame,
1761
+ (props: ScopedProps<MenuSubContentProps>, forwardedRef) => {
1784
1762
  const scope = props.scope || MENU_CONTEXT
1785
1763
  const portalContext = usePortalContext(scope)
1786
1764
  const { forceMount = portalContext.forceMount, ...subContentProps } = props
@@ -1834,15 +1812,16 @@ export function createBaseMenu({
1834
1812
  ) as HTMLElement | null
1835
1813
  ;(content || root)?.focus({ preventScroll: true })
1836
1814
  }
1837
- event.preventDefault()
1815
+ event.cancel()
1838
1816
  }}
1839
1817
  // The menu might close because of focusing another menu item in the parent menu. We
1840
1818
  // don't want it to refocus the trigger in that case so we handle trigger focus ourselves.
1841
- onCloseAutoFocus={(event) => event.preventDefault()}
1819
+ onCloseAutoFocus={(event) => event.cancel()}
1842
1820
  onFocusOutside={composeEventHandlers(props.onFocusOutside, (event) => {
1843
1821
  // We prevent closing when the trigger is focused to avoid triggering a re-open animation
1844
1822
  // on pointer interaction.
1845
- if (event.target !== subContext.trigger) context.onOpenChange(false)
1823
+ if (event.event?.target !== subContext.trigger)
1824
+ context.onOpenChange(false)
1846
1825
  })}
1847
1826
  onEscapeKeyDown={composeEventHandlers(props.onEscapeKeyDown, (event) => {
1848
1827
  // close only this submenu, not the root menu
@@ -1850,8 +1829,11 @@ export function createBaseMenu({
1850
1829
  // return focus to the submenu trigger with focusVisible since this is keyboard navigation
1851
1830
  // @ts-ignore focusVisible is a newer API
1852
1831
  subContext.trigger?.focus({ focusVisible: true })
1853
- // ensure pressing escape in submenu doesn't escape full screen mode
1854
- event.preventDefault()
1832
+ // we close the submenu ourselves, so veto the layer's dismiss
1833
+ event.cancel()
1834
+ // and still ensure escape doesn't exit fullscreen (cancel no
1835
+ // longer touches the native event)
1836
+ event.event?.preventDefault?.()
1855
1837
  })}
1856
1838
  {...(isWeb
1857
1839
  ? {
@@ -1888,12 +1870,12 @@ export function createBaseMenu({
1888
1870
  const Anchor = MenuAnchor
1889
1871
  const Portal = MenuPortal
1890
1872
  const Content = MenuContent
1891
- const Group = _MenuGroup.styleable<MenuGroupProps>((props, ref) => {
1892
- return <_MenuGroup {...props} ref={ref} />
1873
+ const Group = createStyledHOC(View, (props: MenuGroupProps, ref) => {
1874
+ return <View role="group" {...props} ref={ref} />
1893
1875
  })
1894
1876
  Group.displayName = 'MenuGroup'
1895
- const Label = _Label.styleable<MenuLabelProps>((props, ref) => {
1896
- return <_Label {...props} ref={ref} />
1877
+ const Label = createStyledHOC(Text, (props: MenuLabelProps, ref) => {
1878
+ return <Text {...props} ref={ref} />
1897
1879
  })
1898
1880
  Label.displayName = 'MenuLabel'
1899
1881
  const Item = MenuItem
@@ -1901,8 +1883,8 @@ export function createBaseMenu({
1901
1883
  const RadioGroup = MenuRadioGroup
1902
1884
  const RadioItem = MenuRadioItem
1903
1885
  const ItemIndicator = MenuItemIndicator
1904
- const Separator = _Separator.styleable<MenuSeparatorProps>((props, ref) => {
1905
- return <_Separator {...props} ref={ref} />
1886
+ const Separator = createStyledHOC(View, (props: MenuSeparatorProps, ref) => {
1887
+ return <View role="separator" aria-orientation="horizontal" {...props} ref={ref} />
1906
1888
  })
1907
1889
  Separator.displayName = 'MenuSeparator'
1908
1890
  const Arrow = MenuArrow