@tamagui/menu 3.0.0-beta.924.1 → 3.0.0
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.
- package/dist/cjs/Menu.cjs +1 -0
- package/dist/cjs/Menu.native.cjs +1 -0
- package/dist/cjs/Menu.native.js +1 -0
- package/dist/cjs/Menu.native.js.map +1 -1
- package/dist/cjs/createNonNativeMenu.cjs +185 -13
- package/dist/cjs/createNonNativeMenu.native.cjs +229 -22
- package/dist/cjs/createNonNativeMenu.native.js +229 -22
- package/dist/cjs/createNonNativeMenu.native.js.map +1 -1
- package/dist/esm/Menu.mjs +1 -0
- package/dist/esm/Menu.mjs.map +1 -1
- package/dist/esm/Menu.native.js +1 -0
- package/dist/esm/Menu.native.js.map +1 -1
- package/dist/esm/createNonNativeMenu.mjs +185 -13
- package/dist/esm/createNonNativeMenu.mjs.map +1 -1
- package/dist/esm/createNonNativeMenu.native.js +229 -22
- package/dist/esm/createNonNativeMenu.native.js.map +1 -1
- package/dist/jsx/Menu.mjs +1 -0
- package/dist/jsx/Menu.mjs.map +1 -1
- package/dist/jsx/Menu.native.cjs +1 -0
- package/dist/jsx/Menu.native.js +1 -0
- package/dist/jsx/Menu.native.js.map +1 -1
- package/dist/jsx/createNonNativeMenu.mjs +185 -13
- package/dist/jsx/createNonNativeMenu.mjs.map +1 -1
- package/dist/jsx/createNonNativeMenu.native.cjs +229 -22
- package/dist/jsx/createNonNativeMenu.native.js +229 -22
- package/dist/jsx/createNonNativeMenu.native.js.map +1 -1
- package/package.json +14 -12
- package/src/Menu.tsx +1 -0
- package/src/createNonNativeMenu.tsx +273 -18
- package/src/index.tsx +2 -0
- package/types/Menu.d.ts +31 -14
- package/types/Menu.d.ts.map +1 -1
- package/types/createNonNativeMenu.d.ts +33 -33
- package/types/createNonNativeMenu.d.ts.map +1 -1
- package/types/index.d.ts +32 -14
- package/types/index.d.ts.map +1 -1
|
@@ -49,6 +49,120 @@ type ScopedProps<P> = P & { scope?: string }
|
|
|
49
49
|
|
|
50
50
|
type MenuTriggerStateSetter = React.Dispatch<React.SetStateAction<boolean>>
|
|
51
51
|
|
|
52
|
+
type TriggerGroupEntry = {
|
|
53
|
+
id: string
|
|
54
|
+
ref: React.RefObject<TamaguiElement | null>
|
|
55
|
+
openRef: React.RefObject<boolean>
|
|
56
|
+
disabled: boolean
|
|
57
|
+
activate(): void
|
|
58
|
+
focusContent(): void
|
|
59
|
+
onOpenChange(open: boolean): void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type TriggerGroupContextValue = {
|
|
63
|
+
dir: Direction
|
|
64
|
+
tabStopId: string | null
|
|
65
|
+
register(entry: TriggerGroupEntry): () => void
|
|
66
|
+
activate(entry: TriggerGroupEntry): void
|
|
67
|
+
isOpen(): boolean
|
|
68
|
+
isActive(openRef: React.RefObject<boolean>): boolean
|
|
69
|
+
contains(target: EventTarget | null): boolean
|
|
70
|
+
move(id: string, key: string, open: boolean): void
|
|
71
|
+
onFocus(id: string): void
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type MenuTriggerGroupProps = ViewProps & { dir?: Direction }
|
|
75
|
+
|
|
76
|
+
const TriggerGroupContext = React.createContext<TriggerGroupContextValue | null>(null)
|
|
77
|
+
|
|
78
|
+
const MenuTriggerGroup = createStyledHOC(
|
|
79
|
+
View,
|
|
80
|
+
({ children, dir = 'ltr', ...props }: MenuTriggerGroupProps, forwardedRef) => {
|
|
81
|
+
const entries = React.useRef(new Map<string, TriggerGroupEntry>())
|
|
82
|
+
const active = React.useRef<TriggerGroupEntry | null>(null)
|
|
83
|
+
const [tabStopId, setTabStopId] = React.useState<string | null>(null)
|
|
84
|
+
const register = useEvent((entry: TriggerGroupEntry) => {
|
|
85
|
+
entries.current.set(entry.id, entry)
|
|
86
|
+
if (!entry.disabled) setTabStopId((id) => id ?? entry.id)
|
|
87
|
+
return () => {
|
|
88
|
+
entries.current.delete(entry.id)
|
|
89
|
+
if (active.current === entry) active.current = null
|
|
90
|
+
setTabStopId((id) =>
|
|
91
|
+
id === entry.id
|
|
92
|
+
? ([...entries.current.values()].find((item) => !item.disabled)?.id ?? null)
|
|
93
|
+
: id
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
const activate = useEvent((entry: TriggerGroupEntry) => {
|
|
98
|
+
const previous = active.current
|
|
99
|
+
active.current = entry
|
|
100
|
+
setTabStopId(entry.id)
|
|
101
|
+
if (previous && previous.openRef !== entry.openRef && previous.openRef.current) {
|
|
102
|
+
previous.onOpenChange(false)
|
|
103
|
+
}
|
|
104
|
+
entry.activate()
|
|
105
|
+
})
|
|
106
|
+
const isOpen = useEvent(() => Boolean(active.current?.openRef.current))
|
|
107
|
+
const isActive = useEvent(
|
|
108
|
+
(openRef: React.RefObject<boolean>) => active.current?.openRef === openRef
|
|
109
|
+
)
|
|
110
|
+
const contains = useEvent((target: EventTarget | null) =>
|
|
111
|
+
[...entries.current.values()].some((entry) =>
|
|
112
|
+
(entry.ref.current as HTMLElement | null)?.contains(target as Node)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
const move = useEvent((id: string, key: string, open: boolean) => {
|
|
116
|
+
const ordered = [...entries.current.values()]
|
|
117
|
+
.filter((entry) => !entry.disabled)
|
|
118
|
+
.sort((a, b) => {
|
|
119
|
+
const first = a.ref.current as HTMLElement | null
|
|
120
|
+
const second = b.ref.current as HTMLElement | null
|
|
121
|
+
return first && second && first.compareDocumentPosition(second) & 2 ? 1 : -1
|
|
122
|
+
})
|
|
123
|
+
const index = ordered.findIndex((entry) => entry.id === id)
|
|
124
|
+
if (index === -1) return
|
|
125
|
+
const step = (key === 'ArrowRight') === (dir === 'ltr') ? 1 : -1
|
|
126
|
+
const next = ordered[(index + step + ordered.length) % ordered.length]
|
|
127
|
+
next.ref.current?.focus?.()
|
|
128
|
+
if (open) {
|
|
129
|
+
activate(next)
|
|
130
|
+
next.onOpenChange(true)
|
|
131
|
+
requestAnimationFrame(() => next.focusContent())
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
const onFocus = useEvent((id: string) => setTabStopId(id))
|
|
135
|
+
const value = React.useMemo(
|
|
136
|
+
() => ({
|
|
137
|
+
dir,
|
|
138
|
+
tabStopId,
|
|
139
|
+
register,
|
|
140
|
+
activate,
|
|
141
|
+
isOpen,
|
|
142
|
+
isActive,
|
|
143
|
+
contains,
|
|
144
|
+
move,
|
|
145
|
+
onFocus,
|
|
146
|
+
}),
|
|
147
|
+
[dir, tabStopId, register, activate, isOpen, isActive, contains, move, onFocus]
|
|
148
|
+
)
|
|
149
|
+
return (
|
|
150
|
+
<TriggerGroupContext.Provider value={value}>
|
|
151
|
+
<View
|
|
152
|
+
flexDirection="row"
|
|
153
|
+
role="menubar"
|
|
154
|
+
{...props}
|
|
155
|
+
{...(isWeb && { dir })}
|
|
156
|
+
ref={forwardedRef}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</View>
|
|
160
|
+
</TriggerGroupContext.Provider>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
MenuTriggerGroup.displayName = 'MenuTriggerGroup'
|
|
165
|
+
|
|
52
166
|
type MenuContextValue = {
|
|
53
167
|
triggerId: string
|
|
54
168
|
triggerRef: React.RefObject<TamaguiElement | null>
|
|
@@ -58,13 +172,26 @@ type MenuContextValue = {
|
|
|
58
172
|
onOpenToggle(): void
|
|
59
173
|
modal: boolean
|
|
60
174
|
setActiveTrigger(id: string | null): void
|
|
61
|
-
registerTrigger(
|
|
175
|
+
registerTrigger(
|
|
176
|
+
id: string,
|
|
177
|
+
setOpen: MenuTriggerStateSetter,
|
|
178
|
+
group: TriggerGroupContextValue | null
|
|
179
|
+
): void
|
|
62
180
|
unregisterTrigger(id: string): void
|
|
181
|
+
activeTriggerIdRef: React.RefObject<string | null>
|
|
182
|
+
activeGroupRef: React.RefObject<TriggerGroupContextValue | null>
|
|
63
183
|
}
|
|
64
184
|
|
|
65
185
|
function useMenuTriggerSetup(open: boolean) {
|
|
66
186
|
const triggerStateSettersRef = React.useRef(new Map<string, MenuTriggerStateSetter>())
|
|
67
187
|
const activeTriggerIdRef = React.useRef<string | null>(null)
|
|
188
|
+
const triggerGroupsRef = React.useRef(
|
|
189
|
+
new Map<string, TriggerGroupContextValue | null>()
|
|
190
|
+
)
|
|
191
|
+
const activeGroupRef = React.useRef<TriggerGroupContextValue | null>(null)
|
|
192
|
+
const [activeGroup, setActiveGroup] = React.useState<TriggerGroupContextValue | null>(
|
|
193
|
+
null
|
|
194
|
+
)
|
|
68
195
|
|
|
69
196
|
const setActiveTrigger = useEvent((id: string | null) => {
|
|
70
197
|
const prevId = activeTriggerIdRef.current
|
|
@@ -73,20 +200,32 @@ function useMenuTriggerSetup(open: boolean) {
|
|
|
73
200
|
triggerStateSettersRef.current.get(prevId)?.(false)
|
|
74
201
|
}
|
|
75
202
|
activeTriggerIdRef.current = id
|
|
203
|
+
activeGroupRef.current = id ? (triggerGroupsRef.current.get(id) ?? null) : null
|
|
204
|
+
setActiveGroup(activeGroupRef.current)
|
|
76
205
|
if (id && open) {
|
|
77
206
|
triggerStateSettersRef.current.get(id)?.(true)
|
|
78
207
|
}
|
|
79
208
|
})
|
|
80
209
|
|
|
81
|
-
const registerTrigger = useEvent(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
210
|
+
const registerTrigger = useEvent(
|
|
211
|
+
(
|
|
212
|
+
id: string,
|
|
213
|
+
setOpenState: MenuTriggerStateSetter,
|
|
214
|
+
group: TriggerGroupContextValue | null
|
|
215
|
+
) => {
|
|
216
|
+
triggerGroupsRef.current.set(id, group)
|
|
217
|
+
triggerStateSettersRef.current.set(id, setOpenState)
|
|
218
|
+
setOpenState(activeTriggerIdRef.current === id && open)
|
|
219
|
+
}
|
|
220
|
+
)
|
|
85
221
|
|
|
86
222
|
const unregisterTrigger = useEvent((id: string) => {
|
|
87
223
|
triggerStateSettersRef.current.delete(id)
|
|
224
|
+
triggerGroupsRef.current.delete(id)
|
|
88
225
|
if (activeTriggerIdRef.current === id) {
|
|
89
226
|
activeTriggerIdRef.current = null
|
|
227
|
+
activeGroupRef.current = null
|
|
228
|
+
setActiveGroup(null)
|
|
90
229
|
}
|
|
91
230
|
})
|
|
92
231
|
|
|
@@ -101,7 +240,14 @@ function useMenuTriggerSetup(open: boolean) {
|
|
|
101
240
|
}
|
|
102
241
|
}, [open, setActiveTrigger])
|
|
103
242
|
|
|
104
|
-
return {
|
|
243
|
+
return {
|
|
244
|
+
setActiveTrigger,
|
|
245
|
+
registerTrigger,
|
|
246
|
+
unregisterTrigger,
|
|
247
|
+
activeTriggerIdRef,
|
|
248
|
+
activeGroupRef,
|
|
249
|
+
activeGroup,
|
|
250
|
+
}
|
|
105
251
|
}
|
|
106
252
|
|
|
107
253
|
interface MenuProps extends BaseMenuTypes.MenuProps {
|
|
@@ -225,6 +371,7 @@ export function createNonNativeMenu() {
|
|
|
225
371
|
modal = true,
|
|
226
372
|
...rest
|
|
227
373
|
} = props
|
|
374
|
+
const outerGroup = React.useContext(TriggerGroupContext)
|
|
228
375
|
const triggerRef = React.useRef<TamaguiElement>(null)
|
|
229
376
|
const [open = false, setOpen] = useControllableState({
|
|
230
377
|
prop: openProp,
|
|
@@ -233,8 +380,15 @@ export function createNonNativeMenu() {
|
|
|
233
380
|
})
|
|
234
381
|
const openRef = React.useRef(open)
|
|
235
382
|
openRef.current = open
|
|
236
|
-
const {
|
|
237
|
-
|
|
383
|
+
const {
|
|
384
|
+
setActiveTrigger,
|
|
385
|
+
registerTrigger,
|
|
386
|
+
unregisterTrigger,
|
|
387
|
+
activeTriggerIdRef,
|
|
388
|
+
activeGroupRef,
|
|
389
|
+
activeGroup,
|
|
390
|
+
} = useMenuTriggerSetup(open)
|
|
391
|
+
const effectiveModal = outerGroup || activeGroup ? false : modal
|
|
238
392
|
|
|
239
393
|
return (
|
|
240
394
|
<MenuProvider
|
|
@@ -251,17 +405,19 @@ export function createNonNativeMenu() {
|
|
|
251
405
|
() => setOpen((prevOpen) => !prevOpen),
|
|
252
406
|
[setOpen]
|
|
253
407
|
)}
|
|
254
|
-
modal={
|
|
408
|
+
modal={effectiveModal}
|
|
255
409
|
setActiveTrigger={setActiveTrigger}
|
|
256
410
|
registerTrigger={registerTrigger}
|
|
257
411
|
unregisterTrigger={unregisterTrigger}
|
|
412
|
+
activeTriggerIdRef={activeTriggerIdRef}
|
|
413
|
+
activeGroupRef={activeGroupRef}
|
|
258
414
|
>
|
|
259
415
|
<Menu
|
|
260
416
|
scope={scope || DROPDOWN_MENU_CONTEXT}
|
|
261
417
|
open={open}
|
|
262
418
|
onOpenChange={setOpen}
|
|
263
|
-
dir={dir}
|
|
264
|
-
modal={
|
|
419
|
+
dir={dir ?? outerGroup?.dir}
|
|
420
|
+
modal={effectiveModal}
|
|
265
421
|
{...rest}
|
|
266
422
|
>
|
|
267
423
|
{children}
|
|
@@ -293,6 +449,7 @@ export function createNonNativeMenu() {
|
|
|
293
449
|
...triggerProps
|
|
294
450
|
} = props
|
|
295
451
|
const context = useMenuContext(scope)
|
|
452
|
+
const group = React.useContext(TriggerGroupContext)
|
|
296
453
|
const popperCtx = usePopperContextSlow(scope || DROPDOWN_MENU_CONTEXT)
|
|
297
454
|
const Comp = asChild ? Slot : View
|
|
298
455
|
const isTouchDevice = useIsTouchDevice()
|
|
@@ -305,9 +462,9 @@ export function createNonNativeMenu() {
|
|
|
305
462
|
// extract stable refs so re-registration doesn't happen when context object changes
|
|
306
463
|
const { registerTrigger, unregisterTrigger } = context
|
|
307
464
|
React.useEffect(() => {
|
|
308
|
-
registerTrigger(triggerId, setTriggerOpen)
|
|
465
|
+
registerTrigger(triggerId, setTriggerOpen, group)
|
|
309
466
|
return () => unregisterTrigger(triggerId)
|
|
310
|
-
}, [registerTrigger, unregisterTrigger, triggerId])
|
|
467
|
+
}, [registerTrigger, unregisterTrigger, triggerId, group?.register])
|
|
311
468
|
|
|
312
469
|
// activate this trigger: set popper reference and update shared triggerRef for close-auto-focus
|
|
313
470
|
const activateSelf = React.useCallback(() => {
|
|
@@ -323,6 +480,40 @@ export function createNonNativeMenu() {
|
|
|
323
480
|
}
|
|
324
481
|
}, [context, triggerId, popperCtx])
|
|
325
482
|
|
|
483
|
+
const activate = useEvent(activateSelf)
|
|
484
|
+
const focusContent = useEvent(() => {
|
|
485
|
+
document
|
|
486
|
+
.getElementById(context.contentId)
|
|
487
|
+
?.querySelector<HTMLElement>('[role^="menuitem"]:not([data-disabled])')
|
|
488
|
+
?.focus()
|
|
489
|
+
})
|
|
490
|
+
const entry = React.useRef<TriggerGroupEntry>(null!)
|
|
491
|
+
if (!entry.current) {
|
|
492
|
+
entry.current = {
|
|
493
|
+
id: triggerId,
|
|
494
|
+
ref: triggerElRef,
|
|
495
|
+
openRef: context.openRef,
|
|
496
|
+
disabled,
|
|
497
|
+
activate,
|
|
498
|
+
focusContent,
|
|
499
|
+
onOpenChange: context.onOpenChange,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
entry.current.disabled = disabled
|
|
503
|
+
entry.current.onOpenChange = context.onOpenChange
|
|
504
|
+
const registerGroup = group?.register
|
|
505
|
+
React.useEffect(() => registerGroup?.(entry.current), [registerGroup])
|
|
506
|
+
const openSelf = () => {
|
|
507
|
+
if (group) group.activate(entry.current)
|
|
508
|
+
else activateSelf()
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const shouldRejectHover = () =>
|
|
512
|
+
disabled ||
|
|
513
|
+
(context.openRef.current &&
|
|
514
|
+
context.activeTriggerIdRef.current !== triggerId &&
|
|
515
|
+
!group?.isActive(context.openRef))
|
|
516
|
+
|
|
326
517
|
// Use onClick for touch devices to avoid race condition with Dismissable
|
|
327
518
|
// Use onPointerDown for mouse for faster feedback
|
|
328
519
|
const pressEvent = isWeb ? (isTouchDevice ? 'onClick' : 'onPointerDown') : 'onPress'
|
|
@@ -334,7 +525,8 @@ export function createNonNativeMenu() {
|
|
|
334
525
|
scope={scope || DROPDOWN_MENU_CONTEXT}
|
|
335
526
|
>
|
|
336
527
|
<Comp
|
|
337
|
-
role=
|
|
528
|
+
role={group ? 'menuitem' : 'button'}
|
|
529
|
+
tabIndex={group ? (group.tabStopId === triggerId ? 0 : -1) : undefined}
|
|
338
530
|
id={context.triggerId}
|
|
339
531
|
aria-haspopup="menu"
|
|
340
532
|
aria-expanded={triggerOpen}
|
|
@@ -342,13 +534,42 @@ export function createNonNativeMenu() {
|
|
|
342
534
|
data-state={triggerOpen ? 'open' : 'closed'}
|
|
343
535
|
data-disabled={disabled ? '' : undefined}
|
|
344
536
|
aria-disabled={disabled || undefined}
|
|
345
|
-
ref={composeRefs(
|
|
537
|
+
ref={composeRefs(
|
|
538
|
+
forwardedRef,
|
|
539
|
+
group ? undefined : context.triggerRef,
|
|
540
|
+
triggerElRef
|
|
541
|
+
)}
|
|
346
542
|
// caller props come first: the composed handlers below already call
|
|
347
543
|
// the caller's own handler, so spreading them after would let a
|
|
348
544
|
// caller's onPointerDown/onClick/onPress or onKeyDown replace the
|
|
349
545
|
// one that opens the menu. the press prop stays read off `props`
|
|
350
546
|
// because which of the three opens the menu is decided at runtime.
|
|
351
547
|
{...triggerProps}
|
|
548
|
+
onFocus={composeEventHandlers(props.onFocus, () => {
|
|
549
|
+
if (!disabled) group?.onFocus(triggerId)
|
|
550
|
+
})}
|
|
551
|
+
onPointerEnter={(event) => {
|
|
552
|
+
// popper checks this child handler before changing its reference.
|
|
553
|
+
if (shouldRejectHover()) {
|
|
554
|
+
event.preventDefault()
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
props.onPointerEnter?.(event)
|
|
558
|
+
}}
|
|
559
|
+
onMouseEnter={(event) => {
|
|
560
|
+
// reject before caller handlers can change the shared descriptor.
|
|
561
|
+
if (shouldRejectHover()) {
|
|
562
|
+
event.preventDefault()
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
composeEventHandlers(props.onMouseEnter, () => {
|
|
566
|
+
if (group?.isOpen() && !triggerOpen) {
|
|
567
|
+
triggerElRef.current?.focus?.()
|
|
568
|
+
openSelf()
|
|
569
|
+
context.onOpenChange(true)
|
|
570
|
+
}
|
|
571
|
+
})?.(event)
|
|
572
|
+
}}
|
|
352
573
|
{...{
|
|
353
574
|
[pressEvent]: composeEventHandlers(
|
|
354
575
|
//@ts-ignore
|
|
@@ -367,7 +588,7 @@ export function createNonNativeMenu() {
|
|
|
367
588
|
if (context.openRef.current) {
|
|
368
589
|
context.setActiveTrigger(null)
|
|
369
590
|
} else {
|
|
370
|
-
|
|
591
|
+
openSelf()
|
|
371
592
|
}
|
|
372
593
|
context.onOpenToggle()
|
|
373
594
|
// prevent trigger focusing when opening
|
|
@@ -380,16 +601,21 @@ export function createNonNativeMenu() {
|
|
|
380
601
|
{...(isWeb && {
|
|
381
602
|
onKeyDown: composeEventHandlers(onKeyDown, (event) => {
|
|
382
603
|
if (disabled) return
|
|
604
|
+
if (group && ['ArrowLeft', 'ArrowRight'].includes(event.key)) {
|
|
605
|
+
group.move(triggerId, event.key, group.isOpen())
|
|
606
|
+
event.preventDefault()
|
|
607
|
+
return
|
|
608
|
+
}
|
|
383
609
|
if (['Enter', ' '].includes(event.key)) {
|
|
384
610
|
if (context.openRef.current) {
|
|
385
611
|
context.setActiveTrigger(null)
|
|
386
612
|
} else {
|
|
387
|
-
|
|
613
|
+
openSelf()
|
|
388
614
|
}
|
|
389
615
|
context.onOpenToggle()
|
|
390
616
|
}
|
|
391
617
|
if (event.key === 'ArrowDown') {
|
|
392
|
-
|
|
618
|
+
openSelf()
|
|
393
619
|
context.onOpenChange(true)
|
|
394
620
|
}
|
|
395
621
|
// prevent keydown from scrolling window / first focused item to execute
|
|
@@ -453,7 +679,30 @@ export function createNonNativeMenu() {
|
|
|
453
679
|
scope={scope || DROPDOWN_MENU_CONTEXT}
|
|
454
680
|
{...contentProps}
|
|
455
681
|
ref={forwardedRef}
|
|
682
|
+
onKeyDown={composeEventHandlers(props.onKeyDown, (event) => {
|
|
683
|
+
const group = context.activeGroupRef.current
|
|
684
|
+
if (!group || !['ArrowLeft', 'ArrowRight'].includes(event.key)) return
|
|
685
|
+
// submenu handlers own their open/close keys, including portal bubbling
|
|
686
|
+
const target = event.target as HTMLElement
|
|
687
|
+
if (target.closest('[data-tamagui-menu-content]') !== event.currentTarget)
|
|
688
|
+
return
|
|
689
|
+
if (
|
|
690
|
+
target.closest('[aria-haspopup="menu"]') &&
|
|
691
|
+
event.key === (group.dir === 'rtl' ? 'ArrowLeft' : 'ArrowRight')
|
|
692
|
+
)
|
|
693
|
+
return
|
|
694
|
+
const id = context.activeTriggerIdRef.current
|
|
695
|
+
if (id) {
|
|
696
|
+
group.move(id, event.key, true)
|
|
697
|
+
event.preventDefault()
|
|
698
|
+
}
|
|
699
|
+
})}
|
|
456
700
|
onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
|
|
701
|
+
const group = context.activeGroupRef.current
|
|
702
|
+
if (group && !group.isActive(context.openRef)) {
|
|
703
|
+
event.cancel()
|
|
704
|
+
return
|
|
705
|
+
}
|
|
457
706
|
if (!hasInteractedOutsideRef.current) {
|
|
458
707
|
// delay to let React render new components and run their autoFocus effects
|
|
459
708
|
requestAnimationFrame(() => {
|
|
@@ -468,6 +717,10 @@ export function createNonNativeMenu() {
|
|
|
468
717
|
event.cancel()
|
|
469
718
|
})}
|
|
470
719
|
onInteractOutside={composeEventHandlers(props.onInteractOutside, (event) => {
|
|
720
|
+
if (context.activeGroupRef.current?.contains(event.event?.target ?? null)) {
|
|
721
|
+
event.cancel()
|
|
722
|
+
return
|
|
723
|
+
}
|
|
471
724
|
if (event.interaction !== 'pointer' || !event.event) return
|
|
472
725
|
const originalEvent = event.event
|
|
473
726
|
const ctrlLeftClick =
|
|
@@ -603,6 +856,7 @@ export function createNonNativeMenu() {
|
|
|
603
856
|
return withStaticProperties(MenuComp, {
|
|
604
857
|
Root: MenuComp,
|
|
605
858
|
Trigger: MenuTrigger,
|
|
859
|
+
TriggerGroup: MenuTriggerGroup,
|
|
606
860
|
Portal: MenuPortal,
|
|
607
861
|
Content: MenuContent,
|
|
608
862
|
Group,
|
|
@@ -642,4 +896,5 @@ export type {
|
|
|
642
896
|
MenuSubProps,
|
|
643
897
|
MenuSubTriggerProps,
|
|
644
898
|
MenuTriggerProps,
|
|
899
|
+
MenuTriggerGroupProps,
|
|
645
900
|
}
|