@react-aria/overlays 3.16.0 → 3.18.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.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -22,16 +22,16 @@
22
22
  "url": "https://github.com/adobe/react-spectrum"
23
23
  },
24
24
  "dependencies": {
25
- "@react-aria/focus": "^3.14.0",
26
- "@react-aria/i18n": "^3.8.1",
27
- "@react-aria/interactions": "^3.17.0",
28
- "@react-aria/ssr": "^3.7.1",
29
- "@react-aria/utils": "^3.19.0",
30
- "@react-aria/visually-hidden": "^3.8.3",
31
- "@react-stately/overlays": "^3.6.1",
32
- "@react-types/button": "^3.7.4",
33
- "@react-types/overlays": "^3.8.1",
34
- "@react-types/shared": "^3.19.0",
25
+ "@react-aria/focus": "^3.14.2",
26
+ "@react-aria/i18n": "^3.8.3",
27
+ "@react-aria/interactions": "^3.19.0",
28
+ "@react-aria/ssr": "^3.8.0",
29
+ "@react-aria/utils": "^3.21.0",
30
+ "@react-aria/visually-hidden": "^3.8.5",
31
+ "@react-stately/overlays": "^3.6.3",
32
+ "@react-types/button": "^3.9.0",
33
+ "@react-types/overlays": "^3.8.3",
34
+ "@react-types/shared": "^3.21.0",
35
35
  "@swc/helpers": "^0.5.0"
36
36
  },
37
37
  "peerDependencies": {
@@ -41,5 +41,5 @@
41
41
  "publishConfig": {
42
42
  "access": "public"
43
43
  },
44
- "gitHead": "d4dfe4bb842a914f10045ee63fc6b92f034c5b30"
44
+ "gitHead": "4122e44d1991c90507d630d35ed297f89db435d3"
45
45
  }
package/src/Overlay.tsx CHANGED
@@ -10,6 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {ClearPressResponder} from '@react-aria/interactions';
13
14
  import {FocusScope} from '@react-aria/focus';
14
15
  import React, {ReactNode, useContext, useMemo, useState} from 'react';
15
16
  import ReactDOM from 'react-dom';
@@ -53,23 +54,23 @@ export function Overlay(props: OverlayProps) {
53
54
  return null;
54
55
  }
55
56
 
56
- let contents;
57
+ let contents = props.children;
57
58
  if (!props.disableFocusManagement) {
58
59
  contents = (
59
- <OverlayContext.Provider value={contextValue}>
60
- <FocusScope restoreFocus contain={contain && !isExiting}>
61
- {props.children}
62
- </FocusScope>
63
- </OverlayContext.Provider>
64
- );
65
- } else {
66
- contents = (
67
- <OverlayContext.Provider value={contextValue}>
68
- {props.children}
69
- </OverlayContext.Provider>
60
+ <FocusScope restoreFocus contain={contain && !isExiting}>
61
+ {contents}
62
+ </FocusScope>
70
63
  );
71
64
  }
72
65
 
66
+ contents = (
67
+ <OverlayContext.Provider value={contextValue}>
68
+ <ClearPressResponder>
69
+ {contents}
70
+ </ClearPressResponder>
71
+ </OverlayContext.Provider>
72
+ );
73
+
73
74
  return ReactDOM.createPortal(contents, portalContainer);
74
75
  }
75
76
 
