@tamagui/popover 2.0.0-rc.9 → 2.1.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/Popover.cjs +637 -406
- package/dist/cjs/Popover.native.js +651 -436
- package/dist/cjs/Popover.native.js.map +1 -1
- package/dist/cjs/index.cjs +7 -5
- package/dist/cjs/index.native.js +7 -5
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/useFloatingContext.cjs +226 -58
- package/dist/cjs/useFloatingContext.native.js +28 -26
- package/dist/cjs/useFloatingContext.native.js.map +1 -1
- package/dist/esm/Popover.mjs +589 -376
- package/dist/esm/Popover.mjs.map +1 -1
- package/dist/esm/Popover.native.js +605 -408
- package/dist/esm/Popover.native.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -6
- package/dist/esm/useFloatingContext.mjs +200 -34
- package/dist/esm/useFloatingContext.mjs.map +1 -1
- package/dist/jsx/Popover.mjs +589 -376
- package/dist/jsx/Popover.mjs.map +1 -1
- package/dist/jsx/Popover.native.js +651 -436
- package/dist/jsx/Popover.native.js.map +1 -1
- package/dist/jsx/index.js +2 -2
- package/dist/jsx/index.js.map +1 -6
- package/dist/jsx/index.native.js +7 -5
- package/dist/jsx/useFloatingContext.mjs +200 -34
- package/dist/jsx/useFloatingContext.mjs.map +1 -1
- package/dist/jsx/useFloatingContext.native.js +28 -26
- package/dist/jsx/useFloatingContext.native.js.map +1 -1
- package/package.json +25 -26
- package/src/Popover.tsx +536 -177
- package/src/useFloatingContext.tsx +321 -43
- package/types/Popover.d.ts +126 -8
- package/types/Popover.d.ts.map +1 -1
- package/types/useFloatingContext.d.ts +14 -8
- package/types/useFloatingContext.d.ts.map +1 -1
- package/dist/cjs/Popover.js +0 -394
- package/dist/cjs/Popover.js.map +0 -6
- package/dist/cjs/index.js +0 -16
- package/dist/cjs/index.js.map +0 -6
- package/dist/cjs/useFloatingContext.js +0 -74
- package/dist/cjs/useFloatingContext.js.map +0 -6
- package/dist/esm/Popover.js +0 -415
- package/dist/esm/Popover.js.map +0 -6
- package/dist/esm/useFloatingContext.js +0 -59
- package/dist/esm/useFloatingContext.js.map +0 -6
- package/dist/jsx/Popover.js +0 -415
- package/dist/jsx/Popover.js.map +0 -6
- package/dist/jsx/useFloatingContext.js +0 -59
- package/dist/jsx/useFloatingContext.js.map +0 -6
package/src/Popover.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '@tamagui/polyfill-dev'
|
|
2
2
|
|
|
3
|
-
import type { UseHoverProps } from '@floating
|
|
3
|
+
import type { UseHoverProps } from '@tamagui/floating'
|
|
4
4
|
import {
|
|
5
5
|
Adapt,
|
|
6
6
|
AdaptParent,
|
|
@@ -12,19 +12,20 @@ import {
|
|
|
12
12
|
import { Animate } from '@tamagui/animate'
|
|
13
13
|
import { ResetPresence } from '@tamagui/animate-presence'
|
|
14
14
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
15
|
-
import { isWeb } from '@tamagui/constants'
|
|
16
|
-
import type { SizeTokens,
|
|
15
|
+
import { isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
|
|
16
|
+
import type { SizeTokens, TamaguiElement, ViewProps } from '@tamagui/core'
|
|
17
17
|
import {
|
|
18
18
|
createStyledContext,
|
|
19
|
-
styled,
|
|
20
|
-
Theme,
|
|
21
19
|
useCreateShallowSetState,
|
|
22
20
|
useEvent,
|
|
23
21
|
useGet,
|
|
24
|
-
useThemeName,
|
|
25
22
|
View,
|
|
26
23
|
} from '@tamagui/core'
|
|
27
|
-
import
|
|
24
|
+
import {
|
|
25
|
+
Dismissable,
|
|
26
|
+
DismissableBranch,
|
|
27
|
+
type DismissableProps,
|
|
28
|
+
} from '@tamagui/dismissable'
|
|
28
29
|
import { FloatingOverrideContext } from '@tamagui/floating'
|
|
29
30
|
import type { FocusScopeProps } from '@tamagui/focus-scope'
|
|
30
31
|
import { FocusScope, FocusScopeController } from '@tamagui/focus-scope'
|
|
@@ -43,14 +44,13 @@ import {
|
|
|
43
44
|
PopperProvider,
|
|
44
45
|
usePopperContext,
|
|
45
46
|
} from '@tamagui/popper'
|
|
46
|
-
import { needsPortalRepropagation, Portal
|
|
47
|
+
import { needsPortalRepropagation, Portal } from '@tamagui/portal'
|
|
47
48
|
import { RemoveScroll } from '@tamagui/remove-scroll'
|
|
48
49
|
import { ScrollView, type ScrollViewProps } from '@tamagui/scroll-view'
|
|
49
50
|
import { SheetController } from '@tamagui/sheet/controller'
|
|
50
51
|
import type { YStackProps } from '@tamagui/stacks'
|
|
51
52
|
import { YStack } from '@tamagui/stacks'
|
|
52
53
|
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
53
|
-
import { StackZIndexContext } from '@tamagui/z-index-stack'
|
|
54
54
|
import * as React from 'react'
|
|
55
55
|
import { useFloatingContext } from './useFloatingContext'
|
|
56
56
|
|
|
@@ -62,6 +62,28 @@ type ScopedPopoverProps<P> = Omit<P, 'scope'> & {
|
|
|
62
62
|
|
|
63
63
|
const needsRepropagation = needsPortalRepropagation()
|
|
64
64
|
|
|
65
|
+
const openPopovers = new Set<React.Dispatch<React.SetStateAction<boolean>>>()
|
|
66
|
+
|
|
67
|
+
export const hasOpenPopovers = () => {
|
|
68
|
+
return openPopovers.size > 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const closeOpenPopovers = () => {
|
|
72
|
+
if (openPopovers.size === 0) return false
|
|
73
|
+
openPopovers.forEach((setOpen) => setOpen(false))
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const closeLastOpenedPopover = () => {
|
|
78
|
+
if (openPopovers.size === 0) return false
|
|
79
|
+
const last = Array.from(openPopovers).pop()
|
|
80
|
+
if (last) {
|
|
81
|
+
last(false)
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
65
87
|
type PopoverVia = 'hover' | 'press'
|
|
66
88
|
|
|
67
89
|
export type PopoverProps = ScopedPopoverProps<PopperProps> & {
|
|
@@ -86,6 +108,24 @@ export type PopoverProps = ScopedPopoverProps<PopperProps> & {
|
|
|
86
108
|
* Disable focusing behavior on open
|
|
87
109
|
*/
|
|
88
110
|
disableFocus?: boolean
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Disable the dismissable layer (escape key, outside click handling).
|
|
114
|
+
* Useful for popovers that stay mounted but are visually hidden.
|
|
115
|
+
*/
|
|
116
|
+
disableDismissable?: boolean
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* z-index for the popover portal. Use this when popovers need to appear
|
|
120
|
+
* above other portaled content like dialogs or fixed headers.
|
|
121
|
+
*
|
|
122
|
+
* By default, Tamagui automatically stacks overlays - later-opened content
|
|
123
|
+
* appears above earlier content, and nested content appears above its parent.
|
|
124
|
+
* Only set this if you need to override the automatic stacking behavior.
|
|
125
|
+
*
|
|
126
|
+
* @see https://tamagui.dev/ui/z-index
|
|
127
|
+
*/
|
|
128
|
+
zIndex?: number
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
// let users override for type safety
|
|
@@ -106,7 +146,24 @@ type PopoverContextValue = {
|
|
|
106
146
|
size?: SizeTokens
|
|
107
147
|
breakpointActive?: boolean
|
|
108
148
|
keepChildrenMounted?: boolean | 'lazy'
|
|
149
|
+
disableDismissable?: boolean
|
|
150
|
+
hoverable?: boolean | object
|
|
151
|
+
anchorTo?: Rect
|
|
152
|
+
// scoped branches Set for DismissableBranch/Dismissable to share
|
|
153
|
+
branches: Set<HTMLElement>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type PopoverTriggerStateSetter = React.Dispatch<React.SetStateAction<boolean>>
|
|
157
|
+
|
|
158
|
+
type PopoverTriggerContextValue = {
|
|
159
|
+
triggerRef: React.RefObject<any>
|
|
160
|
+
hasCustomAnchor: boolean
|
|
109
161
|
anchorTo?: Rect
|
|
162
|
+
branches: Set<HTMLElement>
|
|
163
|
+
onOpenToggle(): void
|
|
164
|
+
setActiveTrigger(id: string | null): void
|
|
165
|
+
registerTrigger(id: string, setOpen: PopoverTriggerStateSetter): void
|
|
166
|
+
unregisterTrigger(id: string): void
|
|
110
167
|
}
|
|
111
168
|
|
|
112
169
|
export const PopoverContext = createStyledContext<PopoverContextValue>(
|
|
@@ -115,93 +172,310 @@ export const PopoverContext = createStyledContext<PopoverContextValue>(
|
|
|
115
172
|
'Popover__'
|
|
116
173
|
)
|
|
117
174
|
|
|
175
|
+
// zIndex flows from root Popover prop to PopoverContent portal
|
|
176
|
+
export const PopoverZIndexContext = React.createContext<number | undefined>(undefined)
|
|
177
|
+
|
|
178
|
+
// when adapted to a Sheet, tracks whether the sheet has finished sliding out.
|
|
179
|
+
// PopoverSheetController flips this true via SheetController.onAnimationComplete
|
|
180
|
+
// so PopoverContent can hold its adapted children mounted until the slide-out
|
|
181
|
+
// is done, instead of unmounting them on the popup's (passThrough) exit.
|
|
182
|
+
// defaults true (= safe to unmount) for any PopoverContent rendered outside a
|
|
183
|
+
// PopoverSheetController.
|
|
184
|
+
const PopoverAdaptHiddenContext = React.createContext(true)
|
|
185
|
+
|
|
186
|
+
export const PopoverTriggerContext = createStyledContext<PopoverTriggerContextValue>(
|
|
187
|
+
{} as PopoverTriggerContextValue,
|
|
188
|
+
'PopoverTrigger__'
|
|
189
|
+
)
|
|
190
|
+
|
|
118
191
|
export const usePopoverContext = PopoverContext.useStyledContext
|
|
192
|
+
export const usePopoverTriggerContext = PopoverTriggerContext.useStyledContext
|
|
119
193
|
|
|
120
|
-
|
|
121
|
-
*
|
|
122
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Read reactive popover open state from the popover context.
|
|
196
|
+
*/
|
|
197
|
+
export function usePopoverOpen(scope?: string): boolean {
|
|
198
|
+
return usePopoverContext(scope).open
|
|
199
|
+
}
|
|
123
200
|
|
|
124
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Hook to set up trigger registration/isolation logic.
|
|
203
|
+
* Used internally by Popover and can be used by Tooltip.
|
|
204
|
+
*/
|
|
205
|
+
export function usePopoverTriggerSetup(open: boolean) {
|
|
206
|
+
const triggerStateSettersRef = React.useRef(
|
|
207
|
+
new Map<string, PopoverTriggerStateSetter>()
|
|
208
|
+
)
|
|
209
|
+
const activeTriggerIdRef = React.useRef<string | null>(null)
|
|
125
210
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
211
|
+
const setActiveTrigger = useEvent((id: string | null) => {
|
|
212
|
+
const prevId = activeTriggerIdRef.current
|
|
213
|
+
if (prevId === id) return
|
|
214
|
+
if (prevId) {
|
|
215
|
+
triggerStateSettersRef.current.get(prevId)?.(false)
|
|
216
|
+
}
|
|
217
|
+
activeTriggerIdRef.current = id
|
|
218
|
+
if (id && open) {
|
|
219
|
+
triggerStateSettersRef.current.get(id)?.(true)
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const registerTrigger = useEvent(
|
|
224
|
+
(id: string, setOpenState: PopoverTriggerStateSetter) => {
|
|
225
|
+
triggerStateSettersRef.current.set(id, setOpenState)
|
|
226
|
+
setOpenState(activeTriggerIdRef.current === id && open)
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const unregisterTrigger = useEvent((id: string) => {
|
|
231
|
+
triggerStateSettersRef.current.delete(id)
|
|
232
|
+
if (activeTriggerIdRef.current === id) {
|
|
233
|
+
activeTriggerIdRef.current = null
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
React.useEffect(() => {
|
|
238
|
+
if (!open) {
|
|
239
|
+
setActiveTrigger(null)
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
const activeId = activeTriggerIdRef.current
|
|
243
|
+
if (activeId) {
|
|
244
|
+
triggerStateSettersRef.current.get(activeId)?.(true)
|
|
245
|
+
}
|
|
246
|
+
}, [open, setActiveTrigger])
|
|
247
|
+
|
|
248
|
+
return { setActiveTrigger, registerTrigger, unregisterTrigger }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export type PopoverContextProviderProps = {
|
|
252
|
+
scope: string
|
|
253
|
+
children: React.ReactNode
|
|
254
|
+
// PopoverContext values
|
|
255
|
+
open: boolean
|
|
256
|
+
onOpenChange(open: boolean, via?: 'hover' | 'press'): void
|
|
257
|
+
onOpenToggle(): void
|
|
258
|
+
triggerRef: React.RefObject<any>
|
|
259
|
+
id?: string
|
|
260
|
+
contentId?: string
|
|
261
|
+
hasCustomAnchor?: boolean
|
|
262
|
+
onCustomAnchorAdd?: () => void
|
|
263
|
+
onCustomAnchorRemove?: () => void
|
|
264
|
+
anchorTo?: Rect
|
|
265
|
+
// extra props for Popover (optional for Tooltip)
|
|
266
|
+
adaptScope?: string
|
|
267
|
+
breakpointActive?: boolean
|
|
268
|
+
keepChildrenMounted?: boolean | 'lazy'
|
|
269
|
+
disableDismissable?: boolean
|
|
270
|
+
hoverable?: boolean | object
|
|
271
|
+
}
|
|
131
272
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Provider that sets up both PopoverContext and PopoverTriggerContext.
|
|
275
|
+
* Use this in Tooltip or other components that need popover trigger behavior.
|
|
276
|
+
*/
|
|
277
|
+
export const PopoverContextProvider = React.memo(
|
|
278
|
+
({
|
|
279
|
+
scope,
|
|
280
|
+
children,
|
|
281
|
+
open,
|
|
282
|
+
onOpenChange,
|
|
283
|
+
onOpenToggle,
|
|
284
|
+
triggerRef,
|
|
285
|
+
id = '',
|
|
286
|
+
contentId,
|
|
287
|
+
hasCustomAnchor = false,
|
|
288
|
+
onCustomAnchorAdd = voidFn,
|
|
289
|
+
onCustomAnchorRemove = voidFn,
|
|
290
|
+
anchorTo,
|
|
291
|
+
adaptScope,
|
|
292
|
+
breakpointActive,
|
|
293
|
+
keepChildrenMounted,
|
|
294
|
+
disableDismissable,
|
|
295
|
+
hoverable,
|
|
296
|
+
}: PopoverContextProviderProps) => {
|
|
297
|
+
const [branches] = React.useState(() => new Set<HTMLElement>())
|
|
298
|
+
const { setActiveTrigger, registerTrigger, unregisterTrigger } =
|
|
299
|
+
usePopoverTriggerSetup(open)
|
|
136
300
|
|
|
137
|
-
return
|
|
301
|
+
return (
|
|
302
|
+
<PopoverContext.Provider
|
|
303
|
+
scope={scope}
|
|
304
|
+
popoverScope={scope}
|
|
305
|
+
adaptScope={adaptScope}
|
|
306
|
+
id={id}
|
|
307
|
+
contentId={contentId}
|
|
308
|
+
triggerRef={triggerRef}
|
|
309
|
+
open={open}
|
|
310
|
+
onOpenChange={onOpenChange}
|
|
311
|
+
onOpenToggle={onOpenToggle}
|
|
312
|
+
hasCustomAnchor={hasCustomAnchor}
|
|
313
|
+
onCustomAnchorAdd={onCustomAnchorAdd}
|
|
314
|
+
onCustomAnchorRemove={onCustomAnchorRemove}
|
|
315
|
+
anchorTo={anchorTo}
|
|
316
|
+
branches={branches}
|
|
317
|
+
breakpointActive={breakpointActive}
|
|
318
|
+
keepChildrenMounted={keepChildrenMounted}
|
|
319
|
+
disableDismissable={disableDismissable}
|
|
320
|
+
hoverable={hoverable}
|
|
321
|
+
>
|
|
322
|
+
<PopoverTriggerContext.Provider
|
|
323
|
+
scope={scope}
|
|
324
|
+
triggerRef={triggerRef}
|
|
325
|
+
hasCustomAnchor={hasCustomAnchor}
|
|
326
|
+
anchorTo={anchorTo}
|
|
327
|
+
branches={branches}
|
|
328
|
+
onOpenToggle={onOpenToggle}
|
|
329
|
+
setActiveTrigger={setActiveTrigger}
|
|
330
|
+
registerTrigger={registerTrigger}
|
|
331
|
+
unregisterTrigger={unregisterTrigger}
|
|
332
|
+
>
|
|
333
|
+
{children}
|
|
334
|
+
</PopoverTriggerContext.Provider>
|
|
335
|
+
</PopoverContext.Provider>
|
|
336
|
+
)
|
|
138
337
|
}
|
|
139
338
|
)
|
|
140
339
|
|
|
340
|
+
const voidFn = () => {}
|
|
341
|
+
|
|
141
342
|
/* -------------------------------------------------------------------------------------------------
|
|
142
|
-
*
|
|
343
|
+
* PopoverAnchor
|
|
143
344
|
* -----------------------------------------------------------------------------------------------*/
|
|
144
345
|
|
|
145
|
-
export type
|
|
346
|
+
export type PopoverAnchorProps = ScopedPopoverProps<YStackProps>
|
|
146
347
|
|
|
147
|
-
export const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
348
|
+
export const PopoverAnchor = React.memo(
|
|
349
|
+
React.forwardRef<TamaguiElement, PopoverAnchorProps>(
|
|
350
|
+
function PopoverAnchor(props, forwardedRef) {
|
|
351
|
+
const { scope, ...rest } = props
|
|
352
|
+
const context = usePopoverContext(scope)
|
|
353
|
+
const { onCustomAnchorAdd, onCustomAnchorRemove } = context || {}
|
|
151
354
|
|
|
152
|
-
|
|
153
|
-
|
|
355
|
+
React.useEffect(() => {
|
|
356
|
+
onCustomAnchorAdd()
|
|
357
|
+
return () => onCustomAnchorRemove()
|
|
358
|
+
}, [onCustomAnchorAdd, onCustomAnchorRemove])
|
|
154
359
|
|
|
155
|
-
|
|
156
|
-
return null
|
|
360
|
+
return <PopperAnchor scope={scope} {...rest} ref={forwardedRef} />
|
|
157
361
|
}
|
|
362
|
+
)
|
|
363
|
+
)
|
|
158
364
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// TODO not matching
|
|
163
|
-
// aria-controls={context.contentId}
|
|
164
|
-
data-state={getState(context.open)}
|
|
165
|
-
{...rest}
|
|
166
|
-
// @ts-ignore
|
|
167
|
-
ref={composedTriggerRef}
|
|
168
|
-
onPress={composeEventHandlers(props.onPress as any, context.onOpenToggle)}
|
|
169
|
-
/>
|
|
170
|
-
)
|
|
365
|
+
/* -------------------------------------------------------------------------------------------------
|
|
366
|
+
* PopoverTrigger
|
|
367
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
171
368
|
|
|
172
|
-
|
|
173
|
-
|
|
369
|
+
export type PopoverTriggerProps = ScopedPopoverProps<
|
|
370
|
+
ViewProps & {
|
|
371
|
+
/**
|
|
372
|
+
* When true, disables the built-in click-to-toggle behavior on the trigger.
|
|
373
|
+
* Useful for hoverable popovers where you want to control open/close
|
|
374
|
+
* entirely through hover or your own handlers.
|
|
375
|
+
*/
|
|
376
|
+
disablePressTrigger?: boolean
|
|
377
|
+
}
|
|
378
|
+
>
|
|
379
|
+
|
|
380
|
+
export const PopoverTrigger = React.memo(
|
|
381
|
+
React.forwardRef<TamaguiElement, PopoverTriggerProps>(
|
|
382
|
+
function PopoverTrigger(props, forwardedRef) {
|
|
383
|
+
const { scope, disablePressTrigger, ...rest } = props
|
|
384
|
+
const triggerContext = usePopoverTriggerContext(scope)
|
|
385
|
+
const triggerId = React.useId()
|
|
386
|
+
const [open, setOpen] = React.useState(false)
|
|
387
|
+
const anchorTo = triggerContext.anchorTo
|
|
388
|
+
const triggerElRef = React.useRef<TamaguiElement>(null)
|
|
389
|
+
const composedTriggerRef = useComposedRefs(forwardedRef, triggerElRef)
|
|
390
|
+
|
|
391
|
+
const { registerTrigger, unregisterTrigger } = triggerContext
|
|
392
|
+
React.useEffect(() => {
|
|
393
|
+
registerTrigger(triggerId, setOpen)
|
|
394
|
+
return () => {
|
|
395
|
+
unregisterTrigger(triggerId)
|
|
396
|
+
}
|
|
397
|
+
}, [registerTrigger, unregisterTrigger, triggerId])
|
|
398
|
+
|
|
399
|
+
if (!rest.children) {
|
|
174
400
|
return null
|
|
175
401
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
c(anchorTo?.x, anchorTo?.y, anchorTo?.width, anchorTo?.height),
|
|
184
|
-
}),
|
|
185
|
-
},
|
|
402
|
+
|
|
403
|
+
const activateSelf = () => {
|
|
404
|
+
triggerContext.setActiveTrigger(triggerId)
|
|
405
|
+
const el = triggerElRef.current
|
|
406
|
+
if (el) {
|
|
407
|
+
triggerContext.triggerRef.current = el
|
|
408
|
+
}
|
|
186
409
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
410
|
+
|
|
411
|
+
const trigger = (
|
|
412
|
+
<View
|
|
413
|
+
aria-expanded={open}
|
|
414
|
+
// TODO not matching
|
|
415
|
+
// aria-controls={context.contentId}
|
|
416
|
+
data-state={getState(open)}
|
|
417
|
+
{...rest}
|
|
418
|
+
// @ts-ignore
|
|
419
|
+
ref={composedTriggerRef}
|
|
420
|
+
onPress={composeEventHandlers(rest.onPress as any, () => {
|
|
421
|
+
if (disablePressTrigger) return
|
|
422
|
+
triggerContext.setActiveTrigger(open ? null : triggerId)
|
|
423
|
+
triggerContext.onOpenToggle()
|
|
424
|
+
})}
|
|
425
|
+
onMouseEnter={composeEventHandlers(rest.onMouseEnter as any, activateSelf)}
|
|
426
|
+
onPressIn={composeEventHandlers(rest.onPressIn as any, activateSelf)}
|
|
427
|
+
onFocus={composeEventHandlers(rest.onFocus as any, activateSelf)}
|
|
428
|
+
/>
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
const virtualRef = React.useMemo(() => {
|
|
432
|
+
if (!anchorTo) {
|
|
433
|
+
return null
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
current: {
|
|
437
|
+
getBoundingClientRect: () => (isWeb ? DOMRect.fromRect(anchorTo) : anchorTo),
|
|
438
|
+
...(!isWeb && {
|
|
439
|
+
measure: (c) =>
|
|
440
|
+
c(anchorTo?.x, anchorTo?.y, anchorTo?.width, anchorTo?.height),
|
|
441
|
+
measureInWindow: (c) =>
|
|
442
|
+
c(anchorTo?.x, anchorTo?.y, anchorTo?.width, anchorTo?.height),
|
|
443
|
+
}),
|
|
444
|
+
},
|
|
445
|
+
}
|
|
446
|
+
}, [
|
|
447
|
+
triggerContext.anchorTo,
|
|
448
|
+
anchorTo?.x,
|
|
449
|
+
anchorTo?.y,
|
|
450
|
+
anchorTo?.height,
|
|
451
|
+
anchorTo?.width,
|
|
452
|
+
])
|
|
453
|
+
|
|
454
|
+
// wrap trigger in DismissableBranch so clicking it doesn't fire pointerDownOutside
|
|
455
|
+
// which would close the popover before onPress can toggle it
|
|
456
|
+
const wrappedTrigger = isWeb ? (
|
|
457
|
+
<DismissableBranch branches={triggerContext.branches}>
|
|
458
|
+
{trigger}
|
|
459
|
+
</DismissableBranch>
|
|
460
|
+
) : (
|
|
461
|
+
trigger
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return triggerContext.hasCustomAnchor ? (
|
|
465
|
+
wrappedTrigger
|
|
466
|
+
) : (
|
|
467
|
+
<PopperAnchor {...(virtualRef && { virtualRef })} scope={scope} asChild>
|
|
468
|
+
{wrappedTrigger}
|
|
469
|
+
</PopperAnchor>
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
)
|
|
197
473
|
)
|
|
198
474
|
|
|
199
475
|
/* -------------------------------------------------------------------------------------------------
|
|
200
476
|
* PopoverContent
|
|
201
477
|
* -----------------------------------------------------------------------------------------------*/
|
|
202
478
|
|
|
203
|
-
type PopoverContentTypeElement = PopoverContentImplElement
|
|
204
|
-
|
|
205
479
|
export interface PopoverContentTypeProps extends Omit<
|
|
206
480
|
PopoverContentImplProps,
|
|
207
481
|
'disableOutsidePointerEvents'
|
|
@@ -216,38 +490,49 @@ export interface PopoverContentTypeProps extends Omit<
|
|
|
216
490
|
|
|
217
491
|
export type PopoverContentProps = PopoverContentTypeProps
|
|
218
492
|
|
|
219
|
-
const
|
|
220
|
-
name: 'Popover',
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
export const PopoverContent = PopoverContentFrame.styleable<PopoverContentProps>(
|
|
493
|
+
export const PopoverContent = PopperContentFrame.styleable<PopoverContentProps>(
|
|
224
494
|
function PopoverContent(props, forwardedRef) {
|
|
225
495
|
const {
|
|
226
496
|
trapFocus,
|
|
227
497
|
enableRemoveScroll = false,
|
|
228
|
-
zIndex,
|
|
498
|
+
zIndex: zIndexProp,
|
|
229
499
|
scope,
|
|
230
500
|
...contentImplProps
|
|
231
501
|
} = props
|
|
232
502
|
|
|
233
503
|
const context = usePopoverContext(scope)
|
|
504
|
+
const zIndexFromContext = React.useContext(PopoverZIndexContext)
|
|
505
|
+
// prop on Content takes precedence for backwards compatibility, then context from root
|
|
506
|
+
const zIndex = zIndexProp ?? zIndexFromContext
|
|
507
|
+
const open = usePopoverOpen(scope)
|
|
234
508
|
const contentRef = React.useRef<any>(null)
|
|
235
509
|
const composedRefs = useComposedRefs(forwardedRef, contentRef)
|
|
236
510
|
const isRightClickOutsideRef = React.useRef(false)
|
|
237
|
-
const [isFullyHidden, setIsFullyHidden] = React.useState(!
|
|
511
|
+
const [isFullyHidden, setIsFullyHidden] = React.useState(!open)
|
|
238
512
|
|
|
239
513
|
// Reset isFullyHidden when popover opens (useEffect avoids render-phase timing issues)
|
|
240
514
|
// there was a hard to isolate bug in tamagui.dev where moving between /ui docs pages quickly
|
|
241
515
|
// caused it to infinite loop, the setState in render (and useLayoutEffect) made it too prone
|
|
242
516
|
// to bug, useEffect maybe fine here because its hidden, ok to be slightly delayed while hidden
|
|
243
|
-
|
|
244
|
-
if (
|
|
517
|
+
useIsomorphicLayoutEffect(() => {
|
|
518
|
+
if (open && isFullyHidden) {
|
|
245
519
|
setIsFullyHidden(false)
|
|
246
520
|
}
|
|
247
|
-
}, [
|
|
248
|
-
|
|
521
|
+
}, [open, isFullyHidden])
|
|
522
|
+
|
|
523
|
+
// when adapted to a Sheet, the content is portaled into the sheet via
|
|
524
|
+
// Adapt.Contents. its mount lifecycle must follow the sheet's slide-out
|
|
525
|
+
// (PopoverAdaptHiddenContext, flipped by SheetController.onAnimationComplete),
|
|
526
|
+
// NOT the popup's own exit animation (isFullyHidden); the popup runs in
|
|
527
|
+
// passThrough mode while adapted, so isFullyHidden either fires immediately
|
|
528
|
+
// (content vanishes mid-slide) or never (content leaks), depending on driver.
|
|
529
|
+
const isAdaptFullyHidden = React.useContext(PopoverAdaptHiddenContext)
|
|
249
530
|
if (!context.keepChildrenMounted) {
|
|
250
|
-
if (
|
|
531
|
+
if (context.breakpointActive) {
|
|
532
|
+
if (!open && isAdaptFullyHidden) {
|
|
533
|
+
return null
|
|
534
|
+
}
|
|
535
|
+
} else if (isFullyHidden && !open) {
|
|
251
536
|
return null
|
|
252
537
|
}
|
|
253
538
|
}
|
|
@@ -256,24 +541,24 @@ export const PopoverContent = PopoverContentFrame.styleable<PopoverContentProps>
|
|
|
256
541
|
<PopoverPortal
|
|
257
542
|
passThrough={context.breakpointActive}
|
|
258
543
|
context={context}
|
|
544
|
+
open={open}
|
|
259
545
|
zIndex={zIndex}
|
|
260
546
|
>
|
|
261
547
|
<View
|
|
262
548
|
passThrough={context.breakpointActive}
|
|
263
|
-
pointerEvents={
|
|
264
|
-
context.open ? (contentImplProps.pointerEvents ?? 'auto') : 'none'
|
|
265
|
-
}
|
|
549
|
+
pointerEvents={open ? (contentImplProps.pointerEvents ?? 'auto') : 'none'}
|
|
266
550
|
>
|
|
267
551
|
<PopoverContentImpl
|
|
268
552
|
{...contentImplProps}
|
|
269
553
|
context={context}
|
|
554
|
+
open={open}
|
|
270
555
|
enableRemoveScroll={enableRemoveScroll}
|
|
271
556
|
ref={composedRefs}
|
|
272
557
|
setIsFullyHidden={setIsFullyHidden}
|
|
273
558
|
scope={scope}
|
|
274
559
|
// we make sure we're not trapping once it's been closed
|
|
275
560
|
// (closed !== unmounted when animating out)
|
|
276
|
-
trapFocus={trapFocus ??
|
|
561
|
+
trapFocus={trapFocus ?? open}
|
|
277
562
|
disableOutsidePointerEvents
|
|
278
563
|
onCloseAutoFocus={
|
|
279
564
|
props.onCloseAutoFocus === false
|
|
@@ -312,12 +597,14 @@ export const PopoverContent = PopoverContentFrame.styleable<PopoverContentProps>
|
|
|
312
597
|
|
|
313
598
|
const useParentContexts = (scope: string) => {
|
|
314
599
|
const context = usePopoverContext(scope)
|
|
600
|
+
const triggerContext = usePopoverTriggerContext(scope)
|
|
315
601
|
const popperContext = usePopperContext(scope)
|
|
316
602
|
const adaptContext = useAdaptContext(context.adaptScope)
|
|
317
603
|
return {
|
|
318
604
|
popperContext,
|
|
319
605
|
adaptContext,
|
|
320
606
|
context,
|
|
607
|
+
triggerContext,
|
|
321
608
|
}
|
|
322
609
|
}
|
|
323
610
|
|
|
@@ -327,14 +614,17 @@ function RepropagateParentContexts({
|
|
|
327
614
|
adaptContext,
|
|
328
615
|
children,
|
|
329
616
|
context,
|
|
617
|
+
triggerContext,
|
|
330
618
|
popperContext,
|
|
331
619
|
}: ParentContexts & {
|
|
332
620
|
children: React.ReactNode
|
|
333
621
|
}) {
|
|
334
622
|
return (
|
|
335
623
|
<PopperProvider scope={context.popoverScope} {...popperContext}>
|
|
336
|
-
<PopoverContext.Provider {...context}>
|
|
337
|
-
<
|
|
624
|
+
<PopoverContext.Provider scope={context.popoverScope} {...context}>
|
|
625
|
+
<PopoverTriggerContext.Provider scope={context.popoverScope} {...triggerContext}>
|
|
626
|
+
<ProvideAdaptContext {...adaptContext}>{children}</ProvideAdaptContext>
|
|
627
|
+
</PopoverTriggerContext.Provider>
|
|
338
628
|
</PopoverContext.Provider>
|
|
339
629
|
</PopperProvider>
|
|
340
630
|
)
|
|
@@ -347,6 +637,8 @@ const PortalAdaptSafe = ({
|
|
|
347
637
|
children?: React.ReactNode
|
|
348
638
|
context: PopoverContextValue
|
|
349
639
|
}) => {
|
|
640
|
+
'use no memo'
|
|
641
|
+
|
|
350
642
|
if (needsRepropagation) {
|
|
351
643
|
const parentContexts = useParentContexts(context.popoverScope)
|
|
352
644
|
return (
|
|
@@ -363,42 +655,40 @@ const PortalAdaptSafe = ({
|
|
|
363
655
|
|
|
364
656
|
function PopoverPortal({
|
|
365
657
|
context,
|
|
658
|
+
open,
|
|
366
659
|
zIndex,
|
|
367
660
|
passThrough,
|
|
368
661
|
children,
|
|
369
662
|
onPress,
|
|
370
663
|
}: Pick<PopoverContentProps, 'zIndex' | 'passThrough' | 'children' | 'onPress'> & {
|
|
371
664
|
context: PopoverContextValue
|
|
665
|
+
open: boolean
|
|
372
666
|
}) {
|
|
373
|
-
|
|
667
|
+
'use no memo'
|
|
374
668
|
|
|
375
669
|
let content = children
|
|
376
670
|
|
|
377
|
-
// native
|
|
671
|
+
// native without teleport
|
|
378
672
|
if (needsRepropagation) {
|
|
379
673
|
const parentContexts = useParentContexts(context.popoverScope)
|
|
380
|
-
|
|
381
674
|
content = (
|
|
382
675
|
<RepropagateParentContexts {...parentContexts}>{content}</RepropagateParentContexts>
|
|
383
676
|
)
|
|
384
677
|
}
|
|
385
678
|
|
|
386
679
|
return (
|
|
387
|
-
<Portal passThrough={passThrough} stackZIndex zIndex={zIndex
|
|
680
|
+
<Portal passThrough={passThrough} stackZIndex zIndex={zIndex}>
|
|
388
681
|
{/* forceClassName avoids forced re-mount renders for some reason... see the HeadMenu as you change tints a few times */}
|
|
389
682
|
{/* without this you'll see the site menu re-rendering. It must be something in wrapping children in Theme */}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
)}
|
|
683
|
+
{!!open && !context.breakpointActive && !context.hoverable && (
|
|
684
|
+
<YStack
|
|
685
|
+
fullscreen
|
|
686
|
+
onPress={composeEventHandlers(onPress as any, context.onOpenToggle)}
|
|
687
|
+
/>
|
|
688
|
+
)}
|
|
397
689
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
</StackZIndexContext>
|
|
401
|
-
</Theme>
|
|
690
|
+
{/* i removed a hardcoded StackZIndex because Portal has it internally now with useStackedZIndex + ZIndexHardcoded */}
|
|
691
|
+
{content}
|
|
402
692
|
</Portal>
|
|
403
693
|
)
|
|
404
694
|
}
|
|
@@ -440,10 +730,20 @@ export type PopoverContentImplProps = PopperContentProps &
|
|
|
440
730
|
enableRemoveScroll?: boolean
|
|
441
731
|
|
|
442
732
|
freezeContentsWhenHidden?: boolean
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Performance - if never going to use feature can permanently disable
|
|
736
|
+
*/
|
|
737
|
+
alwaysDisable?: {
|
|
738
|
+
focus?: boolean
|
|
739
|
+
'remove-scroll'?: boolean
|
|
740
|
+
dismiss?: boolean
|
|
741
|
+
}
|
|
443
742
|
}
|
|
444
743
|
|
|
445
744
|
type PopoverContentImplInteralProps = PopoverContentImplProps & {
|
|
446
745
|
context: PopoverContextValue
|
|
746
|
+
open: boolean
|
|
447
747
|
setIsFullyHidden: React.Dispatch<React.SetStateAction<boolean>>
|
|
448
748
|
}
|
|
449
749
|
|
|
@@ -467,11 +767,14 @@ const PopoverContentImpl = React.forwardRef<
|
|
|
467
767
|
freezeContentsWhenHidden,
|
|
468
768
|
setIsFullyHidden,
|
|
469
769
|
lazyMount,
|
|
770
|
+
forceUnmount,
|
|
470
771
|
context,
|
|
772
|
+
open,
|
|
773
|
+
alwaysDisable,
|
|
471
774
|
...contentProps
|
|
472
775
|
} = props
|
|
473
776
|
|
|
474
|
-
const {
|
|
777
|
+
const { keepChildrenMounted, disableDismissable } = context
|
|
475
778
|
|
|
476
779
|
const handleExitComplete = React.useCallback(() => {
|
|
477
780
|
setIsFullyHidden?.(true)
|
|
@@ -481,14 +784,16 @@ const PopoverContentImpl = React.forwardRef<
|
|
|
481
784
|
<ResetPresence disable={context.breakpointActive}>{children}</ResetPresence>
|
|
482
785
|
)
|
|
483
786
|
|
|
787
|
+
const handleDismiss = React.useCallback(() => {
|
|
788
|
+
context.onOpenChange(false, 'press')
|
|
789
|
+
}, [context])
|
|
790
|
+
|
|
484
791
|
// i want to avoid reparenting but react-remove-scroll makes it hard
|
|
485
792
|
// TODO its removed now so we can probable do it now
|
|
486
793
|
if (!context.breakpointActive) {
|
|
487
794
|
if (process.env.TAMAGUI_TARGET !== 'native') {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
enabled={context.breakpointActive ? false : enableRemoveScroll ? open : false}
|
|
491
|
-
>
|
|
795
|
+
if (!alwaysDisable || !alwaysDisable.focus) {
|
|
796
|
+
contents = (
|
|
492
797
|
<FocusScope
|
|
493
798
|
loop={trapFocus !== false}
|
|
494
799
|
enabled={context.breakpointActive ? false : disableFocusScope ? false : open}
|
|
@@ -498,24 +803,36 @@ const PopoverContentImpl = React.forwardRef<
|
|
|
498
803
|
>
|
|
499
804
|
<div style={dspContentsStyle}>{contents}</div>
|
|
500
805
|
</FocusScope>
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
}
|
|
806
|
+
)
|
|
807
|
+
}
|
|
505
808
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
// onDismiss={handleDismiss}
|
|
516
|
-
// >
|
|
809
|
+
if (!alwaysDisable || !alwaysDisable['remove-scroll']) {
|
|
810
|
+
contents = (
|
|
811
|
+
<RemoveScroll
|
|
812
|
+
enabled={context.breakpointActive ? false : enableRemoveScroll ? open : false}
|
|
813
|
+
>
|
|
814
|
+
{contents}
|
|
815
|
+
</RemoveScroll>
|
|
816
|
+
)
|
|
817
|
+
}
|
|
517
818
|
|
|
518
|
-
|
|
819
|
+
if (!alwaysDisable || !alwaysDisable.dismiss) {
|
|
820
|
+
contents = (
|
|
821
|
+
<Dismissable
|
|
822
|
+
branches={context.branches}
|
|
823
|
+
forceUnmount={disableDismissable || (forceUnmount ?? !open)}
|
|
824
|
+
onEscapeKeyDown={onEscapeKeyDown}
|
|
825
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
826
|
+
onFocusOutside={onFocusOutside}
|
|
827
|
+
onInteractOutside={onInteractOutside}
|
|
828
|
+
onDismiss={handleDismiss}
|
|
829
|
+
>
|
|
830
|
+
{contents}
|
|
831
|
+
</Dismissable>
|
|
832
|
+
)
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
519
836
|
|
|
520
837
|
return (
|
|
521
838
|
<Animate
|
|
@@ -533,6 +850,11 @@ const PopoverContentImpl = React.forwardRef<
|
|
|
533
850
|
id={context.contentId}
|
|
534
851
|
ref={forwardedRef}
|
|
535
852
|
passThrough={context.breakpointActive}
|
|
853
|
+
{...(!contentProps.unstyled && {
|
|
854
|
+
size: '$true',
|
|
855
|
+
backgroundColor: '$background',
|
|
856
|
+
alignItems: 'center',
|
|
857
|
+
})}
|
|
536
858
|
{...contentProps}
|
|
537
859
|
>
|
|
538
860
|
<PortalAdaptSafe context={context}>{contents}</PortalAdaptSafe>
|
|
@@ -683,6 +1005,8 @@ const PopoverInner = React.forwardRef<
|
|
|
683
1005
|
keepChildrenMounted: keepChildrenMountedProp,
|
|
684
1006
|
hoverable,
|
|
685
1007
|
disableFocus,
|
|
1008
|
+
disableDismissable,
|
|
1009
|
+
zIndex,
|
|
686
1010
|
id,
|
|
687
1011
|
adaptScope,
|
|
688
1012
|
...restProps
|
|
@@ -691,11 +1015,13 @@ const PopoverInner = React.forwardRef<
|
|
|
691
1015
|
const triggerRef = React.useRef<TamaguiElement>(null)
|
|
692
1016
|
const [hasCustomAnchor, setHasCustomAnchor] = React.useState(false)
|
|
693
1017
|
const viaRef = React.useRef<PopoverVia>(undefined)
|
|
1018
|
+
|
|
694
1019
|
const [keepChildrenMounted] = useControllableState({
|
|
695
1020
|
prop: keepChildrenMountedProp,
|
|
696
1021
|
defaultProp: false,
|
|
697
1022
|
transition: keepChildrenMountedProp === 'lazy',
|
|
698
1023
|
})
|
|
1024
|
+
|
|
699
1025
|
const [open, setOpen] = useControllableState({
|
|
700
1026
|
prop: openProp,
|
|
701
1027
|
defaultProp: defaultOpen || false,
|
|
@@ -704,6 +1030,15 @@ const PopoverInner = React.forwardRef<
|
|
|
704
1030
|
},
|
|
705
1031
|
})
|
|
706
1032
|
|
|
1033
|
+
// track open popovers for closeOpenPopovers()
|
|
1034
|
+
React.useEffect(() => {
|
|
1035
|
+
if (!open) return
|
|
1036
|
+
openPopovers.add(setOpen)
|
|
1037
|
+
return () => {
|
|
1038
|
+
openPopovers.delete(setOpen)
|
|
1039
|
+
}
|
|
1040
|
+
}, [open, setOpen])
|
|
1041
|
+
|
|
707
1042
|
const handleOpenChange = useEvent((val, via) => {
|
|
708
1043
|
viaRef.current = via
|
|
709
1044
|
setOpen(val)
|
|
@@ -717,7 +1052,7 @@ const PopoverInner = React.forwardRef<
|
|
|
717
1052
|
disable: isAdapted,
|
|
718
1053
|
hoverable,
|
|
719
1054
|
disableFocus: disableFocus,
|
|
720
|
-
})
|
|
1055
|
+
})
|
|
721
1056
|
|
|
722
1057
|
const [anchorTo, setAnchorToRaw] = React.useState<Rect>()
|
|
723
1058
|
|
|
@@ -733,56 +1068,46 @@ const PopoverInner = React.forwardRef<
|
|
|
733
1068
|
setOpen,
|
|
734
1069
|
}))
|
|
735
1070
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (open && isAdapted) {
|
|
748
|
-
return
|
|
749
|
-
}
|
|
750
|
-
setOpen(!open)
|
|
751
|
-
}),
|
|
752
|
-
hasCustomAnchor,
|
|
753
|
-
anchorTo,
|
|
754
|
-
onCustomAnchorAdd: React.useCallback(() => setHasCustomAnchor(true), []),
|
|
755
|
-
onCustomAnchorRemove: React.useCallback(() => setHasCustomAnchor(false), []),
|
|
756
|
-
keepChildrenMounted,
|
|
757
|
-
} satisfies PopoverContextValue
|
|
758
|
-
|
|
759
|
-
// // debug if changing too often
|
|
760
|
-
// if (process.env.NODE_ENV === 'development') {
|
|
761
|
-
// Object.keys(popoverContext).forEach((key) => {
|
|
762
|
-
// React.useEffect(
|
|
763
|
-
// () => console.log(`changed`, key, popoverContext[key]),
|
|
764
|
-
// [popoverContext[key]]
|
|
765
|
-
// )
|
|
766
|
-
// })
|
|
767
|
-
// }
|
|
768
|
-
|
|
769
|
-
const memoizedChildren = React.useMemo(() => {
|
|
770
|
-
return (
|
|
771
|
-
<PopoverContext.Provider scope={scope} {...popoverContext}>
|
|
772
|
-
<PopoverSheetController context={popoverContext} onOpenChange={setOpen}>
|
|
773
|
-
{children}
|
|
774
|
-
</PopoverSheetController>
|
|
775
|
-
</PopoverContext.Provider>
|
|
776
|
-
)
|
|
777
|
-
}, [scope, setOpen, children, ...Object.values(popoverContext)])
|
|
1071
|
+
const contentId = React.useId()
|
|
1072
|
+
|
|
1073
|
+
const onOpenToggle = useEvent(() => {
|
|
1074
|
+
if (open && isAdapted) {
|
|
1075
|
+
return
|
|
1076
|
+
}
|
|
1077
|
+
setOpen(!open)
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
const onCustomAnchorAdd = React.useCallback(() => setHasCustomAnchor(true), [])
|
|
1081
|
+
const onCustomAnchorRemove = React.useCallback(() => setHasCustomAnchor(false), [])
|
|
778
1082
|
|
|
779
1083
|
const contents = (
|
|
780
1084
|
<Popper open={open} passThrough={isAdapted} scope={scope} stayInFrame {...restProps}>
|
|
781
|
-
|
|
1085
|
+
<PopoverContextProvider
|
|
1086
|
+
scope={scope}
|
|
1087
|
+
open={open}
|
|
1088
|
+
onOpenChange={handleOpenChange}
|
|
1089
|
+
onOpenToggle={onOpenToggle}
|
|
1090
|
+
triggerRef={triggerRef}
|
|
1091
|
+
id={id}
|
|
1092
|
+
contentId={contentId}
|
|
1093
|
+
hasCustomAnchor={hasCustomAnchor}
|
|
1094
|
+
onCustomAnchorAdd={onCustomAnchorAdd}
|
|
1095
|
+
onCustomAnchorRemove={onCustomAnchorRemove}
|
|
1096
|
+
anchorTo={anchorTo}
|
|
1097
|
+
adaptScope={adaptScope}
|
|
1098
|
+
breakpointActive={isAdapted}
|
|
1099
|
+
keepChildrenMounted={keepChildrenMounted}
|
|
1100
|
+
disableDismissable={disableDismissable}
|
|
1101
|
+
hoverable={hoverable}
|
|
1102
|
+
>
|
|
1103
|
+
<PopoverSheetController onOpenChange={setOpen} open={open} scope={scope}>
|
|
1104
|
+
{children}
|
|
1105
|
+
</PopoverSheetController>
|
|
1106
|
+
</PopoverContextProvider>
|
|
782
1107
|
</Popper>
|
|
783
1108
|
)
|
|
784
1109
|
|
|
785
|
-
|
|
1110
|
+
let result = (
|
|
786
1111
|
<>
|
|
787
1112
|
{isWeb ? (
|
|
788
1113
|
<FloatingOverrideContext.Provider value={floatingContext}>
|
|
@@ -793,6 +1118,16 @@ const PopoverInner = React.forwardRef<
|
|
|
793
1118
|
)}
|
|
794
1119
|
</>
|
|
795
1120
|
)
|
|
1121
|
+
|
|
1122
|
+
if (zIndex !== undefined) {
|
|
1123
|
+
return (
|
|
1124
|
+
<PopoverZIndexContext.Provider value={zIndex}>
|
|
1125
|
+
{result}
|
|
1126
|
+
</PopoverZIndexContext.Provider>
|
|
1127
|
+
)
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return result
|
|
796
1131
|
})
|
|
797
1132
|
|
|
798
1133
|
/* -----------------------------------------------------------------------------------------------*/
|
|
@@ -802,33 +1137,57 @@ function getState(open: boolean) {
|
|
|
802
1137
|
}
|
|
803
1138
|
|
|
804
1139
|
const PopoverSheetController = ({
|
|
805
|
-
|
|
1140
|
+
open,
|
|
1141
|
+
scope,
|
|
806
1142
|
...props
|
|
807
1143
|
}: {
|
|
808
|
-
|
|
1144
|
+
open: boolean
|
|
1145
|
+
scope?: string
|
|
809
1146
|
children: React.ReactNode
|
|
810
1147
|
onOpenChange: React.Dispatch<React.SetStateAction<boolean>>
|
|
811
1148
|
}) => {
|
|
812
|
-
const
|
|
813
|
-
const
|
|
1149
|
+
const context = usePopoverContext(scope)
|
|
1150
|
+
const showSheet = useShowPopoverSheet(context, open)
|
|
1151
|
+
const breakpointActive = context?.breakpointActive
|
|
814
1152
|
const getShowSheet = useGet(showSheet)
|
|
815
1153
|
|
|
1154
|
+
// tracks whether the adapted Sheet has finished its slide-out animation.
|
|
1155
|
+
// starts true (= safe to unmount) when closed; flips false the moment the
|
|
1156
|
+
// popover opens; flips back to true when the sheet signals onAnimationComplete
|
|
1157
|
+
// with open=false (slide-out finished). mirrors DialogSheetController.
|
|
1158
|
+
const [isAdaptFullyHidden, setIsAdaptFullyHidden] = React.useState(!open)
|
|
1159
|
+
if (open && isAdaptFullyHidden) {
|
|
1160
|
+
setIsAdaptFullyHidden(false)
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const handleSheetAnimationComplete = React.useCallback(
|
|
1164
|
+
({ open: isOpen }: { open: boolean }) => {
|
|
1165
|
+
if (!isOpen) {
|
|
1166
|
+
setIsAdaptFullyHidden(true)
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
[]
|
|
1170
|
+
)
|
|
1171
|
+
|
|
816
1172
|
return (
|
|
817
1173
|
<SheetController
|
|
818
|
-
onOpenChange={(val) => {
|
|
1174
|
+
onOpenChange={(val: boolean) => {
|
|
819
1175
|
if (getShowSheet()) {
|
|
820
1176
|
props.onOpenChange?.(val)
|
|
821
1177
|
}
|
|
822
1178
|
}}
|
|
823
|
-
|
|
1179
|
+
onAnimationComplete={handleSheetAnimationComplete}
|
|
1180
|
+
open={open}
|
|
824
1181
|
hidden={!breakpointActive}
|
|
825
1182
|
>
|
|
826
|
-
{
|
|
1183
|
+
<PopoverAdaptHiddenContext.Provider value={isAdaptFullyHidden}>
|
|
1184
|
+
{props.children}
|
|
1185
|
+
</PopoverAdaptHiddenContext.Provider>
|
|
827
1186
|
</SheetController>
|
|
828
1187
|
)
|
|
829
1188
|
}
|
|
830
1189
|
|
|
831
|
-
const useShowPopoverSheet = (context: PopoverContextValue) => {
|
|
1190
|
+
const useShowPopoverSheet = (context: PopoverContextValue, open: boolean) => {
|
|
832
1191
|
const isAdapted = useAdaptIsActive(context.adaptScope)
|
|
833
|
-
return
|
|
1192
|
+
return open === false ? false : isAdapted
|
|
834
1193
|
}
|