@kubit-ui-web/react-components
Version:
Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience
150 lines • 7.27 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import React from 'react';
import { STYLES_NAME } from '../../constants/stylesName/stylesName';
import { useClickOutside } from '../../hooks/useClickOutside/useClickOutside';
import { useEscPressedV2 } from '../../hooks/useEscPressedV2/useEscPressedV2';
import { useMediaDevice } from '../../hooks/useMediaDevice/useMediaDevice';
import { useScrollBlock } from '../../hooks/useScrollBlock/useScrollBlock';
import { useStyles } from '../../hooks/useStyles/useStyles';
import { useTrapFocus } from '../../hooks/useTrapFocus/useTrapFocus';
import { convertDurationToNumber } from '../../utils/stringUtility/string.utility';
import { ErrorBoundary } from '../../provider/errorBoundary/errorBoundary';
import { FallbackComponent } from '../../provider/errorBoundary/fallbackComponent';
import { focusFirstDescendant } from '../../utils/focusHandlers/focusHandlers';
import { CssAnimationExecuteOption } from '../cssAnimation/types/cssAnimation';
import { PopoverStandAlone } from './popoverStandAlone';
import { PopoverComponentType } from './types/component';
import { getAnimationConfig } from './utils/getAnimationConfig';
const MILISECONDS = 1000;
const PopoverControlledComponent = React.forwardRef(({ variant, component = PopoverComponentType.DIALOG, focusFirstDescendantAutomatically = true, focusLastElementFocusedAfterClose = true, focusScreenFirstDescendantAfterClose = false, onCloseInternally, blockBack, clickOverlayClose = true, pressEscapeClose = true, preventScrollOnCloseFocus = false, trapFocusInsideModal = true, ctv, ...props }, ref) => {
const { blockScroll, allowScroll } = useScrollBlock();
const currentFocus = React.useRef(null);
const innerRef = React.useRef(null);
const forwardedRef = React.useRef(null);
const [forwardedElementInitialized, setForwardedElementInitialized] = React.useState(false);
const setForwareddRef = React.useCallback(node => {
if (node && focusFirstDescendantAutomatically) {
focusFirstDescendant(node);
}
if (props.forwardedRef) {
props.forwardedRef(node);
}
if (node && blockBack) {
blockScroll({ elementsToOmit: [node] });
}
if (!node && blockBack) {
allowScroll();
}
setForwardedElementInitialized(!!node);
forwardedRef.current = node;
}, [props.forwardedRef, focusFirstDescendantAutomatically]);
const styles = useStyles(STYLES_NAME.POPOVER, variant, ctv);
const device = useMediaDevice();
const animationConfig = getAnimationConfig(styles, device, props.animation, props.animationOptions);
const [showAnimationEnd, setShowAnimationEnd] = React.useState(false);
const [openAnimation, setOpenAnimation] = React.useState(props.open);
const openAnimationRef = React.useRef(props.open);
// Expose the ref to the parent component
React.useImperativeHandle(ref, () => {
return innerRef.current;
}, []);
// To improve: this ref has been created to avoid update a state when the component does not longer exists.
// Check waitForAnimation and setOpenAnimation in the onClose method
const componentAlive = React.useRef(true);
React.useEffect(() => {
componentAlive.current = true;
return () => {
componentAlive.current = false;
};
}, []);
const onClose = async () => {
if (!openAnimationRef.current) {
return;
}
if (animationConfig && animationConfig?.animation) {
setShowAnimationEnd(true);
await waitForAnimation();
}
afterModalFocus();
openAnimationRef.current = false;
if (componentAlive.current) {
setOpenAnimation(false);
}
};
const handleClickOutside = async () => {
if (clickOverlayClose) {
await onClose();
onCloseInternally?.();
}
};
const handlePressScape = async () => {
if (pressEscapeClose) {
await onClose();
onCloseInternally?.();
}
};
useClickOutside(forwardedRef, handleClickOutside, props.preventCloseOnClickElements);
useEscPressedV2({
ref: innerRef,
onEscPress: handlePressScape,
disablePreventDefault: !pressEscapeClose,
disableStopPropagation: !pressEscapeClose,
});
const beforeModalFocus = () => {
currentFocus.current = document.activeElement;
setShowAnimationEnd(false);
};
const afterModalFocus = () => {
if (focusScreenFirstDescendantAfterClose) {
focusFirstDescendant(document.body);
return;
}
if (focusLastElementFocusedAfterClose) {
currentFocus.current?.focus({ preventScroll: preventScrollOnCloseFocus });
}
};
useTrapFocus({
ref: forwardedRef,
trapFocus: forwardedElementInitialized && trapFocusInsideModal,
});
React.useEffect(() => {
if (!props.open && openAnimationRef.current) {
onClose();
}
if (props.open) {
if (!openAnimationRef.current) {
beforeModalFocus();
openAnimationRef.current = true;
setOpenAnimation(true);
}
}
}, [props.open]);
// deprecated - Remove the condition when `exitDuration`, `duration` and `delay' are type string
const animationExitDuration = () => {
if (typeof animationConfig?.animationOptions?.exitDuration === 'string' ||
typeof animationConfig?.animationOptions?.duration === 'string' ||
typeof animationConfig?.animationOptions?.delay === 'string') {
const exitDuration = convertDurationToNumber(animationConfig?.animationOptions?.exitDuration);
const duration = convertDurationToNumber(animationConfig?.animationOptions?.duration);
const delay = convertDurationToNumber(animationConfig?.animationOptions?.delay);
return (exitDuration || duration || 0) + (delay || 0);
}
return (((animationConfig?.animationOptions?.exitDuration ||
animationConfig?.animationOptions?.duration ||
0) +
(animationConfig?.animationOptions?.delay || 0)) *
MILISECONDS);
};
const waitForAnimation = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, animationExitDuration());
});
};
return (_jsx(PopoverStandAlone, { ...props, ref: innerRef, animationConfig: animationConfig, animationExecution: showAnimationEnd ? CssAnimationExecuteOption.END : CssAnimationExecuteOption.START, component: component, device: device, forwardedRef: setForwareddRef, open: openAnimation, styles: styles }));
});
PopoverControlledComponent.displayName = 'PopoverControlledComponent';
const PopoverBoundary = (props, ref) => (_jsx(ErrorBoundary, { fallBackComponent: _jsx(FallbackComponent, { children: _jsx(PopoverStandAlone, { ...props, ref: ref }) }), children: _jsx(PopoverControlledComponent, { ...props, ref: ref }) }));
export const PopoverControlled = React.forwardRef(PopoverBoundary);
//# sourceMappingURL=popoverControlled.js.map