@react-aria/overlays 3.22.0 → 3.23.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.
Files changed (90) hide show
  1. package/dist/DismissButton.main.js +2 -2
  2. package/dist/DismissButton.mjs +3 -3
  3. package/dist/DismissButton.module.js +2 -2
  4. package/dist/Overlay.mjs +1 -1
  5. package/dist/PortalProvider.mjs +1 -1
  6. package/dist/ar-AE.mjs +1 -1
  7. package/dist/ariaHideOutside.main.js +7 -7
  8. package/dist/ariaHideOutside.mjs +8 -8
  9. package/dist/ariaHideOutside.module.js +7 -7
  10. package/dist/bg-BG.mjs +1 -1
  11. package/dist/calculatePosition.main.js +40 -40
  12. package/dist/calculatePosition.main.js.map +1 -1
  13. package/dist/calculatePosition.mjs +41 -41
  14. package/dist/calculatePosition.module.js +40 -40
  15. package/dist/calculatePosition.module.js.map +1 -1
  16. package/dist/cs-CZ.mjs +1 -1
  17. package/dist/da-DK.mjs +1 -1
  18. package/dist/de-DE.mjs +1 -1
  19. package/dist/el-GR.mjs +1 -1
  20. package/dist/en-US.mjs +1 -1
  21. package/dist/es-ES.mjs +1 -1
  22. package/dist/et-EE.mjs +1 -1
  23. package/dist/fi-FI.mjs +1 -1
  24. package/dist/fr-FR.mjs +1 -1
  25. package/dist/he-IL.mjs +1 -1
  26. package/dist/hr-HR.mjs +1 -1
  27. package/dist/hu-HU.mjs +1 -1
  28. package/dist/intlStrings.mjs +1 -1
  29. package/dist/it-IT.mjs +1 -1
  30. package/dist/ja-JP.mjs +1 -1
  31. package/dist/ko-KR.mjs +1 -1
  32. package/dist/lt-LT.mjs +1 -1
  33. package/dist/lv-LV.mjs +1 -1
  34. package/dist/nb-NO.mjs +1 -1
  35. package/dist/nl-NL.mjs +1 -1
  36. package/dist/pl-PL.mjs +1 -1
  37. package/dist/pt-BR.mjs +1 -1
  38. package/dist/pt-PT.mjs +1 -1
  39. package/dist/ro-RO.mjs +1 -1
  40. package/dist/ru-RU.mjs +1 -1
  41. package/dist/sk-SK.mjs +1 -1
  42. package/dist/sl-SI.mjs +1 -1
  43. package/dist/sr-SP.mjs +1 -1
  44. package/dist/sv-SE.mjs +1 -1
  45. package/dist/tr-TR.mjs +1 -1
  46. package/dist/types.d.ts +10 -10
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/uk-UA.mjs +1 -1
  49. package/dist/useCloseOnScroll.main.js +6 -2
  50. package/dist/useCloseOnScroll.main.js.map +1 -1
  51. package/dist/useCloseOnScroll.mjs +7 -3
  52. package/dist/useCloseOnScroll.module.js +6 -2
  53. package/dist/useCloseOnScroll.module.js.map +1 -1
  54. package/dist/useModal.main.js +4 -4
  55. package/dist/useModal.mjs +5 -5
  56. package/dist/useModal.module.js +4 -4
  57. package/dist/useModalOverlay.main.js.map +1 -1
  58. package/dist/useModalOverlay.mjs +1 -1
  59. package/dist/useModalOverlay.module.js.map +1 -1
  60. package/dist/useOverlay.main.js +1 -1
  61. package/dist/useOverlay.main.js.map +1 -1
  62. package/dist/useOverlay.mjs +2 -2
  63. package/dist/useOverlay.module.js +1 -1
  64. package/dist/useOverlay.module.js.map +1 -1
  65. package/dist/useOverlayPosition.main.js +62 -19
  66. package/dist/useOverlayPosition.main.js.map +1 -1
  67. package/dist/useOverlayPosition.mjs +63 -20
  68. package/dist/useOverlayPosition.module.js +62 -19
  69. package/dist/useOverlayPosition.module.js.map +1 -1
  70. package/dist/useOverlayTrigger.main.js +5 -5
  71. package/dist/useOverlayTrigger.main.js.map +1 -1
  72. package/dist/useOverlayTrigger.mjs +6 -6
  73. package/dist/useOverlayTrigger.module.js +5 -5
  74. package/dist/useOverlayTrigger.module.js.map +1 -1
  75. package/dist/usePopover.main.js.map +1 -1
  76. package/dist/usePopover.mjs +1 -1
  77. package/dist/usePopover.module.js.map +1 -1
  78. package/dist/usePreventScroll.main.js +22 -22
  79. package/dist/usePreventScroll.mjs +23 -23
  80. package/dist/usePreventScroll.module.js +22 -22
  81. package/dist/zh-CN.mjs +1 -1
  82. package/dist/zh-TW.mjs +1 -1
  83. package/package.json +14 -14
  84. package/src/calculatePosition.ts +3 -3
  85. package/src/useCloseOnScroll.ts +10 -2
  86. package/src/useModalOverlay.ts +3 -3
  87. package/src/useOverlay.ts +4 -4
  88. package/src/useOverlayPosition.ts +58 -11
  89. package/src/useOverlayTrigger.ts +3 -3
  90. package/src/usePopover.ts +3 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/overlays",
