From 77a15bd22d12a9df0de08d1eb7dd627a2dded118 Mon Sep 17 00:00:00 2001 From: zekun <949177145@qq.com> Date: Mon, 11 May 2026 18:45:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(containers):=20defaultSize=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20CSS=20=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过 resolveSize 辅助函数将 CSS 字符串(如 "100vw"、"50%") 解析为实际像素值,使 eo-resizable-box 的 defaultSize 属性 不再局限于纯数字。 --- .../src/resizable-box/index.spec.tsx | 103 ++++++++++++++++++ bricks/containers/src/resizable-box/index.tsx | 45 ++++++-- 2 files changed, 138 insertions(+), 10 deletions(-) diff --git a/bricks/containers/src/resizable-box/index.spec.tsx b/bricks/containers/src/resizable-box/index.spec.tsx index fb23e0a5e..046dc64c2 100644 --- a/bricks/containers/src/resizable-box/index.spec.tsx +++ b/bricks/containers/src/resizable-box/index.spec.tsx @@ -116,6 +116,109 @@ describe("eo-resizable-box", () => { }); }); + test("defaultSize with CSS string value", async () => { + const mockGetBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: () => ({}), + })); + jest + .spyOn(HTMLElement.prototype, "getBoundingClientRect") + .mockImplementation(mockGetBoundingClientRect as any); + + const element = document.createElement("eo-resizable-box") as ResizableBox; + (element as any).defaultSize = "100vw"; + + act(() => { + document.body.appendChild(element); + }); + + expect( + (element.shadowRoot?.querySelector(".box") as HTMLElement).style.width + ).toBe("800px"); + + act(() => { + document.body.removeChild(element); + }); + + (HTMLElement.prototype.getBoundingClientRect as any).mockRestore(); + }); + + test("defaultSize with CSS string value in vertical direction", async () => { + const mockGetBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 500, + top: 0, + left: 0, + bottom: 500, + right: 800, + x: 0, + y: 0, + toJSON: () => ({}), + })); + jest + .spyOn(HTMLElement.prototype, "getBoundingClientRect") + .mockImplementation(mockGetBoundingClientRect as any); + + const element = document.createElement("eo-resizable-box") as ResizableBox; + (element as any).defaultSize = "50vh"; + element.resizeDirection = "top"; + + act(() => { + document.body.appendChild(element); + }); + + expect( + (element.shadowRoot?.querySelector(".box") as HTMLElement).style.height + ).toBe("500px"); + + act(() => { + document.body.removeChild(element); + }); + + (HTMLElement.prototype.getBoundingClientRect as any).mockRestore(); + }); + + test("defaultSize with invalid CSS string falls back to default", async () => { + const element = document.createElement("eo-resizable-box") as ResizableBox; + (element as any).defaultSize = "invalid"; + + act(() => { + document.body.appendChild(element); + }); + + expect( + (element.shadowRoot?.querySelector(".box") as HTMLElement).style.width + ).toBe("200px"); + + act(() => { + document.body.removeChild(element); + }); + }); + + test("defaultSize with numeric string", async () => { + const element = document.createElement("eo-resizable-box") as ResizableBox; + (element as any).defaultSize = "300"; + + act(() => { + document.body.appendChild(element); + }); + + expect( + (element.shadowRoot?.querySelector(".box") as HTMLElement).style.width + ).toBe("300px"); + + act(() => { + document.body.removeChild(element); + }); + }); + test("disabled", async () => { const element = document.createElement("eo-resizable-box") as ResizableBox; element.disabled = true; diff --git a/bricks/containers/src/resizable-box/index.tsx b/bricks/containers/src/resizable-box/index.tsx index 60dd2b738..1cbf52b15 100644 --- a/bricks/containers/src/resizable-box/index.tsx +++ b/bricks/containers/src/resizable-box/index.tsx @@ -12,7 +12,7 @@ const { defineElement, property } = createDecorators(); export interface ResizableBoxProps { resizeDirection?: ResizeDirection; storageKey?: string; - defaultSize?: number; + defaultSize?: number | string; minSize?: number; minSpace?: number; disabled?: boolean; @@ -57,11 +57,11 @@ class ResizableBox extends ReactNextElement implements ResizableBoxProps { accessor storageKey: string | undefined; /** - * 默认尺寸(px) + * 默认尺寸,支持数字(px)或 CSS 字符串(如 "100vw"、"50%") * @default 200 */ - @property({ type: Number }) - accessor defaultSize: number | undefined; + @property() + accessor defaultSize: number | string | undefined; /** * 最小尺寸(px) @@ -136,6 +136,25 @@ interface ResizerStatus { startY: number; } +function resolveSize( + value: number | string | undefined, + direction: "horizontal" | "vertical" +): number | undefined { + if (value == null) return undefined; + if (typeof value === "number") return value; + const num = Number(value); + if (!Number.isNaN(num)) return num; + const el = document.createElement("div"); + el.style.position = "absolute"; + el.style.visibility = "hidden"; + el.style[direction === "horizontal" ? "width" : "height"] = value; + document.body.appendChild(el); + const px = + el.getBoundingClientRect()[direction === "horizontal" ? "width" : "height"]; + document.body.removeChild(el); + return px || undefined; +} + interface ResizableBoxComponentProps extends ResizableBoxProps { host: HTMLElement; } @@ -154,7 +173,18 @@ export function ResizableBoxComponent({ host, }: ResizableBoxComponentProps) { const resizeDirection = _resizeDirection ?? "right"; - const defaultSize = _defaultSize ?? 200; + const isVerticalDirection = useMemo( + () => ["top", "bottom"].includes(resizeDirection), + [resizeDirection] + ); + const defaultSize = useMemo( + () => + resolveSize( + _defaultSize, + isVerticalDirection ? "vertical" : "horizontal" + ) ?? 200, + [_defaultSize, isVerticalDirection] + ); const minSpace = _minSpace ?? 300; const refinedMinSize = minSize ?? defaultSize; @@ -175,11 +205,6 @@ export function ResizableBoxComponent({ const [resized, setResized] = useState(false); const [resizeStatus, setResizerStatus] = useState(null); - const isVerticalDirection = useMemo( - () => ["top", "bottom"].includes(resizeDirection), - [resizeDirection] - ); - const handleResizerMouseDown = useCallback( (event: React.MouseEvent) => { if (disabled) return;