@nex-ui/react
Version:
🎉 A beautiful, modern, and reliable React component library.
198 lines (194 loc) • 7.18 kB
JavaScript
"use client";
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var system = require('@nex-ui/system');
var react = require('react');
var hooks = require('@nex-ui/hooks');
var utils = require('@nex-ui/utils');
var PopperContext = require('./PopperContext.cjs');
var useSlot = require('../utils/useSlot.cjs');
var computePosition = require('../utils/computePosition/computePosition.cjs');
var getOverflowAncestors = require('../utils/computePosition/getOverflowAncestors.cjs');
var Portal = require('../utils/portal/Portal.cjs');
var PresenceMotion = require('../utils/PresenceMotion.cjs');
const recipe = system.defineRecipe({
base: {
pos: 'absolute',
w: 'max-content',
left: 'var(--popper-x)',
top: 'var(--popper-y)'
}
});
const style = recipe();
const PopperRoot = (inProps)=>{
const { open, referenceRef, setOpen, popperRootRef } = PopperContext.usePopper();
const { children, container, flip = {
mainAxis: true,
crossAxis: true
}, offset = 5, shift = true, keepMounted = false, closeOnDetached = true, closeOnEscape = true, placement = 'top', ...props } = inProps;
const [styleVariables, setStyleVariables] = react.useState(undefined);
const unsubscribeRef = react.useRef(undefined);
// To avoid multiple calculations on the initial render,
// because ResizeObserver is triggered when observing starts.
const initialRender = react.useRef(true);
// Portal renders asynchronously. Use this variable to avoid multiple handler registrations.
const initialized = react.useRef(false);
const [PopperMotion, getPopperMotionProps] = useSlot.useSlot({
style,
elementType: PresenceMotion.PresenceMotion,
externalForwardedProps: props,
shouldForwardComponent: false,
additionalProps: {
open,
keepMounted,
ref: popperRootRef,
style: styleVariables
},
a11y: {
'aria-hidden': open ? undefined : true
},
dataAttrs: {
placement,
keepMounted,
closeOnEscape,
state: open ? 'open' : 'closed'
}
});
const setPosition = hooks.useEvent(()=>{
// istanbul ignore if
if (!referenceRef.current || !popperRootRef.current) return;
const { x, y } = computePosition.computePosition(referenceRef.current, popperRootRef.current, {
placement,
offset,
flip,
shift
});
const newStyleVars = {
'--popper-x': x + 'px',
'--popper-y': y + 'px'
};
setStyleVariables(newStyleVars);
});
// istanbul ignore next
const resetPosition = hooks.useEvent(()=>{
if (!referenceRef.current || !popperRootRef.current) {
setStyleVariables(undefined);
return;
}
setPosition();
});
// istanbul ignore next
const observeReferenceIntersection = hooks.useEvent(()=>{
// istanbul ignore if
if (!referenceRef.current || !closeOnDetached) return;
function handleIntersect(entries) {
entries.forEach((entry)=>{
if (!entry.isIntersecting) {
setOpen(false);
}
});
}
const observer = new IntersectionObserver(handleIntersect);
observer.observe(referenceRef.current);
return ()=>{
observer.disconnect();
};
});
// istanbul ignore next
const observeElementResizeChanges = hooks.useEvent(()=>{
// istanbul ignore if
if (!referenceRef.current || !popperRootRef.current) return;
const resizeObserver = new ResizeObserver((entries)=>{
if (initialRender.current) {
initialRender.current = false;
return;
}
for (const entry of entries){
if (entry.target === referenceRef.current || entry.target === popperRootRef.current) {
resetPosition();
}
}
});
resizeObserver.observe(referenceRef.current);
resizeObserver.observe(popperRootRef.current);
return ()=>{
resizeObserver.disconnect();
initialRender.current = true;
};
});
// istanbul ignore next
const subscribeAncestorScrollEvents = hooks.useEvent(()=>{
if (!popperRootRef.current) return;
const ancestors = getOverflowAncestors.getOverflowAncestors(popperRootRef.current);
const unsubscribeScroll = ancestors.map((ancestor)=>{
return utils.addEventListener(ancestor, 'scroll', resetPosition);
});
return ()=>{
unsubscribeScroll.forEach((unsub)=>unsub());
};
});
const subscribeWindowResizeEvent = hooks.useEvent(()=>{
const win = utils.ownerWindow(referenceRef.current);
return utils.addEventListener(win, 'resize', resetPosition);
});
const subscribeEscapeEvent = hooks.useEvent(()=>{
if (!closeOnEscape || !open) return;
const win = utils.ownerWindow(referenceRef.current);
return utils.addEventListener(win, 'keydown', (e)=>{
if (e.key === 'Escape') {
setOpen(false);
}
});
});
const handleMount = hooks.useEvent(()=>{
// istanbul ignore if
if (initialized.current) return;
setPosition();
const unobserveElementResizeChanges = observeElementResizeChanges();
const unsubscribeAncestorScrollEvents = subscribeAncestorScrollEvents();
const unsubscribeWindowResizeEvent = subscribeWindowResizeEvent();
const unobserveReferenceIntersection = observeReferenceIntersection();
const unsubscribeEscapeEvent = subscribeEscapeEvent();
unsubscribeRef.current = ()=>{
unobserveElementResizeChanges?.();
unsubscribeAncestorScrollEvents?.();
unsubscribeWindowResizeEvent?.();
unobserveReferenceIntersection?.();
unsubscribeEscapeEvent?.();
};
initialized.current = true;
});
const handleUnmount = hooks.useEvent(()=>{
if (initialized.current === false) return;
unsubscribeRef.current?.();
initialized.current = false;
});
const onMount = hooks.useEvent(()=>{
// Mainly handle the case where open defaults to true.
if (open) handleMount();
});
const onUnmount = hooks.useEvent(()=>{
handleUnmount();
});
react.useEffect(()=>{
if (!open || !popperRootRef.current) return;
handleMount();
return handleUnmount;
}, [
handleMount,
handleUnmount,
open,
popperRootRef
]);
return /*#__PURE__*/ jsxRuntime.jsx(Portal.Portal, {
onMount: onMount,
onUnmount: onUnmount,
container: container,
children: /*#__PURE__*/ jsxRuntime.jsx(PopperMotion, {
...getPopperMotionProps(),
children: children
})
});
};
PopperRoot.displayName = 'PopperRoot';
exports.PopperRoot = PopperRoot;