3
- "version": "3.22.0",
3
+ "version": "3.23.0",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -22,24 +22,24 @@
22
22
  "url": "https://github.com/adobe/react-spectrum"
23
23
  },
24
24
  "dependencies": {
25
- "@react-aria/focus": "^3.17.0",
26
- "@react-aria/i18n": "^3.11.0",
27
- "@react-aria/interactions": "^3.21.2",
28
- "@react-aria/ssr": "^3.9.3",
29
- "@react-aria/utils": "^3.24.0",
30
- "@react-aria/visually-hidden": "^3.8.11",
31
- "@react-stately/overlays": "^3.6.6",
32
- "@react-types/button": "^3.9.3",
33
- "@react-types/overlays": "^3.8.6",
34
- "@react-types/shared": "^3.23.0",
25
+ "@react-aria/focus": "^3.18.0",
26
+ "@react-aria/i18n": "^3.12.0",
27
+ "@react-aria/interactions": "^3.22.0",
28
+ "@react-aria/ssr": "^3.9.5",
29
+ "@react-aria/utils": "^3.25.0",
30
+ "@react-aria/visually-hidden": "^3.8.13",
31
+ "@react-stately/overlays": "^3.6.8",
32
+ "@react-types/button": "^3.9.5",
33
+ "@react-types/overlays": "^3.8.8",
34
+ "@react-types/shared": "^3.24.0",
35
35
  "@swc/helpers": "^0.5.0"
36
36
  },
37
37
  "peerDependencies": {
38
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
39
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
38
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
39
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
43
43
  },
44
- "gitHead": "f645f29edc1322153fd60af4640cbcab1d992dbd"
44
+ "gitHead": "86d80e3216bc32e75108831cf3a5a720bc849206"
45
45
  }
