@react-aria/overlays 3.10.0 → 3.11.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 +104 -5
- package/dist/main.js.map +1 -1
- package/dist/module.js +102 -7
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -10
- package/src/Overlay.tsx +61 -0
- package/src/ariaHideOutside.ts +4 -8
- package/src/index.ts +6 -0
- package/src/useModalOverlay.ts +69 -0
- package/src/usePopover.ts +88 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/overlays",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.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/
|
|
21
|
+
"@react-aria/focus": "^3.9.0",
|
|
22
|
+
"@react-aria/i18n": "^3.6.1",
|
|
23
|
+
"@react-aria/interactions": "^3.12.0",
|
|
23
24
|
"@react-aria/ssr": "^3.3.0",
|
|
24
|
-
"@react-aria/utils": "^3.
|
|
25
|
-
"@react-aria/visually-hidden": "^3.
|
|
26
|
-
"@react-stately/overlays": "^3.4.
|
|
27
|
-
"@react-types/button": "^3.6.
|
|
28
|
-
"@react-types/overlays": "^3.6.
|
|
29
|
-
"@react-types/shared": "^3.
|
|
25
|
+
"@react-aria/utils": "^3.14.0",
|
|
26
|
+
"@react-aria/visually-hidden": "^3.5.0",
|
|
27
|
+
"@react-stately/overlays": "^3.4.2",
|
|
28
|
+
"@react-types/button": "^3.6.2",
|
|
29
|
+
"@react-types/overlays": "^3.6.4",
|
|
30
|
+
"@react-types/shared": "^3.15.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": "9202ef59e8c104dd06ffe33148445ef7932a5d1b"
|
|
39
40
|
}
|
package/src/Overlay.tsx
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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, 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
|
+
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
|
+
|
|
40
|
+
if (!portalContainer) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let contents = (
|
|
45
|
+
<OverlayContext.Provider value={setContain}>
|
|
46
|
+
<FocusScope restoreFocus contain={contain}>
|
|
47
|
+
{props.children}
|
|
48
|
+
</FocusScope>
|
|
49
|
+
</OverlayContext.Provider>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return ReactDOM.createPortal(contents, portalContainer);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @private */
|
|
56
|
+
export function useOverlayFocusContain() {
|
|
57
|
+
let setContain = useContext(OverlayContext);
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
setContain?.(true);
|
|
60
|
+
}, [setContain]);
|
|
61
|
+
}
|
package/src/ariaHideOutside.ts
CHANGED
|
@@ -36,20 +36,16 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// 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.
|
|
39
|
+
// Also skip children of already hidden nodes, as aria-hidden is recursive. An exception is
|
|
40
|
+
// made for elements with role="row" since VoiceOver on iOS has issues hiding elements with role="row".
|
|
41
|
+
// For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
|
|
40
42
|
if (
|
|
41
43
|
visibleNodes.has(node as Element) ||
|
|
42
|
-
hiddenNodes.has(node.parentElement)
|
|
44
|
+
(hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute('role') !== 'row')
|
|
43
45
|
) {
|
|
44
46
|
return NodeFilter.FILTER_REJECT;
|
|
45
47
|
}
|
|
46
48
|
|
|
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
49
|
// Skip this node but continue to children if one of the targets is inside the node.
|
|
54
50
|
if (targets.some(target => node.contains(target))) {
|
|
55
51
|
return NodeFilter.FILTER_SKIP;
|
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';
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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 {PositionProps} from '@react-types/overlays';
|
|
18
|
+
import {RefObject, useEffect} from 'react';
|
|
19
|
+
import {useOverlay} from './useOverlay';
|
|
20
|
+
import {useOverlayPosition} from './useOverlayPosition';
|
|
21
|
+
|
|
22
|
+
export interface AriaPopoverProps extends Omit<PositionProps, 'isOpen'> {
|
|
23
|
+
/**
|
|
24
|
+
* The ref for the element which the popover positions itself with respect to.
|
|
25
|
+
*/
|
|
26
|
+
triggerRef: RefObject<Element>,
|
|
27
|
+
/**
|
|
28
|
+
* The ref for the popover element.
|
|
29
|
+
*/
|
|
30
|
+
popoverRef: RefObject<Element>,
|
|
31
|
+
/**
|
|
32
|
+
* Whether the popover is non-modal, i.e. elements outside the popover may be
|
|
33
|
+
* interacted with by assistive technologies.
|
|
34
|
+
*
|
|
35
|
+
* Most popovers should not use this option as it may negatively impact the screen
|
|
36
|
+
* reader experience. Only use with components such as combobox, which are designed
|
|
37
|
+
* to handle this situation carefully.
|
|
38
|
+
*/
|
|
39
|
+
isNonModal?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PopoverAria {
|
|
43
|
+
/** Props for the popover element. */
|
|
44
|
+
popoverProps: DOMAttributes,
|
|
45
|
+
/** Props for the popover tip arrow if any. */
|
|
46
|
+
arrowProps: DOMAttributes
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Provides the behavior and accessibility implementation for a popover component.
|
|
51
|
+
* A popover is an overlay element positioned relative to a trigger.
|
|
52
|
+
*/
|
|
53
|
+
export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState): PopoverAria {
|
|
54
|
+
let {
|
|
55
|
+
triggerRef,
|
|
56
|
+
popoverRef,
|
|
57
|
+
isNonModal,
|
|
58
|
+
...otherProps
|
|
59
|
+
} = props;
|
|
60
|
+
|
|
61
|
+
let {overlayProps} = useOverlay(
|
|
62
|
+
{
|
|
63
|
+
isOpen: state.isOpen,
|
|
64
|
+
onClose: state.close,
|
|
65
|
+
shouldCloseOnBlur: true,
|
|
66
|
+
isDismissable: true
|
|
67
|
+
},
|
|
68
|
+
popoverRef
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
let {overlayProps: positionProps, arrowProps} = useOverlayPosition({
|
|
72
|
+
...otherProps,
|
|
73
|
+
targetRef: triggerRef,
|
|
74
|
+
overlayRef: popoverRef,
|
|
75
|
+
isOpen: state.isOpen
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (state.isOpen && !isNonModal) {
|
|
80
|
+
return ariaHideOutside([popoverRef.current]);
|
|
81
|
+
}
|
|
82
|
+
}, [isNonModal, state.isOpen, popoverRef]);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
popoverProps: mergeProps(overlayProps, positionProps),
|
|
86
|
+
arrowProps
|
|
87
|
+
};
|
|
88
|
+
}
|