@react-aria/overlays 3.27.3 → 3.29.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/Overlay.main.js.map +1 -1
- package/dist/Overlay.module.js.map +1 -1
- package/dist/PortalProvider.main.js.map +1 -1
- package/dist/PortalProvider.module.js.map +1 -1
- package/dist/ariaHideOutside.main.js +34 -16
- package/dist/ariaHideOutside.main.js.map +1 -1
- package/dist/ariaHideOutside.mjs +34 -16
- package/dist/ariaHideOutside.module.js +34 -16
- package/dist/ariaHideOutside.module.js.map +1 -1
- package/dist/calculatePosition.main.js +15 -2
- package/dist/calculatePosition.main.js.map +1 -1
- package/dist/calculatePosition.mjs +15 -2
- package/dist/calculatePosition.module.js +15 -2
- package/dist/calculatePosition.module.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useModalOverlay.main.js +3 -1
- package/dist/useModalOverlay.main.js.map +1 -1
- package/dist/useModalOverlay.mjs +3 -1
- package/dist/useModalOverlay.module.js +3 -1
- package/dist/useModalOverlay.module.js.map +1 -1
- package/dist/useOverlayPosition.main.js +15 -4
- package/dist/useOverlayPosition.main.js.map +1 -1
- package/dist/useOverlayPosition.mjs +15 -4
- package/dist/useOverlayPosition.module.js +15 -4
- package/dist/useOverlayPosition.module.js.map +1 -1
- package/dist/usePopover.main.js +9 -4
- package/dist/usePopover.main.js.map +1 -1
- package/dist/usePopover.mjs +10 -5
- package/dist/usePopover.module.js +10 -5
- package/dist/usePopover.module.js.map +1 -1
- package/package.json +12 -12
- package/src/Overlay.tsx +2 -1
- package/src/PortalProvider.tsx +1 -1
- package/src/ariaHideOutside.ts +38 -13
- package/src/calculatePosition.ts +22 -2
- package/src/useModalOverlay.ts +1 -1
- package/src/useOverlayPosition.ts +25 -3
- package/src/usePopover.ts +12 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"
|
|
1
|
+
{"mappings":";;;;;;;AAAA;;;;;;;;;;CAUC;;;;;;AAyEM,SAAS,0CAAW,KAAuB,EAAE,KAA0B;IAC5E,IAAI,cACF,UAAU,cACV,UAAU,YACV,QAAQ,cACR,UAAU,6BACV,yBAAyB,gCACzB,4BAA4B,EAC5B,GAAG,YACJ,GAAG;IAEJ,IAAI,YAAY,UAAU,CAAC,UAAU,KAAK;IAE1C,IAAI,gBAAC,YAAY,iBAAE,aAAa,EAAC,GAAG,CAAA,GAAA,yCAAS,EAC3C;QACE,QAAQ,MAAM,MAAM;QACpB,SAAS,MAAM,KAAK;QACpB,mBAAmB;QACnB,eAAe,CAAC,cAAc;mCAC9B;sCACA;IACF,GACA,qBAAA,sBAAA,WAAY;IAGd,IAAI,EAAC,cAAc,aAAa,cAAE,UAAU,aAAE,SAAS,EAAE,oBAAoB,MAAM,EAAC,GAAG,CAAA,GAAA,yCAAiB,EAAE;QACxG,GAAG,UAAU;QACb,WAAW;QACX,YAAY;QACZ,QAAQ,MAAM,MAAM;QACpB,SAAS,cAAc,CAAC,YAAY,MAAM,KAAK,GAAG;IACpD;IAEA,CAAA,GAAA,yCAAe,EAAE;QACf,YAAY,cAAc,CAAC,MAAM,MAAM;IACzC;IAEA,CAAA,GAAA,gBAAQ,EAAE;QACR,IAAI,MAAM,MAAM,IAAI,WAAW,OAAO,EAAE;gBAEjB,mBAEK;YAH1B,IAAI,YACF,OAAO,CAAA,GAAA,yCAAU,EAAE,CAAA,oBAAA,qBAAA,+BAAA,SAAU,OAAO,cAAjB,+BAAA,oBAAqB,WAAW,OAAO;iBAE1D,OAAO,CAAA,GAAA,yCAAc,EAAE;gBAAC,CAAA,qBAAA,qBAAA,+BAAA,SAAU,OAAO,cAAjB,gCAAA,qBAAqB,WAAW,OAAO;aAAC,EAAE;gBAAC,gBAAgB;YAAI;QAE3F;IACF,GAAG;QAAC;QAAY,MAAM,MAAM;QAAE;QAAY;KAAS;IAEnD,OAAO;QACL,cAAc,CAAA,GAAA,iBAAS,EAAE,cAAc;oBACvC;uBACA;mBACA;QACA,oBAAoB;IACtB;AACF","sources":["packages/@react-aria/overlays/src/usePopover.ts"],"sourcesContent":["/*\n * Copyright 2022 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {ariaHideOutside, keepVisible} from './ariaHideOutside';\nimport {AriaPositionProps, useOverlayPosition} from './useOverlayPosition';\nimport {DOMAttributes, RefObject} from '@react-types/shared';\nimport {mergeProps} from '@react-aria/utils';\nimport {OverlayTriggerState} from '@react-stately/overlays';\nimport {PlacementAxis} from '@react-types/overlays';\nimport {useEffect} from 'react';\nimport {useOverlay} from './useOverlay';\nimport {usePreventScroll} from './usePreventScroll';\n\nexport interface AriaPopoverProps extends Omit<AriaPositionProps, 'isOpen' | 'onClose' | 'targetRef' | 'overlayRef'> {\n /**\n * The ref for the element which the popover positions itself with respect to.\n */\n triggerRef: RefObject<Element | null>,\n /**\n * The ref for the popover element.\n */\n popoverRef: RefObject<Element | null>,\n /** A ref for the popover arrow element. */\n arrowRef?: RefObject<Element | null>,\n /**\n * An optional ref for a group of popovers, e.g. submenus.\n * When provided, this element is used to detect outside interactions\n * and hiding elements from assistive technologies instead of the popoverRef.\n */\n groupRef?: RefObject<Element | null>,\n /**\n * Whether the popover is non-modal, i.e. elements outside the popover may be\n * interacted with by assistive technologies.\n *\n * Most popovers should not use this option as it may negatively impact the screen\n * reader experience. Only use with components such as combobox, which are designed\n * to handle this situation carefully.\n */\n isNonModal?: boolean,\n /**\n * Whether pressing the escape key to close the popover should be disabled.\n *\n * Most popovers should not use this option. When set to true, an alternative\n * way to close the popover with a keyboard must be provided.\n *\n * @default false\n */\n isKeyboardDismissDisabled?: boolean,\n /**\n * When user interacts with the argument element outside of the popover ref,\n * return true if onClose should be called. This gives you a chance to filter\n * out interaction with elements that should not dismiss the popover.\n * By default, onClose will always be called on interaction outside the popover ref.\n */\n shouldCloseOnInteractOutside?: (element: Element) => boolean\n}\n\nexport interface PopoverAria {\n /** Props for the popover element. */\n popoverProps: DOMAttributes,\n /** Props for the popover tip arrow if any. */\n arrowProps: DOMAttributes,\n /** Props to apply to the underlay element, if any. */\n underlayProps: DOMAttributes,\n /** Placement of the popover with respect to the trigger. */\n placement: PlacementAxis | null,\n /** The origin of the target in the overlay's coordinate system. Useful for animations. */\n triggerAnchorPoint: {x: number, y: number} | null\n}\n\n/**\n * Provides the behavior and accessibility implementation for a popover component.\n * A popover is an overlay element positioned relative to a trigger.\n */\nexport function usePopover(props: AriaPopoverProps, state: OverlayTriggerState): PopoverAria {\n let {\n triggerRef,\n popoverRef,\n groupRef,\n isNonModal,\n isKeyboardDismissDisabled,\n shouldCloseOnInteractOutside,\n ...otherProps\n } = props;\n\n let isSubmenu = otherProps['trigger'] === 'SubmenuTrigger';\n\n let {overlayProps, underlayProps} = useOverlay(\n {\n isOpen: state.isOpen,\n onClose: state.close,\n shouldCloseOnBlur: true,\n isDismissable: !isNonModal || isSubmenu,\n isKeyboardDismissDisabled,\n shouldCloseOnInteractOutside\n },\n groupRef ?? popoverRef\n );\n\n let {overlayProps: positionProps, arrowProps, placement, triggerAnchorPoint: origin} = useOverlayPosition({\n ...otherProps,\n targetRef: triggerRef,\n overlayRef: popoverRef,\n isOpen: state.isOpen,\n onClose: isNonModal && !isSubmenu ? state.close : null\n });\n\n usePreventScroll({\n isDisabled: isNonModal || !state.isOpen\n });\n\n useEffect(() => {\n if (state.isOpen && popoverRef.current) {\n if (isNonModal) {\n return keepVisible(groupRef?.current ?? popoverRef.current);\n } else {\n return ariaHideOutside([groupRef?.current ?? popoverRef.current], {shouldUseInert: true});\n }\n }\n }, [isNonModal, state.isOpen, popoverRef, groupRef]);\n\n return {\n popoverProps: mergeProps(overlayProps, positionProps),\n arrowProps,\n underlayProps,\n placement,\n triggerAnchorPoint: origin\n };\n}\n"],"names":[],"version":3,"file":"usePopover.module.js.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/overlays",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.29.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -26,16 +26,16 @@
|
|
|
26
26
|
"url": "https://github.com/adobe/react-spectrum"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@react-aria/focus": "^3.
|
|
30
|
-
"@react-aria/i18n": "^3.12.
|
|
31
|
-
"@react-aria/interactions": "^3.25.
|
|
32
|
-
"@react-aria/ssr": "^3.9.
|
|
33
|
-
"@react-aria/utils": "^3.
|
|
34
|
-
"@react-aria/visually-hidden": "^3.8.
|
|
35
|
-
"@react-stately/overlays": "^3.6.
|
|
36
|
-
"@react-types/button": "^3.
|
|
37
|
-
"@react-types/overlays": "^3.
|
|
38
|
-
"@react-types/shared": "^3.
|
|
29
|
+
"@react-aria/focus": "^3.21.1",
|
|
30
|
+
"@react-aria/i18n": "^3.12.12",
|
|
31
|
+
"@react-aria/interactions": "^3.25.5",
|
|
32
|
+
"@react-aria/ssr": "^3.9.10",
|
|
33
|
+
"@react-aria/utils": "^3.30.1",
|
|
34
|
+
"@react-aria/visually-hidden": "^3.8.27",
|
|
35
|
+
"@react-stately/overlays": "^3.6.19",
|
|
36
|
+
"@react-types/button": "^3.14.0",
|
|
37
|
+
"@react-types/overlays": "^3.9.1",
|
|
38
|
+
"@react-types/shared": "^3.32.0",
|
|
39
39
|
"@swc/helpers": "^0.5.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "2c58ed3ddd9be9100af9b1d0cfd137fcdc95ba2d"
|
|
49
49
|
}
|
package/src/Overlay.tsx
CHANGED
|
@@ -43,7 +43,8 @@ export interface OverlayProps {
|
|
|
43
43
|
isExiting?: boolean
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export const OverlayContext
|
|
46
|
+
export const OverlayContext: React.Context<{contain: boolean, setContain: React.Dispatch<React.SetStateAction<boolean>>} | null> =
|
|
47
|
+
React.createContext<{contain: boolean, setContain: React.Dispatch<React.SetStateAction<boolean>>} | null>(null);
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* A container which renders an overlay such as a popover or modal in a portal,
|
package/src/PortalProvider.tsx
CHANGED
|
@@ -21,7 +21,7 @@ export interface PortalProviderProps {
|
|
|
21
21
|
|
|
22
22
|
export interface PortalProviderContextValue extends Omit<PortalProviderProps, 'children'>{};
|
|
23
23
|
|
|
24
|
-
export const PortalContext = createContext<PortalProviderContextValue>({});
|
|
24
|
+
export const PortalContext: React.Context<PortalProviderContextValue> = createContext<PortalProviderContextValue>({});
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Sets the portal container for all overlay elements rendered by its children.
|
package/src/ariaHideOutside.ts
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {getOwnerWindow} from '@react-aria/utils';
|
|
14
|
+
const supportsInert = typeof HTMLElement !== 'undefined' && 'inert' in HTMLElement.prototype;
|
|
15
|
+
|
|
16
|
+
interface AriaHideOutsideOptions {
|
|
17
|
+
root?: Element,
|
|
18
|
+
shouldUseInert?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
// Keeps a ref count of all hidden elements. Added to when hiding an element, and
|
|
14
22
|
// subtracted from when showing it again. When it reaches zero, aria-hidden is removed.
|
|
15
23
|
let refCountMap = new WeakMap<Element, number>();
|
|
@@ -29,10 +37,33 @@ let observerStack: Array<ObserverWrapper> = [];
|
|
|
29
37
|
* @param root - Nothing will be hidden above this element.
|
|
30
38
|
* @returns - A function to restore all hidden elements.
|
|
31
39
|
*/
|
|
32
|
-
export function ariaHideOutside(targets: Element[],
|
|
40
|
+
export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOptions | Element) {
|
|
41
|
+
let windowObj = getOwnerWindow(targets?.[0]);
|
|
42
|
+
let opts = options instanceof windowObj.Element ? {root: options} : options;
|
|
43
|
+
let root = opts?.root ?? document.body;
|
|
44
|
+
let shouldUseInert = opts?.shouldUseInert && supportsInert;
|
|
33
45
|
let visibleNodes = new Set<Element>(targets);
|
|
34
46
|
let hiddenNodes = new Set<Element>();
|
|
35
47
|
|
|
48
|
+
let getHidden = (element: Element) => {
|
|
49
|
+
return shouldUseInert && element instanceof windowObj.HTMLElement ? element.inert : element.getAttribute('aria-hidden') === 'true';
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let setHidden = (element: Element, hidden: boolean) => {
|
|
53
|
+
if (shouldUseInert && element instanceof windowObj.HTMLElement) {
|
|
54
|
+
element.inert = hidden;
|
|
55
|
+
} else if (hidden) {
|
|
56
|
+
element.setAttribute('aria-hidden', 'true');
|
|
57
|
+
} else {
|
|
58
|
+
element.removeAttribute('aria-hidden');
|
|
59
|
+
if (element instanceof windowObj.HTMLElement) {
|
|
60
|
+
// We only ever call setHidden with hidden = false when the nodeCount is 1 aka
|
|
61
|
+
// we are trying to make the element visible to screen readers again, so remove inert as well
|
|
62
|
+
element.inert = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
36
67
|
let walk = (root: Element) => {
|
|
37
68
|
// Keep live announcer and top layer elements (e.g. toasts) visible.
|
|
38
69
|
for (let element of root.querySelectorAll('[data-live-announcer], [data-react-aria-top-layer]')) {
|
|
@@ -45,6 +76,7 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
45
76
|
// made for elements with role="row" since VoiceOver on iOS has issues hiding elements with role="row".
|
|
46
77
|
// For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
|
|
47
78
|
if (
|
|
79
|
+
hiddenNodes.has(node) ||
|
|
48
80
|
visibleNodes.has(node) ||
|
|
49
81
|
(node.parentElement && hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute('role') !== 'row')
|
|
50
82
|
) {
|
|
@@ -87,12 +119,12 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
87
119
|
|
|
88
120
|
// If already aria-hidden, and the ref count is zero, then this element
|
|
89
121
|
// was already hidden and there's nothing for us to do.
|
|
90
|
-
if (node
|
|
122
|
+
if (getHidden(node) && refCount === 0) {
|
|
91
123
|
return;
|
|
92
124
|
}
|
|
93
125
|
|
|
94
126
|
if (refCount === 0) {
|
|
95
|
-
node
|
|
127
|
+
setHidden(node, true);
|
|
96
128
|
}
|
|
97
129
|
|
|
98
130
|
hiddenNodes.add(node);
|
|
@@ -109,20 +141,13 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
109
141
|
|
|
110
142
|
let observer = new MutationObserver(changes => {
|
|
111
143
|
for (let change of changes) {
|
|
112
|
-
if (change.type !== 'childList'
|
|
144
|
+
if (change.type !== 'childList') {
|
|
113
145
|
continue;
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
// If the parent element of the added nodes is not within one of the targets,
|
|
117
149
|
// and not already inside a hidden node, hide all of the new children.
|
|
118
150
|
if (![...visibleNodes, ...hiddenNodes].some(node => node.contains(change.target))) {
|
|
119
|
-
for (let node of change.removedNodes) {
|
|
120
|
-
if (node instanceof Element) {
|
|
121
|
-
visibleNodes.delete(node);
|
|
122
|
-
hiddenNodes.delete(node);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
151
|
for (let node of change.addedNodes) {
|
|
127
152
|
if (
|
|
128
153
|
(node instanceof HTMLElement || node instanceof SVGElement) &&
|
|
@@ -161,7 +186,7 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
161
186
|
continue;
|
|
162
187
|
}
|
|
163
188
|
if (count === 1) {
|
|
164
|
-
node
|
|
189
|
+
setHidden(node, false);
|
|
165
190
|
refCountMap.delete(node);
|
|
166
191
|
} else {
|
|
167
192
|
refCountMap.set(node, count - 1);
|
|
@@ -180,7 +205,7 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
180
205
|
};
|
|
181
206
|
}
|
|
182
207
|
|
|
183
|
-
export function keepVisible(element: Element) {
|
|
208
|
+
export function keepVisible(element: Element): (() => void) | undefined {
|
|
184
209
|
let observer = observerStack[observerStack.length - 1];
|
|
185
210
|
if (observer && !observer.visibleNodes.has(element)) {
|
|
186
211
|
observer.visibleNodes.add(element);
|
package/src/calculatePosition.ts
CHANGED
|
@@ -67,6 +67,7 @@ export interface PositionResult {
|
|
|
67
67
|
position: Position,
|
|
68
68
|
arrowOffsetLeft?: number,
|
|
69
69
|
arrowOffsetTop?: number,
|
|
70
|
+
triggerAnchorPoint: {x: number, y: number},
|
|
70
71
|
maxHeight: number,
|
|
71
72
|
placement: PlacementAxis
|
|
72
73
|
}
|
|
@@ -419,7 +420,8 @@ export function calculatePositionInternal(
|
|
|
419
420
|
// childOffset[crossAxis] + .5 * childOffset[crossSize] = absolute position with respect to the trigger's coordinate system that would place the arrow in the center of the trigger
|
|
420
421
|
// position[crossAxis] - margins[AXIS[crossAxis]] = value use to transform the position to a value with respect to the overlay's coordinate system. A child element's (aka arrow) position absolute's "0"
|
|
421
422
|
// is positioned after the margin of its parent (aka overlay) so we need to subtract it to get the proper coordinate transform
|
|
422
|
-
let
|
|
423
|
+
let origin = childOffset[crossAxis] - position[crossAxis]! - margins[AXIS[crossAxis]];
|
|
424
|
+
let preferredArrowPosition = origin + .5 * childOffset[crossSize];
|
|
423
425
|
|
|
424
426
|
// Min/Max position limits for the arrow with respect to the overlay
|
|
425
427
|
const arrowMinPosition = arrowSize / 2 + arrowBoundaryOffset;
|
|
@@ -436,12 +438,30 @@ export function calculatePositionInternal(
|
|
|
436
438
|
const arrowPositionOverlappingChild = clamp(preferredArrowPosition, arrowOverlappingChildMinEdge, arrowOverlappingChildMaxEdge);
|
|
437
439
|
arrowPosition[crossAxis] = clamp(arrowPositionOverlappingChild, arrowMinPosition, arrowMaxPosition);
|
|
438
440
|
|
|
441
|
+
// If there is an arrow, use that as the origin so that animations are smooth.
|
|
442
|
+
// Otherwise use the target edge.
|
|
443
|
+
({placement, crossPlacement} = placementInfo);
|
|
444
|
+
if (arrowSize) {
|
|
445
|
+
origin = arrowPosition[crossAxis];
|
|
446
|
+
} else if (crossPlacement === 'right') {
|
|
447
|
+
origin += childOffset[crossSize];
|
|
448
|
+
} else if (crossPlacement === 'center') {
|
|
449
|
+
origin += childOffset[crossSize] / 2;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let crossOrigin = placement === 'left' || placement === 'top' ? overlaySize[size] : 0;
|
|
453
|
+
let triggerAnchorPoint = {
|
|
454
|
+
x: placement === 'top' || placement === 'bottom' ? origin : crossOrigin,
|
|
455
|
+
y: placement === 'left' || placement === 'right' ? origin : crossOrigin
|
|
456
|
+
};
|
|
457
|
+
|
|
439
458
|
return {
|
|
440
459
|
position,
|
|
441
460
|
maxHeight: maxHeight,
|
|
442
461
|
arrowOffsetLeft: arrowPosition.left,
|
|
443
462
|
arrowOffsetTop: arrowPosition.top,
|
|
444
|
-
placement
|
|
463
|
+
placement,
|
|
464
|
+
triggerAnchorPoint
|
|
445
465
|
};
|
|
446
466
|
}
|
|
447
467
|
|
package/src/useModalOverlay.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTrig
|
|
|
58
58
|
|
|
59
59
|
useEffect(() => {
|
|
60
60
|
if (state.isOpen && ref.current) {
|
|
61
|
-
return ariaHideOutside([ref.current]);
|
|
61
|
+
return ariaHideOutside([ref.current], {shouldUseInert: true});
|
|
62
62
|
}
|
|
63
63
|
}, [state.isOpen, ref]);
|
|
64
64
|
|
|
@@ -37,6 +37,10 @@ export interface AriaPositionProps extends PositionProps {
|
|
|
37
37
|
* The ref for the overlay element.
|
|
38
38
|
*/
|
|
39
39
|
overlayRef: RefObject<Element | null>,
|
|
40
|
+
/**
|
|
41
|
+
* The ref for the arrow element.
|
|
42
|
+
*/
|
|
43
|
+
arrowRef?: RefObject<Element | null>,
|
|
40
44
|
/**
|
|
41
45
|
* A ref for the scrollable region within the overlay.
|
|
42
46
|
* @default overlayRef
|
|
@@ -68,6 +72,8 @@ export interface PositionAria {
|
|
|
68
72
|
arrowProps: DOMAttributes,
|
|
69
73
|
/** Placement of the overlay with respect to the overlay trigger. */
|
|
70
74
|
placement: PlacementAxis | null,
|
|
75
|
+
/** The origin of the target in the overlay's coordinate system. Useful for animations. */
|
|
76
|
+
triggerAnchorPoint: {x: number, y: number} | null,
|
|
71
77
|
/** Updates the position of the overlay. */
|
|
72
78
|
updatePosition(): void
|
|
73
79
|
}
|
|
@@ -86,9 +92,10 @@ let visualViewport = typeof document !== 'undefined' ? window.visualViewport : n
|
|
|
86
92
|
export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
87
93
|
let {direction} = useLocale();
|
|
88
94
|
let {
|
|
89
|
-
arrowSize
|
|
95
|
+
arrowSize,
|
|
90
96
|
targetRef,
|
|
91
97
|
overlayRef,
|
|
98
|
+
arrowRef,
|
|
92
99
|
scrollRef = overlayRef,
|
|
93
100
|
placement = 'bottom' as Placement,
|
|
94
101
|
containerPadding = 12,
|
|
@@ -109,6 +116,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
109
116
|
placement,
|
|
110
117
|
overlayRef.current,
|
|
111
118
|
targetRef.current,
|
|
119
|
+
arrowRef?.current,
|
|
112
120
|
scrollRef.current,
|
|
113
121
|
containerPadding,
|
|
114
122
|
shouldFlip,
|
|
@@ -141,6 +149,17 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
141
149
|
return;
|
|
142
150
|
}
|
|
143
151
|
|
|
152
|
+
// Delay updating the position until children are finished rendering (e.g. collections).
|
|
153
|
+
if (overlayRef.current.querySelector('[data-react-aria-incomplete]')) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Don't update while the overlay is animating.
|
|
158
|
+
// Things like scale animations can mess up positioning by affecting the overlay's computed size.
|
|
159
|
+
if (overlayRef.current.getAnimations?.().length > 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
144
163
|
// Determine a scroll anchor based on the focused element.
|
|
145
164
|
// This stores the offset of the anchor element from the scroll container
|
|
146
165
|
// so it can be restored after repositioning. This way if the overlay height
|
|
@@ -181,7 +200,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
181
200
|
offset,
|
|
182
201
|
crossOffset,
|
|
183
202
|
maxHeight,
|
|
184
|
-
arrowSize,
|
|
203
|
+
arrowSize: arrowSize ?? arrowRef?.current?.getBoundingClientRect().width ?? 0,
|
|
185
204
|
arrowBoundaryOffset
|
|
186
205
|
});
|
|
187
206
|
|
|
@@ -280,13 +299,16 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
280
299
|
return {
|
|
281
300
|
overlayProps: {
|
|
282
301
|
style: {
|
|
283
|
-
position: 'absolute',
|
|
302
|
+
position: position ? 'absolute' : 'fixed',
|
|
303
|
+
top: !position ? 0 : undefined,
|
|
304
|
+
left: !position ? 0 : undefined,
|
|
284
305
|
zIndex: 100000, // should match the z-index in ModalTrigger
|
|
285
306
|
...position?.position,
|
|
286
307
|
maxHeight: position?.maxHeight ?? '100vh'
|
|
287
308
|
}
|
|
288
309
|
},
|
|
289
310
|
placement: position?.placement ?? null,
|
|
311
|
+
triggerAnchorPoint: position?.triggerAnchorPoint ?? null,
|
|
290
312
|
arrowProps: {
|
|
291
313
|
'aria-hidden': 'true',
|
|
292
314
|
role: 'presentation',
|
package/src/usePopover.ts
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
import {ariaHideOutside, keepVisible} from './ariaHideOutside';
|
|
14
14
|
import {AriaPositionProps, useOverlayPosition} from './useOverlayPosition';
|
|
15
15
|
import {DOMAttributes, RefObject} from '@react-types/shared';
|
|
16
|
-
import {mergeProps
|
|
16
|
+
import {mergeProps} from '@react-aria/utils';
|
|
17
17
|
import {OverlayTriggerState} from '@react-stately/overlays';
|
|
18
18
|
import {PlacementAxis} from '@react-types/overlays';
|
|
19
|
+
import {useEffect} from 'react';
|
|
19
20
|
import {useOverlay} from './useOverlay';
|
|
20
21
|
import {usePreventScroll} from './usePreventScroll';
|
|
21
22
|
|
|
@@ -28,6 +29,8 @@ export interface AriaPopoverProps extends Omit<AriaPositionProps, 'isOpen' | 'on
|
|
|
28
29
|
* The ref for the popover element.
|
|
29
30
|
*/
|
|
30
31
|
popoverRef: RefObject<Element | null>,
|
|
32
|
+
/** A ref for the popover arrow element. */
|
|
33
|
+
arrowRef?: RefObject<Element | null>,
|
|
31
34
|
/**
|
|
32
35
|
* An optional ref for a group of popovers, e.g. submenus.
|
|
33
36
|
* When provided, this element is used to detect outside interactions
|
|
@@ -69,7 +72,9 @@ export interface PopoverAria {
|
|
|
69
72
|
/** Props to apply to the underlay element, if any. */
|
|
70
73
|
underlayProps: DOMAttributes,
|
|
71
74
|
/** Placement of the popover with respect to the trigger. */
|
|
72
|
-
placement: PlacementAxis | null
|
|
75
|
+
placement: PlacementAxis | null,
|
|
76
|
+
/** The origin of the target in the overlay's coordinate system. Useful for animations. */
|
|
77
|
+
triggerAnchorPoint: {x: number, y: number} | null
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
/**
|
|
@@ -101,7 +106,7 @@ export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState):
|
|
|
101
106
|
groupRef ?? popoverRef
|
|
102
107
|
);
|
|
103
108
|
|
|
104
|
-
let {overlayProps: positionProps, arrowProps, placement} = useOverlayPosition({
|
|
109
|
+
let {overlayProps: positionProps, arrowProps, placement, triggerAnchorPoint: origin} = useOverlayPosition({
|
|
105
110
|
...otherProps,
|
|
106
111
|
targetRef: triggerRef,
|
|
107
112
|
overlayRef: popoverRef,
|
|
@@ -113,12 +118,12 @@ export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState):
|
|
|
113
118
|
isDisabled: isNonModal || !state.isOpen
|
|
114
119
|
});
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
useEffect(() => {
|
|
117
122
|
if (state.isOpen && popoverRef.current) {
|
|
118
123
|
if (isNonModal) {
|
|
119
124
|
return keepVisible(groupRef?.current ?? popoverRef.current);
|
|
120
125
|
} else {
|
|
121
|
-
return ariaHideOutside([groupRef?.current ?? popoverRef.current]);
|
|
126
|
+
return ariaHideOutside([groupRef?.current ?? popoverRef.current], {shouldUseInert: true});
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
}, [isNonModal, state.isOpen, popoverRef, groupRef]);
|
|
@@ -127,6 +132,7 @@ export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState):
|
|
|
127
132
|
popoverProps: mergeProps(overlayProps, positionProps),
|
|
128
133
|
arrowProps,
|
|
129
134
|
underlayProps,
|
|
130
|
-
placement
|
|
135
|
+
placement,
|
|
136
|
+
triggerAnchorPoint: origin
|
|
131
137
|
};
|
|
132
138
|
}
|