diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.test.ts b/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.test.ts deleted file mode 100644 index dff96a42f2..0000000000 --- a/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { formatStrokeSummary, parseStrokeSummary } from "./propertyPanelFlatStyleHelpers"; - -describe("formatStrokeSummary", () => { - it("formats width and style into one string", () => { - expect(formatStrokeSummary(1, "solid")).toBe("1px solid"); - expect(formatStrokeSummary(2.5, "dashed")).toBe("2.5px dashed"); - expect(formatStrokeSummary(0, "none")).toBe("0px none"); - }); -}); - -describe("parseStrokeSummary", () => { - it("parses a well-formed summary back into width and style", () => { - expect(parseStrokeSummary("1px solid")).toEqual({ widthPx: 1, style: "solid" }); - expect(parseStrokeSummary(" 2.5px dashed ")).toEqual({ widthPx: 2.5, style: "dashed" }); - }); - - it("returns null for unparseable input", () => { - expect(parseStrokeSummary("garbage")).toBeNull(); - expect(parseStrokeSummary("")).toBeNull(); - expect(parseStrokeSummary("1px")).toBeNull(); - }); -}); diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.ts b/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.ts index 56939a48d0..af2b3be8b3 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.ts +++ b/packages/studio/src/components/editor/propertyPanelFlatStyleHelpers.ts @@ -12,16 +12,3 @@ export const STROKE_STYLE_OPTIONS: string[] = [ "inset", "outset", ]; - -export function formatStrokeSummary(widthPx: number, style: string): string { - return `${widthPx}px ${style}`; -} - -export function parseStrokeSummary(text: string): { widthPx: number; style: string } | null { - const match = /^\s*(-?\d+(?:\.\d+)?)px\s+(\S+)\s*$/.exec(text); - if (!match) return null; - const widthPx = Number.parseFloat(match[1]); - const style = match[2]; - if (!Number.isFinite(widthPx) || !style) return null; - return { widthPx, style }; -} diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx index 018f17ca50..37d4e169e1 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx +++ b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx @@ -175,31 +175,43 @@ function setInputValue(input: HTMLInputElement, nextValue: string) { } describe("FlatStyleSection — Stroke and Radius", () => { - it("renders the combined stroke row and commits width+style together on blur", () => { + it("renders a width-only Stroke width row — style is only ever set through the Stroke style select below it", () => { const { host, root } = renderSection(STROKE_STYLES); - expect(host.textContent).toContain("Stroke"); - expect(getFlatRowInput(host, "Stroke").value).toBe("1px solid"); + expect(host.textContent).toContain("Stroke width"); + expect(host.textContent).toContain("Stroke style"); + expect(getFlatRowInput(host, "Stroke width").value).toBe("1px"); act(() => root.unmount()); }); - it("commits the stroke row's new width and style together on blur", async () => { + it("commits a new stroke width without touching the existing style", async () => { const { host, root, onSetStyle } = renderSection(STROKE_STYLES); - await commitFlatRowInput(host, "Stroke", "2px dashed"); + await commitFlatRowInput(host, "Stroke width", "2px"); expect(onSetStyle).toHaveBeenCalledWith("border-width", "2px"); - expect(onSetStyle).toHaveBeenCalledWith("border-style", "dashed"); + expect(onSetStyle).not.toHaveBeenCalledWith("border-style", expect.anything()); + act(() => root.unmount()); + }); + + it("defaults a from-zero width commit to solid, since a width with no style renders no visible border", async () => { + const { host, root, onSetStyle } = renderSection({ + "border-width": "0px", + "border-style": "none", + "border-color": "rgba(255,255,255,.12)", + }); + await commitFlatRowInput(host, "Stroke width", "3px"); + expect(onSetStyle).toHaveBeenCalledWith("border-style", "solid"); act(() => root.unmount()); }); it("clamps an out-of-range stroke width commit to 200px (fix 2)", async () => { const { host, root, onSetStyle } = renderSection(STROKE_STYLES); - await commitFlatRowInput(host, "Stroke", "9999px solid"); + await commitFlatRowInput(host, "Stroke width", "9999px"); expect(onSetStyle).toHaveBeenCalledWith("border-width", "200px"); act(() => root.unmount()); }); - it("rejects a stroke commit whose style token is not a valid border-style (fix 2)", async () => { + it("ignores an unparseable stroke width commit", async () => { const { host, root, onSetStyle } = renderSection(STROKE_STYLES); - await commitFlatRowInput(host, "Stroke", "12px bogus"); + await commitFlatRowInput(host, "Stroke width", "bogus"); expect(onSetStyle).not.toHaveBeenCalled(); act(() => root.unmount()); }); diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx index d3103b570c..f5681c6c88 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx +++ b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx @@ -3,11 +3,7 @@ import { useEffect, useState } from "react"; import { isTextEditableSelection, type DomEditSelection } from "./domEditing"; import { buildDefaultGradientModel, serializeGradient } from "./gradientValue"; import { BorderRadiusEditor } from "./BorderRadiusEditor"; -import { - formatStrokeSummary, - parseStrokeSummary, - STROKE_STYLE_OPTIONS, -} from "./propertyPanelFlatStyleHelpers"; +import { STROKE_STYLE_OPTIONS } from "./propertyPanelFlatStyleHelpers"; import { buildBoxShadowPresetValue, buildClipPathValue, @@ -147,10 +143,9 @@ function FlatFillFields({ } /* ------------------------------------------------------------------ */ -/* Flat Stroke row — combined width+style+color */ +/* Flat Stroke row — width (numeric), style (select), color */ /* ------------------------------------------------------------------ */ -// fallow-ignore-next-line complexity function FlatStrokeRow({ styles, disabled, @@ -167,52 +162,40 @@ function FlatStrokeRow({ const borderStyleValue = styles["border-style"] || styles["border-top-style"] || "none"; const borderColorValue = styles["border-color"] || styles["border-top-color"] || "rgba(255, 255, 255, 0.18)"; - const summary = formatStrokeSummary(borderWidthValue, borderStyleValue); - const tier = resolveValueTier( - styles["border-width"] != null || styles["border-style"] != null ? summary : undefined, - formatStrokeSummary(0, "none"), - ); + const widthDisplay = formatPxMetricValue(borderWidthValue); return ( <> { - const parsed = parseStrokeSummary(next); - if (!parsed) return; - if (!STROKE_STYLE_OPTIONS.includes(parsed.style)) return; - const normalizedWidth = normalizePanelPxValue(`${parsed.widthPx}px`, { + const normalizedWidth = normalizePanelPxValue(next, { min: 0, max: 200, fallback: borderWidthValue, }); if (!normalizedWidth) return; + // buildStrokeWidthStyleUpdates already covers "a width typed in + // from 0 needs a style to actually render a visible border" — + // it only defaults to solid when the current style is none/hidden, + // never clobbering an already-chosen style (dashed, dotted, …). for (const [property, value] of buildStrokeWidthStyleUpdates( normalizedWidth, - parsed.style, + borderStyleValue, )) { await onSetStyle(property, value); } - for (const [property, value] of buildStrokeStyleUpdates(parsed.style, normalizedWidth)) { - await onSetStyle(property, value); - } }} - suffix={ - <> - - {borderColorValue} - - } />