221 lines
13 KiB
Plaintext
221 lines
13 KiB
Plaintext
import {calculatePosition as $edcf132a9284368a$export$b3ceb0cbf1056d98, getRect as $edcf132a9284368a$export$4b834cebd9e5cebe} from "./calculatePosition.mjs";
|
|
import {useCloseOnScroll as $dd149f63282afbbf$export$18fc8428861184da} from "./useCloseOnScroll.mjs";
|
|
import {useState as $39EOa$useState, useRef as $39EOa$useRef, useEffect as $39EOa$useEffect, useCallback as $39EOa$useCallback} from "react";
|
|
import {useLayoutEffect as $39EOa$useLayoutEffect, useResizeObserver as $39EOa$useResizeObserver} from "@react-aria/utils";
|
|
import {useLocale as $39EOa$useLocale} from "@react-aria/i18n";
|
|
|
|
/*
|
|
* Copyright 2020 Adobe. All rights reserved.
|
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
* governing permissions and limitations under the License.
|
|
*/
|
|
|
|
|
|
|
|
|
|
let $2a41e45df1593e64$var$visualViewport = typeof document !== 'undefined' ? window.visualViewport : null;
|
|
function $2a41e45df1593e64$export$d39e1813b3bdd0e1(props) {
|
|
let { direction: direction } = (0, $39EOa$useLocale)();
|
|
let { arrowSize: arrowSize, targetRef: targetRef, overlayRef: overlayRef, arrowRef: arrowRef, scrollRef: scrollRef = overlayRef, placement: placement = 'bottom', containerPadding: containerPadding = 12, shouldFlip: shouldFlip = true, boundaryElement: boundaryElement = typeof document !== 'undefined' ? document.body : null, offset: offset = 0, crossOffset: crossOffset = 0, shouldUpdatePosition: shouldUpdatePosition = true, isOpen: isOpen = true, onClose: onClose, maxHeight: maxHeight, arrowBoundaryOffset: arrowBoundaryOffset = 0 } = props;
|
|
let [position, setPosition] = (0, $39EOa$useState)(null);
|
|
let deps = [
|
|
shouldUpdatePosition,
|
|
placement,
|
|
overlayRef.current,
|
|
targetRef.current,
|
|
arrowRef === null || arrowRef === void 0 ? void 0 : arrowRef.current,
|
|
scrollRef.current,
|
|
containerPadding,
|
|
shouldFlip,
|
|
boundaryElement,
|
|
offset,
|
|
crossOffset,
|
|
isOpen,
|
|
direction,
|
|
maxHeight,
|
|
arrowBoundaryOffset,
|
|
arrowSize
|
|
];
|
|
// Note, the position freezing breaks if body sizes itself dynamicly with the visual viewport but that might
|
|
// just be a non-realistic use case
|
|
// Upon opening a overlay, record the current visual viewport scale so we can freeze the overlay styles
|
|
let lastScale = (0, $39EOa$useRef)($2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.scale);
|
|
(0, $39EOa$useEffect)(()=>{
|
|
if (isOpen) lastScale.current = $2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.scale;
|
|
}, [
|
|
isOpen
|
|
]);
|
|
let updatePosition = (0, $39EOa$useCallback)(()=>{
|
|
if (shouldUpdatePosition === false || !isOpen || !overlayRef.current || !targetRef.current || !boundaryElement) return;
|
|
if (($2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.scale) !== lastScale.current) return;
|
|
// Determine a scroll anchor based on the focused element.
|
|
// This stores the offset of the anchor element from the scroll container
|
|
// so it can be restored after repositioning. This way if the overlay height
|
|
// changes, the focused element appears to stay in the same position.
|
|
let anchor = null;
|
|
if (scrollRef.current && scrollRef.current.contains(document.activeElement)) {
|
|
var _document_activeElement;
|
|
let anchorRect = (_document_activeElement = document.activeElement) === null || _document_activeElement === void 0 ? void 0 : _document_activeElement.getBoundingClientRect();
|
|
let scrollRect = scrollRef.current.getBoundingClientRect();
|
|
var _anchorRect_top;
|
|
// Anchor from the top if the offset is in the top half of the scrollable element,
|
|
// otherwise anchor from the bottom.
|
|
anchor = {
|
|
type: 'top',
|
|
offset: ((_anchorRect_top = anchorRect === null || anchorRect === void 0 ? void 0 : anchorRect.top) !== null && _anchorRect_top !== void 0 ? _anchorRect_top : 0) - scrollRect.top
|
|
};
|
|
if (anchor.offset > scrollRect.height / 2) {
|
|
anchor.type = 'bottom';
|
|
var _anchorRect_bottom;
|
|
anchor.offset = ((_anchorRect_bottom = anchorRect === null || anchorRect === void 0 ? void 0 : anchorRect.bottom) !== null && _anchorRect_bottom !== void 0 ? _anchorRect_bottom : 0) - scrollRect.bottom;
|
|
}
|
|
}
|
|
// Always reset the overlay's previous max height if not defined by the user so that we can compensate for
|
|
// RAC collections populating after a second render and properly set a correct max height + positioning when it populates.
|
|
let overlay = overlayRef.current;
|
|
if (!maxHeight && overlayRef.current) {
|
|
var _window_visualViewport;
|
|
overlay.style.top = '0px';
|
|
overlay.style.bottom = '';
|
|
var _window_visualViewport_height;
|
|
overlay.style.maxHeight = ((_window_visualViewport_height = (_window_visualViewport = window.visualViewport) === null || _window_visualViewport === void 0 ? void 0 : _window_visualViewport.height) !== null && _window_visualViewport_height !== void 0 ? _window_visualViewport_height : window.innerHeight) + 'px';
|
|
}
|
|
let position = (0, $edcf132a9284368a$export$b3ceb0cbf1056d98)({
|
|
placement: $2a41e45df1593e64$var$translateRTL(placement, direction),
|
|
overlayNode: overlayRef.current,
|
|
targetNode: targetRef.current,
|
|
scrollNode: scrollRef.current || overlayRef.current,
|
|
padding: containerPadding,
|
|
shouldFlip: shouldFlip,
|
|
boundaryElement: boundaryElement,
|
|
offset: offset,
|
|
crossOffset: crossOffset,
|
|
maxHeight: maxHeight,
|
|
arrowSize: arrowSize !== null && arrowSize !== void 0 ? arrowSize : (arrowRef === null || arrowRef === void 0 ? void 0 : arrowRef.current) ? (0, $edcf132a9284368a$export$4b834cebd9e5cebe)(arrowRef.current, true).width : 0,
|
|
arrowBoundaryOffset: arrowBoundaryOffset
|
|
});
|
|
if (!position.position) return;
|
|
// Modify overlay styles directly so positioning happens immediately without the need of a second render
|
|
// This is so we don't have to delay autoFocus scrolling or delay applying preventScroll for popovers
|
|
overlay.style.top = '';
|
|
overlay.style.bottom = '';
|
|
overlay.style.left = '';
|
|
overlay.style.right = '';
|
|
Object.keys(position.position).forEach((key)=>overlay.style[key] = position.position[key] + 'px');
|
|
overlay.style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' : '';
|
|
// Restore scroll position relative to anchor element.
|
|
if (anchor && document.activeElement && scrollRef.current) {
|
|
let anchorRect = document.activeElement.getBoundingClientRect();
|
|
let scrollRect = scrollRef.current.getBoundingClientRect();
|
|
let newOffset = anchorRect[anchor.type] - scrollRect[anchor.type];
|
|
scrollRef.current.scrollTop += newOffset - anchor.offset;
|
|
}
|
|
// Trigger a set state for a second render anyway for arrow positioning
|
|
setPosition(position);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, deps);
|
|
// Update position when anything changes
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
(0, $39EOa$useLayoutEffect)(updatePosition, deps);
|
|
// Update position on window resize
|
|
$2a41e45df1593e64$var$useResize(updatePosition);
|
|
// Update position when the overlay changes size (might need to flip).
|
|
(0, $39EOa$useResizeObserver)({
|
|
ref: overlayRef,
|
|
onResize: updatePosition
|
|
});
|
|
// Update position when the target changes size (might need to flip).
|
|
(0, $39EOa$useResizeObserver)({
|
|
ref: targetRef,
|
|
onResize: 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 = (0, $39EOa$useRef)(false);
|
|
(0, $39EOa$useLayoutEffect)(()=>{
|
|
let timeout;
|
|
let onResize = ()=>{
|
|
isResizing.current = true;
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(()=>{
|
|
isResizing.current = false;
|
|
}, 500);
|
|
updatePosition();
|
|
};
|
|
// Only reposition the overlay if a scroll event happens immediately as a result of resize (aka the virtual keyboard has appears)
|
|
// We don't want to reposition the overlay if the user has pinch zoomed in and is scrolling the viewport around.
|
|
let onScroll = ()=>{
|
|
if (isResizing.current) onResize();
|
|
};
|
|
$2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.addEventListener('resize', onResize);
|
|
$2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.addEventListener('scroll', onScroll);
|
|
return ()=>{
|
|
$2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.removeEventListener('resize', onResize);
|
|
$2a41e45df1593e64$var$visualViewport === null || $2a41e45df1593e64$var$visualViewport === void 0 ? void 0 : $2a41e45df1593e64$var$visualViewport.removeEventListener('scroll', onScroll);
|
|
};
|
|
}, [
|
|
updatePosition
|
|
]);
|
|
let close = (0, $39EOa$useCallback)(()=>{
|
|
if (!isResizing.current) onClose === null || onClose === void 0 ? void 0 : 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.
|
|
(0, $dd149f63282afbbf$export$18fc8428861184da)({
|
|
triggerRef: targetRef,
|
|
isOpen: isOpen,
|
|
onClose: onClose && close
|
|
});
|
|
var _position_maxHeight, _position_placement, _position_triggerAnchorPoint;
|
|
return {
|
|
overlayProps: {
|
|
style: {
|
|
position: position ? 'absolute' : 'fixed',
|
|
top: !position ? 0 : undefined,
|
|
left: !position ? 0 : undefined,
|
|
zIndex: 100000,
|
|
...position === null || position === void 0 ? void 0 : position.position,
|
|
maxHeight: (_position_maxHeight = position === null || position === void 0 ? void 0 : position.maxHeight) !== null && _position_maxHeight !== void 0 ? _position_maxHeight : '100vh'
|
|
}
|
|
},
|
|
placement: (_position_placement = position === null || position === void 0 ? void 0 : position.placement) !== null && _position_placement !== void 0 ? _position_placement : null,
|
|
triggerAnchorPoint: (_position_triggerAnchorPoint = position === null || position === void 0 ? void 0 : position.triggerAnchorPoint) !== null && _position_triggerAnchorPoint !== void 0 ? _position_triggerAnchorPoint : null,
|
|
arrowProps: {
|
|
'aria-hidden': 'true',
|
|
role: 'presentation',
|
|
style: {
|
|
left: position === null || position === void 0 ? void 0 : position.arrowOffsetLeft,
|
|
top: position === null || position === void 0 ? void 0 : position.arrowOffsetTop
|
|
}
|
|
},
|
|
updatePosition: updatePosition
|
|
};
|
|
}
|
|
function $2a41e45df1593e64$var$useResize(onResize) {
|
|
(0, $39EOa$useLayoutEffect)(()=>{
|
|
window.addEventListener('resize', onResize, false);
|
|
return ()=>{
|
|
window.removeEventListener('resize', onResize, false);
|
|
};
|
|
}, [
|
|
onResize
|
|
]);
|
|
}
|
|
function $2a41e45df1593e64$var$translateRTL(position, direction) {
|
|
if (direction === 'rtl') return position.replace('start', 'right').replace('end', 'left');
|
|
return position.replace('start', 'left').replace('end', 'right');
|
|
}
|
|
|
|
|
|
export {$2a41e45df1593e64$export$d39e1813b3bdd0e1 as useOverlayPosition};
|
|
//# sourceMappingURL=useOverlayPosition.module.js.map
|