@redocly/theme
Version:
Shared UI components lib
173 lines • 6.77 kB
JavaScript
;
/**
* 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