@kunukn/react-collapse
Version:
Collapse library based on CSS transition for React
1 lines • 10.5 kB
Source Map (JSON)
{"version":3,"file":"react-collapse.mjs","sources":["../lib/Collapse.tsx"],"sourcesContent":["import { useCallback, useEffect, useReducer, useRef, useState } from 'react'\r\n\r\nimport type { CollapseProps } from '../types/index.d.ts'\r\n\r\nconst COLLAPSED = 'collapsed'\r\nconst COLLAPSING = 'collapsing'\r\nconst EXPANDING = 'expanding'\r\nconst EXPANDED = 'expanded'\r\n\r\nconst defaultClassName = 'collapse-css-transition'\r\nconst defaultElementType = 'div'\r\nconst defaultCollapseHeight = '0px'\r\n\r\nfunction nextFrame(callback: FrameRequestCallback) {\r\n requestAnimationFrame(function () {\r\n //setTimeout(callback, 0); // NOT used because can be jumpy if click-spamming.\r\n requestAnimationFrame(callback) // This is used.\r\n })\r\n}\r\n\r\nexport const Collapse: React.FunctionComponent<CollapseProps> = ({\r\n children,\r\n transition,\r\n style,\r\n render,\r\n elementType = defaultElementType,\r\n isOpen,\r\n collapseHeight = defaultCollapseHeight,\r\n onInit,\r\n onChange,\r\n className = defaultClassName,\r\n addState,\r\n noAnim,\r\n overflowOnExpanded,\r\n ...rest\r\n}: CollapseProps): React.JSX.Element => {\r\n const getCollapsedVisibility = () =>\r\n collapseHeight === '0px' ? 'hidden' : undefined\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const [_, forceUpdate] = useReducer((_) => _ + 1, 0)\r\n\r\n const elementRef = useRef()\r\n const [callbackTick, setCallbackTick] = useState(0)\r\n\r\n // Avoiding setState to control when stuff are updated.\r\n // Might not be needed.\r\n const state = useRef({\r\n collapse: isOpen ? EXPANDED : COLLAPSED,\r\n style: {\r\n height: isOpen ? undefined : collapseHeight,\r\n visibility: isOpen ? undefined : getCollapsedVisibility(),\r\n },\r\n }).current\r\n\r\n useEffect(() => {\r\n // Invoke callback when data are updated, use Effect to sync state.\r\n callbackTick && onCallback(onChange)\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [callbackTick])\r\n\r\n const onCallback = (callback, params = {}) => {\r\n if (callback) {\r\n callback({ state: state.collapse, style: state.style, ...params })\r\n }\r\n }\r\n\r\n function setCollapsed() {\r\n if (!elementRef.current) return // might be redundant\r\n\r\n // Update state\r\n state.collapse = COLLAPSED\r\n\r\n state.style = {\r\n height: collapseHeight,\r\n visibility: getCollapsedVisibility(),\r\n }\r\n forceUpdate()\r\n\r\n setTimeout(() => setCallbackTick(Date.now), 0) // callback and re-render\r\n }\r\n\r\n function setCollapsing() {\r\n if (!elementRef.current) return // might be redundant\r\n\r\n if (noAnim) {\r\n return setCollapsed()\r\n }\r\n\r\n // Update state\r\n state.collapse = COLLAPSING\r\n\r\n state.style = {\r\n height: getElementHeight(),\r\n visibility: undefined,\r\n }\r\n forceUpdate()\r\n\r\n nextFrame(() => {\r\n if (!elementRef.current) return\r\n if (state.collapse !== COLLAPSING) return\r\n\r\n state.style = {\r\n height: collapseHeight,\r\n visibility: undefined,\r\n }\r\n\r\n setCallbackTick(Date.now) // callback and re-render\r\n })\r\n }\r\n\r\n function setExpanding() {\r\n if (!elementRef.current) return // might be redundant\r\n\r\n if (noAnim) {\r\n return setExpanded()\r\n }\r\n\r\n // Updatetate\r\n state.collapse = EXPANDING\r\n\r\n nextFrame(() => {\r\n if (!elementRef.current) return // might be redundant\r\n if (state.collapse !== EXPANDING) return\r\n\r\n state.style = {\r\n height: getElementHeight(),\r\n visibility: undefined,\r\n }\r\n\r\n setCallbackTick(Date.now) // callback and re-render\r\n })\r\n }\r\n\r\n function setExpanded() {\r\n if (!elementRef.current) return // might be redundant\r\n\r\n // Update state\r\n state.collapse = EXPANDED\r\n\r\n state.style = {\r\n height: undefined,\r\n visibility: undefined,\r\n }\r\n forceUpdate()\r\n\r\n setTimeout(() => setCallbackTick(Date.now), 0) // callback and re-render\r\n }\r\n\r\n function getElementHeight() {\r\n // @ts-ignore\r\n return `${elementRef.current.scrollHeight}px`\r\n }\r\n\r\n function onTransitionEnd({ target, propertyName }) {\r\n if (target === elementRef.current && propertyName === 'height') {\r\n const styleHeight = target.style.height\r\n\r\n switch (state.collapse) {\r\n case EXPANDING:\r\n if (styleHeight === undefined || styleHeight === collapseHeight)\r\n // This is stale, a newer event has happened before this could execute\r\n console.warn(\r\n `onTransitionEnd height unexpected ${styleHeight}`,\r\n 'ignore setExpanded'\r\n )\r\n else setExpanded()\r\n break\r\n case COLLAPSING:\r\n if (styleHeight === undefined || styleHeight !== collapseHeight)\r\n // This is stale, a newer event has happened before this could execute\r\n console.warn(\r\n `onTransitionEnd height unexpected ${styleHeight}`,\r\n 'ignore setCollapsed'\r\n )\r\n else setCollapsed()\r\n break\r\n default:\r\n console.warn('Ignored in onTransitionEnd', state.collapse)\r\n }\r\n }\r\n }\r\n\r\n // getDerivedStateFromProps\r\n const didOpen = state.collapse === EXPANDED || state.collapse === EXPANDING\r\n\r\n if (!didOpen && isOpen) setExpanding()\r\n\r\n if (didOpen && !isOpen) setCollapsing()\r\n // END getDerivedStateFromProps\r\n\r\n const overflow =\r\n state.collapse === EXPANDED && overflowOnExpanded ? undefined : 'hidden'\r\n\r\n const computedStyle = {\r\n overflow,\r\n transition,\r\n ...style,\r\n ...state.style,\r\n }\r\n const ElementType = elementType\r\n\r\n const callbackRef = useCallback(\r\n (node) => {\r\n if (node) {\r\n elementRef.current = node\r\n onCallback(onInit, { node })\r\n }\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [elementType]\r\n )\r\n\r\n const collapseClassName = addState\r\n ? `${className} --c-${state.collapse}`\r\n : className\r\n\r\n return (\r\n // @ts-ignore\r\n <ElementType\r\n ref={callbackRef}\r\n style={computedStyle}\r\n onTransitionEnd={onTransitionEnd}\r\n className={collapseClassName}\r\n {...rest}\r\n >\r\n {typeof children === 'function'\r\n ? children(state.collapse)\r\n : typeof render === 'function'\r\n ? render(state.collapse)\r\n : children}\r\n </ElementType>\r\n )\r\n}\r\n"],"names":["COLLAPSED","COLLAPSING","EXPANDING","EXPANDED","defaultClassName","defaultElementType","defaultCollapseHeight","nextFrame","callback","Collapse","children","transition","style","render","elementType","isOpen","collapseHeight","onInit","onChange","className","addState","noAnim","overflowOnExpanded","rest","getCollapsedVisibility","_","forceUpdate","useReducer","elementRef","useRef","callbackTick","setCallbackTick","useState","state","useEffect","onCallback","params","setCollapsed","setCollapsing","getElementHeight","setExpanding","setExpanded","onTransitionEnd","target","propertyName","styleHeight","didOpen","computedStyle","ElementType","callbackRef","useCallback","node","collapseClassName","jsx"],"mappings":"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"}