UNPKG

@redocly/theme

Version:

Shared UI components lib

173 lines 6.77 kB
"use strict"; /** * Based on https://github.com/roginfarrer/collapsed/blob/main/packages/react-collapsed/src/index.ts * Simplified for our usecase. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.useCollapse = useCollapse; exports.getAutoHeightDuration = getAutoHeightDuration; exports.getElementHeight = getElementHeight; exports.setAnimationTimeout = setAnimationTimeout; exports.clearAnimationTimeout = clearAnimationTimeout; exports.useEvent = useEvent; const react_1 = require("react"); const useLayoutEffect = typeof window === 'undefined' ? react_1.useEffect : react_1.useLayoutEffect; const easing = 'cubic-bezier(0.4, 0, 0.2, 1)'; const collapsedHeight = `0px`; function useCollapse({ isExpanded, collapseElRef, onTransitionStateChange: configOnTransitionStateChange = () => { }, }) { const prevExpanded = (0, react_1.useRef)(isExpanded); const [isAnimating, setIsAnimating] = (0, react_1.useState)(false); const onTransitionStateChange = useEvent(configOnTransitionStateChange); // Animation frames const frameId = (0, react_1.useRef)(undefined); const endFrameId = (0, react_1.useRef)(undefined); useLayoutEffect(() => { const collapse = collapseElRef.current; if (!collapse) return; if (isExpanded === prevExpanded.current) return; prevExpanded.current = isExpanded; function getDuration(height) { return getAutoHeightDuration(height); } const getTransitionStyles = (height) => `height ${getDuration(height)}ms ${easing}`; const setTransitionEndTimeout = (duration) => { function endTransition() { if (isExpanded) { setStyles(collapse, { height: '', overflow: '', transition: '', display: '', pointerEvents: 'auto', }); onTransitionStateChange('expandEnd'); } else { setStyles(collapse, { transition: '', pointerEvents: '' }); onTransitionStateChange('collapseEnd'); } setIsAnimating(false); } if (endFrameId.current) { clearAnimationTimeout(endFrameId.current); } endFrameId.current = setAnimationTimeout(endTransition, duration); }; setIsAnimating(true); if (isExpanded) { frameId.current = requestAnimationFrame(() => { onTransitionStateChange('expandStart'); setStyles(collapse, { display: 'block', overflow: 'hidden', pointerEvents: 'none', height: collapsedHeight, }); frameId.current = requestAnimationFrame(() => { onTransitionStateChange('expanding'); const height = getElementHeight(collapseElRef); setTransitionEndTimeout(getDuration(height)); if (collapseElRef.current) { // Order is important! Setting directly. collapseElRef.current.style.transition = getTransitionStyles(height); collapseElRef.current.style.height = `${height}px`; } }); }); } else { frameId.current = requestAnimationFrame(() => { onTransitionStateChange('collapseStart'); const height = getElementHeight(collapseElRef); setTransitionEndTimeout(getDuration(height)); setStyles(collapse, { transition: getTransitionStyles(height), height: `${height}px`, pointerEvents: 'none', }); frameId.current = requestAnimationFrame(() => { onTransitionStateChange('collapsing'); setStyles(collapse, { height: collapsedHeight, overflow: 'hidden', }); }); }); } return () => { if (frameId.current) cancelAnimationFrame(frameId.current); if (endFrameId.current) clearAnimationTimeout(endFrameId.current); }; }, [isExpanded, collapseElRef, onTransitionStateChange]); return { isExpanded, style: !isAnimating && !isExpanded ? { // collapsed and not animating display: 'none', height: collapsedHeight, overflow: 'hidden', } : {}, }; } // https://github.com/mui-org/material-ui/blob/da362266f7c137bf671d7e8c44c84ad5cfc0e9e2/packages/material-ui/src/styles/transitions.js#L89-L98 function getAutoHeightDuration(height) { if (!height || typeof height === 'string') { return 0; } const constant = height / 36; return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10); } function getElementHeight(el) { var _a, _b; // scrollHeight will give us the height of the element, even if it's not visible. // clientHeight, offsetHeight, nor getBoundingClientRect().height will do so return (_b = (_a = el.current) === null || _a === void 0 ? void 0 : _a.scrollHeight) !== null && _b !== void 0 ? _b : 0; } function setAnimationTimeout(callback, timeout) { const startTime = performance.now(); const frame = {}; function call() { frame.id = requestAnimationFrame((now) => { if (now - startTime > timeout) { callback(); } else { call(); } }); } call(); return frame; } function clearAnimationTimeout(frame) { if (frame.id) cancelAnimationFrame(frame.id); } function setStyles(target, newStyles) { if (!target) return; for (const property in newStyles) { const value = newStyles[property]; if (value) { target.style[property] = value; } else { target.style.removeProperty(property); } } } /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ function useEvent(callback) { const ref = (0, react_1.useRef)(callback); (0, react_1.useEffect)(() => { ref.current = callback; }); return (0, react_1.useCallback)(((...args) => { var _a; return (_a = ref.current) === null || _a === void 0 ? void 0 : _a.call(ref, ...args); }), []); } //# sourceMappingURL=use-collapse.js.map