@jimka/typescript-ui 0.6.0 → 0.8.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/dist/lib/AbstractInput-Dt9ooHjo.js +2 -0
- package/dist/lib/AbstractInput-Dt9ooHjo.js.map +1 -0
- package/dist/lib/AccordionConstraints-DahZEfEp.js +2 -0
- package/dist/lib/AccordionConstraints-DahZEfEp.js.map +1 -0
- package/dist/lib/{Anchor-CwRhdP2J.js → Anchor-DdYgyn2V.js} +2 -2
- package/dist/lib/{Anchor-CwRhdP2J.js.map → Anchor-DdYgyn2V.js.map} +1 -1
- package/dist/lib/AnimatedDropdown-_KhHzX5D.js +2 -0
- package/dist/lib/AnimatedDropdown-_KhHzX5D.js.map +1 -0
- package/dist/lib/Border-BELbqMDJ.js +2 -0
- package/dist/lib/Border-BELbqMDJ.js.map +1 -0
- package/dist/lib/Button-CKzjQyqA.js +2 -0
- package/dist/lib/Button-CKzjQyqA.js.map +1 -0
- package/dist/lib/{Card-QYo27r5b.js → Card-CAp7LcB5.js} +2 -2
- package/dist/lib/{Card-QYo27r5b.js.map → Card-CAp7LcB5.js.map} +1 -1
- package/dist/lib/Checkbox-CvAZJ4_T.js +2 -0
- package/dist/lib/Checkbox-CvAZJ4_T.js.map +1 -0
- package/dist/lib/CodeEditor-DoGGdL1Y.js +2 -0
- package/dist/lib/CodeEditor-DoGGdL1Y.js.map +1 -0
- package/dist/lib/ComboBox-DgjMnpdh.js +2 -0
- package/dist/lib/ComboBox-DgjMnpdh.js.map +1 -0
- package/dist/lib/Component-YrLXKJZS.js +2 -0
- package/dist/lib/Component-YrLXKJZS.js.map +1 -0
- package/dist/lib/Container-C5cxnioS.js +2 -0
- package/dist/lib/{Container-abY5Ww3W.js.map → Container-C5cxnioS.js.map} +1 -1
- package/dist/lib/DOM-DDhDN8lQ.js +2 -0
- package/dist/lib/DOM-DDhDN8lQ.js.map +1 -0
- package/dist/lib/Diagnostics-MI6Yw-3E.js +2 -0
- package/dist/lib/Diagnostics-MI6Yw-3E.js.map +1 -0
- package/dist/lib/Dialog-CXQXRQQK.js +2 -0
- package/dist/lib/Dialog-CXQXRQQK.js.map +1 -0
- package/dist/lib/DragManager-D5pBaPPg.js +2 -0
- package/dist/lib/DragManager-D5pBaPPg.js.map +1 -0
- package/dist/lib/{FieldDecorator-BlYMfZ9L.js → FieldDecorator-37IgKHHm.js} +2 -2
- package/dist/lib/{FieldDecorator-BlYMfZ9L.js.map → FieldDecorator-37IgKHHm.js.map} +1 -1
- package/dist/lib/{Fit-BhlB-HAo.js → Fit-amTWlJM2.js} +2 -2
- package/dist/lib/{Fit-BhlB-HAo.js.map → Fit-amTWlJM2.js.map} +1 -1
- package/dist/lib/{FloatingPanel-DkJXsZ2D.js → FloatingPanel-CwsFPYbc.js} +2 -2
- package/dist/lib/{FloatingPanel-DkJXsZ2D.js.map → FloatingPanel-CwsFPYbc.js.map} +1 -1
- package/dist/lib/Glyph-Cof7BxfJ.js +2 -0
- package/dist/lib/Glyph-Cof7BxfJ.js.map +1 -0
- package/dist/lib/{Grid-Daiz7F5d.js → Grid-BQZ4UIUH.js} +2 -2
- package/dist/lib/{Grid-Daiz7F5d.js.map → Grid-BQZ4UIUH.js.map} +1 -1
- package/dist/lib/GridConstraints-BkXa62z0.js +2 -0
- package/dist/lib/GridConstraints-BkXa62z0.js.map +1 -0
- package/dist/lib/{Header-Dql3Zxc6.js → Header-Cj33ABP1.js} +2 -2
- package/dist/lib/{Header-Dql3Zxc6.js.map → Header-Cj33ABP1.js.map} +1 -1
- package/dist/lib/{IconText-PUjhTLlp.js → IconText-Dp64iq_q.js} +2 -2
- package/dist/lib/{IconText-PUjhTLlp.js.map → IconText-Dp64iq_q.js.map} +1 -1
- package/dist/lib/LabeledGrid-Dpelrc1k.js +2 -0
- package/dist/lib/LabeledGrid-Dpelrc1k.js.map +1 -0
- package/dist/lib/LayoutSerialization-DgLJSuew.js +2 -0
- package/dist/lib/LayoutSerialization-DgLJSuew.js.map +1 -0
- package/dist/lib/Link-DE8tE3u3.js +2 -0
- package/dist/lib/Link-DE8tE3u3.js.map +1 -0
- package/dist/lib/List-TfgHScl3.js +2 -0
- package/dist/lib/List-TfgHScl3.js.map +1 -0
- package/dist/lib/ListenerBag-DYRl4TSb.js +2 -0
- package/dist/lib/ListenerBag-DYRl4TSb.js.map +1 -0
- package/dist/lib/{MemoryStore-DhPoU-7X.js → MemoryStore-ByGDEKYI.js} +2 -2
- package/dist/lib/{MemoryStore-DhPoU-7X.js.map → MemoryStore-ByGDEKYI.js.map} +1 -1
- package/dist/lib/Menu-BCQISKiQ.js +2 -0
- package/dist/lib/Menu-BCQISKiQ.js.map +1 -0
- package/dist/lib/{MenuButton-DCXi0tsP.js → MenuButton-BO4y3jf2.js} +2 -2
- package/dist/lib/{MenuButton-DCXi0tsP.js.map → MenuButton-BO4y3jf2.js.map} +1 -1
- package/dist/lib/{OverlayFade-3pof2AJH.js → OverlayFade-C1yQKffL.js} +2 -2
- package/dist/lib/{OverlayFade-3pof2AJH.js.map → OverlayFade-C1yQKffL.js.map} +1 -1
- package/dist/lib/{Panel-Cy1pdlWO.js → Panel-DlsVVuvn.js} +2 -2
- package/dist/lib/{Panel-Cy1pdlWO.js.map → Panel-DlsVVuvn.js.map} +1 -1
- package/dist/lib/{Position-BwtxouxK.js → Position-DWR1nIpA.js} +2 -2
- package/dist/lib/Position-DWR1nIpA.js.map +1 -0
- package/dist/lib/ProgressSpinner-BI9tFIVm.js +2 -0
- package/dist/lib/ProgressSpinner-BI9tFIVm.js.map +1 -0
- package/dist/lib/RadioButton-BxcuFChp.js +2 -0
- package/dist/lib/RadioButton-BxcuFChp.js.map +1 -0
- package/dist/lib/RadioMenuRow-B2bthoxj.js +2 -0
- package/dist/lib/RadioMenuRow-B2bthoxj.js.map +1 -0
- package/dist/lib/{RovingTabIndex-pHEv5oLD.js → RovingTabIndex-BttlsARv.js} +2 -2
- package/dist/lib/{RovingTabIndex-pHEv5oLD.js.map → RovingTabIndex-BttlsARv.js.map} +1 -1
- package/dist/lib/Scrollbar-orAWHWap.js +2 -0
- package/dist/lib/Scrollbar-orAWHWap.js.map +1 -0
- package/dist/lib/SelectableText-BbS_lRzY.js +2 -0
- package/dist/lib/SelectableText-BbS_lRzY.js.map +1 -0
- package/dist/lib/Slider-BvXc-cx5.js +2 -0
- package/dist/lib/Slider-BvXc-cx5.js.map +1 -0
- package/dist/lib/Spacer-C6faKe1V.js +2 -0
- package/dist/lib/Spacer-C6faKe1V.js.map +1 -0
- package/dist/lib/StyleTraits-CAtzeyRR.js +2 -0
- package/dist/lib/StyleTraits-CAtzeyRR.js.map +1 -0
- package/dist/lib/TabButton-BQDaBszs.js +2 -0
- package/dist/lib/TabButton-BQDaBszs.js.map +1 -0
- package/dist/lib/TabCloseButton-B-iLVPGZ.js +2 -0
- package/dist/lib/TabCloseButton-B-iLVPGZ.js.map +1 -0
- package/dist/lib/Table-3McEw5gr.js +7 -0
- package/dist/lib/Table-3McEw5gr.js.map +1 -0
- package/dist/lib/Text-CGFXPhHD.js +2 -0
- package/dist/lib/Text-CGFXPhHD.js.map +1 -0
- package/dist/lib/Tooltip-DgRHQZhL.js +3 -0
- package/dist/lib/Tooltip-DgRHQZhL.js.map +1 -0
- package/dist/lib/Tree-Bq2pjjAz.js +2 -0
- package/dist/lib/Tree-Bq2pjjAz.js.map +1 -0
- package/dist/lib/{VBox-DfJPWXqc.js → VBox-hFIZAEpT.js} +2 -2
- package/dist/lib/{VBox-DfJPWXqc.js.map → VBox-hFIZAEpT.js.map} +1 -1
- package/dist/lib/{VirtualScroller-DTIn19o1.js → VirtualScroller-Csa3GBSM.js} +2 -2
- package/dist/lib/{VirtualScroller-DTIn19o1.js.map → VirtualScroller-Csa3GBSM.js.map} +1 -1
- package/dist/lib/Window-CQ14_65z.js +2 -0
- package/dist/lib/Window-CQ14_65z.js.map +1 -0
- 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 +2 -2
- 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 +1 -6
- 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-DpzpKLWE.js +2 -0
- package/dist/lib/core-DpzpKLWE.js.map +1 -0
- package/dist/lib/core.es.js +1 -2
- package/dist/lib/data.es.js +1 -1
- package/dist/lib/diagnostics.es.js +24 -0
- package/dist/lib/diagnostics.es.js.map +1 -0
- package/dist/lib/glyphs/solid/index.es.js +1 -1
- package/dist/lib/layout.es.js +1 -1
- package/dist/lib/layout.es.js.map +1 -1
- package/dist/lib/overlay.es.js +1 -1
- package/dist/lib/overlay.es.js.map +1 -1
- package/dist/lib/primitive-8YvORgGS.js +2 -0
- package/dist/lib/primitive-8YvORgGS.js.map +1 -0
- package/dist/lib/primitive.es.js +1 -2
- package/dist/lib/router.es.js +1 -1
- package/dist/lib/selectionsEqual-DWdW5pxP.js +2 -0
- package/dist/lib/{selectionsEqual-gMG7gCVI.js.map → selectionsEqual-DWdW5pxP.js.map} +1 -1
- package/dist/lib/types/component/button/Button.d.ts +7 -6
- package/dist/lib/types/component/button/TabButton.d.ts +3 -0
- package/dist/lib/types/component/button/TabCloseButton.d.ts +3 -0
- package/dist/lib/types/component/button/ToggleButton.d.ts +2 -2
- package/dist/lib/types/component/chart/AbstractChart.d.ts +2 -0
- package/dist/lib/types/component/container/AccordionHeader.d.ts +6 -1
- package/dist/lib/types/component/container/AccordionIndicator.d.ts +2 -0
- package/dist/lib/types/component/container/FieldSet.d.ts +2 -0
- package/dist/lib/types/component/container/LabeledFieldSet.d.ts +1 -1
- package/dist/lib/types/component/container/LabeledGrid.d.ts +4 -1
- package/dist/lib/types/component/container/Legend.d.ts +2 -2
- package/dist/lib/types/component/container/MenuItem.d.ts +1 -0
- package/dist/lib/types/component/container/WindowBorder.d.ts +2 -2
- package/dist/lib/types/component/container/WindowHeader.d.ts +6 -0
- package/dist/lib/types/component/diagram/DiagramEdgeLayer.d.ts +10 -0
- package/dist/lib/types/component/diagram/DiagramGroupNode.d.ts +2 -0
- package/dist/lib/types/component/diagram/DiagramNode.d.ts +6 -2
- package/dist/lib/types/component/diagram/DiagramNodeLayer.d.ts +25 -0
- package/dist/lib/types/component/diagram/DiagramResidency.d.ts +11 -0
- package/dist/lib/types/component/diagram/DiagramView.d.ts +22 -0
- package/dist/lib/types/component/display/Markdown.d.ts +2 -1
- package/dist/lib/types/component/input/AbstractCalendarDropdown.d.ts +4 -2
- package/dist/lib/types/component/input/AbstractInput.d.ts +1 -0
- package/dist/lib/types/component/input/AbstractPickerField.d.ts +4 -0
- package/dist/lib/types/component/input/AutoCompleteDropdown.d.ts +2 -0
- package/dist/lib/types/component/input/AutoCompleteField.d.ts +1 -0
- package/dist/lib/types/component/input/ComboBox.d.ts +6 -1
- package/dist/lib/types/component/input/Link.d.ts +2 -0
- package/dist/lib/types/component/input/PasswordField.d.ts +2 -0
- package/dist/lib/types/component/input/PickerButton.d.ts +5 -2
- package/dist/lib/types/component/input/PickerInput.d.ts +2 -0
- package/dist/lib/types/component/input/SelectableText.d.ts +11 -0
- package/dist/lib/types/component/input/SpinButton.d.ts +3 -0
- package/dist/lib/types/component/input/Text.d.ts +12 -2
- package/dist/lib/types/component/input/TextArea.d.ts +2 -0
- package/dist/lib/types/component/input/TextField.d.ts +2 -0
- package/dist/lib/types/component/input/TextInput.d.ts +4 -0
- package/dist/lib/types/component/input/TimePickerDropdown.d.ts +3 -1
- package/dist/lib/types/component/input/UsernameField.d.ts +2 -0
- package/dist/lib/types/component/input/index.d.ts +2 -0
- package/dist/lib/types/component/list/AbstractMarkerList.d.ts +2 -0
- package/dist/lib/types/component/list/AbstractSelectableList.d.ts +6 -2
- package/dist/lib/types/component/list/ListItem.d.ts +2 -1
- package/dist/lib/types/component/menubar/MenuBarButton.d.ts +3 -0
- package/dist/lib/types/component/menubar/ToolBar.d.ts +2 -0
- package/dist/lib/types/component/table/Body.d.ts +54 -20
- package/dist/lib/types/component/table/Column.d.ts +2 -0
- package/dist/lib/types/component/table/ColumnConfig.d.ts +1 -0
- package/dist/lib/types/component/table/Footer.d.ts +2 -0
- package/dist/lib/types/component/table/Header.d.ts +8 -1
- package/dist/lib/types/component/table/Row.d.ts +22 -2
- package/dist/lib/types/component/table/Table.d.ts +6 -1
- package/dist/lib/types/component/table/TableExporter.d.ts +3 -0
- package/dist/lib/types/component/table/TablePanel.d.ts +6 -0
- package/dist/lib/types/component/table/TreeTablePanel.d.ts +6 -0
- package/dist/lib/types/component/table/cell/Cell.d.ts +8 -2
- package/dist/lib/types/component/table/cell/Default.d.ts +1 -1
- package/dist/lib/types/component/table/cell/Header.d.ts +4 -0
- package/dist/lib/types/component/table/cell/ParentHeader.d.ts +4 -0
- package/dist/lib/types/component/table/cell/ResizeHandle.d.ts +1 -1
- package/dist/lib/types/component/table/cell/renderer/CellRenderer.d.ts +2 -2
- package/dist/lib/types/component/table/cell/renderer/String.d.ts +3 -1
- package/dist/lib/types/component/table/index.d.ts +1 -1
- package/dist/lib/types/component/tree/TreeRow.d.ts +2 -0
- package/dist/lib/types/core/AnimatedDropdown.d.ts +2 -0
- package/dist/lib/types/core/Binding.d.ts +1 -0
- package/dist/lib/types/core/ClassStyleRules.d.ts +69 -4
- package/dist/lib/types/core/Component.d.ts +69 -14
- package/dist/lib/types/core/DOM.d.ts +11 -1
- package/dist/lib/types/core/Diagnostics.d.ts +22 -0
- package/dist/lib/types/core/Event.d.ts +7 -0
- package/dist/lib/types/core/LayerManager.d.ts +3 -0
- package/dist/lib/types/core/ListenerBag.d.ts +1 -0
- package/dist/lib/types/core/StyleTarget.d.ts +14 -0
- package/dist/lib/types/core/StyleTraits.d.ts +4 -0
- package/dist/lib/types/core/Theme.d.ts +6 -2
- package/dist/lib/types/core/Util.d.ts +4 -0
- package/dist/lib/types/core/index.d.ts +6 -3
- package/dist/lib/types/diagnostics/DiagnosticsOverlay.d.ts +29 -0
- package/dist/lib/types/diagnostics/DiagnosticsSampler.d.ts +61 -0
- package/dist/lib/types/diagnostics/StyleAudit.d.ts +19 -0
- package/dist/lib/types/diagnostics/StyleAuditOverlay.d.ts +12 -0
- package/dist/lib/types/diagnostics/StyleAuditView.d.ts +13 -0
- package/dist/lib/types/diagnostics/index.d.ts +7 -0
- package/dist/lib/types/layout/LayoutManager.d.ts +3 -0
- package/dist/lib/types/layout/Table.d.ts +3 -0
- package/dist/lib/types/overlay/AbstractWindow.d.ts +36 -0
- package/dist/lib/types/overlay/ButtonGroup.d.ts +1 -0
- package/dist/lib/types/overlay/Dialog.d.ts +1 -0
- package/dist/lib/types/overlay/Menu.d.ts +2 -0
- package/dist/lib/types/overlay/Notification.d.ts +0 -1
- package/dist/lib/types/overlay/PopupPanel.d.ts +2 -0
- package/dist/lib/types/overlay/RailHandle.d.ts +4 -5
- package/dist/lib/types/overlay/TabWindow.d.ts +3 -0
- package/dist/lib/types/overlay/Tooltip.d.ts +1 -0
- package/dist/lib/types/overlay/Window.d.ts +4 -1
- package/dist/lib/validation.es.js +1 -1
- package/llms.txt +2 -0
- package/package.json +5 -1
- package/dist/lib/AbstractInput-sRNPzWel.js +0 -2
- package/dist/lib/AbstractInput-sRNPzWel.js.map +0 -1
- package/dist/lib/AnimatedDropdown-H3Rh-YMZ.js +0 -2
- package/dist/lib/AnimatedDropdown-H3Rh-YMZ.js.map +0 -1
- package/dist/lib/Border-0DJFDiij.js +0 -2
- package/dist/lib/Border-0DJFDiij.js.map +0 -1
- package/dist/lib/Button-MpMIYBYJ.js +0 -2
- package/dist/lib/Button-MpMIYBYJ.js.map +0 -1
- package/dist/lib/Checkbox-D0JYK-wc.js +0 -2
- package/dist/lib/Checkbox-D0JYK-wc.js.map +0 -1
- package/dist/lib/CheckboxMenuRow-CesLhSnp.js +0 -2
- package/dist/lib/CheckboxMenuRow-CesLhSnp.js.map +0 -1
- package/dist/lib/CodeEditor-N1FQodcF.js +0 -2
- package/dist/lib/CodeEditor-N1FQodcF.js.map +0 -1
- package/dist/lib/ComboBox-Dx8Tk9yg.js +0 -2
- package/dist/lib/ComboBox-Dx8Tk9yg.js.map +0 -1
- package/dist/lib/Component-B0ShfPVi.js +0 -2
- package/dist/lib/Component-B0ShfPVi.js.map +0 -1
- package/dist/lib/Container-abY5Ww3W.js +0 -2
- package/dist/lib/DOM-C-Edb3tA.js +0 -2
- package/dist/lib/DOM-C-Edb3tA.js.map +0 -1
- package/dist/lib/Dialog-cbA9V17e.js +0 -2
- package/dist/lib/Dialog-cbA9V17e.js.map +0 -1
- package/dist/lib/Glyph-_VeBacS2.js +0 -2
- package/dist/lib/Glyph-_VeBacS2.js.map +0 -1
- package/dist/lib/GridConstraints-C58LuTqM.js +0 -2
- package/dist/lib/GridConstraints-C58LuTqM.js.map +0 -1
- package/dist/lib/LayoutSerialization-CMxunLfV.js +0 -2
- package/dist/lib/LayoutSerialization-CMxunLfV.js.map +0 -1
- package/dist/lib/List-nx8220Fi.js +0 -2
- package/dist/lib/List-nx8220Fi.js.map +0 -1
- package/dist/lib/ListenerBag-WPdKuuds.js +0 -2
- package/dist/lib/ListenerBag-WPdKuuds.js.map +0 -1
- package/dist/lib/Menu-Q0bpXXze.js +0 -2
- package/dist/lib/Menu-Q0bpXXze.js.map +0 -1
- package/dist/lib/Position-BwtxouxK.js.map +0 -1
- package/dist/lib/ProgressSpinner-DK8xEQ9y.js +0 -2
- package/dist/lib/ProgressSpinner-DK8xEQ9y.js.map +0 -1
- package/dist/lib/RadioButton-BEwpqg1k.js +0 -2
- package/dist/lib/RadioButton-BEwpqg1k.js.map +0 -1
- package/dist/lib/RadioMenuRow-DP5qPyFR.js +0 -2
- package/dist/lib/RadioMenuRow-DP5qPyFR.js.map +0 -1
- package/dist/lib/Scrollbar-t8fFrUVQ.js +0 -2
- package/dist/lib/Scrollbar-t8fFrUVQ.js.map +0 -1
- package/dist/lib/Slider-CTbwR6HR.js +0 -2
- package/dist/lib/Slider-CTbwR6HR.js.map +0 -1
- package/dist/lib/Spacer-srdN2d2b.js +0 -2
- package/dist/lib/Spacer-srdN2d2b.js.map +0 -1
- package/dist/lib/TabButton-DLoMQzky.js +0 -2
- package/dist/lib/TabButton-DLoMQzky.js.map +0 -1
- package/dist/lib/TabCloseButton-CY7BGitY.js +0 -2
- package/dist/lib/TabCloseButton-CY7BGitY.js.map +0 -1
- package/dist/lib/Text-CMKnK9uB.js +0 -2
- package/dist/lib/Text-CMKnK9uB.js.map +0 -1
- package/dist/lib/Tooltip-CUOJOqQi.js +0 -3
- package/dist/lib/Tooltip-CUOJOqQi.js.map +0 -1
- package/dist/lib/Tree-C64JNPWJ.js +0 -2
- package/dist/lib/Tree-C64JNPWJ.js.map +0 -1
- package/dist/lib/core.es.js.map +0 -1
- package/dist/lib/primitive.es.js.map +0 -1
- package/dist/lib/selectionsEqual-gMG7gCVI.js +0 -2
- package/dist/lib/types/component/table/CellGeometry.d.ts +0 -6
|
@@ -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 terminateWorker(): void;\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/** Message carried by the rejection a disposed engine's `layout` produces. */\nconst DISPOSED_MESSAGE = \"ElkLayoutEngine has been disposed\";\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/** Construction options for {@link ElkLayoutEngine}. */\nexport interface ElkLayoutEngineOptions {\n /**\n * Consumer-provided factory returning a Web Worker for off-thread ELK\n * layout. Takes precedence over {@link ElkLayoutEngineOptions.workerUrl}\n * when both are set.\n */\n workerFactory?: () => Worker;\n /**\n * URL of a consumer-hosted `elk-worker.js`, requesting off-thread layout.\n * With the `elk.bundled.js` module this engine imports, elkjs's own\n * worker-availability check (`require.resolve('web-worker')`) always\n * fails — the browserify module system that check runs against has no\n * `.resolve` — so `workerUrl` alone never actually constructs a Worker\n * here; elkjs logs its own console warning and runs on the main thread via\n * its in-process fallback. Pass\n * {@link ElkLayoutEngineOptions.workerFactory} for real off-thread\n * execution; `workerUrl` is kept only for parity with elkjs's own API.\n */\n workerUrl?: string;\n}\n\n/**\n * Lazily-loaded ELK layout adapter. One instance owns one ELK engine, built on\n * the first {@link ElkLayoutEngine.layout} call and reused afterward. With\n * neither `workerFactory` nor `workerUrl` set, ELK runs on the main thread.\n * Setting `workerFactory` runs layout in the Worker it returns. Setting\n * `workerUrl` does not: see {@link ElkLayoutEngineOptions.workerUrl} — it\n * always ends up on the main thread with this engine's `elk.bundled.js`\n * import. Either mode falls back transparently to the main thread — within\n * the same failing `layout` call, which rebuilds the engine and retries once\n * — the first time its engine fails to construct or a layout rejects; that\n * fallback is permanent for the instance's lifetime, so later layouts run on\n * the main thread even if the worker would have recovered.\n *\n * In `workerFactory` mode the engine owns a real Web Worker for as long as it\n * lives. Its owner is expected to call {@link ElkLayoutEngine.dispose} when\n * discarding it, or that worker thread outlives the engine.\n *\n * @category Components\n */\nexport class ElkLayoutEngine {\n\n private _elk: ElkInstance | null = null;\n private readonly _workerFactory?: () => Worker;\n private readonly _workerUrl?: string;\n /**\n * True while `_elk` is worker-backed. Set before constructing a\n * worker-backed engine and never cleared on a successful layout, so the\n * *next* layout failure — whichever one it is — still triggers the\n * one-time main-thread rebuild in {@link layout}; after that rebuild it is\n * `false` for good and a further failure propagates.\n */\n private _workerBacked = false;\n /** True once `dispose` has run. A disposed engine never builds another ELK. */\n private _disposed = false;\n /**\n * True while the ELK instance this engine holds — or is in the middle of\n * building — drives a real Web Worker, which is the consumer-factory mode\n * and only that mode. Cleared when the main-thread fallback replaces the\n * instance. Deliberately NOT cleared by `dispose`: a construction still in\n * flight sets this just before it builds, and the adopt-time check needs\n * it afterward to terminate an instance `dispose` could not yet see.\n */\n private _ownsWorker = false;\n /**\n * The in-flight construction, shared by every `layout` that arrives while\n * ELK is still being imported and built. Without it each of those builds\n * its own ELK — and its own Worker — because `_elk` is still `null` for\n * all of them, and `dispose` can only terminate the one instance the\n * engine ends up keeping. Cleared whenever what it memoised stops being\n * this engine's instance: a failed construction (so a later `layout`\n * retries the import instead of replaying the failure), the main-thread\n * fallback replacing the instance, and `dispose`. Keeping those in step\n * is what stops the memo retaining an ELK — and its `Worker` — the engine\n * has already abandoned, so any future code that assigns or nulls `_elk`\n * must clear this too.\n */\n private _building: Promise<ElkInstance> | null = null;\n\n /**\n * @param options - Optional worker configuration. With neither field set,\n * ELK runs on the main thread via the zero-config bundle.\n */\n constructor(options?: ElkLayoutEngineOptions) {\n this._workerFactory = options?.workerFactory;\n this._workerUrl = options?.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. The first time a worker-backed engine\n * fails to construct or a layout rejects, rebuilds on the main thread and\n * retries once, so a worker problem never surfaces as a diagram error;\n * that switch to the main thread is permanent for this instance's\n * lifetime, even if a later failure is unrelated to the worker itself.\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, if a\n * main-thread layout fails (no worker fallback remains at that point),\n * or if the engine was already disposed when the call started or is\n * disposed while it is building or rebuilding its ELK. Disposal landing\n * during the layout compute itself does not reject — that call can still\n * resolve, which is why `DiagramView` drops late results by generation\n * rather than relying on this.\n */\n async layout(\n data: DiagramData,\n sizes: Map<string, { width: number; height: number }>,\n defaults?: Record<string, string>,\n ): Promise<DiagramLayoutResult> {\n if (this._disposed) {\n throw new Error(DISPOSED_MESSAGE);\n }\n\n const graph = buildElkGraph(data, sizes, defaults);\n\n try {\n const elk = await this.ensureElk();\n\n return mapElkResult(await elk.layout(graph) as ElkNode);\n } catch (error) {\n // Disposed mid-flight: the engine is gone for good, so there is\n // nothing to fall back onto. Main-thread failure: elkjs itself is\n // absent/broken. Either way, propagate.\n if (this._disposed || !this._workerBacked) {\n throw error;\n }\n\n // Worker construction failed (factory threw, `Worker` undefined,\n // CSP block) or its first compute failed. Terminate the worker\n // being abandoned before the reference is dropped, then rebuild on\n // the main thread and retry once so the diagram still renders.\n this.terminateOwnedWorker(this._elk);\n this._workerBacked = false;\n this._ownsWorker = false;\n\n const rebuilt = await this.createMainThreadElk();\n\n // `dispose` can land while that rebuild is awaited. Adopting the\n // instance then would leave a disposed engine holding a live ELK\n // and laying out through it, so drop it. Unlike `ensureElk`'s\n // equivalent check this does NOT terminate what it drops: the\n // rebuild is main-thread, and terminating a non-factory instance\n // throws a `TypeError` (see `terminateOwnedWorker`).\n if (this._disposed) {\n throw new Error(DISPOSED_MESSAGE);\n }\n\n this._elk = rebuilt;\n // The memo still resolves to the worker-backed instance just\n // abandoned; drop it so it does not outlive what it built.\n this._building = null;\n\n return mapElkResult(await rebuilt.layout(graph) as ElkNode);\n }\n }\n\n /**\n * Terminates this engine's ELK Web Worker, if it owns one, and drops the\n * ELK instance. A later {@link ElkLayoutEngine.layout} rejects rather than\n * rebuilding, so a disposed engine never resurrects a worker. Idempotent,\n * and a no-op on an engine that never laid out or never ran off-thread.\n *\n * A layout request outstanding in a real worker at this moment never\n * settles — elkjs does not reject pending requests on termination — so a\n * caller awaiting one must not depend on it resolving or rejecting.\n */\n dispose(): void {\n this._disposed = true;\n\n this.terminateOwnedWorker(this._elk);\n\n this._elk = null;\n // Dropped too, or the engine keeps the terminated instance reachable\n // through the memoised construction for as long as the engine lives.\n this._building = null;\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 * @throws Error - If the engine was disposed while ELK was still being\n * imported and constructed.\n */\n private async ensureElk(): Promise<ElkInstance> {\n if (this._elk) {\n return this._elk;\n }\n\n // Memoised so overlapping layouts share one construction rather than\n // each building an ELK of its own — see `_building`. The adopt-or-\n // terminate decision therefore runs exactly once however many callers\n // are waiting, and they all settle on its outcome.\n this._building ??= this.createElk()\n .then((elk) => {\n if (this._disposed) {\n // `dispose` ran while ELK was still being imported and\n // constructed, so it saw no instance to terminate.\n // Terminate this one rather than adopting it, or the\n // worker outlives the disposed engine.\n this.terminateOwnedWorker(elk);\n\n throw new Error(DISPOSED_MESSAGE);\n }\n\n this._elk = elk;\n\n return elk;\n })\n .catch((error: unknown) => {\n this._building = null;\n\n throw error;\n });\n\n return this._building;\n }\n\n /**\n * Terminates `elk`'s Web Worker, but only when this engine built it from a\n * consumer factory. In every other mode elkjs drives an in-process stand-in\n * worker with no `terminate` method, and asking elkjs to terminate that\n * throws a `TypeError` — so the guard is load-bearing, not defensive.\n *\n * @param elk - The instance to terminate, or `null` if none was built.\n */\n private terminateOwnedWorker(elk: ElkInstance | null): void {\n if (elk && this._ownsWorker) {\n elk.terminateWorker();\n }\n }\n\n /**\n * Builds the ELK engine per the configured mode: a consumer worker\n * factory, a consumer-hosted worker URL, or the main-thread default —\n * checked in that precedence order. `_workerBacked` is set *before*\n * constructing a worker-backed engine, so a synchronous construction\n * throw is still caught by {@link layout}'s retry.\n *\n * @returns The constructed ELK engine.\n */\n private async createElk(): Promise<ElkInstance> {\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 if (this._workerFactory) {\n this._workerBacked = true;\n this._ownsWorker = true;\n\n return new ELK({ workerFactory: this._workerFactory });\n }\n\n if (this._workerUrl) {\n this._workerBacked = true;\n\n return new ELK({ workerUrl: this._workerUrl });\n }\n\n return new ELK();\n }\n\n /**\n * Builds a plain main-thread ELK engine, used both as the default mode\n * and as the fallback after a worker-backed engine fails.\n *\n * @returns The constructed main-thread ELK engine.\n */\n private async createMainThreadElk(): Promise<ElkInstance> {\n const { default: ELK } = await import(\"elkjs/lib/elk.bundled.js\");\n\n return new 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 { Component } from \"~/core/Component.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 { HBox } from \"~/layout/HBox.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 /** Short marker text drawn after the label, in the same row. */\n badge?: 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 backgroundColor: \"var(--ts-ui-diagram-node-bg, var(--ts-ui-button-bg, rgb(245, 245, 245)))\",\n border: \"1px solid var(--ts-ui-border-color, rgb(180, 180, 180))\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n};\n\n// The badge's opacity: present but secondary to the label it trails. Matches\n// the \"dim the supporting value\" weight the framework already uses for a\n// receded label, so the badge reads as an annotation rather than a second\n// name.\nconst BADGE_OPACITY = 0.6;\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 glyph+label (or bare label) component. */\n private _label!: IconText | Text;\n\n /** The child added to the node: `_label` alone, or a row of `_label` + `_badge`. */\n private _content!: Component;\n\n /** The trailing badge chip, when the node carries one. */\n private _badge?: 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 /**\n * @param options - Optional construction-time options.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults; subclasses forward their `_defaultXxxOptions`\n * constant here.\n */\n constructor(options?: DiagramNodeOptions, subclassDefaults?: Partial<DiagramNodeOptions>) {\n super(options, {\n ..._defaultDiagramNodeOptions,\n layoutManager: new Fit(),\n ...(subclassDefaults ?? {}),\n });\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/badge/selected values cached pure in `applyOptions` are\n // dispatched now that the row exists.\n this.buildContent(this._options.glyph, this._options.label ?? \"\", this._options.badge);\n\n if (this._options.selected !== undefined) {\n this.setSelected(this._options.selected);\n }\n }\n\n /**\n * Caches the label/glyph/badge/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.badge !== undefined) this._options.badge = options.badge;\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: `_label` alone (an\n * `IconText` when a glyph is present, else a bare `Text`), or — when a\n * badge is given — a row of `_label` followed by the badge `Text`.\n *\n * @param glyph - Optional glyph name.\n * @param label - The label text.\n * @param badge - Optional trailing badge text.\n */\n private buildContent(glyph: string | undefined, label: string, badge: string | undefined): void {\n if (this._content) {\n this.removeComponent(this._content);\n }\n\n this._label = glyph !== undefined ? new IconText(glyph, label) : new Text(label);\n\n if (badge === undefined) {\n this._badge = undefined;\n this._content = this._label;\n } else {\n this._badge = new Text(badge);\n this._badge.setOpacity(BADGE_OPACITY);\n this._content = new Component({ layoutManager: new HBox(), components: [this._label, this._badge] });\n }\n\n // Every Component stamps its own `cursor` (defaulting to `default`) onto\n // its CSS rule, so the label/glyph/badge would override the node's\n // `pointer` wherever they sit under the cursor — the hover cursor would\n // flicker between pointer (over padding) and arrow (over the text).\n // Make the content transparent to pointer events so hover + clicks land\n // on the node itself; `pointer-events: none` inherits, so this one call\n // also covers everything nested inside (the glyph and text inside an\n // `IconText`, or the label + badge inside the row). Mirrors how `Button`\n // 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._label.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 * Returns the node's badge text, or `null` when none was set.\n *\n * @returns The badge text, or `null`.\n */\n getBadge(): string | null {\n return this._options.badge ?? 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/**\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\nconst _defaultDiagramGroupNodeOptions: Partial<DiagramGroupNodeOptions> = {\n backgroundColor: \"var(--ts-ui-diagram-group-bg, rgba(120, 120, 120, 0.08))\",\n // A container is a selectable node like any leaf, so it carries the same\n // pointer cursor `DiagramNode` does — left at the Component default it\n // would read as an arrow and promise a pan its own box does not perform.\n cursor: \"pointer\",\n border: \"1px solid var(--ts-ui-diagram-group-border, var(--ts-ui-border-color, rgb(180, 180, 180)))\",\n borderRadius: \"4px\",\n};\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, subclassDefaults?: Partial<DiagramGroupNodeOptions>) {\n super(options, {\n layoutManager: new Absolute(),\n ..._defaultDiagramGroupNodeOptions,\n ...(subclassDefaults ?? {}),\n });\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's root stays\n// non-interactive (`pointer-events: none`) so node clicks fall through to the\n// node components; each drawn edge additionally gets an invisible wide hit\n// path that opts itself back into pointer events, so hovering/pressing an edge\n// is possible without making the layer as a whole interactive.\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 * Stroke width (px, unscaled graph units) of an edge's invisible hit path —\n * ±6px either side of the 1.5px visible hairline. A hairline is far too thin\n * to aim at, and this is deliberately wider than ELK's default 10px\n * `elk.spacing.edgeEdge`, so a bundle of parallel routes reports as a bundle\n * rather than forcing the user to land on exactly one of them.\n */\nconst EDGE_HIT_WIDTH = 12;\n\n/** Half the hit width: the distance from a route within which it answers `edgesNear`. */\nconst EDGE_HIT_TOLERANCE = EDGE_HIT_WIDTH / 2;\n\n/**\n * Opacity of the group holding every edge outside a non-empty emphasis set. A\n * 1.5px hairline has almost no area, so it still reads as a full line at\n * ChartLegend's `HIDDEN_OPACITY` (`0.4`), a strength tuned for a filled legend\n * swatch — dimmed enough there is not dimmed enough for a stroke. `0.15` over\n * the default light canvas resolves to a pale, still-traceable grey while\n * leaving the emphasised edges clearly what the eye lands on.\n *\n * Carried by the dimmed *group* rather than each dimmed edge, because routes\n * overlap by design: fan-in and fan-out bundles share a junction stub, so two\n * or more dimmed paths coincide there. Per-element alpha composites at each\n * overlap — two paths at `0.15` resolve to `0.28`, three to `0.39` — so a\n * bundle read as emphasised precisely where it was densest. Group opacity\n * composites the group's whole rendering once, so an overlap looks the same as\n * a single line.\n */\nconst DIMMED_EDGE_OPACITY = \"0.15\";\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 * How far, in unscaled graph units, the longest end marker reaches back along\n * an edge from the point it attaches to. Every marker anchors its vertex at\n * `x = width` (via `refX`) and opens toward `x = 0`, so a marker's `width` *is*\n * its reach, and this is the widest of them all.\n *\n * Exported because a consumer that rewrites edge routes needs it: anything\n * placed on the route within this distance of an endpoint lands underneath the\n * marker glyph rather than beside it. SQLAdmin's junction stubs use it as the\n * floor for how far from a node a bundle may branch.\n *\n * @category Components\n */\nexport const EDGE_MARKER_EXTENT: number =\n Math.max(ARROW_SIZE, ...Object.values(MARKER_GEOMETRY).map(geometry => geometry.width));\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 shortest distance from point `(px, py)` to the line segment `(ax, ay)`–\n * `(bx, by)`, via the standard clamped projection onto the segment.\n *\n * @param px - Point x.\n * @param py - Point y.\n * @param ax - Segment start x.\n * @param ay - Segment start y.\n * @param bx - Segment end x.\n * @param by - Segment end y.\n * @returns The shortest distance from the point to the segment.\n */\nfunction distanceToSegment(px: number, py: number, ax: number, ay: number, bx: number, by: number): number {\n const dx = bx - ax;\n const dy = by - ay;\n const lengthSquared = dx * dx + dy * dy;\n\n if (lengthSquared === 0) {\n return Math.hypot(px - ax, py - ay);\n }\n\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lengthSquared));\n\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n}\n\n/**\n * The shortest distance from point `(x, y)` to a routed edge's polyline —\n * the minimum over every segment of every section, walking the same\n * start/bend/end points {@link labelPoint} does.\n *\n * @param sections - The edge's routed sections.\n * @param x - Point x in unscaled graph coordinates.\n * @param y - Point y in unscaled graph coordinates.\n * @returns The shortest distance to the route, or `Infinity` when it has no drawable segments.\n */\nfunction distanceToRoute(sections: ElkEdgeSection[], x: number, y: number): number {\n let min = Infinity;\n\n for (const section of sections) {\n const points = [section.startPoint, ...(section.bendPoints ?? []), section.endPoint];\n\n for (let i = 1; i < points.length; i++) {\n min = Math.min(min, distanceToSegment(x, y, points[i - 1].x, points[i - 1].y, points[i].x, points[i].y));\n }\n }\n\n return min;\n}\n\n/** The elements drawn for one edge, so it can be hit-tested and released. */\ninterface DrawnEdge {\n id: string;\n route: DiagramEdgeRoute;\n /** The visible stroked path. */\n path: Handle;\n /** The invisible wide path that takes pointer events. */\n hit: Handle;\n /** The mid-route label, when the edge carries one. */\n label: Handle | null;\n /** The group the three elements above were appended into, so they can be removed from it. */\n group: Handle;\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 /** Everything currently drawn, released and rebuilt by `rebuildPaths`. */\n private _drawn: DrawnEdge[] = [];\n\n /**\n * The currently emphasised edge ids. Runtime interaction state (not a\n * `ComponentOptions` field — see ARCHITECTURE.md's rule that transient,\n * framework-managed state stays off the options bag). Cleared by `setEdges`.\n */\n private _edgeEmphasis: Set<string> = new Set();\n\n /**\n * The `<g>` holding every edge outside a non-empty emphasis set, carrying\n * {@link DIMMED_EDGE_OPACITY}. Painted before `_normalLayer`, so an\n * emphasised edge always draws over a dimmed one it crosses. Created by\n * `createRootElement`, so it exists before any draw.\n */\n private _dimLayer!: Handle;\n\n /** The `<g>` holding every edge drawn at full strength. Painted over `_dimLayer`. */\n private _normalLayer!: 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 // Every Component stamps ComponentDefaults' \"default\" cursor into its\n // own rule; left at that, a hit path inheriting from this `<svg>`\n // would resolve to an arrow regardless of what it declares itself.\n // Inheriting through to the view root's live grab/grabbing write is\n // what lets an edge press pan the canvas honestly.\n this.setCursor(\"inherit\");\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. Clears any\n * active edge emphasis first, so the freshly-drawn set always starts\n * undimmed — a re-layout that swaps in a new graph is not the graph the\n * emphasis was computed against.\n *\n * Routes routinely arrive before this layer has an element: a diagram built\n * inside a dock tab runs its whole ELK layout while the tab is still\n * detached (the app awaits\n * [`DiagramView.whenLaidOut`](/api/component/diagram/classes/DiagramView)\n * before mounting, so the layout *always* lands first), and `rebuildPaths`\n * cannot draw without one. `render` only performs the first draw when it is\n * what creates the element, which is not the case here — so without the\n * deferral below the sole draw for those routes is silently lost and the\n * diagram shows nodes with no edges until some later `setEdges` happens to\n * find an element. {@link Component.onFirstLayout} exists for exactly this\n * \"content built before the host attaches it\" case.\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._edgeEmphasis = new Set();\n\n if (this.getElement()) {\n this.rebuildPaths();\n } else {\n this.onFirstLayout(() => this.rebuildPaths());\n }\n\n return this;\n }\n\n /**\n * Sets the emphasised edge ids. While the set is non-empty every edge NOT\n * in it is drawn at a reduced opacity; the emphasised edges keep their\n * normal weight. `null` or an empty array clears the emphasis. Ids naming\n * no drawn edge are kept but have no effect.\n *\n * @param ids - The edge ids to emphasise, or null to clear.\n *\n * @returns This layer, for method chaining.\n */\n setEdgeEmphasis(ids: readonly string[] | null): this {\n this._edgeEmphasis = new Set(ids ?? []);\n\n // A full redraw rather than a restyle in place: which of the two groups\n // an edge belongs to is decided at draw time, and redrawing is what\n // moves it. Cheap enough to do on a click — a redraw of a 1000-edge\n // graph's paths measures around 20ms, against the seconds ELK itself\n // takes — and it reuses the release/rebuild path already covered by\n // tests instead of relying on appendChild's move semantics through the\n // DOM seam.\n this.rebuildPaths();\n\n return this;\n }\n\n /**\n * The currently emphasised edge ids.\n *\n * @returns A copy of the emphasised id array; empty when nothing is emphasised.\n */\n getEdgeEmphasis(): string[] {\n return [...this._edgeEmphasis];\n }\n\n /**\n * Resolves a raw DOM event target to the edge whose invisible hit path it is.\n *\n * @param target - The raw DOM event target.\n * @returns The edge id, or null when the target is not an edge hit path.\n */\n edgeIdAt(target: EventTarget | null): string | null {\n if (target === null) {\n return null;\n }\n\n const handle = DOM.source.intern(target);\n\n return this._drawn.find(d => d.hit === handle)?.id ?? null;\n }\n\n /**\n * Every drawn edge whose route passes within the hit tolerance of a point,\n * in draw order. Several edges answer here wherever their routes overlap —\n * which is what makes a merged trunk answerable.\n *\n * @param x - Point x in unscaled graph coordinates.\n * @param y - Point y in unscaled graph coordinates.\n * @returns The routes within tolerance; empty when none is.\n */\n edgesNear(x: number, y: number): DiagramEdgeRoute[] {\n return this._drawn\n .filter(drawn => distanceToRoute(drawn.route.sections, x, y) <= EDGE_HIT_TOLERANCE)\n .map(drawn => drawn.route);\n }\n\n /**\n * Which group one edge draws into: the dimmed group when an emphasis set is\n * active and this edge is not in it, the full-strength group otherwise.\n *\n * @param id - The edge id.\n * @returns The group handle to append the edge's elements into.\n */\n private groupFor(id: string): Handle {\n if (this._edgeEmphasis.size === 0 || this._edgeEmphasis.has(id)) {\n return this._normalLayer;\n }\n\n return this._dimLayer;\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 this._dimLayer = this.createEdgeGroup(svg, DIMMED_EDGE_OPACITY);\n this._normalLayer = this.createEdgeGroup(svg, null);\n\n return svg;\n }\n\n /**\n * Creates one of the two persistent edge groups as a child of the root\n * `<svg>`. Append order is paint order, so the caller creates the dimmed\n * group first.\n *\n * @param svg - The root `<svg>` handle to append the group into.\n * @param opacity - The group's opacity, or null to leave it at full strength.\n *\n * @returns The `<g>` handle.\n */\n private createEdgeGroup(svg: Handle, opacity: string | null): Handle {\n const group = DOM.sink.createElementNS(SVG_NS, \"g\");\n\n if (opacity !== null) {\n DOM.sink.apply(group, { setAttr: { opacity } });\n }\n\n DOM.sink.appendChild(svg, group);\n this.trackHandle(group);\n\n return group;\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 drawn of this._drawn) {\n this.releaseDrawnEdge(drawn);\n }\n\n this._drawn = [];\n\n for (const edge of this._edges) {\n const d = buildPathData(edge.sections);\n\n if (!d) {\n continue;\n }\n\n const group = this.groupFor(edge.id);\n const hit = this.drawHitPath(group, d);\n const path = this.drawVisiblePath(group, edge, d);\n const style = edge.style;\n let label: Handle | null = null;\n\n if (style?.label) {\n label = this.drawLabel(group, edge, style.label);\n }\n\n this._drawn.push({ id: edge.id, route: edge, path, hit, label, group });\n }\n }\n\n /**\n * Draws one edge's invisible wide hit path: same `d` as the visible path,\n * transparent stroke, and the only element in the layer that opts back\n * into pointer events (`pointer-events: stroke`) — the root `<svg>` stays\n * inert. Appended before the visible path.\n *\n * @param group - The edge group handle to append into.\n * @param d - The edge's path data (shared with the visible path).\n * @returns The hit path's handle.\n */\n private drawHitPath(group: Handle, d: string): Handle {\n const hit = DOM.sink.createElementNS(SVG_NS, \"path\");\n\n DOM.sink.apply(hit, { setAttr: {\n d,\n fill: \"none\",\n stroke: \"transparent\",\n \"stroke-width\": String(EDGE_HIT_WIDTH),\n \"pointer-events\": \"stroke\",\n // Dragging an edge pans the canvas like empty canvas does, so the\n // hit path takes the viewport's own live grab/grabbing cursor by\n // inheriting rather than promising a cursor of its own.\n cursor: \"inherit\",\n } });\n\n DOM.sink.appendChild(group, hit);\n this.trackHandle(hit);\n\n return hit;\n }\n\n /**\n * Draws one edge's visible stroked path, carrying its markers and dash. The\n * edge's emphasis state is not written here — it is the group the path is\n * appended into (see {@link DIMMED_EDGE_OPACITY}).\n *\n * @param group - The edge group handle to append into.\n * @param edge - The routed edge to draw.\n * @param d - The edge's path data.\n * @returns The visible path's handle.\n */\n private drawVisiblePath(group: Handle, edge: DiagramEdgeRoute, d: string): Handle {\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(group, path);\n this.trackHandle(path);\n\n return path;\n }\n\n /**\n * Releases one previously-drawn edge's elements (hit path, visible path,\n * and optional label) from the group they were appended into and from the\n * tracked-handle set.\n *\n * @param drawn - The drawn edge record to release.\n */\n private releaseDrawnEdge(drawn: DrawnEdge): void {\n const handles = [drawn.hit, drawn.path, ...(drawn.label ? [drawn.label] : [])];\n\n for (const handle of handles) {\n DOM.sink.removeChild(drawn.group, handle);\n this.untrackHandle(handle);\n DOM.sink.release(handle);\n }\n }\n\n /**\n * Draws one edge's optional mid-route label as a `<text>` element.\n *\n * @param group - The edge group handle to append into, which also carries\n * the label's emphasis state (see {@link DIMMED_EDGE_OPACITY}).\n * @param edge - The edge route the label belongs to (for its anchor point).\n * @param label - The label text.\n * @returns The label's handle.\n */\n private drawLabel(group: Handle, edge: DiagramEdgeRoute, label: string): Handle {\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(group, text);\n this.trackHandle(text);\n\n return 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 (an Anchor-managed Panel viewport) owns a single\n// content host Container (Absolute layout) that carries the pan/zoom transform\n// and holds the node components + the edge layer, plus a corner-pinned control\n// cluster (zoom in/out, fit, reset). Pan is an unbounded `translate()` on the\n// content host's transform, not native scroll — the viewport has no scrollbars\n// and simply clips (`overflow: hidden`) whatever pans outside it, giving an\n// infinite-canvas feel. Zoom is the transform's `scale()` factor.\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 { Anchor } from \"~/layout/Anchor.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { FloatingPanel } from \"~/component/container/FloatingPanel.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { ProgressSpinner } from \"~/component/display/ProgressSpinner.js\";\nimport { plus } from \"~/glyphs/solid/plus.js\";\nimport { minus } from \"~/glyphs/solid/minus.js\";\nimport { expand } from \"~/glyphs/solid/expand.js\";\nimport { crosshairs } from \"~/glyphs/solid/crosshairs.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, DiagramEdgeData, 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\nGlyph.register(plus, minus, expand, crosshairs);\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\" | \"contextmenu\" | \"edgehover\" | \"edgeleave\";\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/** Multiplicative zoom step per control-cluster button press. */\nconst ZOOM_BUTTON_STEP = 1.5;\n\n/**\n * Pixel distance the pointer must travel between `pointerdown` and a later\n * `pointermove` before the press-and-release it ends in counts as a drag\n * rather than a click. Matches `DragManager.DRAG_THRESHOLD`, the library's\n * only other click-versus-drag disambiguation.\n */\nconst CLICK_SLOP = 4;\n\n/**\n * Opacity of a node component outside a non-empty node-emphasis set. Higher\n * than `DiagramEdgeLayer`'s `DIMMED_EDGE_OPACITY` (`0.15`) because perceived\n * presence scales with area, not just alpha: a `TableCardNode`-sized box with\n * a border and text rows still reads at `0.15`, whereas a 1.5px hairline\n * needs the lower number to recede. `0.35` keeps a dimmed card's shape and\n * label legible while clearly receding behind the emphasised ones.\n */\nconst DIMMED_NODE_OPACITY = 0.35;\n\n// Diameter in pixels of the busy overlay's arc. Matches `TablePanel`'s\n// store-loading spinner and `createSpinnerWrap`'s lazy-tab placeholder, so a\n// slow diagram update and a slow data load read as the same kind of wait.\nconst BUSY_SPINNER_DIAMETER = 24;\n\n// Structural breathing room between the control cluster and the viewport\n// corner it is pinned to — not a cosmetic choice, but the inset the Anchor\n// constraint needs so the cluster does not paint flush against the edge.\nconst CONTROLS_MARGIN = 12;\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 /**\n * URL of a consumer-hosted `elk-worker.js`, requesting off-thread layout.\n * With the `elk.bundled.js` module this view's engine imports, elkjs's\n * own worker-availability check always fails, so `workerUrl` alone never\n * actually constructs a Worker; layout still runs on the main thread, via\n * elkjs's own fallback. Pass {@link DiagramViewOptions.elkWorkerFactory}\n * for real off-thread execution.\n */\n elkWorkerUrl?: string;\n /**\n * Factory returning a Web Worker for off-thread ELK layout. When set,\n * ELK's compute runs in the returned worker. Construct it in your app so\n * your bundler emits the worker, e.g.\n * `() => new Worker(new URL(\"elkjs/lib/elk-worker.min.js\", import.meta.url), { type: \"classic\" })`.\n * Takes precedence over {@link DiagramViewOptions.elkWorkerUrl} when both\n * are set.\n */\n elkWorkerFactory?: () => Worker;\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 /** Show the built-in zoom / fit / reset control cluster (default true). */\n controls?: boolean;\n /**\n * Id of the node the one-shot initial view centres on, instead of the\n * graph's bounds. An id naming no node in the graph falls back to\n * centring the bounds. The configured `zoom` is honoured, except that a\n * focus node too large to fit the viewport lowers it until the node fits.\n */\n initialFocusNode?: string;\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 contextmenu?: (node: DiagramNodeData, event: MouseEvent) => void;\n edgehover?: (edges: DiagramEdgeData[], event: MouseEvent) => void;\n edgeleave?: () => 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 — the graph currently on screen. */\n private _nodeComponents: Map<string, Component> = new Map();\n\n /** Node model data keyed by node id (selection payload source) — the graph currently on screen. */\n private _nodeData: Map<string, DiagramNodeData> = new Map();\n\n /** Ids of the compound container nodes in the graph currently on screen. */\n private _containerIds: Set<string> = new Set();\n\n /**\n * The graph built by the latest `setData`, mounted hidden and awaiting a\n * layout. Promoted into the shown set above by `applyLayout`, discarded by\n * `handleLayoutFailure` or by the next `rebuildNodes`. Kept separate from\n * the shown set so a re-layout leaves the previous graph on screen for the\n * whole ELK round-trip instead of blanking the canvas.\n */\n private _incomingComponents: Map<string, Component> = new Map();\n private _incomingData: Map<string, DiagramNodeData> = new Map();\n private _incomingContainerIds: Set<string> = new Set();\n\n /** Currently selected node data (single-select). */\n private _selection: DiagramNodeData[] = [];\n\n /**\n * Ids of the emphasised nodes; every other node component is dimmed.\n * Runtime interaction state, off the options bag — the view's own mirror\n * of `DiagramEdgeLayer`'s `_edgeEmphasis`, except there is no equivalent\n * layer for nodes, so it lives here instead. Cleared in\n * `promoteIncomingNodes` beside `_selection`.\n */\n private _nodeEmphasis: Set<string> = new Set();\n\n /** Monotonic layout token; guards against a stale in-flight layout landing. */\n private _layoutGeneration: number = 0;\n\n /** Resolver for the promise `whenLaidOut` hands out, or `null` when idle. */\n private _layoutSettled: { promise: Promise<void>; resolve: () => void } | null = null;\n\n /**\n * The overlay busy indicator, built the first time a layout pass runs on a\n * view that has a size. Runtime state, deliberately off the options bag.\n */\n private _busySpinner: ProgressSpinner | null = null;\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\"` / `\"contextmenu\"`. */\n private _listeners: ListenerBag<DiagramViewEvent> = new ListenerBag<DiagramViewEvent>();\n\n /** Edge ids of the last emitted \"edgehover\", joined, or null when not hovering. */\n private _hoveredEdgeKey: string | null = null;\n\n /** Pan drag state. */\n private _panning: boolean = false;\n private _panStartX: number = 0;\n private _panStartY: number = 0;\n\n /**\n * Pointer position at the last `pointerdown`, and whether the pointer has\n * since travelled past `CLICK_SLOP`. Runtime gesture state, off the\n * options bag: `_handleClick` reads `_pointerMoved` to tell a click from\n * the tail end of a drag — a pan that starts and ends on empty canvas\n * still fires a native `click`, and it must not clear the selection (or\n * the edge emphasis the app keys off it).\n */\n private _pressX: number = 0;\n private _pressY: number = 0;\n private _pointerMoved: boolean = false;\n\n /** Pan offset (viewport pixels) captured when a drag begins. */\n private _panOriginX: number = 0;\n private _panOriginY: number = 0;\n\n /** Current, unbounded pan offset (viewport pixels) driving the content host's transform. */\n private _panX: number = 0;\n private _panY: number = 0;\n\n /**\n * Whether the graph still owes its one-time initial centring. Cleared by\n * the first layout that manages to centre, so a later `setData` re-layout\n * never yanks a pan the user has since dragged to.\n */\n private _needsInitialCentre: boolean = true;\n\n /**\n * Id of the node the one-shot initial view centres on, instead of the\n * graph's bounds; also the target of the most recent `focusNode` call.\n * Seeded from `initialFocusNode` in the constructor body (mirroring how\n * `data`/`controls` are cached during `applyOptions` and dispatched once\n * the constructor body can act on them) and re-pointed by `focusNode`.\n */\n private _focusNodeId: string | null = null;\n\n /**\n * Viewport size at the last layout, so a resize can be measured as a delta\n * and the graph point under the viewport centre held in place. `NaN` until\n * the view is first sized (matching `getWidth`/`getHeight`).\n */\n private _lastViewportWidth: number = NaN;\n private _lastViewportHeight: number = NaN;\n\n /** The corner-pinned zoom / fit / reset control cluster. */\n private _controls!: FloatingPanel;\n private _zoomInBtn!: Button;\n private _zoomOutBtn!: Button;\n private _fitBtn!: Button;\n private _resetBtn!: Button;\n\n private readonly _onZoomIn: () => void = () => this.zoomIn();\n private readonly _onZoomOut: () => void = () => this.zoomOut();\n private readonly _onFit: () => void = () => this.zoomToFit();\n private readonly _onReset: () => void = () => this.resetView();\n\n constructor(options?: DiagramViewOptions) {\n super(options, { zoom: DEFAULT_ZOOM, minZoom: DEFAULT_MIN_ZOOM, maxZoom: DEFAULT_MAX_ZOOM, controls: true });\n\n this.setLayoutManager(new Anchor());\n this.setCursor(\"grab\");\n\n this._engine = this.createEngine();\n\n // Nodes are laid out at unscaled graph coordinates under the host's\n // `translate(panX,panY) scale(zoom)` transform, and the host's own box\n // is likewise sized to the unscaled graph bounds (see applyLayout), so\n // node coordinates never exceed the box regardless of zoom — unlike the\n // old scaled-box model this stays visible for consistency with that\n // history rather than out of present necessity.\n // `cursor: \"inherit\"` rather than the Component default: the host is an\n // invisible box spanning the whole graph bounds, so the default\n // `cursor: default` every Component stamps would paint an arrow across\n // the entire canvas and mask the viewport's own grab/grabbing. Inheriting\n // lets the single write on the view root govern the whole canvas.\n this._contentHost = new Container({ layoutManager: new Absolute(), overflow: \"visible\", cursor: \"inherit\" });\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.buildControls();\n this.wireControlListeners();\n\n this.addComponent(this._controls, this._controls.getAnchorConstraints());\n\n this.applyListeners(options?.listeners);\n\n this.setControlsVisible(this._options.controls ?? this._defaultOptions.controls ?? true);\n\n if (this._options.zoom !== undefined) {\n this.setZoom(this._options.zoom);\n }\n\n this._focusNodeId = this._options.initialFocusNode ?? null;\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({\n workerFactory: this._options.elkWorkerFactory,\n workerUrl: this._options.elkWorkerUrl,\n });\n }\n\n /**\n * Disposes the layout engine — releasing its ELK Web Worker, if it had one\n * — before the inherited destructor detaches the element.\n */\n protected destructor(): void {\n // Invalidate any layout still in flight before the engine goes away. A\n // result landing afterwards would write into a torn-down view, and a\n // failure landing afterwards would strip nodes off it; both guards\n // compare against this token and drop a stale one.\n this._layoutGeneration += 1;\n\n // Settle rather than leave dangling: a caller awaiting `whenLaidOut()`\n // (e.g. the app holding a lazy tab's spinner) must not hang forever\n // just because the view was disposed on some other path mid-pass.\n this.settleLayout();\n\n // The spinner is mounted by a raw DOM append rather than as a child\n // component, so the inherited destructor's child pass never reaches it.\n this._busySpinner?.dispose();\n this._busySpinner = null;\n\n this._engine.dispose();\n\n super.destructor();\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.elkWorkerFactory !== undefined) this._options.elkWorkerFactory = options.elkWorkerFactory;\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 // Cached only: the control cluster does not exist yet during the\n // `super()` cascade. The constructor dispatches `setControlsVisible`\n // itself once the cluster is built.\n if (options.controls !== undefined) this._options.controls = options.controls;\n // Cached only: dispatched into the runtime `_focusNodeId` field from\n // the constructor body, mirroring `data` below.\n if (options.initialFocusNode !== undefined) this._options.initialFocusNode = options.initialFocusNode;\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 * Builds fresh node components via the node renderer (default:\n * `DiagramNode`) into the *incoming* set, built and measured off the\n * component tree and left `setVisible(false)` — not `display: none`, so\n * the flag still reads false the instant a component mounts, before the\n * promote step's reveal. `applyLayout` positions them, then\n * `promoteIncomingNodes` swaps them in for the shown set, mounts them, and\n * reveals them together, so a diagram never paints an unplaced graph and a\n * re-layout keeps the previous graph on screen for the whole ELK\n * round-trip. A graph superseded by a newer `setData` before its layout\n * lands is therefore never mounted at all. Leads with\n * `discardIncomingNodes()` so a second `setData` arriving before the\n * first's layout lands does not leak the first attempt's components.\n * Recurses into `children`: a container node (non-empty `children`) is\n * built via the group renderer (default: `DiagramGroupNode`) and its\n * children are built the same way, so container + leaf components all\n * land as flat siblings in `_incomingComponents`/`_incomingData`, keyed by\n * id — a container is never a DOM parent of its children (that would\n * perturb the single-content-host model `nodeIdAt` hit-testing relies on).\n *\n * @param data - The graph whose nodes to build.\n */\n private rebuildNodes(data: DiagramData): void {\n this.discardIncomingNodes();\n\n const renderer = this._options.nodeRenderer ?? ((node: DiagramNodeData): Component =>\n new DiagramNode({ label: node.label, glyph: node.glyph, badge: node.badge }));\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 component.setVisible(false);\n\n this._incomingComponents.set(node.id, component);\n this._incomingData.set(node.id, node);\n\n if (isContainer) {\n this._incomingContainerIds.add(node.id);\n build(node.children!);\n }\n }\n };\n\n build(data.nodes);\n }\n\n /** Forgets the un-promoted incoming components; never mounted, so nothing to detach. */\n private discardIncomingNodes(): void {\n this._incomingComponents.clear();\n this._incomingData.clear();\n this._incomingContainerIds.clear();\n }\n\n /**\n * Swaps the incoming set in for the shown one: removes and disposes the\n * shown components (they are being discarded, not re-parented, so a bare\n * `removeComponent` would leak their listeners/theme subscriptions/style\n * rules — see `Component.destructor`), promotes the incoming maps,\n * clears the selection and the node emphasis, then mounts and reveals\n * every promoted component together — this is the first time an\n * incoming component is added to the content host at all.\n */\n private promoteIncomingNodes(): void {\n for (const component of this._nodeComponents.values()) {\n this._contentHost.removeComponent(component);\n component.dispose();\n }\n\n this._nodeComponents = this._incomingComponents;\n this._nodeData = this._incomingData;\n this._containerIds = this._incomingContainerIds;\n\n this._incomingComponents = new Map();\n this._incomingData = new Map();\n this._incomingContainerIds = new Set();\n\n this._selection = [];\n this._nodeEmphasis = new Set();\n\n for (const component of this._nodeComponents.values()) {\n this._contentHost.addComponent(component);\n component.setVisible(true);\n }\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 this.armLayoutSettled();\n this.syncBusyIndicator();\n\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 * Arms the awaitable `whenLaidOut` hands out, so one deferred spans\n * however many layout passes run before one of them finishes. A no-op\n * when a pass is already in flight, so two rapid `setData` calls share\n * one promise, resolved by whichever pass settles it.\n */\n private armLayoutSettled(): void {\n if (this._layoutSettled !== null) {\n return;\n }\n\n let resolve!: () => void;\n const promise = new Promise<void>((r) => { resolve = r; });\n\n this._layoutSettled = { promise, resolve };\n }\n\n /** Settles the armed `whenLaidOut` awaitable, if there is one. */\n private settleLayout(): void {\n const settled = this._layoutSettled;\n\n this._layoutSettled = null;\n settled?.resolve();\n\n this.syncBusyIndicator();\n }\n\n /**\n * Matches the overlay busy indicator to whether a layout pass is in flight,\n * and re-sizes a showing overlay to the current viewport. Called when a pass\n * starts, when one settles, and from every layout pass — which is where a\n * view that had no size when its pass started finally gets one.\n *\n * A view with no committed size has nothing to cover: `getWidth()` /\n * `getHeight()` are `NaN` (not 0) until the first `setSize`, and `> 0` rejects\n * both (see `effectiveMinZoom`). That also keeps the indicator off the first\n * pass every diagram runs from its own constructor, before its host has sized\n * it — whatever opened the view owns that first wait.\n */\n private syncBusyIndicator(): void {\n if (this._layoutSettled === null || !(this.getWidth() > 0) || !(this.getHeight() > 0)) {\n this._busySpinner?.hideOverlay();\n\n return;\n }\n\n if (this._busySpinner === null) {\n this._busySpinner = new ProgressSpinner(BUSY_SPINNER_DIAMETER);\n }\n\n // `showOverlay` is a no-op once shown, so the explicit `doLayout` is what\n // re-sizes a showing overlay after a viewport resize: the spinner is\n // mounted by a raw DOM append, so it is not in this view's laid-out set\n // and nothing else ever lays it out.\n this._busySpinner.showOverlay(this);\n this._busySpinner.doLayout();\n }\n\n /**\n * Resolves once the layout pass currently in flight has finished placing\n * nodes. Resolves immediately when no pass is in flight, and resolves\n * (never rejects) when a pass fails or the view is disposed mid-pass —\n * a rejecting promise would force every caller into a `try`/`catch`\n * whose only sensible branch is \"carry on anyway\".\n *\n * @returns A promise settling on the next finished layout pass.\n */\n whenLaidOut(): Promise<void> {\n return this._layoutSettled?.promise ?? Promise.resolve();\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 — read\n * before the component is mounted, which is safe because measurement goes\n * through the DOM seam's font metrics rather than the live document (a\n * custom `nodeRenderer` whose preferred size needs a live element in the\n * document is not supported). Recurses into `children` so every container\n * and leaf is represented; a container's entry is harmless-but-unused\n * since `buildElkGraph` computes a container's box from its contents\n * 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._incomingComponents.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 incoming node, promotes the\n * incoming set to shown (revealing it), sizes + scales the content host,\n * redraws the edges, and emits `\"layout\"`. A stale result (superseded by\n * 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._incomingComponents.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.promoteIncomingNodes();\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 // The host is no longer resized per zoom (see applyTransformToHost) — its\n // box always matches the unscaled graph bounds the nodes are laid out at.\n this._contentHost.setPreferredSize({ width: result.width, height: result.height });\n this.applyTransformToHost();\n\n // Before `emit`, so a consumer's own `\"layout\"` listener (the sanctioned\n // auto-fit hook, `view.on(\"layout\", () => view.zoomToFit())`) still runs\n // afterwards and wins. Only succeeds if the view is already sized; the\n // `doLayout` override retries otherwise.\n this.tryInitialCentre();\n\n this.scheduleLayout();\n\n this.emit(\"layout\");\n\n this.settleLayout();\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 * Discards the just-built incoming nodes when a layout fails (e.g. `elkjs`\n * absent), so a first, never-shown graph leaves the view empty, and a\n * failed *re*-layout leaves whatever graph was already on screen — rather\n * than showing stacked, unpositioned nodes either way.\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 this.discardIncomingNodes();\n this.settleLayout();\n }\n\n /**\n * Writes the content host's `translate(panX,panY) scale(zoom)` transform\n * from the current pan offset and zoom factor. The host's box is set once\n * per layout (see `applyLayout`) to the unscaled graph bounds and is not\n * touched here — pan and zoom live entirely on the transform.\n */\n private applyTransformToHost(): void {\n const zoom = this.getZoom();\n\n this._contentHost.setTransform(`translate(${this._panX}px, ${this._panY}px) scale(${zoom})`);\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]` (the lower bound\n * adaptively lowered so a huge graph can still reach its fit zoom — see\n * `effectiveMinZoom`), and re-applies the content host's transform. A\n * non-finite request (e.g. `zoomToFit`'s `graphWidth / 0` on an unsized\n * view) is rejected outright rather than clamped, since `Math.max`/`min`\n * propagate `NaN` instead of resolving it — this is the one guard every\n * zoom-changing entry point (`zoomToFit`, `resetView`, `zoomIn`/`zoomOut`)\n * relies on rather than each re-checking its own inputs.\n *\n * @param zoom - The desired zoom factor.\n *\n * @returns This view, for method chaining.\n */\n setZoom(zoom: number): this {\n if (!Number.isFinite(zoom)) {\n return this;\n }\n\n this._options.zoom = this.clampZoom(zoom);\n this.applyTransformToHost();\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, then centres the graph. Requires a\n * 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 this.centreGraph();\n\n return this;\n }\n\n /**\n * Resets to the default zoom, then re-centres: on the focus node when the\n * view has one that is in the shown graph, else on the graph bounds. The\n * focus node is `initialFocusNode`, or the target of the most recent\n * `focusNode` call. Centring a node also lowers the zoom, if needed, until\n * the node's whole box fits. Retried after the next layout pass when the\n * view has no committed size yet.\n *\n * @returns This view, for method chaining.\n */\n resetView(): this {\n this.setZoom(this._defaultOptions.zoom ?? DEFAULT_ZOOM);\n\n this._needsInitialCentre = true;\n this.tryInitialCentre();\n\n return this;\n }\n\n /**\n * Steps the zoom up by the configured multiplicative factor, keeping the\n * graph point currently at the viewport centre fixed.\n *\n * @returns This view, for method chaining.\n */\n zoomIn(): this {\n this.zoomAboutViewportPoint(ZOOM_BUTTON_STEP, this.getWidth() / 2, this.getHeight() / 2);\n\n return this;\n }\n\n /**\n * Steps the zoom down by the configured multiplicative factor, keeping the\n * graph point currently at the viewport centre fixed.\n *\n * @returns This view, for method chaining.\n */\n zoomOut(): this {\n this.zoomAboutViewportPoint(1 / ZOOM_BUTTON_STEP, this.getWidth() / 2, this.getHeight() / 2);\n\n return this;\n }\n\n /**\n * Attempts the one-time initial centring, so the first render shows the\n * graph where `resetView` would put it rather than at the viewport's\n * top-left corner. The configured `zoom` deliberately stands (unlike\n * `resetView`, which also restores the default zoom), so a consumer's\n * explicit `zoom` option still decides the initial scale — except that a\n * focus node too large to fit the viewport lowers it until the node fits.\n *\n * Both inputs arrive asynchronously and in either order: the graph bounds\n * come from an ELK layout, the viewport size from the host's layout pass.\n * So this is *retried* — from `applyLayout`, from every `doLayout`, and\n * from `resetView` — until it actually succeeds, and the pending flag is\n * cleared only on a confirmed centring. Clearing it on an attempt that\n * silently no-opped (the view not yet sized, `centreGraph` returning\n * `false`) is what used to leave the diagram stuck in the corner whenever\n * the layout landed first.\n *\n * Picks between two targets: `_focusNodeId`, when it names a node in the\n * graph just promoted, or the graph's bounds otherwise — the same\n * generalisation `focusNode` reuses at runtime instead of a separate\n * one-shot mechanism.\n */\n private tryInitialCentre(): void {\n if (!this._needsInitialCentre || !(this._graphWidth > 0) || !(this._graphHeight > 0)) {\n return;\n }\n\n const focus = this._focusNodeId !== null && this._nodeComponents.has(this._focusNodeId)\n ? this._focusNodeId\n : null;\n\n if (focus !== null ? this.centreNode(focus) : this.centreGraph()) {\n this._needsInitialCentre = false;\n }\n }\n\n /**\n * Centres the graph bounds in the viewport at the current zoom, without\n * changing the zoom factor. Shared by `zoomToFit`, `resetView`, and the\n * initial centring. Declines before the view is sized —\n * `getWidth()`/`getHeight()` are `NaN` (not 0) until then (see\n * `effectiveMinZoom`), and writing a `NaN` pan would blank the diagram\n * until another call recomputes it.\n *\n * @returns `true` when the pan was written, `false` when the view has no\n * committed size yet and the caller should try again later.\n */\n private centreGraph(): boolean {\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n if (!(vw > 0) || !(vh > 0)) {\n return false;\n }\n\n const zoom = this.getZoom();\n\n this._panX = (vw - this._graphWidth * zoom) / 2;\n this._panY = (vh - this._graphHeight * zoom) / 2;\n\n this.applyTransformToHost();\n\n return true;\n }\n\n /**\n * Scales by `factor` about a fixed viewport point: the graph point under\n * `(vx, vy)` maps to the same viewport point before and after the zoom.\n * Shared by the zoom in/out control buttons and (about the pointer instead\n * of the viewport centre) by wheel-zoom. A no-op when `vx`/`vy` is not\n * finite — `zoomIn`/`zoomOut` pass the viewport centre, which is `NaN`\n * before the view is sized (see `centreGraph`).\n *\n * @param factor - The multiplicative zoom change (`> 1` zooms in).\n * @param vx - The viewport-relative x coordinate to keep fixed.\n * @param vy - The viewport-relative y coordinate to keep fixed.\n */\n private zoomAboutViewportPoint(factor: number, vx: number, vy: number): void {\n if (!Number.isFinite(vx) || !Number.isFinite(vy)) {\n return;\n }\n\n const oldZoom = this.getZoom();\n const newZoom = this.clampZoom(oldZoom * factor);\n\n if (newZoom === oldZoom) {\n return;\n }\n\n const graphX = (vx - this._panX) / oldZoom;\n const graphY = (vy - this._panY) / oldZoom;\n\n this._panX = vx - graphX * newZoom;\n this._panY = vy - graphY * newZoom;\n\n this.setZoom(newZoom);\n }\n\n /**\n * Resolves the effective minimum zoom: the configured `minZoom`, floored\n * further down to the zoom that fits the whole graph in the current\n * viewport when the graph is too large for that configured floor to reach.\n * A small graph is unaffected (its fit zoom already exceeds the configured\n * minimum); an unsized or unlaid-out view falls back to the configured\n * minimum untouched.\n *\n * @returns The effective minimum zoom factor.\n */\n private effectiveMinZoom(): number {\n const configuredMin = this._options.minZoom ?? this._defaultOptions.minZoom ?? DEFAULT_MIN_ZOOM;\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n // `getWidth()` / `getHeight()` are `NaN` (not 0) before the first\n // `setSize` — `> 0` (rather than `<= 0`) also rejects NaN, since every\n // NaN comparison is false.\n if (!(this._graphWidth > 0) || !(this._graphHeight > 0) || !(vw > 0) || !(vh > 0)) {\n return configuredMin;\n }\n\n const fitZoom = Math.min(vw / this._graphWidth, vh / this._graphHeight);\n\n return Math.min(configuredMin, fitZoom);\n }\n\n /**\n * Clamps a zoom request to `[effectiveMinZoom(), maxZoom]`.\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.effectiveMinZoom();\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 * Resolves the zoom that fits `size` whole in the viewport, never raising\n * the current zoom — only lowering it when the size does not already fit.\n * Shared by every node-centring entry point (`centreNode`, and so\n * `revealNode`, `focusNode`, and the focus-node branch of\n * `tryInitialCentre`) so a card larger than the viewport is never\n * centred-and-clipped.\n *\n * @param size - The node box to fit, in unscaled graph coordinates.\n * @returns The clamped zoom to use for the centring.\n */\n private zoomFittingNode(size: { width: number; height: number }): number {\n const current = this.getZoom();\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n // `getWidth()` / `getHeight()` are `NaN` before the first `setSize`, and a\n // zero-sized node box has no fit zoom — `> 0` rejects both, since every\n // NaN comparison is false (see `effectiveMinZoom`).\n if (!(vw > 0) || !(vh > 0) || !(size.width > 0) || !(size.height > 0)) {\n return this.clampZoom(current);\n }\n\n const fitZoom = Math.min(vw / size.width, vh / size.height);\n\n return this.clampZoom(Math.min(current, fitZoom));\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 * Emphasises a subset of the drawn edges: every edge outside the set\n * recedes to a lower opacity while the named ones keep their normal\n * weight. Cleared by `null`, by an empty array, and by the next layout\n * that replaces the drawn edges. Emits nothing. Forwards straight to the\n * edge layer, which owns the emphasis state — this view holds no copy of\n * its own.\n *\n * @param ids - The edge ids to emphasise, or null to clear.\n *\n * @returns This view, for method chaining.\n */\n setEdgeEmphasis(ids: readonly string[] | null): this {\n this._edgeLayer.setEdgeEmphasis(ids);\n\n return this;\n }\n\n /**\n * The currently emphasised edge ids.\n *\n * @returns A copy of the emphasised id array; empty when nothing is emphasised.\n */\n getEdgeEmphasis(): string[] {\n return this._edgeLayer.getEdgeEmphasis();\n }\n\n /**\n * Emphasises a subset of the drawn nodes: every node component outside\n * the set dims to a reduced opacity while the named ones keep full\n * opacity. Cleared by `null`, by an empty array, and by the next layout\n * that rebuilds the node components (`promoteIncomingNodes`). Emits\n * nothing. Dims through `Component.setOpacity` / `clearOpacity` directly\n * on each node's own root, so a custom `nodeRenderer` needs no\n * cooperation — unlike `applySelectedVisual`, which duck-types a\n * `setSelected?.()` because \"selected\" has no single generic rendering.\n * An unknown id is kept in the set (and reported back by\n * `getNodeEmphasis`) but dims nothing, since no node component answers to\n * it.\n *\n * @param ids - The node ids to emphasise, or null to clear.\n *\n * @returns This view, for method chaining.\n */\n setNodeEmphasis(ids: readonly string[] | null): this {\n this._nodeEmphasis = new Set(ids ?? []);\n this.applyNodeEmphasis();\n\n return this;\n }\n\n /**\n * The currently emphasised node ids.\n *\n * @returns A copy of the emphasised id array; empty when nothing is emphasised.\n */\n getNodeEmphasis(): string[] {\n return [...this._nodeEmphasis];\n }\n\n /** Rewrites every node component's opacity from the current emphasis set. */\n private applyNodeEmphasis(): void {\n for (const [id, component] of this._nodeComponents) {\n if (this._nodeEmphasis.size === 0 || this._nodeEmphasis.has(id)) {\n component.clearOpacity();\n } else {\n component.setOpacity(DIMMED_NODE_OPACITY);\n }\n }\n }\n\n /**\n * Pans so the given node is centred in the viewport, without changing the\n * selection or emitting any event. Also lowers the zoom, if needed, until\n * the node's whole box fits the viewport — never raises it. No-op for an\n * unknown id, before the first layout has positioned the node, or before\n * the view has a real committed size — `getWidth()`/`getHeight()` are\n * `NaN` (not `0`) until then (see `effectiveMinZoom`). Unlike a\n * non-finite zoom request, which `setZoom` rejects outright, a `NaN` pan\n * has no such gate here, so it is guarded directly: a `NaN` pan is\n * sticky, silently blanking the diagram until another call recomputes\n * it. Pair with {@link selectNode} to both highlight and reveal. Unlike\n * {@link focusNode}, this does not retry — a no-op call must be repeated\n * by the caller once the view is sized.\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 this.centreNode(id);\n\n return this;\n }\n\n /**\n * Centres a node in the viewport by writing the pan, without changing the\n * selection or emitting any event. Also lowers the zoom, via\n * `zoomFittingNode`, when the node's whole box does not already fit the\n * viewport — never raises it.\n *\n * @param id - The node id to centre.\n *\n * @returns `true` when the pan and zoom were written, `false` when the\n * node is unknown or the view has no committed size yet.\n */\n private centreNode(id: string): boolean {\n const component = this._nodeComponents.get(id);\n const size = component?.getPreferredSize();\n\n if (!component || !size) {\n return false;\n }\n\n const zoom = this.zoomFittingNode(size);\n\n // Node centre in unscaled graph coordinates.\n const centreX = component.getX() + size.width / 2;\n const centreY = component.getY() + size.height / 2;\n\n // Pan so the node centre maps to the viewport centre: viewport = pan + graph·zoom.\n const panX = this.getWidth() / 2 - centreX * zoom;\n const panY = this.getHeight() / 2 - centreY * zoom;\n\n if (!Number.isFinite(panX) || !Number.isFinite(panY)) {\n return false;\n }\n\n this._panX = panX;\n this._panY = panY;\n\n // Writes the pan fields first, then lets `setZoom` apply the transform\n // once — the shape `zoomAboutViewportPoint` already uses.\n this.setZoom(zoom);\n\n return true;\n }\n\n /**\n * Centres the given node in the viewport, retrying after each layout pass\n * until it succeeds — unlike `revealNode`, which centres only when the\n * graph and the viewport are both already measured. Also lowers the\n * zoom, if needed, until the node's whole box fits the viewport; never\n * raises it.\n *\n * @param id - The node id to centre on.\n *\n * @returns This view, for method chaining.\n */\n focusNode(id: string): this {\n this._focusNodeId = id;\n this._needsInitialCentre = true;\n\n this.tryInitialCentre();\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; `\"contextmenu\"` fires when a\n * node is right-clicked; `\"edgehover\"` fires with every model edge\n * within the pointer's hit tolerance (several where routes overlap) and\n * the originating event; `\"edgeleave\"` fires when the pointer leaves\n * whatever edge(s) it was hovering.\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: \"contextmenu\", listener: (node: DiagramNodeData, event: MouseEvent) => void): this;\n on(event: \"edgehover\", listener: (edges: DiagramEdgeData[], event: MouseEvent) => void): this;\n on(event: \"edgeleave\", 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 / right-clicked node data (for `\"activate\"` / `\"contextmenu\"`).\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: \"contextmenu\", node: DiagramNodeData, mouseEvent: MouseEvent): void;\n protected emit(event: \"edgehover\", edges: DiagramEdgeData[], mouseEvent: MouseEvent): void;\n protected emit(event: \"edgeleave\"): void;\n protected emit(event: DiagramViewEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Lays the viewport out, then retries the pending initial centring. This is\n * where the viewport size becomes known, and an ELK layout that landed\n * before the host sized this view has nothing to centre against until now\n * (see `tryInitialCentre`). Also re-syncs the busy overlay, which is where a\n * view that had no size when its layout pass started finally picks the\n * overlay up. Writes only the content host's transform and the (unmanaged)\n * overlay, never a child's rect, so neither can feed back into the layout it\n * runs inside.\n *\n * @returns This view, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n this.tryInitialCentre();\n this.anchorCentreAcrossResize();\n this.syncBusyIndicator();\n\n return this;\n }\n\n /**\n * Holds the graph point under the viewport centre still across a viewport\n * resize, so whatever the user was looking at stays in the middle instead\n * of drifting toward a corner as the window grows or shrinks.\n *\n * With `viewport = pan + graph·zoom`, keeping the centre's graph point\n * fixed means `pan += (newExtent − oldExtent) / 2` on each axis — the zoom\n * cancels out, so this is correct at any zoom and never changes it. The\n * first sizing has no previous centre to preserve, so it only records the\n * extent (the initial centring owns that pass).\n */\n private anchorCentreAcrossResize(): void {\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n if (!(vw > 0) || !(vh > 0)) {\n return;\n }\n\n const previousWidth = this._lastViewportWidth;\n const previousHeight = this._lastViewportHeight;\n\n this._lastViewportWidth = vw;\n this._lastViewportHeight = vh;\n\n if (!(previousWidth > 0) || !(previousHeight > 0)) {\n return;\n }\n\n if (previousWidth === vw && previousHeight === vh) {\n return;\n }\n\n this._panX += (vw - previousWidth) / 2;\n this._panY += (vh - previousHeight) / 2;\n\n this.applyTransformToHost();\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 // All seven use the SUBTREE variant: the content (nodes, the SVG edge\n // layer, and the Panel's own overlay-scroll element) are descendants of\n // this view's root, so a real wheel/pointer event's target is never the\n // root itself. An exact-target `addListener` would therefore never fire\n // for pan/zoom — only `addSubtreeListener` sees the descendant events\n // (mirrors click/dblclick, which is why selection worked but pan did not).\n Event.addSubtreeListener(this, \"click\", this._handleClick);\n Event.addSubtreeListener(this, \"dblclick\", this._handleDoubleClick);\n Event.addSubtreeListener(this, \"contextmenu\", this._handleContextMenu);\n // Non-passive: `_handleWheel` calls `preventDefault()` to suppress the\n // page's native scroll/zoom, which a passive listener silently ignores\n // (mirrors `Component.attachWheelScrolling` / `WheelTrap`).\n Event.addSubtreeListener(this, \"wheel\", { passive: false, prevent: true, handler: this._handleWheel });\n Event.addSubtreeListener(this, \"pointerdown\", this._handlePointerDown);\n // pointermove defaults to a button-agnostic registration (see\n // Event.ts's PRIMARY_BUTTON_TYPES); `_handlePointerMove` gates on\n // the live `buttons` bitmask itself during a real pan drag.\n Event.addSubtreeListener(this, \"pointermove\", this._handlePointerMove);\n Event.addSubtreeListener(this, \"pointerup\", this._handlePointerUp);\n // `mousemove`/`mouseout` rather than `mouseenter`/`mouseleave`: per\n // ARCHITECTURE.md, the non-bubbling enter/leave pair never reaches the\n // framework's window-level capture handler.\n Event.addSubtreeListener(this, \"mousemove\", this._handleEdgeMouseMove);\n Event.addSubtreeListener(this, \"mouseout\", this._handleEdgeMouseOut);\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 // A drag is not a click: a pan that starts and ends on empty canvas\n // (or starts on a node and ends on empty canvas) still fires a native\n // \"click\" on their nearest common ancestor, and it must not clear the\n // selection or the edge emphasis the user was studying.\n if (this._pointerMoved) {\n return;\n }\n\n if (this.isControlsTarget(event.target)) {\n return;\n }\n\n // A press on an edge is neither a node click nor a canvas click: it\n // must not clear the selection the user is looking at.\n if (this._edgeLayer.edgeIdAt(event.target) !== null) {\n return;\n }\n\n const id = this.nodeIdAt(event.target);\n\n if (id !== null) {\n if (id === (this._selection[0]?.id ?? null)) {\n return;\n }\n\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 * Reports hover over the diagram's edges: resolves the pointer's graph\n * coordinate (inverting the pan/zoom transform `zoomAboutViewportPoint`\n * applies) and asks the edge layer which routes are within hit tolerance\n * there, emitting `\"edgehover\"` with the joined model edges whenever the\n * reported set changes. A drag in progress is not a hover (`_panning`), and\n * a move whose target is not an edge hit path — or whose resolved point\n * lands on no route — instead ends any hover in progress.\n *\n * @param event - The mousemove event, dispatched to the whole subtree.\n */\n private _handleEdgeMouseMove(event: MouseEvent): void {\n if (this._panning) {\n return;\n }\n\n if (this._edgeLayer.edgeIdAt(event.target) === null) {\n this.leaveEdges();\n\n return;\n }\n\n const rect = DOM.source.getViewportRect(this);\n const zoom = this.getZoom();\n const gx = (event.clientX - rect.left - this._panX) / zoom;\n const gy = (event.clientY - rect.top - this._panY) / zoom;\n\n const routes = this._edgeLayer.edgesNear(gx, gy);\n\n if (routes.length === 0) {\n this.leaveEdges();\n\n return;\n }\n\n const key = routes.map(r => r.id).join(\" \");\n\n if (key === this._hoveredEdgeKey) {\n return;\n }\n\n const modelById = new Map(this._options.data?.edges.map(e => [e.id, e]) ?? []);\n const edges = routes.map(r => modelById.get(r.id)).filter((e): e is DiagramEdgeData => e !== undefined);\n\n this._hoveredEdgeKey = key;\n this.emit(\"edgehover\", edges, event);\n }\n\n /**\n * Ends an edge hover when the pointer's `mouseout` leaves an edge hit\n * path. A `mouseout` from anywhere else is not a hover boundary and is\n * ignored — `_handleEdgeMouseMove`'s own \"target is not an edge\" branch\n * already covers a move that lands on empty canvas or a node.\n *\n * @param event - The mouseout event, dispatched to the whole subtree.\n */\n private _handleEdgeMouseOut(event: MouseEvent): void {\n if (this._edgeLayer.edgeIdAt(event.target) !== null) {\n this.leaveEdges();\n }\n }\n\n /** Emits `\"edgeleave\"` and clears the hover key, unless already idle. */\n private leaveEdges(): void {\n if (this._hoveredEdgeKey === null) {\n return;\n }\n\n this._hoveredEdgeKey = null;\n this.emit(\"edgeleave\");\n }\n\n /**\n * Resolves the node under a right-click and emits `\"contextmenu\"` with its\n * data, mirroring `Tree`'s contextmenu handling: a node hit suppresses the\n * browser's native menu via `preventDefault` and emits; a right-click on\n * empty canvas is left to the browser.\n *\n * @param event - The contextmenu event whose target is inside the view's\n * subtree.\n */\n private _handleContextMenu(event: MouseEvent): Event.ListenerResult {\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(\"contextmenu\", data, event);\n\n return { prevent: true };\n }\n }\n\n /**\n * Whether the given raw DOM event target lands inside the control cluster\n * — used to keep the cluster's own clicks/drags from also being\n * interpreted as a canvas click (clearing selection) or the start of a pan.\n *\n * @param target - The raw DOM event target.\n * @returns `true` when the target is the cluster or one of its descendants.\n */\n private isControlsTarget(target: EventTarget | null): boolean {\n if (target === null || this._controls === undefined) {\n return false;\n }\n\n const el = this._controls.getElement();\n const handle = DOM.source.intern(target);\n\n return el !== undefined && (el === handle || DOM.source.contains(el, handle));\n }\n\n /**\n * Wheel-zoom about the pointer: scales toward/away, keeping the graph\n * point under the cursor fixed in the viewport.\n *\n * @param event - The wheel event. `preventDefault` is applied by the\n * registration's `prevent: true` floor.\n */\n private _handleWheel(event: WheelEvent): void {\n const rect = DOM.source.getViewportRect(this);\n\n this.zoomAboutViewportPoint(event.deltaY < 0 ? WHEEL_ZOOM_STEP : 1 / WHEEL_ZOOM_STEP,\n event.clientX - rect.left, event.clientY - rect.top);\n }\n\n /**\n * Begins a pan drag on a primary-button press over the canvas — empty\n * space or an edge alike; only a node or the control cluster refuses —\n * recording the start pointer and pan offset, and switches the cursor to\n * `\"grabbing\"`. Also arms the click-versus-drag guard `_handleClick`\n * reads, regardless of whether this press ends up panning.\n *\n * @param event - The pointerdown event.\n */\n private _handlePointerDown(event: PointerEvent): void {\n // Recorded above every guard below, so a press that does not pan (a\n // node, the control cluster) still arms the click-versus-drag guard\n // `_handleClick` reads.\n this._pressX = event.clientX;\n this._pressY = event.clientY;\n this._pointerMoved = false;\n\n // A press on a node (leaf or container) or the control cluster is not\n // a pan: both show `pointer`, and the cursor has to promise what the\n // drag will actually do. Everything else — empty canvas and edges\n // alike — pans, since dragging an edge is this component's way of\n // panning from under the graph's own lines.\n if (event.button !== 0 || this.isControlsTarget(event.target) || this.nodeIdAt(event.target) !== null) {\n return;\n }\n\n this._panning = true;\n this._panStartX = event.clientX;\n this._panStartY = event.clientY;\n this._panOriginX = this._panX;\n this._panOriginY = this._panY;\n\n this.setCursor(\"grabbing\");\n }\n\n /**\n * Pans the content host during a drag by writing the pan offset from the\n * pointer delta — unbounded, so the graph can be dragged into empty space\n * in any direction.\n *\n * @param event - The pointermove event.\n */\n private _handlePointerMove(event: PointerEvent): void {\n // Gated on the primary button, like DragManager.onMouseMove gates on\n // an open session (DragManager.ts:492): this handler fires on every\n // pointer move over the subtree, including ordinary ambient hover\n // with no button held, and `_pressX`/`_pressY` default to `(0, 0)`\n // until the first real `pointerdown` — without this gate, the very\n // first hover of a session would latch `_pointerMoved` permanently.\n if (!this._pointerMoved && (event.buttons & 1) !== 0) {\n const dx = event.clientX - this._pressX;\n const dy = event.clientY - this._pressY;\n\n // Squared-distance comparison avoids a Math.hypot call, matching\n // DragManager's own threshold test.\n this._pointerMoved = dx * dx + dy * dy >= CLICK_SLOP * CLICK_SLOP;\n }\n\n if (!this._panning) {\n return;\n }\n\n // The primary button is no longer held — the pointer was released\n // outside this view's subtree, so its `pointerup` never reached us. End\n // the pan here rather than keep dragging on a button-up move.\n if ((event.buttons & 1) === 0) {\n this._panning = false;\n this.setCursor(\"grab\");\n\n return;\n }\n\n this._panX = this._panOriginX + (event.clientX - this._panStartX);\n this._panY = this._panOriginY + (event.clientY - this._panStartY);\n\n this.applyTransformToHost();\n }\n\n /**\n * Ends a pan drag and restores the `\"grab\"` cursor.\n */\n private _handlePointerUp(): void {\n this._panning = false;\n this.setCursor(\"grab\");\n }\n\n /** Builds the corner-pinned zoom / fit / reset control cluster. */\n private buildControls(): void {\n this._zoomInBtn = this.makeControlButton(\"plus\", \"Zoom in\");\n this._zoomOutBtn = this.makeControlButton(\"minus\", \"Zoom out\");\n this._fitBtn = this.makeControlButton(\"expand\", \"Fit to view\");\n this._resetBtn = this.makeControlButton(\"crosshairs\", \"Reset view\");\n\n this._controls = new FloatingPanel({ corner: \"bottom-right\", margin: CONTROLS_MARGIN, layoutManager: new VBox() });\n this._controls.addComponent(this._zoomInBtn);\n this._controls.addComponent(this._zoomOutBtn);\n this._controls.addComponent(this._fitBtn);\n this._controls.addComponent(this._resetBtn);\n }\n\n /**\n * Builds a glyph-only control button with an accessible label, mirroring\n * `VideoPlayer`'s control-bar buttons.\n *\n * @param glyph - The glyph name to show.\n * @param label - The accessible name (drives `aria-label` and the tooltip).\n * @returns The configured button.\n */\n private makeControlButton(glyph: string, label: string): Button {\n return new Button({ glyph, text: label, showText: false });\n }\n\n /** Wires the control cluster's buttons to their viewport-motion methods. */\n private wireControlListeners(): void {\n this._zoomInBtn.on(\"action\", this._onZoomIn);\n this._zoomOutBtn.on(\"action\", this._onZoomOut);\n this._fitBtn.on(\"action\", this._onFit);\n this._resetBtn.on(\"action\", this._onReset);\n }\n\n /**\n * Whether the built-in zoom / fit / reset control cluster is visible.\n *\n * @returns `true` when the control cluster shows.\n */\n isControlsVisible(): boolean {\n return this._options.controls ?? this._defaultOptions.controls ?? true;\n }\n\n /**\n * Shows or hides the built-in control cluster.\n *\n * @param value - Whether the control cluster is visible.\n *\n * @returns This view, for method chaining.\n */\n setControlsVisible(value: boolean): this {\n this._options.controls = value;\n this._controls.setVisible(value);\n\n return this;\n }\n}\n\nconst DiagramViewCallable = callable(DiagramView);\ntype DiagramViewCallable = DiagramView;\nexport {\n DiagramView as _DiagramView,\n DiagramViewCallable as DiagramView,\n};\n"],"mappings":"+3BA2FA,IAAM,GAAqB,IAMrB,EAAsB,GAGtB,EAAmB,oCASzB,SAAS,mBAAmB,GAAG,EAAyE,CACpG,IAAM,EAAiC,CAAC,EAExC,IAAK,IAAM,KAAO,EACV,GACA,OAAO,OAAO,EAAQ,CAAG,EAIjC,OAAO,CACX,CAaA,IAAM,EAAoD,CAAE,cAAe,qCAAsC,EAgBjH,SAAS,eACL,EACA,EACO,CACP,GAAI,EAAK,UAAY,EAAK,SAAS,OAAS,EACxC,MAAO,CACH,GAAe,EAAK,GACpB,cAAe,mBAAmB,EAA2B,EAAK,aAAa,EAC/E,SAAe,EAAK,SAAS,IAAK,GAAU,eAAe,EAAO,CAAK,CAAC,CAC5E,EAGJ,IAAM,EAAO,EAAM,IAAI,EAAK,EAAE,EAE9B,MAAO,CACH,GAAe,EAAK,GACpB,MAAe,EAAK,OAAU,GAAM,OAAU,GAC9C,OAAe,EAAK,QAAU,GAAM,QAAU,EAC9C,cAAe,EAAK,cACpB,MAAe,EAAK,OAAO,IAAK,IAAO,CACnC,GAAe,EAAE,GACjB,EAAe,EAAE,EACjB,EAAe,EAAE,EACjB,MAAe,EAAE,MACjB,OAAe,EAAE,OACjB,cAAe,EAAE,OAAS,IAAA,GAA0C,IAAA,GAA9B,CAAE,gBAAiB,EAAE,IAAK,CACpE,EAAE,CACN,CACJ,CASA,IAAM,EAAqD,CAAE,wBAAyB,kBAAmB,EAiBzG,SAAgB,cACZ,EACA,EACA,EACO,CACP,IAAM,EAAsB,EAAK,MAAM,IAAK,GAAS,eAAe,EAAM,CAAK,CAAC,EAE1E,EAA2B,EAAK,MAAM,IAAK,IAAU,CACvD,GAAS,EAAK,GACd,QAAS,CAAC,EAAK,YAAc,EAAK,MAAM,EACxC,QAAS,CAAC,EAAK,YAAc,EAAK,MAAM,CAC5C,EAAE,EAEF,MAAO,CACH,GAAe,OACf,cAAe,mBAAmB,EAA4B,EAAU,EAAK,aAAa,EAC1F,WACA,OACJ,CACJ,CAcA,SAAS,eACL,EACA,EACA,EACA,EACI,CACJ,IAAM,EAAI,GAAW,EAAK,GAAK,GACzB,EAAI,GAAW,EAAK,GAAK,GAE/B,EAAI,KAAK,CAAE,GAAI,EAAK,GAAI,IAAG,IAAG,MAAO,EAAK,OAAS,EAAG,OAAQ,EAAK,QAAU,CAAE,CAAC,EAEhF,IAAK,IAAM,KAAS,EAAK,UAAY,CAAC,EAClC,eAAe,EAAO,EAAG,EAAG,CAAG,CAEvC,CAaA,SAAS,gBAAgB,EAAe,EAAgE,CACpG,IAAK,IAAM,KAAQ,EAAK,OAAS,CAAC,EAC9B,EAAI,KAAK,CAAE,OAAM,UAAW,EAAK,WAAa,EAAK,EAAG,CAAC,EAG3D,IAAK,IAAM,KAAS,EAAK,UAAY,CAAC,EAClC,gBAAgB,EAAO,CAAG,CAElC,CAYA,SAAS,eAAe,EAA4B,EAAoC,CACpF,GAAI,EAAO,IAAM,GAAK,EAAO,IAAM,EAC/B,OAAO,EAGX,IAAM,MAAS,IAA+B,CAAE,EAAG,EAAM,EAAI,EAAO,EAAG,EAAG,EAAM,EAAI,EAAO,CAAE,GAE7F,OAAO,EAAS,IAAK,IAAa,CAC9B,WAAY,MAAM,EAAQ,UAAU,EACpC,SAAY,MAAM,EAAQ,QAAQ,EAClC,WAAY,EAAQ,YAAY,IAAI,KAAK,CAC7C,EAAE,CACN,CAeA,SAAgB,aAAa,EAAsC,CAC/D,IAAM,EAAoF,CAAC,EAE3F,IAAK,IAAM,KAAS,EAAO,UAAY,CAAC,EACpC,eAAe,EAAO,EAAG,EAAG,CAAK,EAMrC,IAAM,EAAU,IAAI,IAAsB,CAAC,CAAC,EAAO,GAAI,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,CAAC,CAAC,EAEvE,IAAK,IAAM,KAAQ,EACf,EAAQ,IAAI,EAAK,GAAI,CAAE,EAAG,EAAK,EAAG,EAAG,EAAK,CAAE,CAAC,EAGjD,IAAM,EAAiE,CAAC,EAQxE,OAPA,gBAAgB,EAAQ,CAAS,EAO1B,CACH,QACA,MAPU,EAAU,KAAK,CAAE,OAAM,gBAAiB,CAClD,GAAU,EAAK,GACf,SAAU,eAAe,EAAK,UAAY,CAAC,EAAG,EAAQ,IAAI,CAAS,GAAK,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,CAC1F,EAII,EACA,MAAQ,EAAO,OAAU,EACzB,OAAQ,EAAO,QAAU,CAC7B,CACJ,CA2CA,IAAa,gBAAb,KAA6B,CAEzB,KAAmC,KACnC,eACA,WAQA,cAAwB,GAExB,UAAoB,GASpB,YAAsB,GActB,UAAiD,KAMjD,YAAY,EAAkC,CAC1C,KAAK,eAAiB,GAAS,cAC/B,KAAK,WAAiB,GAAS,SACnC,CAsBA,MAAM,OACF,EACA,EACA,EAC4B,CAC5B,GAAI,KAAK,UACL,MAAU,MAAM,CAAgB,EAGpC,IAAM,EAAQ,cAAc,EAAM,EAAO,CAAQ,EAEjD,GAAI,CAGA,OAAO,aAAa,MAAM,MAFR,KAAK,UAAU,EAAA,CAEH,OAAO,CAAK,CAAY,CAC1D,OAAS,EAAO,CAIZ,GAAI,KAAK,WAAa,CAAC,KAAK,cACxB,MAAM,EAOV,KAAK,qBAAqB,KAAK,IAAI,EACnC,KAAK,cAAgB,GACrB,KAAK,YAAgB,GAErB,IAAM,EAAU,MAAM,KAAK,oBAAoB,EAQ/C,GAAI,KAAK,UACL,MAAU,MAAM,CAAgB,EAQpC,MALA,MAAK,KAAO,EAGZ,KAAK,UAAY,KAEV,aAAa,MAAM,EAAQ,OAAO,CAAK,CAAY,CAC9D,CACJ,CAYA,SAAgB,CACZ,KAAK,UAAY,GAEjB,KAAK,qBAAqB,KAAK,IAAI,EAEnC,KAAK,KAAO,KAGZ,KAAK,UAAY,IACrB,CAWA,MAAc,WAAkC,CA+B5C,OA9BI,KAAK,KACE,KAAK,MAOhB,KAAK,YAAc,KAAK,UAAU,CAAC,CAC9B,KAAM,GAAQ,CACX,GAAI,KAAK,UAOL,MAFA,KAAK,qBAAqB,CAAG,EAEnB,MAAM,CAAgB,EAKpC,MAFA,MAAK,KAAO,EAEL,CACX,CAAC,CAAC,CACD,MAAO,GAAmB,CAGvB,KAFA,MAAK,UAAY,KAEX,CACV,CAAC,EAEE,KAAK,UAChB,CAUA,qBAA6B,EAA+B,CACpD,GAAO,KAAK,aACZ,EAAI,gBAAgB,CAE5B,CAWA,MAAc,WAAkC,CAG5C,GAAM,CAAE,QAAS,GAAQ,MAAM,OAAO,4BAetC,OAbI,KAAK,gBACL,KAAK,cAAgB,GACrB,KAAK,YAAgB,GAEd,IAAI,EAAI,CAAE,cAAe,KAAK,cAAe,CAAC,GAGrD,KAAK,YACL,KAAK,cAAgB,GAEd,IAAI,EAAI,CAAE,UAAW,KAAK,UAAW,CAAC,GAG1C,IAAI,CACf,CAQA,MAAc,qBAA4C,CACtD,GAAM,CAAE,QAAS,GAAQ,MAAM,OAAO,4BAEtC,OAAO,IAAI,CACf,CACJ,ECvkBM,EAA0D,CAG5D,OAAQ,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,EAC7B,gBAAiB,2EACjB,OAAc,0DACd,aAAc,MACd,OAAc,SAClB,EAMM,EAAgB,GAUhB,YAAN,cAA0B,CAA0B,CAGhD,OAGA,SAGA,OAKA,IAAY,mBAA+B,CACvC,MAAO,MAAK,qBAAuB,KAAK,gBAAgB,WAAW,CACvE,CAQA,YAAY,EAA8B,EAAgD,CACtF,MAAM,EAAS,CACX,GAAG,EACH,cAAe,IAAI,EACnB,GAAI,GAAoB,CAAC,CAC7B,CAAC,EAED,KAAK,kBAAkB,IAAI,cAAmB,8CAA8C,EAC5F,KAAK,kBAAkB,IAAI,kBAAmB,kGAAkG,EAKhJ,KAAK,aAAa,KAAK,SAAS,MAAO,KAAK,SAAS,OAAS,GAAI,KAAK,SAAS,KAAK,EAEjF,KAAK,SAAS,WAAa,IAAA,IAC3B,KAAK,YAAY,KAAK,SAAS,QAAQ,CAE/C,CAQA,aAAuB,EAAmC,CAQtD,OAPA,MAAM,aAAa,CAAO,EAEtB,EAAQ,QAAa,IAAA,KAAW,KAAK,SAAS,MAAW,EAAQ,OACjE,EAAQ,QAAa,IAAA,KAAW,KAAK,SAAS,MAAW,EAAQ,OACjE,EAAQ,QAAa,IAAA,KAAW,KAAK,SAAS,MAAW,EAAQ,OACjE,EAAQ,WAAa,IAAA,KAAW,KAAK,SAAS,SAAW,EAAQ,UAE9D,IACX,CAWA,aAAqB,EAA2B,EAAe,EAAiC,CACxF,KAAK,UACL,KAAK,gBAAgB,KAAK,QAAQ,EAGtC,KAAK,OAAS,IAAU,IAAA,GAAyC,IAAI,EAAK,CAAK,EAA3C,IAAI,EAAS,EAAO,CAAK,EAEzD,IAAU,IAAA,IACV,KAAK,OAAS,IAAA,GACd,KAAK,SAAW,KAAK,SAErB,KAAK,OAAS,IAAI,EAAK,CAAK,EAC5B,KAAK,OAAO,WAAW,CAAa,EACpC,KAAK,SAAW,IAAI,EAAU,CAAE,cAAe,IAAI,EAAQ,WAAY,CAAC,KAAK,OAAQ,KAAK,MAAM,CAAE,CAAC,GAYvG,KAAK,SAAS,iBAAiB,MAAM,EAErC,KAAK,aAAa,KAAK,QAAQ,CACnC,CASA,SAAS,EAAqB,CAI1B,MAHA,MAAK,SAAS,MAAQ,EACtB,KAAK,OAAO,QAAQ,CAAK,EAElB,IACX,CAOA,UAA0B,CACtB,OAAO,KAAK,SAAS,OAAS,IAClC,CAOA,UAA0B,CACtB,OAAO,KAAK,SAAS,OAAS,IAClC,CASA,YAAY,EAAsB,CAC9B,KAAK,SAAS,SAAW,EAEzB,IAAM,EAAU,KAAK,WAAW,EAMhC,OAJI,GACA,EAAI,KAAK,MAAM,EAAS,CAAE,YAAa,CAAE,SAAU,CAAM,CAAE,CAAC,EAGzD,IACX,CAOA,YAAsB,CAClB,OAAO,KAAK,SAAS,UAAY,EACrC,CAUA,KAAe,EAAwB,CACnC,MAAM,KAAK,CAAO,EAElB,IAAM,EAAK,GAAW,KAAK,WAAW,EAMtC,OAJI,GAAM,KAAK,WAAW,GACtB,EAAI,KAAK,MAAM,EAAI,CAAE,YAAa,CAAE,SAAU,EAAK,CAAE,CAAC,EAGnD,IACX,CACJ,EAEM,EAAsB,EAAS,WAAW,ECpN1C,EAAe,EAEf,EAAoE,CACtE,gBAAiB,2DAIjB,OAAc,UACd,OAAc,6FACd,aAAc,KAClB,EAYM,iBAAN,cAA+B,CAA+B,CAG1D,QAEA,YAAY,EAAmC,EAAqD,CAChG,MAAM,EAAS,CACX,cAAe,IAAI,EACnB,GAAG,EACH,GAAI,GAAoB,CAAC,CAC7B,CAAC,EAMD,IAAM,EAAQ,KAAK,SAAS,OAAS,GACrC,KAAK,QAAU,KAAK,SAAS,QAAU,IAAA,GAEjC,IAAI,EAAK,CAAK,EADd,IAAI,EAAS,KAAK,SAAS,MAAO,CAAK,EAE7C,KAAK,QAAQ,iBAAiB,MAAM,EACpC,KAAK,QAAQ,KAAK,CAAY,EAC9B,KAAK,QAAQ,KAAK,CAAY,EAC9B,KAAK,aAAa,KAAK,OAAO,CAClC,CAQA,aAAuB,EAAwC,CAM3D,OALA,MAAM,aAAa,CAAO,EAEtB,EAAQ,QAAU,IAAA,KAAW,KAAK,SAAS,MAAQ,EAAQ,OAC3D,EAAQ,QAAU,IAAA,KAAW,KAAK,SAAS,MAAQ,EAAQ,OAExD,IACX,CASA,SAAS,EAAqB,CAI1B,MAHA,MAAK,SAAS,MAAQ,EACtB,KAAK,QAAQ,QAAQ,CAAK,EAEnB,IACX,CAOA,UAA0B,CACtB,OAAO,KAAK,SAAS,OAAS,IAClC,CACJ,EAEM,EAA2B,EAAS,gBAAgB,ECnGpD,EAAS,6BAGT,EAAc,2EAGd,EAAoB,MAGpB,EAAkB,KAQlB,EAAmB,qEACnB,EAAmB,IAGnB,EAAa,EAGb,EAAa,MASb,EAAiB,GAGjB,EAAqB,EAAiB,EAkBtC,EAAsB,OAOtB,EACF,CAAC,MAAO,YAAa,YAAa,YAAY,EAyB5C,EAA+E,CAEjF,IAAK,CACD,MAAO,GAAI,OAAQ,GAAI,KAAM,GAAI,KAAM,EACvC,MAAO,CAAC,CAAE,IAAK,OAAQ,MAAO,CAAE,EAAG,2BAA4B,CAAE,CAAC,CACtE,EAEA,UAAW,CACP,MAAO,GAAI,OAAQ,GAAI,KAAM,GAAI,KAAM,EACvC,MAAO,CACH,CAAE,IAAK,OAAQ,MAAO,CAAE,EAAG,gBAAiB,CAAE,EAC9C,CAAE,IAAK,SAAU,MAAO,CAAE,GAAI,IAAK,GAAI,IAAK,EAAG,GAAI,CAAE,CACzD,CACJ,EAEA,UAAW,CACP,MAAO,GAAI,OAAQ,GAAI,KAAM,GAAI,KAAM,EACvC,MAAO,CACH,CAAE,IAAK,OAAQ,MAAO,CAAE,EAAG,yCAA0C,CAAE,EACvE,CAAE,IAAK,OAAQ,MAAO,CAAE,EAAG,gBAAiB,CAAE,CAClD,CACJ,EAEA,WAAY,CACR,MAAO,GAAI,OAAQ,GAAI,KAAM,GAAI,KAAM,EACvC,MAAO,CACH,CAAE,IAAK,OAAQ,MAAO,CAAE,EAAG,yCAA0C,CAAE,EACvE,CAAE,IAAK,SAAU,MAAO,CAAE,GAAI,IAAK,GAAI,IAAK,EAAG,GAAI,CAAE,CACzD,CACJ,CACJ,EAea,EACT,KAAK,IAAI,EAAY,GAAG,OAAO,OAAO,CAAe,CAAC,CAAC,IAAI,GAAY,EAAS,KAAK,CAAC,EAwB1F,SAAS,WAAW,EAAsC,CACtD,IAAM,EAAU,EAAS,GAQzB,OANK,EAME,cAAc,CAFL,EAAQ,WAAY,GAAI,EAAQ,YAAc,CAAC,EAAI,EAAQ,QAEtD,CAAM,EALhB,CAAE,EAAG,EAAG,EAAG,CAAE,CAM5B,CASA,SAAS,cAAc,EAA8B,CACjD,IAAM,eAAiB,EAAa,IAAwB,KAAK,MAAM,EAAE,EAAI,EAAE,EAAG,EAAE,EAAI,EAAE,CAAC,EAEvF,EAAQ,EAEZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAC/B,GAAS,cAAc,EAAO,EAAI,GAAI,EAAO,EAAE,EAGnD,IAAI,EAAY,EAAQ,EAExB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAAK,CACpC,IAAM,EAAS,cAAc,EAAO,EAAI,GAAI,EAAO,EAAE,EAErD,GAAI,GAAU,EAAW,CACrB,IAAM,EAAI,IAAW,EAAI,EAAI,EAAY,EAEzC,MAAO,CACH,EAAG,EAAO,EAAI,EAAE,CAAC,GAAK,EAAO,EAAE,CAAC,EAAI,EAAO,EAAI,EAAE,CAAC,GAAK,EACvD,EAAG,EAAO,EAAI,EAAE,CAAC,GAAK,EAAO,EAAE,CAAC,EAAI,EAAO,EAAI,EAAE,CAAC,GAAK,CAC3D,CACJ,CAEA,GAAa,CACjB,CAEA,OAAO,EAAO,EAAO,OAAS,EAClC,CAUA,SAAS,cAAc,EAAoC,CACvD,IAAM,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAW,EAAU,CAC5B,EAAM,KAAK,KAAK,EAAQ,WAAW,EAAE,GAAG,EAAQ,WAAW,GAAG,EAE9D,IAAK,IAAM,KAAQ,EAAQ,YAAc,CAAC,EACtC,EAAM,KAAK,KAAK,EAAK,EAAE,GAAG,EAAK,GAAG,EAGtC,EAAM,KAAK,KAAK,EAAQ,SAAS,EAAE,GAAG,EAAQ,SAAS,GAAG,CAC9D,CAEA,OAAO,EAAM,KAAK,GAAG,CACzB,CAcA,SAAS,kBAAkB,EAAY,EAAY,EAAY,EAAY,EAAY,EAAoB,CACvG,IAAM,EAAK,EAAK,EACV,EAAK,EAAK,EACV,EAAgB,EAAK,EAAK,EAAK,EAErC,GAAI,IAAkB,EAClB,OAAO,KAAK,MAAM,EAAK,EAAI,EAAK,CAAE,EAGtC,IAAM,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,EAAK,GAAM,GAAM,EAAK,GAAM,GAAM,CAAa,CAAC,EAEpF,OAAO,KAAK,MAAM,GAAM,EAAK,EAAI,GAAK,GAAM,EAAK,EAAI,EAAG,CAC5D,CAYA,SAAS,gBAAgB,EAA4B,EAAW,EAAmB,CAC/E,IAAI,EAAM,IAEV,IAAK,IAAM,KAAW,EAAU,CAC5B,IAAM,EAAS,CAAC,EAAQ,WAAY,GAAI,EAAQ,YAAc,CAAC,EAAI,EAAQ,QAAQ,EAEnF,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAC/B,EAAM,KAAK,IAAI,EAAK,kBAAkB,EAAG,EAAG,EAAO,EAAI,EAAE,CAAC,EAAG,EAAO,EAAI,EAAE,CAAC,EAAG,EAAO,EAAE,CAAC,EAAG,EAAO,EAAE,CAAC,CAAC,CAAC,CAE/G,CAEA,OAAO,CACX,CAuBA,IAAM,iBAAN,cAA+B,CAA4B,CAGvD,OAAqC,CAAC,EAGtC,OAA8B,CAAC,EAO/B,cAAqC,IAAI,IAQzC,UAGA,aAGA,UAGA,oBAEA,YAAY,EAA4B,CACpC,MAAM,CAAO,EAEb,KAAK,UAAY,GAAG,KAAK,MAAM,EAAE,QACjC,KAAK,oBAAsB,OAAO,YAC9B,EAAmB,IAAI,GAAQ,CAAC,EAAM,GAAG,KAAK,MAAM,EAAE,GAAG,GAAM,CAAC,CACpE,EAGA,KAAK,iBAAiB,MAAM,EAO5B,KAAK,UAAU,SAAS,CAC5B,CASA,UAAkB,EAAyD,CAClE,KAML,MAAO,QAFI,IAAS,QAAU,KAAK,UAAY,KAAK,oBAAoB,GAEtD,EACtB,CAwBA,SAAS,EAAiC,CAUtC,MATA,MAAK,OAAS,EACd,KAAK,cAAgB,IAAI,IAErB,KAAK,WAAW,EAChB,KAAK,aAAa,EAElB,KAAK,kBAAoB,KAAK,aAAa,CAAC,EAGzC,IACX,CAYA,gBAAgB,EAAqC,CAYjD,MAXA,MAAK,cAAgB,IAAI,IAAI,GAAO,CAAC,CAAC,EAStC,KAAK,aAAa,EAEX,IACX,CAOA,iBAA4B,CACxB,MAAO,CAAC,GAAG,KAAK,aAAa,CACjC,CAQA,SAAS,EAA2C,CAChD,GAAI,IAAW,KACX,OAAO,KAGX,IAAM,EAAS,EAAI,OAAO,OAAO,CAAM,EAEvC,OAAO,KAAK,OAAO,KAAK,GAAK,EAAE,MAAQ,CAAM,CAAC,EAAE,IAAM,IAC1D,CAWA,UAAU,EAAW,EAA+B,CAChD,OAAO,KAAK,OACP,OAAO,GAAS,gBAAgB,EAAM,MAAM,SAAU,EAAG,CAAC,GAAK,CAAkB,CAAC,CAClF,IAAI,GAAS,EAAM,KAAK,CACjC,CASA,SAAiB,EAAoB,CAKjC,OAJI,KAAK,cAAc,OAAS,GAAK,KAAK,cAAc,IAAI,CAAE,EACnD,KAAK,aAGT,KAAK,SAChB,CASA,mBAAsC,CAClC,IAAM,EAAO,EAAI,KAAK,gBAAgB,EAAQ,KAAK,EAC7C,EAAO,EAAI,KAAK,gBAAgB,EAAQ,MAAM,EAEpD,KAAK,kBAAkB,CAAI,EAE3B,IAAK,IAAM,KAAQ,EACf,KAAK,sBAAsB,EAAM,CAAI,EASzC,OANA,EAAI,KAAK,YAAY,EAAK,CAAI,EAC9B,KAAK,YAAY,CAAI,EAErB,KAAK,UAAe,KAAK,gBAAgB,EAAK,CAAmB,EACjE,KAAK,aAAe,KAAK,gBAAgB,EAAK,IAAI,EAE3C,CACX,CAYA,gBAAwB,EAAa,EAAgC,CACjE,IAAM,EAAQ,EAAI,KAAK,gBAAgB,EAAQ,GAAG,EASlD,OAPI,IAAY,MACZ,EAAI,KAAK,MAAM,EAAO,CAAE,QAAS,CAAE,SAAQ,CAAE,CAAC,EAGlD,EAAI,KAAK,YAAY,EAAK,CAAK,EAC/B,KAAK,YAAY,CAAK,EAEf,CACX,CAUA,kBAA0B,EAAoB,CAC1C,IAAM,EAAS,EAAI,KAAK,gBAAgB,EAAQ,QAAQ,EAExD,EAAI,KAAK,MAAM,EAAQ,CAAE,QAAS,CAC9B,GAAc,KAAK,UACnB,YAAc,OAAO,CAAU,EAC/B,aAAc,OAAO,CAAU,EAC/B,KAAc,OAAO,CAAU,EAC/B,KAAc,OAAO,EAAa,CAAC,EACnC,OAAc,qBACd,YAAc,gBAClB,CAAE,CAAC,EAEH,IAAM,EAAQ,EAAI,KAAK,gBAAgB,EAAQ,MAAM,EACrD,EAAI,KAAK,MAAM,EAAO,CAAE,QAAS,CAC7B,EAAM,WAAW,EAAW,GAAG,EAAa,EAAE,OAAO,EAAW,IAChE,KAAM,CACV,CAAE,CAAC,EAEH,EAAI,KAAK,YAAY,EAAQ,CAAK,EAClC,EAAI,KAAK,YAAY,EAAM,CAAM,EAEjC,KAAK,YAAY,CAAM,EACvB,KAAK,YAAY,CAAK,CAC1B,CAWA,sBAA8B,EAAc,EAAiD,CACzF,IAAM,EAAW,EAAgB,GAC3B,EAAW,EAAI,KAAK,gBAAgB,EAAQ,QAAQ,EAE1D,EAAI,KAAK,MAAM,EAAQ,CAAE,QAAS,CAC9B,GAAc,KAAK,oBAAoB,GACvC,YAAc,OAAO,EAAS,KAAK,EACnC,aAAc,OAAO,EAAS,MAAM,EACpC,KAAc,OAAO,EAAS,IAAI,EAClC,KAAc,OAAO,EAAS,IAAI,EAClC,OAAc,qBACd,YAAc,gBAClB,CAAE,CAAC,EAEH,EAAI,KAAK,YAAY,EAAM,CAAM,EACjC,KAAK,YAAY,CAAM,EAEvB,IAAK,IAAM,KAAQ,EAAS,MAAO,CAC/B,IAAM,EAAQ,EAAI,KAAK,gBAAgB,EAAQ,EAAK,GAAG,EAEvD,EAAI,KAAK,MAAM,EAAO,CAAE,QAAS,CAC7B,GAAG,EAAK,MACR,KAAQ,OACR,OAAQ,CACZ,CAAE,CAAC,EAEH,EAAI,KAAK,YAAY,EAAQ,CAAK,EAClC,KAAK,YAAY,CAAK,CAC1B,CACJ,CAOA,QAA2B,CACvB,IAAM,EAAU,MAAM,OAAO,EAI7B,OAFA,KAAK,aAAa,EAEX,CACX,CAMA,cAA6B,CACb,QAAK,WAEZ,EAIL,KAAK,IAAM,KAAS,KAAK,OACrB,KAAK,iBAAiB,CAAK,EAG/B,KAAK,OAAS,CAAC,EAEf,IAAK,IAAM,KAAQ,KAAK,OAAQ,CAC5B,IAAM,EAAI,cAAc,EAAK,QAAQ,EAErC,GAAI,CAAC,EACD,SAGJ,IAAM,EAAQ,KAAK,SAAS,EAAK,EAAE,EAC7B,EAAQ,KAAK,YAAY,EAAO,CAAC,EACjC,EAAQ,KAAK,gBAAgB,EAAO,EAAM,CAAC,EAC3C,EAAQ,EAAK,MACf,EAAuB,KAEvB,GAAO,QACP,EAAQ,KAAK,UAAU,EAAO,EAAM,EAAM,KAAK,GAGnD,KAAK,OAAO,KAAK,CAAE,GAAI,EAAK,GAAI,MAAO,EAAM,OAAM,MAAK,QAAO,OAAM,CAAC,CAC1E,CAvB+B,CAwBnC,CAYA,YAAoB,EAAe,EAAmB,CAClD,IAAM,EAAM,EAAI,KAAK,gBAAgB,EAAQ,MAAM,EAiBnD,OAfA,EAAI,KAAK,MAAM,EAAK,CAAE,QAAS,CAC3B,IACA,KAAkB,OAClB,OAAkB,cAClB,eAAkB,OAAO,CAAc,EACvC,iBAAkB,SAIlB,OAAQ,SACZ,CAAE,CAAC,EAEH,EAAI,KAAK,YAAY,EAAO,CAAG,EAC/B,KAAK,YAAY,CAAG,EAEb,CACX,CAYA,gBAAwB,EAAe,EAAwB,EAAmB,CAC9E,IAAM,EAAQ,EAAI,KAAK,gBAAgB,EAAQ,MAAM,EAC/C,EAAQ,EAAK,MAMb,EAAc,EAAQ,KAAK,UAAU,EAAM,SAAS,EAAI,KAAK,UAAU,OAAO,EAC9E,EAAc,EAAQ,KAAK,UAAU,EAAM,WAAW,EAAI,IAAA,GAE1D,EAAgC,CAClC,IACA,KAAgB,OAChB,OAAgB,GAAO,QAAU,EACjC,eAAgB,CACpB,EAmBA,OAjBI,IACA,EAAM,cAAgB,GAGtB,IACA,EAAM,gBAAkB,GAGxB,GAAO,SACP,EAAM,oBAAsB,GAGhC,EAAI,KAAK,MAAM,EAAM,CAAE,QAAS,CAAM,CAAC,EAEvC,EAAI,KAAK,YAAY,EAAO,CAAI,EAChC,KAAK,YAAY,CAAI,EAEd,CACX,CASA,iBAAyB,EAAwB,CAC7C,IAAM,EAAU,CAAC,EAAM,IAAK,EAAM,KAAM,GAAI,EAAM,MAAQ,CAAC,EAAM,KAAK,EAAI,CAAC,CAAE,EAE7E,IAAK,IAAM,KAAU,EACjB,EAAI,KAAK,YAAY,EAAM,MAAO,CAAM,EACxC,KAAK,cAAc,CAAM,EACzB,EAAI,KAAK,QAAQ,CAAM,CAE/B,CAWA,UAAkB,EAAe,EAAwB,EAAuB,CAC5E,IAAM,EAAQ,WAAW,EAAK,QAAQ,EAChC,EAAQ,EAAI,KAAK,gBAAgB,EAAQ,MAAM,EAuBrD,OArBA,EAAI,KAAK,MAAM,EAAM,CACjB,QAAS,CACL,EAAkB,OAAO,EAAM,CAAC,EAChC,EAAkB,OAAO,EAAM,CAAC,EAChC,KAAkB,EAGlB,OAAkB,EAClB,eAAkB,EAClB,kBAAmB,QACnB,cAAkB,SAClB,YAAkB,EAClB,cAAkB,SAClB,oBAAqB,SACzB,EACA,KAAM,CACV,CAAC,EAED,EAAI,KAAK,YAAY,EAAO,CAAI,EAChC,KAAK,YAAY,CAAI,EAEd,CACX,CACJ,EAEM,EAA2B,EAAS,gBAAgB,ECjwB1D,EAAM,SAAS,EAAM,EAAO,EAAQ,CAAU,EAS9C,IAAM,EAAe,EAGf,EAAmB,IAGnB,EAAmB,EAGnB,EAAkB,IAGlB,EAAmB,IAkBnB,EAAsB,IAKtB,GAAwB,GAKxB,GAAkB,GAWlB,GAAkB,EAClB,GAAoB,EACpB,GAAqB,EACrB,GAAe,EAiFf,YAAN,cAA0B,CAA0B,CAGhD,aAGA,WAGA,QAGA,gBAAkD,IAAI,IAGtD,UAAkD,IAAI,IAGtD,cAAqC,IAAI,IASzC,oBAAuD,IAAI,IAC3D,cAA6D,IAAI,IACjE,sBAA6C,IAAI,IAGjD,WAAwC,CAAC,EASzC,cAAqC,IAAI,IAGzC,kBAAoC,EAGpC,eAAiF,KAMjF,aAA+C,KAG/C,YAA+B,EAC/B,aAA+B,EAG/B,WAAoD,IAAI,EAGxD,gBAAyC,KAGzC,SAA4B,GAC5B,WAA6B,EAC7B,WAA6B,EAU7B,QAA0B,EAC1B,QAA0B,EAC1B,cAAiC,GAGjC,YAA8B,EAC9B,YAA8B,EAG9B,MAAwB,EACxB,MAAwB,EAOxB,oBAAuC,GASvC,aAAsC,KAOtC,mBAAsC,IACtC,oBAAsC,IAGtC,UACA,WACA,YACA,QACA,UAEA,cAAgD,KAAK,OAAO,EAC5D,eAAgD,KAAK,QAAQ,EAC7D,WAAgD,KAAK,UAAU,EAC/D,aAAgD,KAAK,UAAU,EAE/D,YAAY,EAA8B,CACtC,MAAM,EAAS,CAAE,KAAM,EAAc,QAAS,EAAkB,QAAS,EAAkB,SAAU,EAAK,CAAC,EAE3G,KAAK,iBAAiB,IAAI,CAAQ,EAClC,KAAK,UAAU,MAAM,EAErB,KAAK,QAAU,KAAK,aAAa,EAajC,KAAK,aAAe,IAAI,EAAU,CAAE,cAAe,IAAI,EAAY,SAAU,UAAW,OAAQ,SAAU,CAAC,EAC3G,KAAK,aAAa,mBAAmB,KAAK,EAC1C,KAAK,aAAa,KAAK,YAAY,EAEnC,KAAK,WAAa,IAAI,EACtB,KAAK,aAAa,aAAa,KAAK,UAAU,EAE9C,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAE1B,KAAK,aAAa,KAAK,UAAW,KAAK,UAAU,qBAAqB,CAAC,EAEvE,KAAK,eAAe,GAAS,SAAS,EAEtC,KAAK,mBAAmB,KAAK,SAAS,UAAY,KAAK,gBAAgB,UAAY,EAAI,EAEnF,KAAK,SAAS,OAAS,IAAA,IACvB,KAAK,QAAQ,KAAK,SAAS,IAAI,EAGnC,KAAK,aAAe,KAAK,SAAS,kBAAoB,KAElD,KAAK,SAAS,MACd,KAAK,QAAQ,KAAK,SAAS,IAAI,CAEvC,CASA,cAA0C,CACtC,OAAO,IAAI,gBAAgB,CACvB,cAAe,KAAK,SAAS,iBAC7B,UAAe,KAAK,SAAS,YACjC,CAAC,CACL,CAMA,YAA6B,CAKzB,KAAK,mBAAqB,EAK1B,KAAK,aAAa,EAIlB,KAAK,cAAc,QAAQ,EAC3B,KAAK,aAAe,KAEpB,KAAK,QAAQ,QAAQ,EAErB,MAAM,WAAW,CACrB,CAQA,aAAuB,EAAmC,CAoBtD,OAnBA,MAAM,aAAa,CAAO,EAEtB,EAAQ,OAAsB,IAAA,KAAW,KAAK,SAAS,KAAoB,EAAQ,MACnF,EAAQ,eAAsB,IAAA,KAAW,KAAK,SAAS,aAAoB,EAAQ,cACnF,EAAQ,gBAAsB,IAAA,KAAW,KAAK,SAAS,cAAoB,EAAQ,eACnF,EAAQ,gBAAsB,IAAA,KAAW,KAAK,SAAS,cAAoB,EAAQ,eACnF,EAAQ,eAAsB,IAAA,KAAW,KAAK,SAAS,aAAoB,EAAQ,cACnF,EAAQ,mBAAsB,IAAA,KAAW,KAAK,SAAS,iBAAoB,EAAQ,kBACnF,EAAQ,UAAsB,IAAA,KAAW,KAAK,SAAS,QAAoB,EAAQ,SACnF,EAAQ,UAAsB,IAAA,KAAW,KAAK,SAAS,QAAoB,EAAQ,SACnF,EAAQ,OAAsB,IAAA,KAAW,KAAK,SAAS,KAAoB,EAAQ,MAInF,EAAQ,WAAmB,IAAA,KAAW,KAAK,SAAS,SAAe,EAAQ,UAG3E,EAAQ,mBAAqB,IAAA,KAAW,KAAK,SAAS,iBAAmB,EAAQ,kBAE9E,IACX,CASA,QAAQ,EAAyB,CAK7B,MAJA,MAAK,SAAS,KAAO,EACrB,KAAK,aAAa,CAAI,EACtB,KAAK,SAAS,CAAI,EAEX,IACX,CAOA,SAA8B,CAC1B,OAAO,KAAK,SAAS,MAAQ,IACjC,CAwBA,aAAqB,EAAyB,CAC1C,KAAK,qBAAqB,EAE1B,IAAM,EAAW,KAAK,SAAS,eAAkB,GAC7C,IAAI,EAAY,CAAE,MAAO,EAAK,MAAO,MAAO,EAAK,MAAO,MAAO,EAAK,KAAM,CAAC,GACzE,EAAgB,KAAK,SAAS,gBAAmB,GACnD,IAAI,EAAiB,CAAE,MAAO,EAAK,MAAO,MAAO,EAAK,KAAM,CAAC,GAE3D,MAAS,GAAmC,CAC9C,IAAK,IAAM,KAAQ,EAAO,CACtB,IAAM,GAAe,EAAK,UAAU,QAAU,GAAK,EAC7C,EAAY,EAAc,EAAc,CAAI,EAAI,EAAS,CAAI,EAEnE,EAAU,WAAW,EAAK,EAE1B,KAAK,oBAAoB,IAAI,EAAK,GAAI,CAAS,EAC/C,KAAK,cAAc,IAAI,EAAK,GAAI,CAAI,EAEhC,IACA,KAAK,sBAAsB,IAAI,EAAK,EAAE,EACtC,MAAM,EAAK,QAAS,EAE5B,CACJ,EAEA,MAAM,EAAK,KAAK,CACpB,CAGA,sBAAqC,CACjC,KAAK,oBAAoB,MAAM,EAC/B,KAAK,cAAc,MAAM,EACzB,KAAK,sBAAsB,MAAM,CACrC,CAWA,sBAAqC,CACjC,IAAK,IAAM,KAAa,KAAK,gBAAgB,OAAO,EAChD,KAAK,aAAa,gBAAgB,CAAS,EAC3C,EAAU,QAAQ,EAGtB,KAAK,gBAAmB,KAAK,oBAC7B,KAAK,UAAmB,KAAK,cAC7B,KAAK,cAAmB,KAAK,sBAE7B,KAAK,oBAAuB,IAAI,IAChC,KAAK,cAAuB,IAAI,IAChC,KAAK,sBAAwB,IAAI,IAEjC,KAAK,WAAa,CAAC,EACnB,KAAK,cAAgB,IAAI,IAEzB,IAAK,IAAM,KAAa,KAAK,gBAAgB,OAAO,EAChD,KAAK,aAAa,aAAa,CAAS,EACxC,EAAU,WAAW,EAAI,CAEjC,CASA,SAAiB,EAAyB,CACtC,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EAEvB,IAAM,EAAQ,KAAK,iBAAiB,CAAI,EAExC,KAAK,mBAAqB,EAC1B,IAAM,EAAa,KAAK,kBAExB,KAAK,QACA,OAAO,EAAM,EAAO,KAAK,SAAS,aAAa,CAAC,CAChD,KAAM,GAAW,KAAK,YAAY,EAAQ,CAAU,CAAC,CAAC,CACtD,UAAY,KAAK,oBAAoB,CAAU,CAAC,CACzD,CAQA,kBAAiC,CAC7B,GAAI,KAAK,iBAAmB,KACxB,OAGJ,IAAI,EACE,EAAU,IAAI,QAAe,GAAM,CAAE,EAAU,CAAG,CAAC,EAEzD,KAAK,eAAiB,CAAE,UAAS,SAAQ,CAC7C,CAGA,cAA6B,CACzB,IAAM,EAAU,KAAK,eAErB,KAAK,eAAiB,KACtB,GAAS,QAAQ,EAEjB,KAAK,kBAAkB,CAC3B,CAcA,mBAAkC,CAC9B,GAAI,KAAK,iBAAmB,MAAQ,EAAE,KAAK,SAAS,EAAI,IAAM,EAAE,KAAK,UAAU,EAAI,GAAI,CACnF,KAAK,cAAc,YAAY,EAE/B,MACJ,CAEI,KAAK,eAAiB,OACtB,KAAK,aAAe,IAAI,EAAgB,EAAqB,GAOjE,KAAK,aAAa,YAAY,IAAI,EAClC,KAAK,aAAa,SAAS,CAC/B,CAWA,aAA6B,CACzB,OAAO,KAAK,gBAAgB,SAAW,QAAQ,QAAQ,CAC3D,CAgBA,iBAAyB,EAAmE,CACxF,IAAM,EAAQ,IAAI,IAEZ,QAAW,GAAmC,CAChD,IAAK,IAAM,KAAQ,EAAO,CAEtB,IAAM,EADY,KAAK,oBAAoB,IAAI,EAAK,EAClC,CAAA,EAAW,iBAAiB,EAE9C,EAAM,IAAI,EAAK,GAAI,CACf,MAAQ,EAAK,OAAU,GAAW,OAAU,EAC5C,OAAQ,EAAK,QAAU,GAAW,QAAU,CAChD,CAAC,EAEG,EAAK,UAAY,EAAK,SAAS,OAAS,GACxC,QAAQ,EAAK,QAAQ,CAE7B,CACJ,EAIA,OAFA,QAAQ,EAAK,KAAK,EAEX,CACX,CAWA,YAAoB,EAA6B,EAA0B,CACnE,OAAe,KAAK,kBAIxB,KAAK,IAAM,KAAQ,EAAO,MAAO,CAC7B,IAAM,EAAY,KAAK,oBAAoB,IAAI,EAAK,EAAE,EAElD,IACA,EAAU,iBAAiB,CAAE,MAAO,EAAK,MAAO,OAAQ,EAAK,MAAO,CAAC,EACrE,EAAU,KAAK,EAAK,CAAC,EACrB,EAAU,KAAK,EAAK,CAAC,EAE7B,CAEA,KAAK,YAAe,EAAO,MAC3B,KAAK,aAAe,EAAO,OAE3B,KAAK,qBAAqB,EAE1B,KAAK,WAAW,KAAK,CAAC,EACtB,KAAK,WAAW,KAAK,CAAC,EACtB,KAAK,WAAW,iBAAiB,CAAE,MAAO,EAAO,MAAO,OAAQ,EAAO,MAAO,CAAC,EAC/E,KAAK,WAAW,SAAS,KAAK,eAAe,EAAO,KAAK,CAAC,EAE1D,KAAK,qBAAqB,EAI1B,KAAK,aAAa,iBAAiB,CAAE,MAAO,EAAO,MAAO,OAAQ,EAAO,MAAO,CAAC,EACjF,KAAK,qBAAqB,EAM1B,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEpB,KAAK,KAAK,QAAQ,EAElB,KAAK,aAAa,CA7BlB,CA8BJ,CAWA,sBAAqC,CACjC,GAAI,KAAK,cAAc,OAAS,EAAG,CAC/B,KAAK,WAAW,UAAU,EAAe,EAEzC,MACJ,CAEA,KAAK,WAAW,UAAU,EAAkB,EAE5C,IAAK,GAAM,CAAC,EAAI,KAAc,KAAK,gBAC/B,EAAU,UAAU,KAAK,cAAc,IAAI,CAAE,EAAI,GAAoB,EAAY,CAEzF,CAWA,eAAuB,EAA+C,CAClE,IAAM,EAAY,IAAI,IAAI,KAAK,SAAS,MAAM,MAAM,IAAI,GAAK,CAAC,EAAE,GAAI,CAAC,CAAC,GAAK,CAAC,CAAC,EAE7E,OAAO,EAAM,IAAI,IAAS,CAAE,GAAG,EAAM,MAAO,EAAU,IAAI,EAAK,EAAE,CAAC,EAAE,KAAM,EAAE,CAChF,CAUA,oBAA4B,EAA0B,CAC9C,IAAe,KAAK,oBAIxB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EACtB,CAQA,sBAAqC,CACjC,IAAM,EAAO,KAAK,QAAQ,EAE1B,KAAK,aAAa,aAAa,aAAa,KAAK,MAAM,MAAM,KAAK,MAAM,YAAY,EAAK,EAAE,CAC/F,CAOA,SAAkB,CACd,OAAO,KAAK,SAAS,MAAQ,KAAK,gBAAgB,MAAQ,CAC9D,CAgBA,QAAQ,EAAoB,CAQxB,OAPK,OAAO,SAAS,CAAI,GAIzB,KAAK,SAAS,KAAO,KAAK,UAAU,CAAI,EACxC,KAAK,qBAAqB,EAEnB,MANI,IAOf,CASA,WAAkB,CACd,GAAI,KAAK,aAAe,GAAK,KAAK,cAAgB,EAC9C,OAAO,KAGX,IAAM,EAAQ,KAAK,SAAS,EAAK,KAAK,YAChC,EAAQ,KAAK,UAAU,EAAI,KAAK,aAKtC,OAHA,KAAK,QAAQ,KAAK,IAAI,EAAO,CAAK,CAAC,EACnC,KAAK,YAAY,EAEV,IACX,CAYA,WAAkB,CAMd,OALA,KAAK,QAAQ,KAAK,gBAAgB,MAAQ,CAAY,EAEtD,KAAK,oBAAsB,GAC3B,KAAK,iBAAiB,EAEf,IACX,CAQA,QAAe,CAGX,OAFA,KAAK,uBAAuB,EAAkB,KAAK,SAAS,EAAI,EAAG,KAAK,UAAU,EAAI,CAAC,EAEhF,IACX,CAQA,SAAgB,CAGZ,OAFA,KAAK,uBAAuB,EAAI,EAAkB,KAAK,SAAS,EAAI,EAAG,KAAK,UAAU,EAAI,CAAC,EAEpF,IACX,CAwBA,kBAAiC,CAC7B,GAAI,CAAC,KAAK,qBAAuB,EAAE,KAAK,YAAc,IAAM,EAAE,KAAK,aAAe,GAC9E,OAGJ,IAAM,EAAQ,KAAK,eAAiB,MAAQ,KAAK,gBAAgB,IAAI,KAAK,YAAY,EAChF,KAAK,aACL,MAEF,IAAU,KAAgC,KAAK,YAAY,EAA1C,KAAK,WAAW,CAAK,KACtC,KAAK,oBAAsB,GAEnC,CAaA,aAA+B,CAC3B,IAAM,EAAK,KAAK,SAAS,EACnB,EAAK,KAAK,UAAU,EAE1B,GAAI,EAAE,EAAK,IAAM,EAAE,EAAK,GACpB,MAAO,GAGX,IAAM,EAAO,KAAK,QAAQ,EAO1B,MALA,MAAK,OAAS,EAAK,KAAK,YAAe,GAAQ,EAC/C,KAAK,OAAS,EAAK,KAAK,aAAe,GAAQ,EAE/C,KAAK,qBAAqB,EAEnB,EACX,CAcA,uBAA+B,EAAgB,EAAY,EAAkB,CACzE,GAAI,CAAC,OAAO,SAAS,CAAE,GAAK,CAAC,OAAO,SAAS,CAAE,EAC3C,OAGJ,IAAM,EAAU,KAAK,QAAQ,EACvB,EAAU,KAAK,UAAU,EAAU,CAAM,EAE/C,GAAI,IAAY,EACZ,OAGJ,IAAM,GAAU,EAAK,KAAK,OAAS,EAC7B,GAAU,EAAK,KAAK,OAAS,EAEnC,KAAK,MAAQ,EAAK,EAAS,EAC3B,KAAK,MAAQ,EAAK,EAAS,EAE3B,KAAK,QAAQ,CAAO,CACxB,CAYA,kBAAmC,CAC/B,IAAM,EAAgB,KAAK,SAAS,SAAW,KAAK,gBAAgB,SAAW,EACzE,EAAK,KAAK,SAAS,EACnB,EAAK,KAAK,UAAU,EAK1B,GAAI,EAAE,KAAK,YAAc,IAAM,EAAE,KAAK,aAAe,IAAM,EAAE,EAAK,IAAM,EAAE,EAAK,GAC3E,OAAO,EAGX,IAAM,EAAU,KAAK,IAAI,EAAK,KAAK,YAAa,EAAK,KAAK,YAAY,EAEtE,OAAO,KAAK,IAAI,EAAe,CAAO,CAC1C,CAQA,UAAkB,EAAsB,CACpC,IAAM,EAAM,KAAK,iBAAiB,EAC5B,EAAM,KAAK,SAAS,SAAW,KAAK,gBAAgB,SAAW,EAErE,OAAO,KAAK,IAAI,EAAK,KAAK,IAAI,EAAK,CAAI,CAAC,CAC5C,CAaA,gBAAwB,EAAiD,CACrE,IAAM,EAAU,KAAK,QAAQ,EACvB,EAAW,KAAK,SAAS,EACzB,EAAW,KAAK,UAAU,EAKhC,GAAI,EAAE,EAAK,IAAM,EAAE,EAAK,IAAM,EAAE,EAAK,MAAQ,IAAM,EAAE,EAAK,OAAS,GAC/D,OAAO,KAAK,UAAU,CAAO,EAGjC,IAAM,EAAU,KAAK,IAAI,EAAK,EAAK,MAAO,EAAK,EAAK,MAAM,EAE1D,OAAO,KAAK,UAAU,KAAK,IAAI,EAAS,CAAO,CAAC,CACpD,CAOA,cAAkC,CAC9B,MAAO,CAAC,GAAG,KAAK,UAAU,CAC9B,CAUA,WAAW,EAAyB,CAGhC,OAFA,KAAK,aAAa,CAAE,EAEb,IACX,CAcA,gBAAgB,EAAqC,CAGjD,OAFA,KAAK,WAAW,gBAAgB,CAAG,EAE5B,IACX,CAOA,iBAA4B,CACxB,OAAO,KAAK,WAAW,gBAAgB,CAC3C,CAmBA,gBAAgB,EAAqC,CAIjD,MAHA,MAAK,cAAgB,IAAI,IAAI,GAAO,CAAC,CAAC,EACtC,KAAK,kBAAkB,EAEhB,IACX,CAOA,iBAA4B,CACxB,MAAO,CAAC,GAAG,KAAK,aAAa,CACjC,CAGA,mBAAkC,CAC9B,IAAK,GAAM,CAAC,EAAI,KAAc,KAAK,gBAC3B,KAAK,cAAc,OAAS,GAAK,KAAK,cAAc,IAAI,CAAE,EAC1D,EAAU,aAAa,EAEvB,EAAU,WAAW,CAAmB,CAGpD,CAoBA,WAAW,EAAkB,CAGzB,OAFA,KAAK,WAAW,CAAE,EAEX,IACX,CAaA,WAAmB,EAAqB,CACpC,IAAM,EAAY,KAAK,gBAAgB,IAAI,CAAE,EACvC,EAAY,GAAW,iBAAiB,EAE9C,GAAI,CAAC,GAAa,CAAC,EACf,MAAO,GAGX,IAAM,EAAO,KAAK,gBAAgB,CAAI,EAGhC,EAAU,EAAU,KAAK,EAAI,EAAK,MAAS,EAC3C,EAAU,EAAU,KAAK,EAAI,EAAK,OAAS,EAG3C,EAAO,KAAK,SAAS,EAAK,EAAI,EAAU,EACxC,EAAO,KAAK,UAAU,EAAI,EAAI,EAAU,EAa9C,MAXI,CAAC,OAAO,SAAS,CAAI,GAAK,CAAC,OAAO,SAAS,CAAI,EACxC,IAGX,KAAK,MAAQ,EACb,KAAK,MAAQ,EAIb,KAAK,QAAQ,CAAI,EAEV,GACX,CAaA,UAAU,EAAkB,CAMxB,MALA,MAAK,aAAe,EACpB,KAAK,oBAAsB,GAE3B,KAAK,iBAAiB,EAEf,IACX,CAOA,aAAqB,EAAyB,CAC1C,IAAK,IAAM,KAAa,KAAK,gBAAgB,OAAO,EAChD,KAAK,oBAAoB,EAAW,EAAK,EAG7C,IAAM,EAAO,IAAO,KAAO,IAAA,GAAY,KAAK,UAAU,IAAI,CAAE,EAExD,GACA,KAAK,WAAa,CAAC,CAAI,EACvB,KAAK,oBAAoB,KAAK,gBAAgB,IAAI,CAAG,EAAI,EAAI,GAE7D,KAAK,WAAa,CAAC,CAE3B,CASA,oBAA4B,EAAsB,EAAyB,CAGvE,EAAK,cAAc,CAAQ,CAC/B,CAsBA,GAAG,EAAyB,EAA0B,CAGlD,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAUA,IAAI,EAAyB,EAA0B,CAGnD,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAeA,KAAe,EAAyB,GAAG,EAA0B,CACjE,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CAcA,UAAiB,CAOb,OANA,MAAM,SAAS,EAEf,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EAEhB,IACX,CAaA,0BAAyC,CACrC,IAAM,EAAK,KAAK,SAAS,EACnB,EAAK,KAAK,UAAU,EAE1B,GAAI,EAAE,EAAK,IAAM,EAAE,EAAK,GACpB,OAGJ,IAAM,EAAiB,KAAK,mBACtB,EAAiB,KAAK,oBAE5B,KAAK,mBAAsB,EAC3B,KAAK,oBAAsB,EAEvB,IAAE,EAAgB,IAAM,EAAE,EAAiB,MAI3C,IAAkB,GAAM,IAAmB,KAI/C,KAAK,QAAU,EAAK,GAAkB,EACtC,KAAK,QAAU,EAAK,GAAkB,EAEtC,KAAK,qBAAqB,EAC9B,CAUA,KAAe,EAAwB,CA4BnC,OA3BA,MAAM,KAAK,CAAO,EAQlB,EAAM,mBAAmB,KAAM,QAAS,KAAK,YAAY,EACzD,EAAM,mBAAmB,KAAM,WAAY,KAAK,kBAAkB,EAClE,EAAM,mBAAmB,KAAM,cAAe,KAAK,kBAAkB,EAIrE,EAAM,mBAAmB,KAAM,QAAS,CAAE,QAAS,GAAO,QAAS,GAAM,QAAS,KAAK,YAAa,CAAC,EACrG,EAAM,mBAAmB,KAAM,cAAe,KAAK,kBAAkB,EAIrE,EAAM,mBAAmB,KAAM,cAAe,KAAK,kBAAkB,EACrE,EAAM,mBAAmB,KAAM,YAAa,KAAK,gBAAgB,EAIjE,EAAM,mBAAmB,KAAM,YAAa,KAAK,oBAAoB,EACrE,EAAM,mBAAmB,KAAM,WAAY,KAAK,mBAAmB,EAE5D,IACX,CASA,aAAqB,EAAyB,CAe1C,GAVI,KAAK,eAIL,KAAK,iBAAiB,EAAM,MAAM,GAMlC,KAAK,WAAW,SAAS,EAAM,MAAM,IAAM,KAC3C,OAGJ,IAAM,EAAK,KAAK,SAAS,EAAM,MAAM,EAErC,GAAI,IAAO,KAAM,CACb,GAAI,KAAQ,KAAK,WAAW,EAAE,EAAE,IAAM,MAClC,OAGJ,KAAK,aAAa,CAAE,EACpB,KAAK,KAAK,YAAa,KAAK,aAAa,CAAC,CAC9C,MAAW,KAAK,WAAW,OAAS,IAChC,KAAK,aAAa,IAAI,EACtB,KAAK,KAAK,YAAa,KAAK,aAAa,CAAC,EAElD,CAWA,mBAA2B,EAAyB,CAChD,IAAM,EAAK,KAAK,SAAS,EAAM,MAAM,EAErC,GAAI,IAAO,KACP,OAGJ,IAAM,EAAO,KAAK,UAAU,IAAI,CAAE,EAE9B,IAAS,IAAA,IACT,KAAK,KAAK,WAAY,CAAI,CAElC,CAQA,SAAiB,EAA2C,CACxD,GAAI,IAAW,KACX,OAAO,KAGX,IAAM,EAAS,EAAI,OAAO,OAAO,CAAM,EAEvC,IAAK,GAAM,CAAC,EAAI,KAAc,KAAK,gBAAiB,CAChD,IAAM,EAAU,EAAU,WAAW,EAErC,GAAI,IAAY,IAAY,GAAU,EAAI,OAAO,SAAS,EAAS,CAAM,GACrE,OAAO,CAEf,CAEA,OAAO,IACX,CAaA,qBAA6B,EAAyB,CAClD,GAAI,KAAK,SACL,OAGJ,GAAI,KAAK,WAAW,SAAS,EAAM,MAAM,IAAM,KAAM,CACjD,KAAK,WAAW,EAEhB,MACJ,CAEA,IAAM,EAAO,EAAI,OAAO,gBAAgB,IAAI,EACtC,EAAO,KAAK,QAAQ,EACpB,GAAQ,EAAM,QAAU,EAAK,KAAO,KAAK,OAAS,EAClD,GAAQ,EAAM,QAAU,EAAK,IAAO,KAAK,OAAS,EAElD,EAAS,KAAK,WAAW,UAAU,EAAI,CAAE,EAE/C,GAAI,EAAO,SAAW,EAAG,CACrB,KAAK,WAAW,EAEhB,MACJ,CAEA,IAAM,EAAM,EAAO,IAAI,GAAK,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,EAE1C,GAAI,IAAQ,KAAK,gBACb,OAGJ,IAAM,EAAY,IAAI,IAAI,KAAK,SAAS,MAAM,MAAM,IAAI,GAAK,CAAC,EAAE,GAAI,CAAC,CAAC,GAAK,CAAC,CAAC,EACvE,EAAQ,EAAO,IAAI,GAAK,EAAU,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,OAAQ,GAA4B,IAAM,IAAA,EAAS,EAEtG,KAAK,gBAAkB,EACvB,KAAK,KAAK,YAAa,EAAO,CAAK,CACvC,CAUA,oBAA4B,EAAyB,CAC7C,KAAK,WAAW,SAAS,EAAM,MAAM,IAAM,MAC3C,KAAK,WAAW,CAExB,CAGA,YAA2B,CACnB,KAAK,kBAAoB,OAI7B,KAAK,gBAAkB,KACvB,KAAK,KAAK,WAAW,EACzB,CAWA,mBAA2B,EAAyC,CAChE,IAAM,EAAK,KAAK,SAAS,EAAM,MAAM,EAErC,GAAI,IAAO,KACP,OAGJ,IAAM,EAAO,KAAK,UAAU,IAAI,CAAE,EAElC,GAAI,IAAS,IAAA,GAGT,OAFA,KAAK,KAAK,cAAe,EAAM,CAAK,EAE7B,CAAE,QAAS,EAAK,CAE/B,CAUA,iBAAyB,EAAqC,CAC1D,GAAI,IAAW,MAAQ,KAAK,YAAc,IAAA,GACtC,MAAO,GAGX,IAAM,EAAK,KAAK,UAAU,WAAW,EAC/B,EAAS,EAAI,OAAO,OAAO,CAAM,EAEvC,OAAO,IAAO,IAAA,KAAc,IAAO,GAAU,EAAI,OAAO,SAAS,EAAI,CAAM,EAC/E,CASA,aAAqB,EAAyB,CAC1C,IAAM,EAAO,EAAI,OAAO,gBAAgB,IAAI,EAE5C,KAAK,uBAAuB,EAAM,OAAS,EAAI,EAAkB,EAAI,EACjE,EAAM,QAAU,EAAK,KAAM,EAAM,QAAU,EAAK,GAAG,CAC3D,CAWA,mBAA2B,EAA2B,CAIlD,KAAK,QAAU,EAAM,QACrB,KAAK,QAAU,EAAM,QACrB,KAAK,cAAgB,GAOjB,IAAM,SAAW,GAAK,KAAK,iBAAiB,EAAM,MAAM,GAAK,KAAK,SAAS,EAAM,MAAM,IAAM,QAIjG,KAAK,SAAc,GACnB,KAAK,WAAc,EAAM,QACzB,KAAK,WAAc,EAAM,QACzB,KAAK,YAAc,KAAK,MACxB,KAAK,YAAc,KAAK,MAExB,KAAK,UAAU,UAAU,EAC7B,CASA,mBAA2B,EAA2B,CAOlD,GAAI,CAAC,KAAK,eAAkB,EAAM,QAAU,EAAU,CAClD,IAAM,EAAK,EAAM,QAAU,KAAK,QAC1B,EAAK,EAAM,QAAU,KAAK,QAIhC,KAAK,cAAgB,EAAK,EAAK,EAAK,GAAM,EAC9C,CAEK,QAAK,SAOV,IAAA,EAAK,EAAM,QAAU,GAAU,CAC3B,KAAK,SAAW,GAChB,KAAK,UAAU,MAAM,EAErB,MACJ,CAEA,KAAK,MAAQ,KAAK,aAAe,EAAM,QAAU,KAAK,YACtD,KAAK,MAAQ,KAAK,aAAe,EAAM,QAAU,KAAK,YAEtD,KAAK,qBAAqB,CAL1B,CAMJ,CAKA,kBAAiC,CAC7B,KAAK,SAAW,GAChB,KAAK,UAAU,MAAM,CACzB,CAGA,eAA8B,CAC1B,KAAK,WAAc,KAAK,kBAAkB,OAAc,SAAS,EACjE,KAAK,YAAc,KAAK,kBAAkB,QAAc,UAAU,EAClE,KAAK,QAAc,KAAK,kBAAkB,SAAc,aAAa,EACrE,KAAK,UAAc,KAAK,kBAAkB,aAAc,YAAY,EAEpE,KAAK,UAAY,IAAI,EAAc,CAAE,OAAQ,eAAgB,OAAQ,GAAiB,cAAe,IAAI,CAAO,CAAC,EACjH,KAAK,UAAU,aAAa,KAAK,UAAU,EAC3C,KAAK,UAAU,aAAa,KAAK,WAAW,EAC5C,KAAK,UAAU,aAAa,KAAK,OAAO,EACxC,KAAK,UAAU,aAAa,KAAK,SAAS,CAC9C,CAUA,kBAA0B,EAAe,EAAuB,CAC5D,OAAO,IAAI,EAAO,CAAE,QAAO,KAAM,EAAO,SAAU,EAAM,CAAC,CAC7D,CAGA,sBAAqC,CACjC,KAAK,WAAW,GAAG,SAAU,KAAK,SAAS,EAC3C,KAAK,YAAY,GAAG,SAAU,KAAK,UAAU,EAC7C,KAAK,QAAQ,GAAG,SAAU,KAAK,MAAM,EACrC,KAAK,UAAU,GAAG,SAAU,KAAK,QAAQ,CAC7C,CAOA,mBAA6B,CACzB,OAAO,KAAK,SAAS,UAAY,KAAK,gBAAgB,UAAY,EACtE,CASA,mBAAmB,EAAsB,CAIrC,MAHA,MAAK,SAAS,SAAW,EACzB,KAAK,UAAU,WAAW,CAAK,EAExB,IACX,CACJ,EAEM,GAAsB,EAAS,WAAW"}
|
|
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/DiagramResidency.ts","../../../src/typescript/lib/component/diagram/DiagramEdgeLayer.ts","../../../src/typescript/lib/component/diagram/DiagramNodeLayer.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 terminateWorker(): void;\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/** Message carried by the rejection a disposed engine's `layout` produces. */\nconst DISPOSED_MESSAGE = \"ElkLayoutEngine has been disposed\";\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/** Construction options for {@link ElkLayoutEngine}. */\nexport interface ElkLayoutEngineOptions {\n /**\n * Consumer-provided factory returning a Web Worker for off-thread ELK\n * layout. Takes precedence over {@link ElkLayoutEngineOptions.workerUrl}\n * when both are set.\n */\n workerFactory?: () => Worker;\n /**\n * URL of a consumer-hosted `elk-worker.js`, requesting off-thread layout.\n * With the `elk.bundled.js` module this engine imports, elkjs's own\n * worker-availability check (`require.resolve('web-worker')`) always\n * fails — the browserify module system that check runs against has no\n * `.resolve` — so `workerUrl` alone never actually constructs a Worker\n * here; elkjs logs its own console warning and runs on the main thread via\n * its in-process fallback. Pass\n * {@link ElkLayoutEngineOptions.workerFactory} for real off-thread\n * execution; `workerUrl` is kept only for parity with elkjs's own API.\n */\n workerUrl?: string;\n}\n\n/**\n * Lazily-loaded ELK layout adapter. One instance owns one ELK engine, built on\n * the first {@link ElkLayoutEngine.layout} call and reused afterward. With\n * neither `workerFactory` nor `workerUrl` set, ELK runs on the main thread.\n * Setting `workerFactory` runs layout in the Worker it returns. Setting\n * `workerUrl` does not: see {@link ElkLayoutEngineOptions.workerUrl} — it\n * always ends up on the main thread with this engine's `elk.bundled.js`\n * import. Either mode falls back transparently to the main thread — within\n * the same failing `layout` call, which rebuilds the engine and retries once\n * — the first time its engine fails to construct or a layout rejects; that\n * fallback is permanent for the instance's lifetime, so later layouts run on\n * the main thread even if the worker would have recovered.\n *\n * In `workerFactory` mode the engine owns a real Web Worker for as long as it\n * lives. Its owner is expected to call {@link ElkLayoutEngine.dispose} when\n * discarding it, or that worker thread outlives the engine.\n *\n * @category Components\n */\nexport class ElkLayoutEngine {\n\n private _elk: ElkInstance | null = null;\n private readonly _workerFactory?: () => Worker;\n private readonly _workerUrl?: string;\n /**\n * True while `_elk` is worker-backed. Set before constructing a\n * worker-backed engine and never cleared on a successful layout, so the\n * *next* layout failure — whichever one it is — still triggers the\n * one-time main-thread rebuild in {@link layout}; after that rebuild it is\n * `false` for good and a further failure propagates.\n */\n private _workerBacked = false;\n /** True once `dispose` has run. A disposed engine never builds another ELK. */\n private _disposed = false;\n /**\n * True while the ELK instance this engine holds — or is in the middle of\n * building — drives a real Web Worker, which is the consumer-factory mode\n * and only that mode. Cleared when the main-thread fallback replaces the\n * instance. Deliberately NOT cleared by `dispose`: a construction still in\n * flight sets this just before it builds, and the adopt-time check needs\n * it afterward to terminate an instance `dispose` could not yet see.\n */\n private _ownsWorker = false;\n /**\n * The in-flight construction, shared by every `layout` that arrives while\n * ELK is still being imported and built. Without it each of those builds\n * its own ELK — and its own Worker — because `_elk` is still `null` for\n * all of them, and `dispose` can only terminate the one instance the\n * engine ends up keeping. Cleared whenever what it memoised stops being\n * this engine's instance: a failed construction (so a later `layout`\n * retries the import instead of replaying the failure), the main-thread\n * fallback replacing the instance, and `dispose`. Keeping those in step\n * is what stops the memo retaining an ELK — and its `Worker` — the engine\n * has already abandoned, so any future code that assigns or nulls `_elk`\n * must clear this too.\n */\n private _building: Promise<ElkInstance> | null = null;\n\n /**\n * @param options - Optional worker configuration. With neither field set,\n * ELK runs on the main thread via the zero-config bundle.\n */\n constructor(options?: ElkLayoutEngineOptions) {\n this._workerFactory = options?.workerFactory;\n this._workerUrl = options?.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. The first time a worker-backed engine\n * fails to construct or a layout rejects, rebuilds on the main thread and\n * retries once, so a worker problem never surfaces as a diagram error;\n * that switch to the main thread is permanent for this instance's\n * lifetime, even if a later failure is unrelated to the worker itself.\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, if a\n * main-thread layout fails (no worker fallback remains at that point),\n * or if the engine was already disposed when the call started or is\n * disposed while it is building or rebuilding its ELK. Disposal landing\n * during the layout compute itself does not reject — that call can still\n * resolve, which is why `DiagramView` drops late results by generation\n * rather than relying on this.\n */\n async layout(\n data: DiagramData,\n sizes: Map<string, { width: number; height: number }>,\n defaults?: Record<string, string>,\n ): Promise<DiagramLayoutResult> {\n if (this._disposed) {\n throw new Error(DISPOSED_MESSAGE);\n }\n\n const graph = buildElkGraph(data, sizes, defaults);\n\n try {\n const elk = await this.ensureElk();\n\n return mapElkResult(await elk.layout(graph) as ElkNode);\n } catch (error) {\n // Disposed mid-flight: the engine is gone for good, so there is\n // nothing to fall back onto. Main-thread failure: elkjs itself is\n // absent/broken. Either way, propagate.\n if (this._disposed || !this._workerBacked) {\n throw error;\n }\n\n // Worker construction failed (factory threw, `Worker` undefined,\n // CSP block) or its first compute failed. Terminate the worker\n // being abandoned before the reference is dropped, then rebuild on\n // the main thread and retry once so the diagram still renders.\n this.terminateOwnedWorker(this._elk);\n this._workerBacked = false;\n this._ownsWorker = false;\n\n const rebuilt = await this.createMainThreadElk();\n\n // `dispose` can land while that rebuild is awaited. Adopting the\n // instance then would leave a disposed engine holding a live ELK\n // and laying out through it, so drop it. Unlike `ensureElk`'s\n // equivalent check this does NOT terminate what it drops: the\n // rebuild is main-thread, and terminating a non-factory instance\n // throws a `TypeError` (see `terminateOwnedWorker`).\n if (this._disposed) {\n throw new Error(DISPOSED_MESSAGE);\n }\n\n this._elk = rebuilt;\n // The memo still resolves to the worker-backed instance just\n // abandoned; drop it so it does not outlive what it built.\n this._building = null;\n\n return mapElkResult(await rebuilt.layout(graph) as ElkNode);\n }\n }\n\n /**\n * Terminates this engine's ELK Web Worker, if it owns one, and drops the\n * ELK instance. A later {@link ElkLayoutEngine.layout} rejects rather than\n * rebuilding, so a disposed engine never resurrects a worker. Idempotent,\n * and a no-op on an engine that never laid out or never ran off-thread.\n *\n * A layout request outstanding in a real worker at this moment never\n * settles — elkjs does not reject pending requests on termination — so a\n * caller awaiting one must not depend on it resolving or rejecting.\n */\n dispose(): void {\n this._disposed = true;\n\n this.terminateOwnedWorker(this._elk);\n\n this._elk = null;\n // Dropped too, or the engine keeps the terminated instance reachable\n // through the memoised construction for as long as the engine lives.\n this._building = null;\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 * @throws Error - If the engine was disposed while ELK was still being\n * imported and constructed.\n */\n private async ensureElk(): Promise<ElkInstance> {\n if (this._elk) {\n return this._elk;\n }\n\n // Memoised so overlapping layouts share one construction rather than\n // each building an ELK of its own — see `_building`. The adopt-or-\n // terminate decision therefore runs exactly once however many callers\n // are waiting, and they all settle on its outcome.\n this._building ??= this.createElk()\n .then((elk) => {\n if (this._disposed) {\n // `dispose` ran while ELK was still being imported and\n // constructed, so it saw no instance to terminate.\n // Terminate this one rather than adopting it, or the\n // worker outlives the disposed engine.\n this.terminateOwnedWorker(elk);\n\n throw new Error(DISPOSED_MESSAGE);\n }\n\n this._elk = elk;\n\n return elk;\n })\n .catch((error: unknown) => {\n this._building = null;\n\n throw error;\n });\n\n return this._building;\n }\n\n /**\n * Terminates `elk`'s Web Worker, but only when this engine built it from a\n * consumer factory. In every other mode elkjs drives an in-process stand-in\n * worker with no `terminate` method, and asking elkjs to terminate that\n * throws a `TypeError` — so the guard is load-bearing, not defensive.\n *\n * @param elk - The instance to terminate, or `null` if none was built.\n */\n private terminateOwnedWorker(elk: ElkInstance | null): void {\n if (elk && this._ownsWorker) {\n elk.terminateWorker();\n }\n }\n\n /**\n * Builds the ELK engine per the configured mode: a consumer worker\n * factory, a consumer-hosted worker URL, or the main-thread default —\n * checked in that precedence order. `_workerBacked` is set *before*\n * constructing a worker-backed engine, so a synchronous construction\n * throw is still caught by {@link layout}'s retry.\n *\n * @returns The constructed ELK engine.\n */\n private async createElk(): Promise<ElkInstance> {\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 if (this._workerFactory) {\n this._workerBacked = true;\n this._ownsWorker = true;\n\n return new ELK({ workerFactory: this._workerFactory });\n }\n\n if (this._workerUrl) {\n this._workerBacked = true;\n\n return new ELK({ workerUrl: this._workerUrl });\n }\n\n return new ELK();\n }\n\n /**\n * Builds a plain main-thread ELK engine, used both as the default mode\n * and as the fallback after a worker-backed engine fails.\n *\n * @returns The constructed main-thread ELK engine.\n */\n private async createMainThreadElk(): Promise<ElkInstance> {\n const { default: ELK } = await import(\"elkjs/lib/elk.bundled.js\");\n\n return new 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 { Component } from \"~/core/Component.js\";\nimport type { StyleBag, StyleStateSpec } from \"~/core/ClassStyleRules.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { HBox } from \"~/layout/HBox.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 /** Short marker text drawn after the label, in the same row. */\n badge?: string;\n /** Whether the node starts selected. */\n selected?: boolean;\n}\n\n/** Resting fill colour of a `DiagramNode` — and, once level-of-detail\n * simplification engages, of the `<rect>` `DiagramNodeLayer` draws in its\n * place. @internal */\nexport const DIAGRAM_NODE_BACKGROUND_COLOR = \"var(--ts-ui-diagram-node-bg, var(--ts-ui-button-bg, rgb(245, 245, 245)))\";\n\n/** Resting border colour of a `DiagramNode` — and, once level-of-detail\n * simplification engages, of the `<rect>` `DiagramNodeLayer` draws in its\n * place. @internal */\nexport const DIAGRAM_NODE_BORDER_COLOR = \"var(--ts-ui-border-color, rgb(180, 180, 180))\";\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 backgroundColor: DIAGRAM_NODE_BACKGROUND_COLOR,\n border: `1px solid ${DIAGRAM_NODE_BORDER_COLOR}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n};\n\n// The badge's opacity: present but secondary to the label it trails. Matches\n// the \"dim the supporting value\" weight the framework already uses for a\n// receded label, so the badge reads as an annotation rather than a second\n// name.\nconst BADGE_OPACITY = 0.6;\n\n/** `.selected`'s background-color declaration — and, once level-of-detail\n * simplification engages, a selected leaf rect's `fill`. @internal */\nexport const DIAGRAM_NODE_SELECTED_BACKGROUND_COLOR = \"var(--ts-ui-diagram-node-selected-bg, var(--ts-ui-table-row-selected, rgba(30, 100, 200, 0.15)))\";\n\n/** `.selected`'s border-color declaration. Recolours the resting border,\n * leaving `_defaultDiagramNodeOptions.border`'s width and style intact — and,\n * once level-of-detail simplification engages, a selected leaf rect's\n * `stroke`. @internal */\nexport const DIAGRAM_NODE_SELECTED_BORDER_COLOR = \"var(--ts-ui-accent-color, rgb(30, 100, 200))\";\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 // Declares `.selected` so `styleLayers()`/`restingGuardSuffix` know\n // about it — see `Button`'s `ownStyleStates` for the full mechanism.\n // Both declarations hoist onto the shared `.DiagramNode.selected` rule.\n // The state declares the `borderColor` longhand rather than the `border`\n // shorthand so it recolours the resting border without restating its\n // width or style.\n protected static readonly ownStyleStates: readonly StyleStateSpec[] = [\n {\n selector: \".selected\",\n extract: (): StyleBag => ({\n backgroundColor: DIAGRAM_NODE_SELECTED_BACKGROUND_COLOR,\n borderColor: DIAGRAM_NODE_SELECTED_BORDER_COLOR,\n }),\n },\n ];\n\n /** The glyph+label (or bare label) component. */\n private _label!: IconText | Text;\n\n /** The child added to the node: `_label` alone, or a row of `_label` + `_badge`. */\n private _content!: Component;\n\n /** The trailing badge chip, when the node carries one. */\n private _badge?: Text;\n\n /**\n * @param options - Optional construction-time options.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults; subclasses forward their `_defaultXxxOptions`\n * constant here.\n */\n constructor(options?: DiagramNodeOptions, subclassDefaults?: Partial<DiagramNodeOptions>) {\n super(options, {\n ..._defaultDiagramNodeOptions,\n layoutManager: new Fit(),\n ...(subclassDefaults ?? {}),\n });\n\n // Content children are built here (not during super's cascade), so the\n // label/glyph/badge/selected values cached pure in `applyOptions` are\n // dispatched now that the row exists.\n this.buildContent(this._options.glyph, this._options.label ?? \"\", this._options.badge);\n\n if (this._options.selected !== undefined) {\n this.setSelected(this._options.selected);\n }\n }\n\n /**\n * Caches the label/glyph/badge/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.badge !== undefined) this._options.badge = options.badge;\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: `_label` alone (an\n * `IconText` when a glyph is present, else a bare `Text`), or — when a\n * badge is given — a row of `_label` followed by the badge `Text`.\n *\n * @param glyph - Optional glyph name.\n * @param label - The label text.\n * @param badge - Optional trailing badge text.\n */\n private buildContent(glyph: string | undefined, label: string, badge: string | undefined): void {\n if (this._content) {\n this.removeComponent(this._content);\n }\n\n this._label = glyph !== undefined ? new IconText(glyph, label) : new Text(label);\n\n if (badge === undefined) {\n this._badge = undefined;\n this._content = this._label;\n } else {\n this._badge = new Text(badge);\n this._badge.setOpacity(BADGE_OPACITY);\n this._content = new Component({ layoutManager: new HBox(), components: [this._label, this._badge] });\n }\n\n // Every Component stamps its own `cursor` (defaulting to `default`) onto\n // its CSS rule, so the label/glyph/badge would override the node's\n // `pointer` wherever they sit under the cursor — the hover cursor would\n // flicker between pointer (over padding) and arrow (over the text).\n // Make the content transparent to pointer events so hover + clicks land\n // on the node itself; `pointer-events: none` inherits, so this one call\n // also covers everything nested inside (the glyph and text inside an\n // `IconText`, or the label + badge inside the row). Mirrors how `Button`\n // 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._label.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 * Returns the node's badge text, or `null` when none was set.\n *\n * @returns The badge text, or `null`.\n */\n getBadge(): string | null {\n return this._options.badge ?? 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 // Unconditional, not gated on `this.getElement()`: `setStyleState`\n // updates `_activeStates` regardless of whether an element exists\n // yet (only its own DOM write is internally element-gated) — see\n // `ToggleButton.setSelected`'s own comment for the full reasoning.\n this.setStyleState(\".selected\", value);\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/**\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/** Resting fill colour of a `DiagramGroupNode` — and, once level-of-detail\n * simplification engages, of a container `<rect>` `DiagramNodeLayer` draws\n * in its place. @internal */\nexport const DIAGRAM_GROUP_BACKGROUND_COLOR = \"var(--ts-ui-diagram-group-bg, rgba(120, 120, 120, 0.08))\";\n\n/** Resting border colour of a `DiagramGroupNode` — and, once level-of-detail\n * simplification engages, of a container `<rect>` `DiagramNodeLayer` draws\n * in its place. @internal */\nexport const DIAGRAM_GROUP_BORDER_COLOR = \"var(--ts-ui-diagram-group-border, var(--ts-ui-border-color, rgb(180, 180, 180)))\";\n\nconst _defaultDiagramGroupNodeOptions: Partial<DiagramGroupNodeOptions> = {\n backgroundColor: DIAGRAM_GROUP_BACKGROUND_COLOR,\n // A container is a selectable node like any leaf, so it carries the same\n // pointer cursor `DiagramNode` does — left at the Component default it\n // would read as an arrow and promise a pan its own box does not perform.\n cursor: \"pointer\",\n border: `1px solid ${DIAGRAM_GROUP_BORDER_COLOR}`,\n borderRadius: \"4px\",\n};\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, subclassDefaults?: Partial<DiagramGroupNodeOptions>) {\n super(options, {\n layoutManager: new Absolute(),\n ..._defaultDiagramGroupNodeOptions,\n ...(subclassDefaults ?? {}),\n });\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/** An axis-aligned box in unscaled graph coordinates. @internal */\nexport interface DiagramRect { x: number; y: number; width: number; height: number; }\n\n/**\n * Inflates `rect` by `fraction` of its own width on the left/right and its\n * own height on the top/bottom.\n *\n * @param rect - The rectangle to inflate.\n * @param fraction - The fraction of the rect's own extent to add on each side.\n * @returns The inflated rectangle.\n *\n * @internal\n */\nexport function inflateRect(rect: DiagramRect, fraction: number): DiagramRect {\n const dx = rect.width * fraction;\n const dy = rect.height * fraction;\n\n return { x: rect.x - dx, y: rect.y - dy, width: rect.width + 2 * dx, height: rect.height + 2 * dy };\n}\n\n/**\n * Whether the residency set must be rebuilt for `live`, given the rectangle\n * it was last committed for. `true` when nothing has been committed yet, when\n * `live`'s extents differ from `committed`'s (a zoom or a viewport resize —\n * the residency rect's size must track it exactly), or when `live` has\n * escaped the *trigger rect*: `committed` inflated by half of `margin`. This\n * is the hysteresis that lets an ordinary pan or a slow zoom change the\n * mounted set a few times per screen of travel instead of on every transform\n * write — see `computePoolTarget` in `VirtualRowView.ts` and\n * `computeColumnWindowSize` in `table/Body.ts` for the same \"derive the\n * rendered set from the viewport, not the current position\" rule on the\n * row/column axes.\n *\n * @param committed - The rectangle the residency set was last computed for, or `null` before the first computation.\n * @param live - The current visible graph rectangle.\n * @param margin - The residency margin (see {@link RESIDENCY_MARGIN}); half of it is the trigger-rect inflation.\n * @returns Whether the residency set must be recomputed.\n *\n * @internal\n */\nexport function residencyNeedsRefresh(committed: DiagramRect | null, live: DiagramRect, margin: number): boolean {\n if (committed === null) {\n return true;\n }\n\n if (live.width !== committed.width || live.height !== committed.height) {\n return true;\n }\n\n const trigger = inflateRect(committed, margin / 2);\n\n return live.x < trigger.x\n || live.y < trigger.y\n || live.x + live.width > trigger.x + trigger.width\n || live.y + live.height > trigger.y + trigger.height;\n}\n\n/**\n * Whether two boxes overlap; inclusive, so edge-to-edge counts.\n *\n * @param a - The first box.\n * @param b - The second box.\n * @returns Whether `a` and `b` overlap.\n *\n * @internal\n */\nexport function rectsIntersect(a: DiagramRect, b: DiagramRect): boolean {\n return a.x <= b.x + b.width && a.x + a.width >= b.x\n && a.y <= b.y + b.height && a.y + a.height >= b.y;\n}\n\n/**\n * Every id whose box intersects `residency`; an id with no entry in `rects`\n * is always resident (nothing has placed it yet, so it has no box to test,\n * and it cannot be culled). Intersection is inclusive — a box touching the\n * residency rect edge-to-edge counts as resident.\n *\n * @param ids - Every id to test.\n * @param rects - Laid-out box per id.\n * @param residency - The residency rectangle to test each box against.\n * @returns The set of resident ids.\n *\n * @internal\n */\nexport function computeResidentIds(ids: Iterable<string>, rects: Map<string, DiagramRect>, residency: DiagramRect): Set<string> {\n const resident = new Set<string>();\n\n for (const id of ids) {\n const rect = rects.get(id);\n\n if (!rect) {\n resident.add(id);\n\n continue;\n }\n\n if (rectsIntersect(rect, residency)) {\n resident.add(id);\n }\n }\n\n return resident;\n}\n\n/**\n * Whether any box in `rects` overlaps `area`.\n *\n * @param rects - Every box to test.\n * @param area - The area to test each box against.\n * @returns Whether at least one box in `rects` overlaps `area`.\n *\n * @internal\n */\nexport function anyRectIntersects(rects: Iterable<DiagramRect>, area: DiagramRect): boolean {\n for (const rect of rects) {\n if (rectsIntersect(rect, area)) {\n return true;\n }\n }\n\n return false;\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's root stays\n// non-interactive (`pointer-events: none`) so node clicks fall through to the\n// node components; each drawn edge additionally gets an invisible wide hit\n// path that opts itself back into pointer events, so hovering/pressing an edge\n// is possible without making the layer as a whole interactive.\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 { computeResidentIds } from \"~/component/diagram/DiagramResidency.js\";\nimport type { DiagramRect } from \"~/component/diagram/DiagramResidency.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 * Stroke width (px, unscaled graph units) of an edge's invisible hit path —\n * ±6px either side of the 1.5px visible hairline. A hairline is far too thin\n * to aim at, and this is deliberately wider than ELK's default 10px\n * `elk.spacing.edgeEdge`, so a bundle of parallel routes reports as a bundle\n * rather than forcing the user to land on exactly one of them.\n */\nconst EDGE_HIT_WIDTH = 12;\n\n/** Half the hit width: the distance from a route within which it answers `edgesNear`. */\nconst EDGE_HIT_TOLERANCE = EDGE_HIT_WIDTH / 2;\n\n/**\n * Opacity of the group holding every edge outside a non-empty emphasis set. A\n * 1.5px hairline has almost no area, so it still reads as a full line at\n * ChartLegend's `HIDDEN_OPACITY` (`0.4`), a strength tuned for a filled legend\n * swatch — dimmed enough there is not dimmed enough for a stroke. `0.15` over\n * the default light canvas resolves to a pale, still-traceable grey while\n * leaving the emphasised edges clearly what the eye lands on.\n *\n * Carried by the dimmed *group* rather than each dimmed edge, because routes\n * overlap by design: fan-in and fan-out bundles share a junction stub, so two\n * or more dimmed paths coincide there. Per-element alpha composites at each\n * overlap — two paths at `0.15` resolve to `0.28`, three to `0.39` — so a\n * bundle read as emphasised precisely where it was densest. Group opacity\n * composites the group's whole rendering once, so an overlap looks the same as\n * a single line.\n */\nconst DIMMED_EDGE_OPACITY = \"0.15\";\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 * How far, in unscaled graph units, the longest end marker reaches back along\n * an edge from the point it attaches to. Every marker anchors its vertex at\n * `x = width` (via `refX`) and opens toward `x = 0`, so a marker's `width` *is*\n * its reach, and this is the widest of them all.\n *\n * Exported because a consumer that rewrites edge routes needs it: anything\n * placed on the route within this distance of an endpoint lands underneath the\n * marker glyph rather than beside it. SQLAdmin's junction stubs use it as the\n * floor for how far from a node a bundle may branch.\n *\n * @category Components\n */\nexport const EDGE_MARKER_EXTENT: number =\n Math.max(ARROW_SIZE, ...Object.values(MARKER_GEOMETRY).map(geometry => geometry.width));\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 shortest distance from point `(px, py)` to the line segment `(ax, ay)`–\n * `(bx, by)`, via the standard clamped projection onto the segment.\n *\n * @param px - Point x.\n * @param py - Point y.\n * @param ax - Segment start x.\n * @param ay - Segment start y.\n * @param bx - Segment end x.\n * @param by - Segment end y.\n * @returns The shortest distance from the point to the segment.\n */\nfunction distanceToSegment(px: number, py: number, ax: number, ay: number, bx: number, by: number): number {\n const dx = bx - ax;\n const dy = by - ay;\n const lengthSquared = dx * dx + dy * dy;\n\n if (lengthSquared === 0) {\n return Math.hypot(px - ax, py - ay);\n }\n\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lengthSquared));\n\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n}\n\n/**\n * The shortest distance from point `(x, y)` to a routed edge's polyline —\n * the minimum over every segment of every section, walking the same\n * start/bend/end points {@link labelPoint} does.\n *\n * @param sections - The edge's routed sections.\n * @param x - Point x in unscaled graph coordinates.\n * @param y - Point y in unscaled graph coordinates.\n * @returns The shortest distance to the route, or `Infinity` when it has no drawable segments.\n */\nfunction distanceToRoute(sections: ElkEdgeSection[], x: number, y: number): number {\n let min = Infinity;\n\n for (const section of sections) {\n const points = [section.startPoint, ...(section.bendPoints ?? []), section.endPoint];\n\n for (let i = 1; i < points.length; i++) {\n min = Math.min(min, distanceToSegment(x, y, points[i - 1].x, points[i - 1].y, points[i].x, points[i].y));\n }\n }\n\n return min;\n}\n\n/**\n * How far outside its own polyline an edge paints, in unscaled graph units.\n * Three things reach past the bare line — the end markers, the 12px-wide\n * invisible hit stroke, and the label's halo — and the widest end marker's\n * own reach covers all three, so a route's box is grown by this much on\n * every side before it is tested against the residency rect. Without it an\n * edge whose route stops just outside the rect could still have a marker or\n * label glyph reaching into it.\n */\nconst EDGE_BOUNDS_PADDING = EDGE_MARKER_EXTENT;\n\n/**\n * The box an edge's drawing occupies, padded for markers, hit stroke, and\n * label: the smallest rectangle containing every point on the routed\n * polyline — start point, bend points, end point of every section, walking\n * the same points {@link distanceToRoute} does — grown by\n * {@link EDGE_BOUNDS_PADDING} on each of the four sides.\n *\n * @param sections - The edge's routed sections.\n * @returns The edge's box, or `null` when the route has no points.\n *\n * @internal\n */\nexport function routeBounds(sections: ElkEdgeSection[]): DiagramRect | null {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const section of sections) {\n const points = [section.startPoint, ...(section.bendPoints ?? []), section.endPoint];\n\n for (const point of points) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n }\n\n if (minX === Infinity) {\n return null;\n }\n\n return {\n x: minX - EDGE_BOUNDS_PADDING,\n y: minY - EDGE_BOUNDS_PADDING,\n width: maxX - minX + 2 * EDGE_BOUNDS_PADDING,\n height: maxY - minY + 2 * EDGE_BOUNDS_PADDING,\n };\n}\n\n/** The elements drawn for one edge, so it can be hit-tested and released. */\ninterface DrawnEdge {\n id: string;\n route: DiagramEdgeRoute;\n /** The visible stroked path. */\n path: Handle;\n /** The invisible wide path that takes pointer events. */\n hit: Handle;\n /** The mid-route label, when the edge carries one. */\n label: Handle | null;\n /** The group the three elements above were appended into, so they can be removed from it. */\n group: Handle;\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 /** Everything currently drawn, released and rebuilt by `rebuildPaths`. */\n private _drawn: DrawnEdge[] = [];\n\n /**\n * Box per edge id, rebuilt by `setEdges` from each route. An edge whose\n * route has no points has no entry, which `computeResidentIds` treats as\n * always admitted — and `drawEdge` skips it anyway for having no path data.\n */\n private _edgeRects: Map<string, DiagramRect> = new Map();\n\n /**\n * The rectangle an edge's box must reach to be drawn, in unscaled graph\n * coordinates, or `null` when nothing has told this layer where the\n * viewport is — in which case every edge is drawn.\n */\n private _residency: DiagramRect | null = null;\n\n /** The admitted edge ids, or `null` when `_residency` is `null` (every edge is admitted). */\n private _residentIds: Set<string> | null = null;\n\n /**\n * The currently emphasised edge ids. Runtime interaction state (not a\n * `ComponentOptions` field — see ARCHITECTURE.md's rule that transient,\n * framework-managed state stays off the options bag). Cleared by `setEdges`.\n */\n private _edgeEmphasis: Set<string> = new Set();\n\n /**\n * The `<g>` holding every edge outside a non-empty emphasis set, carrying\n * {@link DIMMED_EDGE_OPACITY}. Painted before `_normalLayer`, so an\n * emphasised edge always draws over a dimmed one it crosses. Created by\n * `createRootElement`, so it exists before any draw.\n */\n private _dimLayer!: Handle;\n\n /** The `<g>` holding every edge drawn at full strength. Painted over `_dimLayer`. */\n private _normalLayer!: 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 // Every Component stamps ComponentDefaults' \"default\" cursor into its\n // own rule; left at that, a hit path inheriting from this `<svg>`\n // would resolve to an arrow regardless of what it declares itself.\n // Inheriting through to the view root's live grab/grabbing write is\n // what lets an edge press pan the canvas honestly.\n this.setCursor(\"inherit\");\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. Clears any\n * active edge emphasis first, so the freshly-drawn set always starts\n * undimmed — a re-layout that swaps in a new graph is not the graph the\n * emphasis was computed against.\n *\n * Routes routinely arrive before this layer has an element: a diagram built\n * inside a dock tab runs its whole ELK layout while the tab is still\n * detached (the app awaits\n * [`DiagramView.whenLaidOut`](/api/component/diagram/classes/DiagramView)\n * before mounting, so the layout *always* lands first), and `rebuildPaths`\n * cannot draw without one. `render` only performs the first draw when it is\n * what creates the element, which is not the case here — so without the\n * deferral below the sole draw for those routes is silently lost and the\n * diagram shows nodes with no edges until some later `setEdges` happens to\n * find an element. {@link Component.onFirstLayout} exists for exactly this\n * \"content built before the host attaches it\" case.\n *\n * Also rebuilds the per-edge box cache from the new routes and re-derives\n * which edges the standing residency rect admits, so a graph swap culls\n * correctly against whichever rectangle `DiagramView` last pushed.\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._edgeEmphasis = new Set();\n\n this._edgeRects = new Map();\n\n for (const edge of edges) {\n const bounds = routeBounds(edge.sections);\n\n if (bounds) {\n this._edgeRects.set(edge.id, bounds);\n }\n }\n\n this.recomputeResidentEdges();\n\n if (this.getElement()) {\n this.rebuildPaths();\n } else {\n this.onFirstLayout(() => this.rebuildPaths());\n }\n\n return this;\n }\n\n /**\n * Sets the emphasised edge ids. While the set is non-empty every edge NOT\n * in it is drawn at a reduced opacity; the emphasised edges keep their\n * normal weight. `null` or an empty array clears the emphasis. Ids naming\n * no drawn edge are kept but have no effect.\n *\n * @param ids - The edge ids to emphasise, or null to clear.\n *\n * @returns This layer, for method chaining.\n */\n setEdgeEmphasis(ids: readonly string[] | null): this {\n this._edgeEmphasis = new Set(ids ?? []);\n\n // A full redraw rather than a restyle in place: which of the two groups\n // an edge belongs to is decided at draw time, and redrawing is what\n // moves it. Cheap enough to do on a click — a redraw of a 1000-edge\n // graph's paths measures around 20ms, against the seconds ELK itself\n // takes — and it reuses the release/rebuild path already covered by\n // tests instead of relying on appendChild's move semantics through the\n // DOM seam.\n this.rebuildPaths();\n\n return this;\n }\n\n /**\n * The currently emphasised edge ids.\n *\n * @returns A copy of the emphasised id array; empty when nothing is emphasised.\n */\n getEdgeEmphasis(): string[] {\n return [...this._edgeEmphasis];\n }\n\n /**\n * Resolves a raw DOM event target to the edge whose invisible hit path it\n * is. Answers only for the edges this layer is currently drawing, which\n * on a large graph is those near the visible area.\n *\n * @param target - The raw DOM event target.\n * @returns The edge id, or null when the target is not an edge hit path.\n */\n edgeIdAt(target: EventTarget | null): string | null {\n if (target === null) {\n return null;\n }\n\n const handle = DOM.source.intern(target);\n\n return this._drawn.find(d => d.hit === handle)?.id ?? null;\n }\n\n /**\n * Every drawn edge whose route passes within the hit tolerance of a point,\n * in draw order. Several edges answer here wherever their routes overlap —\n * which is what makes a merged trunk answerable. Answers only for the\n * edges this layer is currently drawing, which on a large graph is those\n * near the visible area.\n *\n * @param x - Point x in unscaled graph coordinates.\n * @param y - Point y in unscaled graph coordinates.\n * @returns The routes within tolerance; empty when none is.\n */\n edgesNear(x: number, y: number): DiagramEdgeRoute[] {\n return this._drawn\n .filter(drawn => distanceToRoute(drawn.route.sections, x, y) <= EDGE_HIT_TOLERANCE)\n .map(drawn => drawn.route);\n }\n\n /**\n * Which group one edge draws into: the dimmed group when an emphasis set is\n * active and this edge is not in it, the full-strength group otherwise.\n *\n * @param id - The edge id.\n * @returns The group handle to append the edge's elements into.\n */\n private groupFor(id: string): Handle {\n if (this._edgeEmphasis.size === 0 || this._edgeEmphasis.has(id)) {\n return this._normalLayer;\n }\n\n return this._dimLayer;\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 this._dimLayer = this.createEdgeGroup(svg, DIMMED_EDGE_OPACITY);\n this._normalLayer = this.createEdgeGroup(svg, null);\n\n return svg;\n }\n\n /**\n * Creates one of the two persistent edge groups as a child of the root\n * `<svg>`. Append order is paint order, so the caller creates the dimmed\n * group first.\n *\n * @param svg - The root `<svg>` handle to append the group into.\n * @param opacity - The group's opacity, or null to leave it at full strength.\n *\n * @returns The `<g>` handle.\n */\n private createEdgeGroup(svg: Handle, opacity: string | null): Handle {\n const group = DOM.sink.createElementNS(SVG_NS, \"g\");\n\n if (opacity !== null) {\n DOM.sink.apply(group, { setAttr: { opacity } });\n }\n\n DOM.sink.appendChild(svg, group);\n this.trackHandle(group);\n\n return group;\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 the admitted subset from\n * the cached edge routes. No-op before the element exists — `render`\n * 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 drawn of this._drawn) {\n this.releaseDrawnEdge(drawn);\n }\n\n this._drawn = [];\n\n this.updateDrawnEdges();\n }\n\n /**\n * Draws one edge's path data, hit path, visible path, and optional label\n * into its emphasis group.\n *\n * @param edge - The routed edge to draw.\n * @returns The drawn edge record, or `null` when the route has no path data.\n */\n private drawEdge(edge: DiagramEdgeRoute): DrawnEdge | null {\n const d = buildPathData(edge.sections);\n\n if (!d) {\n return null;\n }\n\n const group = this.groupFor(edge.id);\n const hit = this.drawHitPath(group, d);\n const path = this.drawVisiblePath(group, edge, d);\n const style = edge.style;\n let label: Handle | null = null;\n\n if (style?.label) {\n label = this.drawLabel(group, edge, style.label);\n }\n\n return { id: edge.id, route: edge, path, hit, label, group };\n }\n\n /** Recomputes the admitted-id set from the standing residency rect and the current routes. */\n private recomputeResidentEdges(): void {\n this._residentIds = this._residency === null\n ? null\n : computeResidentIds(this._edges.map(edge => edge.id), this._edgeRects, this._residency);\n }\n\n /**\n * Whether `id` is admitted by the standing residency rect.\n *\n * @param id - The edge id to test.\n * @returns Whether the edge should be drawn.\n */\n private isResident(id: string): boolean {\n return this._residentIds === null || this._residentIds.has(id);\n }\n\n /**\n * Reconciles `_drawn` against `_edges` and the standing residency rect by\n * difference: an already-drawn edge that stays admitted is left alone, a\n * newly-admitted edge is drawn, and anything drawn that is no longer\n * admitted is released. No-op before the element exists.\n */\n private updateDrawnEdges(): void {\n if (!this.getElement()) {\n return;\n }\n\n const previous = new Map(this._drawn.map(drawn => [drawn.id, drawn]));\n const next: DrawnEdge[] = [];\n\n for (const edge of this._edges) {\n if (!this.isResident(edge.id)) {\n continue;\n }\n\n const already = previous.get(edge.id);\n\n if (already) {\n previous.delete(edge.id);\n next.push(already);\n\n continue;\n }\n\n const drawn = this.drawEdge(edge);\n\n if (drawn) {\n next.push(drawn);\n }\n }\n\n for (const drawn of previous.values()) {\n this.releaseDrawnEdge(drawn);\n }\n\n this._drawn = next;\n }\n\n /**\n * Sets the rectangle, in unscaled graph coordinates, an edge's box must\n * reach to be drawn, and reconciles the drawn set against it. `null`\n * draws every edge — the state a layer starts in and stays in when never\n * told where the viewport is.\n *\n * @param rect - The residency rectangle, or `null` to draw every edge.\n *\n * @returns This layer, for method chaining.\n *\n * @internal Framework wiring between `DiagramView` and its edge layer; application code does not call this.\n */\n setResidency(rect: DiagramRect | null): this {\n this._residency = rect;\n this.recomputeResidentEdges();\n this.updateDrawnEdges();\n\n return this;\n }\n\n /**\n * Draws one edge's invisible wide hit path: same `d` as the visible path,\n * transparent stroke, and the only element in the layer that opts back\n * into pointer events (`pointer-events: stroke`) — the root `<svg>` stays\n * inert. Appended before the visible path.\n *\n * @param group - The edge group handle to append into.\n * @param d - The edge's path data (shared with the visible path).\n * @returns The hit path's handle.\n */\n private drawHitPath(group: Handle, d: string): Handle {\n const hit = DOM.sink.createElementNS(SVG_NS, \"path\");\n\n DOM.sink.apply(hit, { setAttr: {\n d,\n fill: \"none\",\n stroke: \"transparent\",\n \"stroke-width\": String(EDGE_HIT_WIDTH),\n \"pointer-events\": \"stroke\",\n // Dragging an edge pans the canvas like empty canvas does, so the\n // hit path takes the viewport's own live grab/grabbing cursor by\n // inheriting rather than promising a cursor of its own.\n cursor: \"inherit\",\n } });\n\n DOM.sink.appendChild(group, hit);\n this.trackHandle(hit);\n\n return hit;\n }\n\n /**\n * Draws one edge's visible stroked path, carrying its markers and dash. The\n * edge's emphasis state is not written here — it is the group the path is\n * appended into (see {@link DIMMED_EDGE_OPACITY}).\n *\n * @param group - The edge group handle to append into.\n * @param edge - The routed edge to draw.\n * @param d - The edge's path data.\n * @returns The visible path's handle.\n */\n private drawVisiblePath(group: Handle, edge: DiagramEdgeRoute, d: string): Handle {\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(group, path);\n this.trackHandle(path);\n\n return path;\n }\n\n /**\n * Releases one previously-drawn edge's elements (hit path, visible path,\n * and optional label) from the group they were appended into and from the\n * tracked-handle set.\n *\n * @param drawn - The drawn edge record to release.\n */\n private releaseDrawnEdge(drawn: DrawnEdge): void {\n const handles = [drawn.hit, drawn.path, ...(drawn.label ? [drawn.label] : [])];\n\n for (const handle of handles) {\n DOM.sink.removeChild(drawn.group, handle);\n this.untrackHandle(handle);\n DOM.sink.release(handle);\n }\n }\n\n /**\n * Draws one edge's optional mid-route label as a `<text>` element.\n *\n * @param group - The edge group handle to append into, which also carries\n * the label's emphasis state (see {@link DIMMED_EDGE_OPACITY}).\n * @param edge - The edge route the label belongs to (for its anchor point).\n * @param label - The label text.\n * @returns The label's handle.\n */\n private drawLabel(group: Handle, edge: DiagramEdgeRoute, label: string): Handle {\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(group, text);\n this.trackHandle(text);\n\n return 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// One `<svg>` Component drawing a diagram's low-zoom simplified nodes as\n// plain `<rect>` boxes — one per node, standing in for the node component\n// `DiagramView` stops mounting once level-of-detail simplification engages.\n// Follows the same SVG-through-the-seam pattern as `DiagramEdgeLayer`: the\n// root `<svg>` and every leaf `<rect>` are created via\n// `DOM.sink.createElementNS` and tracked with `trackHandle`, never raw DOM.\n// The layer's root stays non-interactive (`pointer-events: none`) —\n// `DiagramView.nodeIdAtGraphPoint` resolves a click geometrically against the\n// laid-out boxes instead of hit-testing an element.\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport type { DiagramRect } from \"~/component/diagram/DiagramResidency.js\";\nimport {\n DIAGRAM_NODE_BACKGROUND_COLOR,\n DIAGRAM_NODE_BORDER_COLOR,\n DIAGRAM_NODE_SELECTED_BACKGROUND_COLOR,\n DIAGRAM_NODE_SELECTED_BORDER_COLOR,\n} from \"~/component/diagram/DiagramNode.js\";\nimport { DIAGRAM_GROUP_BACKGROUND_COLOR, DIAGRAM_GROUP_BORDER_COLOR } from \"~/component/diagram/DiagramGroupNode.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** SVG namespace URI. */\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/**\n * Corner radius (SVG user units) of a simplified rect — the unitless twin of\n * the 4px `borderRadius` both `DiagramNode` and `DiagramGroupNode` declare,\n * so a simplified node keeps the silhouette of the component it stands in for.\n */\nconst RECT_RADIUS = \"4\";\n\n/** Stroke width of a simplified rect, matching the 1px border both renderers declare. */\nconst RECT_STROKE_WIDTH = \"1\";\n\n/**\n * Opacity of a node component outside a non-empty node-emphasis set. Higher\n * than `DiagramEdgeLayer`'s `DIMMED_EDGE_OPACITY` (`0.15`) because perceived\n * presence scales with area, not just alpha: a `TableCardNode`-sized box with\n * a border and text rows still reads at `0.15`, whereas a 1.5px hairline\n * needs the lower number to recede. `0.35` keeps a dimmed card's shape and\n * label legible while clearly receding behind the emphasised ones.\n *\n * @internal\n */\nexport const DIMMED_NODE_OPACITY = 0.35;\n\n/**\n * The SVG low-zoom node layer for a\n * [`DiagramView`](/api/component/diagram/classes/DiagramView). Owns exactly\n * one `<svg>` element and draws one `<rect>` per node id it is given, in\n * place of the node component `DiagramView` stops mounting once\n * level-of-detail simplification engages. Never re-exported from the package\n * entry point — this is framework-internal wiring, not a documented component.\n */\nclass DiagramNodeLayer extends Component<ComponentOptions> {\n\n /** The rects to draw, keyed by node id. Compared by identity in `setNodes`. */\n private _rects: Map<string, DiagramRect> = new Map();\n\n /** Ids of `_rects` that are compound containers rather than leaves. */\n private _containerIds: ReadonlySet<string> = new Set();\n\n /** The rect drawn per node id, in paint order: containers first, then leaves. */\n private _drawn: Map<string, Handle> = new Map();\n\n /** The currently selected leaf id, or `null`. A container never paints selected. */\n private _selected: string | null = null;\n\n /** Ids of the emphasised nodes; every other drawn rect dims. */\n private _emphasis: ReadonlySet<string> = new Set();\n\n constructor(options?: ComponentOptions) {\n super(options);\n\n // Non-interactive overlay: nothing hit-tests against a rect —\n // `DiagramView.nodeIdAtGraphPoint` resolves clicks geometrically\n // against the laid-out boxes instead (mirrors `DiagramEdgeLayer`'s\n // own reasoning for its root).\n this.setPointerEvents(\"none\");\n\n // Inherits the viewport's live grab/grabbing cursor rather than\n // promising one of its own — a press over a simplified node still\n // pans (mirrors `DiagramEdgeLayer`'s own reasoning for its root).\n this.setCursor(\"inherit\");\n }\n\n /**\n * Creates the root `<svg>` element.\n *\n * @returns The root `<svg>` handle.\n */\n protected createRootElement(): Handle {\n return DOM.sink.createElementNS(SVG_NS, \"svg\");\n }\n\n /**\n * Renders the root element and draws the cached rects.\n *\n * @returns The rendered root element.\n */\n protected render(): Handle {\n const element = super.render();\n\n this.redraw();\n\n return element;\n }\n\n /**\n * Replaces the drawn rects. A no-op when both `rects` and `containerIds`\n * are identical (`===`) to what this layer already holds — exact rather\n * than approximate, because `DiagramView` never mutates either map in\n * place (see the plan's `[^identity]` note): a pan or a zoom that changes\n * neither costs zero DOM writes.\n *\n * @param rects - The boxes to draw, keyed by node id.\n * @param containerIds - Ids of `rects` that are compound containers.\n *\n * @returns This layer, for method chaining.\n *\n * @internal Framework wiring between `DiagramView` and its node layer; application code does not call this.\n */\n setNodes(rects: Map<string, DiagramRect>, containerIds: ReadonlySet<string>): this {\n if (rects === this._rects && containerIds === this._containerIds) {\n return this;\n }\n\n this._rects = rects;\n this._containerIds = containerIds;\n\n if (this.getElement()) {\n this.redraw();\n } else {\n this.onFirstLayout(() => this.redraw());\n }\n\n return this;\n }\n\n /**\n * Sets the selected leaf id, repainting only the outgoing and incoming\n * rects. A container id is accepted but never paints selected — it has\n * no `setSelected` visual, mirroring `DiagramGroupNode`.\n *\n * @param id - The node id to select, or `null` to clear.\n *\n * @returns This layer, for method chaining.\n *\n * @internal\n */\n setSelected(id: string | null): this {\n const previous = this._selected;\n\n this._selected = id;\n\n if (previous !== null) {\n this.repaint(previous);\n }\n\n if (id !== null) {\n this.repaint(id);\n }\n\n return this;\n }\n\n /**\n * Sets the emphasised node ids, repainting every drawn rect's opacity.\n *\n * @param ids - The node ids to emphasise; an empty set clears emphasis.\n *\n * @returns This layer, for method chaining.\n *\n * @internal\n */\n setEmphasis(ids: ReadonlySet<string>): this {\n this._emphasis = ids;\n\n for (const id of this._drawn.keys()) {\n this.repaint(id);\n }\n\n return this;\n }\n\n /**\n * Releases every previously-drawn rect, then draws every id in\n * `_containerIds` first and every other id second, so document order\n * puts container washes behind leaf boxes. No-op before the element\n * exists — `setNodes` defers to the first connected layout, and `render`\n * performs the first draw.\n */\n private redraw(): void {\n const root = this.getElement();\n\n if (!root) {\n return;\n }\n\n for (const handle of this._drawn.values()) {\n this.releaseDrawnRect(root, handle);\n }\n\n this._drawn.clear();\n\n for (const [id, rect] of this._rects) {\n if (this._containerIds.has(id)) {\n this.drawRect(root, id, rect);\n }\n }\n\n for (const [id, rect] of this._rects) {\n if (!this._containerIds.has(id)) {\n this.drawRect(root, id, rect);\n }\n }\n }\n\n /**\n * Creates one `<rect>` through the seam, applies its attributes, appends\n * it to the root, and tracks the handle.\n *\n * @param root - The root `<svg>` handle to append into.\n * @param id - The node id the rect stands in for.\n * @param rect - The node's laid-out box.\n */\n private drawRect(root: Handle, id: string, rect: DiagramRect): void {\n const el = DOM.sink.createElementNS(SVG_NS, \"rect\");\n\n DOM.sink.apply(el, { setAttr: this.rectAttrs(id, rect) });\n DOM.sink.appendChild(root, el);\n this.trackHandle(el);\n\n this._drawn.set(id, el);\n }\n\n /**\n * Releases one previously-drawn rect from the root and from the\n * tracked-handle set.\n *\n * @param root - The root `<svg>` handle the rect was appended into.\n * @param handle - The rect's handle.\n */\n private releaseDrawnRect(root: Handle, handle: Handle): void {\n DOM.sink.removeChild(root, handle);\n this.untrackHandle(handle);\n DOM.sink.release(handle);\n }\n\n /**\n * Re-applies `id`'s current attributes to its already-drawn rect. A\n * no-op when `id` has never been drawn — `setSelected`/`setEmphasis` may\n * be called for a graph not yet drawn (state applied before a draw\n * survives it, since `rectAttrs` reads `_selected`/`_emphasis` at draw\n * time too).\n *\n * @param id - The node id to repaint.\n */\n private repaint(id: string): void {\n const handle = this._drawn.get(id);\n const rect = this._rects.get(id);\n\n if (handle === undefined || rect === undefined) {\n return;\n }\n\n // Attribute removals apply before sets (see `ElementPatch`'s own\n // ordering doc), so an unconditional `opacity` removal followed by\n // `rectAttrs`' conditional re-set is what lets a rect go from dimmed\n // back to resting — `rectAttrs` simply omits the key when it no\n // longer applies.\n DOM.sink.apply(handle, { removeAttr: [\"opacity\"], setAttr: this.rectAttrs(id, rect) });\n }\n\n /**\n * The attribute bag for `id`'s rect: its box, corner radius, stroke\n * width, and the fill/stroke/opacity the paint-order table in the plan's\n * Architecture Decisions describes — a container ignores selection and\n * always carries the group colours; a leaf carries the node colours,\n * swapped to the selected pair when `id` is the selected leaf; either\n * carries `opacity` when a non-empty emphasis set excludes `id`.\n *\n * @param id - The node id.\n * @param rect - The node's laid-out box.\n * @returns The attribute bag to apply to the rect.\n */\n private rectAttrs(id: string, rect: DiagramRect): Record<string, string> {\n const container = this._containerIds.has(id);\n const selected = !container && this._selected === id;\n\n const fill = container\n ? DIAGRAM_GROUP_BACKGROUND_COLOR\n : (selected ? DIAGRAM_NODE_SELECTED_BACKGROUND_COLOR : DIAGRAM_NODE_BACKGROUND_COLOR);\n const stroke = container\n ? DIAGRAM_GROUP_BORDER_COLOR\n : (selected ? DIAGRAM_NODE_SELECTED_BORDER_COLOR : DIAGRAM_NODE_BORDER_COLOR);\n\n const attrs: Record<string, string> = {\n x: String(rect.x),\n y: String(rect.y),\n width: String(rect.width),\n height: String(rect.height),\n rx: RECT_RADIUS,\n fill,\n stroke,\n \"stroke-width\": RECT_STROKE_WIDTH,\n };\n\n if (this._emphasis.size > 0 && !this._emphasis.has(id)) {\n attrs.opacity = String(DIMMED_NODE_OPACITY);\n }\n\n return attrs;\n }\n}\n\nconst DiagramNodeLayerCallable = callable(DiagramNodeLayer);\ntype DiagramNodeLayerCallable = DiagramNodeLayer;\nexport {\n DiagramNodeLayer as _DiagramNodeLayer,\n DiagramNodeLayerCallable as DiagramNodeLayer,\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 (an Anchor-managed Panel viewport) owns a single\n// content host Container (Absolute layout) that carries the pan/zoom transform\n// and holds the node components + the edge layer, plus a corner-pinned control\n// cluster (zoom in/out, fit, reset). Pan is an unbounded `translate()` on the\n// content host's transform, not native scroll — the viewport has no scrollbars\n// and simply clips (`overflow: hidden`) whatever pans outside it, giving an\n// infinite-canvas feel. Zoom is the transform's `scale()` factor.\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 { Anchor } from \"~/layout/Anchor.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { FloatingPanel } from \"~/component/container/FloatingPanel.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { ProgressSpinner } from \"~/component/display/ProgressSpinner.js\";\nimport { plus } from \"~/glyphs/solid/plus.js\";\nimport { minus } from \"~/glyphs/solid/minus.js\";\nimport { expand } from \"~/glyphs/solid/expand.js\";\nimport { crosshairs } from \"~/glyphs/solid/crosshairs.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, DiagramEdgeData, 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 { DiagramNodeLayer, DIMMED_NODE_OPACITY } from \"~/component/diagram/DiagramNodeLayer.js\";\nimport { anyRectIntersects, computeResidentIds, inflateRect, residencyNeedsRefresh } from \"~/component/diagram/DiagramResidency.js\";\nimport type { DiagramRect } from \"~/component/diagram/DiagramResidency.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(plus, minus, expand, crosshairs);\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\" | \"contextmenu\" | \"edgehover\" | \"edgeleave\";\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/** Multiplicative zoom step per control-cluster button press. */\nconst ZOOM_BUTTON_STEP = 1.5;\n\n/**\n * Pixel distance the pointer must travel between `pointerdown` and a later\n * `pointermove` before the press-and-release it ends in counts as a drag\n * rather than a click. Matches `DragManager.DRAG_THRESHOLD`, the library's\n * only other click-versus-drag disambiguation.\n */\nconst CLICK_SLOP = 4;\n\n// Diameter in pixels of the busy overlay's arc. Matches `TablePanel`'s\n// store-loading spinner and `createSpinnerWrap`'s lazy-tab placeholder, so a\n// slow diagram update and a slow data load read as the same kind of wait.\nconst BUSY_SPINNER_DIAMETER = 24;\n\n// Structural breathing room between the control cluster and the viewport\n// corner it is pinned to — not a cosmetic choice, but the inset the Anchor\n// constraint needs so the cluster does not paint flush against the edge.\nconst CONTROLS_MARGIN = 12;\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 * How far beyond the visible graph rectangle nodes stay mounted and edges\n * stay drawn, as a fraction of the viewport's own extent on each side — so\n * the residency rect is twice the viewport in each axis. The diagram's\n * counterpart to the row pool's ±2-row scroll buffer: rows have a uniform\n * pitch to count in, diagram nodes and edges do not, so the buffer is\n * expressed in viewports. Half a viewport is what lets the refresh threshold\n * (half of this margin, a quarter viewport of travel) absorb a fast drag\n * without a node or edge appearing at the viewport edge.\n */\nconst RESIDENCY_MARGIN = 0.5;\n\n/**\n * Fewest node components a graph must have before low-zoom simplification\n * engages at all. Below this there is nothing worth saving — a hundred nodes\n * is a few hundred elements, not the several thousand that make a fit-zoom\n * repaint expensive — and a small diagram the user deliberately zoomed out\n * of should keep rendering what it always rendered.\n */\nconst LOD_MIN_NODES = 200;\n\n/**\n * Rendered height, in CSS pixels, below which a node's own content stops\n * being worth drawing. `DiagramNode` spends 8px of its box on interior\n * insets, so at 16px the label's glyphs resolve to about 6px — under the\n * point where a table name can be read, and under the point where a 1px\n * border and a 4px corner radius resolve at all.\n */\nconst LOD_ENGAGE_HEIGHT = 16;\n\n/**\n * Rendered height at which full node components come back. Higher than\n * LOD_ENGAGE_HEIGHT so the two form a hysteresis band: two wheel notches\n * (1.1x each) inside the band cannot flip the mode back, which is what keeps\n * a zoom burst near the boundary from mounting and unmounting every node\n * component repeatedly. Same idea as the residency trigger rect, on the\n * zoom axis instead of the pan axis.\n */\nconst LOD_DISENGAGE_HEIGHT = 20;\n\n/** The empty rect map handed to the node layer when the view is not simplified;\n * a shared instance so the layer's identity check sees no change between passes. */\nconst EMPTY_RECTS: Map<string, DiagramRect> = new Map();\n\n/**\n * The median height of the leaf boxes in `rects`; `0` when there are none.\n * Containers are excluded because a compound graph's few container boxes are\n * hundreds of units tall and would drag the statistic away from what the\n * user is actually reading. The median (rather than the mean) keeps one\n * outsized leaf from doing the same.\n *\n * @param rects - Laid-out box per node id.\n * @param containerIds - Ids of the compound container nodes to exclude.\n * @returns The median leaf height, or `0` when `rects` holds no leaf.\n *\n * @internal\n */\nexport function medianLeafHeight(rects: Map<string, DiagramRect>, containerIds: ReadonlySet<string>): number {\n const heights: number[] = [];\n\n for (const [id, rect] of rects) {\n if (!containerIds.has(id)) {\n heights.push(rect.height);\n }\n }\n\n if (heights.length === 0) {\n return 0;\n }\n\n heights.sort((a, b) => a - b);\n\n return heights[Math.floor(heights.length / 2)];\n}\n\n/**\n * Whether the view should draw simplified node boxes rather than mounting\n * node components: `nodeCount` must reach `LOD_MIN_NODES`, `medianHeight`\n * must be a genuine positive measurement, and the rendered height —\n * `medianHeight * zoom` — must be under the engage threshold, or under the\n * (higher) disengage threshold when already simplified. That last branch is\n * the hysteresis: two wheel notches either side of the boundary cannot flip\n * the whole graph back and forth.\n *\n * @param nodeCount - The number of node components in the graph.\n * @param medianHeight - The median leaf height, from {@link medianLeafHeight}.\n * @param zoom - The current zoom factor.\n * @param simplified - Whether the view is currently simplified.\n * @returns Whether the view should draw simplified node boxes.\n *\n * @internal\n */\nexport function shouldSimplify(nodeCount: number, medianHeight: number, zoom: number, simplified: boolean): boolean {\n if (nodeCount < LOD_MIN_NODES || !(medianHeight > 0)) {\n return false;\n }\n\n const rendered = medianHeight * zoom;\n\n return rendered < (simplified ? LOD_DISENGAGE_HEIGHT : LOD_ENGAGE_HEIGHT);\n}\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 /**\n * URL of a consumer-hosted `elk-worker.js`, requesting off-thread layout.\n * With the `elk.bundled.js` module this view's engine imports, elkjs's\n * own worker-availability check always fails, so `workerUrl` alone never\n * actually constructs a Worker; layout still runs on the main thread, via\n * elkjs's own fallback. Pass {@link DiagramViewOptions.elkWorkerFactory}\n * for real off-thread execution.\n */\n elkWorkerUrl?: string;\n /**\n * Factory returning a Web Worker for off-thread ELK layout. When set,\n * ELK's compute runs in the returned worker. Construct it in your app so\n * your bundler emits the worker, e.g.\n * `() => new Worker(new URL(\"elkjs/lib/elk-worker.min.js\", import.meta.url), { type: \"classic\" })`.\n * Takes precedence over {@link DiagramViewOptions.elkWorkerUrl} when both\n * are set.\n */\n elkWorkerFactory?: () => Worker;\n /** Minimum zoom factor (default 0.25). */\n minZoom?: number;\n /** Maximum zoom factor (default 4). */\n maxZoom?: number;\n /**\n * Initial zoom factor (default 1). Overridden on the view's first\n * successful centring by {@link DiagramViewOptions.fitOnLoad}'s default\n * of `true`, unless `fitOnLoad: false` is passed or a focus node wins.\n */\n zoom?: number;\n /** Show the built-in zoom / fit / reset control cluster (default true). */\n controls?: boolean;\n /** Draw a simplified node box instead of the node component at low zoom (default true). */\n simplifyAtLowZoom?: boolean;\n /**\n * Id of the node the one-shot initial view centres on, instead of the\n * graph's bounds. An id naming no node in the graph falls back to the\n * bounds — fitted by default, per\n * {@link DiagramViewOptions.fitOnLoad}, or held at the configured `zoom`\n * when `fitOnLoad: false`. When the id does name a node, the configured\n * `zoom` is honoured, except that a focus node too large to fit the\n * viewport lowers it until the node fits.\n */\n initialFocusNode?: string;\n /**\n * Fit the whole graph to the viewport on the view's first centring,\n * instead of holding the configured `zoom` (default `true`). The scale\n * chosen is the one {@link DiagramView.zoomToFit} would choose — the\n * largest at which the graph bounds fit both axes — so the view opens\n * exactly as the built-in \"Fit to view\" control would leave it. Applies\n * once, to the first centring that succeeds, and only when no focus node\n * is set: {@link DiagramViewOptions.initialFocusNode} still wins, and\n * neither a later `setData` nor the \"Reset view\" control is affected.\n * Pass `false` to hold the configured `zoom` instead — the opening every\n * `DiagramView` had before this option existed.\n */\n fitOnLoad?: boolean;\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 contextmenu?: (node: DiagramNodeData, event: MouseEvent) => void;\n edgehover?: (edges: DiagramEdgeData[], event: MouseEvent) => void;\n edgeleave?: () => 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 simplified-node layer, a persistent child of the content host beside the edge layer. */\n private _nodeLayer!: DiagramNodeLayer;\n\n /** The ELK layout adapter. Runtime state, off the options bag. */\n private _engine!: ElkLayoutEngine;\n\n /** Node components keyed by node id — the graph currently on screen. */\n private _nodeComponents: Map<string, Component> = new Map();\n\n /** Laid-out box per node id, from the last successful layout — the graph currently on screen. */\n private _nodeRects: Map<string, DiagramRect> = new Map();\n\n /** Node model data keyed by node id (selection payload source) — the graph currently on screen. */\n private _nodeData: Map<string, DiagramNodeData> = new Map();\n\n /** Ids of the compound container nodes in the graph currently on screen. */\n private _containerIds: Set<string> = new Set();\n\n /**\n * The graph built by the latest `setData`, mounted hidden and awaiting a\n * layout. Promoted into the shown set above by `applyLayout`, discarded by\n * `handleLayoutFailure` or by the next `rebuildNodes`. Kept separate from\n * the shown set so a re-layout leaves the previous graph on screen for the\n * whole ELK round-trip instead of blanking the canvas.\n */\n private _incomingComponents: Map<string, Component> = new Map();\n /** Boxes for the graph awaiting a layout; promoted beside `_incomingComponents`. */\n private _incomingRects: Map<string, DiagramRect> = new Map();\n private _incomingData: Map<string, DiagramNodeData> = new Map();\n private _incomingContainerIds: Set<string> = new Set();\n\n /** Ids of the node components currently added to the content host. */\n private _residentIds: Set<string> = new Set();\n\n /** Visible graph rectangle the resident set was last computed for; `null` forces a rebuild. */\n private _residencyViewport: DiagramRect | null = null;\n\n /** Whether the view is currently drawing simplified node boxes instead of mounting node components. */\n private _simplified: boolean = false;\n\n /** Currently selected node data (single-select). */\n private _selection: DiagramNodeData[] = [];\n\n /**\n * Ids of the emphasised nodes; every other node component is dimmed.\n * Runtime interaction state, off the options bag — the view's own mirror\n * of `DiagramEdgeLayer`'s `_edgeEmphasis`, except there is no equivalent\n * layer for nodes, so it lives here instead. Cleared in\n * `promoteIncomingNodes` beside `_selection`.\n */\n private _nodeEmphasis: Set<string> = new Set();\n\n /** Monotonic layout token; guards against a stale in-flight layout landing. */\n private _layoutGeneration: number = 0;\n\n /** Resolver for the promise `whenLaidOut` hands out, or `null` when idle. */\n private _layoutSettled: { promise: Promise<void>; resolve: () => void } | null = null;\n\n /**\n * The overlay busy indicator, built the first time a layout pass runs on a\n * view that has a size. Runtime state, deliberately off the options bag.\n */\n private _busySpinner: ProgressSpinner | null = null;\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\"` / `\"contextmenu\"`. */\n private _listeners: ListenerBag<DiagramViewEvent> = this.registerListenerBag(new ListenerBag<DiagramViewEvent>());\n\n /** Edge ids of the last emitted \"edgehover\", joined, or null when not hovering. */\n private _hoveredEdgeKey: string | null = null;\n\n /** Pan drag state. */\n private _panning: boolean = false;\n private _panStartX: number = 0;\n private _panStartY: number = 0;\n\n /**\n * Pointer position at the last `pointerdown`, and whether the pointer has\n * since travelled past `CLICK_SLOP`. Runtime gesture state, off the\n * options bag: `_handleClick` reads `_pointerMoved` to tell a click from\n * the tail end of a drag — a pan that starts and ends on empty canvas\n * still fires a native `click`, and it must not clear the selection (or\n * the edge emphasis the app keys off it).\n */\n private _pressX: number = 0;\n private _pressY: number = 0;\n private _pointerMoved: boolean = false;\n\n /** Pan offset (viewport pixels) captured when a drag begins. */\n private _panOriginX: number = 0;\n private _panOriginY: number = 0;\n\n /** Current, unbounded pan offset (viewport pixels) driving the content host's transform. */\n private _panX: number = 0;\n private _panY: number = 0;\n\n /**\n * Whether the graph still owes its one-time initial centring. Cleared by\n * the first layout that manages to centre, so a later `setData` re-layout\n * never yanks a pan the user has since dragged to — except that a later\n * layout re-arms this flag when the graph it just promoted lands entirely\n * outside the viewport, via `rearmCentreIfOffScreen`, so a `setData` swap\n * at a very different scale never leaves the view showing an empty canvas.\n */\n private _needsInitialCentre: boolean = true;\n\n /**\n * Whether this view has ever completed a centring. Set by the first\n * `tryInitialCentre` pass that actually centres, and forced by\n * `resetView` before it re-arms, so a Reset click is never treated as a\n * first load. Never cleared. Deliberately separate from\n * `_needsInitialCentre`, which is a pending *request* that `resetView`,\n * `focusNode`, and `rearmCentreIfOffScreen` all legitimately re-arm:\n * this flag records that the first-load moment has passed, so the\n * `fitOnLoad` option fits exactly once.\n */\n private _hasCentredOnce: boolean = false;\n\n /**\n * Id of the node the one-shot initial view centres on, instead of the\n * graph's bounds; also the target of the most recent `focusNode` call.\n * Seeded from `initialFocusNode` in the constructor body (mirroring how\n * `data`/`controls` are cached during `applyOptions` and dispatched once\n * the constructor body can act on them) and re-pointed by `focusNode`.\n */\n private _focusNodeId: string | null = null;\n\n /**\n * Viewport size at the last layout, so a resize can be measured as a delta\n * and the graph point under the viewport centre held in place. `NaN` until\n * the view is first sized (matching `getWidth`/`getHeight`).\n */\n private _lastViewportWidth: number = NaN;\n private _lastViewportHeight: number = NaN;\n\n /** The corner-pinned zoom / fit / reset control cluster. */\n private _controls!: FloatingPanel;\n private _zoomInBtn!: Button;\n private _zoomOutBtn!: Button;\n private _fitBtn!: Button;\n private _resetBtn!: Button;\n\n private readonly _onZoomIn: () => void = () => this.zoomIn();\n private readonly _onZoomOut: () => void = () => this.zoomOut();\n private readonly _onFit: () => void = () => this.zoomToFit();\n private readonly _onReset: () => void = () => this.resetView();\n\n constructor(options?: DiagramViewOptions) {\n super(options, {\n zoom: DEFAULT_ZOOM, minZoom: DEFAULT_MIN_ZOOM, maxZoom: DEFAULT_MAX_ZOOM,\n controls: true, simplifyAtLowZoom: true,\n });\n\n this.setLayoutManager(new Anchor());\n this.setCursor(\"grab\");\n\n this._engine = this.createEngine();\n\n // Nodes are laid out at unscaled graph coordinates under the host's\n // `translate(panX,panY) scale(zoom)` transform, and the host's own box\n // is likewise sized to the unscaled graph bounds (see applyLayout), so\n // node coordinates never exceed the box regardless of zoom — unlike the\n // old scaled-box model this stays visible for consistency with that\n // history rather than out of present necessity.\n // `cursor: \"inherit\"` rather than the Component default: the host is an\n // invisible box spanning the whole graph bounds, so the default\n // `cursor: default` every Component stamps would paint an arrow across\n // the entire canvas and mask the viewport's own grab/grabbing. Inheriting\n // lets the single write on the view root govern the whole canvas.\n this._contentHost = new Container({ layoutManager: new Absolute(), overflow: \"visible\", cursor: \"inherit\" });\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 // Added right after the edge layer: in a flat graph both sit at\n // DEFAULT_Z_INDEX, and document order is what paints node rects over\n // edges (see applyContainerZIndex for the compound-graph z-indexing).\n this._nodeLayer = new DiagramNodeLayer();\n this._contentHost.addComponent(this._nodeLayer);\n\n this.buildControls();\n this.wireControlListeners();\n\n this.addComponent(this._controls, this._controls.getAnchorConstraints());\n\n this.applyListeners(options?.listeners);\n\n this.setControlsVisible(this._options.controls ?? this._defaultOptions.controls ?? true);\n\n if (this._options.zoom !== undefined) {\n this.setZoom(this._options.zoom);\n }\n\n this._focusNodeId = this._options.initialFocusNode ?? null;\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({\n workerFactory: this._options.elkWorkerFactory,\n workerUrl: this._options.elkWorkerUrl,\n });\n }\n\n /**\n * Disposes the layout engine — releasing its ELK Web Worker, if it had one\n * — before the inherited destructor detaches the element.\n */\n protected destructor(): void {\n // Invalidate any layout still in flight before the engine goes away. A\n // result landing afterwards would write into a torn-down view, and a\n // failure landing afterwards would strip nodes off it; both guards\n // compare against this token and drop a stale one.\n this._layoutGeneration += 1;\n\n // Settle rather than leave dangling: a caller awaiting `whenLaidOut()`\n // (e.g. the app holding a lazy tab's spinner) must not hang forever\n // just because the view was disposed on some other path mid-pass.\n this.settleLayout();\n\n // The spinner is mounted by a raw DOM append rather than as a child\n // component, so the inherited destructor's child pass never reaches it.\n this._busySpinner?.dispose();\n this._busySpinner = null;\n\n // A resident node component is a content-host child, so the inherited\n // destructor's own child recursion (through `_contentHost`) reaches\n // and disposes it. An unmounted one is detached with no parent to\n // recurse through, so it must be disposed explicitly here — without\n // this, tearing the view down would strand every non-resident node's\n // per-instance stylesheet rule.\n for (const [id, component] of this._nodeComponents) {\n if (!this._residentIds.has(id)) {\n component.dispose();\n }\n }\n\n this._engine.dispose();\n\n super.destructor();\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.elkWorkerFactory !== undefined) this._options.elkWorkerFactory = options.elkWorkerFactory;\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 // Cached only: the control cluster does not exist yet during the\n // `super()` cascade. The constructor dispatches `setControlsVisible`\n // itself once the cluster is built.\n if (options.controls !== undefined) this._options.controls = options.controls;\n // Cached only: the getter folds the class default (see\n // `isSimplifyAtLowZoom`), and the effect is read by `updateResidency`\n // rather than dispatched here — there is nothing to apply yet during\n // the `super()` cascade, before the content host exists.\n if (options.simplifyAtLowZoom !== undefined) this._options.simplifyAtLowZoom = options.simplifyAtLowZoom;\n // Cached only: dispatched into the runtime `_focusNodeId` field from\n // the constructor body, mirroring `data` below.\n if (options.initialFocusNode !== undefined) this._options.initialFocusNode = options.initialFocusNode;\n // Cached only: read once by `tryInitialCentre`, which is where the\n // view's first centring decides between fitting and holding the zoom.\n if (options.fitOnLoad !== undefined) this._options.fitOnLoad = options.fitOnLoad;\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 * Builds fresh node components via the node renderer (default:\n * `DiagramNode`) into the *incoming* set, built and measured off the\n * component tree and left `setVisible(false)` — not `display: none`, so\n * the flag still reads false the instant a component mounts, before the\n * promote step's reveal. `applyLayout` positions them, then\n * `promoteIncomingNodes` swaps them in for the shown set, mounts them, and\n * reveals them together, so a diagram never paints an unplaced graph and a\n * re-layout keeps the previous graph on screen for the whole ELK\n * round-trip. A graph superseded by a newer `setData` before its layout\n * lands is therefore never mounted at all. Leads with\n * `discardIncomingNodes()` so a second `setData` arriving before the\n * first's layout lands does not leak the first attempt's components.\n * Recurses into `children`: a container node (non-empty `children`) is\n * built via the group renderer (default: `DiagramGroupNode`) and its\n * children are built the same way, so container + leaf components all\n * land as flat siblings in `_incomingComponents`/`_incomingData`, keyed by\n * id — a container is never a DOM parent of its children (that would\n * perturb the single-content-host model `nodeIdAt` hit-testing relies on).\n *\n * @param data - The graph whose nodes to build.\n */\n private rebuildNodes(data: DiagramData): void {\n this.discardIncomingNodes();\n\n const renderer = this._options.nodeRenderer ?? ((node: DiagramNodeData): Component =>\n new DiagramNode({ label: node.label, glyph: node.glyph, badge: node.badge }));\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 component.setVisible(false);\n\n this._incomingComponents.set(node.id, component);\n this._incomingData.set(node.id, node);\n\n if (isContainer) {\n this._incomingContainerIds.add(node.id);\n build(node.children!);\n }\n }\n };\n\n build(data.nodes);\n }\n\n /** Forgets the un-promoted incoming components; never mounted, so nothing to detach. */\n private discardIncomingNodes(): void {\n this._incomingComponents.clear();\n this._incomingRects.clear();\n this._incomingData.clear();\n this._incomingContainerIds.clear();\n }\n\n /**\n * Swaps the incoming set in for the shown one: disposes every shown\n * component (they are being discarded, not re-parented, so a bare\n * `removeComponent` would leak their listeners/theme subscriptions/style\n * rules — see `Component.destructor`), detaching first only the ones\n * currently mounted — an unmounted component has no content-host parent\n * to detach from — promotes the incoming maps, clears the selection and\n * the node emphasis, reveals every promoted component together, then\n * rebuilds the residency set from scratch so the new graph mounts\n * whichever of its nodes are near the viewport. This is the first time\n * an incoming component can be added to the content host at all.\n */\n private promoteIncomingNodes(): void {\n for (const [id, component] of this._nodeComponents) {\n if (this._residentIds.has(id)) {\n this._contentHost.removeComponent(component);\n }\n\n component.dispose();\n }\n\n this._residentIds = new Set();\n this._residencyViewport = null;\n\n this._nodeComponents = this._incomingComponents;\n this._nodeRects = this._incomingRects;\n this._nodeData = this._incomingData;\n this._containerIds = this._incomingContainerIds;\n\n this._incomingComponents = new Map();\n this._incomingRects = new Map();\n this._incomingData = new Map();\n this._incomingContainerIds = new Set();\n\n this._selection = [];\n this._nodeEmphasis = new Set();\n this._nodeLayer.setSelected(null);\n this._nodeLayer.setEmphasis(new Set());\n\n for (const component of this._nodeComponents.values()) {\n component.setVisible(true);\n }\n\n this.updateResidency();\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 this.armLayoutSettled();\n this.syncBusyIndicator();\n\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 * Arms the awaitable `whenLaidOut` hands out, so one deferred spans\n * however many layout passes run before one of them finishes. A no-op\n * when a pass is already in flight, so two rapid `setData` calls share\n * one promise, resolved by whichever pass settles it.\n */\n private armLayoutSettled(): void {\n if (this._layoutSettled !== null) {\n return;\n }\n\n let resolve!: () => void;\n const promise = new Promise<void>((r) => { resolve = r; });\n\n this._layoutSettled = { promise, resolve };\n }\n\n /** Settles the armed `whenLaidOut` awaitable, if there is one. */\n private settleLayout(): void {\n const settled = this._layoutSettled;\n\n this._layoutSettled = null;\n settled?.resolve();\n\n this.syncBusyIndicator();\n }\n\n /**\n * Matches the overlay busy indicator to whether a layout pass is in flight,\n * and re-sizes a showing overlay to the current viewport. Called when a pass\n * starts, when one settles, and from every layout pass — which is where a\n * view that had no size when its pass started finally gets one.\n *\n * A view with no committed size has nothing to cover: `getWidth()` /\n * `getHeight()` are `NaN` (not 0) until the first `setSize`, and `> 0` rejects\n * both (see `effectiveMinZoom`). That also keeps the indicator off the first\n * pass every diagram runs from its own constructor, before its host has sized\n * it — whatever opened the view owns that first wait.\n */\n private syncBusyIndicator(): void {\n if (this._layoutSettled === null || !(this.getWidth() > 0) || !(this.getHeight() > 0)) {\n this._busySpinner?.hideOverlay();\n\n return;\n }\n\n if (this._busySpinner === null) {\n this._busySpinner = new ProgressSpinner(BUSY_SPINNER_DIAMETER);\n }\n\n // `showOverlay` is a no-op once shown, so the explicit `doLayout` is what\n // re-sizes a showing overlay after a viewport resize: the spinner is\n // mounted by a raw DOM append, so it is not in this view's laid-out set\n // and nothing else ever lays it out.\n this._busySpinner.showOverlay(this);\n this._busySpinner.doLayout();\n }\n\n /**\n * Resolves once the layout pass currently in flight has finished placing\n * nodes. Resolves immediately when no pass is in flight, and resolves\n * (never rejects) when a pass fails or the view is disposed mid-pass —\n * a rejecting promise would force every caller into a `try`/`catch`\n * whose only sensible branch is \"carry on anyway\".\n *\n * @returns A promise settling on the next finished layout pass.\n */\n whenLaidOut(): Promise<void> {\n return this._layoutSettled?.promise ?? Promise.resolve();\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 — read\n * before the component is mounted, which is safe because measurement goes\n * through the DOM seam's font metrics rather than the live document (a\n * custom `nodeRenderer` whose preferred size needs a live element in the\n * document is not supported). Recurses into `children` so every container\n * and leaf is represented; a container's entry is harmless-but-unused\n * since `buildElkGraph` computes a container's box from its contents\n * 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._incomingComponents.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 incoming node, promotes the\n * incoming set to shown (revealing it), sizes + scales the content host,\n * redraws the edges, and emits `\"layout\"`. A stale result (superseded by\n * 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._incomingComponents.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 this._incomingRects.set(node.id, { x: node.x, y: node.y, width: node.width, height: node.height });\n }\n }\n\n this._graphWidth = result.width;\n this._graphHeight = result.height;\n\n this.promoteIncomingNodes();\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 // The host is no longer resized per zoom (see applyTransformToHost) — its\n // box always matches the unscaled graph bounds the nodes are laid out at.\n this._contentHost.setPreferredSize({ width: result.width, height: result.height });\n this.applyTransformToHost();\n\n // The floor under the preserved pan: when a graph swap at a very\n // different scale leaves the new graph entirely off screen, re-arm the\n // one-shot centring so the call below actually runs.\n this.rearmCentreIfOffScreen();\n\n // Before `emit`, so a consumer's own `\"layout\"` listener (the sanctioned\n // auto-fit hook, `view.on(\"layout\", () => view.zoomToFit())`) still runs\n // afterwards and wins. Only succeeds if the view is already sized; the\n // `doLayout` override retries otherwise.\n this.tryInitialCentre();\n\n this.scheduleLayout();\n\n this.emit(\"layout\");\n\n this.settleLayout();\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). The node layer is the second\n * persistent child needing this same flat-mode restore, for the same\n * reason as the edge layer.\n */\n private applyContainerZIndex(): void {\n if (this._containerIds.size === 0) {\n this._edgeLayer.setZIndex(DEFAULT_Z_INDEX);\n this._nodeLayer.setZIndex(DEFAULT_Z_INDEX);\n\n return;\n }\n\n this._edgeLayer.setZIndex(EDGE_LAYER_Z_INDEX);\n this._nodeLayer.setZIndex(LEAF_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 * Discards the just-built incoming nodes when a layout fails (e.g. `elkjs`\n * absent), so a first, never-shown graph leaves the view empty, and a\n * failed *re*-layout leaves whatever graph was already on screen — rather\n * than showing stacked, unpositioned nodes either way.\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 this.discardIncomingNodes();\n this.settleLayout();\n }\n\n /**\n * Writes the content host's `translate(panX,panY) scale(zoom)` transform\n * from the current pan offset and zoom factor. The host's box is set once\n * per layout (see `applyLayout`) to the unscaled graph bounds and is not\n * touched here — pan and zoom live entirely on the transform. Also the\n * single place the mounted node set and the drawn edge set are\n * reconciled (`updateResidency`) — every pan, zoom, centring, and\n * resize-anchoring path in this view ends here, so a residency check\n * placed anywhere else would be redundant or miss an entry point.\n */\n private applyTransformToHost(): void {\n const zoom = this.getZoom();\n\n this._contentHost.setTransform(`translate(${this._panX}px, ${this._panY}px) scale(${zoom})`);\n\n this.updateResidency();\n }\n\n /**\n * The visible viewport as a box in unscaled graph coordinates, or `null`\n * before the view is sized. Inverts the same `translate(panX,panY)\n * scale(zoom)` transform `applyTransformToHost` writes — the mapping\n * `_handleEdgeMouseMove` already applies to a pointer position.\n *\n * @returns The visible graph rectangle, or `null` when the view has no committed size yet.\n */\n private viewportGraphRect(): DiagramRect | null {\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n if (!(vw > 0) || !(vh > 0)) {\n return null;\n }\n\n const zoom = this.getZoom();\n\n return { x: -this._panX / zoom, y: -this._panY / zoom, width: vw / zoom, height: vh / zoom };\n }\n\n /**\n * Reconciles the mounted node set and the drawn edge set with the current\n * viewport, and decides whether the view draws simplified node boxes\n * instead of mounting node components: recomputes the residency rect\n * only when `residencyNeedsRefresh` says the live viewport has outgrown\n * the one the set was last computed for, then — while simplified — hands\n * every laid-out box to the node layer and leaves the resident set empty,\n * or — while not — mounts every newly-resident node, unmounts every node\n * that fell out, and clears the node layer, and either way hands the\n * residency rectangle to the edge layer to reconcile its own drawn set.\n * A no-op while the view has no committed size (`viewportGraphRect`\n * returns `null`), leaving whatever residency set already exists.\n */\n private updateResidency(): void {\n const live = this.viewportGraphRect();\n\n if (live === null || !residencyNeedsRefresh(this._residencyViewport, live, RESIDENCY_MARGIN)) {\n return;\n }\n\n this._residencyViewport = live;\n\n const residency = inflateRect(live, RESIDENCY_MARGIN);\n const simplified = this.isSimplifyAtLowZoom()\n && shouldSimplify(this._nodeComponents.size,\n medianLeafHeight(this._nodeRects, this._containerIds), this.getZoom(), this._simplified);\n\n this._simplified = simplified;\n this._nodeLayer.setNodes(simplified ? this._nodeRects : EMPTY_RECTS, this._containerIds);\n\n const next = simplified\n ? new Set<string>()\n : computeResidentIds(this._nodeComponents.keys(), this._nodeRects, residency);\n\n for (const id of this._residentIds) {\n if (!next.has(id)) {\n this.unmountNode(id);\n }\n }\n\n for (const id of next) {\n if (!this._residentIds.has(id)) {\n this.mountNode(id);\n }\n }\n\n this._residentIds = next;\n\n this._edgeLayer.setResidency(residency);\n }\n\n /**\n * Adds `id`'s node component to the content host, first writing its\n * committed width/height from the cached laid-out box — the value the\n * content host's own `Absolute` layout pass would otherwise write only on\n * its next flush (see `LayoutManager.commitBounds`) — so the element\n * never paints one frame at its intrinsic (unset) size. A node with no\n * cached box (a graph that has never been laid out) is mounted unsized;\n * `applyLayout`'s own `setPreferredSize`/`setX`/`setY` writes always\n * precede this for a node that has one.\n *\n * @param id - The node id to mount.\n */\n private mountNode(id: string): void {\n const component = this._nodeComponents.get(id);\n\n if (!component) {\n return;\n }\n\n const rect = this._nodeRects.get(id);\n\n if (rect) {\n component.setWidth(rect.width);\n component.setHeight(rect.height);\n }\n\n this._contentHost.addComponent(component);\n }\n\n /**\n * Removes `id`'s node component from the content host. Detach-only, like\n * the underlying `Component.removeComponent` it calls: the component\n * object and its now-detached element are left intact, so re-entering the\n * residency rect later re-appends the same element instead of rebuilding\n * it (see the \"Unmounting detaches the element\" Architecture Decision in\n * the node-virtualization plan).\n *\n * @param id - The node id to unmount.\n */\n private unmountNode(id: string): void {\n const component = this._nodeComponents.get(id);\n\n if (!component) {\n return;\n }\n\n this._contentHost.removeComponent(component);\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]` (the lower bound\n * adaptively lowered so a huge graph can still reach its fit zoom — see\n * `effectiveMinZoom`), and re-applies the content host's transform. A\n * non-finite request (e.g. `zoomToFit`'s `graphWidth / 0` on an unsized\n * view) is rejected outright rather than clamped, since `Math.max`/`min`\n * propagate `NaN` instead of resolving it — this is the one guard every\n * zoom-changing entry point (`zoomToFit`, `resetView`, `zoomIn`/`zoomOut`)\n * relies on rather than each re-checking its own inputs.\n *\n * @param zoom - The desired zoom factor.\n *\n * @returns This view, for method chaining.\n */\n setZoom(zoom: number): this {\n if (!Number.isFinite(zoom)) {\n return this;\n }\n\n this._options.zoom = this.clampZoom(zoom);\n this.applyTransformToHost();\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, then centres the graph. Requires a\n * completed layout.\n *\n * @returns This view, for method chaining.\n */\n zoomToFit(): this {\n this.fitGraph();\n\n return this;\n }\n\n /**\n * Fits the whole graph in the viewport: picks the largest zoom at which\n * the graph bounds fit both axes, then centres the graph at that zoom.\n * Shared by `zoomToFit` and the fit-on-load branch of `tryInitialCentre`,\n * which needs the success flag to decide whether its one-shot is spent.\n *\n * @returns `true` when the zoom and pan were written, `false` when no\n * layout has completed or the view has no committed size yet.\n */\n private fitGraph(): boolean {\n if (this._graphWidth <= 0 || this._graphHeight <= 0) {\n return false;\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.centreGraph();\n }\n\n /**\n * Resets to the default zoom, then re-centres: on the focus node when the\n * view has one that is in the shown graph, else on the graph bounds. The\n * focus node is `initialFocusNode`, or the target of the most recent\n * `focusNode` call. Centring a node also lowers the zoom, if needed, until\n * the node's whole box fits. Retried after the next layout pass when the\n * view has no committed size yet. The default zoom this restores is never\n * overridden by the fit-on-load default, because a Reset is not a first\n * load.\n *\n * @returns This view, for method chaining.\n */\n resetView(): this {\n this.setZoom(this._defaultOptions.zoom ?? DEFAULT_ZOOM);\n\n // Reset is never a first load: spend the fit-on-load one-shot before\n // re-arming, so `fitOnLoad` cannot override the default zoom this\n // method just restored. \"Reset view\" and \"Fit to view\" are two\n // separate controls and must stay two separate outcomes.\n this._hasCentredOnce = true;\n this._needsInitialCentre = true;\n this.tryInitialCentre();\n\n return this;\n }\n\n /**\n * Steps the zoom up by the configured multiplicative factor, keeping the\n * graph point currently at the viewport centre fixed.\n *\n * @returns This view, for method chaining.\n */\n zoomIn(): this {\n this.zoomAboutViewportPoint(ZOOM_BUTTON_STEP, this.getWidth() / 2, this.getHeight() / 2);\n\n return this;\n }\n\n /**\n * Steps the zoom down by the configured multiplicative factor, keeping the\n * graph point currently at the viewport centre fixed.\n *\n * @returns This view, for method chaining.\n */\n zoomOut(): this {\n this.zoomAboutViewportPoint(1 / ZOOM_BUTTON_STEP, this.getWidth() / 2, this.getHeight() / 2);\n\n return this;\n }\n\n /**\n * Re-arms the one-shot initial centring when the graph `applyLayout` just\n * promoted has no node overlapping the visible viewport — the floor under\n * the preserved pan (see `_needsInitialCentre`). Every early return here\n * is a \"leave the pan alone\" case: no nodes to test (an empty graph — a\n * `setData` to nothing is not a state the floor needs to recover from),\n * or no committed viewport size yet (nothing to test against, and the\n * `doLayout` retry path re-attempts centring once the view is sized\n * anyway). This method never centres by itself — the `tryInitialCentre`\n * call straight after it does, only once the flag is armed.\n */\n private rearmCentreIfOffScreen(): void {\n if (this._nodeRects.size === 0) {\n return;\n }\n\n const viewport = this.viewportGraphRect();\n\n if (viewport === null || anyRectIntersects(this._nodeRects.values(), viewport)) {\n return;\n }\n\n this._needsInitialCentre = true;\n }\n\n /**\n * Attempts the one-time initial centring, so the first render shows the\n * graph where `resetView` would put it rather than at the viewport's\n * top-left corner. By default, the fit branch runs and the whole graph is\n * fitted to the viewport — the same scale `zoomToFit` would choose —\n * unless `fitOnLoad: false` was passed, or a focus node wins instead. A\n * focus node too large to fit the viewport lowers the zoom until the node\n * fits. `_hasCentredOnce` records that this one-shot has already fired —\n * once set, a later `setData` or the off-screen recovery floor never\n * re-fits.\n *\n * Both inputs arrive asynchronously and in either order: the graph bounds\n * come from an ELK layout, the viewport size from the host's layout pass.\n * So this is *retried* — from `applyLayout`, from every `doLayout`, and\n * from `resetView` — until it actually succeeds, and the pending flag is\n * cleared only on a confirmed centring. Clearing it on an attempt that\n * silently no-opped (the view not yet sized, `centreGraph` returning\n * `false`) is what used to leave the diagram stuck in the corner whenever\n * the layout landed first.\n *\n * Picks between two targets: `_focusNodeId`, when it names a node in the\n * graph just promoted, or the graph's bounds otherwise — the same\n * generalisation `focusNode` reuses at runtime instead of a separate\n * one-shot mechanism.\n */\n private tryInitialCentre(): void {\n if (!this._needsInitialCentre || !(this._graphWidth > 0) || !(this._graphHeight > 0)) {\n return;\n }\n\n const focus = this._focusNodeId !== null && this._nodeComponents.has(this._focusNodeId)\n ? this._focusNodeId\n : null;\n\n const fitting = !this._hasCentredOnce && (this._options.fitOnLoad ?? true);\n\n const centred = focus !== null\n ? this.centreNode(focus)\n : (fitting ? this.fitGraph() : this.centreGraph());\n\n if (centred) {\n this._needsInitialCentre = false;\n this._hasCentredOnce = true;\n }\n }\n\n /**\n * Centres the graph bounds in the viewport at the current zoom, without\n * changing the zoom factor. Shared by `zoomToFit`, `resetView`, and the\n * initial centring. Declines before the view is sized —\n * `getWidth()`/`getHeight()` are `NaN` (not 0) until then (see\n * `effectiveMinZoom`), and writing a `NaN` pan would blank the diagram\n * until another call recomputes it.\n *\n * @returns `true` when the pan was written, `false` when the view has no\n * committed size yet and the caller should try again later.\n */\n private centreGraph(): boolean {\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n if (!(vw > 0) || !(vh > 0)) {\n return false;\n }\n\n const zoom = this.getZoom();\n\n this._panX = (vw - this._graphWidth * zoom) / 2;\n this._panY = (vh - this._graphHeight * zoom) / 2;\n\n this.applyTransformToHost();\n\n return true;\n }\n\n /**\n * Scales by `factor` about a fixed viewport point: the graph point under\n * `(vx, vy)` maps to the same viewport point before and after the zoom.\n * Shared by the zoom in/out control buttons and (about the pointer instead\n * of the viewport centre) by wheel-zoom. A no-op when `vx`/`vy` is not\n * finite — `zoomIn`/`zoomOut` pass the viewport centre, which is `NaN`\n * before the view is sized (see `centreGraph`).\n *\n * @param factor - The multiplicative zoom change (`> 1` zooms in).\n * @param vx - The viewport-relative x coordinate to keep fixed.\n * @param vy - The viewport-relative y coordinate to keep fixed.\n */\n private zoomAboutViewportPoint(factor: number, vx: number, vy: number): void {\n if (!Number.isFinite(vx) || !Number.isFinite(vy)) {\n return;\n }\n\n const oldZoom = this.getZoom();\n const newZoom = this.clampZoom(oldZoom * factor);\n\n if (newZoom === oldZoom) {\n return;\n }\n\n const graphX = (vx - this._panX) / oldZoom;\n const graphY = (vy - this._panY) / oldZoom;\n\n this._panX = vx - graphX * newZoom;\n this._panY = vy - graphY * newZoom;\n\n this.setZoom(newZoom);\n }\n\n /**\n * Resolves the effective minimum zoom: the configured `minZoom`, floored\n * further down to the zoom that fits the whole graph in the current\n * viewport when the graph is too large for that configured floor to reach.\n * A small graph is unaffected (its fit zoom already exceeds the configured\n * minimum); an unsized or unlaid-out view falls back to the configured\n * minimum untouched.\n *\n * @returns The effective minimum zoom factor.\n */\n private effectiveMinZoom(): number {\n const configuredMin = this._options.minZoom ?? this._defaultOptions.minZoom ?? DEFAULT_MIN_ZOOM;\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n // `getWidth()` / `getHeight()` are `NaN` (not 0) before the first\n // `setSize` — `> 0` (rather than `<= 0`) also rejects NaN, since every\n // NaN comparison is false.\n if (!(this._graphWidth > 0) || !(this._graphHeight > 0) || !(vw > 0) || !(vh > 0)) {\n return configuredMin;\n }\n\n const fitZoom = Math.min(vw / this._graphWidth, vh / this._graphHeight);\n\n return Math.min(configuredMin, fitZoom);\n }\n\n /**\n * Clamps a zoom request to `[effectiveMinZoom(), maxZoom]`.\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.effectiveMinZoom();\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 * Resolves the zoom that fits `size` whole in the viewport, never raising\n * the current zoom — only lowering it when the size does not already fit.\n * Shared by every node-centring entry point (`centreNode`, and so\n * `revealNode`, `focusNode`, and the focus-node branch of\n * `tryInitialCentre`) so a card larger than the viewport is never\n * centred-and-clipped.\n *\n * @param size - The node box to fit, in unscaled graph coordinates.\n * @returns The clamped zoom to use for the centring.\n */\n private zoomFittingNode(size: { width: number; height: number }): number {\n const current = this.getZoom();\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n // `getWidth()` / `getHeight()` are `NaN` before the first `setSize`, and a\n // zero-sized node box has no fit zoom — `> 0` rejects both, since every\n // NaN comparison is false (see `effectiveMinZoom`).\n if (!(vw > 0) || !(vh > 0) || !(size.width > 0) || !(size.height > 0)) {\n return this.clampZoom(current);\n }\n\n const fitZoom = Math.min(vw / size.width, vh / size.height);\n\n return this.clampZoom(Math.min(current, fitZoom));\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 * Emphasises a subset of the drawn edges: every edge outside the set\n * recedes to a lower opacity while the named ones keep their normal\n * weight. Cleared by `null`, by an empty array, and by the next layout\n * that replaces the drawn edges. Emits nothing. Forwards straight to the\n * edge layer, which owns the emphasis state — this view holds no copy of\n * its own.\n *\n * @param ids - The edge ids to emphasise, or null to clear.\n *\n * @returns This view, for method chaining.\n */\n setEdgeEmphasis(ids: readonly string[] | null): this {\n this._edgeLayer.setEdgeEmphasis(ids);\n\n return this;\n }\n\n /**\n * The currently emphasised edge ids.\n *\n * @returns A copy of the emphasised id array; empty when nothing is emphasised.\n */\n getEdgeEmphasis(): string[] {\n return this._edgeLayer.getEdgeEmphasis();\n }\n\n /**\n * Emphasises a subset of the drawn nodes: every node component outside\n * the set dims to a reduced opacity while the named ones keep full\n * opacity. Cleared by `null`, by an empty array, and by the next layout\n * that rebuilds the node components (`promoteIncomingNodes`). Emits\n * nothing. Dims through `Component.setOpacity` / `clearOpacity` directly\n * on each node's own root, so a custom `nodeRenderer` needs no\n * cooperation — unlike `applySelectedVisual`, which duck-types a\n * `setSelected?.()` because \"selected\" has no single generic rendering.\n * An unknown id is kept in the set (and reported back by\n * `getNodeEmphasis`) but dims nothing, since no node component answers to\n * it.\n *\n * @param ids - The node ids to emphasise, or null to clear.\n *\n * @returns This view, for method chaining.\n */\n setNodeEmphasis(ids: readonly string[] | null): this {\n this._nodeEmphasis = new Set(ids ?? []);\n this.applyNodeEmphasis();\n\n return this;\n }\n\n /**\n * The currently emphasised node ids.\n *\n * @returns A copy of the emphasised id array; empty when nothing is emphasised.\n */\n getNodeEmphasis(): string[] {\n return [...this._nodeEmphasis];\n }\n\n /** Rewrites every node component's opacity from the current emphasis set, and the node layer's rects too. */\n private applyNodeEmphasis(): void {\n for (const [id, component] of this._nodeComponents) {\n if (this._nodeEmphasis.size === 0 || this._nodeEmphasis.has(id)) {\n component.clearOpacity();\n } else {\n component.setOpacity(DIMMED_NODE_OPACITY);\n }\n }\n\n this._nodeLayer.setEmphasis(this._nodeEmphasis);\n }\n\n /**\n * Pans so the given node is centred in the viewport, without changing the\n * selection or emitting any event. Also lowers the zoom, if needed, until\n * the node's whole box fits the viewport — never raises it. No-op for an\n * unknown id, before the first layout has positioned the node, or before\n * the view has a real committed size — `getWidth()`/`getHeight()` are\n * `NaN` (not `0`) until then (see `effectiveMinZoom`). Unlike a\n * non-finite zoom request, which `setZoom` rejects outright, a `NaN` pan\n * has no such gate here, so it is guarded directly: a `NaN` pan is\n * sticky, silently blanking the diagram until another call recomputes\n * it. Pair with {@link selectNode} to both highlight and reveal. Unlike\n * {@link focusNode}, this does not retry — a no-op call must be repeated\n * by the caller once the view is sized.\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 this.centreNode(id);\n\n return this;\n }\n\n /**\n * Centres a node in the viewport by writing the pan, without changing the\n * selection or emitting any event. Also lowers the zoom, via\n * `zoomFittingNode`, when the node's whole box does not already fit the\n * viewport — never raises it.\n *\n * @param id - The node id to centre.\n *\n * @returns `true` when the pan and zoom were written, `false` when the\n * node is unknown or the view has no committed size yet.\n */\n private centreNode(id: string): boolean {\n const component = this._nodeComponents.get(id);\n const size = component?.getPreferredSize();\n\n if (!component || !size) {\n return false;\n }\n\n const zoom = this.zoomFittingNode(size);\n\n // Node centre in unscaled graph coordinates.\n const centreX = component.getX() + size.width / 2;\n const centreY = component.getY() + size.height / 2;\n\n // Pan so the node centre maps to the viewport centre: viewport = pan + graph·zoom.\n const panX = this.getWidth() / 2 - centreX * zoom;\n const panY = this.getHeight() / 2 - centreY * zoom;\n\n if (!Number.isFinite(panX) || !Number.isFinite(panY)) {\n return false;\n }\n\n this._panX = panX;\n this._panY = panY;\n\n // Writes the pan fields first, then lets `setZoom` apply the transform\n // once — the shape `zoomAboutViewportPoint` already uses.\n this.setZoom(zoom);\n\n return true;\n }\n\n /**\n * Centres the given node in the viewport, retrying after each layout pass\n * until it succeeds — unlike `revealNode`, which centres only when the\n * graph and the viewport are both already measured. Also lowers the\n * zoom, if needed, until the node's whole box fits the viewport; never\n * raises it.\n *\n * @param id - The node id to centre on.\n *\n * @returns This view, for method chaining.\n */\n focusNode(id: string): this {\n this._focusNodeId = id;\n this._needsInitialCentre = true;\n\n this.tryInitialCentre();\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 this._nodeLayer.setSelected(data ? id : null);\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; `\"contextmenu\"` fires when a\n * node is right-clicked; `\"edgehover\"` fires with every model edge\n * within the pointer's hit tolerance (several where routes overlap) and\n * the originating event; `\"edgeleave\"` fires when the pointer leaves\n * whatever edge(s) it was hovering.\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: \"contextmenu\", listener: (node: DiagramNodeData, event: MouseEvent) => void): this;\n on(event: \"edgehover\", listener: (edges: DiagramEdgeData[], event: MouseEvent) => void): this;\n on(event: \"edgeleave\", 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 / right-clicked node data (for `\"activate\"` / `\"contextmenu\"`).\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: \"contextmenu\", node: DiagramNodeData, mouseEvent: MouseEvent): void;\n protected emit(event: \"edgehover\", edges: DiagramEdgeData[], mouseEvent: MouseEvent): void;\n protected emit(event: \"edgeleave\"): void;\n protected emit(event: DiagramViewEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Lays the viewport out, then retries the pending initial centring. This is\n * where the viewport size becomes known, and an ELK layout that landed\n * before the host sized this view has nothing to centre against until now\n * (see `tryInitialCentre`). Also re-syncs the busy overlay, which is where a\n * view that had no size when its layout pass started finally picks the\n * overlay up. Writes only the content host's transform and the (unmanaged)\n * overlay, never a child's rect, so neither can feed back into the layout it\n * runs inside.\n *\n * @returns This view, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n this.tryInitialCentre();\n this.anchorCentreAcrossResize();\n this.syncBusyIndicator();\n\n return this;\n }\n\n /**\n * Holds the graph point under the viewport centre still across a viewport\n * resize, so whatever the user was looking at stays in the middle instead\n * of drifting toward a corner as the window grows or shrinks.\n *\n * With `viewport = pan + graph·zoom`, keeping the centre's graph point\n * fixed means `pan += (newExtent − oldExtent) / 2` on each axis — the zoom\n * cancels out, so this is correct at any zoom and never changes it. The\n * first sizing has no previous centre to preserve, so it only records the\n * extent (the initial centring owns that pass).\n */\n private anchorCentreAcrossResize(): void {\n const vw = this.getWidth();\n const vh = this.getHeight();\n\n if (!(vw > 0) || !(vh > 0)) {\n return;\n }\n\n const previousWidth = this._lastViewportWidth;\n const previousHeight = this._lastViewportHeight;\n\n this._lastViewportWidth = vw;\n this._lastViewportHeight = vh;\n\n if (!(previousWidth > 0) || !(previousHeight > 0)) {\n return;\n }\n\n if (previousWidth === vw && previousHeight === vh) {\n return;\n }\n\n this._panX += (vw - previousWidth) / 2;\n this._panY += (vh - previousHeight) / 2;\n\n this.applyTransformToHost();\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 // All seven use the SUBTREE variant: the content (nodes, the SVG edge\n // layer, and the Panel's own overlay-scroll element) are descendants of\n // this view's root, so a real wheel/pointer event's target is never the\n // root itself. An exact-target `addListener` would therefore never fire\n // for pan/zoom — only `addSubtreeListener` sees the descendant events\n // (mirrors click/dblclick, which is why selection worked but pan did not).\n Event.addSubtreeListener(this, \"click\", this._handleClick);\n Event.addSubtreeListener(this, \"dblclick\", this._handleDoubleClick);\n Event.addSubtreeListener(this, \"contextmenu\", this._handleContextMenu);\n // Non-passive: `_handleWheel` calls `preventDefault()` to suppress the\n // page's native scroll/zoom, which a passive listener silently ignores\n // (mirrors `Component.attachWheelScrolling` / `WheelTrap`).\n Event.addSubtreeListener(this, \"wheel\", { passive: false, prevent: true, handler: this._handleWheel });\n Event.addSubtreeListener(this, \"pointerdown\", this._handlePointerDown);\n // pointermove defaults to a button-agnostic registration (see\n // Event.ts's PRIMARY_BUTTON_TYPES); `_handlePointerMove` gates on\n // the live `buttons` bitmask itself during a real pan drag.\n Event.addSubtreeListener(this, \"pointermove\", this._handlePointerMove);\n Event.addSubtreeListener(this, \"pointerup\", this._handlePointerUp);\n // `mousemove`/`mouseout` rather than `mouseenter`/`mouseleave`: per\n // ARCHITECTURE.md, the non-bubbling enter/leave pair never reaches the\n // framework's window-level capture handler.\n Event.addSubtreeListener(this, \"mousemove\", this._handleEdgeMouseMove);\n Event.addSubtreeListener(this, \"mouseout\", this._handleEdgeMouseOut);\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 // A drag is not a click: a pan that starts and ends on empty canvas\n // (or starts on a node and ends on empty canvas) still fires a native\n // \"click\" on their nearest common ancestor, and it must not clear the\n // selection or the edge emphasis the user was studying.\n if (this._pointerMoved) {\n return;\n }\n\n if (this.isControlsTarget(event.target)) {\n return;\n }\n\n // A press on an edge is neither a node click nor a canvas click: it\n // must not clear the selection the user is looking at.\n if (this._edgeLayer.edgeIdAt(event.target) !== null) {\n return;\n }\n\n const id = this.nodeIdAtEvent(event);\n\n if (id !== null) {\n if (id === (this._selection[0]?.id ?? null)) {\n return;\n }\n\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.nodeIdAtEvent(event);\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 * Resolves the node id under a raw DOM event: delegates to `nodeIdAt`\n * when the view is not simplified (no node components exist to compare\n * against `nodeIdAt`'s element-containment test), else resolves the\n * event's pointer position to a graph point and tests it geometrically\n * against the laid-out boxes.\n *\n * @param event - The raw mouse event.\n * @returns The node id under the event, or `null`.\n */\n private nodeIdAtEvent(event: MouseEvent): string | null {\n if (!this._simplified) {\n return this.nodeIdAt(event.target);\n }\n\n const host = this._contentHost.getElement();\n\n if (host === undefined || event.target === null) {\n return null;\n }\n\n const handle = DOM.source.intern(event.target);\n\n // Only a target inside the content host can be over a node box. This is\n // what keeps a press on the control cluster or on the busy overlay —\n // both children of the view root, not of the content host — from\n // resolving to whatever node happens to sit under them.\n if (handle !== host && !DOM.source.contains(host, handle)) {\n return null;\n }\n\n const rect = DOM.source.getViewportRect(this);\n const zoom = this.getZoom();\n\n return this.nodeIdAtGraphPoint((event.clientX - rect.left - this._panX) / zoom,\n (event.clientY - rect.top - this._panY) / zoom);\n }\n\n /**\n * Resolves the node id whose box contains a graph-coordinate point, while\n * simplified. A leaf beats the container it sits in — the loop returns a\n * leaf hit immediately, and only falls back to a container hit once every\n * box has been checked. `_nodeRects` iterates in the same outer-before-\n * inner order `rebuildNodes` built it in (a container immediately\n * followed by its children), so among several nested containers whose\n * boxes all contain the point, the last one seen is the innermost — the\n * unconditional overwrite (not `??=`) is what picks that one, matching\n * `nodeIdAt`'s DOM hit test: containers share one z-index, and the\n * innermost is mounted after its ancestors, so it paints on top and is\n * what a real click on the overlap actually resolves to.\n *\n * @param x - The point's x coordinate, in unscaled graph coordinates.\n * @param y - The point's y coordinate, in unscaled graph coordinates.\n * @returns The node id at the point, or `null`.\n */\n private nodeIdAtGraphPoint(x: number, y: number): string | null {\n let container: string | null = null;\n\n for (const [id, rect] of this._nodeRects) {\n if (x < rect.x || y < rect.y || x > rect.x + rect.width || y > rect.y + rect.height) {\n continue;\n }\n\n if (!this._containerIds.has(id)) {\n return id;\n }\n\n container = id;\n }\n\n return container;\n }\n\n /**\n * Reports hover over the diagram's edges: resolves the pointer's graph\n * coordinate (inverting the pan/zoom transform `zoomAboutViewportPoint`\n * applies) and asks the edge layer which routes are within hit tolerance\n * there, emitting `\"edgehover\"` with the joined model edges whenever the\n * reported set changes. A drag in progress is not a hover (`_panning`), and\n * a move whose target is not an edge hit path — or whose resolved point\n * lands on no route — instead ends any hover in progress.\n *\n * @param event - The mousemove event, dispatched to the whole subtree.\n */\n private _handleEdgeMouseMove(event: MouseEvent): void {\n if (this._panning) {\n return;\n }\n\n if (this._edgeLayer.edgeIdAt(event.target) === null) {\n this.leaveEdges();\n\n return;\n }\n\n const rect = DOM.source.getViewportRect(this);\n const zoom = this.getZoom();\n const gx = (event.clientX - rect.left - this._panX) / zoom;\n const gy = (event.clientY - rect.top - this._panY) / zoom;\n\n const routes = this._edgeLayer.edgesNear(gx, gy);\n\n if (routes.length === 0) {\n this.leaveEdges();\n\n return;\n }\n\n const key = routes.map(r => r.id).join(\" \");\n\n if (key === this._hoveredEdgeKey) {\n return;\n }\n\n const modelById = new Map(this._options.data?.edges.map(e => [e.id, e]) ?? []);\n const edges = routes.map(r => modelById.get(r.id)).filter((e): e is DiagramEdgeData => e !== undefined);\n\n this._hoveredEdgeKey = key;\n this.emit(\"edgehover\", edges, event);\n }\n\n /**\n * Ends an edge hover when the pointer's `mouseout` leaves an edge hit\n * path. A `mouseout` from anywhere else is not a hover boundary and is\n * ignored — `_handleEdgeMouseMove`'s own \"target is not an edge\" branch\n * already covers a move that lands on empty canvas or a node.\n *\n * @param event - The mouseout event, dispatched to the whole subtree.\n */\n private _handleEdgeMouseOut(event: MouseEvent): void {\n if (this._edgeLayer.edgeIdAt(event.target) !== null) {\n this.leaveEdges();\n }\n }\n\n /** Emits `\"edgeleave\"` and clears the hover key, unless already idle. */\n private leaveEdges(): void {\n if (this._hoveredEdgeKey === null) {\n return;\n }\n\n this._hoveredEdgeKey = null;\n this.emit(\"edgeleave\");\n }\n\n /**\n * Resolves the node under a right-click and emits `\"contextmenu\"` with its\n * data, mirroring `Tree`'s contextmenu handling: a node hit suppresses the\n * browser's native menu via `preventDefault` and emits; a right-click on\n * empty canvas is left to the browser.\n *\n * @param event - The contextmenu event whose target is inside the view's\n * subtree.\n */\n private _handleContextMenu(event: MouseEvent): Event.ListenerResult {\n const id = this.nodeIdAtEvent(event);\n\n if (id === null) {\n return;\n }\n\n const data = this._nodeData.get(id);\n\n if (data !== undefined) {\n this.emit(\"contextmenu\", data, event);\n\n return { prevent: true };\n }\n }\n\n /**\n * Whether the given raw DOM event target lands inside the control cluster\n * — used to keep the cluster's own clicks/drags from also being\n * interpreted as a canvas click (clearing selection) or the start of a pan.\n *\n * @param target - The raw DOM event target.\n * @returns `true` when the target is the cluster or one of its descendants.\n */\n private isControlsTarget(target: EventTarget | null): boolean {\n if (target === null || this._controls === undefined) {\n return false;\n }\n\n const el = this._controls.getElement();\n const handle = DOM.source.intern(target);\n\n return el !== undefined && (el === handle || DOM.source.contains(el, handle));\n }\n\n /**\n * Wheel-zoom about the pointer: scales toward/away, keeping the graph\n * point under the cursor fixed in the viewport.\n *\n * @param event - The wheel event. `preventDefault` is applied by the\n * registration's `prevent: true` floor.\n */\n private _handleWheel(event: WheelEvent): void {\n const rect = DOM.source.getViewportRect(this);\n\n this.zoomAboutViewportPoint(event.deltaY < 0 ? WHEEL_ZOOM_STEP : 1 / WHEEL_ZOOM_STEP,\n event.clientX - rect.left, event.clientY - rect.top);\n }\n\n /**\n * Begins a pan drag on a primary-button press over the canvas — empty\n * space or an edge alike; only a node or the control cluster refuses —\n * recording the start pointer and pan offset, and switches the cursor to\n * `\"grabbing\"`. Also arms the click-versus-drag guard `_handleClick`\n * reads, regardless of whether this press ends up panning.\n *\n * @param event - The pointerdown event.\n */\n private _handlePointerDown(event: PointerEvent): void {\n // Recorded above every guard below, so a press that does not pan (a\n // node, the control cluster) still arms the click-versus-drag guard\n // `_handleClick` reads.\n this._pressX = event.clientX;\n this._pressY = event.clientY;\n this._pointerMoved = false;\n\n // A press on a node (leaf or container) or the control cluster is not\n // a pan: both show `pointer`, and the cursor has to promise what the\n // drag will actually do. Everything else — empty canvas and edges\n // alike — pans, since dragging an edge is this component's way of\n // panning from under the graph's own lines.\n if (event.button !== 0 || this.isControlsTarget(event.target) || this.nodeIdAt(event.target) !== null) {\n return;\n }\n\n this._panning = true;\n this._panStartX = event.clientX;\n this._panStartY = event.clientY;\n this._panOriginX = this._panX;\n this._panOriginY = this._panY;\n\n this.setCursor(\"grabbing\");\n }\n\n /**\n * Pans the content host during a drag by writing the pan offset from the\n * pointer delta — unbounded, so the graph can be dragged into empty space\n * in any direction.\n *\n * @param event - The pointermove event.\n */\n private _handlePointerMove(event: PointerEvent): void {\n // Gated on the primary button, like DragManager.onMouseMove gates on\n // an open session (DragManager.ts:492): this handler fires on every\n // pointer move over the subtree, including ordinary ambient hover\n // with no button held, and `_pressX`/`_pressY` default to `(0, 0)`\n // until the first real `pointerdown` — without this gate, the very\n // first hover of a session would latch `_pointerMoved` permanently.\n if (!this._pointerMoved && (event.buttons & 1) !== 0) {\n const dx = event.clientX - this._pressX;\n const dy = event.clientY - this._pressY;\n\n // Squared-distance comparison avoids a Math.hypot call, matching\n // DragManager's own threshold test.\n this._pointerMoved = dx * dx + dy * dy >= CLICK_SLOP * CLICK_SLOP;\n }\n\n if (!this._panning) {\n return;\n }\n\n // The primary button is no longer held — the pointer was released\n // outside this view's subtree, so its `pointerup` never reached us. End\n // the pan here rather than keep dragging on a button-up move.\n if ((event.buttons & 1) === 0) {\n this._panning = false;\n this.setCursor(\"grab\");\n\n return;\n }\n\n this._panX = this._panOriginX + (event.clientX - this._panStartX);\n this._panY = this._panOriginY + (event.clientY - this._panStartY);\n\n this.applyTransformToHost();\n }\n\n /**\n * Ends a pan drag and restores the `\"grab\"` cursor.\n */\n private _handlePointerUp(): void {\n this._panning = false;\n this.setCursor(\"grab\");\n }\n\n /** Builds the corner-pinned zoom / fit / reset control cluster. */\n private buildControls(): void {\n this._zoomInBtn = this.makeControlButton(\"plus\", \"Zoom in\");\n this._zoomOutBtn = this.makeControlButton(\"minus\", \"Zoom out\");\n this._fitBtn = this.makeControlButton(\"expand\", \"Fit to view\");\n this._resetBtn = this.makeControlButton(\"crosshairs\", \"Reset view\");\n\n this._controls = new FloatingPanel({ corner: \"bottom-right\", margin: CONTROLS_MARGIN, layoutManager: new VBox() });\n this._controls.addComponent(this._zoomInBtn);\n this._controls.addComponent(this._zoomOutBtn);\n this._controls.addComponent(this._fitBtn);\n this._controls.addComponent(this._resetBtn);\n }\n\n /**\n * Builds a glyph-only control button with an accessible label, mirroring\n * `VideoPlayer`'s control-bar buttons.\n *\n * @param glyph - The glyph name to show.\n * @param label - The accessible name (drives `aria-label` and the tooltip).\n * @returns The configured button.\n */\n private makeControlButton(glyph: string, label: string): Button {\n return new Button({ glyph, text: label, showText: false });\n }\n\n /** Wires the control cluster's buttons to their viewport-motion methods. */\n private wireControlListeners(): void {\n this._zoomInBtn.on(\"action\", this._onZoomIn);\n this._zoomOutBtn.on(\"action\", this._onZoomOut);\n this._fitBtn.on(\"action\", this._onFit);\n this._resetBtn.on(\"action\", this._onReset);\n }\n\n /**\n * Whether the built-in zoom / fit / reset control cluster is visible.\n *\n * @returns `true` when the control cluster shows.\n */\n isControlsVisible(): boolean {\n return this._options.controls ?? this._defaultOptions.controls ?? true;\n }\n\n /**\n * Shows or hides the built-in control cluster.\n *\n * @param value - Whether the control cluster is visible.\n *\n * @returns This view, for method chaining.\n */\n setControlsVisible(value: boolean): this {\n this._options.controls = value;\n this._controls.setVisible(value);\n\n return this;\n }\n\n /**\n * Whether the view draws plain node boxes instead of node components once\n * a large graph is zoomed out far enough that node content is no longer\n * legible.\n *\n * @returns `true` when low-zoom simplification is enabled (the default).\n */\n isSimplifyAtLowZoom(): boolean {\n return this._options.simplifyAtLowZoom ?? this._defaultOptions.simplifyAtLowZoom ?? true;\n }\n\n /**\n * Sets whether the view draws plain node boxes instead of node\n * components once a large graph is zoomed out far enough that node\n * content is no longer legible (default `true`). Forces `updateResidency`\n * to re-run so a mid-life change takes effect immediately, rather than\n * waiting for the next pan/zoom.\n *\n * @param value - Whether low-zoom simplification is enabled.\n *\n * @returns This view, for method chaining.\n */\n setSimplifyAtLowZoom(value: boolean): this {\n this._options.simplifyAtLowZoom = value;\n this._residencyViewport = null;\n this.updateResidency();\n\n return this;\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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"}
|