@react-aria/overlays 3.12.0 → 3.13.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/import.mjs +1401 -0
- package/dist/main.js +336 -203
- package/dist/main.js.map +1 -1
- package/dist/module.js +336 -203
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +18 -13
- package/src/ariaHideOutside.ts +55 -30
- package/src/useOverlayPosition.ts +20 -14
- package/src/useOverlayTrigger.ts +1 -1
- package/src/usePopover.ts +2 -12
- package/src/usePreventScroll.ts +20 -4
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/overlays",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
7
7
|
"module": "dist/module.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
"types": "./dist/types.d.ts",
|
|
10
|
+
"import": "./dist/import.mjs",
|
|
11
|
+
"require": "./dist/main.js"
|
|
12
|
+
},
|
|
8
13
|
"types": "dist/types.d.ts",
|
|
9
14
|
"source": "src/index.ts",
|
|
10
15
|
"files": [
|
|
@@ -17,17 +22,17 @@
|
|
|
17
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
18
23
|
},
|
|
19
24
|
"dependencies": {
|
|
20
|
-
"@
|
|
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/
|
|
30
|
-
"@
|
|
25
|
+
"@react-aria/focus": "^3.11.0",
|
|
26
|
+
"@react-aria/i18n": "^3.7.0",
|
|
27
|
+
"@react-aria/interactions": "^3.14.0",
|
|
28
|
+
"@react-aria/ssr": "^3.5.0",
|
|
29
|
+
"@react-aria/utils": "^3.15.0",
|
|
30
|
+
"@react-aria/visually-hidden": "^3.7.0",
|
|
31
|
+
"@react-stately/overlays": "^3.5.0",
|
|
32
|
+
"@react-types/button": "^3.7.1",
|
|
33
|
+
"@react-types/overlays": "^3.7.0",
|
|
34
|
+
"@react-types/shared": "^3.17.0",
|
|
35
|
+
"@swc/helpers": "^0.4.14"
|
|
31
36
|
},
|
|
32
37
|
"peerDependencies": {
|
|
33
38
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
|
|
@@ -36,5 +41,5 @@
|
|
|
36
41
|
"publishConfig": {
|
|
37
42
|
"access": "public"
|
|
38
43
|
},
|
|
39
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "a0efee84aa178cb1a202951dfd6d8de02b292307"
|
|
40
45
|
}
|
package/src/ariaHideOutside.ts
CHANGED
|
@@ -26,36 +26,55 @@ let observerStack = [];
|
|
|
26
26
|
export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
27
27
|
let visibleNodes = new Set<Element>(targets);
|
|
28
28
|
let hiddenNodes = new Set<Element>();
|
|
29
|
-
let walker = document.createTreeWalker(
|
|
30
|
-
root,
|
|
31
|
-
NodeFilter.SHOW_ELEMENT,
|
|
32
|
-
{
|
|
33
|
-
acceptNode(node) {
|
|
34
|
-
// If this node is a live announcer, add it to the set of nodes to keep visible.
|
|
35
|
-
if (((node instanceof HTMLElement || node instanceof SVGElement) && node.dataset.liveAnnouncer === 'true')) {
|
|
36
|
-
visibleNodes.add(node);
|
|
37
|
-
}
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
let walk = (root: Element) => {
|
|
31
|
+
// Keep live announcer and top layer elements (e.g. toasts) visible.
|
|
32
|
+
for (let element of root.querySelectorAll('[data-live-announcer], [data-react-aria-top-layer]')) {
|
|
33
|
+
visibleNodes.add(element);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let acceptNode = (node: Element) => {
|
|
37
|
+
// Skip this node and its children if it is one of the target nodes, or a live announcer.
|
|
38
|
+
// Also skip children of already hidden nodes, as aria-hidden is recursive. An exception is
|
|
39
|
+
// made for elements with role="row" since VoiceOver on iOS has issues hiding elements with role="row".
|
|
40
|
+
// For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
|
|
41
|
+
if (
|
|
42
|
+
visibleNodes.has(node) ||
|
|
43
|
+
(hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute('role') !== 'row')
|
|
44
|
+
) {
|
|
45
|
+
return NodeFilter.FILTER_REJECT;
|
|
46
|
+
}
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
// Skip this node but continue to children if one of the targets is inside the node.
|
|
49
|
+
for (let target of visibleNodes) {
|
|
50
|
+
if (node.contains(target)) {
|
|
52
51
|
return NodeFilter.FILTER_SKIP;
|
|
53
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let walker = document.createTreeWalker(
|
|
59
|
+
root,
|
|
60
|
+
NodeFilter.SHOW_ELEMENT,
|
|
61
|
+
{acceptNode}
|
|
62
|
+
);
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
// TreeWalker does not include the root.
|
|
65
|
+
let acceptRoot = acceptNode(root);
|
|
66
|
+
if (acceptRoot === NodeFilter.FILTER_ACCEPT) {
|
|
67
|
+
hide(root);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (acceptRoot !== NodeFilter.FILTER_REJECT) {
|
|
71
|
+
let node = walker.nextNode() as Element;
|
|
72
|
+
while (node != null) {
|
|
73
|
+
hide(node);
|
|
74
|
+
node = walker.nextNode() as Element;
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
|
-
|
|
77
|
+
};
|
|
59
78
|
|
|
60
79
|
let hide = (node: Element) => {
|
|
61
80
|
let refCount = refCountMap.get(node) ?? 0;
|
|
@@ -80,11 +99,7 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
80
99
|
observerStack[observerStack.length - 1].disconnect();
|
|
81
100
|
}
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
while (node != null) {
|
|
85
|
-
hide(node);
|
|
86
|
-
node = walker.nextNode() as Element;
|
|
87
|
-
}
|
|
102
|
+
walk(root);
|
|
88
103
|
|
|
89
104
|
let observer = new MutationObserver(changes => {
|
|
90
105
|
for (let change of changes) {
|
|
@@ -95,11 +110,21 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
95
110
|
// If the parent element of the added nodes is not within one of the targets,
|
|
96
111
|
// and not already inside a hidden node, hide all of the new children.
|
|
97
112
|
if (![...visibleNodes, ...hiddenNodes].some(node => node.contains(change.target))) {
|
|
113
|
+
for (let node of change.removedNodes) {
|
|
114
|
+
if (node instanceof Element) {
|
|
115
|
+
visibleNodes.delete(node);
|
|
116
|
+
hiddenNodes.delete(node);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
98
120
|
for (let node of change.addedNodes) {
|
|
99
|
-
if (
|
|
121
|
+
if (
|
|
122
|
+
(node instanceof HTMLElement || node instanceof SVGElement) &&
|
|
123
|
+
(node.dataset.liveAnnouncer === 'true' || node.dataset.reactAriaTopLayer === 'true')
|
|
124
|
+
) {
|
|
100
125
|
visibleNodes.add(node);
|
|
101
126
|
} else if (node instanceof Element) {
|
|
102
|
-
|
|
127
|
+
walk(node);
|
|
103
128
|
}
|
|
104
129
|
}
|
|
105
130
|
}
|
|
@@ -115,20 +115,26 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
let position = calculatePosition({
|
|
119
|
+
placement: translateRTL(placement, direction),
|
|
120
|
+
overlayNode: overlayRef.current,
|
|
121
|
+
targetNode: targetRef.current,
|
|
122
|
+
scrollNode: scrollRef.current,
|
|
123
|
+
padding: containerPadding,
|
|
124
|
+
shouldFlip,
|
|
125
|
+
boundaryElement,
|
|
126
|
+
offset,
|
|
127
|
+
crossOffset,
|
|
128
|
+
maxHeight
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Modify overlay styles directly so positioning happens immediately without the need of a second render
|
|
132
|
+
// This is so we don't have to delay autoFocus scrolling or delay applying preventScroll for popovers
|
|
133
|
+
Object.keys(position.position).forEach(key => (overlayRef.current as HTMLElement).style[key] = position.position[key] + 'px');
|
|
134
|
+
(overlayRef.current as HTMLElement).style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' : undefined;
|
|
135
|
+
|
|
136
|
+
// Trigger a set state for a second render anyway for arrow positioning
|
|
137
|
+
setPosition(position);
|
|
132
138
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
133
139
|
}, deps);
|
|
134
140
|
|
package/src/useOverlayTrigger.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface OverlayTriggerAria {
|
|
|
34
34
|
* Handles the behavior and accessibility for an overlay trigger, e.g. a button
|
|
35
35
|
* that opens a popover, menu, or other overlay that is positioned relative to the trigger.
|
|
36
36
|
*/
|
|
37
|
-
export function useOverlayTrigger(props: OverlayTriggerProps, state: OverlayTriggerState, ref
|
|
37
|
+
export function useOverlayTrigger(props: OverlayTriggerProps, state: OverlayTriggerState, ref?: RefObject<Element>): OverlayTriggerAria {
|
|
38
38
|
let {type} = props;
|
|
39
39
|
let {isOpen} = state;
|
|
40
40
|
|
package/src/usePopover.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {DOMAttributes} from '@react-types/shared';
|
|
|
16
16
|
import {mergeProps, useLayoutEffect} from '@react-aria/utils';
|
|
17
17
|
import {OverlayTriggerState} from '@react-stately/overlays';
|
|
18
18
|
import {PlacementAxis} from '@react-types/overlays';
|
|
19
|
-
import {RefObject
|
|
19
|
+
import {RefObject} from 'react';
|
|
20
20
|
import {useOverlay} from './useOverlay';
|
|
21
21
|
import {usePreventScroll} from './usePreventScroll';
|
|
22
22
|
|
|
@@ -92,18 +92,8 @@ export function usePopover(props: AriaPopoverProps, state: OverlayTriggerState):
|
|
|
92
92
|
onClose: null
|
|
93
93
|
});
|
|
94
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
95
|
usePreventScroll({
|
|
106
|
-
isDisabled: isNonModal
|
|
96
|
+
isDisabled: isNonModal
|
|
107
97
|
});
|
|
108
98
|
|
|
109
99
|
useLayoutEffect(() => {
|
package/src/usePreventScroll.ts
CHANGED
|
@@ -33,6 +33,10 @@ const nonTextInputTypes = new Set([
|
|
|
33
33
|
'reset'
|
|
34
34
|
]);
|
|
35
35
|
|
|
36
|
+
// The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position
|
|
37
|
+
let preventScrollCount = 0;
|
|
38
|
+
let restore;
|
|
39
|
+
|
|
36
40
|
/**
|
|
37
41
|
* Prevents scrolling on the document body on mount, and
|
|
38
42
|
* restores it on unmount. Also ensures that content does not
|
|
@@ -46,11 +50,21 @@ export function usePreventScroll(options: PreventScrollOptions = {}) {
|
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
preventScrollCount++;
|
|
54
|
+
if (preventScrollCount === 1) {
|
|
55
|
+
if (isIOS()) {
|
|
56
|
+
restore = preventScrollMobileSafari();
|
|
57
|
+
} else {
|
|
58
|
+
restore = preventScrollStandard();
|
|
59
|
+
}
|
|
53
60
|
}
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
preventScrollCount--;
|
|
64
|
+
if (preventScrollCount === 0) {
|
|
65
|
+
restore();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
54
68
|
}, [isDisabled]);
|
|
55
69
|
}
|
|
56
70
|
|
|
@@ -183,6 +197,7 @@ function preventScrollMobileSafari() {
|
|
|
183
197
|
// enable us to scroll the window to the top, which is required for the rest of this to work.
|
|
184
198
|
let scrollX = window.pageXOffset;
|
|
185
199
|
let scrollY = window.pageYOffset;
|
|
200
|
+
|
|
186
201
|
let restoreStyles = chain(
|
|
187
202
|
setStyle(document.documentElement, 'paddingRight', `${window.innerWidth - document.documentElement.clientWidth}px`),
|
|
188
203
|
setStyle(document.documentElement, 'overflow', 'hidden'),
|
|
@@ -212,6 +227,7 @@ function preventScrollMobileSafari() {
|
|
|
212
227
|
function setStyle(element: HTMLElement, style: string, value: string) {
|
|
213
228
|
let cur = element.style[style];
|
|
214
229
|
element.style[style] = value;
|
|
230
|
+
|
|
215
231
|
return () => {
|
|
216
232
|
element.style[style] = cur;
|
|
217
233
|
};
|