// Test-only host for property-fields.browser.mjs (2026-11-10). Not a Storybook Story and not shipped (package "files" is // dist + ai). It restores the host data and host controls of the two demos removed in 5faa1f1 that the field checks // need: SW/Header › Property Fields (data-fields live / default / pinned hover) and the Header-hosted Property Panel // (원 1개 selection). Records and controls are copied from src/sw/sw-header.stories.tsx at 5faa1f1^ without changes. // Uses only the public @hm-design/sw API, like a consuming project. The pinned Hover column uses the Storybook pseudo-state // mirror (.storybook/pseudo-states.ts) exactly as the retired Story did. import { useEffect, useRef, useState, type ReactNode } from "react" import { createRoot } from "react-dom/client" import { SWHeader, SWHeaderPropertyPanel, SWTheme, swHeaderLinePatterns, type SWHeaderColorMode, type SWHeaderColorOption, type SWHeaderLayer, type SWHeaderLineType, type SWHeaderLineWeight, type SWHeaderProperty, type SWHeaderPropertyGroup, type SWHeaderPropertyMenus, type SWHeaderPropertySelection, } from "@hm-design/sw" import "@hm-design/sw/styles.css" import { ensurePseudoStates } from "../../../../.storybook/pseudo-states" const FIGMA_COLORS: SWHeaderColorOption[] = [ { id: "white", color: "#ffffff", label: "흰색" }, { id: "red", color: "#f10404", label: "빨강" }, { id: "green", color: "#48ff08", label: "초록" }, { id: "yellow", color: "#ffff00", label: "노랑" }, { id: "blue", color: "#0012ff", label: "파랑" }, { id: "magenta", color: "#ff00f6", label: "자홍" }, { id: "cyan", color: "#00f6ff", label: "청록" }, ] const FIGMA_LINE_TYPES: SWHeaderLineType[] = [ { id: "byblock", name: "ByBlock" }, { id: "bylayer", name: "ByLayer" }, { id: "continuous", name: "Continuous" }, { id: "center", name: "CENTER", pattern: swHeaderLinePatterns.center }, { id: "hidden", name: "HIDDEN", pattern: swHeaderLinePatterns.hidden }, ] const FIGMA_LINE_WEIGHTS: SWHeaderLineWeight[] = [ { id: "w-bylayer", name: "ByLayer" }, { id: "w-byblock", name: "ByBlock" }, { id: "w-default", name: "ByDefault", value: "0.25" }, { id: "w-user", name: "User", value: "0.25", editable: true }, ] const PROPERTY_GROUPS: SWHeaderPropertyGroup[] = [ { id: "common", collapsedCount: 6 }, { id: "shape", name: "형상", layout: "circle" }, { id: "position", name: "위치", layout: "position", positionLabel: "중간점" }, ] const PROPERTY_FIXTURE: SWHeaderProperty[] = [ { id: "layer", name: "레이어", value: "0_Layer_Cludmark", type: "text", groupId: "common", picker: "layer", validate: draft => draft.length <= 40 }, { id: "color", name: "색상", value: "255,229,0", type: "color", groupId: "common" }, { id: "line", name: "선스타일", value: "Continuous", type: "select", groupId: "common", unit: "(ByL)", linePreview: true, options: [{ id: "Continuous", name: "Continuous" }, { id: "CENTER", name: "CENTER" }, { id: "HIDDEN", name: "HIDDEN" }] }, { id: "weight", name: "선가중치", value: "0.25", type: "number", groupId: "common", min: 0 }, { id: "scale", name: "선축적", value: "1.0000", type: "number", groupId: "common", min: .0001, decimals: 4, unit: "(LTscale:1)" }, { id: "opacity", name: "투명도", value: "0.25", type: "slider", groupId: "common", min: 0, max: 1, step: .01, annotation: "(ByL)", unit: "%" }, { id: "link", name: "하이퍼링크", value: "--", type: "text", groupId: "common" }, { id: "thickness", name: "두께", value: "0.25", type: "number", groupId: "common", unit: "mm", min: 0 }, { id: "material", name: "재료", value: "-", type: "computed", groupId: "common", unit: "(ByL)" }, { id: "circumference", name: "원주", value: "0.25", type: "computed", groupId: "shape", graphicSlot: "circumference" }, { id: "diameter", name: "지름", value: "8.1502", type: "number", groupId: "shape", graphicSlot: "diameter", min: 0 }, { id: "radius", name: "반지름", value: "4.0526", type: "number", groupId: "shape", graphicSlot: "radius", min: 0 }, { id: "area", name: "면적", value: "51.5962", type: "computed", groupId: "shape", graphicSlot: "area" }, { id: "x", name: "X", value: "48.0808", type: "number", groupId: "position" }, { id: "y", name: "Y", value: "14.9160", type: "number", groupId: "position" }, { id: "z", name: "Z", value: "0.0000", type: "number", groupId: "position" }, ] const PROPERTY_COUNTS = [ { id: "shape", name: "도형", count: 10, row: 1, column: 1 }, { id: "line", name: "직선", count: 10, row: 1, column: 2 }, { id: "circle", name: "원", count: 1, row: 1, column: 3 }, { id: "ray", name: "광선", count: 1, row: 1, column: 4 }, { id: "curve", name: "곡선", count: 10, row: 2, column: 3 }, { id: "arc", name: "호", count: 1, row: 2, column: 4 }, { id: "polyline", name: "폴리선", count: 10, row: 3, column: 3 }, { id: "polyline2", name: "폴리선", count: 2, row: 3, column: 4 }, { id: "text1", name: "문자", count: 1, row: 4, column: 1 }, { id: "text2", name: "문자", count: 1, row: 4, column: 2 }, { id: "text3", name: "문자", count: 1, row: 4, column: 3 }, ] type PropertyExample = "none" | "all" | "circle" | "expanded" function propertySelection(example: PropertyExample): SWHeaderPropertySelection { return example === "none" ? { id: "none", title: "선택된 객체 - 없음", kind: "none" } : example === "all" ? { id: "all", title: "선택된 객체 - 전체 11", kind: "all", counts: PROPERTY_COUNTS } : { id: "circle", title: "선택된 객체 - 원1", kind: "single", overviewTitle: "선택된 객체 - 전체 11" } } function propertyFixture(example: PropertyExample) { return PROPERTY_FIXTURE.filter(item => (example === "none" || example === "all") ? item.groupId === "common" : true).map(item => ({ ...item, ...(example === "none" && item.id === "layer" ? { value: "0" } : {}), ...(example === "none" && item.id === "color" ? { value: "138,138,138" } : {}), ...(example === "all" && item.id === "layer" ? { mixed: true } : {}), ...((example === "none" || example === "all") && item.id === "opacity" ? { value: "0" } : {}), })) } const PROPERTY_LAYERS: SWHeaderLayer[] = [ { id: "0", name: "0" }, { id: "0_Layer_Cludmark", name: "0_Layer_Cludmark", color: "#ff0000" }, { id: "CC-D GNL", name: "CC-D GNL" }, { id: "Sample-Wall", name: "Sample-Wall", color: "#ffe500", visible: false }, { id: "Sample-Door", name: "Sample-Door", color: "#00ff00", frozen: true }, { id: "Sample-Text", name: "Sample-Text", color: "#00ffff", locked: true }, { id: "Sample-Defpoints", name: "Sample-Defpoints", color: "#808080", disabled: true }, ] const PROPERTY_LINE_TYPES: SWHeaderLineType[] = FIGMA_LINE_TYPES.map(item => ({ ...item, id: item.name })) const linePatternOf = (value: string) => PROPERTY_LINE_TYPES.find(item => item.id === value)?.pattern function usePropertyMenus(record: (line: string) => void, setProperties: (update: (items: SWHeaderProperty[]) => SWHeaderProperty[]) => void): SWHeaderPropertyMenus { const [layers, setLayers] = useState(PROPERTY_LAYERS) const [weights, setWeights] = useState(FIGMA_LINE_WEIGHTS) const [weightId, setWeightId] = useState("w-default") const [colorMode, setColorMode] = useState(null) const setValue = (id: string, value: string) => setProperties(items => items.map(item => item.id === id ? { ...item, value, mixed: false } : item)) const scale = "1.0000" const toggle = (key: "visible" | "frozen" | "locked") => (layer: SWHeaderLayer, next: boolean) => { record(`layer.${key}(${layer.id}, ${next})`) setLayers(items => items.map(item => item.id === layer.id ? { ...item, [key]: next } : item)) } return { layers, onLayerVisibilityChange: toggle("visible"), onLayerFreezeChange: toggle("frozen"), onLayerLockChange: toggle("locked"), onLayerDetail: () => record("layerDetail"), lineTypes: PROPERTY_LINE_TYPES, lineWeights: weights, activeLineWeightId: weightId, lineScale: scale, onLineWeightSelect: weight => { record(`lineWeight(${weight.id})`); setWeightId(weight.id); if (weight.value) setValue("weight", weight.value) }, onLineWeightValueChange: (weight, value) => { record(`lineWeightValue(${weight.id}, ${value})`); setWeights(items => items.map(item => item.id === weight.id ? { ...item, value } : item)) }, onLineScaleChange: value => { record(`lineScale(${value})`); setValue("scale", Number(value).toFixed(4)) }, onLineStyleDetail: () => record("lineStyleDetail"), colors: FIGMA_COLORS, colorMode, onColorModeChange: mode => { record(`colorMode(${mode})`); setColorMode(mode) }, onColorPick: property => record(`colorPick(${property.id})`), onColorDetail: () => record("colorDetail"), } } function PinHover({ selectors, children }: { selectors: string[]; children: ReactNode }) { const ref = useRef(null) useEffect(() => { ensurePseudoStates() ref.current?.querySelectorAll(selectors.join(",")).forEach(element => element.setAttribute("data-sb-hover", "")) }, [selectors]) return
{children}
} /* SW/Header › Property Fields (retired in 5faa1f1): the common rows of PROPERTY_FIXTURE plus a mixed layer row. Figma sample text 255,259,0 is out of range: shown as host text only, never accepted as input. */ const FIELD_FIXTURE: SWHeaderProperty[] = PROPERTY_FIXTURE.filter(item => item.groupId === "common").flatMap(item => item.id === "layer" ? [{ ...item, id: "layer-mixed", mixed: true }, item] : item.id === "color" ? [{ ...item, value: "255,259,0", swatch: "#ffe500" }] : [item]) const FIELD_HOVER = [".hm-sw-property__value[data-field]", ".hm-sw-property__compact"] function FieldPanel({ title, properties, onChange, menus }: { title: string; properties: SWHeaderProperty[]; onChange?: (property: SWHeaderProperty, value: string) => void; menus?: SWHeaderPropertyMenus }) { return } function PropertyFieldsHost() { const [properties, setProperties] = useState(FIELD_FIXTURE) const [log, setLog] = useState([]) const record = (line: string) => setLog(lines => [...lines.slice(-11), line]) const menus = usePropertyMenus(record, setProperties as (update: (items: SWHeaderProperty[]) => SWHeaderProperty[]) => void) const [noMenus, setNoMenus] = useState(false) const patch = (id: string, change: Partial) => setProperties(items => items.map(item => item.id === id ? { ...item, ...change } : item)) const change = (property: SWHeaderProperty, value: string) => { record(`property(${property.id}, ${value})`) patch(property.id, { value, mixed: false, ...(property.id === "line" ? { linePattern: linePatternOf(value) } : {}), ...(property.id === "color" ? { swatch: undefined } : {}) }) } return
외부 데이터

{log.join(" → ") || "Callback 없음"}

} /* SW/Header › Property Panel (retired in 5faa1f1): SWHeader with the propertyPanel prop and the selection buttons. */ function HeaderPanelHost() { const [selection, setSelection] = useState(propertySelection("none")) const [properties, setProperties] = useState(propertyFixture("none")) const [expanded, setExpanded] = useState([]) const menus = usePropertyMenus(() => {}, setProperties) const changeExample = (next: PropertyExample) => { setSelection(propertySelection(next)); setProperties(propertyFixture(next)) setExpanded(next === "expanded" ? ["common"] : []) } const panel = { selection, properties, groups: PROPERTY_GROUPS, expandedGroupIds: expanded, menus, onExpandedGroupsChange: setExpanded, onPropertyChange: (property: SWHeaderProperty, value: string) => setProperties(items => items.map(item => item.id === property.id ? { ...item, value, mixed: false } : item)), onOverviewClick: () => changeExample("all"), } return
{(["none", "all", "circle", "expanded"] as const).map((name, index) => )}
} const view = new URLSearchParams(location.search).get("view") createRoot(document.getElementById("root")!).render(view === "header" ? : )