UNPKG

fanyucomponents

Version:

一款以 純邏輯為核心、無樣式綁定 的 React 元件套件

116 lines (115 loc) 4.39 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { useLayoutEffect, useRef, useState, useCallback } from "react"; export const Collapse = ({ as, state: show, style, children, ...rest }) => { const Tag = as !== null && as !== void 0 ? as : "div"; const innerRef = useRef(null); const [collapsed, setCollapsed] = useState(!show); const transitionTimerRef = useRef(null); const resizeObserverRef = useRef(null); // 清理函數 const cleanup = useCallback(() => { if (transitionTimerRef.current) { clearTimeout(transitionTimerRef.current); transitionTimerRef.current = null; } if (resizeObserverRef.current) { resizeObserverRef.current.disconnect(); resizeObserverRef.current = null; } }, []); // 處理過渡結束 const handleTransitionEnd = useCallback((e) => { if (e.propertyName !== "height") return; const el = innerRef.current; if (!el) return; if (show) { el.style.height = "auto"; // 監聽內容變化以保持正確高度 if (!resizeObserverRef.current) { resizeObserverRef.current = new ResizeObserver(() => { if (el.style.height === "auto") { el.style.height = "auto"; // 確保保持 auto 狀態 } }); resizeObserverRef.current.observe(el); } } else { // 收起動畫完成後才設置 collapsed 狀態 setCollapsed(true); } // 清理定時器 if (transitionTimerRef.current) { clearTimeout(transitionTimerRef.current); transitionTimerRef.current = null; } }, [show]); // 設置高度並啟動動畫 const setElementHeight = useCallback((targetHeight) => { const el = innerRef.current; if (!el) return; el.style.height = targetHeight; }, []); // 啟動備用定時器(防止 transitionend 未觸發) const startFallbackTimer = useCallback(() => { const el = innerRef.current; if (!el) return; const transitionDuration = parseInt(el.style.transitionDuration || "500"); transitionTimerRef.current = setTimeout(() => { if (show) { el.style.height = "auto"; if (!resizeObserverRef.current) { resizeObserverRef.current = new ResizeObserver(() => { if (el.style.height === "auto") { el.style.height = "auto"; } }); resizeObserverRef.current.observe(el); } } else { // 收起動畫完成後才設置 collapsed 狀態 setCollapsed(true); } }, transitionDuration + 50); // 額外 50ms 緩衝時間 }, [show]); useLayoutEffect(() => { const el = innerRef.current; if (!el) return; // 先清理之前的狀態 cleanup(); if (show) { setCollapsed(false); // 從收合狀態展開 el.style.height = "0px"; // 強制重繪,確保起始狀態生效 void el.offsetHeight; setElementHeight(`${el.scrollHeight}px`); } else { // 從展開狀態收合 - 不立即設置 collapsed,讓動畫執行 el.style.height = `${el.scrollHeight}px`; // 強制重繪,確保起始狀態生效 void el.offsetHeight; setElementHeight("0px"); // 不在這裡設置 setCollapsed(true),讓動畫完成後再設置 } el.addEventListener("transitionend", handleTransitionEnd); startFallbackTimer(); return () => { cleanup(); el.removeEventListener("transitionend", handleTransitionEnd); }; }, [show, cleanup, handleTransitionEnd, setElementHeight, startFallbackTimer]); return (_jsx(Tag, { ref: innerRef, style: { overflow: "hidden", maxHeight: collapsed ? "0px" : undefined, ...style, }, ...rest, children: children })); }; Collapse.displayName = "Collapse";