@@ -140,19 +140,26 @@ function getDelta(
140
140
  axis: Axis,
141
141
  offset: number,
142
142
  size: number,
143
+ // The dimensions of the boundary element that the popover is
144
+ // positioned within (most of the time this is the <body>).
145
+ boundaryDimensions: Dimensions,
146
+ // The dimensions of the containing block element that the popover is
147
+ // positioned relative to (e.g. parent with position: relative).
148
+ // Usually this is the same as the boundary element, but if the popover
149
+ // is portaled somewhere other than the body and has an ancestor with
150
+ // position: relative/absolute, it will be different.
143
151
  containerDimensions: Dimensions,
144
152
  padding: number
145
153
  ) {
146
154
  let containerScroll = containerDimensions.scroll[axis];
147
- let containerHeight = containerDimensions[AXIS_SIZE[axis]];
148
-
155
+ let boundaryHeight = boundaryDimensions[AXIS_SIZE[axis]];
149
156
  let startEdgeOffset = offset - padding - containerScroll;
150
157
  let endEdgeOffset = offset + padding - containerScroll + size;
151
158
 
152
159
  if (startEdgeOffset < 0) {
153
160
  return -startEdgeOffset;
154
- } else if (endEdgeOffset > containerHeight) {
155
- return Math.max(containerHeight - endEdgeOffset, -startEdgeOffset);
161
+ } else if (endEdgeOffset > boundaryHeight) {
162
+ return Math.max(boundaryHeight - endEdgeOffset, -startEdgeOffset);
156
163
  } else {
157
164
  return 0;
158
165
  }
@@ -287,6 +294,7 @@ export function calculatePositionInternal(
287
294
  padding: number,
288
295
  flip: boolean,
289
296
  boundaryDimensions: Dimensions,
297
+ containerDimensions: Dimensions,
290
298
  containerOffsetWithBoundary: Offset,
291
299
  offset: number,
292
300
  crossOffset: number,
@@ -329,7 +337,7 @@ export function calculatePositionInternal(
329
337
  }
330
338
  }
331
339
 
332
- let delta = getDelta(crossAxis, position[crossAxis], overlaySize[crossSize], boundaryDimensions, padding);
340
+ let delta = getDelta(crossAxis, position[crossAxis], overlaySize[crossSize], boundaryDimensions, containerDimensions, padding);
333
341
  position[crossAxis] += delta;
334
342
 
335
343
  let maxHeight = getMaxHeight(
@@ -348,7 +356,7 @@ export function calculatePositionInternal(
348
356
  overlaySize.height = Math.min(overlaySize.height, maxHeight);
349
357
 
350
358
  position = computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, normalizedOffset, crossOffset, containerOffsetWithBoundary, isContainerPositioned, arrowSize, arrowBoundaryOffset);
351
- delta = getDelta(crossAxis, position[crossAxis], overlaySize[crossSize], boundaryDimensions, padding);
359
+ delta = getDelta(crossAxis, position[crossAxis], overlaySize[crossSize], boundaryDimensions, containerDimensions, padding);
352
360
  position[crossAxis] += delta;
353
361
 
354
362
  let arrowPosition: Position = {};
@@ -416,6 +424,7 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
416
424
 
417
425
  let scrollSize = getScroll(scrollNode);
418
426
  let boundaryDimensions = getContainerDimensions(boundaryElement);
427
+ let containerDimensions = getContainerDimensions(container);
419
428
  let containerOffsetWithBoundary: Offset = boundaryElement.tagName === 'BODY' ? getOffset(container) : getPosition(container, boundaryElement);
420
429
 
421
430
  return calculatePositionInternal(
@@ -427,6 +436,7 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
427
436
  padding,
428
437
  shouldFlip,
429
438
  boundaryDimensions,
439
+ containerDimensions,
430
440
  containerOffsetWithBoundary,
431
441
  offset,
432
442
  crossOffset,
package/src/useOverlay.ts CHANGED
@@ -125,10 +125,16 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element>): Ov
125
125
  let {focusWithinProps} = useFocusWithin({
126
126
  isDisabled: !shouldCloseOnBlur,
127
127
  onBlurWithin: (e) => {
128
+ // Do not close if relatedTarget is null, which means focus is lost to the body.
129
+ // That can happen when switching tabs, or due to a VoiceOver/Chrome bug with Control+Option+Arrow navigation.
130
+ // Clicking on the body to close the overlay should already be handled by useInteractOutside.
131
+ // https://github.com/adobe/react-spectrum/issues/4130
132
+ // https://github.com/adobe/react-spectrum/issues/4922
133
+ //
128
134
  // If focus is moving into a child focus scope (e.g. menu inside a dialog),
129
135
  // do not close the outer overlay. At this point, the active scope should
130
136
  // still be the outer overlay, since blur events run before focus.
131
- if (e.relatedTarget && isElementInChildOfActiveScope(e.relatedTarget)) {
137
+ if (!e.relatedTarget || isElementInChildOfActiveScope(e.relatedTarget)) {
132
138
  return;
133
139
  }
134
140