@cdx-ui/primitives 0.0.1-beta.159 → 0.0.1-beta.160
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/lib/commonjs/dialog/DialogContent.js +17 -3
- package/lib/commonjs/dialog/DialogContent.js.map +1 -1
- package/lib/commonjs/dialog/DialogPopup.js +5 -1
- package/lib/commonjs/dialog/DialogPopup.js.map +1 -1
- package/lib/commonjs/index.js +54 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/menu/MenuCheckboxItem.js +42 -0
- package/lib/commonjs/menu/MenuCheckboxItem.js.map +1 -0
- package/lib/commonjs/menu/MenuItem.js +142 -0
- package/lib/commonjs/menu/MenuItem.js.map +1 -0
- package/lib/commonjs/menu/MenuOverlayRoot.js +80 -0
- package/lib/commonjs/menu/MenuOverlayRoot.js.map +1 -0
- package/lib/commonjs/menu/MenuPopup.js +78 -0
- package/lib/commonjs/menu/MenuPopup.js.map +1 -0
- package/lib/commonjs/menu/MenuRadioGroup.js +48 -0
- package/lib/commonjs/menu/MenuRadioGroup.js.map +1 -0
- package/lib/commonjs/menu/MenuRadioItem.js +32 -0
- package/lib/commonjs/menu/MenuRadioItem.js.map +1 -0
- package/lib/commonjs/menu/MenuRoot.js +139 -0
- package/lib/commonjs/menu/MenuRoot.js.map +1 -0
- package/lib/commonjs/menu/MenuTrigger.js +59 -0
- package/lib/commonjs/menu/MenuTrigger.js.map +1 -0
- package/lib/commonjs/menu/context.js +36 -0
- package/lib/commonjs/menu/context.js.map +1 -0
- package/lib/commonjs/menu/index.js +118 -0
- package/lib/commonjs/menu/index.js.map +1 -0
- package/lib/commonjs/menu/overlayContext.js +30 -0
- package/lib/commonjs/menu/overlayContext.js.map +1 -0
- package/lib/commonjs/menu/types.js +6 -0
- package/lib/commonjs/menu/types.js.map +1 -0
- package/lib/commonjs/menu/useMenuNavigation.js +87 -0
- package/lib/commonjs/menu/useMenuNavigation.js.map +1 -0
- package/lib/commonjs/menu/useMenuOverlayLifecycle.js +68 -0
- package/lib/commonjs/menu/useMenuOverlayLifecycle.js.map +1 -0
- package/lib/commonjs/menu/useMenuTrigger.js +115 -0
- package/lib/commonjs/menu/useMenuTrigger.js.map +1 -0
- package/lib/commonjs/overlay/OverlayContainer.js +2 -3
- package/lib/commonjs/overlay/OverlayContainer.js.map +1 -1
- package/lib/commonjs/overlay/OverlayStack.js +125 -0
- package/lib/commonjs/overlay/OverlayStack.js.map +1 -0
- package/lib/commonjs/overlay/constants.js +7 -1
- package/lib/commonjs/overlay/constants.js.map +1 -1
- package/lib/commonjs/overlay/getAnchoredOverlayPosition.js +188 -0
- package/lib/commonjs/overlay/getAnchoredOverlayPosition.js.map +1 -0
- package/lib/commonjs/overlay/index.js +39 -0
- package/lib/commonjs/overlay/index.js.map +1 -1
- package/lib/commonjs/overlay/useAnchoredOverlay.js +127 -0
- package/lib/commonjs/overlay/useAnchoredOverlay.js.map +1 -0
- package/lib/commonjs/overlay/useDismissOverlay.js +8 -1
- package/lib/commonjs/overlay/useDismissOverlay.js.map +1 -1
- package/lib/commonjs/overlay/useDismissOverlay.web.js +11 -3
- package/lib/commonjs/overlay/useDismissOverlay.web.js.map +1 -1
- package/lib/commonjs/overlay/useEscapeKey.js +8 -1
- package/lib/commonjs/overlay/useEscapeKey.js.map +1 -1
- package/lib/commonjs/overlay/useEscapeKey.web.js +11 -3
- package/lib/commonjs/overlay/useEscapeKey.web.js.map +1 -1
- package/lib/commonjs/overlay/useOverlayPosition.js +16 -7
- package/lib/commonjs/overlay/useOverlayPosition.js.map +1 -1
- package/lib/commonjs/portal/portal.js +86 -63
- package/lib/commonjs/portal/portal.js.map +1 -1
- package/lib/commonjs/select/SelectContent.js +23 -11
- package/lib/commonjs/select/SelectContent.js.map +1 -1
- package/lib/commonjs/select/useListboxNavigation.js +10 -11
- package/lib/commonjs/select/useListboxNavigation.js.map +1 -1
- package/lib/commonjs/tooltip/TooltipContent.js +25 -24
- package/lib/commonjs/tooltip/TooltipContent.js.map +1 -1
- package/lib/commonjs/tooltip/getTooltipPosition.js +37 -84
- package/lib/commonjs/tooltip/getTooltipPosition.js.map +1 -1
- package/lib/module/dialog/DialogContent.js +18 -4
- package/lib/module/dialog/DialogContent.js.map +1 -1
- package/lib/module/dialog/DialogPopup.js +5 -1
- package/lib/module/dialog/DialogPopup.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/menu/MenuCheckboxItem.js +38 -0
- package/lib/module/menu/MenuCheckboxItem.js.map +1 -0
- package/lib/module/menu/MenuItem.js +138 -0
- package/lib/module/menu/MenuItem.js.map +1 -0
- package/lib/module/menu/MenuOverlayRoot.js +76 -0
- package/lib/module/menu/MenuOverlayRoot.js.map +1 -0
- package/lib/module/menu/MenuPopup.js +74 -0
- package/lib/module/menu/MenuPopup.js.map +1 -0
- package/lib/module/menu/MenuRadioGroup.js +44 -0
- package/lib/module/menu/MenuRadioGroup.js.map +1 -0
- package/lib/module/menu/MenuRadioItem.js +28 -0
- package/lib/module/menu/MenuRadioItem.js.map +1 -0
- package/lib/module/menu/MenuRoot.js +135 -0
- package/lib/module/menu/MenuRoot.js.map +1 -0
- package/lib/module/menu/MenuTrigger.js +55 -0
- package/lib/module/menu/MenuTrigger.js.map +1 -0
- package/lib/module/menu/context.js +27 -0
- package/lib/module/menu/context.js.map +1 -0
- package/lib/module/menu/index.js +13 -0
- package/lib/module/menu/index.js.map +1 -0
- package/lib/module/menu/overlayContext.js +24 -0
- package/lib/module/menu/overlayContext.js.map +1 -0
- package/lib/module/menu/types.js +4 -0
- package/lib/module/menu/types.js.map +1 -0
- package/lib/module/menu/useMenuNavigation.js +84 -0
- package/lib/module/menu/useMenuNavigation.js.map +1 -0
- package/lib/module/menu/useMenuOverlayLifecycle.js +63 -0
- package/lib/module/menu/useMenuOverlayLifecycle.js.map +1 -0
- package/lib/module/menu/useMenuTrigger.js +111 -0
- package/lib/module/menu/useMenuTrigger.js.map +1 -0
- package/lib/module/overlay/OverlayContainer.js +2 -3
- package/lib/module/overlay/OverlayContainer.js.map +1 -1
- package/lib/module/overlay/OverlayStack.js +119 -0
- package/lib/module/overlay/OverlayStack.js.map +1 -0
- package/lib/module/overlay/constants.js +6 -0
- package/lib/module/overlay/constants.js.map +1 -1
- package/lib/module/overlay/getAnchoredOverlayPosition.js +183 -0
- package/lib/module/overlay/getAnchoredOverlayPosition.js.map +1 -0
- package/lib/module/overlay/index.js +4 -1
- package/lib/module/overlay/index.js.map +1 -1
- package/lib/module/overlay/useAnchoredOverlay.js +123 -0
- package/lib/module/overlay/useAnchoredOverlay.js.map +1 -0
- package/lib/module/overlay/useDismissOverlay.js +9 -1
- package/lib/module/overlay/useDismissOverlay.js.map +1 -1
- package/lib/module/overlay/useDismissOverlay.web.js +11 -3
- package/lib/module/overlay/useDismissOverlay.web.js.map +1 -1
- package/lib/module/overlay/useEscapeKey.js +9 -1
- package/lib/module/overlay/useEscapeKey.js.map +1 -1
- package/lib/module/overlay/useEscapeKey.web.js +11 -3
- package/lib/module/overlay/useEscapeKey.web.js.map +1 -1
- package/lib/module/overlay/useOverlayPosition.js +16 -7
- package/lib/module/overlay/useOverlayPosition.js.map +1 -1
- package/lib/module/portal/portal.js +86 -64
- package/lib/module/portal/portal.js.map +1 -1
- package/lib/module/select/SelectContent.js +25 -13
- package/lib/module/select/SelectContent.js.map +1 -1
- package/lib/module/select/useListboxNavigation.js +10 -11
- package/lib/module/select/useListboxNavigation.js.map +1 -1
- package/lib/module/tooltip/TooltipContent.js +30 -29
- package/lib/module/tooltip/TooltipContent.js.map +1 -1
- package/lib/module/tooltip/getTooltipPosition.js +37 -84
- package/lib/module/tooltip/getTooltipPosition.js.map +1 -1
- package/lib/typescript/dialog/DialogContent.d.ts.map +1 -1
- package/lib/typescript/dialog/DialogPopup.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/menu/MenuCheckboxItem.d.ts +6 -0
- package/lib/typescript/menu/MenuCheckboxItem.d.ts.map +1 -0
- package/lib/typescript/menu/MenuItem.d.ts +8 -0
- package/lib/typescript/menu/MenuItem.d.ts.map +1 -0
- package/lib/typescript/menu/MenuOverlayRoot.d.ts +8 -0
- package/lib/typescript/menu/MenuOverlayRoot.d.ts.map +1 -0
- package/lib/typescript/menu/MenuPopup.d.ts +8 -0
- package/lib/typescript/menu/MenuPopup.d.ts.map +1 -0
- package/lib/typescript/menu/MenuRadioGroup.d.ts +8 -0
- package/lib/typescript/menu/MenuRadioGroup.d.ts.map +1 -0
- package/lib/typescript/menu/MenuRadioItem.d.ts +6 -0
- package/lib/typescript/menu/MenuRadioItem.d.ts.map +1 -0
- package/lib/typescript/menu/MenuRoot.d.ts +8 -0
- package/lib/typescript/menu/MenuRoot.d.ts.map +1 -0
- package/lib/typescript/menu/MenuTrigger.d.ts +7 -0
- package/lib/typescript/menu/MenuTrigger.d.ts.map +1 -0
- package/lib/typescript/menu/context.d.ts +15 -0
- package/lib/typescript/menu/context.d.ts.map +1 -0
- package/lib/typescript/menu/index.d.ts +12 -0
- package/lib/typescript/menu/index.d.ts.map +1 -0
- package/lib/typescript/menu/overlayContext.d.ts +35 -0
- package/lib/typescript/menu/overlayContext.d.ts.map +1 -0
- package/lib/typescript/menu/types.d.ts +104 -0
- package/lib/typescript/menu/types.d.ts.map +1 -0
- package/lib/typescript/menu/useMenuNavigation.d.ts +16 -0
- package/lib/typescript/menu/useMenuNavigation.d.ts.map +1 -0
- package/lib/typescript/menu/useMenuOverlayLifecycle.d.ts +15 -0
- package/lib/typescript/menu/useMenuOverlayLifecycle.d.ts.map +1 -0
- package/lib/typescript/menu/useMenuTrigger.d.ts +35 -0
- package/lib/typescript/menu/useMenuTrigger.d.ts.map +1 -0
- package/lib/typescript/overlay/OverlayContainer.d.ts.map +1 -1
- package/lib/typescript/overlay/OverlayStack.d.ts +46 -0
- package/lib/typescript/overlay/OverlayStack.d.ts.map +1 -0
- package/lib/typescript/overlay/constants.d.ts +5 -0
- package/lib/typescript/overlay/constants.d.ts.map +1 -1
- package/lib/typescript/overlay/getAnchoredOverlayPosition.d.ts +45 -0
- package/lib/typescript/overlay/getAnchoredOverlayPosition.d.ts.map +1 -0
- package/lib/typescript/overlay/index.d.ts +5 -2
- package/lib/typescript/overlay/index.d.ts.map +1 -1
- package/lib/typescript/overlay/useAnchoredOverlay.d.ts +15 -0
- package/lib/typescript/overlay/useAnchoredOverlay.d.ts.map +1 -0
- package/lib/typescript/overlay/useDismissOverlay.d.ts +2 -1
- package/lib/typescript/overlay/useDismissOverlay.d.ts.map +1 -1
- package/lib/typescript/overlay/useDismissOverlay.web.d.ts +2 -1
- package/lib/typescript/overlay/useDismissOverlay.web.d.ts.map +1 -1
- package/lib/typescript/overlay/useEscapeKey.d.ts +2 -1
- package/lib/typescript/overlay/useEscapeKey.d.ts.map +1 -1
- package/lib/typescript/overlay/useEscapeKey.web.d.ts +2 -1
- package/lib/typescript/overlay/useEscapeKey.web.d.ts.map +1 -1
- package/lib/typescript/overlay/useOverlayPosition.d.ts +5 -1
- package/lib/typescript/overlay/useOverlayPosition.d.ts.map +1 -1
- package/lib/typescript/portal/portal.d.ts +3 -0
- package/lib/typescript/portal/portal.d.ts.map +1 -1
- package/lib/typescript/select/SelectContent.d.ts.map +1 -1
- package/lib/typescript/select/useListboxNavigation.d.ts.map +1 -1
- package/lib/typescript/tooltip/TooltipContent.d.ts.map +1 -1
- package/lib/typescript/tooltip/getTooltipPosition.d.ts +6 -8
- package/lib/typescript/tooltip/getTooltipPosition.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/dialog/DialogContent.tsx +16 -4
- package/src/dialog/DialogPopup.tsx +5 -1
- package/src/index.ts +23 -0
- package/src/menu/MenuCheckboxItem.tsx +51 -0
- package/src/menu/MenuItem.tsx +197 -0
- package/src/menu/MenuOverlayRoot.tsx +100 -0
- package/src/menu/MenuPopup.tsx +81 -0
- package/src/menu/MenuRadioGroup.tsx +48 -0
- package/src/menu/MenuRadioItem.tsx +32 -0
- package/src/menu/MenuRoot.tsx +190 -0
- package/src/menu/MenuTrigger.tsx +57 -0
- package/src/menu/context.tsx +38 -0
- package/src/menu/index.ts +36 -0
- package/src/menu/overlayContext.tsx +61 -0
- package/src/menu/types.ts +119 -0
- package/src/menu/useMenuNavigation.ts +102 -0
- package/src/menu/useMenuOverlayLifecycle.ts +77 -0
- package/src/menu/useMenuTrigger.ts +161 -0
- package/src/overlay/OverlayContainer.tsx +3 -1
- package/src/overlay/OverlayStack.tsx +202 -0
- package/src/overlay/constants.ts +6 -0
- package/src/overlay/getAnchoredOverlayPosition.ts +326 -0
- package/src/overlay/index.ts +29 -1
- package/src/overlay/useAnchoredOverlay.ts +153 -0
- package/src/overlay/useDismissOverlay.ts +10 -2
- package/src/overlay/useDismissOverlay.web.ts +9 -2
- package/src/overlay/useEscapeKey.ts +13 -1
- package/src/overlay/useEscapeKey.web.ts +13 -3
- package/src/overlay/useOverlayPosition.ts +35 -8
- package/src/portal/portal.tsx +109 -69
- package/src/select/SelectContent.tsx +29 -17
- package/src/select/useListboxNavigation.ts +12 -9
- package/src/tooltip/TooltipContent.tsx +32 -34
- package/src/tooltip/getTooltipPosition.ts +58 -125
package/src/overlay/index.ts
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
|
-
export { MODAL_OVERLAY_Z_INDEX } from './constants';
|
|
1
|
+
export { MODAL_OVERLAY_Z_INDEX, NOTIFICATION_OVERLAY_Z_INDEX } from './constants';
|
|
2
2
|
export { OverlayContainer } from './OverlayContainer';
|
|
3
3
|
export type { OverlayContainerProps } from './OverlayContainer';
|
|
4
|
+
export {
|
|
5
|
+
OverlayStackProvider,
|
|
6
|
+
useOverlayStackRegistration,
|
|
7
|
+
type OverlayStackCapability,
|
|
8
|
+
type OverlayStackLayer,
|
|
9
|
+
type OverlayStackMetadata,
|
|
10
|
+
type OverlayStackRegistrationOptions,
|
|
11
|
+
type OverlayStackRegistrationResult,
|
|
12
|
+
} from './OverlayStack';
|
|
4
13
|
export { useAnchorPosition, type AnchorLayout } from './useAnchorPosition';
|
|
5
14
|
export { useDismissOverlay } from './useDismissOverlay';
|
|
6
15
|
export { useEscapeKey } from './useEscapeKey';
|
|
16
|
+
export {
|
|
17
|
+
getAnchoredOverlayPosition,
|
|
18
|
+
OVERLAY_PLACEMENTS,
|
|
19
|
+
type AnchoredOverlayPosition,
|
|
20
|
+
type AnchoredOverlayPositionOptions,
|
|
21
|
+
type FloatingLayout,
|
|
22
|
+
type OverlayAlign,
|
|
23
|
+
type OverlayCollisionBehavior,
|
|
24
|
+
type OverlayPlacement,
|
|
25
|
+
type OverlaySide,
|
|
26
|
+
type OverlayWidthPolicy,
|
|
27
|
+
type ResolvedOverlayPlacement,
|
|
28
|
+
} from './getAnchoredOverlayPosition';
|
|
29
|
+
export {
|
|
30
|
+
useAnchoredOverlay,
|
|
31
|
+
type UseAnchoredOverlayOptions,
|
|
32
|
+
type UseAnchoredOverlayResult,
|
|
33
|
+
} from './useAnchoredOverlay';
|
|
7
34
|
export {
|
|
8
35
|
useOverlayPosition,
|
|
9
36
|
type OverlayPosition,
|
|
37
|
+
type UseOverlayPositionOptions,
|
|
10
38
|
OverlayInsetsProvider,
|
|
11
39
|
type EdgeInsets,
|
|
12
40
|
} from './useOverlayPosition';
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
I18nManager,
|
|
4
|
+
Platform,
|
|
5
|
+
useWindowDimensions,
|
|
6
|
+
type LayoutChangeEvent,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import {
|
|
10
|
+
getAnchoredOverlayPosition,
|
|
11
|
+
type AnchoredOverlayPosition,
|
|
12
|
+
type AnchoredOverlayPositionOptions,
|
|
13
|
+
type FloatingLayout,
|
|
14
|
+
} from './getAnchoredOverlayPosition';
|
|
15
|
+
import { useAnchorPosition, type UseAnchorPositionOptions } from './useAnchorPosition';
|
|
16
|
+
import { OverlayInsetsContext } from './useOverlayPosition';
|
|
17
|
+
|
|
18
|
+
export interface UseAnchoredOverlayOptions
|
|
19
|
+
extends AnchoredOverlayPositionOptions, UseAnchorPositionOptions {}
|
|
20
|
+
|
|
21
|
+
export interface UseAnchoredOverlayResult {
|
|
22
|
+
floatingRef: React.RefObject<any>;
|
|
23
|
+
onFloatingLayout: (event: LayoutChangeEvent) => void;
|
|
24
|
+
style: ViewStyle;
|
|
25
|
+
position: AnchoredOverlayPosition | null;
|
|
26
|
+
ready: boolean;
|
|
27
|
+
update: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const STAGING_STYLE: ViewStyle = {
|
|
31
|
+
left: -10000,
|
|
32
|
+
opacity: 0,
|
|
33
|
+
pointerEvents: 'none',
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
top: -10000,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function setLayoutIfChanged(
|
|
39
|
+
setLayout: React.Dispatch<React.SetStateAction<FloatingLayout | null>>,
|
|
40
|
+
next: FloatingLayout,
|
|
41
|
+
) {
|
|
42
|
+
setLayout((previous) => {
|
|
43
|
+
if (previous?.width === next.width && previous.height === next.height) {
|
|
44
|
+
return previous;
|
|
45
|
+
}
|
|
46
|
+
return next;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useAnchoredOverlay(
|
|
51
|
+
anchorRef: React.RefObject<any>,
|
|
52
|
+
open: boolean,
|
|
53
|
+
options: UseAnchoredOverlayOptions = {},
|
|
54
|
+
): UseAnchoredOverlayResult {
|
|
55
|
+
const { remeasureKey: externalRemeasureKey, trackWhileOpen, ...positionOptions } = options;
|
|
56
|
+
const [revision, update] = useReducer((value: number) => value + 1, 0);
|
|
57
|
+
const remeasureKey = useMemo(
|
|
58
|
+
() => ({ externalRemeasureKey, revision }),
|
|
59
|
+
[externalRemeasureKey, revision],
|
|
60
|
+
);
|
|
61
|
+
const anchorLayout = useAnchorPosition(anchorRef, open, {
|
|
62
|
+
remeasureKey,
|
|
63
|
+
trackWhileOpen,
|
|
64
|
+
});
|
|
65
|
+
const floatingRef = useRef<any>(null);
|
|
66
|
+
const [floatingLayout, setFloatingLayout] = useState<FloatingLayout | null>(null);
|
|
67
|
+
const viewport = useWindowDimensions();
|
|
68
|
+
const insets = useContext(OverlayInsetsContext);
|
|
69
|
+
|
|
70
|
+
const measureFloating = useCallback(() => {
|
|
71
|
+
const floating = floatingRef.current;
|
|
72
|
+
if (!floating) return;
|
|
73
|
+
|
|
74
|
+
if (Platform.OS === 'web' && typeof floating.getBoundingClientRect === 'function') {
|
|
75
|
+
const rect = floating.getBoundingClientRect();
|
|
76
|
+
setLayoutIfChanged(setFloatingLayout, { width: rect.width, height: rect.height });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof floating.measure === 'function') {
|
|
81
|
+
floating.measure((_x: number, _y: number, width: number, height: number) => {
|
|
82
|
+
setLayoutIfChanged(setFloatingLayout, { width, height });
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const onFloatingLayout = useCallback((event: LayoutChangeEvent) => {
|
|
88
|
+
const { width, height } = event.nativeEvent.layout;
|
|
89
|
+
setLayoutIfChanged(setFloatingLayout, { width, height });
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
const requestUpdate = useCallback(() => {
|
|
93
|
+
update();
|
|
94
|
+
measureFloating();
|
|
95
|
+
}, [measureFloating]);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!open) {
|
|
99
|
+
setFloatingLayout(null);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
measureFloating();
|
|
104
|
+
}, [measureFloating, open, revision]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!open || Platform.OS !== 'web') return;
|
|
108
|
+
|
|
109
|
+
const floating = floatingRef.current as HTMLElement | null;
|
|
110
|
+
const anchor = anchorRef.current as HTMLElement | null;
|
|
111
|
+
const resizeObserver =
|
|
112
|
+
typeof ResizeObserver === 'undefined'
|
|
113
|
+
? null
|
|
114
|
+
: new ResizeObserver(() => {
|
|
115
|
+
requestUpdate();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (floating) resizeObserver?.observe(floating);
|
|
119
|
+
if (anchor) resizeObserver?.observe(anchor);
|
|
120
|
+
|
|
121
|
+
const visualViewport = window.visualViewport;
|
|
122
|
+
visualViewport?.addEventListener('resize', requestUpdate);
|
|
123
|
+
visualViewport?.addEventListener('scroll', requestUpdate);
|
|
124
|
+
|
|
125
|
+
return () => {
|
|
126
|
+
resizeObserver?.disconnect();
|
|
127
|
+
visualViewport?.removeEventListener('resize', requestUpdate);
|
|
128
|
+
visualViewport?.removeEventListener('scroll', requestUpdate);
|
|
129
|
+
};
|
|
130
|
+
}, [anchorRef, open, requestUpdate]);
|
|
131
|
+
|
|
132
|
+
const position = useMemo(() => {
|
|
133
|
+
if (!anchorLayout || !floatingLayout) return null;
|
|
134
|
+
|
|
135
|
+
return getAnchoredOverlayPosition(anchorLayout, floatingLayout, viewport, {
|
|
136
|
+
...positionOptions,
|
|
137
|
+
insets: positionOptions.insets ?? insets,
|
|
138
|
+
isRTL: positionOptions.isRTL ?? I18nManager.isRTL,
|
|
139
|
+
});
|
|
140
|
+
}, [anchorLayout, floatingLayout, viewport, positionOptions, insets]);
|
|
141
|
+
|
|
142
|
+
const ready = position !== null;
|
|
143
|
+
const positionStyle = (Platform.OS === 'web' ? 'fixed' : 'absolute') as ViewStyle['position'];
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
floatingRef,
|
|
147
|
+
onFloatingLayout,
|
|
148
|
+
style: ready ? { position: positionStyle, ...position.style } : STAGING_STYLE,
|
|
149
|
+
position,
|
|
150
|
+
ready,
|
|
151
|
+
update: requestUpdate,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
|
+
import { useOverlayStackRegistration, type OverlayStackMetadata } from './OverlayStack';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* No-op on native — overlay dismissal is handled by backdrop press
|
|
5
6
|
* or the system back gesture / hardware button via Modal.
|
|
6
7
|
*/
|
|
7
8
|
export function useDismissOverlay(
|
|
8
|
-
|
|
9
|
+
open: boolean,
|
|
9
10
|
_contentRef: RefObject<any>,
|
|
10
11
|
_excludeRefs: RefObject<any>[],
|
|
11
12
|
_onDismiss: () => void,
|
|
12
|
-
|
|
13
|
+
options: OverlayStackMetadata = {},
|
|
14
|
+
) {
|
|
15
|
+
useOverlayStackRegistration({
|
|
16
|
+
active: open,
|
|
17
|
+
capabilities: ['outside'],
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useOverlayStackRegistration, type OverlayStackMetadata } from './OverlayStack';
|
|
3
4
|
|
|
4
5
|
function refContainsNode(ref: RefObject<unknown>, target: Node): boolean {
|
|
5
6
|
const current = ref.current as { contains?: (node: Node) => boolean } | null;
|
|
@@ -19,12 +20,18 @@ export function useDismissOverlay(
|
|
|
19
20
|
contentRef: RefObject<any>,
|
|
20
21
|
excludeRefs: RefObject<any>[],
|
|
21
22
|
onDismiss: () => void,
|
|
23
|
+
options: OverlayStackMetadata = {},
|
|
22
24
|
) {
|
|
23
25
|
const onDismissRef = useRef(onDismiss);
|
|
24
26
|
onDismissRef.current = onDismiss;
|
|
25
27
|
|
|
26
28
|
const excludeRefsRef = useRef(excludeRefs);
|
|
27
29
|
excludeRefsRef.current = excludeRefs;
|
|
30
|
+
const { isTopmost } = useOverlayStackRegistration({
|
|
31
|
+
active: open,
|
|
32
|
+
capabilities: ['outside'],
|
|
33
|
+
...options,
|
|
34
|
+
});
|
|
28
35
|
|
|
29
36
|
useEffect(() => {
|
|
30
37
|
if (!open) {
|
|
@@ -44,7 +51,7 @@ export function useDismissOverlay(
|
|
|
44
51
|
};
|
|
45
52
|
|
|
46
53
|
const dismissIfOutside = (e: Event) => {
|
|
47
|
-
if (!isInsideOverlay(e.target as Node)) {
|
|
54
|
+
if (isTopmost('outside') && !isInsideOverlay(e.target as Node)) {
|
|
48
55
|
onDismissRef.current();
|
|
49
56
|
}
|
|
50
57
|
};
|
|
@@ -55,5 +62,5 @@ export function useDismissOverlay(
|
|
|
55
62
|
document.removeEventListener('pointerdown', dismissIfOutside);
|
|
56
63
|
document.removeEventListener('focusin', dismissIfOutside);
|
|
57
64
|
};
|
|
58
|
-
}, [open, contentRef]);
|
|
65
|
+
}, [open, contentRef, isTopmost]);
|
|
59
66
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
import { useOverlayStackRegistration, type OverlayStackMetadata } from './OverlayStack';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* No-op on native — Escape key dismissal is not applicable.
|
|
3
5
|
* Native uses the system back gesture / hardware button via Modal.
|
|
4
6
|
*/
|
|
5
|
-
export function useEscapeKey(
|
|
7
|
+
export function useEscapeKey(
|
|
8
|
+
open: boolean,
|
|
9
|
+
_onEscape: () => void,
|
|
10
|
+
options: OverlayStackMetadata = {},
|
|
11
|
+
) {
|
|
12
|
+
useOverlayStackRegistration({
|
|
13
|
+
active: open,
|
|
14
|
+
capabilities: ['escape'],
|
|
15
|
+
...options,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useOverlayStackRegistration, type OverlayStackMetadata } from './OverlayStack';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Closes an overlay when the user presses the Escape key.
|
|
5
6
|
* Web-only — native platforms use Modal's `onRequestClose` for back/dismiss.
|
|
6
7
|
*/
|
|
7
|
-
export function useEscapeKey(
|
|
8
|
+
export function useEscapeKey(
|
|
9
|
+
open: boolean,
|
|
10
|
+
onEscape: () => void,
|
|
11
|
+
options: OverlayStackMetadata = {},
|
|
12
|
+
) {
|
|
8
13
|
const onEscapeRef = useRef(onEscape);
|
|
9
14
|
onEscapeRef.current = onEscape;
|
|
15
|
+
const { isTopmost } = useOverlayStackRegistration({
|
|
16
|
+
active: open,
|
|
17
|
+
capabilities: ['escape'],
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
10
20
|
|
|
11
21
|
useEffect(() => {
|
|
12
22
|
if (!open) {
|
|
@@ -14,7 +24,7 @@ export function useEscapeKey(open: boolean, onEscape: () => void) {
|
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
17
|
-
if (e.key === 'Escape') {
|
|
27
|
+
if (e.key === 'Escape' && isTopmost('escape')) {
|
|
18
28
|
onEscapeRef.current();
|
|
19
29
|
}
|
|
20
30
|
};
|
|
@@ -23,5 +33,5 @@ export function useEscapeKey(open: boolean, onEscape: () => void) {
|
|
|
23
33
|
return () => {
|
|
24
34
|
document.removeEventListener('keydown', handleKeyDown);
|
|
25
35
|
};
|
|
26
|
-
}, [open]);
|
|
36
|
+
}, [isTopmost, open]);
|
|
27
37
|
}
|
|
@@ -49,6 +49,11 @@ export interface OverlayPosition {
|
|
|
49
49
|
placement: 'top' | 'bottom';
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface UseOverlayPositionOptions {
|
|
53
|
+
/** When true (default), overlay width matches the anchor (e.g. Select). When false, width follows content (e.g. Menu). */
|
|
54
|
+
matchAnchorWidth?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
/**
|
|
53
58
|
* Computes viewport-aware overlay positioning relative to an anchor element.
|
|
54
59
|
* Automatically flips placement from bottom to top when insufficient space
|
|
@@ -57,9 +62,17 @@ export interface OverlayPosition {
|
|
|
57
62
|
*
|
|
58
63
|
* Respects safe-area insets provided via `OverlayInsetsProvider`.
|
|
59
64
|
*/
|
|
60
|
-
export function useOverlayPosition(
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
export function useOverlayPosition(
|
|
66
|
+
anchorLayout: AnchorLayout | null,
|
|
67
|
+
options?: UseOverlayPositionOptions,
|
|
68
|
+
): OverlayPosition | null {
|
|
69
|
+
const { width: viewportWidth, height: viewportHeight } = useWindowDimensions();
|
|
70
|
+
const {
|
|
71
|
+
top: insetTop,
|
|
72
|
+
bottom: insetBottom,
|
|
73
|
+
right: insetRight,
|
|
74
|
+
} = useContext(OverlayInsetsContext);
|
|
75
|
+
const matchAnchorWidth = options?.matchAnchorWidth ?? true;
|
|
63
76
|
|
|
64
77
|
return useMemo(() => {
|
|
65
78
|
if (!anchorLayout) return null;
|
|
@@ -70,13 +83,20 @@ export function useOverlayPosition(anchorLayout: AnchorLayout | null): OverlayPo
|
|
|
70
83
|
|
|
71
84
|
const preferBelow = spaceBelow >= MIN_CONTENT_HEIGHT || spaceBelow >= spaceAbove;
|
|
72
85
|
|
|
86
|
+
const horizontalStyle: ViewStyle = matchAnchorWidth
|
|
87
|
+
? { left: anchorLayout.x, width: anchorLayout.width }
|
|
88
|
+
: {
|
|
89
|
+
left: anchorLayout.x,
|
|
90
|
+
alignItems: 'flex-start',
|
|
91
|
+
maxWidth: Math.max(0, viewportWidth - anchorLayout.x - insetRight - SCREEN_EDGE_PADDING),
|
|
92
|
+
};
|
|
93
|
+
|
|
73
94
|
if (preferBelow) {
|
|
74
95
|
const maxHeight = Math.max(0, spaceBelow);
|
|
75
96
|
return {
|
|
76
97
|
style: {
|
|
98
|
+
...horizontalStyle,
|
|
77
99
|
top: anchorBottom + POPOVER_GAP,
|
|
78
|
-
left: anchorLayout.x,
|
|
79
|
-
width: anchorLayout.width,
|
|
80
100
|
maxHeight,
|
|
81
101
|
},
|
|
82
102
|
maxHeight,
|
|
@@ -87,13 +107,20 @@ export function useOverlayPosition(anchorLayout: AnchorLayout | null): OverlayPo
|
|
|
87
107
|
const maxHeight = Math.max(0, spaceAbove);
|
|
88
108
|
return {
|
|
89
109
|
style: {
|
|
110
|
+
...horizontalStyle,
|
|
90
111
|
bottom: viewportHeight - anchorLayout.y + POPOVER_GAP,
|
|
91
|
-
left: anchorLayout.x,
|
|
92
|
-
width: anchorLayout.width,
|
|
93
112
|
maxHeight,
|
|
94
113
|
},
|
|
95
114
|
maxHeight,
|
|
96
115
|
placement: 'top' as const,
|
|
97
116
|
};
|
|
98
|
-
}, [
|
|
117
|
+
}, [
|
|
118
|
+
anchorLayout,
|
|
119
|
+
viewportWidth,
|
|
120
|
+
viewportHeight,
|
|
121
|
+
insetTop,
|
|
122
|
+
insetBottom,
|
|
123
|
+
insetRight,
|
|
124
|
+
matchAnchorWidth,
|
|
125
|
+
]);
|
|
99
126
|
}
|
package/src/portal/portal.tsx
CHANGED
|
@@ -8,54 +8,55 @@ const DEFAULT_PORTAL_HOST = 'FORGE_UI_DEFAULT_HOST_NAME';
|
|
|
8
8
|
type PortalMap = Map<string, React.ReactNode>;
|
|
9
9
|
type PortalHostMap = Map<string, PortalMap>;
|
|
10
10
|
|
|
11
|
-
interface
|
|
11
|
+
interface PortalRegistry {
|
|
12
12
|
map: PortalHostMap;
|
|
13
|
-
|
|
13
|
+
hostStacks: Map<string, symbol[]>;
|
|
14
|
+
portalListeners: Set<() => void>;
|
|
15
|
+
hostListeners: Set<() => void>;
|
|
16
|
+
hasWarnedMultipleHosts: boolean;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
listeners: new Set<() => void>(),
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
let hasWarnedMultiplePortalHosts = false;
|
|
28
|
-
|
|
29
|
-
// --------------------------------------------------
|
|
30
|
-
|
|
31
|
-
function emit() {
|
|
32
|
-
for (const cb of store.listeners) cb();
|
|
19
|
+
function createPortalRegistry(): PortalRegistry {
|
|
20
|
+
return {
|
|
21
|
+
map: new Map<string, PortalMap>().set(DEFAULT_PORTAL_HOST, new Map()),
|
|
22
|
+
hostStacks: new Map(),
|
|
23
|
+
portalListeners: new Set(),
|
|
24
|
+
hostListeners: new Set(),
|
|
25
|
+
hasWarnedMultipleHosts: false,
|
|
26
|
+
};
|
|
33
27
|
}
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Standalone Portal and PortalHost instances continue to share this fallback.
|
|
31
|
+
* PortalProvider creates an isolated registry for each provider subtree.
|
|
32
|
+
*/
|
|
33
|
+
const fallbackPortalRegistry = createPortalRegistry();
|
|
34
|
+
const PortalRegistryContext = React.createContext<PortalRegistry | null>(null);
|
|
35
|
+
|
|
36
|
+
function emitPortals(registry: PortalRegistry) {
|
|
37
|
+
for (const cb of registry.portalListeners) cb();
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
function
|
|
40
|
-
|
|
40
|
+
function emitHosts(registry: PortalRegistry) {
|
|
41
|
+
for (const cb of registry.hostListeners) cb();
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function
|
|
44
|
-
|
|
44
|
+
function subscribeHosts(registry: PortalRegistry, cb: () => void) {
|
|
45
|
+
registry.hostListeners.add(cb);
|
|
45
46
|
return () => {
|
|
46
|
-
|
|
47
|
+
registry.hostListeners.delete(cb);
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function pushPortalHost(name: string, id: symbol) {
|
|
51
|
-
const stacks = new Map(
|
|
51
|
+
function pushPortalHost(registry: PortalRegistry, name: string, id: symbol) {
|
|
52
|
+
const stacks = new Map(registry.hostStacks);
|
|
52
53
|
const stack = [...(stacks.get(name) ?? [])];
|
|
53
54
|
stack.push(id);
|
|
54
55
|
stacks.set(name, stack);
|
|
55
|
-
|
|
56
|
+
registry.hostStacks = stacks;
|
|
56
57
|
|
|
57
|
-
if (__DEV__ && !
|
|
58
|
-
|
|
58
|
+
if (__DEV__ && !registry.hasWarnedMultipleHosts && stack.length > 1) {
|
|
59
|
+
registry.hasWarnedMultipleHosts = true;
|
|
59
60
|
console.warn(
|
|
60
61
|
'[Forge UI] Multiple PortalHost instances detected for the same host name. ' +
|
|
61
62
|
'Only the first mounted PortalHost renders portal content. ' +
|
|
@@ -63,73 +64,107 @@ function pushPortalHost(name: string, id: symbol) {
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
emitHosts(registry);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function popPortalHost(name: string, id: symbol) {
|
|
70
|
-
const stacks = new Map(
|
|
70
|
+
function popPortalHost(registry: PortalRegistry, name: string, id: symbol) {
|
|
71
|
+
const stacks = new Map(registry.hostStacks);
|
|
71
72
|
const stack = (stacks.get(name) ?? []).filter((entry) => entry !== id);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (stack.length === 0) {
|
|
74
|
+
stacks.delete(name);
|
|
75
|
+
} else {
|
|
76
|
+
stacks.set(name, stack);
|
|
77
|
+
}
|
|
78
|
+
registry.hostStacks = stacks;
|
|
79
|
+
emitHosts(registry);
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
function isActivePortalHost(name: string, id: symbol) {
|
|
78
|
-
const stack =
|
|
82
|
+
function isActivePortalHost(registry: PortalRegistry, name: string, id: symbol) {
|
|
83
|
+
const stack = registry.hostStacks.get(name) ?? [];
|
|
79
84
|
return stack.length > 0 && stack[0] === id;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function subscribe(cb: () => void) {
|
|
87
|
-
store.listeners.add(cb);
|
|
87
|
+
function subscribePortals(registry: PortalRegistry, cb: () => void) {
|
|
88
|
+
registry.portalListeners.add(cb);
|
|
88
89
|
return () => {
|
|
89
|
-
|
|
90
|
+
registry.portalListeners.delete(cb);
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
function updatePortal(
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
function updatePortal(
|
|
95
|
+
registry: PortalRegistry,
|
|
96
|
+
hostName: string,
|
|
97
|
+
name: string,
|
|
98
|
+
children: React.ReactNode,
|
|
99
|
+
) {
|
|
100
|
+
const next = new Map(registry.map);
|
|
101
|
+
const portal = new Map(next.get(hostName) ?? []);
|
|
96
102
|
portal.set(name, children);
|
|
97
103
|
next.set(hostName, portal);
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
registry.map = next;
|
|
105
|
+
emitPortals(registry);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
function removePortal(hostName: string, name: string) {
|
|
103
|
-
const next = new Map(
|
|
104
|
-
const portal = next.get(hostName) ??
|
|
108
|
+
function removePortal(registry: PortalRegistry, hostName: string, name: string) {
|
|
109
|
+
const next = new Map(registry.map);
|
|
110
|
+
const portal = new Map(next.get(hostName) ?? []);
|
|
105
111
|
portal.delete(name);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
if (portal.size === 0 && hostName !== DEFAULT_PORTAL_HOST) {
|
|
113
|
+
next.delete(hostName);
|
|
114
|
+
} else {
|
|
115
|
+
next.set(hostName, portal);
|
|
116
|
+
}
|
|
117
|
+
registry.map = next;
|
|
118
|
+
emitPortals(registry);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --------------------------------------------------
|
|
122
|
+
|
|
123
|
+
export function PortalProvider({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
124
|
+
const [registry] = React.useState(createPortalRegistry);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<PortalRegistryContext.Provider value={registry}>{children}</PortalRegistryContext.Provider>
|
|
128
|
+
);
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
// --------------------------------------------------
|
|
112
132
|
|
|
113
133
|
export function PortalHost({ name = DEFAULT_PORTAL_HOST }: { name?: string }) {
|
|
134
|
+
const registry = React.useContext(PortalRegistryContext) ?? fallbackPortalRegistry;
|
|
114
135
|
const hostId = React.useRef(Symbol()).current;
|
|
115
136
|
|
|
116
137
|
useEffect(() => {
|
|
117
|
-
pushPortalHost(name, hostId);
|
|
138
|
+
pushPortalHost(registry, name, hostId);
|
|
118
139
|
return () => {
|
|
119
|
-
popPortalHost(name, hostId);
|
|
140
|
+
popPortalHost(registry, name, hostId);
|
|
120
141
|
};
|
|
121
|
-
}, [name, hostId]);
|
|
122
|
-
|
|
123
|
-
useSyncExternalStore(
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
}, [registry, name, hostId]);
|
|
143
|
+
|
|
144
|
+
useSyncExternalStore(
|
|
145
|
+
(cb) => subscribeHosts(registry, cb),
|
|
146
|
+
() => registry.hostStacks,
|
|
147
|
+
() => registry.hostStacks,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const map = useSyncExternalStore(
|
|
151
|
+
(cb) => subscribePortals(registry, cb),
|
|
152
|
+
() => registry.map,
|
|
153
|
+
() => registry.map,
|
|
154
|
+
);
|
|
126
155
|
const portalMap = map.get(name) ?? new Map<string, React.ReactNode>();
|
|
127
156
|
|
|
128
|
-
if (!isActivePortalHost(name, hostId) || portalMap.size === 0) {
|
|
157
|
+
if (!isActivePortalHost(registry, name, hostId) || portalMap.size === 0) {
|
|
129
158
|
return null;
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
const content =
|
|
161
|
+
const content = (
|
|
162
|
+
<>
|
|
163
|
+
{Array.from(portalMap.entries()).map(([portalName, children]) => (
|
|
164
|
+
<React.Fragment key={portalName}>{children}</React.Fragment>
|
|
165
|
+
))}
|
|
166
|
+
</>
|
|
167
|
+
);
|
|
133
168
|
|
|
134
169
|
if (Platform.OS === 'web' && createPortalFn && typeof document !== 'undefined') {
|
|
135
170
|
return createPortalFn(content, document.body);
|
|
@@ -149,12 +184,17 @@ export function Portal({
|
|
|
149
184
|
hostName?: string;
|
|
150
185
|
children: React.ReactNode;
|
|
151
186
|
}) {
|
|
187
|
+
const registry = React.useContext(PortalRegistryContext) ?? fallbackPortalRegistry;
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
updatePortal(registry, hostName, name, children);
|
|
191
|
+
}, [registry, hostName, name, children]);
|
|
192
|
+
|
|
152
193
|
useEffect(() => {
|
|
153
|
-
updatePortal(hostName, name, children);
|
|
154
194
|
return () => {
|
|
155
|
-
removePortal(hostName, name);
|
|
195
|
+
removePortal(registry, hostName, name);
|
|
156
196
|
};
|
|
157
|
-
}, [hostName, name
|
|
197
|
+
}, [registry, hostName, name]);
|
|
158
198
|
|
|
159
199
|
return null;
|
|
160
200
|
}
|