@@ -417,15 +417,15 @@ export function calculatePositionInternal(
417
417
 
418
418
  // All values are transformed so that 0 is at the top/left of the overlay depending on the orientation
419
419
  // Prefer the arrow being in the center of the trigger/overlay anchor element
420
- let preferredArrowPosition = childOffset[crossAxis] + .5 * childOffset[crossSize] - overlaySize[crossAxis];
420
+ let preferredArrowPosition = childOffset[crossAxis] + .5 * childOffset[crossSize] - position[crossAxis];
421
421
 
422
422
  // Min/Max position limits for the arrow with respect to the overlay
423
423
  const arrowMinPosition = arrowSize / 2 + arrowBoundaryOffset;
424
424
  const arrowMaxPosition = overlaySize[crossSize] - (arrowSize / 2) - arrowBoundaryOffset;
425
425
 
426
426
  // Min/Max position limits for the arrow with respect to the trigger/overlay anchor element
427
- const arrowOverlappingChildMinEdge = childOffset[crossAxis] - overlaySize[crossAxis] + (arrowSize / 2);
428
- const arrowOverlappingChildMaxEdge = childOffset[crossAxis] + childOffset[crossSize] - overlaySize[crossAxis] - (arrowSize / 2);
427
+ const arrowOverlappingChildMinEdge = childOffset[crossAxis] - position[crossAxis] + (arrowSize / 2);
428
+ const arrowOverlappingChildMaxEdge = childOffset[crossAxis] + childOffset[crossSize] - position[crossAxis] - (arrowSize / 2);
429
429
 
430
430
  // Clamp the arrow positioning so that it always is within the bounds of the anchor and the overlay
431
431
  const arrowPositionOverlappingChild = clamp(preferredArrowPosition, arrowOverlappingChildMinEdge, arrowOverlappingChildMaxEdge);
@@ -10,7 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {RefObject, useEffect} from 'react';
13
+ import {RefObject} from '@react-types/shared';
14
+ import {useEffect} from 'react';
14
15
 
15
16
  // This behavior moved from useOverlayTrigger to useOverlayPosition.
16
17
  // For backward compatibility, where useOverlayTrigger handled hiding the popover on close,
@@ -20,7 +21,7 @@ import {RefObject, useEffect} from 'react';
20
21
  export const onCloseMap: WeakMap<Element, () => void> = new WeakMap();
21
22
 
22
23
  interface CloseOnScrollOptions {
23
- triggerRef: RefObject<Element>,
24
+ triggerRef: RefObject<Element | null>,
24
25
  isOpen?: boolean,
25
26
  onClose?: () => void
26
27
  }
@@ -42,6 +43,13 @@ export function useCloseOnScroll(opts: CloseOnScrollOptions) {
42
43
  return;
43
44
  }
44
45
 
46
+ // Ignore scroll events on any input or textarea as the cursor position can cause it to scroll
47
+ // such as in a combobox. Clicking the dropdown button places focus on the input, and if the
48
+ // text inside the input extends beyond the 'end', then it will scroll so the cursor is visible at the end.
49
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
50
+ return;
51
+ }
52
+
45
53
  let onCloseHandler = onClose || onCloseMap.get(triggerRef.current);
