@react-aria/overlays 3.10.1 → 3.12.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/main.js +166 -7
- package/dist/main.js.map +1 -1
- package/dist/module.js +164 -9
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/Overlay.tsx +63 -0
- package/src/ariaHideOutside.ts +32 -8
- package/src/index.ts +6 -0
- package/src/useCloseOnScroll.ts +1 -1
- package/src/useModalOverlay.ts +69 -0
- package/src/useOverlay.ts +8 -0
- package/src/useOverlayPosition.ts +10 -2
- package/src/usePopover.ts +121 -0
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;;;;AEoBA,kCAAmC,SAAQ,aAAa;IACtD;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,SAAS,EAAE,UAAU,OAAO,CAAC,CAAC;IAC9B;;OAEG;IACH,UAAU,EAAE,UAAU,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,UAAU,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;IACE,+CAA+C;IAC/C,YAAY,EAAE,aAAa,CAAC;IAC5B,8CAA8C;IAC9C,UAAU,EAAE,aAAa,CAAC;IAC1B,oEAAoE;IACpE,SAAS,EAAE,aAAa,CAAC;IACzB,2CAA2C;IAC3C,cAAc,IAAI,IAAI,CAAA;CACvB;AAKD;;;GAGG;AACH,mCAAmC,KAAK,EAAE,iBAAiB,GAAG,YAAY,
|
|
1
|
+
{"mappings":"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","sources":["packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/calculatePosition.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/useCloseOnScroll.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/useOverlayPosition.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/useOverlay.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/useOverlayTrigger.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/usePreventScroll.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/useModal.tsx","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/DismissButton.tsx","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/ariaHideOutside.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/usePopover.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/Overlay.tsx","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/useModalOverlay.ts","packages/@react-aria/overlays/src/packages/@react-aria/overlays/src/index.ts","packages/@react-aria/overlays/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null,null,"/*\n * Copyright 2020 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 */\nexport {useOverlayPosition} from './useOverlayPosition';\nexport {useOverlay} from './useOverlay';\nexport {useOverlayTrigger} from './useOverlayTrigger';\nexport {usePreventScroll} from './usePreventScroll';\nexport {ModalProvider, useModalProvider, OverlayProvider, OverlayContainer, useModal} from './useModal';\nexport {DismissButton} from './DismissButton';\nexport {ariaHideOutside} from './ariaHideOutside';\nexport {usePopover} from './usePopover';\nexport {useModalOverlay} from './useModalOverlay';\nexport {Overlay, useOverlayFocusContain} from './Overlay';\n\nexport type {AriaPositionProps, PositionAria} from './useOverlayPosition';\nexport type {AriaOverlayProps, OverlayAria} from './useOverlay';\nexport type {OverlayTriggerAria, OverlayTriggerProps} from './useOverlayTrigger';\nexport type {AriaModalOptions, ModalAria, ModalProviderAria, ModalProviderProps, OverlayContainerProps} from './useModal';\nexport type {DismissButtonProps} from './DismissButton';\nexport type {AriaPopoverProps, PopoverAria} from './usePopover';\nexport type {AriaModalOverlayProps, ModalOverlayAria} from './useModalOverlay';\nexport type {OverlayProps} from './Overlay';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/overlays",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -18,15 +18,16 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.6.2",
|
|
21
|
-
"@react-aria/
|
|
22
|
-
"@react-aria/
|
|
23
|
-
"@react-aria/
|
|
24
|
-
"@react-aria/
|
|
25
|
-
"@react-aria/
|
|
26
|
-
"@react-
|
|
27
|
-
"@react-
|
|
28
|
-
"@react-types/
|
|
29
|
-
"@react-types/
|
|
21
|
+
"@react-aria/focus": "^3.10.0",
|
|
22
|
+
"@react-aria/i18n": "^3.6.2",
|
|
23
|
+
"@react-aria/interactions": "^3.13.0",
|
|
24
|
+
"@react-aria/ssr": "^3.4.0",
|
|
25
|
+
"@react-aria/utils": "^3.14.1",
|
|
26
|
+
"@react-aria/visually-hidden": "^3.6.0",
|
|
27
|
+
"@react-stately/overlays": "^3.4.3",
|
|
28
|
+
"@react-types/button": "^3.7.0",
|
|
29
|
+
"@react-types/overlays": "^3.6.5",
|
|
30
|
+
"@react-types/shared": "^3.16.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
|
|
@@ -35,5 +36,5 @@
|
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
38
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "2954307ddbefe149241685440c81f80ece6b2c83"
|
|
39
40
|
}
|
package/src/Overlay.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {FocusScope} from '@react-aria/focus';
|
|
14
|
+
import React, {ReactNode, useContext, useMemo, useState} from 'react';
|
|
15
|
+
import ReactDOM from 'react-dom';
|
|
16
|
+
import {useIsSSR} from '@react-aria/ssr';
|
|
17
|
+
import {useLayoutEffect} from '@react-aria/utils';
|
|
18
|
+
|
|
19
|
+
export interface OverlayProps {
|
|
20
|
+
/**
|
|
21
|
+
* The container element in which the overlay portal will be placed.
|
|
22
|
+
* @default document.body
|
|
23
|
+
*/
|
|
24
|
+
portalContainer?: Element,
|
|
25
|
+
/** The overlay to render in the portal. */
|
|
26
|
+
children: ReactNode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const OverlayContext = React.createContext(null);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A container which renders an overlay such as a popover or modal in a portal,
|
|
33
|
+
* and provides a focus scope for the child elements.
|
|
34
|
+
*/
|
|
35
|
+
export function Overlay(props: OverlayProps) {
|
|
36
|
+
let isSSR = useIsSSR();
|
|
37
|
+
let {portalContainer = isSSR ? null : document.body} = props;
|
|
38
|
+
let [contain, setContain] = useState(false);
|
|
39
|
+
let contextValue = useMemo(() => ({contain, setContain}), [contain, setContain]);
|
|
40
|
+
|
|
41
|
+
if (!portalContainer) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let contents = (
|
|
46
|
+
<OverlayContext.Provider value={contextValue}>
|
|
47
|
+
<FocusScope restoreFocus contain={contain}>
|
|
48
|
+
{props.children}
|
|
49
|
+
</FocusScope>
|
|
50
|
+
</OverlayContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return ReactDOM.createPortal(contents, portalContainer);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @private */
|
|
57
|
+
export function useOverlayFocusContain() {
|
|
58
|
+
let ctx = useContext(OverlayContext);
|
|
59
|
+
let setContain = ctx?.setContain;
|
|
60
|
+
useLayoutEffect(() => {
|
|
61
|
+
setContain?.(true);
|
|
62
|
+
}, [setContain]);
|
|
63
|
+
}
|
package/src/ariaHideOutside.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// Keeps a ref count of all hidden elements. Added to when hiding an element, and
|
|
14
14
|
// subtracted from when showing it again. When it reaches zero, aria-hidden is removed.
|
|
15
15
|
let refCountMap = new WeakMap<Element, number>();
|
|
16
|
+
let observerStack = [];
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Hides all elements in the DOM outside the given targets from screen readers using aria-hidden,
|
|
@@ -36,20 +37,16 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// Skip this node and its children if it is one of the target nodes, or a live announcer.
|
|
39
|
-
// Also skip children of already hidden nodes, as aria-hidden is recursive.
|
|
40
|
+
// Also skip children of already hidden nodes, as aria-hidden is recursive. An exception is
|
|
41
|
+
// made for elements with role="row" since VoiceOver on iOS has issues hiding elements with role="row".
|
|
42
|
+
// For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
|
|
40
43
|
if (
|
|
41
44
|
visibleNodes.has(node as Element) ||
|
|
42
|
-
hiddenNodes.has(node.parentElement)
|
|
45
|
+
(hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute('role') !== 'row')
|
|
43
46
|
) {
|
|
44
47
|
return NodeFilter.FILTER_REJECT;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
// VoiceOver on iOS has issues hiding elements with role="row". Hide the cells inside instead.
|
|
48
|
-
// https://bugs.webkit.org/show_bug.cgi?id=222623
|
|
49
|
-
if (node instanceof Element && node.getAttribute('role') === 'row') {
|
|
50
|
-
return NodeFilter.FILTER_SKIP;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
// Skip this node but continue to children if one of the targets is inside the node.
|
|
54
51
|
if (targets.some(target => node.contains(target))) {
|
|
55
52
|
return NodeFilter.FILTER_SKIP;
|
|
@@ -77,6 +74,12 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
77
74
|
refCountMap.set(node, refCount + 1);
|
|
78
75
|
};
|
|
79
76
|
|
|
77
|
+
// If there is already a MutationObserver listening from a previous call,
|
|
78
|
+
// disconnect it so the new on takes over.
|
|
79
|
+
if (observerStack.length) {
|
|
80
|
+
observerStack[observerStack.length - 1].disconnect();
|
|
81
|
+
}
|
|
82
|
+
|
|
80
83
|
let node = walker.nextNode() as Element;
|
|
81
84
|
while (node != null) {
|
|
82
85
|
hide(node);
|
|
@@ -105,6 +108,17 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
105
108
|
|
|
106
109
|
observer.observe(root, {childList: true, subtree: true});
|
|
107
110
|
|
|
111
|
+
let observerWrapper = {
|
|
112
|
+
observe() {
|
|
113
|
+
observer.observe(root, {childList: true, subtree: true});
|
|
114
|
+
},
|
|
115
|
+
disconnect() {
|
|
116
|
+
observer.disconnect();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
observerStack.push(observerWrapper);
|
|
121
|
+
|
|
108
122
|
return () => {
|
|
109
123
|
observer.disconnect();
|
|
110
124
|
|
|
@@ -117,5 +131,15 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
117
131
|
refCountMap.set(node, count - 1);
|
|
118
132
|
}
|
|
119
133
|
}
|
|
134
|
+
|
|
135
|
+
// Remove this observer from the stack, and start the previous one.
|
|
136
|
+
if (observerWrapper === observerStack[observerStack.length - 1]) {
|
|
137
|
+
observerStack.pop();
|
|
138
|
+
if (observerStack.length) {
|
|
139
|
+
observerStack[observerStack.length - 1].observe();
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
observerStack.splice(observerStack.indexOf(observerWrapper), 1);
|
|
143
|
+
}
|
|
120
144
|
};
|
|
121
145
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,9 +16,15 @@ export {usePreventScroll} from './usePreventScroll';
|
|
|
16
16
|
export {ModalProvider, useModalProvider, OverlayProvider, OverlayContainer, useModal} from './useModal';
|
|
17
17
|
export {DismissButton} from './DismissButton';
|
|
18
18
|
export {ariaHideOutside} from './ariaHideOutside';
|
|
19
|
+
export {usePopover} from './usePopover';
|
|
20
|
+
export {useModalOverlay} from './useModalOverlay';
|
|
21
|
+
export {Overlay, useOverlayFocusContain} from './Overlay';
|
|
19
22
|
|
|
20
23
|
export type {AriaPositionProps, PositionAria} from './useOverlayPosition';
|
|
21
24
|
export type {AriaOverlayProps, OverlayAria} from './useOverlay';
|
|
22
25
|
export type {OverlayTriggerAria, OverlayTriggerProps} from './useOverlayTrigger';
|
|
23
26
|
export type {AriaModalOptions, ModalAria, ModalProviderAria, ModalProviderProps, OverlayContainerProps} from './useModal';
|
|
24
27
|
export type {DismissButtonProps} from './DismissButton';
|
|
28
|
+
export type {AriaPopoverProps, PopoverAria} from './usePopover';
|
|
29
|
+
export type {AriaModalOverlayProps, ModalOverlayAria} from './useModalOverlay';
|
|
30
|
+
export type {OverlayProps} from './Overlay';
|
package/src/useCloseOnScroll.ts
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {ariaHideOutside} from './ariaHideOutside';
|
|
14
|
+
import {DOMAttributes} from '@react-types/shared';
|
|
15
|
+
import {mergeProps} from '@react-aria/utils';
|
|
16
|
+
import {OverlayTriggerState} from '@react-stately/overlays';
|
|
17
|
+
import {RefObject, useEffect} from 'react';
|
|
18
|
+
import {useOverlay} from './useOverlay';
|
|
19
|
+
import {useOverlayFocusContain} from './Overlay';
|
|
20
|
+
import {usePreventScroll} from './usePreventScroll';
|
|
21
|
+
|
|
22
|
+
export interface AriaModalOverlayProps {
|
|
23
|
+
/**
|
|
24
|
+
* Whether to close the modal when the user interacts outside it.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
isDismissable?: boolean,
|
|
28
|
+
/**
|
|
29
|
+
* Whether pressing the escape key to close the modal should be disabled.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
isKeyboardDismissDisabled?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ModalOverlayAria {
|
|
36
|
+
/** Props for the modal element. */
|
|
37
|
+
modalProps: DOMAttributes,
|
|
38
|
+
/** Props for the underlay element. */
|
|
39
|
+
underlayProps: DOMAttributes
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Provides the behavior and accessibility implementation for a modal component.
|
|
44
|
+
* A modal is an overlay element which blocks interaction with elements outside it.
|
|
45
|
+
*/
|
|
46
|
+
export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTriggerState, ref: RefObject<HTMLElement>): ModalOverlayAria {
|
|
47
|
+
let {overlayProps, underlayProps} = useOverlay({
|
|
48
|
+
...props,
|
|
49
|
+
isOpen: state.isOpen,
|
|
50
|
+
onClose: state.close
|
|
51
|
+
}, ref);
|
|
52
|
+
|
|
53
|
+
usePreventScroll({
|
|
54
|
+
isDisabled: !state.isOpen
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
useOverlayFocusContain();
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (state.isOpen) {
|
|
61
|
+
return ariaHideOutside([ref.current]);
|
|
62
|
+
}
|
|
63
|
+
}, [state.isOpen, ref]);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
modalProps: mergeProps(overlayProps),
|
|
67
|
+
underlayProps
|
|
68
|
+
};
|
|
69
|
+
}
|
package/src/useOverlay.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {DOMAttributes} from '@react-types/shared';
|
|
14
|
+
import {isElementInChildOfActiveScope} from '@react-aria/focus';
|
|
14
15
|
import {RefObject, SyntheticEvent, useEffect} from 'react';
|
|
15
16
|
import {useFocusWithin, useInteractOutside} from '@react-aria/interactions';
|
|
16
17
|
|
|
@@ -124,6 +125,13 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element>): Ov
|
|
|
124
125
|
let {focusWithinProps} = useFocusWithin({
|
|
125
126
|
isDisabled: !shouldCloseOnBlur,
|
|
126
127
|
onBlurWithin: (e) => {
|
|
128
|
+
// If focus is moving into a child focus scope (e.g. menu inside a dialog),
|
|
129
|
+
// do not close the outer overlay. At this point, the active scope should
|
|
130
|
+
// still be the outer overlay, since blur events run before focus.
|
|
131
|
+
if (e.relatedTarget && isElementInChildOfActiveScope(e.relatedTarget)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
127
135
|
if (!shouldCloseOnInteractOutside || shouldCloseOnInteractOutside(e.relatedTarget as Element)) {
|
|
128
136
|
onClose();
|
|
129
137
|
}
|
|
@@ -15,7 +15,7 @@ import {DOMAttributes} from '@react-types/shared';
|
|
|
15
15
|
import {Placement, PlacementAxis, PositionProps} from '@react-types/overlays';
|
|
16
16
|
import {RefObject, useCallback, useRef, useState} from 'react';
|
|
17
17
|
import {useCloseOnScroll} from './useCloseOnScroll';
|
|
18
|
-
import {useLayoutEffect} from '@react-aria/utils';
|
|
18
|
+
import {useLayoutEffect, useResizeObserver} from '@react-aria/utils';
|
|
19
19
|
import {useLocale} from '@react-aria/i18n';
|
|
20
20
|
|
|
21
21
|
export interface AriaPositionProps extends PositionProps {
|
|
@@ -129,14 +129,22 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
129
129
|
maxHeight
|
|
130
130
|
})
|
|
131
131
|
);
|
|
132
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
133
|
}, deps);
|
|
133
134
|
|
|
134
135
|
// Update position when anything changes
|
|
136
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
135
137
|
useLayoutEffect(updatePosition, deps);
|
|
136
138
|
|
|
137
139
|
// Update position on window resize
|
|
138
140
|
useResize(updatePosition);
|
|
139
141
|
|
|
142
|
+
// Update position when the overlay changes size (might need to flip).
|
|
143
|
+
useResizeObserver({
|
|
144
|
+
ref: overlayRef,
|
|
145
|
+
onResize: updatePosition
|
|
146
|
+
});
|
|
147
|
+
|
|
140
148
|
// Reposition the overlay and do not close on scroll while the visual viewport is resizing.
|
|
141
149
|
// This will ensure that overlays adjust their positioning when the iOS virtual keyboard appears.
|
|
142
150
|
let isResizing = useRef(false);
|
|
@@ -171,7 +179,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
171
179
|
useCloseOnScroll({
|
|
172
180
|
triggerRef: targetRef,
|
|
173
181
|
isOpen,
|
|
174
|
-
onClose: onClose
|
|
182
|
+
onClose: onClose && close
|
|
175
183
|
});
|
|
176
184
|
|
|
177
185
|
return {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {ariaHideOutside} from './ariaHideOutside';
|
|
14
|
+
import {AriaPositionProps, useOverlayPosition} from './useOverlayPosition';
|
|
15
|
+
import {DOMAttributes} from '@react-types/shared';
|
|
16
|
+
import {mergeProps, useLayoutEffect} from '@react-aria/utils';
|
|
17
|
+
import {OverlayTriggerState} from '@react-stately/overlays';
|
|
18
|
+
import {PlacementAxis} from '@react-types/overlays';
|
|
19
|
+
import {RefObject, useState} from 'react';
|
|
20
|
+
import {useOverlay} from './useOverlay';
|
|
21
|
+
import {usePreventScroll} from './usePreventScroll';
|
|
22
|
+
|
|
23
|
+
export interface AriaPopoverProps extends Omit<AriaPositionProps, 'isOpen' | 'onClose' | 'targetRef' | 'overlayRef'> {
|
|
24
|
+
/**
|
|
25
|
+
* The ref for the element which the popover positions itself with respect to.
|
|
26
|
+
*/
|
|
27
|
+
triggerRef: RefObject<Element>,
|
|
28
|
+
/**
|
|
29
|
+
* The ref for the popover element.
|
|
30
|
+
*/
|
|
31
|
+
popoverRef: RefObject<Element>,
|
|
32
|
+
/**
|
|
33
|
+
* Whether the popover is non-modal, i.e. elements outside the popover may be
|
|
34
|
+
* interacted with by assistive technologies.
|
|
35
|
+
*
|
|
36
|
+
* Most popovers should not use this option as it may negatively impact the screen
|
|
37
|
+
* reader experience. Only use with components such as combobox, which are designed
|
|
38
|
+
* to handle this situation carefully.
|
|
39
|
+
*/
|
|
40
|
+
isNonModal?: boolean,
|
|
41
|
+
/**
|
|
42
|
+
* Whether pressing the escape key to close the popover should be disabled.
|
|
43
|
+
*
|
|
44
|
+
* Most popovers should not use this option. When set to true, an alternative
|
|
45
|
+
* way to close the popover with a keyboard must be provided.
|
|
46
|
+
*
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
isKeyboardDismissDisabled?: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface PopoverAria {
|
|
53
|
+
/** Props for the popover element. */
|
|
54
|
+
popoverProps: DOMAttributes,
|
|
55
|
+
/** Props for the popover tip arrow if any. */
|
|
56
|
+
arrowProps: DOMAttributes,
|
|
57
|
+
/** Props to apply to the underlay element, if any. */
|
|
58
|
+
underlayProps: DOMAttributes,
|
|
59
|
+
/** Placement of the popover with respect to the trigger. */
|
|
60
|
+
placement: PlacementAxis
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Provides the behavior and accessibility implementation for a popover component.
|
|
65
|
+
* A popover is an overlay element positioned relative to a trigger.
|
|
66
|
+
*/
|
|
67
|
+
export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState): PopoverAria {
|
|
68
|
+
let {
|
|
69
|
+
triggerRef,
|
|
70
|
+
popoverRef,
|
|
71
|
+
isNonModal,
|
|
72
|
+
isKeyboardDismissDisabled,
|
|
73
|
+
...otherProps
|
|
74
|
+
} = props;
|
|
75
|
+
|
|
76
|
+
let {overlayProps, underlayProps} = useOverlay(
|
|
77
|
+
{
|
|
78
|
+
isOpen: state.isOpen,
|
|
79
|
+
onClose: state.close,
|
|
80
|
+
shouldCloseOnBlur: true,
|
|
81
|
+
isDismissable: !isNonModal,
|
|
82
|
+
isKeyboardDismissDisabled
|
|
83
|
+
},
|
|
84
|
+
popoverRef
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
let {overlayProps: positionProps, arrowProps, placement} = useOverlayPosition({
|
|
88
|
+
...otherProps,
|
|
89
|
+
targetRef: triggerRef,
|
|
90
|
+
overlayRef: popoverRef,
|
|
91
|
+
isOpen: state.isOpen,
|
|
92
|
+
onClose: null
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Delay preventing scroll until popover is positioned to avoid extra scroll padding.
|
|
96
|
+
// This requires a layout effect so that positioning has been committed to the DOM
|
|
97
|
+
// by the time usePreventScroll measures the element.
|
|
98
|
+
let [isPositioned, setPositioned] = useState(false);
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
if (!isNonModal && placement) {
|
|
101
|
+
setPositioned(true);
|
|
102
|
+
}
|
|
103
|
+
}, [isNonModal, placement]);
|
|
104
|
+
|
|
105
|
+
usePreventScroll({
|
|
106
|
+
isDisabled: isNonModal || !isPositioned
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
useLayoutEffect(() => {
|
|
110
|
+
if (state.isOpen && !isNonModal && popoverRef.current) {
|
|
111
|
+
return ariaHideOutside([popoverRef.current]);
|
|
112
|
+
}
|
|
113
|
+
}, [isNonModal, state.isOpen, popoverRef]);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
popoverProps: mergeProps(overlayProps, positionProps),
|
|
117
|
+
arrowProps,
|
|
118
|
+
underlayProps,
|
|
119
|
+
placement
|
|
120
|
+
};
|
|
121
|
+
}
|