fanyucomponents
Advanced tools
| import { jsx as _jsx } from "react/jsx-runtime"; | ||
| import { useLayoutEffect, useRef, useState } from "react"; | ||
| 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 [collapsed, setCollapsed] = useState(!show); | ||
| const transitionTimerRef = useRef(null); | ||
| const resizeObserverRef = useRef(null); | ||
| useLayoutEffect(() => { | ||
| // 清理函數 | ||
| 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; | ||
| // 清理函数 | ||
| const cleanup = () => { | ||
| if (transitionTimerRef.current) { | ||
| clearTimeout(transitionTimerRef.current); | ||
| } | ||
| if (resizeObserverRef.current) { | ||
| resizeObserverRef.current.disconnect(); | ||
| } | ||
| }; | ||
| // 處理過度結束 | ||
| const onTransitionEnd = (e) => { | ||
| if (e.propertyName === "height" && show) { | ||
| el.style.height = "auto"; | ||
| // 添加防抖重置以防内容变化 | ||
| if (show) { | ||
| el.style.height = "auto"; | ||
| // 監聽內容變化以保持正確高度 | ||
| if (!resizeObserverRef.current) { | ||
| resizeObserverRef.current = new ResizeObserver(() => { | ||
| el.style.height = "auto"; | ||
| if (el.style.height === "auto") { | ||
| el.style.height = "auto"; // 確保保持 auto 狀態 | ||
| } | ||
| }); | ||
| resizeObserverRef.current.observe(el); | ||
| } | ||
| }; | ||
| // 備用(防止 transitionend 未觸發) | ||
| const startFallbackTimer = () => { | ||
| transitionTimerRef.current = setTimeout(() => { | ||
| if (show) { | ||
| el.style.height = "auto"; | ||
| } | ||
| else { | ||
| 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); | ||
| } | ||
| }, parseInt(el.style.transitionDuration || "500")); // 匹配 CSS 过渡时间 | ||
| }; | ||
| } | ||
| else { | ||
| setCollapsed(true); | ||
| } | ||
| }, transitionDuration + 50); // 額外 50ms 緩衝時間 | ||
| }, [show]); | ||
| useLayoutEffect(() => { | ||
| const el = innerRef.current; | ||
| if (!el) | ||
| return; | ||
| // 先清理之前的狀態 | ||
| cleanup(); | ||
| if (show) { | ||
| setCollapsed(true); | ||
| setCollapsed(false); | ||
| // 從收合狀態展開 | ||
| el.style.height = "0px"; | ||
| // 強制重繪,讓上面設定生效 | ||
| // 強制重繪,確保起始狀態生效 | ||
| void el.offsetHeight; | ||
| el.style.height = `${el.scrollHeight}px`; | ||
| setElementHeight(`${el.scrollHeight}px`); | ||
| } | ||
| else { | ||
| // 從展開狀態收合 | ||
| el.style.height = `${el.scrollHeight}px`; | ||
| // 強制重繪,讓上面設定生效 | ||
| // 強制重繪,確保起始狀態生效 | ||
| void el.offsetHeight; | ||
| el.style.height = "0px"; | ||
| setElementHeight("0px"); | ||
| } | ||
| el.addEventListener("transitionend", onTransitionEnd); | ||
| el.addEventListener("transitionend", handleTransitionEnd); | ||
| startFallbackTimer(); | ||
| return () => { | ||
| cleanup(); | ||
| el.removeEventListener("transitionend", onTransitionEnd); | ||
| el.removeEventListener("transitionend", handleTransitionEnd); | ||
| }; | ||
| }, [show]); | ||
| }, [show, cleanup, handleTransitionEnd, setElementHeight, startFallbackTimer]); | ||
| return (_jsx(Tag, { ref: innerRef, style: { | ||
@@ -62,0 +107,0 @@ overflow: "hidden", |
| import { jsx as _jsx } from "react/jsx-runtime"; | ||
| import React, { useState } from "react"; | ||
| import ReactDOM from "react-dom"; | ||
| import { flexAlignMap } from "../utils/flex"; | ||
| import { onEventHandlerKeys, } from "../types"; | ||
| import { StateStylesComponent } from "./StateStylesComponent"; | ||
| import { flexAlignMap } from "../utils/flex"; | ||
| export const useModal = (initOption) => { | ||
@@ -8,0 +8,0 @@ var _a; |
+1
-1
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "name": "fanyucomponents", | ||
| "version": "2.10.16", | ||
| "version": "2.11.0", | ||
| "description": "一款以 純邏輯為核心、無樣式綁定 的 React 元件套件", | ||
@@ -6,0 +6,0 @@ "main": "dist/index.js", |
@@ -1,2 +0,2 @@ | ||
| import { useLayoutEffect, useRef, useState } from "react"; | ||
| import { useLayoutEffect, useRef, useState, useCallback } from "react"; | ||
| import { CollapseProps } from "../types"; | ||
@@ -13,55 +13,101 @@ | ||
| const innerRef = useRef<HTMLDivElement>(null); | ||
| const [collapsed, setCollapsed] = useState<boolean>(show); | ||
| const transitionTimerRef = useRef<number>(null); | ||
| const resizeObserverRef = useRef<ResizeObserver>(null); | ||
| const [collapsed, setCollapsed] = useState<boolean>(!show); | ||
| const transitionTimerRef = useRef<number | null>(null); | ||
| const resizeObserverRef = useRef<ResizeObserver | null>(null); | ||
| useLayoutEffect(() => { | ||
| // 清理函數 | ||
| 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: TransitionEvent) => { | ||
| if (e.propertyName !== "height") return; | ||
| const el = innerRef.current; | ||
| if (!el) return; | ||
| // 清理函数 | ||
| const cleanup = () => { | ||
| if (transitionTimerRef.current) { | ||
| clearTimeout(transitionTimerRef.current); | ||
| } | ||
| if (resizeObserverRef.current) { | ||
| resizeObserverRef.current.disconnect(); | ||
| } | ||
| }; | ||
| // 處理過度結束 | ||
| const onTransitionEnd = (e: TransitionEvent) => { | ||
| if (e.propertyName === "height" && show) { | ||
| el.style.height = "auto"; | ||
| // 添加防抖重置以防内容变化 | ||
| if (show) { | ||
| el.style.height = "auto"; | ||
| // 監聽內容變化以保持正確高度 | ||
| if (!resizeObserverRef.current) { | ||
| resizeObserverRef.current = new ResizeObserver(() => { | ||
| el.style.height = "auto"; | ||
| if (el.style.height === "auto") { | ||
| el.style.height = "auto"; // 確保保持 auto 狀態 | ||
| } | ||
| }); | ||
| resizeObserverRef.current.observe(el); | ||
| } | ||
| }; | ||
| } else { | ||
| setCollapsed(true); | ||
| } | ||
| // 清理定時器 | ||
| if (transitionTimerRef.current) { | ||
| clearTimeout(transitionTimerRef.current); | ||
| transitionTimerRef.current = null; | ||
| } | ||
| }, [show]); | ||
| // 備用(防止 transitionend 未觸發) | ||
| const startFallbackTimer = () => { | ||
| transitionTimerRef.current = setTimeout(() => { | ||
| if (show) { | ||
| el.style.height = "auto"; | ||
| // 設置高度並啟動動畫 | ||
| const setElementHeight = useCallback((targetHeight: string) => { | ||
| 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); | ||
| } | ||
| }, parseInt(el.style.transitionDuration || "500")); // 匹配 CSS 过渡时间 | ||
| }; | ||
| } else { | ||
| setCollapsed(true); | ||
| } | ||
| }, transitionDuration + 50); // 額外 50ms 緩衝時間 | ||
| }, [show]); | ||
| useLayoutEffect(() => { | ||
| const el = innerRef.current; | ||
| if (!el) return; | ||
| // 先清理之前的狀態 | ||
| cleanup(); | ||
| if (show) { | ||
| setCollapsed(true); | ||
| setCollapsed(false); | ||
| // 從收合狀態展開 | ||
| el.style.height = "0px"; | ||
| // 強制重繪,讓上面設定生效 | ||
| // 強制重繪,確保起始狀態生效 | ||
| void el.offsetHeight; | ||
| el.style.height = `${el.scrollHeight}px`; | ||
| setElementHeight(`${el.scrollHeight}px`); | ||
| } else { | ||
| // 從展開狀態收合 | ||
| el.style.height = `${el.scrollHeight}px`; | ||
| // 強制重繪,讓上面設定生效 | ||
| // 強制重繪,確保起始狀態生效 | ||
| void el.offsetHeight; | ||
| el.style.height = "0px"; | ||
| setElementHeight("0px"); | ||
| } | ||
| el.addEventListener("transitionend", onTransitionEnd); | ||
| el.addEventListener("transitionend", handleTransitionEnd); | ||
| startFallbackTimer(); | ||
@@ -71,5 +117,5 @@ | ||
| cleanup(); | ||
| el.removeEventListener("transitionend", onTransitionEnd); | ||
| el.removeEventListener("transitionend", handleTransitionEnd); | ||
| }; | ||
| }, [show]); | ||
| }, [show, cleanup, handleTransitionEnd, setElementHeight, startFallbackTimer]); | ||
| return ( | ||
@@ -76,0 +122,0 @@ <Tag |
| import React, { useState } from "react"; | ||
| import ReactDOM from "react-dom"; | ||
| import { flexAlignMap } from "../utils/flex"; | ||
| import { | ||
@@ -11,2 +10,3 @@ ModalContainerProps, | ||
| import { StateStylesComponent } from "./StateStylesComponent"; | ||
| import { flexAlignMap } from "../utils/flex"; | ||
@@ -13,0 +13,0 @@ export const useModal = (initOption?: { isShow?: boolean }) => { |
137004
2.27%1514
5.87%