46
54
  if (onCloseHandler) {
47
55
  onCloseHandler();
@@ -12,10 +12,10 @@
12
12
 
13
13
  import {ariaHideOutside} from './ariaHideOutside';
14
14
  import {AriaOverlayProps, useOverlay} from './useOverlay';
15
- import {DOMAttributes} from '@react-types/shared';
15
+ import {DOMAttributes, RefObject} from '@react-types/shared';
16
16
  import {mergeProps} from '@react-aria/utils';
17
17
  import {OverlayTriggerState} from '@react-stately/overlays';
18
- import {RefObject, useEffect} from 'react';
18
+ import {useEffect} from 'react';
19
19
  import {useOverlayFocusContain} from './Overlay';
20
20
  import {usePreventScroll} from './usePreventScroll';
21
21
 
@@ -43,7 +43,7 @@ export interface ModalOverlayAria {
43
43
  * Provides the behavior and accessibility implementation for a modal component.
44
44
  * A modal is an overlay element which blocks interaction with elements outside it.
45
45
  */
46
- export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTriggerState, ref: RefObject<HTMLElement>): ModalOverlayAria {
46
+ export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTriggerState, ref: RefObject<HTMLElement | null>): ModalOverlayAria {
47
47
  let {overlayProps, underlayProps} = useOverlay({
48
48
  ...props,
49
49
  isOpen: state.isOpen,
package/src/useOverlay.ts CHANGED
@@ -10,9 +10,9 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {DOMAttributes} from '@react-types/shared';
13
+ import {DOMAttributes, RefObject} from '@react-types/shared';
14
14
  import {isElementInChildOfActiveScope} from '@react-aria/focus';
15
- import {RefObject, useEffect} from 'react';
15
+ import {useEffect} from 'react';
16
16
  import {useFocusWithin, useInteractOutside} from '@react-aria/interactions';
17
17
 
18
18
  export interface AriaOverlayProps {
@@ -53,14 +53,14 @@ export interface OverlayAria {
53
53
  underlayProps: DOMAttributes
54
54
  }
55
55
 
56
- const visibleOverlays: RefObject<Element>[] = [];
56
+ const visibleOverlays: RefObject<Element | null>[] = [];
57
57
 
58
58
  /**
59
59
  * Provides the behavior for overlays such as dialogs, popovers, and menus.
60
60
  * Hides the overlay when the user interacts outside it, when the Escape key is pressed,
61
61
  * or optionally, on blur. Only the top-most overlay will close at once.
62
62
  */
63
- export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element>): OverlayAria {
63
+ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element | null>): OverlayAria {
64
64
  let {
65
65
  onClose,
66
66
  shouldCloseOnBlur,
@@ -11,9 +11,9 @@
11
11
  */
12
12
 
13
13
  import {calculatePosition, PositionResult} from './calculatePosition';
14
- import {DOMAttributes} from '@react-types/shared';
14
+ import {DOMAttributes, RefObject} from '@react-types/shared';
15
15
  import {Placement, PlacementAxis, PositionProps} from '@react-types/overlays';
16
- import {RefObject, useCallback, useEffect, useRef, useState} from 'react';
16
+ import {useCallback, useEffect, useRef, useState} from 'react';
17
17
  import {useCloseOnScroll} from './useCloseOnScroll';
18
18
  import {useLayoutEffect, useResizeObserver} from '@react-aria/utils';
19
19
  import {useLocale} from '@react-aria/i18n';
@@ -32,16 +32,16 @@ export interface AriaPositionProps extends PositionProps {
32
32
  /**
33
33
  * The ref for the element which the overlay positions itself with respect to.
34
34
  */
35
- targetRef: RefObject<Element>,
35
+ targetRef: RefObject<Element | null>,
36
36
  /**
37
37
  * The ref for the overlay element.
38
38
  */
39
- overlayRef: RefObject<Element>,
39
+ overlayRef: RefObject<Element | null>,
40
40
  /**
41
41
  * A ref for the scrollable region within the overlay.
42
42
  * @default overlayRef
43
43
  */
44
- scrollRef?: RefObject<Element>,
44
+ scrollRef?: RefObject<Element | null>,
45
45
  /**
46
46
  * Whether the overlay should update its position automatically.
47
47
  * @default true
@@ -72,6 +72,11 @@ export interface PositionAria {
72
72
  updatePosition(): void
73
73
  }
74
74
 
75
+ interface ScrollAnchor {
76
+ type: 'top' | 'bottom',
77
+ offset: number
78
+ }
79
+
75
80
  // @ts-ignore
76
81
  let visualViewport = typeof document !== 'undefined' && window.visualViewport;
77
82
 
@@ -135,7 +140,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
135
140
  }, [isOpen]);
136
141
 
137
142
  let updatePosition = useCallback(() => {
138
- if (shouldUpdatePosition === false || !isOpen || !overlayRef.current || !targetRef.current || !scrollRef.current || !boundaryElement) {
143
+ if (shouldUpdatePosition === false || !isOpen || !overlayRef.current || !targetRef.current || !boundaryElement) {
139
144
  return;
140
145
  }
141
146
 
@@ -143,17 +148,40 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
143
148
  return;
144
149
  }
145
150
 
151
+ // Determine a scroll anchor based on the focused element.
152
+ // This stores the offset of the anchor element from the scroll container
153
+ // so it can be restored after repositioning. This way if the overlay height
154
+ // changes, the focused element appears to stay in the same position.
155
+ let anchor: ScrollAnchor | null = null;
156
+ if (scrollRef.current && scrollRef.current.contains(document.activeElement)) {
157
+ let anchorRect = document.activeElement.getBoundingClientRect();
158
+ let scrollRect = scrollRef.current.getBoundingClientRect();
159
+ // Anchor from the top if the offset is in the top half of the scrollable element,
160
+ // otherwise anchor from the bottom.
161
+ anchor = {
162
+ type: 'top',
163
+ offset: anchorRect.top - scrollRect.top
164
+ };
165
+ if (anchor.offset > scrollRect.height / 2) {
166
+ anchor.type = 'bottom';
167
+ anchor.offset = anchorRect.bottom - scrollRect.bottom;
168
+ }
169
+ }
170
+
146
171
  // Always reset the overlay's previous max height if not defined by the user so that we can compensate for
147
172
  // RAC collections populating after a second render and properly set a correct max height + positioning when it populates.
173
+ let overlay = (overlayRef.current as HTMLElement);
148
174
  if (!maxHeight && overlayRef.current) {
149
- (overlayRef.current as HTMLElement).style.maxHeight = 'none';
175
+ overlay.style.top = '0px';
176
+ overlay.style.bottom = '';
177
+ overlay.style.maxHeight = (window.visualViewport?.height ?? window.innerHeight) + 'px';
150
178
  }
151
179
 
152
180
  let position = calculatePosition({
153
181
  placement: translateRTL(placement, direction),
154
182
  overlayNode: overlayRef.current,
155
183
  targetNode: targetRef.current,
156
- scrollNode: scrollRef.current,
184
+ scrollNode: scrollRef.current || overlayRef.current,
157
185
  padding: containerPadding,
158
186
  shouldFlip,
159
187
  boundaryElement,
@@ -166,8 +194,21 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
166
194
 
167
195
  // Modify overlay styles directly so positioning happens immediately without the need of a second render
168
196
  // This is so we don't have to delay autoFocus scrolling or delay applying preventScroll for popovers
169
- Object.keys(position.position).forEach(key => (overlayRef.current as HTMLElement).style[key] = position.position[key] + 'px');
170
- (overlayRef.current as HTMLElement).style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' : undefined;
197
+ overlay.style.top = '';
198
+ overlay.style.bottom = '';
199
+ overlay.style.left = '';
200
+ overlay.style.right = '';
201
+
202
+ Object.keys(position.position).forEach(key => overlay.style[key] = position.position[key] + 'px');
203
+ overlay.style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' : undefined;
204
+
205
+ // Restore scroll position relative to anchor element.
206
+ if (anchor) {
207
+ let anchorRect = document.activeElement.getBoundingClientRect();
208
+ let scrollRect = scrollRef.current.getBoundingClientRect();
209
+ let newOffset = anchorRect[anchor.type] - scrollRect[anchor.type];
210
+ scrollRef.current.scrollTop += newOffset - anchor.offset;
211
+ }
171
212
 
172
213
  // Trigger a set state for a second render anyway for arrow positioning
173
214
  setPosition(position);
@@ -187,6 +228,12 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
187
228
  onResize: updatePosition
188
229
  });
189
230
 
231
+ // Update position when the target changes size (might need to flip).
232
+ useResizeObserver({
233
+ ref: targetRef,
234
+ onResize: updatePosition
235
+ });
236
+
190
237
  // Reposition the overlay and do not close on scroll while the visual viewport is resizing.
191
238
  // This will ensure that overlays adjust their positioning when the iOS virtual keyboard appears.
192
239
  let isResizing = useRef(false);
@@ -239,7 +286,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
239
286
  position: 'absolute',
240
287
  zIndex: 100000, // should match the z-index in ModalTrigger
241
288
  ...position.position,
242
- maxHeight: position.maxHeight
289
+ maxHeight: position.maxHeight ?? '100vh'
243
290
  }
244
291
  },
245
292
  placement: position.placement,
@@ -11,10 +11,10 @@
11
11
  */
12
12
 
13
13
  import {AriaButtonProps} from '@react-types/button';
14
- import {DOMProps} from '@react-types/shared';
14
+ import {DOMProps, RefObject} from '@react-types/shared';
15
15
  import {onCloseMap} from './useCloseOnScroll';
16
16
  import {OverlayTriggerState} from '@react-stately/overlays';
17
- import {RefObject, useEffect} from 'react';
17
+ import {useEffect} from 'react';
18
18
  import {useId} from '@react-aria/utils';
19
19
 
20
20
  export interface OverlayTriggerProps {
@@ -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?: RefObject<Element>): OverlayTriggerAria {
37
+ export function useOverlayTrigger(props: OverlayTriggerProps, state: OverlayTriggerState, ref?: RefObject<Element | null>): OverlayTriggerAria {
38
38
  let {type} = props;
39
39
  let {isOpen} = state;
40
40
 
package/src/usePopover.ts CHANGED
@@ -12,11 +12,10 @@
12
12
 
13
13
  import {ariaHideOutside} from './ariaHideOutside';
14
14
  import {AriaPositionProps, useOverlayPosition} from './useOverlayPosition';
15
- import {DOMAttributes} from '@react-types/shared';
15
+ import {DOMAttributes, RefObject} 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} from 'react';
20
19
  import {useOverlay} from './useOverlay';
21
20
  import {usePreventScroll} from './usePreventScroll';
22
21
 
@@ -24,11 +23,11 @@ export interface AriaPopoverProps extends Omit<AriaPositionProps, 'isOpen' | 'on
24
23
  /**
25
24
  * The ref for the element which the popover positions itself with respect to.
26
25
  */
27
- triggerRef: RefObject<Element>,
26
+ triggerRef: RefObject<Element | null>,
28
27
  /**
29
28
  * The ref for the popover element.
30
29
  */
31
- popoverRef: RefObject<Element>,
30
+ popoverRef: RefObject<Element | null>,
32
31
  /**
33
32
  * Whether the popover is non-modal, i.e. elements outside the popover may be
34
33
  * interacted with by assistive technologies.