@jimka/typescript-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -18
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js → AbstractBooleanInput-BGesvIZy.js} +2 -2
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js.map → AbstractBooleanInput-BGesvIZy.js.map} +1 -1
- package/dist/lib/{AbstractInput-DD-NpicS.js → AbstractInput-BxOtFrER.js} +2 -2
- package/dist/lib/{AbstractInput-DD-NpicS.js.map → AbstractInput-BxOtFrER.js.map} +1 -1
- package/dist/lib/AnimatedDropdown-CZQEqQi-.js +2 -0
- package/dist/lib/AnimatedDropdown-CZQEqQi-.js.map +1 -0
- package/dist/lib/Border-BgaZkc1R.js +2 -0
- package/dist/lib/Border-BgaZkc1R.js.map +1 -0
- package/dist/lib/{BoxLayout-DgZukB5_.js → BoxLayout-DrKGfsup.js} +2 -2
- package/dist/lib/{BoxLayout-DgZukB5_.js.map → BoxLayout-DrKGfsup.js.map} +1 -1
- package/dist/lib/Button-U1rsP4v8.js +2 -0
- package/dist/lib/Button-U1rsP4v8.js.map +1 -0
- package/dist/lib/{Card-UF0fq8cQ.js → Card-BVwOS8AG.js} +2 -2
- package/dist/lib/{Card-UF0fq8cQ.js.map → Card-BVwOS8AG.js.map} +1 -1
- package/dist/lib/ComboBox-B9EDPu6T.js +2 -0
- package/dist/lib/ComboBox-B9EDPu6T.js.map +1 -0
- package/dist/lib/Component-DXf5a3g0.js +2 -0
- package/dist/lib/Component-DXf5a3g0.js.map +1 -0
- package/dist/lib/Container-D5H8VxJK.js +2 -0
- package/dist/lib/{Container-3KsvsJ6S.js.map → Container-D5H8VxJK.js.map} +1 -1
- package/dist/lib/DOM-CGn7xyTw.js +2 -0
- package/dist/lib/DOM-CGn7xyTw.js.map +1 -0
- package/dist/lib/DragManager-CI4mZQFy.js +2 -0
- package/dist/lib/{DragManager-tPMnbjeO.js.map → DragManager-CI4mZQFy.js.map} +1 -1
- package/dist/lib/FieldDecorator-CJs9gwqH.js +2 -0
- package/dist/lib/{FieldDecorator-E7WzutjO.js.map → FieldDecorator-CJs9gwqH.js.map} +1 -1
- package/dist/lib/{Fit-CFIr4P9R.js → Fit-DjRsiwlg.js} +2 -2
- package/dist/lib/{Fit-CFIr4P9R.js.map → Fit-DjRsiwlg.js.map} +1 -1
- package/dist/lib/Glyph-KEf4nIv2.js +2 -0
- package/dist/lib/Glyph-KEf4nIv2.js.map +1 -0
- package/dist/lib/{Grid-Czux--Mr.js → Grid-DdL9TVeJ.js} +2 -2
- package/dist/lib/{Grid-Czux--Mr.js.map → Grid-DdL9TVeJ.js.map} +1 -1
- package/dist/lib/GridConstraints-DkLueyrf.js +2 -0
- package/dist/lib/GridConstraints-DkLueyrf.js.map +1 -0
- package/dist/lib/{HBox-Q8y1PHlk.js → HBox-Ce0GTCJP.js} +2 -2
- package/dist/lib/{HBox-Q8y1PHlk.js.map → HBox-Ce0GTCJP.js.map} +1 -1
- package/dist/lib/{Header-zPdvC2AX.js → Header-BQIVpBte.js} +2 -2
- package/dist/lib/{Header-zPdvC2AX.js.map → Header-BQIVpBte.js.map} +1 -1
- package/dist/lib/{IconText-BvGfu7eq.js → IconText-Bo4Qcnot.js} +2 -2
- package/dist/lib/{IconText-BvGfu7eq.js.map → IconText-Bo4Qcnot.js.map} +1 -1
- package/dist/lib/LayoutConstraints-DkctJZXB.js +2 -0
- package/dist/lib/LayoutConstraints-DkctJZXB.js.map +1 -0
- package/dist/lib/LayoutSerialization-BT66TY83.js +2 -0
- package/dist/lib/LayoutSerialization-BT66TY83.js.map +1 -0
- package/dist/lib/LayoutSizes-DU3qSq0w.js +2 -0
- package/dist/lib/LayoutSizes-DU3qSq0w.js.map +1 -0
- package/dist/lib/List-DtTVTyWW.js +2 -0
- package/dist/lib/List-DtTVTyWW.js.map +1 -0
- package/dist/lib/Menu-Dz4L7EKJ.js +2 -0
- package/dist/lib/Menu-Dz4L7EKJ.js.map +1 -0
- package/dist/lib/MenuButton-VJ5uC3c1.js +2 -0
- package/dist/lib/{MenuButton-JQQaKThJ.js.map → MenuButton-VJ5uC3c1.js.map} +1 -1
- package/dist/lib/Panel-DzWdYwq1.js +2 -0
- package/dist/lib/Panel-DzWdYwq1.js.map +1 -0
- package/dist/lib/Position-A39T7-_1.js +2 -0
- package/dist/lib/Position-A39T7-_1.js.map +1 -0
- package/dist/lib/ProgressSpinner-zJKtGyNJ.js +2 -0
- package/dist/lib/ProgressSpinner-zJKtGyNJ.js.map +1 -0
- package/dist/lib/RadioButton-CYSSv4nV.js +2 -0
- package/dist/lib/RadioButton-CYSSv4nV.js.map +1 -0
- package/dist/lib/{RovingTabIndex-CFIzIjjU.js → RovingTabIndex-BSZ31Vp5.js} +2 -2
- package/dist/lib/{RovingTabIndex-CFIzIjjU.js.map → RovingTabIndex-BSZ31Vp5.js.map} +1 -1
- package/dist/lib/Scrollbar-BhtOjqkG.js +2 -0
- package/dist/lib/Scrollbar-BhtOjqkG.js.map +1 -0
- package/dist/lib/Slider-CdXm7E64.js +2 -0
- package/dist/lib/Slider-CdXm7E64.js.map +1 -0
- package/dist/lib/{Spacer-BVQncB5B.js → Spacer-D9NGW4QG.js} +2 -2
- package/dist/lib/{Spacer-BVQncB5B.js.map → Spacer-D9NGW4QG.js.map} +1 -1
- package/dist/lib/TabButton-CThevdY_.js +2 -0
- package/dist/lib/{TabButton-pZKFYg7b.js.map → TabButton-CThevdY_.js.map} +1 -1
- package/dist/lib/Text-gz6qjFfP.js +2 -0
- package/dist/lib/Text-gz6qjFfP.js.map +1 -0
- package/dist/lib/Tooltip-tkXDk5Bo.js +3 -0
- package/dist/lib/Tooltip-tkXDk5Bo.js.map +1 -0
- package/dist/lib/{VBox-D5B9ecw0.js → VBox-EIqw85Rh.js} +2 -2
- package/dist/lib/{VBox-D5B9ecw0.js.map → VBox-EIqw85Rh.js.map} +1 -1
- package/dist/lib/VirtualRowView-BxpHQ_u8.js +2 -0
- package/dist/lib/{VirtualRowView-Con9SWiU.js.map → VirtualRowView-BxpHQ_u8.js.map} +1 -1
- package/dist/lib/VirtualScroller-DwEEJE1_.js +2 -0
- package/dist/lib/{VirtualScroller-BPDmO1ir.js.map → VirtualScroller-DwEEJE1_.js.map} +1 -1
- package/dist/lib/component/button.es.js +1 -1
- package/dist/lib/component/button.es.js.map +1 -1
- package/dist/lib/component/chart.es.js +1 -1
- package/dist/lib/component/chart.es.js.map +1 -1
- package/dist/lib/component/container.es.js +1 -1
- package/dist/lib/component/container.es.js.map +1 -1
- package/dist/lib/component/diagram.es.js +1 -1
- package/dist/lib/component/diagram.es.js.map +1 -1
- package/dist/lib/component/display.es.js +1 -1
- package/dist/lib/component/display.es.js.map +1 -1
- package/dist/lib/component/editor.es.js +3 -1
- package/dist/lib/component/editor.es.js.map +1 -1
- package/dist/lib/component/input.es.js +1 -1
- package/dist/lib/component/input.es.js.map +1 -1
- package/dist/lib/component/list.es.js +1 -1
- package/dist/lib/component/list.es.js.map +1 -1
- package/dist/lib/component/menubar.es.js +1 -1
- package/dist/lib/component/menubar.es.js.map +1 -1
- package/dist/lib/component/table.es.js +2 -2
- package/dist/lib/component/table.es.js.map +1 -1
- package/dist/lib/component/tree.es.js +1 -1
- package/dist/lib/component/tree.es.js.map +1 -1
- package/dist/lib/core.es.js +1 -1
- package/dist/lib/core.es.js.map +1 -1
- package/dist/lib/layout.es.js +1 -1
- package/dist/lib/overlay.es.js +1 -1
- package/dist/lib/overlay.es.js.map +1 -1
- package/dist/lib/primitive.es.js +1 -1
- package/dist/lib/router.es.js +2 -0
- package/dist/lib/router.es.js.map +1 -0
- package/dist/lib/types/component/button/Button.d.ts +4 -3
- package/dist/lib/types/component/chart/AbstractChart.d.ts +1 -1
- package/dist/lib/types/component/chart/ChartLegend.d.ts +1 -1
- package/dist/lib/types/component/container/DialogBackdrop.d.ts +2 -1
- package/dist/lib/types/component/container/MenuItem.d.ts +1 -1
- package/dist/lib/types/component/container/ScrollStrip.d.ts +1 -0
- package/dist/lib/types/component/container/SplitGutter.d.ts +3 -2
- package/dist/lib/types/component/container/TabBar.d.ts +1 -2
- package/dist/lib/types/component/container/TabPanel.d.ts +6 -3
- package/dist/lib/types/component/container/WindowBorder.d.ts +2 -1
- package/dist/lib/types/component/container/WindowHeader.d.ts +2 -1
- package/dist/lib/types/component/display/Glyph.d.ts +2 -1
- package/dist/lib/types/component/display/Markdown.d.ts +11 -1
- package/dist/lib/types/component/display/PaginationBar.d.ts +1 -1
- package/dist/lib/types/component/display/SpinnerWrap.d.ts +2 -0
- package/dist/lib/types/component/display/Video.d.ts +1 -1
- package/dist/lib/types/component/display/VideoPlayer.d.ts +1 -1
- package/dist/lib/types/component/display/index.d.ts +1 -1
- package/dist/lib/types/component/editor/CodeEditor.d.ts +2 -1
- package/dist/lib/types/component/editor/MarkdownEditor.d.ts +7 -1
- package/dist/lib/types/component/editor/markdownTableTransformer.d.ts +2 -0
- package/dist/lib/types/component/input/Checkbox.d.ts +2 -1
- package/dist/lib/types/component/input/Link.d.ts +1 -1
- package/dist/lib/types/component/input/RadioButton.d.ts +2 -1
- package/dist/lib/types/component/input/Slider.d.ts +2 -1
- package/dist/lib/types/component/input/Text.d.ts +1 -3
- package/dist/lib/types/component/input/TextInput.d.ts +3 -2
- package/dist/lib/types/component/list/AbstractSelectableList.d.ts +2 -1
- package/dist/lib/types/component/menubar/MenuBarButton.d.ts +1 -1
- package/dist/lib/types/core/AnimatedDropdown.d.ts +6 -2
- package/dist/lib/types/core/Animation.d.ts +9 -6
- package/dist/lib/types/core/Component.d.ts +16 -11
- package/dist/lib/types/core/DOM.d.ts +14 -0
- package/dist/lib/types/core/Event.d.ts +12 -6
- package/dist/lib/types/core/index.d.ts +2 -2
- package/dist/lib/types/layout/Accordion.d.ts +2 -0
- package/dist/lib/types/layout/Border.d.ts +1 -0
- package/dist/lib/types/layout/CollapseSupport.d.ts +5 -1
- package/dist/lib/types/layout/LayoutConstraints.d.ts +1 -0
- package/dist/lib/types/layout/LayoutManager.d.ts +2 -0
- package/dist/lib/types/layout/Split.d.ts +1 -0
- package/dist/lib/types/layout/Tab.d.ts +10 -3
- package/dist/lib/types/overlay/AbstractWindow.d.ts +9 -2
- package/dist/lib/types/overlay/Dialog.d.ts +6 -0
- package/dist/lib/types/overlay/Dock.d.ts +12 -2
- package/dist/lib/types/overlay/Drawer.d.ts +5 -0
- package/dist/lib/types/overlay/Menu.d.ts +3 -1
- package/dist/lib/types/overlay/Notification.d.ts +3 -0
- package/dist/lib/types/overlay/Popover.d.ts +3 -1
- package/dist/lib/types/overlay/Rail.d.ts +7 -0
- package/dist/lib/types/overlay/Tooltip.d.ts +3 -0
- package/dist/lib/types/overlay/index.d.ts +1 -1
- package/dist/lib/types/router/RoutePattern.d.ts +18 -0
- package/dist/lib/types/router/Router.d.ts +36 -0
- package/dist/lib/types/router/index.d.ts +2 -0
- package/dist/lib/validation.es.js +1 -1
- package/llms.txt +9 -5
- package/package.json +6 -1
- package/dist/lib/AnimatedDropdown-BbGCs70E.js +0 -2
- package/dist/lib/AnimatedDropdown-BbGCs70E.js.map +0 -1
- package/dist/lib/Border-oZC_SDy8.js +0 -2
- package/dist/lib/Border-oZC_SDy8.js.map +0 -1
- package/dist/lib/Button-DEUtBqK4.js +0 -2
- package/dist/lib/Button-DEUtBqK4.js.map +0 -1
- package/dist/lib/ComboBox-GOBOAJ1N.js +0 -2
- package/dist/lib/ComboBox-GOBOAJ1N.js.map +0 -1
- package/dist/lib/Component-BR2f0Itb.js +0 -2
- package/dist/lib/Component-BR2f0Itb.js.map +0 -1
- package/dist/lib/Container-3KsvsJ6S.js +0 -2
- package/dist/lib/DragManager-tPMnbjeO.js +0 -2
- package/dist/lib/FieldDecorator-E7WzutjO.js +0 -2
- package/dist/lib/Glyph-WUqTF_Z9.js +0 -2
- package/dist/lib/Glyph-WUqTF_Z9.js.map +0 -1
- package/dist/lib/GridConstraints-Q3wvuovN.js +0 -2
- package/dist/lib/GridConstraints-Q3wvuovN.js.map +0 -1
- package/dist/lib/LayoutConstraints-CHI2lhPd.js +0 -2
- package/dist/lib/LayoutConstraints-CHI2lhPd.js.map +0 -1
- package/dist/lib/LayoutSerialization-CWU0iEuV.js +0 -2
- package/dist/lib/LayoutSerialization-CWU0iEuV.js.map +0 -1
- package/dist/lib/LayoutSizes-BW4wyGDI.js +0 -2
- package/dist/lib/LayoutSizes-BW4wyGDI.js.map +0 -1
- package/dist/lib/List-D-GhoVgB.js +0 -2
- package/dist/lib/List-D-GhoVgB.js.map +0 -1
- package/dist/lib/Menu-Wz4MK6sA.js +0 -2
- package/dist/lib/Menu-Wz4MK6sA.js.map +0 -1
- package/dist/lib/MenuButton-JQQaKThJ.js +0 -2
- package/dist/lib/Panel-DIsdaqc2.js +0 -2
- package/dist/lib/Panel-DIsdaqc2.js.map +0 -1
- package/dist/lib/Position-B-WfZYAJ.js +0 -2
- package/dist/lib/Position-B-WfZYAJ.js.map +0 -1
- package/dist/lib/ProgressSpinner-Dzaka7_c.js +0 -2
- package/dist/lib/ProgressSpinner-Dzaka7_c.js.map +0 -1
- package/dist/lib/RadioButton-eI3-vXn3.js +0 -2
- package/dist/lib/RadioButton-eI3-vXn3.js.map +0 -1
- package/dist/lib/Scrollbar-DchfiFKC.js +0 -2
- package/dist/lib/Scrollbar-DchfiFKC.js.map +0 -1
- package/dist/lib/Slider-xRxNSjFN.js +0 -2
- package/dist/lib/Slider-xRxNSjFN.js.map +0 -1
- package/dist/lib/TabButton-pZKFYg7b.js +0 -2
- package/dist/lib/Text-D1RrBY52.js +0 -2
- package/dist/lib/Text-D1RrBY52.js.map +0 -1
- package/dist/lib/Tooltip-4lU_tf6r.js +0 -3
- package/dist/lib/Tooltip-4lU_tf6r.js.map +0 -1
- package/dist/lib/VirtualRowView-Con9SWiU.js +0 -2
- package/dist/lib/VirtualScroller-BPDmO1ir.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagram.es.js","names":[],"sources":["../../../src/typescript/lib/component/diagram/ElkLayoutEngine.ts","../../../src/typescript/lib/component/diagram/DiagramNode.ts","../../../src/typescript/lib/component/diagram/DiagramGroupNode.ts","../../../src/typescript/lib/component/diagram/DiagramEdgeLayer.ts","../../../src/typescript/lib/component/diagram/DiagramView.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// The sole adapter between the framework-native diagram model and ElkJS. It is\n// the only module that names ELK types or imports `elkjs`; everything else in\n// the family speaks `DiagramData` / `DiagramLayoutResult`, keeping the layout\n// engine swappable. ELK takes JSON and returns JSON — it never touches the DOM,\n// so it needs no `DOM.sink` / `DOM.source` seam. `elkjs` is an optional peer\n// dependency imported lazily the first time a diagram lays out (mirroring\n// `StoreWorkerClient.ensureWorker`) and externalised from the library bundle so\n// its GWT blob never lands in the core chunk.\n\nimport type { DiagramData, DiagramNodeData } from \"~/component/diagram/DiagramModel.js\";\n\n/** A point in ELK's layout coordinate space. */\nexport interface ElkPoint {\n x: number;\n y: number;\n}\n\n/**\n * One routed section of an ELK edge: a straight run from `startPoint` to\n * `endPoint`, optionally threaded through `bendPoints`.\n */\nexport interface ElkEdgeSection {\n startPoint: ElkPoint;\n endPoint: ElkPoint;\n bendPoints?: ElkPoint[];\n}\n\n/** The engine-agnostic layout result consumed by `DiagramView`. */\nexport interface DiagramLayoutResult {\n /** Each node's absolute position and size in graph space. */\n nodes: Array<{ id: string; x: number; y: number; width: number; height: number }>;\n /** Each edge's routed sections. */\n edges: Array<{ id: string; sections: ElkEdgeSection[] }>;\n /** Graph bounding-box width. */\n width: number;\n /** Graph bounding-box height. */\n height: number;\n}\n\n/** ELK port shape: a fixed anchor on a node an edge can attach to. */\ninterface ElkPort {\n id: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n layoutOptions?: Record<string, string>;\n}\n\n/** ELK graph node shape (input carries sizes/options; output adds x/y). */\ninterface ElkNode {\n id: string;\n width?: number;\n height?: number;\n x?: number;\n y?: number;\n layoutOptions?: Record<string, string>;\n children?: ElkNode[];\n edges?: ElkExtendedEdge[];\n ports?: ElkPort[];\n}\n\n/** ELK edge shape with explicit source/target endpoint lists. */\ninterface ElkExtendedEdge {\n id: string;\n sources: string[];\n targets: string[];\n sections?: ElkEdgeSection[];\n /**\n * The node whose origin the routed `sections` coordinates are relative to,\n * as reported by ELK. For an edge between two nodes nested inside the same\n * container, ELK routes it in that container and reports container-relative\n * coordinates here (see {@link mapElkResult}, which shifts them to absolute).\n */\n container?: string;\n}\n\n/** Minimal structural type for the lazily-imported ELK instance. */\ninterface ElkInstance {\n layout(graph: unknown): Promise<unknown>;\n}\n\n/**\n * Fallback node width in pixels, used when a node carries neither an explicit\n * `width` nor a measurable preferred width. Sized to comfortably hold a short\n * label so a graph still lays out sensibly rather than collapsing to zero-area\n * nodes ELK would overlap.\n */\nconst DEFAULT_NODE_WIDTH = 120;\n\n/**\n * Fallback node height in pixels, mirroring {@link DEFAULT_NODE_WIDTH}. Tracks a\n * single label line plus vertical breathing room.\n */\nconst DEFAULT_NODE_HEIGHT = 40;\n\n/**\n * Merges layout-option maps left-to-right, so later arguments win. `undefined`\n * maps are skipped. Used to layer graph options over the view-level defaults.\n *\n * @param maps - Option maps in ascending precedence order.\n * @returns The merged option map.\n */\nfunction mergeLayoutOptions(...maps: Array<Record<string, string> | undefined>): Record<string, string> {\n const merged: Record<string, string> = {};\n\n for (const map of maps) {\n if (map) {\n Object.assign(merged, map);\n }\n }\n\n return merged;\n}\n\n/**\n * Default padding reserved inside every container's ELK box, keyed by side.\n * The default renderer (`DiagramGroupNode`) paints its header label a few\n * pixels from the top-left corner, so without a reserved top inset ELK could\n * place a child flush against — or under — the title. The top inset reserves\n * the header row (its inset + one text/glyph line) plus a ~10px gap so the\n * first child sits clearly below the title, not tight against it. The\n * left/bottom/right values match ELK's own built-in default (12px); a consumer\n * supplying a custom `groupRenderer` with a taller or shorter header overrides\n * this via the container's own `layoutOptions`.\n */\nconst CONTAINER_PADDING_DEFAULT: Record<string, string> = { \"elk.padding\": \"[top=34,left=12,bottom=12,right=12]\" };\n\n/**\n * Maps one framework-native node to its ELK counterpart, recursing into\n * `children` for a compound container. A container (non-empty `children`)\n * carries no explicit `width`/`height` — ELK computes its box from its\n * contents — and its children are mapped the same way, so nesting is\n * unbounded. A container's `layoutOptions` resolve as\n * `CONTAINER_PADDING_DEFAULT` < `node.layoutOptions` (the node's own option\n * wins), reserving header clearance by default while staying overridable. A\n * leaf (no children) maps exactly as before.\n *\n * @param node - The framework-native node.\n * @param sizes - Per-leaf resolved sizes (explicit size, else preferred size).\n * @returns The mapped ELK node.\n */\nfunction mapDiagramNode(\n node: DiagramNodeData,\n sizes: Map<string, { width: number; height: number }>,\n): ElkNode {\n if (node.children && node.children.length > 0) {\n return {\n id: node.id,\n layoutOptions: mergeLayoutOptions(CONTAINER_PADDING_DEFAULT, node.layoutOptions),\n children: node.children.map((child) => mapDiagramNode(child, sizes)),\n };\n }\n\n const size = sizes.get(node.id);\n\n return {\n id: node.id,\n width: node.width ?? size?.width ?? DEFAULT_NODE_WIDTH,\n height: node.height ?? size?.height ?? DEFAULT_NODE_HEIGHT,\n layoutOptions: node.layoutOptions,\n ports: node.ports?.map((p) => ({\n id: p.id,\n x: p.x,\n y: p.y,\n width: p.width,\n height: p.height,\n layoutOptions: p.side !== undefined ? { \"elk.port.side\": p.side } : undefined,\n })),\n };\n}\n\n/**\n * Root-level ELK options enabling cross-container edge routing. Without it,\n * an edge declared on the root (as every {@link DiagramEdgeData} is) between\n * two nodes nested under different containers may be dropped or mis-routed by\n * ELK's hierarchical layout. Merged in as the lowest-precedence tier, so a\n * view-level default or a graph's own `layoutOptions` can still override it.\n */\nconst HIERARCHY_HANDLING_DEFAULT: Record<string, string> = { \"elk.hierarchyHandling\": \"INCLUDE_CHILDREN\" };\n\n/**\n * Maps the framework-native graph to an ELK graph JSON. Pure and synchronous —\n * no `elkjs` import — so it is unit-testable directly.\n *\n * Graph-level options resolve as `HIERARCHY_HANDLING_DEFAULT` < `defaults` <\n * `data.layoutOptions` (graph wins over view defaults, which win over the\n * hierarchy-handling default) on the root; each node carries its own\n * `layoutOptions`, which ELK resolves over the inherited root options so a\n * per-node option wins over all three.\n *\n * @param data - The framework-native graph.\n * @param sizes - Per-node resolved sizes (explicit size, else preferred size).\n * @param defaults - View-level default ELK options applied to every layout.\n * @returns The ELK graph JSON ready for `elk.layout`.\n */\nexport function buildElkGraph(\n data: DiagramData,\n sizes: Map<string, { width: number; height: number }>,\n defaults?: Record<string, string>,\n): ElkNode {\n const children: ElkNode[] = data.nodes.map((node) => mapDiagramNode(node, sizes));\n\n const edges: ElkExtendedEdge[] = data.edges.map((edge) => ({\n id: edge.id,\n sources: [edge.sourcePort ?? edge.source],\n targets: [edge.targetPort ?? edge.target],\n }));\n\n return {\n id: \"root\",\n layoutOptions: mergeLayoutOptions(HIERARCHY_HANDLING_DEFAULT, defaults, data.layoutOptions),\n children,\n edges,\n };\n}\n\n/**\n * Recursively flattens an ELK node (and its `children`, if any) into\n * `out`, threading an `(offsetX, offsetY)` accumulator. ELK reports each\n * child's `x`/`y` relative to its own parent, so a node's absolute position is\n * its parent's absolute origin plus its own relative `x`/`y`; a container then\n * recurses with that absolute origin as the new offset for its own children.\n *\n * @param node - The ELK node to flatten (a container or a leaf).\n * @param offsetX - The accumulated absolute x of `node`'s parent (0 at the root).\n * @param offsetY - The accumulated absolute y of `node`'s parent (0 at the root).\n * @param out - The flat output array nodes are appended to, in traversal order.\n */\nfunction flattenElkNode(\n node: ElkNode,\n offsetX: number,\n offsetY: number,\n out: Array<{ id: string; x: number; y: number; width: number; height: number }>,\n): void {\n const x = offsetX + (node.x ?? 0);\n const y = offsetY + (node.y ?? 0);\n\n out.push({ id: node.id, x, y, width: node.width ?? 0, height: node.height ?? 0 });\n\n for (const child of node.children ?? []) {\n flattenElkNode(child, x, y, out);\n }\n}\n\n/**\n * Collects every ELK edge in the tree — the root's own `edges` plus any nested\n * in a container's `edges` — resolving each edge's container id (the node its\n * routed coordinates are relative to). ELK tags a routed edge with its own\n * `container`; absent that, the edge is relative to the node whose `edges`\n * array holds it (the root for a flat graph).\n *\n * @param node - The ELK node whose `edges` (and descendants') to collect.\n * @param out - The flat output array edges are appended to, each paired with\n * its resolved container id.\n */\nfunction collectElkEdges(node: ElkNode, out: Array<{ edge: ElkExtendedEdge; container: string }>): void {\n for (const edge of node.edges ?? []) {\n out.push({ edge, container: edge.container ?? node.id });\n }\n\n for (const child of node.children ?? []) {\n collectElkEdges(child, out);\n }\n}\n\n/**\n * Shifts every point of an edge's routed sections by `origin`, translating\n * container-relative ELK coordinates into absolute graph space. Returns the\n * sections unchanged when `origin` is the graph origin, so a flat graph's\n * root-relative edges pass through untouched.\n *\n * @param sections - The edge's routed sections, in their container's frame.\n * @param origin - The absolute origin of the edge's container.\n * @returns The sections with every point translated into absolute coordinates.\n */\nfunction offsetSections(sections: ElkEdgeSection[], origin: ElkPoint): ElkEdgeSection[] {\n if (origin.x === 0 && origin.y === 0) {\n return sections;\n }\n\n const shift = (point: ElkPoint): ElkPoint => ({ x: point.x + origin.x, y: point.y + origin.y });\n\n return sections.map((section) => ({\n startPoint: shift(section.startPoint),\n endPoint: shift(section.endPoint),\n bendPoints: section.bendPoints?.map(shift),\n }));\n}\n\n/**\n * Maps an ELK layout result back to the engine-agnostic\n * {@link DiagramLayoutResult}. Pure and synchronous, so it is unit-testable\n * directly. A container's descendants are flattened into the same output list\n * as its siblings, each carrying absolute (not parent-relative) coordinates —\n * see {@link flattenElkNode}. Edge sections get the same treatment: ELK reports\n * an intra-container edge's route relative to that container's origin, so each\n * edge is shifted by its container's absolute position (a flat graph's\n * root-relative edges are shifted by the zero origin, i.e. left unchanged).\n *\n * @param result - The root ELK node returned by `elk.layout`.\n * @returns The mapped layout result.\n */\nexport function mapElkResult(result: ElkNode): DiagramLayoutResult {\n const nodes: Array<{ id: string; x: number; y: number; width: number; height: number }> = [];\n\n for (const child of result.children ?? []) {\n flattenElkNode(child, 0, 0, nodes);\n }\n\n // Absolute origin of every node (containers included), so an edge routed in\n // a container's frame can be lifted into absolute graph space. The root maps\n // to the zero origin.\n const origins = new Map<string, ElkPoint>([[result.id, { x: 0, y: 0 }]]);\n\n for (const node of nodes) {\n origins.set(node.id, { x: node.x, y: node.y });\n }\n\n const collected: Array<{ edge: ElkExtendedEdge; container: string }> = [];\n collectElkEdges(result, collected);\n\n const edges = collected.map(({ edge, container }) => ({\n id: edge.id,\n sections: offsetSections(edge.sections ?? [], origins.get(container) ?? { x: 0, y: 0 }),\n }));\n\n return {\n nodes,\n edges,\n width: result.width ?? 0,\n height: result.height ?? 0,\n };\n}\n\n/**\n * Lazily-loaded ELK layout adapter. One instance owns one lazily-imported ELK\n * engine; the import fires on the first {@link ElkLayoutEngine.layout} call and\n * the engine is reused afterward.\n *\n * @category Components\n */\nexport class ElkLayoutEngine {\n\n private _elk: ElkInstance | null = null;\n private readonly _workerUrl?: string;\n\n /**\n * @param workerUrl - Optional URL of a consumer-hosted `elk-worker.js`. When\n * set, ELK runs its layout compute off the main thread; otherwise the\n * zero-config main-thread bundle is used.\n */\n constructor(workerUrl?: string) {\n this._workerUrl = workerUrl;\n }\n\n /**\n * Lazily imports ELK (if needed), maps the model to ELK JSON, runs the\n * layout, and maps the result back.\n *\n * @param data - The framework-native graph.\n * @param sizes - Per-node resolved sizes.\n * @param defaults - View-level default ELK options.\n * @returns The mapped layout result.\n * @throws Error - If `elkjs` is not installed / cannot be imported.\n */\n async layout(\n data: DiagramData,\n sizes: Map<string, { width: number; height: number }>,\n defaults?: Record<string, string>,\n ): Promise<DiagramLayoutResult> {\n const elk = await this.ensureElk();\n const graph = buildElkGraph(data, sizes, defaults);\n const result = await elk.layout(graph);\n\n return mapElkResult(result as ElkNode);\n }\n\n /**\n * Returns the ELK engine, importing and constructing it on first use. The\n * dynamic `import(\"elkjs/...\")` is left external in the library build so the\n * GWT bundle resolves from the consumer's install rather than being inlined.\n *\n * @returns The ELK engine instance.\n */\n private async ensureElk(): Promise<ElkInstance> {\n if (this._elk) {\n return this._elk;\n }\n\n // `elkjs` is an optional peer dep, typed by the local ambient shim in\n // `elkjs.d.ts` and resolved by the consumer's bundler at runtime.\n const { default: ELK } = await import(\"elkjs/lib/elk.bundled.js\");\n\n const elk = this._workerUrl ? new ELK({ workerUrl: this._workerUrl }) : new ELK();\n this._elk = elk;\n\n return elk;\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// The default node renderer for a DiagramView: a themed rounded box wrapping a\n// glyph + label (or a bare label). Its content-derived preferred size is what\n// the view feeds to ELK when a node carries no explicit width/height.\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { IconText } from \"~/component/display/IconText.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link DiagramNode}.\n *\n * @category Components\n */\nexport interface DiagramNodeOptions extends PanelOptions {\n /** Label text shown inside the node. */\n label?: string;\n /** Optional registered glyph name shown before the label. */\n glyph?: string;\n /** Whether the node starts selected. */\n selected?: boolean;\n}\n\n/**\n * User-overridable defaults. The node paints a themed rounded box; its interior\n * inset gives the label structural breathing room away from the border.\n */\nconst _defaultDiagramNodeOptions: Partial<DiagramNodeOptions> = {\n // Vertical 4px / horizontal 8px so a short label never sits flush against\n // the rounded border — structural interior spacing, not cosmetic nudging.\n insets: new Insets(4, 8, 4, 8),\n};\n\n/** Corner radius in pixels for the node's rounded box. */\nconst NODE_BORDER_RADIUS = \"4px\";\n\n/**\n * The default themed node renderer for a\n * [`DiagramView`](/api/component/diagram/classes/DiagramView). Composes a glyph\n * and label inside a rounded, theme-aware box and toggles a `.selected` state\n * rule when selected.\n *\n * @category Components\n */\nclass DiagramNode extends Panel<DiagramNodeOptions> {\n\n /** The inner glyph+label (or bare label) component. */\n private _content!: IconText | Text;\n\n // Lazy `.selected` state rule. The slot caches the wrapper returned by\n // Component's `createStyleRule` builder, which dedupes by selector suffix.\n private declare _selectedStyleRule?: StyleRule;\n private get selectedStyleRule(): StyleRule {\n return this._selectedStyleRule ??= this.createStyleRule(\".selected\");\n }\n\n constructor(options?: DiagramNodeOptions) {\n super(options, { ..._defaultDiagramNodeOptions, layoutManager: new Fit() });\n\n this.setBackgroundColor(\"var(--ts-ui-diagram-node-bg, var(--ts-ui-button-bg, rgb(245, 245, 245)))\");\n this.setBorder(\"1px solid var(--ts-ui-border-color, rgb(180, 180, 180))\");\n this.setBorderRadius(NODE_BORDER_RADIUS);\n this.setCursor(\"pointer\");\n\n this.selectedStyleRule.set(\"borderColor\", \"var(--ts-ui-accent-color, rgb(30, 100, 200))\");\n this.selectedStyleRule.set(\"backgroundColor\", \"var(--ts-ui-diagram-node-selected-bg, var(--ts-ui-table-row-selected, rgba(30, 100, 200, 0.15)))\");\n\n // Content children are built here (not during super's cascade), so the\n // label/glyph/selected values cached pure in `applyOptions` are\n // dispatched now that the row exists.\n this.buildContent(this._options.glyph, this._options.label ?? \"\");\n\n if (this._options.selected !== undefined) {\n this.setSelected(this._options.selected);\n }\n }\n\n /**\n * Caches the label/glyph/selected fields pure to `_options`; they are\n * dispatched from the constructor body once the content child exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DiagramNodeOptions): this {\n super.applyOptions(options);\n\n if (options.label !== undefined) this._options.label = options.label;\n if (options.glyph !== undefined) this._options.glyph = options.glyph;\n if (options.selected !== undefined) this._options.selected = options.selected;\n\n return this;\n }\n\n /**\n * Builds (or rebuilds) the inner content child — an `IconText` when a glyph\n * is present, else a bare `Text`.\n *\n * @param glyph - Optional glyph name.\n * @param label - The label text.\n */\n private buildContent(glyph: string | undefined, label: string): void {\n if (this._content) {\n this.removeComponent(this._content);\n }\n\n this._content = glyph !== undefined ? new IconText(glyph, label) : new Text(label);\n\n // Every Component stamps its own `cursor` (defaulting to `default`) onto\n // its CSS rule, so the label/glyph would override the node's `pointer`\n // wherever they sit under the cursor — the hover cursor would flicker\n // between pointer (over padding) and arrow (over the text). Make the\n // content transparent to pointer events so hover + clicks land on the\n // node itself; `pointer-events: none` inherits, so this one call also\n // covers the glyph and text nested inside an `IconText`. Mirrors how\n // `Button` frees its label row so the button's cursor governs.\n this._content.setPointerEvents(\"none\");\n\n this.addComponent(this._content);\n }\n\n /**\n * Updates the node's label text.\n *\n * @param value - The new label.\n *\n * @returns This node, for method chaining.\n */\n setLabel(value: string): this {\n this._options.label = value;\n this._content.setText(value);\n\n return this;\n }\n\n /**\n * Returns the node's label, or `null` when none was set.\n *\n * @returns The label text, or `null`.\n */\n getLabel(): string | null {\n return this._options.label ?? null;\n }\n\n /**\n * Sets the selected state and toggles the `.selected` CSS class.\n *\n * @param value - True to select, false to deselect.\n *\n * @returns This node, for method chaining.\n */\n setSelected(value: boolean): this {\n this._options.selected = value;\n\n const element = this.getElement();\n\n if (element) {\n DOM.sink.apply(element, { toggleClass: { selected: value } });\n }\n\n return this;\n }\n\n /**\n * Returns whether the node is currently selected.\n *\n * @returns True when selected.\n */\n isSelected(): boolean {\n return this._options.selected ?? false;\n }\n\n /**\n * Re-applies the cached selected class after (re-)render, since a fresh\n * element starts without it.\n *\n * @param element - Optional element from the render pipeline.\n *\n * @returns This node, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element || this.getElement();\n\n if (el && this.isSelected()) {\n DOM.sink.apply(el, { toggleClass: { selected: true } });\n }\n\n return this;\n }\n}\n\nconst DiagramNodeCallable = callable(DiagramNode);\ntype DiagramNodeCallable = DiagramNode;\nexport {\n DiagramNode as _DiagramNode,\n DiagramNodeCallable as DiagramNode,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// The default container renderer for a DiagramView compound node: a titled,\n// translucent box painted behind its (flat-sibling, not DOM-child) leaf/child\n// components. `DiagramView` positions this box at ELK's computed absolute\n// bounds and paints it at the lowest z-index among a compound graph's nodes,\n// so it never intercepts a click meant for a leaf sitting visually inside it.\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { Absolute } from \"~/layout/Absolute.js\";\nimport { IconText } from \"~/component/display/IconText.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link DiagramGroupNode}.\n *\n * @category Components\n */\nexport interface DiagramGroupNodeOptions extends PanelOptions {\n /** The container's header label (e.g. the schema name). */\n label?: string;\n /** Optional registered glyph name shown before the header label. */\n glyph?: string;\n}\n\n/** Corner radius in pixels for the container's box, matching `DiagramNode`. */\nconst GROUP_BORDER_RADIUS = \"4px\";\n\n/**\n * Distance in pixels from the box's top-left corner to the header label's\n * origin — enough breathing room that the label never sits flush against the\n * rounded border, mirroring `DiagramNode`'s interior inset.\n */\nconst HEADER_INSET = 6;\n\n/**\n * The default themed container renderer for a\n * [`DiagramView`](/api/component/diagram/classes/DiagramView) compound node.\n * Paints a translucent, rounded box with a header label pinned to the\n * top-left corner; the rest of the box stays open so the container's\n * children — rendered as separate flat siblings positioned by ELK, not as DOM\n * children of this component — visually read as sitting inside it.\n *\n * @category Components\n */\nclass DiagramGroupNode extends Panel<DiagramGroupNodeOptions> {\n\n /** The header shown at the box's top-left corner: a bare label, or a glyph + label. */\n private _header!: IconText | Text;\n\n constructor(options?: DiagramGroupNodeOptions) {\n super(options, { layoutManager: new Absolute() });\n\n this.setBackgroundColor(\"var(--ts-ui-diagram-group-bg, rgba(120, 120, 120, 0.08))\");\n this.setBorder(\"1px solid var(--ts-ui-diagram-group-border, var(--ts-ui-border-color, rgb(180, 180, 180)))\");\n this.setBorderRadius(GROUP_BORDER_RADIUS);\n\n // The header is built here (not during super's cascade), so the values\n // cached pure in `applyOptions` are dispatched now that the box exists.\n // An IconText when a glyph is present (glyph before the label, mirroring\n // DiagramNode), else a bare Text.\n const label = this._options.label ?? \"\";\n this._header = this._options.glyph !== undefined\n ? new IconText(this._options.glyph, label)\n : new Text(label);\n this._header.setPointerEvents(\"none\");\n this._header.setX(HEADER_INSET);\n this._header.setY(HEADER_INSET);\n this.addComponent(this._header);\n }\n\n /**\n * Caches the label/glyph fields pure to `_options`; they are dispatched from\n * the constructor body once the header child exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DiagramGroupNodeOptions): this {\n super.applyOptions(options);\n\n if (options.label !== undefined) this._options.label = options.label;\n if (options.glyph !== undefined) this._options.glyph = options.glyph;\n\n return this;\n }\n\n /**\n * Updates the container's header label.\n *\n * @param value - The new label.\n *\n * @returns This node, for method chaining.\n */\n setLabel(value: string): this {\n this._options.label = value;\n this._header.setText(value);\n\n return this;\n }\n\n /**\n * Returns the container's header label, or `null` when none was set.\n *\n * @returns The label text, or `null`.\n */\n getLabel(): string | null {\n return this._options.label ?? null;\n }\n}\n\nconst DiagramGroupNodeCallable = callable(DiagramGroupNode);\ntype DiagramGroupNodeCallable = DiagramGroupNode;\nexport {\n DiagramGroupNode as _DiagramGroupNode,\n DiagramGroupNodeCallable as DiagramGroupNode,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// One `<svg>` Component drawing the diagram's edges as `<path>` elements with a\n// shared arrowhead `<marker>`. Follows the Glyph SVG-through-the-seam pattern:\n// the root `<svg>` and every leaf child are created via `DOM.sink.createElementNS`\n// and tracked with `trackHandle`, never raw DOM. The layer is non-interactive\n// (`pointer-events: none`) so node clicks fall through to the node components.\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport type { ElkEdgeSection, ElkPoint } from \"~/component/diagram/ElkLayoutEngine.js\";\nimport type { DiagramEdgeMarker, DiagramEdgeStyle } from \"~/component/diagram/DiagramModel.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** SVG namespace URI. */\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/** Themed stroke for edge paths and the arrowhead. */\nconst EDGE_STROKE = \"var(--ts-ui-diagram-edge, var(--ts-ui-border-color, rgb(120, 120, 120)))\";\n\n/** Edge path stroke width in pixels — a hairline that reads at any zoom. */\nconst EDGE_STROKE_WIDTH = \"1.5\";\n\n/** Font size (px) of an edge's mid-route label. */\nconst LABEL_FONT_SIZE = \"12\";\n\n/**\n * Background colour and width (px) of the halo drawn behind an edge label. The\n * label text is painted `stroke`-then-`fill` (`paint-order`), so this\n * background-coloured stroke masks the edge line running under the glyphs — the\n * label reads clearly instead of being crossed out by the edge.\n */\nconst LABEL_HALO = \"var(--ts-ui-diagram-bg, var(--ts-ui-panel-bg, rgb(255, 255, 255)))\";\nconst LABEL_HALO_WIDTH = \"4\";\n\n/** Arrowhead marker box size in user units (matches the `refX`/`refY` below). */\nconst ARROW_SIZE = 8;\n\n/** Dash pattern applied when an edge's `style.dashed` is set. */\nconst DASH_ARRAY = \"6 4\";\n\n/**\n * Non-`\"arrow\"` crow's-foot marker kinds this layer pre-defines in `<defs>`.\n * Kept as a literal array (rather than deriving from {@link DiagramEdgeMarker})\n * so `createRootElement` can iterate it directly.\n */\nconst CROWS_FOOT_MARKERS: readonly Exclude<DiagramEdgeMarker, \"arrow\">[] =\n [\"one\", \"zeroOrOne\", \"oneOrMany\", \"zeroOrMany\"];\n\n/**\n * One crow's-foot marker's geometry: its `<marker>` box (`width`/`height`),\n * the point within that box the edge vertex anchors to (`refX`/`refY`), and\n * its child shapes as bare SVG element specs. Every marker uses\n * `orient=\"auto-start-reverse\"`, so the same definition auto-flips when used\n * at `marker-start` and reads unreversed at `marker-end`.\n */\ninterface MarkerGeometry {\n width: number;\n height: number;\n refX: number;\n refY: number;\n /** Child shapes, each a bare tag name plus its SVG attributes. */\n parts: Array<{ tag: \"path\" | \"circle\"; attrs: Record<string, string> }>;\n}\n\n/**\n * Geometry table for the four crow's-foot markers. Coordinates are chosen so\n * every marker's \"attach\" edge sits at `x = width` (the vertex, via `refX`)\n * and its \"open\" edge sits toward `x = 0` (away from the node) — exact pixels\n * are a manual visual-tuning step, not a correctness concern (see the\n * fk-diagram-cardinality-and-index-coverage plan).\n */\nconst MARKER_GEOMETRY: Record<Exclude<DiagramEdgeMarker, \"arrow\">, MarkerGeometry> = {\n // \"one and only one\": two perpendicular bars across the line.\n one: {\n width: 12, height: 12, refX: 12, refY: 6,\n parts: [{ tag: \"path\", attrs: { d: \"M 4 0 L 4 12 M 8 0 L 8 12\" } }],\n },\n // \"zero or one\": one perpendicular bar plus a small inboard circle.\n zeroOrOne: {\n width: 16, height: 12, refX: 16, refY: 6,\n parts: [\n { tag: \"path\", attrs: { d: \"M 12 0 L 12 12\" } },\n { tag: \"circle\", attrs: { cx: \"6\", cy: \"6\", r: \"4\" } },\n ],\n },\n // \"one or many\": a three-prong crow's foot plus one perpendicular bar just inboard of it.\n oneOrMany: {\n width: 16, height: 12, refX: 16, refY: 6,\n parts: [\n { tag: \"path\", attrs: { d: \"M 0 6 L 12 0 M 0 6 L 12 6 M 0 6 L 12 12\" } },\n { tag: \"path\", attrs: { d: \"M 14 0 L 14 12\" } },\n ],\n },\n // \"zero or many\": a three-prong crow's foot plus a small inboard circle.\n zeroOrMany: {\n width: 18, height: 12, refX: 18, refY: 6,\n parts: [\n { tag: \"path\", attrs: { d: \"M 6 6 L 18 0 M 6 6 L 18 6 M 6 6 L 18 12\" } },\n { tag: \"circle\", attrs: { cx: \"3\", cy: \"6\", r: \"3\" } },\n ],\n },\n};\n\n/**\n * A single routed edge: its identity, the ELK sections describing its\n * polyline route (start point, optional bend points, end point), and its\n * optional visual style (joined in from the model by\n * [`DiagramView.applyLayout`](/api/component/diagram/classes/DiagramView)).\n */\nexport interface DiagramEdgeRoute {\n id: string;\n sections: ElkEdgeSection[];\n /** Optional cardinality/dependency style. Absent = today's plain arrow-ended edge. */\n style?: DiagramEdgeStyle;\n}\n\n/**\n * The point used to place an edge's optional label: the halfway point *along the\n * routed polyline* (start → bend points → end), measured by arc length. This\n * stays visually centred on the edge even when it bends — a bend point can sit\n * far off centre and drag the label toward one endpoint.\n *\n * @param sections - The edge's routed sections.\n * @returns The label anchor point.\n */\nfunction labelPoint(sections: ElkEdgeSection[]): ElkPoint {\n const section = sections[0];\n\n if (!section) {\n return { x: 0, y: 0 };\n }\n\n const points = [section.startPoint, ...(section.bendPoints ?? []), section.endPoint];\n\n return midpointAlong(points);\n}\n\n/**\n * The point half the total length along a polyline, interpolated within the\n * segment that straddles the midpoint.\n *\n * @param points - The polyline vertices, in order (length >= 1).\n * @returns The arc-length midpoint.\n */\nfunction midpointAlong(points: ElkPoint[]): ElkPoint {\n const segmentLength = (a: ElkPoint, b: ElkPoint): number => Math.hypot(b.x - a.x, b.y - a.y);\n\n let total = 0;\n\n for (let i = 1; i < points.length; i++) {\n total += segmentLength(points[i - 1], points[i]);\n }\n\n let remaining = total / 2;\n\n for (let i = 1; i < points.length; i++) {\n const length = segmentLength(points[i - 1], points[i]);\n\n if (length >= remaining) {\n const t = length === 0 ? 0 : remaining / length;\n\n return {\n x: points[i - 1].x + (points[i].x - points[i - 1].x) * t,\n y: points[i - 1].y + (points[i].y - points[i - 1].y) * t,\n };\n }\n\n remaining -= length;\n }\n\n return points[points.length - 1];\n}\n\n/**\n * Serialises an edge's ELK sections into an SVG path `d` string. Each section\n * starts with a move to its `startPoint`, threads any `bendPoints`, and lines to\n * its `endPoint`.\n *\n * @param sections - The edge's routed sections.\n * @returns The SVG path data, or an empty string when there is nothing to draw.\n */\nfunction buildPathData(sections: ElkEdgeSection[]): string {\n const parts: string[] = [];\n\n for (const section of sections) {\n parts.push(`M ${section.startPoint.x} ${section.startPoint.y}`);\n\n for (const bend of section.bendPoints ?? []) {\n parts.push(`L ${bend.x} ${bend.y}`);\n }\n\n parts.push(`L ${section.endPoint.x} ${section.endPoint.y}`);\n }\n\n return parts.join(\" \");\n}\n\n/**\n * The SVG edge layer for a [`DiagramView`](/api/component/diagram/classes/DiagramView).\n * Owns exactly one `<svg>` element and rebuilds its `<path>` children from ELK\n * edge routes whenever the diagram re-lays-out.\n *\n * @category Components\n */\nclass DiagramEdgeLayer extends Component<ComponentOptions> {\n\n /** Cached edge routes, redrawn at render time and on every `setEdges`. */\n private _edges: DiagramEdgeRoute[] = [];\n\n /** Handles of the currently-drawn `<path>`/`<text>` children, released on rebuild. */\n private _pathHandles: Handle[] = [];\n\n /** Per-instance arrowhead marker id, referenced by each plain edge's `marker-end`. */\n private readonly _markerId: string;\n\n /** Per-instance crow's-foot marker ids, keyed by marker kind. */\n private readonly _crowsFootMarkerIds: Record<Exclude<DiagramEdgeMarker, \"arrow\">, string>;\n\n constructor(options?: ComponentOptions) {\n super(options);\n\n this._markerId = `${this.getId()}-arrow`;\n this._crowsFootMarkerIds = Object.fromEntries(\n CROWS_FOOT_MARKERS.map(kind => [kind, `${this.getId()}-${kind}`]),\n ) as Record<Exclude<DiagramEdgeMarker, \"arrow\">, string>;\n\n // Non-interactive overlay: clicks must reach the node components beneath.\n this.setPointerEvents(\"none\");\n }\n\n /**\n * Resolves a marker kind to its namespaced `url(#…)` reference, or\n * `undefined` when the kind is absent (no marker on that end).\n *\n * @param kind - The requested marker kind, if any.\n * @returns The `url(#id)` reference, or `undefined`.\n */\n private markerUrl(kind: DiagramEdgeMarker | undefined): string | undefined {\n if (!kind) {\n return undefined;\n }\n\n const id = kind === \"arrow\" ? this._markerId : this._crowsFootMarkerIds[kind];\n\n return `url(#${id})`;\n }\n\n /**\n * Replaces the drawn edges and rebuilds the path children.\n *\n * @param edges - The routed edges to draw.\n *\n * @returns This layer, for method chaining.\n */\n setEdges(edges: DiagramEdgeRoute[]): this {\n this._edges = edges;\n this.rebuildPaths();\n\n return this;\n }\n\n /**\n * Creates the root `<svg>` with the arrowhead marker plus one `<marker>` per\n * crow's-foot kind in a shared `<defs>`. Every marker and its child shapes\n * are seam-created leaf children tracked for release.\n *\n * @returns The root `<svg>` handle.\n */\n protected createRootElement(): Handle {\n const svg = DOM.sink.createElementNS(SVG_NS, \"svg\");\n const defs = DOM.sink.createElementNS(SVG_NS, \"defs\");\n\n this.createArrowMarker(defs);\n\n for (const kind of CROWS_FOOT_MARKERS) {\n this.createCrowsFootMarker(defs, kind);\n }\n\n DOM.sink.appendChild(svg, defs);\n this.trackHandle(defs);\n\n return svg;\n }\n\n /**\n * Defines the default arrowhead `<marker>` into `defs`. `orient=\"auto-start-\n * reverse\"` so the one definition serves both `marker-end` (unreversed, the\n * common case) and `marker-start` (auto-flipped to point back out of the\n * source node) — e.g. a data-flow edge that draws its arrow at the source end.\n *\n * @param defs - The `<defs>` element to append into.\n */\n private createArrowMarker(defs: Handle): void {\n const marker = DOM.sink.createElementNS(SVG_NS, \"marker\");\n\n DOM.sink.apply(marker, { setAttr: {\n id: this._markerId,\n markerWidth: String(ARROW_SIZE),\n markerHeight: String(ARROW_SIZE),\n refX: String(ARROW_SIZE),\n refY: String(ARROW_SIZE / 2),\n orient: \"auto-start-reverse\",\n markerUnits: \"userSpaceOnUse\",\n } });\n\n const arrow = DOM.sink.createElementNS(SVG_NS, \"path\");\n DOM.sink.apply(arrow, { setAttr: {\n d: `M 0 0 L ${ARROW_SIZE} ${ARROW_SIZE / 2} L 0 ${ARROW_SIZE} z`,\n fill: EDGE_STROKE,\n } });\n\n DOM.sink.appendChild(marker, arrow);\n DOM.sink.appendChild(defs, marker);\n\n this.trackHandle(marker);\n this.trackHandle(arrow);\n }\n\n /**\n * Defines one crow's-foot `<marker>` into `defs` from its\n * {@link MARKER_GEOMETRY} entry. `orient=\"auto-start-reverse\"` lets the same\n * definition serve both `marker-start` (auto-flipped) and `marker-end`\n * (unreversed).\n *\n * @param defs - The `<defs>` element to append into.\n * @param kind - Which crow's-foot marker to define.\n */\n private createCrowsFootMarker(defs: Handle, kind: Exclude<DiagramEdgeMarker, \"arrow\">): void {\n const geometry = MARKER_GEOMETRY[kind];\n const marker = DOM.sink.createElementNS(SVG_NS, \"marker\");\n\n DOM.sink.apply(marker, { setAttr: {\n id: this._crowsFootMarkerIds[kind],\n markerWidth: String(geometry.width),\n markerHeight: String(geometry.height),\n refX: String(geometry.refX),\n refY: String(geometry.refY),\n orient: \"auto-start-reverse\",\n markerUnits: \"userSpaceOnUse\",\n } });\n\n DOM.sink.appendChild(defs, marker);\n this.trackHandle(marker);\n\n for (const part of geometry.parts) {\n const shape = DOM.sink.createElementNS(SVG_NS, part.tag);\n\n DOM.sink.apply(shape, { setAttr: {\n ...part.attrs,\n fill: \"none\",\n stroke: EDGE_STROKE,\n } });\n\n DOM.sink.appendChild(marker, shape);\n this.trackHandle(shape);\n }\n }\n\n /**\n * Renders the root element and draws the cached edges.\n *\n * @returns The rendered root element.\n */\n protected render(): Handle {\n const element = super.render();\n\n this.rebuildPaths();\n\n return element;\n }\n\n /**\n * Clears the previously-drawn paths and rebuilds them from the cached edge\n * routes. No-op before the element exists — `render` performs the first draw.\n */\n private rebuildPaths(): void {\n const svg = this.getElement();\n\n if (!svg) {\n return;\n }\n\n for (const handle of this._pathHandles) {\n DOM.sink.removeChild(svg, handle);\n this.untrackHandle(handle);\n DOM.sink.release(handle);\n }\n\n this._pathHandles = [];\n\n for (const edge of this._edges) {\n const d = buildPathData(edge.sections);\n\n if (!d) {\n continue;\n }\n\n const path = DOM.sink.createElementNS(SVG_NS, \"path\");\n const style = edge.style;\n\n // No style: today's back-compat behaviour — a plain arrow at the end,\n // no start marker. With a style, each end draws only the marker (if\n // any) the style names; an edge can carry cardinality with no\n // marker-end at all.\n const markerEnd = style ? this.markerUrl(style.endMarker) : this.markerUrl(\"arrow\");\n const markerStart = style ? this.markerUrl(style.startMarker) : undefined;\n\n const attrs: Record<string, string> = {\n d,\n fill: \"none\",\n stroke: style?.stroke ?? EDGE_STROKE,\n \"stroke-width\": EDGE_STROKE_WIDTH,\n };\n\n if (markerEnd) {\n attrs[\"marker-end\"] = markerEnd;\n }\n\n if (markerStart) {\n attrs[\"marker-start\"] = markerStart;\n }\n\n if (style?.dashed) {\n attrs[\"stroke-dasharray\"] = DASH_ARRAY;\n }\n\n DOM.sink.apply(path, { setAttr: attrs });\n\n DOM.sink.appendChild(svg, path);\n this.trackHandle(path);\n this._pathHandles.push(path);\n\n if (style?.label) {\n this.drawLabel(svg, edge, style.label);\n }\n }\n }\n\n /**\n * Draws one edge's optional mid-route label as a `<text>` element.\n *\n * @param svg - The root `<svg>` handle to append into.\n * @param edge - The edge route the label belongs to (for its anchor point).\n * @param label - The label text.\n */\n private drawLabel(svg: Handle, edge: DiagramEdgeRoute, label: string): void {\n const point = labelPoint(edge.sections);\n const text = DOM.sink.createElementNS(SVG_NS, \"text\");\n\n DOM.sink.apply(text, {\n setAttr: {\n x: String(point.x),\n y: String(point.y),\n fill: EDGE_STROKE,\n // Background-coloured halo painted first, so the edge line under the\n // glyphs is masked and the label reads clearly (not crossed out).\n stroke: LABEL_HALO,\n \"stroke-width\": LABEL_HALO_WIDTH,\n \"stroke-linejoin\": \"round\",\n \"paint-order\": \"stroke\",\n \"font-size\": LABEL_FONT_SIZE,\n \"text-anchor\": \"middle\",\n \"dominant-baseline\": \"central\",\n },\n text: label,\n });\n\n DOM.sink.appendChild(svg, text);\n this.trackHandle(text);\n this._pathHandles.push(text);\n }\n}\n\nconst DiagramEdgeLayerCallable = callable(DiagramEdgeLayer);\ntype DiagramEdgeLayerCallable = DiagramEdgeLayer;\nexport {\n DiagramEdgeLayer as _DiagramEdgeLayer,\n DiagramEdgeLayerCallable as DiagramEdgeLayer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// A read-only graph/diagram viewer. Takes a framework-native node/edge model,\n// runs it through ELK for automatic layout (off-seam pure compute, lazily\n// imported), and renders themed node components plus an SVG edge layer with pan,\n// zoom, and node selection.\n//\n// Structure: DiagramView (the scrolling Panel viewport) owns a single content\n// host Container (Absolute layout) that carries the zoom transform and holds the\n// node components + the edge layer. Pan is native scroll on the viewport; zoom\n// is a `scale()` transform on the content host, whose box is sized to\n// graphBounds × zoom so the native scroll extent stays correct.\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { Container } from \"~/core/Container.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Absolute } from \"~/layout/Absolute.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { DiagramData, DiagramNodeData } from \"~/component/diagram/DiagramModel.js\";\nimport { ElkLayoutEngine, DiagramLayoutResult } from \"~/component/diagram/ElkLayoutEngine.js\";\nimport { DiagramNode } from \"~/component/diagram/DiagramNode.js\";\nimport { DiagramGroupNode } from \"~/component/diagram/DiagramGroupNode.js\";\nimport { DiagramEdgeLayer } from \"~/component/diagram/DiagramEdgeLayer.js\";\nimport type { DiagramEdgeRoute } from \"~/component/diagram/DiagramEdgeLayer.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** Factory producing a node component from a node's model data. */\nexport type DiagramNodeRenderer = (data: DiagramNodeData) => Component;\n\n/** String-literal union of the events emitted by {@link DiagramView}. */\nexport type DiagramViewEvent = \"selection\" | \"activate\" | \"layout\";\n\n/** Default initial zoom factor. */\nconst DEFAULT_ZOOM = 1;\n\n/** Default minimum zoom factor (a quarter scale). */\nconst DEFAULT_MIN_ZOOM = 0.25;\n\n/** Default maximum zoom factor (4× scale). */\nconst DEFAULT_MAX_ZOOM = 4;\n\n/** Multiplicative zoom step per wheel notch. */\nconst WHEEL_ZOOM_STEP = 1.1;\n\n// Paint order for a compound graph (one with at least one container): the\n// container boxes sit behind the edges, which sit behind the leaves, so a\n// leaf's click is never intercepted by its own container box. A leaf/container\n// component is always freshly built by rebuildNodes, so on a flat graph it\n// simply starts at DEFAULT_Z_INDEX and is never touched. The edge layer is the\n// one exception — it is a persistent child of the content host (built once in\n// the constructor, never torn down/rebuilt — see the constructor comment), so\n// a flat pass must explicitly restore DEFAULT_Z_INDEX in case an earlier\n// compound pass on the same view left EDGE_LAYER_Z_INDEX behind.\nconst DEFAULT_Z_INDEX = 0;\nconst CONTAINER_Z_INDEX = 0;\nconst EDGE_LAYER_Z_INDEX = 1;\nconst LEAF_Z_INDEX = 2;\n\n/**\n * Construction-time options for {@link DiagramView}.\n *\n * @category Components\n */\nexport interface DiagramViewOptions extends PanelOptions {\n /** The initial graph to lay out and render. */\n data?: DiagramData;\n /** Factory for node components; defaults to building a `DiagramNode`. */\n nodeRenderer?: DiagramNodeRenderer;\n /** Factory for compound container components; defaults to `DiagramGroupNode`. */\n groupRenderer?: DiagramNodeRenderer;\n /** Default ELK layout options applied to every layout pass. */\n layoutOptions?: Record<string, string>;\n /** Optional URL of a consumer-hosted `elk-worker.js` for off-thread layout. */\n elkWorkerUrl?: string;\n /** Minimum zoom factor (default 0.25). */\n minZoom?: number;\n /** Maximum zoom factor (default 4). */\n maxZoom?: number;\n /** Initial zoom factor (default 1). */\n zoom?: number;\n /** Construction-time listener bag dispatched to {@link DiagramView.on}. */\n listeners?: {\n selection?: (nodes: DiagramNodeData[]) => void;\n activate?: (node: DiagramNodeData) => void;\n layout?: () => void;\n };\n}\n\n/**\n * A read-only automatic-layout graph viewer with pan, zoom, and node selection.\n *\n * Pass a graph via the `data` option or {@link DiagramView.setData}. Layout runs\n * asynchronously through ELK; a `\"layout\"` event fires after each successful\n * pass, a `\"selection\"` event fires when the selected node changes, and an\n * `\"activate\"` event fires when a node is double-clicked.\n *\n * @example\n * ```typescript\n * const view = new DiagramView({\n * data: {\n * nodes: [{ id: \"a\", label: \"A\" }, { id: \"b\", label: \"B\" }],\n * edges: [{ id: \"e\", source: \"a\", target: \"b\" }],\n * },\n * });\n * view.on(\"selection\", (nodes) => console.log(nodes));\n * ```\n *\n * @category Components\n */\nclass DiagramView extends Panel<DiagramViewOptions> {\n\n /** The transform-carrying content host holding nodes + the edge layer. */\n private _contentHost!: Container;\n\n /** The SVG edge layer, a persistent child of the content host. */\n private _edgeLayer!: DiagramEdgeLayer;\n\n /** The ELK layout adapter. Runtime state, off the options bag. */\n private _engine!: ElkLayoutEngine;\n\n /** Node components keyed by node id. */\n private _nodeComponents: Map<string, Component> = new Map();\n\n /** Node model data keyed by node id (selection payload source). */\n private _nodeData: Map<string, DiagramNodeData> = new Map();\n\n /** Ids of the compound container nodes built by the last `rebuildNodes`. */\n private _containerIds: Set<string> = new Set();\n\n /** Currently selected node data (single-select). */\n private _selection: DiagramNodeData[] = [];\n\n /** Monotonic layout token; guards against a stale in-flight layout landing. */\n private _layoutGeneration: number = 0;\n\n /** Cached graph bounding box from the last successful layout. */\n private _graphWidth: number = 0;\n private _graphHeight: number = 0;\n\n /** Custom-event fan-out for `\"selection\"` / `\"layout\"`. */\n private _listeners: ListenerBag<DiagramViewEvent> = new ListenerBag<DiagramViewEvent>();\n\n /** Pan drag state. */\n private _panning: boolean = false;\n private _panStartX: number = 0;\n private _panStartY: number = 0;\n private _panScrollLeft: number = 0;\n private _panScrollTop: number = 0;\n\n constructor(options?: DiagramViewOptions) {\n super(options, { zoom: DEFAULT_ZOOM, minZoom: DEFAULT_MIN_ZOOM, maxZoom: DEFAULT_MAX_ZOOM });\n\n this.setAutoScroll(\"auto\");\n\n this._engine = this.createEngine();\n\n // Nodes are laid out at unscaled graph coordinates under the host's\n // `scale(zoom)` transform, so the host box (graph bounds × zoom) is\n // smaller than the node coordinates whenever zoom < 1. The base\n // `overflow: hidden` default would then crop the diagram to a\n // `zoom`-fraction of the graph; keep it visible so the whole graph\n // paints and the overflowing scaled nodes drive the native scroll\n // extent (which tracks the visual size — graph bounds × zoom).\n this._contentHost = new Container({ layoutManager: new Absolute(), overflow: \"visible\" });\n this._contentHost.setTransformOrigin(\"0 0\");\n this.addComponent(this._contentHost);\n\n this._edgeLayer = new DiagramEdgeLayer();\n this._contentHost.addComponent(this._edgeLayer);\n\n this.applyListeners(options?.listeners);\n\n if (this._options.zoom !== undefined) {\n this.setZoom(this._options.zoom);\n }\n\n if (this._options.data) {\n this.setData(this._options.data);\n }\n }\n\n /**\n * Builds the layout engine. Isolated as a factory so the swappable-engine\n * seam can be exercised (a test substitutes a stub engine that returns a\n * fixed result without importing ELK).\n *\n * @returns A fresh {@link ElkLayoutEngine}.\n */\n protected createEngine(): ElkLayoutEngine {\n return new ElkLayoutEngine(this._options.elkWorkerUrl);\n }\n\n /**\n * Caches consumer-configurable fields pure to `_options`; effects that need\n * the content host (built in the constructor body) are dispatched there.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DiagramViewOptions): this {\n super.applyOptions(options);\n\n if (options.data !== undefined) this._options.data = options.data;\n if (options.nodeRenderer !== undefined) this._options.nodeRenderer = options.nodeRenderer;\n if (options.groupRenderer !== undefined) this._options.groupRenderer = options.groupRenderer;\n if (options.layoutOptions !== undefined) this._options.layoutOptions = options.layoutOptions;\n if (options.elkWorkerUrl !== undefined) this._options.elkWorkerUrl = options.elkWorkerUrl;\n if (options.minZoom !== undefined) this._options.minZoom = options.minZoom;\n if (options.maxZoom !== undefined) this._options.maxZoom = options.maxZoom;\n if (options.zoom !== undefined) this._options.zoom = options.zoom;\n\n return this;\n }\n\n /**\n * Replaces the graph, rebuilds node components, and triggers an async layout.\n *\n * @param data - The new graph.\n *\n * @returns This view, for method chaining.\n */\n setData(data: DiagramData): this {\n this._options.data = data;\n this.rebuildNodes(data);\n this.relayout(data);\n\n return this;\n }\n\n /**\n * Returns the current graph, or `null` when none has been set.\n *\n * @returns The graph data, or `null`.\n */\n getData(): DiagramData | null {\n return this._options.data ?? null;\n }\n\n /**\n * Tears down the old node components + selection and builds fresh ones via\n * the node renderer (default: `DiagramNode`), adding them to the content\n * host. The persistent edge layer is cleared but not removed. Recurses into\n * `children`: a container node (non-empty `children`) is built via the\n * group renderer (default: `DiagramGroupNode`) and its children are built\n * the same way, so container + leaf components all land as flat siblings in\n * `_nodeComponents`/`_nodeData`, keyed by id — a container is never a DOM\n * parent of its children (that would perturb the single-content-host model\n * `nodeIdAt` hit-testing relies on).\n *\n * @param data - The graph whose nodes to build.\n */\n private rebuildNodes(data: DiagramData): void {\n for (const component of this._nodeComponents.values()) {\n this._contentHost.removeComponent(component);\n }\n\n this._nodeComponents.clear();\n this._nodeData.clear();\n this._containerIds.clear();\n this._selection = [];\n this._edgeLayer.setEdges([]);\n\n const renderer = this._options.nodeRenderer ?? ((node: DiagramNodeData): Component =>\n new DiagramNode({ label: node.label, glyph: node.glyph }));\n const groupRenderer = this._options.groupRenderer ?? ((node: DiagramNodeData): Component =>\n new DiagramGroupNode({ label: node.label, glyph: node.glyph }));\n\n const build = (nodes: DiagramNodeData[]): void => {\n for (const node of nodes) {\n const isContainer = (node.children?.length ?? 0) > 0;\n const component = isContainer ? groupRenderer(node) : renderer(node);\n\n this._nodeComponents.set(node.id, component);\n this._nodeData.set(node.id, node);\n this._contentHost.addComponent(component);\n\n if (isContainer) {\n this._containerIds.add(node.id);\n build(node.children!);\n }\n }\n };\n\n build(data.nodes);\n }\n\n /**\n * Collects node sizes, bumps the generation token, and runs the async ELK\n * layout. A stale result (older token) is dropped; a failed layout tears the\n * nodes down so the view stays empty.\n *\n * @param data - The graph to lay out.\n */\n private relayout(data: DiagramData): void {\n const sizes = this.collectNodeSizes(data);\n\n this._layoutGeneration += 1;\n const generation = this._layoutGeneration;\n\n this._engine\n .layout(data, sizes, this._options.layoutOptions)\n .then((result) => this.applyLayout(result, generation))\n .catch(() => this.handleLayoutFailure(generation));\n }\n\n /**\n * Resolves each node's size fed to ELK: the explicit `width`/`height` from\n * the model when present, else the node component's preferred size.\n * Recurses into `children` so every container and leaf is represented; a\n * container's entry is harmless-but-unused since `buildElkGraph` computes a\n * container's box from its contents rather than consulting this map.\n *\n * @param data - The graph whose node sizes to collect.\n * @returns A map of node id to resolved size.\n */\n private collectNodeSizes(data: DiagramData): Map<string, { width: number; height: number }> {\n const sizes = new Map<string, { width: number; height: number }>();\n\n const collect = (nodes: DiagramNodeData[]): void => {\n for (const node of nodes) {\n const component = this._nodeComponents.get(node.id);\n const preferred = component?.getPreferredSize();\n\n sizes.set(node.id, {\n width: node.width ?? preferred?.width ?? 0,\n height: node.height ?? preferred?.height ?? 0,\n });\n\n if (node.children && node.children.length > 0) {\n collect(node.children);\n }\n }\n };\n\n collect(data.nodes);\n\n return sizes;\n }\n\n /**\n * Applies a completed layout: positions each node, sizes + scales the\n * content host, redraws the edges, and emits `\"layout\"`. A stale result\n * (superseded by a newer `setData`) is ignored.\n *\n * @param result - The ELK layout result.\n * @param generation - The generation token captured when the layout started.\n */\n private applyLayout(result: DiagramLayoutResult, generation: number): void {\n if (generation !== this._layoutGeneration) {\n return;\n }\n\n for (const node of result.nodes) {\n const component = this._nodeComponents.get(node.id);\n\n if (component) {\n component.setPreferredSize(node.width, node.height);\n component.setX(node.x);\n component.setY(node.y);\n }\n }\n\n this._graphWidth = result.width;\n this._graphHeight = result.height;\n\n this._edgeLayer.setX(0);\n this._edgeLayer.setY(0);\n this._edgeLayer.setPreferredSize(result.width, result.height);\n this._edgeLayer.setEdges(this.joinEdgeStyles(result.edges));\n\n this.applyContainerZIndex();\n\n this.applyZoomToHost();\n\n this.scheduleLayout();\n\n this.emit(\"layout\");\n }\n\n /**\n * Applies the compound paint order — containers behind the edge layer,\n * leaves in front of it — when `rebuildNodes` built at least one\n * container. A flat graph's own (freshly-built) node components are never\n * touched here — they start at `DEFAULT_Z_INDEX` — but the persistent edge\n * layer is explicitly restored to `DEFAULT_Z_INDEX`, since an earlier\n * compound `setData` on this same view could have left it elevated (see\n * the module-level z-index constants).\n */\n private applyContainerZIndex(): void {\n if (this._containerIds.size === 0) {\n this._edgeLayer.setZIndex(DEFAULT_Z_INDEX);\n\n return;\n }\n\n this._edgeLayer.setZIndex(EDGE_LAYER_Z_INDEX);\n\n for (const [id, component] of this._nodeComponents) {\n component.setZIndex(this._containerIds.has(id) ? CONTAINER_Z_INDEX : LEAF_Z_INDEX);\n }\n }\n\n /**\n * Re-attaches each model edge's `style` to its layout-routed counterpart.\n * ELK's result carries only `{ id, sections }` — `style` never survives the\n * round trip — so this joins by edge id back to `this._options.data.edges`,\n * the model the layout was computed from.\n *\n * @param edges - The ELK-routed edges (id + sections only).\n * @returns The same routes, each carrying its model edge's `style` if any.\n */\n private joinEdgeStyles(edges: DiagramEdgeRoute[]): DiagramEdgeRoute[] {\n const modelById = new Map(this._options.data?.edges.map(e => [e.id, e]) ?? []);\n\n return edges.map(edge => ({ ...edge, style: modelById.get(edge.id)?.style }));\n }\n\n /**\n * Removes the just-built nodes when a layout fails (e.g. `elkjs` absent), so\n * the view stays empty rather than showing stacked, unpositioned nodes.\n *\n * @param generation - The generation token captured when the layout started.\n */\n private handleLayoutFailure(generation: number): void {\n if (generation !== this._layoutGeneration) {\n return;\n }\n\n for (const component of this._nodeComponents.values()) {\n this._contentHost.removeComponent(component);\n }\n\n this._nodeComponents.clear();\n this._nodeData.clear();\n }\n\n /**\n * Writes the content host's `scale()` transform and its scaled box from the\n * cached graph bounds and current zoom.\n */\n private applyZoomToHost(): void {\n const zoom = this.getZoom();\n\n this._contentHost.setTransform(`scale(${zoom})`);\n\n // The host's box feeds the viewport's native scroll extent, and the\n // `scale(zoom)` transform above already scales the box's contribution:\n // Chrome enlarges a scaled-up element's scroll-overflow footprint but\n // ignores a scaled-down one (it keeps the untransformed box). So the box\n // multiplier is clamped at 1 — on zoom-in the transform supplies the\n // growth (a `× zoom` box would overshoot to graph bounds × zoom² and add\n // phantom scrollbars), while on zoom-out the box must shrink with `zoom`\n // itself since the transform's shrink is ignored. Either way the host's\n // effective extent matches the nodes' own scaled extent (bounds × zoom).\n const boxScale = Math.min(zoom, 1);\n this._contentHost.setPreferredSize(this._graphWidth * boxScale, this._graphHeight * boxScale);\n }\n\n /**\n * Returns the current zoom factor.\n *\n * @returns The zoom factor.\n */\n getZoom(): number {\n return this._options.zoom ?? this._defaultOptions.zoom ?? DEFAULT_ZOOM;\n }\n\n /**\n * Sets the zoom factor, clamped to `[minZoom, maxZoom]`, and re-scales the\n * content host.\n *\n * @param zoom - The desired zoom factor.\n *\n * @returns This view, for method chaining.\n */\n setZoom(zoom: number): this {\n this._options.zoom = this.clampZoom(zoom);\n this.applyZoomToHost();\n\n return this;\n }\n\n /**\n * Fits the whole graph into the viewport by choosing the largest zoom at\n * which the graph bounds fit both axes. Requires a completed layout.\n *\n * @returns This view, for method chaining.\n */\n zoomToFit(): this {\n if (this._graphWidth <= 0 || this._graphHeight <= 0) {\n return this;\n }\n\n const zoomX = this.getWidth() / this._graphWidth;\n const zoomY = this.getHeight() / this._graphHeight;\n\n this.setZoom(Math.min(zoomX, zoomY));\n\n return this;\n }\n\n /**\n * Clamps a zoom request to the configured `[minZoom, maxZoom]` range.\n *\n * @param zoom - The requested zoom factor.\n * @returns The clamped zoom factor.\n */\n private clampZoom(zoom: number): number {\n const min = this._options.minZoom ?? this._defaultOptions.minZoom ?? DEFAULT_MIN_ZOOM;\n const max = this._options.maxZoom ?? this._defaultOptions.maxZoom ?? DEFAULT_MAX_ZOOM;\n\n return Math.max(min, Math.min(max, zoom));\n }\n\n /**\n * Returns the current selection (single-select).\n *\n * @returns A copy of the selected node data array.\n */\n getSelection(): DiagramNodeData[] {\n return [...this._selection];\n }\n\n /**\n * Selects a node programmatically without emitting `\"selection\"` (mirroring\n * the Tree precedent). Passing `null` clears the selection.\n *\n * @param id - The node id to select, or `null` to clear.\n *\n * @returns This view, for method chaining.\n */\n selectNode(id: string | null): this {\n this.setSelection(id);\n\n return this;\n }\n\n /**\n * Scrolls the viewport so the given node is centred, without changing the\n * selection or emitting any event. No-op for an unknown id or before the\n * first layout has positioned the node. Pair with {@link selectNode} to both\n * highlight and reveal.\n *\n * @param id - The node id to centre, or a no-op when not found.\n *\n * @returns This view, for method chaining.\n */\n revealNode(id: string): this {\n const component = this._nodeComponents.get(id);\n\n if (!component) {\n return this;\n }\n\n const zoom = this.getZoom();\n\n // Node centre in scaled (on-screen) coordinates; the inverse of\n // _handleWheel's graphX = (pointer + scroll) / zoom. Nodes carry unscaled\n // graph coords via getX/getY under the content host's scale(zoom).\n const centreX = (component.getX() + component.getWidth() / 2) * zoom;\n const centreY = (component.getY() + component.getHeight() / 2) * zoom;\n\n // Scroll so the node centre lands at the viewport centre; the DOM clamps\n // the scroll offsets to their valid range on write-back.\n this.setScrollLeft(Math.max(0, centreX - this.getWidth() / 2));\n this.setScrollTop (Math.max(0, centreY - this.getHeight() / 2));\n\n return this;\n }\n\n /**\n * Updates the selection state and toggles each node's selected visual.\n *\n * @param id - The node id to select, or `null` to clear.\n */\n private setSelection(id: string | null): void {\n for (const component of this._nodeComponents.values()) {\n this.applySelectedVisual(component, false);\n }\n\n const data = id === null ? undefined : this._nodeData.get(id);\n\n if (data) {\n this._selection = [data];\n this.applySelectedVisual(this._nodeComponents.get(id!)!, true);\n } else {\n this._selection = [];\n }\n }\n\n /**\n * Toggles a node component's selected visual when it supports one (the\n * default `DiagramNode` does; a custom renderer may not).\n *\n * @param component - The node component.\n * @param selected - The selected state to apply.\n */\n private applySelectedVisual(component: Component, selected: boolean): void {\n const node = component as unknown as { setSelected?: (value: boolean) => void };\n\n node.setSelected?.(selected);\n }\n\n /**\n * Registers a listener for a diagram event.\n *\n * @param event - `\"selection\"` fires when the selected node changes;\n * `\"activate\"` fires when a node is double-clicked; `\"layout\"` fires\n * after each successful ELK layout pass.\n * @param listener - The callback to invoke.\n *\n * @returns This view, for method chaining.\n */\n on(event: \"selection\", listener: (nodes: DiagramNodeData[]) => void): this;\n on(event: \"activate\", listener: (node: DiagramNodeData) => void): this;\n on(event: \"layout\", listener: () => void): this;\n on(event: DiagramViewEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener; the exact reference must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This view, for method chaining.\n */\n off(event: DiagramViewEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event`, in registration order.\n *\n * @param event - The event to emit.\n * @param nodes - The selected node data (for `\"selection\"`), or the\n * activated node data (for `\"activate\"`).\n */\n protected emit(event: \"selection\", nodes: DiagramNodeData[]): void;\n protected emit(event: \"activate\", node: DiagramNodeData): void;\n protected emit(event: \"layout\"): void;\n protected emit(event: DiagramViewEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Wires the DOM-routed pan / zoom / selection listeners once the element\n * exists.\n *\n * @param element - Optional element from the render pipeline.\n *\n * @returns This view, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n Event.addSubtreeListener(this, \"click\", this._handleClick);\n Event.addSubtreeListener(this, \"dblclick\", this._handleDoubleClick);\n Event.addListener(this, \"wheel\", this._handleWheel);\n Event.addListener(this, \"pointerdown\", this._handlePointerDown);\n Event.addListener(this, \"pointermove\", this._handlePointerMove);\n Event.addListener(this, \"pointerup\", this._handlePointerUp);\n\n return this;\n }\n\n /**\n * Resolves the node under a click and updates the selection, emitting\n * `\"selection\"` when it changes. A click on empty space clears the\n * selection.\n *\n * @param event - The click event whose target is inside the view's subtree.\n */\n private _handleClick(event: MouseEvent): void {\n const id = this.nodeIdAt(event.target);\n\n if (id !== null) {\n this.setSelection(id);\n this.emit(\"selection\", this.getSelection());\n } else if (this._selection.length > 0) {\n this.setSelection(null);\n this.emit(\"selection\", this.getSelection());\n }\n }\n\n /**\n * Resolves the node under a double-click and emits `\"activate\"` with its\n * data. A double-click is preceded by the single click that already\n * selected the node, so this only signals activation. A double-click on\n * empty space resolves to no node and emits nothing.\n *\n * @param event - The dblclick event whose target is inside the view's\n * subtree.\n */\n private _handleDoubleClick(event: MouseEvent): void {\n const id = this.nodeIdAt(event.target);\n\n if (id === null) {\n return;\n }\n\n const data = this._nodeData.get(id);\n\n if (data !== undefined) {\n this.emit(\"activate\", data);\n }\n }\n\n /**\n * Resolves the node id whose component owns the given event target.\n *\n * @param target - The raw DOM event target.\n * @returns The owning node id, or `null` when the target is not on a node.\n */\n private nodeIdAt(target: EventTarget | null): string | null {\n if (target === null) {\n return null;\n }\n\n const handle = DOM.source.intern(target);\n\n for (const [id, component] of this._nodeComponents) {\n const element = component.getElement();\n\n if (element && (element === handle || DOM.source.contains(element, handle))) {\n return id;\n }\n }\n\n return null;\n }\n\n /**\n * Wheel-zoom about the pointer: scales toward/away and adjusts the scroll\n * offsets so the graph point under the cursor stays put.\n *\n * @param event - The wheel event.\n */\n private _handleWheel(event: WheelEvent): void {\n event.preventDefault();\n\n const oldZoom = this.getZoom();\n const newZoom = this.clampZoom(oldZoom * (event.deltaY < 0 ? WHEEL_ZOOM_STEP : 1 / WHEEL_ZOOM_STEP));\n\n if (newZoom === oldZoom) {\n return;\n }\n\n const rect = DOM.source.getViewportRect(this);\n const pointerX = event.clientX - rect.left;\n const pointerY = event.clientY - rect.top;\n\n const graphX = (pointerX + this.getScrollLeft()) / oldZoom;\n const graphY = (pointerY + this.getScrollTop()) / oldZoom;\n\n this.setZoom(newZoom);\n\n this.setScrollLeft(graphX * newZoom - pointerX);\n this.setScrollTop(graphY * newZoom - pointerY);\n }\n\n /**\n * Begins a pan drag on a primary-button press, recording the start pointer\n * and scroll offsets.\n *\n * @param event - The pointerdown event.\n */\n private _handlePointerDown(event: PointerEvent): void {\n if (event.button !== 0) {\n return;\n }\n\n this._panning = true;\n this._panStartX = event.clientX;\n this._panStartY = event.clientY;\n this._panScrollLeft = this.getScrollLeft();\n this._panScrollTop = this.getScrollTop();\n }\n\n /**\n * Pans the viewport during a drag by writing the scroll offsets from the\n * pointer delta.\n *\n * @param event - The pointermove event.\n */\n private _handlePointerMove(event: PointerEvent): void {\n if (!this._panning) {\n return;\n }\n\n this.setScrollLeft(this._panScrollLeft - (event.clientX - this._panStartX));\n this.setScrollTop(this._panScrollTop - (event.clientY - this._panStartY));\n }\n\n /**\n * Ends a pan drag.\n */\n private _handlePointerUp(): void {\n this._panning = false;\n }\n}\n\nconst DiagramViewCallable = callable(DiagramView);\ntype DiagramViewCallable = DiagramView;\nexport {\n DiagramView as _DiagramView,\n DiagramViewCallable as DiagramView,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"diagram.es.js","names":[],"sources":["../../../src/typescript/lib/component/diagram/ElkLayoutEngine.ts","../../../src/typescript/lib/component/diagram/DiagramNode.ts","../../../src/typescript/lib/component/diagram/DiagramGroupNode.ts","../../../src/typescript/lib/component/diagram/DiagramEdgeLayer.ts","../../../src/typescript/lib/component/diagram/DiagramView.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// The sole adapter between the framework-native diagram model and ElkJS. It is\n// the only module that names ELK types or imports `elkjs`; everything else in\n// the family speaks `DiagramData` / `DiagramLayoutResult`, keeping the layout\n// engine swappable. ELK takes JSON and returns JSON — it never touches the DOM,\n// so it needs no `DOM.sink` / `DOM.source` seam. `elkjs` is an optional peer\n// dependency imported lazily the first time a diagram lays out (mirroring\n// `StoreWorkerClient.ensureWorker`) and externalised from the library bundle so\n// its GWT blob never lands in the core chunk.\n\nimport type { DiagramData, DiagramNodeData } from \"~/component/diagram/DiagramModel.js\";\n\n/** A point in ELK's layout coordinate space. */\nexport interface ElkPoint {\n x: number;\n y: number;\n}\n\n/**\n * One routed section of an ELK edge: a straight run from `startPoint` to\n * `endPoint`, optionally threaded through `bendPoints`.\n */\nexport interface ElkEdgeSection {\n startPoint: ElkPoint;\n endPoint: ElkPoint;\n bendPoints?: ElkPoint[];\n}\n\n/** The engine-agnostic layout result consumed by `DiagramView`. */\nexport interface DiagramLayoutResult {\n /** Each node's absolute position and size in graph space. */\n nodes: Array<{ id: string; x: number; y: number; width: number; height: number }>;\n /** Each edge's routed sections. */\n edges: Array<{ id: string; sections: ElkEdgeSection[] }>;\n /** Graph bounding-box width. */\n width: number;\n /** Graph bounding-box height. */\n height: number;\n}\n\n/** ELK port shape: a fixed anchor on a node an edge can attach to. */\ninterface ElkPort {\n id: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n layoutOptions?: Record<string, string>;\n}\n\n/** ELK graph node shape (input carries sizes/options; output adds x/y). */\ninterface ElkNode {\n id: string;\n width?: number;\n height?: number;\n x?: number;\n y?: number;\n layoutOptions?: Record<string, string>;\n children?: ElkNode[];\n edges?: ElkExtendedEdge[];\n ports?: ElkPort[];\n}\n\n/** ELK edge shape with explicit source/target endpoint lists. */\ninterface ElkExtendedEdge {\n id: string;\n sources: string[];\n targets: string[];\n sections?: ElkEdgeSection[];\n /**\n * The node whose origin the routed `sections` coordinates are relative to,\n * as reported by ELK. For an edge between two nodes nested inside the same\n * container, ELK routes it in that container and reports container-relative\n * coordinates here (see {@link mapElkResult}, which shifts them to absolute).\n */\n container?: string;\n}\n\n/** Minimal structural type for the lazily-imported ELK instance. */\ninterface ElkInstance {\n layout(graph: unknown): Promise<unknown>;\n}\n\n/**\n * Fallback node width in pixels, used when a node carries neither an explicit\n * `width` nor a measurable preferred width. Sized to comfortably hold a short\n * label so a graph still lays out sensibly rather than collapsing to zero-area\n * nodes ELK would overlap.\n */\nconst DEFAULT_NODE_WIDTH = 120;\n\n/**\n * Fallback node height in pixels, mirroring {@link DEFAULT_NODE_WIDTH}. Tracks a\n * single label line plus vertical breathing room.\n */\nconst DEFAULT_NODE_HEIGHT = 40;\n\n/**\n * Merges layout-option maps left-to-right, so later arguments win. `undefined`\n * maps are skipped. Used to layer graph options over the view-level defaults.\n *\n * @param maps - Option maps in ascending precedence order.\n * @returns The merged option map.\n */\nfunction mergeLayoutOptions(...maps: Array<Record<string, string> | undefined>): Record<string, string> {\n const merged: Record<string, string> = {};\n\n for (const map of maps) {\n if (map) {\n Object.assign(merged, map);\n }\n }\n\n return merged;\n}\n\n/**\n * Default padding reserved inside every container's ELK box, keyed by side.\n * The default renderer (`DiagramGroupNode`) paints its header label a few\n * pixels from the top-left corner, so without a reserved top inset ELK could\n * place a child flush against — or under — the title. The top inset reserves\n * the header row (its inset + one text/glyph line) plus a ~10px gap so the\n * first child sits clearly below the title, not tight against it. The\n * left/bottom/right values match ELK's own built-in default (12px); a consumer\n * supplying a custom `groupRenderer` with a taller or shorter header overrides\n * this via the container's own `layoutOptions`.\n */\nconst CONTAINER_PADDING_DEFAULT: Record<string, string> = { \"elk.padding\": \"[top=34,left=12,bottom=12,right=12]\" };\n\n/**\n * Maps one framework-native node to its ELK counterpart, recursing into\n * `children` for a compound container. A container (non-empty `children`)\n * carries no explicit `width`/`height` — ELK computes its box from its\n * contents — and its children are mapped the same way, so nesting is\n * unbounded. A container's `layoutOptions` resolve as\n * `CONTAINER_PADDING_DEFAULT` < `node.layoutOptions` (the node's own option\n * wins), reserving header clearance by default while staying overridable. A\n * leaf (no children) maps exactly as before.\n *\n * @param node - The framework-native node.\n * @param sizes - Per-leaf resolved sizes (explicit size, else preferred size).\n * @returns The mapped ELK node.\n */\nfunction mapDiagramNode(\n node: DiagramNodeData,\n sizes: Map<string, { width: number; height: number }>,\n): ElkNode {\n if (node.children && node.children.length > 0) {\n return {\n id: node.id,\n layoutOptions: mergeLayoutOptions(CONTAINER_PADDING_DEFAULT, node.layoutOptions),\n children: node.children.map((child) => mapDiagramNode(child, sizes)),\n };\n }\n\n const size = sizes.get(node.id);\n\n return {\n id: node.id,\n width: node.width ?? size?.width ?? DEFAULT_NODE_WIDTH,\n height: node.height ?? size?.height ?? DEFAULT_NODE_HEIGHT,\n layoutOptions: node.layoutOptions,\n ports: node.ports?.map((p) => ({\n id: p.id,\n x: p.x,\n y: p.y,\n width: p.width,\n height: p.height,\n layoutOptions: p.side !== undefined ? { \"elk.port.side\": p.side } : undefined,\n })),\n };\n}\n\n/**\n * Root-level ELK options enabling cross-container edge routing. Without it,\n * an edge declared on the root (as every {@link DiagramEdgeData} is) between\n * two nodes nested under different containers may be dropped or mis-routed by\n * ELK's hierarchical layout. Merged in as the lowest-precedence tier, so a\n * view-level default or a graph's own `layoutOptions` can still override it.\n */\nconst HIERARCHY_HANDLING_DEFAULT: Record<string, string> = { \"elk.hierarchyHandling\": \"INCLUDE_CHILDREN\" };\n\n/**\n * Maps the framework-native graph to an ELK graph JSON. Pure and synchronous —\n * no `elkjs` import — so it is unit-testable directly.\n *\n * Graph-level options resolve as `HIERARCHY_HANDLING_DEFAULT` < `defaults` <\n * `data.layoutOptions` (graph wins over view defaults, which win over the\n * hierarchy-handling default) on the root; each node carries its own\n * `layoutOptions`, which ELK resolves over the inherited root options so a\n * per-node option wins over all three.\n *\n * @param data - The framework-native graph.\n * @param sizes - Per-node resolved sizes (explicit size, else preferred size).\n * @param defaults - View-level default ELK options applied to every layout.\n * @returns The ELK graph JSON ready for `elk.layout`.\n */\nexport function buildElkGraph(\n data: DiagramData,\n sizes: Map<string, { width: number; height: number }>,\n defaults?: Record<string, string>,\n): ElkNode {\n const children: ElkNode[] = data.nodes.map((node) => mapDiagramNode(node, sizes));\n\n const edges: ElkExtendedEdge[] = data.edges.map((edge) => ({\n id: edge.id,\n sources: [edge.sourcePort ?? edge.source],\n targets: [edge.targetPort ?? edge.target],\n }));\n\n return {\n id: \"root\",\n layoutOptions: mergeLayoutOptions(HIERARCHY_HANDLING_DEFAULT, defaults, data.layoutOptions),\n children,\n edges,\n };\n}\n\n/**\n * Recursively flattens an ELK node (and its `children`, if any) into\n * `out`, threading an `(offsetX, offsetY)` accumulator. ELK reports each\n * child's `x`/`y` relative to its own parent, so a node's absolute position is\n * its parent's absolute origin plus its own relative `x`/`y`; a container then\n * recurses with that absolute origin as the new offset for its own children.\n *\n * @param node - The ELK node to flatten (a container or a leaf).\n * @param offsetX - The accumulated absolute x of `node`'s parent (0 at the root).\n * @param offsetY - The accumulated absolute y of `node`'s parent (0 at the root).\n * @param out - The flat output array nodes are appended to, in traversal order.\n */\nfunction flattenElkNode(\n node: ElkNode,\n offsetX: number,\n offsetY: number,\n out: Array<{ id: string; x: number; y: number; width: number; height: number }>,\n): void {\n const x = offsetX + (node.x ?? 0);\n const y = offsetY + (node.y ?? 0);\n\n out.push({ id: node.id, x, y, width: node.width ?? 0, height: node.height ?? 0 });\n\n for (const child of node.children ?? []) {\n flattenElkNode(child, x, y, out);\n }\n}\n\n/**\n * Collects every ELK edge in the tree — the root's own `edges` plus any nested\n * in a container's `edges` — resolving each edge's container id (the node its\n * routed coordinates are relative to). ELK tags a routed edge with its own\n * `container`; absent that, the edge is relative to the node whose `edges`\n * array holds it (the root for a flat graph).\n *\n * @param node - The ELK node whose `edges` (and descendants') to collect.\n * @param out - The flat output array edges are appended to, each paired with\n * its resolved container id.\n */\nfunction collectElkEdges(node: ElkNode, out: Array<{ edge: ElkExtendedEdge; container: string }>): void {\n for (const edge of node.edges ?? []) {\n out.push({ edge, container: edge.container ?? node.id });\n }\n\n for (const child of node.children ?? []) {\n collectElkEdges(child, out);\n }\n}\n\n/**\n * Shifts every point of an edge's routed sections by `origin`, translating\n * container-relative ELK coordinates into absolute graph space. Returns the\n * sections unchanged when `origin` is the graph origin, so a flat graph's\n * root-relative edges pass through untouched.\n *\n * @param sections - The edge's routed sections, in their container's frame.\n * @param origin - The absolute origin of the edge's container.\n * @returns The sections with every point translated into absolute coordinates.\n */\nfunction offsetSections(sections: ElkEdgeSection[], origin: ElkPoint): ElkEdgeSection[] {\n if (origin.x === 0 && origin.y === 0) {\n return sections;\n }\n\n const shift = (point: ElkPoint): ElkPoint => ({ x: point.x + origin.x, y: point.y + origin.y });\n\n return sections.map((section) => ({\n startPoint: shift(section.startPoint),\n endPoint: shift(section.endPoint),\n bendPoints: section.bendPoints?.map(shift),\n }));\n}\n\n/**\n * Maps an ELK layout result back to the engine-agnostic\n * {@link DiagramLayoutResult}. Pure and synchronous, so it is unit-testable\n * directly. A container's descendants are flattened into the same output list\n * as its siblings, each carrying absolute (not parent-relative) coordinates —\n * see {@link flattenElkNode}. Edge sections get the same treatment: ELK reports\n * an intra-container edge's route relative to that container's origin, so each\n * edge is shifted by its container's absolute position (a flat graph's\n * root-relative edges are shifted by the zero origin, i.e. left unchanged).\n *\n * @param result - The root ELK node returned by `elk.layout`.\n * @returns The mapped layout result.\n */\nexport function mapElkResult(result: ElkNode): DiagramLayoutResult {\n const nodes: Array<{ id: string; x: number; y: number; width: number; height: number }> = [];\n\n for (const child of result.children ?? []) {\n flattenElkNode(child, 0, 0, nodes);\n }\n\n // Absolute origin of every node (containers included), so an edge routed in\n // a container's frame can be lifted into absolute graph space. The root maps\n // to the zero origin.\n const origins = new Map<string, ElkPoint>([[result.id, { x: 0, y: 0 }]]);\n\n for (const node of nodes) {\n origins.set(node.id, { x: node.x, y: node.y });\n }\n\n const collected: Array<{ edge: ElkExtendedEdge; container: string }> = [];\n collectElkEdges(result, collected);\n\n const edges = collected.map(({ edge, container }) => ({\n id: edge.id,\n sections: offsetSections(edge.sections ?? [], origins.get(container) ?? { x: 0, y: 0 }),\n }));\n\n return {\n nodes,\n edges,\n width: result.width ?? 0,\n height: result.height ?? 0,\n };\n}\n\n/**\n * Lazily-loaded ELK layout adapter. One instance owns one lazily-imported ELK\n * engine; the import fires on the first {@link ElkLayoutEngine.layout} call and\n * the engine is reused afterward.\n *\n * @category Components\n */\nexport class ElkLayoutEngine {\n\n private _elk: ElkInstance | null = null;\n private readonly _workerUrl?: string;\n\n /**\n * @param workerUrl - Optional URL of a consumer-hosted `elk-worker.js`. When\n * set, ELK runs its layout compute off the main thread; otherwise the\n * zero-config main-thread bundle is used.\n */\n constructor(workerUrl?: string) {\n this._workerUrl = workerUrl;\n }\n\n /**\n * Lazily imports ELK (if needed), maps the model to ELK JSON, runs the\n * layout, and maps the result back.\n *\n * @param data - The framework-native graph.\n * @param sizes - Per-node resolved sizes.\n * @param defaults - View-level default ELK options.\n * @returns The mapped layout result.\n * @throws Error - If `elkjs` is not installed / cannot be imported.\n */\n async layout(\n data: DiagramData,\n sizes: Map<string, { width: number; height: number }>,\n defaults?: Record<string, string>,\n ): Promise<DiagramLayoutResult> {\n const elk = await this.ensureElk();\n const graph = buildElkGraph(data, sizes, defaults);\n const result = await elk.layout(graph);\n\n return mapElkResult(result as ElkNode);\n }\n\n /**\n * Returns the ELK engine, importing and constructing it on first use. The\n * dynamic `import(\"elkjs/...\")` is left external in the library build so the\n * GWT bundle resolves from the consumer's install rather than being inlined.\n *\n * @returns The ELK engine instance.\n */\n private async ensureElk(): Promise<ElkInstance> {\n if (this._elk) {\n return this._elk;\n }\n\n // `elkjs` is an optional peer dep, typed by the local ambient shim in\n // `elkjs.d.ts` and resolved by the consumer's bundler at runtime.\n const { default: ELK } = await import(\"elkjs/lib/elk.bundled.js\");\n\n const elk = this._workerUrl ? new ELK({ workerUrl: this._workerUrl }) : new ELK();\n this._elk = elk;\n\n return elk;\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// The default node renderer for a DiagramView: a themed rounded box wrapping a\n// glyph + label (or a bare label). Its content-derived preferred size is what\n// the view feeds to ELK when a node carries no explicit width/height.\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { IconText } from \"~/component/display/IconText.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link DiagramNode}.\n *\n * @category Components\n */\nexport interface DiagramNodeOptions extends PanelOptions {\n /** Label text shown inside the node. */\n label?: string;\n /** Optional registered glyph name shown before the label. */\n glyph?: string;\n /** Whether the node starts selected. */\n selected?: boolean;\n}\n\n/**\n * User-overridable defaults. The node paints a themed rounded box; its interior\n * inset gives the label structural breathing room away from the border.\n */\nconst _defaultDiagramNodeOptions: Partial<DiagramNodeOptions> = {\n // Vertical 4px / horizontal 8px so a short label never sits flush against\n // the rounded border — structural interior spacing, not cosmetic nudging.\n insets: new Insets(4, 8, 4, 8),\n};\n\n/** Corner radius in pixels for the node's rounded box. */\nconst NODE_BORDER_RADIUS = \"4px\";\n\n/**\n * The default themed node renderer for a\n * [`DiagramView`](/api/component/diagram/classes/DiagramView). Composes a glyph\n * and label inside a rounded, theme-aware box and toggles a `.selected` state\n * rule when selected.\n *\n * @category Components\n */\nclass DiagramNode extends Panel<DiagramNodeOptions> {\n\n /** The inner glyph+label (or bare label) component. */\n private _content!: IconText | Text;\n\n // Lazy `.selected` state rule. The slot caches the wrapper returned by\n // Component's `createStyleRule` builder, which dedupes by selector suffix.\n private declare _selectedStyleRule?: StyleRule;\n private get selectedStyleRule(): StyleRule {\n return this._selectedStyleRule ??= this.createStyleRule(\".selected\");\n }\n\n constructor(options?: DiagramNodeOptions) {\n super(options, { ..._defaultDiagramNodeOptions, layoutManager: new Fit() });\n\n this.setBackgroundColor(\"var(--ts-ui-diagram-node-bg, var(--ts-ui-button-bg, rgb(245, 245, 245)))\");\n this.setBorder(\"1px solid var(--ts-ui-border-color, rgb(180, 180, 180))\");\n this.setBorderRadius(NODE_BORDER_RADIUS);\n this.setCursor(\"pointer\");\n\n this.selectedStyleRule.set(\"borderColor\", \"var(--ts-ui-accent-color, rgb(30, 100, 200))\");\n this.selectedStyleRule.set(\"backgroundColor\", \"var(--ts-ui-diagram-node-selected-bg, var(--ts-ui-table-row-selected, rgba(30, 100, 200, 0.15)))\");\n\n // Content children are built here (not during super's cascade), so the\n // label/glyph/selected values cached pure in `applyOptions` are\n // dispatched now that the row exists.\n this.buildContent(this._options.glyph, this._options.label ?? \"\");\n\n if (this._options.selected !== undefined) {\n this.setSelected(this._options.selected);\n }\n }\n\n /**\n * Caches the label/glyph/selected fields pure to `_options`; they are\n * dispatched from the constructor body once the content child exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DiagramNodeOptions): this {\n super.applyOptions(options);\n\n if (options.label !== undefined) this._options.label = options.label;\n if (options.glyph !== undefined) this._options.glyph = options.glyph;\n if (options.selected !== undefined) this._options.selected = options.selected;\n\n return this;\n }\n\n /**\n * Builds (or rebuilds) the inner content child — an `IconText` when a glyph\n * is present, else a bare `Text`.\n *\n * @param glyph - Optional glyph name.\n * @param label - The label text.\n */\n private buildContent(glyph: string | undefined, label: string): void {\n if (this._content) {\n this.removeComponent(this._content);\n }\n\n this._content = glyph !== undefined ? new IconText(glyph, label) : new Text(label);\n\n // Every Component stamps its own `cursor` (defaulting to `default`) onto\n // its CSS rule, so the label/glyph would override the node's `pointer`\n // wherever they sit under the cursor — the hover cursor would flicker\n // between pointer (over padding) and arrow (over the text). Make the\n // content transparent to pointer events so hover + clicks land on the\n // node itself; `pointer-events: none` inherits, so this one call also\n // covers the glyph and text nested inside an `IconText`. Mirrors how\n // `Button` frees its label row so the button's cursor governs.\n this._content.setPointerEvents(\"none\");\n\n this.addComponent(this._content);\n }\n\n /**\n * Updates the node's label text.\n *\n * @param value - The new label.\n *\n * @returns This node, for method chaining.\n */\n setLabel(value: string): this {\n this._options.label = value;\n this._content.setText(value);\n\n return this;\n }\n\n /**\n * Returns the node's label, or `null` when none was set.\n *\n * @returns The label text, or `null`.\n */\n getLabel(): string | null {\n return this._options.label ?? null;\n }\n\n /**\n * Sets the selected state and toggles the `.selected` CSS class.\n *\n * @param value - True to select, false to deselect.\n *\n * @returns This node, for method chaining.\n */\n setSelected(value: boolean): this {\n this._options.selected = value;\n\n const element = this.getElement();\n\n if (element) {\n DOM.sink.apply(element, { toggleClass: { selected: value } });\n }\n\n return this;\n }\n\n /**\n * Returns whether the node is currently selected.\n *\n * @returns True when selected.\n */\n isSelected(): boolean {\n return this._options.selected ?? false;\n }\n\n /**\n * Re-applies the cached selected class after (re-)render, since a fresh\n * element starts without it.\n *\n * @param element - Optional element from the render pipeline.\n *\n * @returns This node, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element || this.getElement();\n\n if (el && this.isSelected()) {\n DOM.sink.apply(el, { toggleClass: { selected: true } });\n }\n\n return this;\n }\n}\n\nconst DiagramNodeCallable = callable(DiagramNode);\ntype DiagramNodeCallable = DiagramNode;\nexport {\n DiagramNode as _DiagramNode,\n DiagramNodeCallable as DiagramNode,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// The default container renderer for a DiagramView compound node: a titled,\n// translucent box painted behind its (flat-sibling, not DOM-child) leaf/child\n// components. `DiagramView` positions this box at ELK's computed absolute\n// bounds and paints it at the lowest z-index among a compound graph's nodes,\n// so it never intercepts a click meant for a leaf sitting visually inside it.\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { Absolute } from \"~/layout/Absolute.js\";\nimport { IconText } from \"~/component/display/IconText.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link DiagramGroupNode}.\n *\n * @category Components\n */\nexport interface DiagramGroupNodeOptions extends PanelOptions {\n /** The container's header label (e.g. the schema name). */\n label?: string;\n /** Optional registered glyph name shown before the header label. */\n glyph?: string;\n}\n\n/** Corner radius in pixels for the container's box, matching `DiagramNode`. */\nconst GROUP_BORDER_RADIUS = \"4px\";\n\n/**\n * Distance in pixels from the box's top-left corner to the header label's\n * origin — enough breathing room that the label never sits flush against the\n * rounded border, mirroring `DiagramNode`'s interior inset.\n */\nconst HEADER_INSET = 6;\n\n/**\n * The default themed container renderer for a\n * [`DiagramView`](/api/component/diagram/classes/DiagramView) compound node.\n * Paints a translucent, rounded box with a header label pinned to the\n * top-left corner; the rest of the box stays open so the container's\n * children — rendered as separate flat siblings positioned by ELK, not as DOM\n * children of this component — visually read as sitting inside it.\n *\n * @category Components\n */\nclass DiagramGroupNode extends Panel<DiagramGroupNodeOptions> {\n\n /** The header shown at the box's top-left corner: a bare label, or a glyph + label. */\n private _header!: IconText | Text;\n\n constructor(options?: DiagramGroupNodeOptions) {\n super(options, { layoutManager: new Absolute() });\n\n this.setBackgroundColor(\"var(--ts-ui-diagram-group-bg, rgba(120, 120, 120, 0.08))\");\n this.setBorder(\"1px solid var(--ts-ui-diagram-group-border, var(--ts-ui-border-color, rgb(180, 180, 180)))\");\n this.setBorderRadius(GROUP_BORDER_RADIUS);\n\n // The header is built here (not during super's cascade), so the values\n // cached pure in `applyOptions` are dispatched now that the box exists.\n // An IconText when a glyph is present (glyph before the label, mirroring\n // DiagramNode), else a bare Text.\n const label = this._options.label ?? \"\";\n this._header = this._options.glyph !== undefined\n ? new IconText(this._options.glyph, label)\n : new Text(label);\n this._header.setPointerEvents(\"none\");\n this._header.setX(HEADER_INSET);\n this._header.setY(HEADER_INSET);\n this.addComponent(this._header);\n }\n\n /**\n * Caches the label/glyph fields pure to `_options`; they are dispatched from\n * the constructor body once the header child exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DiagramGroupNodeOptions): this {\n super.applyOptions(options);\n\n if (options.label !== undefined) this._options.label = options.label;\n if (options.glyph !== undefined) this._options.glyph = options.glyph;\n\n return this;\n }\n\n /**\n * Updates the container's header label.\n *\n * @param value - The new label.\n *\n * @returns This node, for method chaining.\n */\n setLabel(value: string): this {\n this._options.label = value;\n this._header.setText(value);\n\n return this;\n }\n\n /**\n * Returns the container's header label, or `null` when none was set.\n *\n * @returns The label text, or `null`.\n */\n getLabel(): string | null {\n return this._options.label ?? null;\n }\n}\n\nconst DiagramGroupNodeCallable = callable(DiagramGroupNode);\ntype DiagramGroupNodeCallable = DiagramGroupNode;\nexport {\n DiagramGroupNode as _DiagramGroupNode,\n DiagramGroupNodeCallable as DiagramGroupNode,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// One `<svg>` Component drawing the diagram's edges as `<path>` elements with a\n// shared arrowhead `<marker>`. Follows the Glyph SVG-through-the-seam pattern:\n// the root `<svg>` and every leaf child are created via `DOM.sink.createElementNS`\n// and tracked with `trackHandle`, never raw DOM. The layer is non-interactive\n// (`pointer-events: none`) so node clicks fall through to the node components.\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport type { ElkEdgeSection, ElkPoint } from \"~/component/diagram/ElkLayoutEngine.js\";\nimport type { DiagramEdgeMarker, DiagramEdgeStyle } from \"~/component/diagram/DiagramModel.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** SVG namespace URI. */\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/** Themed stroke for edge paths and the arrowhead. */\nconst EDGE_STROKE = \"var(--ts-ui-diagram-edge, var(--ts-ui-border-color, rgb(120, 120, 120)))\";\n\n/** Edge path stroke width in pixels — a hairline that reads at any zoom. */\nconst EDGE_STROKE_WIDTH = \"1.5\";\n\n/** Font size (px) of an edge's mid-route label. */\nconst LABEL_FONT_SIZE = \"12\";\n\n/**\n * Background colour and width (px) of the halo drawn behind an edge label. The\n * label text is painted `stroke`-then-`fill` (`paint-order`), so this\n * background-coloured stroke masks the edge line running under the glyphs — the\n * label reads clearly instead of being crossed out by the edge.\n */\nconst LABEL_HALO = \"var(--ts-ui-diagram-bg, var(--ts-ui-panel-bg, rgb(255, 255, 255)))\";\nconst LABEL_HALO_WIDTH = \"4\";\n\n/** Arrowhead marker box size in user units (matches the `refX`/`refY` below). */\nconst ARROW_SIZE = 8;\n\n/** Dash pattern applied when an edge's `style.dashed` is set. */\nconst DASH_ARRAY = \"6 4\";\n\n/**\n * Non-`\"arrow\"` crow's-foot marker kinds this layer pre-defines in `<defs>`.\n * Kept as a literal array (rather than deriving from {@link DiagramEdgeMarker})\n * so `createRootElement` can iterate it directly.\n */\nconst CROWS_FOOT_MARKERS: readonly Exclude<DiagramEdgeMarker, \"arrow\">[] =\n [\"one\", \"zeroOrOne\", \"oneOrMany\", \"zeroOrMany\"];\n\n/**\n * One crow's-foot marker's geometry: its `<marker>` box (`width`/`height`),\n * the point within that box the edge vertex anchors to (`refX`/`refY`), and\n * its child shapes as bare SVG element specs. Every marker uses\n * `orient=\"auto-start-reverse\"`, so the same definition auto-flips when used\n * at `marker-start` and reads unreversed at `marker-end`.\n */\ninterface MarkerGeometry {\n width: number;\n height: number;\n refX: number;\n refY: number;\n /** Child shapes, each a bare tag name plus its SVG attributes. */\n parts: Array<{ tag: \"path\" | \"circle\"; attrs: Record<string, string> }>;\n}\n\n/**\n * Geometry table for the four crow's-foot markers. Coordinates are chosen so\n * every marker's \"attach\" edge sits at `x = width` (the vertex, via `refX`)\n * and its \"open\" edge sits toward `x = 0` (away from the node) — exact pixels\n * are a manual visual-tuning step, not a correctness concern (see the\n * fk-diagram-cardinality-and-index-coverage plan).\n */\nconst MARKER_GEOMETRY: Record<Exclude<DiagramEdgeMarker, \"arrow\">, MarkerGeometry> = {\n // \"one and only one\": two perpendicular bars across the line.\n one: {\n width: 12, height: 12, refX: 12, refY: 6,\n parts: [{ tag: \"path\", attrs: { d: \"M 4 0 L 4 12 M 8 0 L 8 12\" } }],\n },\n // \"zero or one\": one perpendicular bar plus a small inboard circle.\n zeroOrOne: {\n width: 16, height: 12, refX: 16, refY: 6,\n parts: [\n { tag: \"path\", attrs: { d: \"M 12 0 L 12 12\" } },\n { tag: \"circle\", attrs: { cx: \"6\", cy: \"6\", r: \"4\" } },\n ],\n },\n // \"one or many\": a three-prong crow's foot plus one perpendicular bar just inboard of it.\n oneOrMany: {\n width: 16, height: 12, refX: 16, refY: 6,\n parts: [\n { tag: \"path\", attrs: { d: \"M 0 6 L 12 0 M 0 6 L 12 6 M 0 6 L 12 12\" } },\n { tag: \"path\", attrs: { d: \"M 14 0 L 14 12\" } },\n ],\n },\n // \"zero or many\": a three-prong crow's foot plus a small inboard circle.\n zeroOrMany: {\n width: 18, height: 12, refX: 18, refY: 6,\n parts: [\n { tag: \"path\", attrs: { d: \"M 6 6 L 18 0 M 6 6 L 18 6 M 6 6 L 18 12\" } },\n { tag: \"circle\", attrs: { cx: \"3\", cy: \"6\", r: \"3\" } },\n ],\n },\n};\n\n/**\n * A single routed edge: its identity, the ELK sections describing its\n * polyline route (start point, optional bend points, end point), and its\n * optional visual style (joined in from the model by\n * [`DiagramView.applyLayout`](/api/component/diagram/classes/DiagramView)).\n */\nexport interface DiagramEdgeRoute {\n id: string;\n sections: ElkEdgeSection[];\n /** Optional cardinality/dependency style. Absent = today's plain arrow-ended edge. */\n style?: DiagramEdgeStyle;\n}\n\n/**\n * The point used to place an edge's optional label: the halfway point *along the\n * routed polyline* (start → bend points → end), measured by arc length. This\n * stays visually centred on the edge even when it bends — a bend point can sit\n * far off centre and drag the label toward one endpoint.\n *\n * @param sections - The edge's routed sections.\n * @returns The label anchor point.\n */\nfunction labelPoint(sections: ElkEdgeSection[]): ElkPoint {\n const section = sections[0];\n\n if (!section) {\n return { x: 0, y: 0 };\n }\n\n const points = [section.startPoint, ...(section.bendPoints ?? []), section.endPoint];\n\n return midpointAlong(points);\n}\n\n/**\n * The point half the total length along a polyline, interpolated within the\n * segment that straddles the midpoint.\n *\n * @param points - The polyline vertices, in order (length >= 1).\n * @returns The arc-length midpoint.\n */\nfunction midpointAlong(points: ElkPoint[]): ElkPoint {\n const segmentLength = (a: ElkPoint, b: ElkPoint): number => Math.hypot(b.x - a.x, b.y - a.y);\n\n let total = 0;\n\n for (let i = 1; i < points.length; i++) {\n total += segmentLength(points[i - 1], points[i]);\n }\n\n let remaining = total / 2;\n\n for (let i = 1; i < points.length; i++) {\n const length = segmentLength(points[i - 1], points[i]);\n\n if (length >= remaining) {\n const t = length === 0 ? 0 : remaining / length;\n\n return {\n x: points[i - 1].x + (points[i].x - points[i - 1].x) * t,\n y: points[i - 1].y + (points[i].y - points[i - 1].y) * t,\n };\n }\n\n remaining -= length;\n }\n\n return points[points.length - 1];\n}\n\n/**\n * Serialises an edge's ELK sections into an SVG path `d` string. Each section\n * starts with a move to its `startPoint`, threads any `bendPoints`, and lines to\n * its `endPoint`.\n *\n * @param sections - The edge's routed sections.\n * @returns The SVG path data, or an empty string when there is nothing to draw.\n */\nfunction buildPathData(sections: ElkEdgeSection[]): string {\n const parts: string[] = [];\n\n for (const section of sections) {\n parts.push(`M ${section.startPoint.x} ${section.startPoint.y}`);\n\n for (const bend of section.bendPoints ?? []) {\n parts.push(`L ${bend.x} ${bend.y}`);\n }\n\n parts.push(`L ${section.endPoint.x} ${section.endPoint.y}`);\n }\n\n return parts.join(\" \");\n}\n\n/**\n * The SVG edge layer for a [`DiagramView`](/api/component/diagram/classes/DiagramView).\n * Owns exactly one `<svg>` element and rebuilds its `<path>` children from ELK\n * edge routes whenever the diagram re-lays-out.\n *\n * @category Components\n */\nclass DiagramEdgeLayer extends Component<ComponentOptions> {\n\n /** Cached edge routes, redrawn at render time and on every `setEdges`. */\n private _edges: DiagramEdgeRoute[] = [];\n\n /** Handles of the currently-drawn `<path>`/`<text>` children, released on rebuild. */\n private _pathHandles: Handle[] = [];\n\n /** Per-instance arrowhead marker id, referenced by each plain edge's `marker-end`. */\n private readonly _markerId: string;\n\n /** Per-instance crow's-foot marker ids, keyed by marker kind. */\n private readonly _crowsFootMarkerIds: Record<Exclude<DiagramEdgeMarker, \"arrow\">, string>;\n\n constructor(options?: ComponentOptions) {\n super(options);\n\n this._markerId = `${this.getId()}-arrow`;\n this._crowsFootMarkerIds = Object.fromEntries(\n CROWS_FOOT_MARKERS.map(kind => [kind, `${this.getId()}-${kind}`]),\n ) as Record<Exclude<DiagramEdgeMarker, \"arrow\">, string>;\n\n // Non-interactive overlay: clicks must reach the node components beneath.\n this.setPointerEvents(\"none\");\n }\n\n /**\n * Resolves a marker kind to its namespaced `url(#…)` reference, or\n * `undefined` when the kind is absent (no marker on that end).\n *\n * @param kind - The requested marker kind, if any.\n * @returns The `url(#id)` reference, or `undefined`.\n */\n private markerUrl(kind: DiagramEdgeMarker | undefined): string | undefined {\n if (!kind) {\n return undefined;\n }\n\n const id = kind === \"arrow\" ? this._markerId : this._crowsFootMarkerIds[kind];\n\n return `url(#${id})`;\n }\n\n /**\n * Replaces the drawn edges and rebuilds the path children.\n *\n * @param edges - The routed edges to draw.\n *\n * @returns This layer, for method chaining.\n */\n setEdges(edges: DiagramEdgeRoute[]): this {\n this._edges = edges;\n this.rebuildPaths();\n\n return this;\n }\n\n /**\n * Creates the root `<svg>` with the arrowhead marker plus one `<marker>` per\n * crow's-foot kind in a shared `<defs>`. Every marker and its child shapes\n * are seam-created leaf children tracked for release.\n *\n * @returns The root `<svg>` handle.\n */\n protected createRootElement(): Handle {\n const svg = DOM.sink.createElementNS(SVG_NS, \"svg\");\n const defs = DOM.sink.createElementNS(SVG_NS, \"defs\");\n\n this.createArrowMarker(defs);\n\n for (const kind of CROWS_FOOT_MARKERS) {\n this.createCrowsFootMarker(defs, kind);\n }\n\n DOM.sink.appendChild(svg, defs);\n this.trackHandle(defs);\n\n return svg;\n }\n\n /**\n * Defines the default arrowhead `<marker>` into `defs`. `orient=\"auto-start-\n * reverse\"` so the one definition serves both `marker-end` (unreversed, the\n * common case) and `marker-start` (auto-flipped to point back out of the\n * source node) — e.g. a data-flow edge that draws its arrow at the source end.\n *\n * @param defs - The `<defs>` element to append into.\n */\n private createArrowMarker(defs: Handle): void {\n const marker = DOM.sink.createElementNS(SVG_NS, \"marker\");\n\n DOM.sink.apply(marker, { setAttr: {\n id: this._markerId,\n markerWidth: String(ARROW_SIZE),\n markerHeight: String(ARROW_SIZE),\n refX: String(ARROW_SIZE),\n refY: String(ARROW_SIZE / 2),\n orient: \"auto-start-reverse\",\n markerUnits: \"userSpaceOnUse\",\n } });\n\n const arrow = DOM.sink.createElementNS(SVG_NS, \"path\");\n DOM.sink.apply(arrow, { setAttr: {\n d: `M 0 0 L ${ARROW_SIZE} ${ARROW_SIZE / 2} L 0 ${ARROW_SIZE} z`,\n fill: EDGE_STROKE,\n } });\n\n DOM.sink.appendChild(marker, arrow);\n DOM.sink.appendChild(defs, marker);\n\n this.trackHandle(marker);\n this.trackHandle(arrow);\n }\n\n /**\n * Defines one crow's-foot `<marker>` into `defs` from its\n * {@link MARKER_GEOMETRY} entry. `orient=\"auto-start-reverse\"` lets the same\n * definition serve both `marker-start` (auto-flipped) and `marker-end`\n * (unreversed).\n *\n * @param defs - The `<defs>` element to append into.\n * @param kind - Which crow's-foot marker to define.\n */\n private createCrowsFootMarker(defs: Handle, kind: Exclude<DiagramEdgeMarker, \"arrow\">): void {\n const geometry = MARKER_GEOMETRY[kind];\n const marker = DOM.sink.createElementNS(SVG_NS, \"marker\");\n\n DOM.sink.apply(marker, { setAttr: {\n id: this._crowsFootMarkerIds[kind],\n markerWidth: String(geometry.width),\n markerHeight: String(geometry.height),\n refX: String(geometry.refX),\n refY: String(geometry.refY),\n orient: \"auto-start-reverse\",\n markerUnits: \"userSpaceOnUse\",\n } });\n\n DOM.sink.appendChild(defs, marker);\n this.trackHandle(marker);\n\n for (const part of geometry.parts) {\n const shape = DOM.sink.createElementNS(SVG_NS, part.tag);\n\n DOM.sink.apply(shape, { setAttr: {\n ...part.attrs,\n fill: \"none\",\n stroke: EDGE_STROKE,\n } });\n\n DOM.sink.appendChild(marker, shape);\n this.trackHandle(shape);\n }\n }\n\n /**\n * Renders the root element and draws the cached edges.\n *\n * @returns The rendered root element.\n */\n protected render(): Handle {\n const element = super.render();\n\n this.rebuildPaths();\n\n return element;\n }\n\n /**\n * Clears the previously-drawn paths and rebuilds them from the cached edge\n * routes. No-op before the element exists — `render` performs the first draw.\n */\n private rebuildPaths(): void {\n const svg = this.getElement();\n\n if (!svg) {\n return;\n }\n\n for (const handle of this._pathHandles) {\n DOM.sink.removeChild(svg, handle);\n this.untrackHandle(handle);\n DOM.sink.release(handle);\n }\n\n this._pathHandles = [];\n\n for (const edge of this._edges) {\n const d = buildPathData(edge.sections);\n\n if (!d) {\n continue;\n }\n\n const path = DOM.sink.createElementNS(SVG_NS, \"path\");\n const style = edge.style;\n\n // No style: today's back-compat behaviour — a plain arrow at the end,\n // no start marker. With a style, each end draws only the marker (if\n // any) the style names; an edge can carry cardinality with no\n // marker-end at all.\n const markerEnd = style ? this.markerUrl(style.endMarker) : this.markerUrl(\"arrow\");\n const markerStart = style ? this.markerUrl(style.startMarker) : undefined;\n\n const attrs: Record<string, string> = {\n d,\n fill: \"none\",\n stroke: style?.stroke ?? EDGE_STROKE,\n \"stroke-width\": EDGE_STROKE_WIDTH,\n };\n\n if (markerEnd) {\n attrs[\"marker-end\"] = markerEnd;\n }\n\n if (markerStart) {\n attrs[\"marker-start\"] = markerStart;\n }\n\n if (style?.dashed) {\n attrs[\"stroke-dasharray\"] = DASH_ARRAY;\n }\n\n DOM.sink.apply(path, { setAttr: attrs });\n\n DOM.sink.appendChild(svg, path);\n this.trackHandle(path);\n this._pathHandles.push(path);\n\n if (style?.label) {\n this.drawLabel(svg, edge, style.label);\n }\n }\n }\n\n /**\n * Draws one edge's optional mid-route label as a `<text>` element.\n *\n * @param svg - The root `<svg>` handle to append into.\n * @param edge - The edge route the label belongs to (for its anchor point).\n * @param label - The label text.\n */\n private drawLabel(svg: Handle, edge: DiagramEdgeRoute, label: string): void {\n const point = labelPoint(edge.sections);\n const text = DOM.sink.createElementNS(SVG_NS, \"text\");\n\n DOM.sink.apply(text, {\n setAttr: {\n x: String(point.x),\n y: String(point.y),\n fill: EDGE_STROKE,\n // Background-coloured halo painted first, so the edge line under the\n // glyphs is masked and the label reads clearly (not crossed out).\n stroke: LABEL_HALO,\n \"stroke-width\": LABEL_HALO_WIDTH,\n \"stroke-linejoin\": \"round\",\n \"paint-order\": \"stroke\",\n \"font-size\": LABEL_FONT_SIZE,\n \"text-anchor\": \"middle\",\n \"dominant-baseline\": \"central\",\n },\n text: label,\n });\n\n DOM.sink.appendChild(svg, text);\n this.trackHandle(text);\n this._pathHandles.push(text);\n }\n}\n\nconst DiagramEdgeLayerCallable = callable(DiagramEdgeLayer);\ntype DiagramEdgeLayerCallable = DiagramEdgeLayer;\nexport {\n DiagramEdgeLayer as _DiagramEdgeLayer,\n DiagramEdgeLayerCallable as DiagramEdgeLayer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n//\n// A read-only graph/diagram viewer. Takes a framework-native node/edge model,\n// runs it through ELK for automatic layout (off-seam pure compute, lazily\n// imported), and renders themed node components plus an SVG edge layer with pan,\n// zoom, and node selection.\n//\n// Structure: DiagramView (the scrolling Panel viewport) owns a single content\n// host Container (Absolute layout) that carries the zoom transform and holds the\n// node components + the edge layer. Pan is native scroll on the viewport; zoom\n// is a `scale()` transform on the content host, whose box is sized to\n// graphBounds × zoom so the native scroll extent stays correct.\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { Container } from \"~/core/Container.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Absolute } from \"~/layout/Absolute.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { DiagramData, DiagramNodeData } from \"~/component/diagram/DiagramModel.js\";\nimport { ElkLayoutEngine, DiagramLayoutResult } from \"~/component/diagram/ElkLayoutEngine.js\";\nimport { DiagramNode } from \"~/component/diagram/DiagramNode.js\";\nimport { DiagramGroupNode } from \"~/component/diagram/DiagramGroupNode.js\";\nimport { DiagramEdgeLayer } from \"~/component/diagram/DiagramEdgeLayer.js\";\nimport type { DiagramEdgeRoute } from \"~/component/diagram/DiagramEdgeLayer.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** Factory producing a node component from a node's model data. */\nexport type DiagramNodeRenderer = (data: DiagramNodeData) => Component;\n\n/** String-literal union of the events emitted by {@link DiagramView}. */\nexport type DiagramViewEvent = \"selection\" | \"activate\" | \"layout\";\n\n/** Default initial zoom factor. */\nconst DEFAULT_ZOOM = 1;\n\n/** Default minimum zoom factor (a quarter scale). */\nconst DEFAULT_MIN_ZOOM = 0.25;\n\n/** Default maximum zoom factor (4× scale). */\nconst DEFAULT_MAX_ZOOM = 4;\n\n/** Multiplicative zoom step per wheel notch. */\nconst WHEEL_ZOOM_STEP = 1.1;\n\n// Paint order for a compound graph (one with at least one container): the\n// container boxes sit behind the edges, which sit behind the leaves, so a\n// leaf's click is never intercepted by its own container box. A leaf/container\n// component is always freshly built by rebuildNodes, so on a flat graph it\n// simply starts at DEFAULT_Z_INDEX and is never touched. The edge layer is the\n// one exception — it is a persistent child of the content host (built once in\n// the constructor, never torn down/rebuilt — see the constructor comment), so\n// a flat pass must explicitly restore DEFAULT_Z_INDEX in case an earlier\n// compound pass on the same view left EDGE_LAYER_Z_INDEX behind.\nconst DEFAULT_Z_INDEX = 0;\nconst CONTAINER_Z_INDEX = 0;\nconst EDGE_LAYER_Z_INDEX = 1;\nconst LEAF_Z_INDEX = 2;\n\n/**\n * Construction-time options for {@link DiagramView}.\n *\n * @category Components\n */\nexport interface DiagramViewOptions extends PanelOptions {\n /** The initial graph to lay out and render. */\n data?: DiagramData;\n /** Factory for node components; defaults to building a `DiagramNode`. */\n nodeRenderer?: DiagramNodeRenderer;\n /** Factory for compound container components; defaults to `DiagramGroupNode`. */\n groupRenderer?: DiagramNodeRenderer;\n /** Default ELK layout options applied to every layout pass. */\n layoutOptions?: Record<string, string>;\n /** Optional URL of a consumer-hosted `elk-worker.js` for off-thread layout. */\n elkWorkerUrl?: string;\n /** Minimum zoom factor (default 0.25). */\n minZoom?: number;\n /** Maximum zoom factor (default 4). */\n maxZoom?: number;\n /** Initial zoom factor (default 1). */\n zoom?: number;\n /** Construction-time listener bag dispatched to {@link DiagramView.on}. */\n listeners?: {\n selection?: (nodes: DiagramNodeData[]) => void;\n activate?: (node: DiagramNodeData) => void;\n layout?: () => void;\n };\n}\n\n/**\n * A read-only automatic-layout graph viewer with pan, zoom, and node selection.\n *\n * Pass a graph via the `data` option or {@link DiagramView.setData}. Layout runs\n * asynchronously through ELK; a `\"layout\"` event fires after each successful\n * pass, a `\"selection\"` event fires when the selected node changes, and an\n * `\"activate\"` event fires when a node is double-clicked.\n *\n * @example\n * ```typescript\n * const view = new DiagramView({\n * data: {\n * nodes: [{ id: \"a\", label: \"A\" }, { id: \"b\", label: \"B\" }],\n * edges: [{ id: \"e\", source: \"a\", target: \"b\" }],\n * },\n * });\n * view.on(\"selection\", (nodes) => console.log(nodes));\n * ```\n *\n * @category Components\n */\nclass DiagramView extends Panel<DiagramViewOptions> {\n\n /** The transform-carrying content host holding nodes + the edge layer. */\n private _contentHost!: Container;\n\n /** The SVG edge layer, a persistent child of the content host. */\n private _edgeLayer!: DiagramEdgeLayer;\n\n /** The ELK layout adapter. Runtime state, off the options bag. */\n private _engine!: ElkLayoutEngine;\n\n /** Node components keyed by node id. */\n private _nodeComponents: Map<string, Component> = new Map();\n\n /** Node model data keyed by node id (selection payload source). */\n private _nodeData: Map<string, DiagramNodeData> = new Map();\n\n /** Ids of the compound container nodes built by the last `rebuildNodes`. */\n private _containerIds: Set<string> = new Set();\n\n /** Currently selected node data (single-select). */\n private _selection: DiagramNodeData[] = [];\n\n /** Monotonic layout token; guards against a stale in-flight layout landing. */\n private _layoutGeneration: number = 0;\n\n /** Cached graph bounding box from the last successful layout. */\n private _graphWidth: number = 0;\n private _graphHeight: number = 0;\n\n /** Custom-event fan-out for `\"selection\"` / `\"layout\"`. */\n private _listeners: ListenerBag<DiagramViewEvent> = new ListenerBag<DiagramViewEvent>();\n\n /** Pan drag state. */\n private _panning: boolean = false;\n private _panStartX: number = 0;\n private _panStartY: number = 0;\n private _panScrollLeft: number = 0;\n private _panScrollTop: number = 0;\n\n constructor(options?: DiagramViewOptions) {\n super(options, { zoom: DEFAULT_ZOOM, minZoom: DEFAULT_MIN_ZOOM, maxZoom: DEFAULT_MAX_ZOOM });\n\n this.setAutoScroll(\"auto\");\n\n this._engine = this.createEngine();\n\n // Nodes are laid out at unscaled graph coordinates under the host's\n // `scale(zoom)` transform, so the host box (graph bounds × zoom) is\n // smaller than the node coordinates whenever zoom < 1. The base\n // `overflow: hidden` default would then crop the diagram to a\n // `zoom`-fraction of the graph; keep it visible so the whole graph\n // paints and the overflowing scaled nodes drive the native scroll\n // extent (which tracks the visual size — graph bounds × zoom).\n this._contentHost = new Container({ layoutManager: new Absolute(), overflow: \"visible\" });\n this._contentHost.setTransformOrigin(\"0 0\");\n this.addComponent(this._contentHost);\n\n this._edgeLayer = new DiagramEdgeLayer();\n this._contentHost.addComponent(this._edgeLayer);\n\n this.applyListeners(options?.listeners);\n\n if (this._options.zoom !== undefined) {\n this.setZoom(this._options.zoom);\n }\n\n if (this._options.data) {\n this.setData(this._options.data);\n }\n }\n\n /**\n * Builds the layout engine. Isolated as a factory so the swappable-engine\n * seam can be exercised (a test substitutes a stub engine that returns a\n * fixed result without importing ELK).\n *\n * @returns A fresh {@link ElkLayoutEngine}.\n */\n protected createEngine(): ElkLayoutEngine {\n return new ElkLayoutEngine(this._options.elkWorkerUrl);\n }\n\n /**\n * Caches consumer-configurable fields pure to `_options`; effects that need\n * the content host (built in the constructor body) are dispatched there.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DiagramViewOptions): this {\n super.applyOptions(options);\n\n if (options.data !== undefined) this._options.data = options.data;\n if (options.nodeRenderer !== undefined) this._options.nodeRenderer = options.nodeRenderer;\n if (options.groupRenderer !== undefined) this._options.groupRenderer = options.groupRenderer;\n if (options.layoutOptions !== undefined) this._options.layoutOptions = options.layoutOptions;\n if (options.elkWorkerUrl !== undefined) this._options.elkWorkerUrl = options.elkWorkerUrl;\n if (options.minZoom !== undefined) this._options.minZoom = options.minZoom;\n if (options.maxZoom !== undefined) this._options.maxZoom = options.maxZoom;\n if (options.zoom !== undefined) this._options.zoom = options.zoom;\n\n return this;\n }\n\n /**\n * Replaces the graph, rebuilds node components, and triggers an async layout.\n *\n * @param data - The new graph.\n *\n * @returns This view, for method chaining.\n */\n setData(data: DiagramData): this {\n this._options.data = data;\n this.rebuildNodes(data);\n this.relayout(data);\n\n return this;\n }\n\n /**\n * Returns the current graph, or `null` when none has been set.\n *\n * @returns The graph data, or `null`.\n */\n getData(): DiagramData | null {\n return this._options.data ?? null;\n }\n\n /**\n * Tears down the old node components + selection and builds fresh ones via\n * the node renderer (default: `DiagramNode`), adding them to the content\n * host. The persistent edge layer is cleared but not removed. Recurses into\n * `children`: a container node (non-empty `children`) is built via the\n * group renderer (default: `DiagramGroupNode`) and its children are built\n * the same way, so container + leaf components all land as flat siblings in\n * `_nodeComponents`/`_nodeData`, keyed by id — a container is never a DOM\n * parent of its children (that would perturb the single-content-host model\n * `nodeIdAt` hit-testing relies on).\n *\n * @param data - The graph whose nodes to build.\n */\n private rebuildNodes(data: DiagramData): void {\n for (const component of this._nodeComponents.values()) {\n this._contentHost.removeComponent(component);\n }\n\n this._nodeComponents.clear();\n this._nodeData.clear();\n this._containerIds.clear();\n this._selection = [];\n this._edgeLayer.setEdges([]);\n\n const renderer = this._options.nodeRenderer ?? ((node: DiagramNodeData): Component =>\n new DiagramNode({ label: node.label, glyph: node.glyph }));\n const groupRenderer = this._options.groupRenderer ?? ((node: DiagramNodeData): Component =>\n new DiagramGroupNode({ label: node.label, glyph: node.glyph }));\n\n const build = (nodes: DiagramNodeData[]): void => {\n for (const node of nodes) {\n const isContainer = (node.children?.length ?? 0) > 0;\n const component = isContainer ? groupRenderer(node) : renderer(node);\n\n this._nodeComponents.set(node.id, component);\n this._nodeData.set(node.id, node);\n this._contentHost.addComponent(component);\n\n if (isContainer) {\n this._containerIds.add(node.id);\n build(node.children!);\n }\n }\n };\n\n build(data.nodes);\n }\n\n /**\n * Collects node sizes, bumps the generation token, and runs the async ELK\n * layout. A stale result (older token) is dropped; a failed layout tears the\n * nodes down so the view stays empty.\n *\n * @param data - The graph to lay out.\n */\n private relayout(data: DiagramData): void {\n const sizes = this.collectNodeSizes(data);\n\n this._layoutGeneration += 1;\n const generation = this._layoutGeneration;\n\n this._engine\n .layout(data, sizes, this._options.layoutOptions)\n .then((result) => this.applyLayout(result, generation))\n .catch(() => this.handleLayoutFailure(generation));\n }\n\n /**\n * Resolves each node's size fed to ELK: the explicit `width`/`height` from\n * the model when present, else the node component's preferred size.\n * Recurses into `children` so every container and leaf is represented; a\n * container's entry is harmless-but-unused since `buildElkGraph` computes a\n * container's box from its contents rather than consulting this map.\n *\n * @param data - The graph whose node sizes to collect.\n * @returns A map of node id to resolved size.\n */\n private collectNodeSizes(data: DiagramData): Map<string, { width: number; height: number }> {\n const sizes = new Map<string, { width: number; height: number }>();\n\n const collect = (nodes: DiagramNodeData[]): void => {\n for (const node of nodes) {\n const component = this._nodeComponents.get(node.id);\n const preferred = component?.getPreferredSize();\n\n sizes.set(node.id, {\n width: node.width ?? preferred?.width ?? 0,\n height: node.height ?? preferred?.height ?? 0,\n });\n\n if (node.children && node.children.length > 0) {\n collect(node.children);\n }\n }\n };\n\n collect(data.nodes);\n\n return sizes;\n }\n\n /**\n * Applies a completed layout: positions each node, sizes + scales the\n * content host, redraws the edges, and emits `\"layout\"`. A stale result\n * (superseded by a newer `setData`) is ignored.\n *\n * @param result - The ELK layout result.\n * @param generation - The generation token captured when the layout started.\n */\n private applyLayout(result: DiagramLayoutResult, generation: number): void {\n if (generation !== this._layoutGeneration) {\n return;\n }\n\n for (const node of result.nodes) {\n const component = this._nodeComponents.get(node.id);\n\n if (component) {\n component.setPreferredSize({ width: node.width, height: node.height });\n component.setX(node.x);\n component.setY(node.y);\n }\n }\n\n this._graphWidth = result.width;\n this._graphHeight = result.height;\n\n this._edgeLayer.setX(0);\n this._edgeLayer.setY(0);\n this._edgeLayer.setPreferredSize({ width: result.width, height: result.height });\n this._edgeLayer.setEdges(this.joinEdgeStyles(result.edges));\n\n this.applyContainerZIndex();\n\n this.applyZoomToHost();\n\n this.scheduleLayout();\n\n this.emit(\"layout\");\n }\n\n /**\n * Applies the compound paint order — containers behind the edge layer,\n * leaves in front of it — when `rebuildNodes` built at least one\n * container. A flat graph's own (freshly-built) node components are never\n * touched here — they start at `DEFAULT_Z_INDEX` — but the persistent edge\n * layer is explicitly restored to `DEFAULT_Z_INDEX`, since an earlier\n * compound `setData` on this same view could have left it elevated (see\n * the module-level z-index constants).\n */\n private applyContainerZIndex(): void {\n if (this._containerIds.size === 0) {\n this._edgeLayer.setZIndex(DEFAULT_Z_INDEX);\n\n return;\n }\n\n this._edgeLayer.setZIndex(EDGE_LAYER_Z_INDEX);\n\n for (const [id, component] of this._nodeComponents) {\n component.setZIndex(this._containerIds.has(id) ? CONTAINER_Z_INDEX : LEAF_Z_INDEX);\n }\n }\n\n /**\n * Re-attaches each model edge's `style` to its layout-routed counterpart.\n * ELK's result carries only `{ id, sections }` — `style` never survives the\n * round trip — so this joins by edge id back to `this._options.data.edges`,\n * the model the layout was computed from.\n *\n * @param edges - The ELK-routed edges (id + sections only).\n * @returns The same routes, each carrying its model edge's `style` if any.\n */\n private joinEdgeStyles(edges: DiagramEdgeRoute[]): DiagramEdgeRoute[] {\n const modelById = new Map(this._options.data?.edges.map(e => [e.id, e]) ?? []);\n\n return edges.map(edge => ({ ...edge, style: modelById.get(edge.id)?.style }));\n }\n\n /**\n * Removes the just-built nodes when a layout fails (e.g. `elkjs` absent), so\n * the view stays empty rather than showing stacked, unpositioned nodes.\n *\n * @param generation - The generation token captured when the layout started.\n */\n private handleLayoutFailure(generation: number): void {\n if (generation !== this._layoutGeneration) {\n return;\n }\n\n for (const component of this._nodeComponents.values()) {\n this._contentHost.removeComponent(component);\n }\n\n this._nodeComponents.clear();\n this._nodeData.clear();\n }\n\n /**\n * Writes the content host's `scale()` transform and its scaled box from the\n * cached graph bounds and current zoom.\n */\n private applyZoomToHost(): void {\n const zoom = this.getZoom();\n\n this._contentHost.setTransform(`scale(${zoom})`);\n\n // The host's box feeds the viewport's native scroll extent, and the\n // `scale(zoom)` transform above already scales the box's contribution:\n // Chrome enlarges a scaled-up element's scroll-overflow footprint but\n // ignores a scaled-down one (it keeps the untransformed box). So the box\n // multiplier is clamped at 1 — on zoom-in the transform supplies the\n // growth (a `× zoom` box would overshoot to graph bounds × zoom² and add\n // phantom scrollbars), while on zoom-out the box must shrink with `zoom`\n // itself since the transform's shrink is ignored. Either way the host's\n // effective extent matches the nodes' own scaled extent (bounds × zoom).\n const boxScale = Math.min(zoom, 1);\n this._contentHost.setPreferredSize({ width: this._graphWidth * boxScale, height: this._graphHeight * boxScale });\n }\n\n /**\n * Returns the current zoom factor.\n *\n * @returns The zoom factor.\n */\n getZoom(): number {\n return this._options.zoom ?? this._defaultOptions.zoom ?? DEFAULT_ZOOM;\n }\n\n /**\n * Sets the zoom factor, clamped to `[minZoom, maxZoom]`, and re-scales the\n * content host.\n *\n * @param zoom - The desired zoom factor.\n *\n * @returns This view, for method chaining.\n */\n setZoom(zoom: number): this {\n this._options.zoom = this.clampZoom(zoom);\n this.applyZoomToHost();\n\n return this;\n }\n\n /**\n * Fits the whole graph into the viewport by choosing the largest zoom at\n * which the graph bounds fit both axes. Requires a completed layout.\n *\n * @returns This view, for method chaining.\n */\n zoomToFit(): this {\n if (this._graphWidth <= 0 || this._graphHeight <= 0) {\n return this;\n }\n\n const zoomX = this.getWidth() / this._graphWidth;\n const zoomY = this.getHeight() / this._graphHeight;\n\n this.setZoom(Math.min(zoomX, zoomY));\n\n return this;\n }\n\n /**\n * Clamps a zoom request to the configured `[minZoom, maxZoom]` range.\n *\n * @param zoom - The requested zoom factor.\n * @returns The clamped zoom factor.\n */\n private clampZoom(zoom: number): number {\n const min = this._options.minZoom ?? this._defaultOptions.minZoom ?? DEFAULT_MIN_ZOOM;\n const max = this._options.maxZoom ?? this._defaultOptions.maxZoom ?? DEFAULT_MAX_ZOOM;\n\n return Math.max(min, Math.min(max, zoom));\n }\n\n /**\n * Returns the current selection (single-select).\n *\n * @returns A copy of the selected node data array.\n */\n getSelection(): DiagramNodeData[] {\n return [...this._selection];\n }\n\n /**\n * Selects a node programmatically without emitting `\"selection\"` (mirroring\n * the Tree precedent). Passing `null` clears the selection.\n *\n * @param id - The node id to select, or `null` to clear.\n *\n * @returns This view, for method chaining.\n */\n selectNode(id: string | null): this {\n this.setSelection(id);\n\n return this;\n }\n\n /**\n * Scrolls the viewport so the given node is centred, without changing the\n * selection or emitting any event. No-op for an unknown id or before the\n * first layout has positioned the node. Pair with {@link selectNode} to both\n * highlight and reveal.\n *\n * @param id - The node id to centre, or a no-op when not found.\n *\n * @returns This view, for method chaining.\n */\n revealNode(id: string): this {\n const component = this._nodeComponents.get(id);\n\n if (!component) {\n return this;\n }\n\n const zoom = this.getZoom();\n\n // Node centre in scaled (on-screen) coordinates; the inverse of\n // _handleWheel's graphX = (pointer + scroll) / zoom. Nodes carry unscaled\n // graph coords via getX/getY under the content host's scale(zoom).\n const centreX = (component.getX() + component.getWidth() / 2) * zoom;\n const centreY = (component.getY() + component.getHeight() / 2) * zoom;\n\n // Scroll so the node centre lands at the viewport centre; the DOM clamps\n // the scroll offsets to their valid range on write-back.\n this.setScrollLeft(Math.max(0, centreX - this.getWidth() / 2));\n this.setScrollTop (Math.max(0, centreY - this.getHeight() / 2));\n\n return this;\n }\n\n /**\n * Updates the selection state and toggles each node's selected visual.\n *\n * @param id - The node id to select, or `null` to clear.\n */\n private setSelection(id: string | null): void {\n for (const component of this._nodeComponents.values()) {\n this.applySelectedVisual(component, false);\n }\n\n const data = id === null ? undefined : this._nodeData.get(id);\n\n if (data) {\n this._selection = [data];\n this.applySelectedVisual(this._nodeComponents.get(id!)!, true);\n } else {\n this._selection = [];\n }\n }\n\n /**\n * Toggles a node component's selected visual when it supports one (the\n * default `DiagramNode` does; a custom renderer may not).\n *\n * @param component - The node component.\n * @param selected - The selected state to apply.\n */\n private applySelectedVisual(component: Component, selected: boolean): void {\n const node = component as unknown as { setSelected?: (value: boolean) => void };\n\n node.setSelected?.(selected);\n }\n\n /**\n * Registers a listener for a diagram event.\n *\n * @param event - `\"selection\"` fires when the selected node changes;\n * `\"activate\"` fires when a node is double-clicked; `\"layout\"` fires\n * after each successful ELK layout pass.\n * @param listener - The callback to invoke.\n *\n * @returns This view, for method chaining.\n */\n on(event: \"selection\", listener: (nodes: DiagramNodeData[]) => void): this;\n on(event: \"activate\", listener: (node: DiagramNodeData) => void): this;\n on(event: \"layout\", listener: () => void): this;\n on(event: DiagramViewEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener; the exact reference must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This view, for method chaining.\n */\n off(event: DiagramViewEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event`, in registration order.\n *\n * @param event - The event to emit.\n * @param nodes - The selected node data (for `\"selection\"`), or the\n * activated node data (for `\"activate\"`).\n */\n protected emit(event: \"selection\", nodes: DiagramNodeData[]): void;\n protected emit(event: \"activate\", node: DiagramNodeData): void;\n protected emit(event: \"layout\"): void;\n protected emit(event: DiagramViewEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Wires the DOM-routed pan / zoom / selection listeners once the element\n * exists.\n *\n * @param element - Optional element from the render pipeline.\n *\n * @returns This view, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n Event.addSubtreeListener(this, \"click\", this._handleClick);\n Event.addSubtreeListener(this, \"dblclick\", this._handleDoubleClick);\n Event.addListener(this, \"wheel\", this._handleWheel);\n Event.addListener(this, \"pointerdown\", this._handlePointerDown);\n Event.addListener(this, \"pointermove\", this._handlePointerMove);\n Event.addListener(this, \"pointerup\", this._handlePointerUp);\n\n return this;\n }\n\n /**\n * Resolves the node under a click and updates the selection, emitting\n * `\"selection\"` when it changes. A click on empty space clears the\n * selection.\n *\n * @param event - The click event whose target is inside the view's subtree.\n */\n private _handleClick(event: MouseEvent): void {\n const id = this.nodeIdAt(event.target);\n\n if (id !== null) {\n this.setSelection(id);\n this.emit(\"selection\", this.getSelection());\n } else if (this._selection.length > 0) {\n this.setSelection(null);\n this.emit(\"selection\", this.getSelection());\n }\n }\n\n /**\n * Resolves the node under a double-click and emits `\"activate\"` with its\n * data. A double-click is preceded by the single click that already\n * selected the node, so this only signals activation. A double-click on\n * empty space resolves to no node and emits nothing.\n *\n * @param event - The dblclick event whose target is inside the view's\n * subtree.\n */\n private _handleDoubleClick(event: MouseEvent): void {\n const id = this.nodeIdAt(event.target);\n\n if (id === null) {\n return;\n }\n\n const data = this._nodeData.get(id);\n\n if (data !== undefined) {\n this.emit(\"activate\", data);\n }\n }\n\n /**\n * Resolves the node id whose component owns the given event target.\n *\n * @param target - The raw DOM event target.\n * @returns The owning node id, or `null` when the target is not on a node.\n */\n private nodeIdAt(target: EventTarget | null): string | null {\n if (target === null) {\n return null;\n }\n\n const handle = DOM.source.intern(target);\n\n for (const [id, component] of this._nodeComponents) {\n const element = component.getElement();\n\n if (element && (element === handle || DOM.source.contains(element, handle))) {\n return id;\n }\n }\n\n return null;\n }\n\n /**\n * Wheel-zoom about the pointer: scales toward/away and adjusts the scroll\n * offsets so the graph point under the cursor stays put.\n *\n * @param event - The wheel event.\n */\n private _handleWheel(event: WheelEvent): void {\n event.preventDefault();\n\n const oldZoom = this.getZoom();\n const newZoom = this.clampZoom(oldZoom * (event.deltaY < 0 ? WHEEL_ZOOM_STEP : 1 / WHEEL_ZOOM_STEP));\n\n if (newZoom === oldZoom) {\n return;\n }\n\n const rect = DOM.source.getViewportRect(this);\n const pointerX = event.clientX - rect.left;\n const pointerY = event.clientY - rect.top;\n\n const graphX = (pointerX + this.getScrollLeft()) / oldZoom;\n const graphY = (pointerY + this.getScrollTop()) / oldZoom;\n\n this.setZoom(newZoom);\n\n this.setScrollLeft(graphX * newZoom - pointerX);\n this.setScrollTop(graphY * newZoom - pointerY);\n }\n\n /**\n * Begins a pan drag on a primary-button press, recording the start pointer\n * and scroll offsets.\n *\n * @param event - The pointerdown event.\n */\n private _handlePointerDown(event: PointerEvent): void {\n if (event.button !== 0) {\n return;\n }\n\n this._panning = true;\n this._panStartX = event.clientX;\n this._panStartY = event.clientY;\n this._panScrollLeft = this.getScrollLeft();\n this._panScrollTop = this.getScrollTop();\n }\n\n /**\n * Pans the viewport during a drag by writing the scroll offsets from the\n * pointer delta.\n *\n * @param event - The pointermove event.\n */\n private _handlePointerMove(event: PointerEvent): void {\n if (!this._panning) {\n return;\n }\n\n this.setScrollLeft(this._panScrollLeft - (event.clientX - this._panStartX));\n this.setScrollTop(this._panScrollTop - (event.clientY - this._panStartY));\n }\n\n /**\n * Ends a pan drag.\n */\n private _handlePointerUp(): void {\n this._panning = false;\n }\n}\n\nconst DiagramViewCallable = callable(DiagramView);\ntype DiagramViewCallable = DiagramView;\nexport {\n DiagramView as _DiagramView,\n DiagramViewCallable as DiagramView,\n};\n"],"mappings":"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"}
|