@react-aria/overlays
Advanced tools
Comparing version 3.5.0 to 3.6.0
165
dist/main.js
@@ -32,2 +32,3 @@ var { | ||
useEffect, | ||
useRef, | ||
useState, | ||
@@ -77,4 +78,6 @@ useLayoutEffect, | ||
}; | ||
const $c13020473a0fe27b1a1c4797269d1704$var$PARSED_PLACEMENT_CACHE = {}; | ||
const $c13020473a0fe27b1a1c4797269d1704$var$PARSED_PLACEMENT_CACHE = {}; // @ts-ignore | ||
let $c13020473a0fe27b1a1c4797269d1704$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
function $c13020473a0fe27b1a1c4797269d1704$var$getContainerDimensions(containerNode) { | ||
@@ -88,4 +91,6 @@ let width = 0, | ||
if (containerNode.tagName === 'BODY') { | ||
width = document.documentElement.clientWidth; | ||
height = document.documentElement.clientHeight; | ||
var _visualViewport$width, _visualViewport$heigh; | ||
width = (_visualViewport$width = $c13020473a0fe27b1a1c4797269d1704$var$visualViewport == null ? void 0 : $c13020473a0fe27b1a1c4797269d1704$var$visualViewport.width) != null ? _visualViewport$width : document.documentElement.clientWidth; | ||
height = (_visualViewport$heigh = $c13020473a0fe27b1a1c4797269d1704$var$visualViewport == null ? void 0 : $c13020473a0fe27b1a1c4797269d1704$var$visualViewport.height) != null ? _visualViewport$heigh : document.documentElement.clientHeight; | ||
scroll.top = _domHelpersQueryScrollTop(_domHelpersOwnerDocument(containerNode).documentElement) || _domHelpersQueryScrollTop(containerNode); | ||
@@ -371,2 +376,4 @@ scroll.left = _domHelpersQueryScrollLeft(_domHelpersOwnerDocument(containerNode).documentElement) || _domHelpersQueryScrollLeft(containerNode); | ||
// @ts-ignore | ||
let $adfbd034e9bc71c1$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
/** | ||
@@ -376,2 +383,3 @@ * Handles positioning overlays like popovers and menus relative to a trigger | ||
*/ | ||
function useOverlayPosition(props) { | ||
@@ -423,3 +431,28 @@ let { | ||
$adfbd034e9bc71c1$var$useResize(updatePosition); // When scrolling a parent scrollable region of the trigger (other than the body), | ||
$adfbd034e9bc71c1$var$useResize(updatePosition); // Reposition the overlay and do not close on scroll while the visual viewport is resizing. | ||
// This will ensure that overlays adjust their positioning when the iOS virtual keyboard appears. | ||
let isResizing = useRef(false); | ||
useEffect(() => { | ||
let timeout; | ||
let onResize = () => { | ||
isResizing.current = true; | ||
clearTimeout(timeout); | ||
timeout = setTimeout(() => { | ||
isResizing.current = false; | ||
}, 500); | ||
updatePosition(); | ||
}; | ||
$adfbd034e9bc71c1$var$visualViewport == null ? void 0 : $adfbd034e9bc71c1$var$visualViewport.addEventListener('resize', onResize); | ||
return () => { | ||
$adfbd034e9bc71c1$var$visualViewport == null ? void 0 : $adfbd034e9bc71c1$var$visualViewport.removeEventListener('resize', onResize); | ||
}; | ||
}, [updatePosition]); | ||
let close = useCallback(() => { | ||
if (!isResizing.current) { | ||
onClose(); | ||
} | ||
}, [onClose, isResizing]); // When scrolling a parent scrollable region of the trigger (other than the body), | ||
// we hide the popover. Otherwise, its position would be incorrect. | ||
@@ -430,3 +463,3 @@ | ||
isOpen, | ||
onClose | ||
onClose: onClose ? close : undefined | ||
}); | ||
@@ -594,3 +627,5 @@ return { | ||
const $a21edfc55f5392c9a20c9978f0e487$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
const $a21edfc55f5392c9a20c9978f0e487$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; // HTML input types that do not cause the software keyboard to appear. | ||
const $a21edfc55f5392c9a20c9978f0e487$var$nonTextInputTypes = new Set(['checkbox', 'radio', 'range', 'color', 'file', 'image', 'button', 'submit', 'reset']); | ||
/** | ||
@@ -697,3 +732,3 @@ * Prevents scrolling on the document body on mount, and | ||
if (target.tagName === 'INPUT') { | ||
if (target instanceof HTMLInputElement && !$a21edfc55f5392c9a20c9978f0e487$var$nonTextInputTypes.has(target.type)) { | ||
e.preventDefault(); // Apply a transform to trick Safari into thinking the input is at the top of the page | ||
@@ -714,3 +749,3 @@ // so it doesn't try to scroll it into view. When tapping on an input, this needs to | ||
if (target.tagName === 'INPUT') { | ||
if (target instanceof HTMLInputElement && !$a21edfc55f5392c9a20c9978f0e487$var$nonTextInputTypes.has(target.type)) { | ||
// Transform also needs to be applied in the focus event in cases where focus moves | ||
@@ -915,3 +950,3 @@ // other than tapping on an input directly, e.g. the next/previous buttons in the | ||
*/ | ||
function useModal() { | ||
function useModal(options) { | ||
// Add aria-hidden to all parent providers on mount, and restore on unmount. | ||
@@ -925,3 +960,3 @@ let context = useContext($c5f9596976ab8bd94c5879001549a3e$var$Context); | ||
useEffect(() => { | ||
if (!context || !context.parent) { | ||
if ((options == null ? void 0 : options.isDisabled) || !context || !context.parent) { | ||
return; | ||
@@ -938,6 +973,6 @@ } // The immediate context is from the provider containing this modal, so we only | ||
}; | ||
}, [context, context.parent]); | ||
}, [context, context.parent, options == null ? void 0 : options.isDisabled]); | ||
return { | ||
modalProps: { | ||
'data-ismodal': true | ||
'data-ismodal': !(options == null ? void 0 : options.isDisabled) | ||
} | ||
@@ -1121,2 +1156,108 @@ }; | ||
exports.DismissButton = DismissButton; | ||
// subtracted from when showing it again. When it reaches zero, aria-hidden is removed. | ||
let $a683bb0d63d5b6f7885c12dcc6f96807$var$refCountMap = new WeakMap(); | ||
/** | ||
* Hides all elements in the DOM outside the given targets from screen readers using aria-hidden, | ||
* and returns a function to revert these changes. In addition, changes to the DOM are watched | ||
* and new elements outside the targets are automatically hidden. | ||
* @param targets - The elements that should remain visible. | ||
* @param root - Nothing will be hidden above this element. | ||
* @returns - A function to restore all hidden elements. | ||
*/ | ||
function ariaHideOutside(targets, root) { | ||
if (root === void 0) { | ||
root = document.body; | ||
} | ||
let visibleNodes = new Set(targets); | ||
let hiddenNodes = new Set(); | ||
let walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, { | ||
acceptNode(node) { | ||
// If this node is a live announcer, add it to the set of nodes to keep visible. | ||
if (node instanceof HTMLElement && node.dataset.liveAnnouncer === 'true') { | ||
visibleNodes.add(node); | ||
} // Skip this node and its children if it is one of the target nodes, or a live announcer. | ||
// Also skip children of already hidden nodes, as aria-hidden is recursive. | ||
if (visibleNodes.has(node) || hiddenNodes.has(node.parentElement)) { | ||
return NodeFilter.FILTER_REJECT; | ||
} // Skip this node but continue to children if one of the targets is inside the node. | ||
if (targets.some(target => node.contains(target))) { | ||
return NodeFilter.FILTER_SKIP; | ||
} | ||
return NodeFilter.FILTER_ACCEPT; | ||
} | ||
}); | ||
let hide = node => { | ||
var _refCountMap$get; | ||
let refCount = (_refCountMap$get = $a683bb0d63d5b6f7885c12dcc6f96807$var$refCountMap.get(node)) != null ? _refCountMap$get : 0; // If already aria-hidden, and the ref count is zero, then this element | ||
// was already hidden and there's nothing for us to do. | ||
if (node.getAttribute('aria-hidden') === 'true' && refCount === 0) { | ||
return; | ||
} | ||
if (refCount === 0) { | ||
node.setAttribute('aria-hidden', 'true'); | ||
} | ||
hiddenNodes.add(node); | ||
$a683bb0d63d5b6f7885c12dcc6f96807$var$refCountMap.set(node, refCount + 1); | ||
}; | ||
let node = walker.nextNode(); | ||
while (node != null) { | ||
hide(node); | ||
node = walker.nextNode(); | ||
} | ||
let observer = new MutationObserver(changes => { | ||
for (let change of changes) { | ||
if (change.type !== 'childList' || change.addedNodes.length === 0) { | ||
continue; | ||
} // If the parent element of the added nodes is not within one of the targets, | ||
// and not already inside a hidden node, hide all of the new children. | ||
if (![...visibleNodes, ...hiddenNodes].some(node => node.contains(change.target))) { | ||
for (let node of change.addedNodes) { | ||
if (node instanceof HTMLElement && node.dataset.liveAnnouncer === 'true') { | ||
visibleNodes.add(node); | ||
} else if (node instanceof Element) { | ||
hide(node); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
observer.observe(root, { | ||
childList: true, | ||
subtree: true | ||
}); | ||
return () => { | ||
observer.disconnect(); | ||
for (let node of hiddenNodes) { | ||
let count = $a683bb0d63d5b6f7885c12dcc6f96807$var$refCountMap.get(node); | ||
if (count === 1) { | ||
node.removeAttribute('aria-hidden'); | ||
$a683bb0d63d5b6f7885c12dcc6f96807$var$refCountMap.delete(node); | ||
} else { | ||
$a683bb0d63d5b6f7885c12dcc6f96807$var$refCountMap.set(node, count - 1); | ||
} | ||
} | ||
}; | ||
} | ||
exports.ariaHideOutside = ariaHideOutside; | ||
//# sourceMappingURL=main.js.map |
@@ -7,3 +7,3 @@ import { VisuallyHidden } from "@react-aria/visually-hidden"; | ||
import { useLocale, useMessageFormatter } from "@react-aria/i18n"; | ||
import _react, { useCallback, useEffect, useState, useLayoutEffect, useContext, useMemo } from "react"; | ||
import _react, { useCallback, useEffect, useRef, useState, useLayoutEffect, useContext, useMemo } from "react"; | ||
import _domHelpersOwnerDocument from "dom-helpers/ownerDocument"; | ||
@@ -36,4 +36,6 @@ import _domHelpersQueryScrollTop from "dom-helpers/query/scrollTop"; | ||
}; | ||
const $d45e305fb90d49e7c81f49bb4afe323b$var$PARSED_PLACEMENT_CACHE = {}; | ||
const $d45e305fb90d49e7c81f49bb4afe323b$var$PARSED_PLACEMENT_CACHE = {}; // @ts-ignore | ||
let $d45e305fb90d49e7c81f49bb4afe323b$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
function $d45e305fb90d49e7c81f49bb4afe323b$var$getContainerDimensions(containerNode) { | ||
@@ -47,4 +49,6 @@ let width = 0, | ||
if (containerNode.tagName === 'BODY') { | ||
width = document.documentElement.clientWidth; | ||
height = document.documentElement.clientHeight; | ||
var _visualViewport$width, _visualViewport$heigh; | ||
width = (_visualViewport$width = $d45e305fb90d49e7c81f49bb4afe323b$var$visualViewport == null ? void 0 : $d45e305fb90d49e7c81f49bb4afe323b$var$visualViewport.width) != null ? _visualViewport$width : document.documentElement.clientWidth; | ||
height = (_visualViewport$heigh = $d45e305fb90d49e7c81f49bb4afe323b$var$visualViewport == null ? void 0 : $d45e305fb90d49e7c81f49bb4afe323b$var$visualViewport.height) != null ? _visualViewport$heigh : document.documentElement.clientHeight; | ||
scroll.top = _domHelpersQueryScrollTop(_domHelpersOwnerDocument(containerNode).documentElement) || _domHelpersQueryScrollTop(containerNode); | ||
@@ -330,2 +334,4 @@ scroll.left = _domHelpersQueryScrollLeft(_domHelpersOwnerDocument(containerNode).documentElement) || _domHelpersQueryScrollLeft(containerNode); | ||
// @ts-ignore | ||
let $ae841ee9d3f76b31663cf0594adb0fc$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
/** | ||
@@ -335,2 +341,3 @@ * Handles positioning overlays like popovers and menus relative to a trigger | ||
*/ | ||
export function useOverlayPosition(props) { | ||
@@ -382,3 +389,28 @@ let { | ||
$ae841ee9d3f76b31663cf0594adb0fc$var$useResize(updatePosition); // When scrolling a parent scrollable region of the trigger (other than the body), | ||
$ae841ee9d3f76b31663cf0594adb0fc$var$useResize(updatePosition); // Reposition the overlay and do not close on scroll while the visual viewport is resizing. | ||
// This will ensure that overlays adjust their positioning when the iOS virtual keyboard appears. | ||
let isResizing = useRef(false); | ||
useEffect(() => { | ||
let timeout; | ||
let onResize = () => { | ||
isResizing.current = true; | ||
clearTimeout(timeout); | ||
timeout = setTimeout(() => { | ||
isResizing.current = false; | ||
}, 500); | ||
updatePosition(); | ||
}; | ||
$ae841ee9d3f76b31663cf0594adb0fc$var$visualViewport == null ? void 0 : $ae841ee9d3f76b31663cf0594adb0fc$var$visualViewport.addEventListener('resize', onResize); | ||
return () => { | ||
$ae841ee9d3f76b31663cf0594adb0fc$var$visualViewport == null ? void 0 : $ae841ee9d3f76b31663cf0594adb0fc$var$visualViewport.removeEventListener('resize', onResize); | ||
}; | ||
}, [updatePosition]); | ||
let close = useCallback(() => { | ||
if (!isResizing.current) { | ||
onClose(); | ||
} | ||
}, [onClose, isResizing]); // When scrolling a parent scrollable region of the trigger (other than the body), | ||
// we hide the popover. Otherwise, its position would be incorrect. | ||
@@ -389,3 +421,3 @@ | ||
isOpen, | ||
onClose | ||
onClose: onClose ? close : undefined | ||
}); | ||
@@ -547,3 +579,5 @@ return { | ||
const $ece0076f06e8a828c60ba0c94f22f89$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
const $ece0076f06e8a828c60ba0c94f22f89$var$visualViewport = typeof window !== 'undefined' && window.visualViewport; // HTML input types that do not cause the software keyboard to appear. | ||
const $ece0076f06e8a828c60ba0c94f22f89$var$nonTextInputTypes = new Set(['checkbox', 'radio', 'range', 'color', 'file', 'image', 'button', 'submit', 'reset']); | ||
/** | ||
@@ -647,3 +681,3 @@ * Prevents scrolling on the document body on mount, and | ||
if (target.tagName === 'INPUT') { | ||
if (target instanceof HTMLInputElement && !$ece0076f06e8a828c60ba0c94f22f89$var$nonTextInputTypes.has(target.type)) { | ||
e.preventDefault(); // Apply a transform to trick Safari into thinking the input is at the top of the page | ||
@@ -664,3 +698,3 @@ // so it doesn't try to scroll it into view. When tapping on an input, this needs to | ||
if (target.tagName === 'INPUT') { | ||
if (target instanceof HTMLInputElement && !$ece0076f06e8a828c60ba0c94f22f89$var$nonTextInputTypes.has(target.type)) { | ||
// Transform also needs to be applied in the focus event in cases where focus moves | ||
@@ -855,3 +889,3 @@ // other than tapping on an input directly, e.g. the next/previous buttons in the | ||
*/ | ||
export function useModal() { | ||
export function useModal(options) { | ||
// Add aria-hidden to all parent providers on mount, and restore on unmount. | ||
@@ -865,3 +899,3 @@ let context = useContext($b876e5ac9c98db373bf726bce3d604e$var$Context); | ||
useEffect(() => { | ||
if (!context || !context.parent) { | ||
if ((options == null ? void 0 : options.isDisabled) || !context || !context.parent) { | ||
return; | ||
@@ -878,6 +912,6 @@ } // The immediate context is from the provider containing this modal, so we only | ||
}; | ||
}, [context, context.parent]); | ||
}, [context, context.parent, options == null ? void 0 : options.isDisabled]); | ||
return { | ||
modalProps: { | ||
'data-ismodal': true | ||
'data-ismodal': !(options == null ? void 0 : options.isDisabled) | ||
} | ||
@@ -1057,2 +1091,106 @@ }; | ||
} | ||
// subtracted from when showing it again. When it reaches zero, aria-hidden is removed. | ||
let $d006cf2ec48f5151923b9435b3d$var$refCountMap = new WeakMap(); | ||
/** | ||
* Hides all elements in the DOM outside the given targets from screen readers using aria-hidden, | ||
* and returns a function to revert these changes. In addition, changes to the DOM are watched | ||
* and new elements outside the targets are automatically hidden. | ||
* @param targets - The elements that should remain visible. | ||
* @param root - Nothing will be hidden above this element. | ||
* @returns - A function to restore all hidden elements. | ||
*/ | ||
export function ariaHideOutside(targets, root) { | ||
if (root === void 0) { | ||
root = document.body; | ||
} | ||
let visibleNodes = new Set(targets); | ||
let hiddenNodes = new Set(); | ||
let walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, { | ||
acceptNode(node) { | ||
// If this node is a live announcer, add it to the set of nodes to keep visible. | ||
if (node instanceof HTMLElement && node.dataset.liveAnnouncer === 'true') { | ||
visibleNodes.add(node); | ||
} // Skip this node and its children if it is one of the target nodes, or a live announcer. | ||
// Also skip children of already hidden nodes, as aria-hidden is recursive. | ||
if (visibleNodes.has(node) || hiddenNodes.has(node.parentElement)) { | ||
return NodeFilter.FILTER_REJECT; | ||
} // Skip this node but continue to children if one of the targets is inside the node. | ||
if (targets.some(target => node.contains(target))) { | ||
return NodeFilter.FILTER_SKIP; | ||
} | ||
return NodeFilter.FILTER_ACCEPT; | ||
} | ||
}); | ||
let hide = node => { | ||
var _refCountMap$get; | ||
let refCount = (_refCountMap$get = $d006cf2ec48f5151923b9435b3d$var$refCountMap.get(node)) != null ? _refCountMap$get : 0; // If already aria-hidden, and the ref count is zero, then this element | ||
// was already hidden and there's nothing for us to do. | ||
if (node.getAttribute('aria-hidden') === 'true' && refCount === 0) { | ||
return; | ||
} | ||
if (refCount === 0) { | ||
node.setAttribute('aria-hidden', 'true'); | ||
} | ||
hiddenNodes.add(node); | ||
$d006cf2ec48f5151923b9435b3d$var$refCountMap.set(node, refCount + 1); | ||
}; | ||
let node = walker.nextNode(); | ||
while (node != null) { | ||
hide(node); | ||
node = walker.nextNode(); | ||
} | ||
let observer = new MutationObserver(changes => { | ||
for (let change of changes) { | ||
if (change.type !== 'childList' || change.addedNodes.length === 0) { | ||
continue; | ||
} // If the parent element of the added nodes is not within one of the targets, | ||
// and not already inside a hidden node, hide all of the new children. | ||
if (![...visibleNodes, ...hiddenNodes].some(node => node.contains(change.target))) { | ||
for (let node of change.addedNodes) { | ||
if (node instanceof HTMLElement && node.dataset.liveAnnouncer === 'true') { | ||
visibleNodes.add(node); | ||
} else if (node instanceof Element) { | ||
hide(node); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
observer.observe(root, { | ||
childList: true, | ||
subtree: true | ||
}); | ||
return () => { | ||
observer.disconnect(); | ||
for (let node of hiddenNodes) { | ||
let count = $d006cf2ec48f5151923b9435b3d$var$refCountMap.get(node); | ||
if (count === 1) { | ||
node.removeAttribute('aria-hidden'); | ||
$d006cf2ec48f5151923b9435b3d$var$refCountMap.delete(node); | ||
} else { | ||
$d006cf2ec48f5151923b9435b3d$var$refCountMap.set(node, count - 1); | ||
} | ||
} | ||
}; | ||
} | ||
//# sourceMappingURL=module.js.map |
@@ -151,2 +151,5 @@ import React, { HTMLAttributes, RefObject, AriaAttributes, ReactNode } from "react"; | ||
} | ||
interface ModalOptions { | ||
isDisabled?: boolean; | ||
} | ||
interface ModalAria { | ||
@@ -162,3 +165,3 @@ /** Props for the modal content element. */ | ||
*/ | ||
export function useModal(): ModalAria; | ||
export function useModal(options?: ModalOptions): ModalAria; | ||
interface DismissButtonProps { | ||
@@ -174,3 +177,12 @@ /** Called when the dismiss button is activated. */ | ||
export function DismissButton(props: DismissButtonProps): JSX.Element; | ||
/** | ||
* Hides all elements in the DOM outside the given targets from screen readers using aria-hidden, | ||
* and returns a function to revert these changes. In addition, changes to the DOM are watched | ||
* and new elements outside the targets are automatically hidden. | ||
* @param targets - The elements that should remain visible. | ||
* @param root - Nothing will be hidden above this element. | ||
* @returns - A function to restore all hidden elements. | ||
*/ | ||
export function ariaHideOutside(targets: HTMLElement[], root?: HTMLElement): () => void; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@react-aria/overlays", | ||
"version": "3.5.0", | ||
"version": "3.6.0", | ||
"description": "Spectrum UI components in React", | ||
@@ -21,9 +21,9 @@ "license": "Apache-2.0", | ||
"@babel/runtime": "^7.6.2", | ||
"@react-aria/i18n": "^3.1.3", | ||
"@react-aria/interactions": "^3.3.0", | ||
"@react-aria/utils": "^3.4.0", | ||
"@react-aria/i18n": "^3.2.0", | ||
"@react-aria/interactions": "^3.3.2", | ||
"@react-aria/utils": "^3.4.1", | ||
"@react-aria/visually-hidden": "^3.2.1", | ||
"@react-stately/overlays": "^3.1.1", | ||
"@react-types/button": "^3.2.1", | ||
"@react-types/overlays": "^3.3.0", | ||
"@react-types/button": "^3.3.0", | ||
"@react-types/overlays": "^3.4.0", | ||
"dom-helpers": "^3.3.1" | ||
@@ -38,3 +38,3 @@ }, | ||
}, | ||
"gitHead": "9f738a06ea4e256c8d975f00502b4b0bbabb8f65" | ||
"gitHead": "f5b429ee8615248f2e3c76754bad2ece83f1c444" | ||
} |
@@ -98,2 +98,5 @@ /* | ||
// @ts-ignore | ||
let visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
function getContainerDimensions(containerNode: Element): Dimensions { | ||
@@ -104,4 +107,4 @@ let width = 0, height = 0, top = 0, left = 0; | ||
if (containerNode.tagName === 'BODY') { | ||
width = document.documentElement.clientWidth; | ||
height = document.documentElement.clientHeight; | ||
width = visualViewport?.width ?? document.documentElement.clientWidth; | ||
height = visualViewport?.height ?? document.documentElement.clientHeight; | ||
@@ -108,0 +111,0 @@ scroll.top = |
@@ -19,1 +19,2 @@ /* | ||
export * from './DismissButton'; | ||
export * from './ariaHideOutside'; |
@@ -14,3 +14,3 @@ /* | ||
import {calculatePosition, PositionResult} from './calculatePosition'; | ||
import {HTMLAttributes, RefObject, useCallback, useEffect, useState} from 'react'; | ||
import {HTMLAttributes, RefObject, useCallback, useEffect, useRef, useState} from 'react'; | ||
import {Placement, PlacementAxis, PositionProps} from '@react-types/overlays'; | ||
@@ -59,2 +59,5 @@ import {useCloseOnScroll} from './useCloseOnScroll'; | ||
// @ts-ignore | ||
let visualViewport = typeof window !== 'undefined' && window.visualViewport; | ||
/** | ||
@@ -129,2 +132,31 @@ * Handles positioning overlays like popovers and menus relative to a trigger | ||
// Reposition the overlay and do not close on scroll while the visual viewport is resizing. | ||
// This will ensure that overlays adjust their positioning when the iOS virtual keyboard appears. | ||
let isResizing = useRef(false); | ||
useEffect(() => { | ||
let timeout: NodeJS.Timeout; | ||
let onResize = () => { | ||
isResizing.current = true; | ||
clearTimeout(timeout); | ||
timeout = setTimeout(() => { | ||
isResizing.current = false; | ||
}, 500); | ||
updatePosition(); | ||
}; | ||
visualViewport?.addEventListener('resize', onResize); | ||
return () => { | ||
visualViewport?.removeEventListener('resize', onResize); | ||
}; | ||
}, [updatePosition]); | ||
let close = useCallback(() => { | ||
if (!isResizing.current) { | ||
onClose(); | ||
} | ||
}, [onClose, isResizing]); | ||
// When scrolling a parent scrollable region of the trigger (other than the body), | ||
@@ -135,3 +167,3 @@ // we hide the popover. Otherwise, its position would be incorrect. | ||
isOpen, | ||
onClose | ||
onClose: onClose ? close : undefined | ||
}); | ||
@@ -138,0 +170,0 @@ |
@@ -33,2 +33,15 @@ /* | ||
// HTML input types that do not cause the software keyboard to appear. | ||
const nonTextInputTypes = new Set([ | ||
'checkbox', | ||
'radio', | ||
'range', | ||
'color', | ||
'file', | ||
'image', | ||
'button', | ||
'submit', | ||
'reset' | ||
]); | ||
/** | ||
@@ -127,3 +140,3 @@ * Prevents scrolling on the document body on mount, and | ||
let target = e.target as HTMLElement; | ||
if (target.tagName === 'INPUT') { | ||
if (target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type)) { | ||
e.preventDefault(); | ||
@@ -144,3 +157,3 @@ | ||
let target = e.target as HTMLElement; | ||
if (target.tagName === 'INPUT') { | ||
if (target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type)) { | ||
// Transform also needs to be applied in the focus event in cases where focus moves | ||
@@ -147,0 +160,0 @@ // other than tapping on an input directly, e.g. the next/previous buttons in the |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
355890
19
3572
Updated@react-aria/i18n@^3.2.0
Updated@react-aria/utils@^3.4.1
Updated@react-types/button@^3.3.0
Updated@react-types/overlays@^3.4.0