@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
128 lines (123 loc) • 6.1 kB
JavaScript
;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.usePopupAutoResize = usePopupAutoResize;
var React = _interopRequireWildcard(require("react"));
var _useAnimationFrame = require("@base-ui-components/utils/useAnimationFrame");
var _useIsoLayoutEffect = require("@base-ui-components/utils/useIsoLayoutEffect");
var _useStableCallback = require("@base-ui-components/utils/useStableCallback");
var _useAnimationsFinished = require("./useAnimationsFinished");
var _getCssDimensions = require("./getCssDimensions");
const supportsResizeObserver = typeof ResizeObserver !== 'undefined';
const DEFAULT_ENABLED = () => true;
/**
* Allows the element to automatically resize based on its content while supporting animations.
*/
function usePopupAutoResize(parameters) {
const {
popupElement,
positionerElement,
content,
mounted,
enabled = DEFAULT_ENABLED,
onMeasureLayout: onMeasureLayoutParam,
onMeasureLayoutComplete: onMeasureLayoutCompleteParam
} = parameters;
const isInitialRender = React.useRef(true);
const runOnceAnimationsFinish = (0, _useAnimationsFinished.useAnimationsFinished)(popupElement, true, false);
const animationFrame = (0, _useAnimationFrame.useAnimationFrame)();
const previousDimensionsRef = React.useRef(null);
const onMeasureLayout = (0, _useStableCallback.useStableCallback)(onMeasureLayoutParam);
const onMeasureLayoutComplete = (0, _useStableCallback.useStableCallback)(onMeasureLayoutCompleteParam);
(0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
// Reset the state when the popup is closed.
if (!mounted || !enabled() || !supportsResizeObserver) {
isInitialRender.current = true;
previousDimensionsRef.current = null;
return undefined;
}
if (!popupElement || !positionerElement) {
return undefined;
}
const observer = new ResizeObserver(entries => {
const entry = entries[0];
if (entry) {
if (previousDimensionsRef.current === null) {
previousDimensionsRef.current = {
width: Math.ceil(entry.borderBoxSize[0].inlineSize),
height: Math.ceil(entry.borderBoxSize[0].blockSize)
};
} else {
previousDimensionsRef.current.width = Math.ceil(entry.borderBoxSize[0].inlineSize);
previousDimensionsRef.current.height = Math.ceil(entry.borderBoxSize[0].blockSize);
}
}
});
observer.observe(popupElement);
// Measure the rendered size to enable transitions:
popupElement.style.setProperty('--popup-width', 'auto');
popupElement.style.setProperty('--popup-height', 'auto');
const restorePopupPosition = overrideElementStyle(popupElement, 'position', 'static');
const restorePopupTransform = overrideElementStyle(popupElement, 'transform', 'none');
const restorePopupScale = overrideElementStyle(popupElement, 'scale', '1');
const restoreAvailableWidth = overrideElementStyle(positionerElement, '--available-width', 'max-content');
const restoreAvailableHeight = overrideElementStyle(positionerElement, '--available-height', 'max-content');
onMeasureLayout?.();
// Initial render (for each time the popup opens).
if (isInitialRender.current || previousDimensionsRef.current === null) {
positionerElement.style.setProperty('--positioner-width', 'max-content');
positionerElement.style.setProperty('--positioner-height', 'max-content');
const dimensions = (0, _getCssDimensions.getCssDimensions)(popupElement);
positionerElement.style.setProperty('--positioner-width', `${dimensions.width}px`);
positionerElement.style.setProperty('--positioner-height', `${dimensions.height}px`);
restorePopupPosition();
restorePopupTransform();
restorePopupScale();
restoreAvailableWidth();
restoreAvailableHeight();
onMeasureLayoutComplete?.(null, dimensions);
isInitialRender.current = false;
return () => {
observer.disconnect();
};
}
// Subsequent renders while open (when `content` changes).
popupElement.style.setProperty('--popup-width', 'auto');
popupElement.style.setProperty('--popup-height', 'auto');
positionerElement.style.setProperty('--positioner-width', 'max-content');
positionerElement.style.setProperty('--positioner-height', 'max-content');
const newDimensions = (0, _getCssDimensions.getCssDimensions)(popupElement);
popupElement.style.setProperty('--popup-width', `${previousDimensionsRef.current.width}px`);
popupElement.style.setProperty('--popup-height', `${previousDimensionsRef.current.height}px`);
restorePopupPosition();
restorePopupTransform();
restoreAvailableWidth();
restoreAvailableHeight();
onMeasureLayoutComplete?.(previousDimensionsRef.current, newDimensions);
positionerElement.style.setProperty('--positioner-width', `${newDimensions.width}px`);
positionerElement.style.setProperty('--positioner-height', `${newDimensions.height}px`);
const abortController = new AbortController();
animationFrame.request(() => {
popupElement.style.setProperty('--popup-width', `${newDimensions.width}px`);
popupElement.style.setProperty('--popup-height', `${newDimensions.height}px`);
runOnceAnimationsFinish(() => {
popupElement.style.setProperty('--popup-width', 'auto');
popupElement.style.setProperty('--popup-height', 'auto');
}, abortController.signal);
});
return () => {
observer.disconnect();
abortController.abort();
animationFrame.cancel();
};
}, [content, popupElement, positionerElement, runOnceAnimationsFinish, animationFrame, enabled, mounted, onMeasureLayout, onMeasureLayoutComplete]);
}
function overrideElementStyle(element, property, value) {
const originalValue = element.style.getPropertyValue(property);
element.style.setProperty(property, value);
return () => {
element.style.setProperty(property, originalValue);
};
}