@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/overlays",
3
- "version": "3.10.0",
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/i18n": "^3.5.0",
22
- "@react-aria/interactions": "^3.10.0",
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.13.2",
25
- "@react-aria/visually-hidden": "^3.4.0",
26
- "@react-stately/overlays": "^3.4.0",
27
- "@react-types/button": "^3.6.0",
28
- "@react-types/overlays": "^3.6.2",
29
- "@react-types/shared": "^3.14.0"
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": "cd7c0ec917122c7612f653c22f8ed558f8b66ecd"
39
+ "gitHead": "9202ef59e8c104dd06ffe33148445ef7932a5d1b"
39
40
  }
@@ -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
+ }
@@ -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
+ }