@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":"overlay.es.js","names":[],"sources":["../../src/typescript/lib/overlay/PopupPanel.ts","../../src/typescript/lib/overlay/Notification.ts","../../src/typescript/lib/overlay/NotificationHistoryButton.ts","../../src/typescript/lib/overlay/Drawer.ts","../../src/typescript/lib/overlay/RailHandle.ts","../../src/typescript/lib/overlay/Rail.ts","../../src/typescript/lib/overlay/Dock.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AnimatedDropdown, AnimatedDropdownOptions } from \"~/core/AnimatedDropdown.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle, Rect } from \"~/core/DOM.js\";\nimport { positionAnchoredFlexible, AnchoredFlexiblePlacement } from \"~/core/OverlayPosition.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport type { Size } from \"~/primitive/Size.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link PopupPanel}. Adds no fields of its\n * own — content, layout, insets, and a pinned size all come from the\n * inherited {@link AnimatedDropdownOptions} / `ComponentOptions` fields\n * (`layoutManager`, `components`, `insets`, `preferredSize`).\n *\n * @category Components\n */\nexport interface PopupPanelOptions extends AnimatedDropdownOptions {}\n\n/** Pixels kept between a clamped panel and the viewport edge so the panel\n * border and shadow are never flush against the screen. Mirrors the small\n * inset used by other floating panels (e.g. {@link Menu}); purely cosmetic\n * breathing room. */\nconst VIEWPORT_MARGIN = 4;\n\n/**\n * A floating panel that sizes itself to its content, places itself against a\n * trigger rect, and caps its height to the room available there — the\n * building block for a custom popup with no overlay plumbing of its own.\n * Content, layout, insets, and a pinned size are supplied through the\n * inherited `ComponentOptions` fields; `PopupPanel` adds only measurement,\n * placement, and the height cap on top of what {@link AnimatedDropdown}\n * already provides (the fade, the portal mount, and the\n * [`DismissableLayer`](/api/core/interfaces/DismissableLayer) contract).\n *\n * Every {@link showAt} recomputes `maxSize` from the room at the anchor and\n * commits the measured content height, so an over-tall panel is capped and\n * scrolls (`overflow-y: auto`) instead of running off-screen. The native\n * scrollbar overlaps the trailing edge of scrolled content — `PopupPanel`\n * reserves no gutter for it, unlike {@link Menu}, because its insets belong\n * to the consumer. A consumer whose popup routinely overflows should wrap\n * its content in a `Panel({ autoScroll: \"y\" })` under a `Fit` layout, whose\n * own gutter machinery insets correctly.\n *\n * @example\n * ```typescript\n * import { PopupPanel } from '@jimka/typescript-ui/overlay';\n * import { VBox } from '@jimka/typescript-ui/layout';\n * import { Checkbox } from '@jimka/typescript-ui/component/input';\n *\n * const panel = new PopupPanel({\n * layoutManager: new VBox({ spacing: 4, stretching: true }),\n * components: [ new Checkbox({ label: 'Show archived' }) ],\n * });\n *\n * panel.toggleFor(triggerEl, DOM.source.getViewportRect(trigger));\n * ```\n *\n * @category Components\n */\nclass PopupPanel<TOptions extends PopupPanelOptions = PopupPanelOptions> extends AnimatedDropdown<TOptions> {\n\n // Opener currently driving the panel via toggleFor, or null when the panel\n // was opened some other way (a direct showAt) or is closed. A plain\n // initializer is correct here — no cascade-dispatched setter writes it.\n private _currentOpener: Handle | null = null;\n\n /**\n * @param options - Optional construction-time options.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults. Forwarded even though no subclass exists yet, per\n * the framework's `subclassDefaults` convention.\n */\n constructor(options?: PopupPanelOptions, subclassDefaults?: Partial<PopupPanelOptions>) {\n super(\n options as TOptions,\n {\n // Constructed inline (never hoisted to a module constant) so\n // every panel gets its own layout manager instance.\n layoutManager: new VBox({ stretching: true }),\n insets: new Insets(4, 4, 4, 4),\n backgroundColor: \"var(--ts-ui-autocomplete-bg, rgb(255, 255, 255))\",\n border: \"var(--ts-ui-input-border)\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n shadow: \"var(--ts-ui-autocomplete-shadow, 2px 4px 8px rgba(0,0,0,0.15))\",\n ...(subclassDefaults ?? {}),\n } as Partial<TOptions>,\n );\n\n this.getAria().setRole(\"dialog\");\n this.setContain(\"layout\");\n\n // Native vertical scroll for over-tall content — the \"y\" case of\n // Panel.setAutoScroll, replicated here because PopupPanel is not a\n // Panel. Reading the manager back through getLayoutManager() (rather\n // than the local default above) is what makes the flag land on a\n // caller-supplied manager too.\n this.setOverflowX(\"hidden\");\n this.setOverflowY(\"auto\");\n this.getLayoutManager()?.setOverflowing(false, true);\n }\n\n /**\n * Measures the panel's content, places it against `anchorRect`, caps its\n * height to the room available there, mounts it, and plays the entrance\n * fade.\n *\n * @param anchorRect - The trigger's bounding rect to place against.\n * @returns This panel, for method chaining.\n */\n showAt(anchorRect: Rect): this {\n // Realise the element before any layout pass: getInnerSize() is null\n // while detached, so a Fit-style manager would size children to 0 on\n // first open.\n this.getElement(true);\n\n // A reused panel still carries the previous open's height cap; clear\n // it before measuring or the content is capped at the old room.\n this.setMaxSize({ width: Number.MAX_VALUE, height: Number.MAX_VALUE });\n\n const preferred = this.getPreferredSize();\n const width = preferred?.width ?? this.getWidth();\n const height = preferred?.height ?? this.getHeight();\n const viewport = DOM.source.getViewportSize();\n const placement = this.resolvePlacement(anchorRect, { width, height }, viewport);\n\n this.setWidth(width);\n this.setMaxSize({ width: Number.MAX_VALUE, height: Math.max(0, placement.available) });\n this.setHeight(height);\n this.setX(placement.x);\n this.setY(placement.y);\n\n this.showAnimated();\n this.doLayout();\n\n return this;\n }\n\n /**\n * Opens the panel anchored at `anchorRect` for `openerEl`, or closes it\n * when `openerEl` already opened it — the toggle-identity contract every\n * dropdown-style trigger uses (see {@link Menu.toggleFor}). Toggling for a\n * *different* opener while open re-shows the panel at the new rect rather\n * than closing it. `setAnchorElement` runs before {@link showAt} so the\n * layer manager excludes the trigger from its outside-pointerdown test.\n *\n * @param openerEl - The trigger element driving the toggle.\n * @param anchorRect - The trigger's bounding rect to place against.\n * @returns This panel, for method chaining.\n */\n toggleFor(openerEl: Handle, anchorRect: Rect): this {\n if (this.isOpen() && this._currentOpener === openerEl) {\n this.hideAnimated();\n\n return this;\n }\n\n this._currentOpener = openerEl;\n this.setAnchorElement(openerEl);\n\n const openerId = DOM.source.getId(openerEl);\n if (openerId !== \"\") {\n this.getAria().setLabelledBy(openerId);\n }\n\n return this.showAt(anchorRect);\n }\n\n /**\n * Clears the opener identity, then defers to\n * [`AnimatedDropdown.hideAnimated`](/api/core/classes/AnimatedDropdown#hideanimated)\n * for the exit fade and detach. Forgetting the opener here (rather than\n * only in {@link toggleFor}'s own toggle-shut branch) is what makes a\n * `hideAnimated` from any path — an outside dismissal via\n * [`AnimatedDropdown.requestClose`](/api/core/classes/AnimatedDropdown#requestclose),\n * or a direct call — read as a plain close rather than half of a toggle,\n * so the next `toggleFor` for the same opener opens instead of toggling\n * shut.\n *\n * @returns This panel, for method chaining.\n */\n hideAnimated(): this {\n this._currentOpener = null;\n\n return super.hideAnimated();\n }\n\n /**\n * Resolves the panel's placement against `anchorRect`. The override\n * point a subclass replaces for different geometry; defaults to\n * {@link positionAnchoredFlexible}.\n *\n * @param anchorRect - The trigger's bounding rect to place against.\n * @param size - The panel's measured (unclamped) preferred width/height.\n * @param viewport - The current viewport size.\n * @returns The resolved top-left coordinate and the room available there.\n */\n protected resolvePlacement(anchorRect: Rect, size: Size, viewport: Size): AnchoredFlexiblePlacement {\n return positionAnchoredFlexible(anchorRect, size, viewport, VIEWPORT_MARGIN);\n }\n}\n\nconst PopupPanelCallable = callable(PopupPanel);\ntype PopupPanelCallable<TOptions extends PopupPanelOptions = PopupPanelOptions> = PopupPanel<TOptions>;\nexport {\n PopupPanel as _PopupPanel,\n PopupPanelCallable as PopupPanel,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager } from \"~/core/LayerManager.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { _Dialog, DialogButtons } from \"~/overlay/Dialog.js\";\nimport { circle_info } from \"~/glyphs/solid/circle_info.js\";\nimport { circle_check } from \"~/glyphs/solid/circle_check.js\";\nimport { triangle_exclamation } from \"~/glyphs/solid/triangle_exclamation.js\";\nimport { circle_exclamation } from \"~/glyphs/solid/circle_exclamation.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\nGlyph.register(circle_info, circle_check, triangle_exclamation, circle_exclamation, xmark);\n\n/**\n * The visual severity of a notification.\n *\n * @category Core\n */\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * A single captured notification, retained in the in-session history returned\n * by {@link Notification.getHistory}.\n *\n * @category Core\n */\nexport interface NotificationRecord {\n /** The full (un-truncated) message text passed to {@link Notification.show}. */\n readonly message: string;\n /** The severity type the toast was shown with. */\n readonly type: NotificationType;\n /** Epoch milliseconds (`Date.now()`) when the toast was shown. */\n readonly timestamp: number;\n}\n\n/**\n * Maps a notification severity to its registry glyph name. Exported (module,\n * not barrel — so it stays out of the public API docs) so the notification\n * history menu reuses the same severity-icon mapping rather than duplicating it.\n */\nexport const BADGE_GLYPH: Record<NotificationType, string> = {\n info: \"circle-info\",\n success: \"circle-check\",\n warning: \"triangle-exclamation\",\n error: \"circle-exclamation\",\n};\n\nconst DETAIL_TITLE: Record<NotificationType, string> = {\n info: \"Information\",\n success: \"Success\",\n warning: \"Warning\",\n error: \"Error\",\n};\n\nconst MIN_RESUMED_MS: number = 8000;\nconst DISMISS_DURATION_MS: number = 200;\nconst ENTRANCE_DURATION_MS: number = 200;\n\n/**\n * A lightweight toast-style notification that appears in the bottom-right corner\n * of the viewport and auto-dismisses after a configurable duration.\n *\n * Multiple notifications stack upward. Each can also be dismissed manually via\n * the × button. Long messages are clipped to two lines with an ellipsis; a\n * double-click on the body opens a modal detail dialog containing the full\n * message.\n *\n * @example\n * ```typescript\n * Notification.show('Record saved.', 'success');\n * Notification.show('Connection lost.', 'error', 0); // persistent\n * ```\n *\n * @category Core\n */\nexport class Notification extends Component {\n\n // In-flight entrance / dismiss animations, cancelled on teardown so their\n // fallback timers cannot fire against this notification's released handle.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _dismissAnimation: Animation.CancelHandle | null = null;\n\n private static readonly WIDTH: number = 320;\n private static readonly HEIGHT: number = 64;\n private static readonly MARGIN: number = 16;\n private static readonly H_PADDING: number = 12;\n private static readonly V_PADDING: number = 10;\n private static readonly CLOSE_SIZE: number = 20;\n private static readonly BADGE_SIZE: number = 20;\n private static readonly BADGE_TEXT_GAP: number = 8;\n // Stacking z-index for toasts. Sits just above the managed dropdown band\n // (`LayerManager.Band.Dropdown` = 10000) so a toast floats over open pickers\n // and menus, yet below the Dialog band (11000) so the modal detail dialog a\n // toast can open covers it. A fixed literal rather than a `Band` allocation\n // because a `Notification` is not a registered layer — it never joins the\n // dismiss / stacking tree, so it has no node for the manager to stamp.\n private static readonly Z_INDEX: number = 10002;\n\n private static activeNotifications: Notification[] = [];\n\n // The most-recent notifications retained by the in-session history. A fixed\n // ring cap keeps memory trivial and the history menu scrollable-but-finite;\n // oldest entries are evicted first.\n private static readonly HISTORY_CAP: number = 50;\n private static history: NotificationRecord[] = [];\n\n // The auto-dismiss timer of every visible notification is paused while\n // either of these counters is positive. `hoverCount` tracks how many\n // notifications the pointer is currently over (so hovering one freezes\n // the whole stack until the pointer leaves the last one); `modalCount`\n // tracks outstanding calls to Notification.pauseAll() (a balanced pair\n // with resumeAll() that callers use to bracket their own modal flows).\n // When the combined paused state transitions back to false, every\n // notification's timer is restarted with at least `MIN_RESUMED_MS` of\n // remaining duration whenever a modal hold was the last released.\n private static hoverCount: number = 0;\n private static modalCount: number = 0;\n\n private readonly _type: NotificationType;\n private readonly _fullMessage: string;\n private readonly _badge: Glyph;\n private readonly _messageText: Text;\n private readonly _closeButton: Button;\n private _dismissTimer: ReturnType<typeof setTimeout> | null = null;\n private _remainingDuration: number = 0;\n private _timerStartedAt: number = 0;\n private _dismissing: boolean = false;\n\n // Named listener refs (removable, grep-able, named in stack traces) for the\n // close button's action, the body double-click, and the hover hold pair.\n private readonly _boundOnCloseAction: () => Event.ListenerResult = () => {\n this.dismiss();\n\n // Prevent the click from contributing to a double-click on the body.\n return true;\n };\n private readonly _boundOnDblClick: () => void = () => this.openDetail();\n private readonly _boundOnMouseOver: (e: MouseEvent) => void = (e) => Notification.acquireHoverHold(e, this.getElement());\n private readonly _boundOnMouseOut: (e: MouseEvent) => void = (e) => Notification.releaseHoverHold(e, this.getElement());\n\n /**\n * Private — use `Notification.show()` to create and display instances.\n *\n * @param message - The text to display inside the notification.\n * @param type - The severity type that controls the colour scheme.\n */\n private constructor(message: string, type: NotificationType) {\n super();\n\n this._type = type;\n this._fullMessage = message;\n\n this.setPosition(Position.FIXED);\n this.setZIndex(Notification.Z_INDEX);\n this.setWidth(Notification.WIDTH);\n this.setHeight(Notification.HEIGHT);\n this.setOverflow(\"hidden\");\n // Fixed size, fixed position, hidden overflow — full strict containment.\n this.setContain(\"strict\");\n\n // Live-region semantics so screen readers announce the toast when it is\n // inserted. Errors and warnings interrupt (`alert`/`assertive`);\n // informational and success toasts wait their turn (`status`/`polite`).\n const assertive = type === 'error' || type === 'warning';\n this.getAria().setRole(assertive ? \"alert\" : \"status\");\n this.getAria().setLive(assertive ? \"assertive\" : \"polite\");\n\n const bgVar = `var(--ts-ui-notification-${type}-bg)`;\n const borderVar = `var(--ts-ui-notification-${type}-border)`;\n const shadowVar = `var(--ts-ui-notification-shadow)`;\n\n this.setBackgroundColor(bgVar);\n this.setBorder({ border: `1px solid ${borderVar}` });\n this.setShadow(shadowVar);\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n this._badge = new Glyph(BADGE_GLYPH[type]);\n this._badge.setForegroundColor(borderVar);\n this._badge.setPreferredSize({ width: Notification.BADGE_SIZE, height: Notification.BADGE_SIZE });\n this._badge.setPointerEvents(\"none\");\n // Decorative severity icon — its meaning is already carried by the\n // message text, so keep it out of the announced live-region content.\n this._badge.getAria().setHidden(true);\n this.addComponent(this._badge);\n\n this._messageText = new Text(message);\n // Webkit-prefixed line clamp is now cross-browser (Chrome, Edge, Safari,\n // Firefox 68+). Clamped to two lines so a long message gets a trailing\n // ellipsis — the full text is reachable via double-click → detail dialog.\n this._messageText.setLineClamp(2);\n this._messageText.setWhiteSpace(\"normal\");\n this._messageText.setWordBreak(\"break-word\");\n this._messageText.setUserSelect(\"text\");\n this._messageText.setCursor(\"text\");\n this.addComponent(this._messageText);\n\n this._closeButton = new Button({ glyph: \"xmark\" });\n this._closeButton.setInsets(new Insets(0, 0, 0, 0));\n this._closeButton.setBorder(\"none\");\n this._closeButton.clearBackgroundImage();\n this._closeButton.setBackgroundColor(\"transparent\");\n this._closeButton.clearShadow();\n this._closeButton.clearPressedShadow();\n this._closeButton.setForegroundColor(\"var(--ts-ui-text-color, rgb(0, 0, 0))\");\n this._closeButton.setPreferredSize({ width: Notification.CLOSE_SIZE, height: Notification.CLOSE_SIZE });\n this._closeButton.getAria().setLabel(\"Dismiss notification\");\n this.addComponent(this._closeButton);\n\n // Route through the button's own `\"action\"` surface rather than reaching\n // into its DOM `click` via the Event API (a component must not listen to\n // another component's events through Event). The handler's returned `true`\n // stops propagation, so the dblclick-suppressing consume is preserved.\n this._closeButton.on(\"action\", this._boundOnCloseAction);\n\n // addSubtreeListener so double-clicks on the badge / text bubble up.\n Event.addSubtreeListener(this, \"dblclick\", this._boundOnDblClick);\n\n // Subtree mouseover / mouseout on the root. These bubble from every\n // descendant of the toast, so the handlers below filter out\n // intra-element movements via `relatedTarget`. mouseenter / mouseleave\n // look cleaner on paper but proved unreliable here in practice —\n // mouseleave didn't always fire on a root carrying a non-empty\n // `transition` CSS rule left over from the entrance animation.\n // Subtree listeners route through `Event`'s window-level base\n // listener, so `e.currentTarget` resolves to `window` (which has no\n // `.contains` method) — pass the toast root explicitly instead.\n Event.addSubtreeListener(this, \"mouseover\", this._boundOnMouseOver);\n Event.addSubtreeListener(this, \"mouseout\", this._boundOnMouseOut);\n }\n\n /**\n * Displays a notification toast in the bottom-right corner of the viewport.\n *\n * @param message - The text to display.\n * @param type - The severity type; controls background and border colour. Defaults to `'info'`.\n * @param duration - How long in milliseconds before the notification auto-dismisses.\n * Pass `0` for a persistent notification. Defaults to `3000`.\n */\n static show(message: string, type: NotificationType = 'info', duration: number = 3000): void {\n Notification.record(message, type);\n\n const n = new Notification(message, type);\n\n Notification.activeNotifications.push(n);\n\n const el = n.getElement(true)!;\n\n n.scheduleLayout();\n\n LayerManager.mount(el);\n\n Notification.restack();\n n.animateIn();\n\n if (duration > 0) {\n n.startTimer(duration);\n\n // If another toast is currently hovered, or a modal hold is open,\n // freeze the brand-new toast too so it doesn't dismiss out from\n // under the user while they're still reading the stack.\n if (Notification.isPaused()) {\n n.pauseTimer();\n }\n }\n }\n\n /**\n * Appends one entry to the in-session history, evicting the oldest once the\n * {@link HISTORY_CAP} ceiling is exceeded. Called by {@link show} for every\n * toast; browsing history via {@link showDetail} deliberately does not\n * record, so \"history = everything ever shown this session, one entry per\n * `show()`\".\n *\n * @param message - The full message text.\n * @param type - The severity type.\n */\n private static record(message: string, type: NotificationType): void {\n Notification.history.push({ message, type, timestamp: Date.now() });\n\n if (Notification.history.length > Notification.HISTORY_CAP) {\n Notification.history.shift();\n }\n }\n\n /**\n * Returns the in-session notification history, oldest first, capped at the\n * most recent 50 entries. The returned array is a defensive copy — mutating\n * it does not affect the retained history.\n *\n * @returns A copy of the history entries, oldest first.\n */\n static getHistory(): readonly NotificationRecord[] {\n return [...Notification.history];\n }\n\n /**\n * Slides the notification in from the right while fading from `opacity: 0`\n * to `opacity: 1` over 200ms. No-op when `prefers-reduced-motion: reduce`\n * is set — the toast snaps into place immediately.\n */\n private animateIn(): void {\n const el = this.getElement();\n\n if (!el) {\n return;\n }\n\n this._showAnimation?.cancel();\n this._showAnimation = Animation.play(el, {\n from: { transform: \"translateX(100%)\", opacity: \"0\" },\n to: { transform: \"translateX(0)\", opacity: \"1\" },\n durationMs: ENTRANCE_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n });\n }\n\n /**\n * Pauses the auto-dismiss timer of every currently visible notification.\n * Balanced with {@link Notification.resumeAll}; nested pause/resume pairs\n * compose, and the combined paused state is released only when every\n * acquired hold has been released.\n *\n * @remarks Intended for use by code that opens a modal flow during which\n * the user is unable to read or interact with active notifications.\n * Resumed timers are then clamped to a minimum of 8 seconds.\n */\n static pauseAll(): void {\n const wasPaused = Notification.isPaused();\n Notification.modalCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Releases one outstanding {@link Notification.pauseAll} hold. When the\n * last modal hold AND every hover hold is released, the stack's timers\n * are restarted with at least 8 seconds of remaining duration so the\n * user has time to read the toasts after the modal dismissal.\n */\n static resumeAll(): void {\n if (Notification.modalCount === 0) {\n return;\n }\n\n Notification.modalCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(true);\n }\n }\n\n /**\n * Increments the hover refcount when the supplied `mouseover` event\n * represents an actual entry into a notification (not a movement\n * between two of its children). The first hover on any notification\n * pauses the entire stack so the user can read without timers eating\n * notifications mid-glance.\n *\n * @param e - The native `mouseover` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static acquireHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n const wasPaused = Notification.isPaused();\n Notification.hoverCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Decrements the hover refcount when the supplied `mouseout` event\n * represents an actual exit (cursor moving outside the notification,\n * not just between two of its children). When the last hover hold AND\n * every outstanding modal hold has been released, every notification's\n * timer is restarted with whatever remaining duration was captured at\n * pause time (no minimum clamp on the hover-only path).\n *\n * @param e - The native `mouseout` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static releaseHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n if (Notification.hoverCount === 0) {\n return;\n }\n\n Notification.hoverCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(false);\n }\n }\n\n /**\n * Returns `true` when any hover or modal hold is currently active.\n */\n private static isPaused(): boolean {\n return Notification.hoverCount > 0 || Notification.modalCount > 0;\n }\n\n /**\n * Pauses every active notification's auto-dismiss timer.\n */\n private static pauseAllTimers(): void {\n for (const n of Notification.activeNotifications) {\n n.pauseTimer();\n }\n }\n\n /**\n * Restarts every active notification's auto-dismiss timer with its\n * captured remaining duration. When `clampMin` is true, the remaining\n * duration is bumped to at least {@link MIN_RESUMED_MS} so a modal\n * release leaves the user enough time to read the stack.\n */\n private static resumeAllTimers(clampMin: boolean): void {\n for (const n of Notification.activeNotifications) {\n n.restartTimer(clampMin);\n }\n }\n\n /**\n * Starts (or restarts) the auto-dismiss timer with the given duration and\n * records when it began so remaining time can be calculated on hover.\n *\n * @param ms - Milliseconds until the notification is dismissed.\n */\n private startTimer(ms: number): void {\n this._remainingDuration = ms;\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), ms);\n }\n\n /**\n * Pauses the auto-dismiss timer when the pointer enters the notification.\n * Stores how many milliseconds were left so `resumeTimer` can pick up where it left off.\n */\n private pauseTimer(): void {\n if (this._dismissTimer === null) {\n return;\n }\n\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n this._remainingDuration = Math.max(0, this._remainingDuration - (Date.now() - this._timerStartedAt));\n }\n\n /**\n * Restarts this notification's auto-dismiss timer with its captured\n * remaining duration. When `clampMin` is true, the remaining duration\n * is bumped to {@link MIN_RESUMED_MS} so the user gets enough time to\n * read what's left after a modal release.\n *\n * @param clampMin - Apply the modal-resume minimum clamp.\n */\n private restartTimer(clampMin: boolean): void {\n if (this._dismissing) {\n return;\n }\n\n if (this._remainingDuration <= 0 || this._dismissTimer !== null) {\n return;\n }\n\n if (clampMin) {\n this._remainingDuration = Math.max(this._remainingDuration, MIN_RESUMED_MS);\n }\n\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), this._remainingDuration);\n }\n\n /**\n * Opens the modal detail dialog for this toast's full (un-truncated) message.\n * Delegates to {@link showDetail}; a double-click on the toast body routes\n * here.\n */\n private openDetail(): void {\n Notification.showDetail(this._fullMessage, this._type);\n }\n\n /**\n * Opens a modal detail dialog showing the full message text — the same dialog\n * a live toast opens on double-click. Active notification timers are paused\n * while the dialog is open and clamped to a minimum of 8 seconds when the\n * dialog is dismissed. Does not itself record a history entry, so re-opening a\n * past notification from the history menu leaves the history unchanged.\n *\n * @param message - The full message text to display.\n * @param type - The severity type; controls the title and title-bar tint.\n */\n static showDetail(message: string, type: NotificationType): void {\n Notification.pauseAll();\n\n const content = new Text(message);\n content.setAutoMeasure(false);\n content.setWhiteSpace(\"pre-wrap\");\n content.setWordBreak(\"break-word\");\n content.setPadding(new Insets(16, 16, 16, 16));\n content.setUserSelect(\"text\");\n content.setCursor(\"text\");\n\n const dialog = new _Dialog({\n title: DETAIL_TITLE[type],\n contentComponent: content,\n buttons: [{ ...DialogButtons.Close, primary: true }],\n width: 420,\n height: 220,\n });\n\n // Tint the title bar to match the notification's severity colours.\n const titleBar = dialog.getTitleBar();\n titleBar.setBackgroundColor(`var(--ts-ui-notification-${type}-bg)`);\n titleBar.getTitleText().setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n titleBar.setGlyph(BADGE_GLYPH[type]);\n\n const titleGlyph = titleBar.getGlyph();\n\n if (titleGlyph !== null) {\n titleGlyph.setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n }\n\n dialog.show().then(() => Notification.resumeAll());\n }\n\n /**\n * Slides the notification rightward while fading it out, then removes the\n * element from the DOM and restacks the remaining notifications.\n */\n private dismiss(): void {\n if (this._dismissing) {\n return;\n }\n\n this._dismissing = true;\n\n if (this._dismissTimer !== null) {\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n }\n\n const el = this.getElement();\n\n if (!el) {\n this.finishDismiss();\n return;\n }\n\n this._dismissAnimation?.cancel();\n this._dismissAnimation = Animation.play(el, {\n to: { transform: \"translateX(100%)\", opacity: \"0\" },\n durationMs: DISMISS_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n onComplete: () => this.finishDismiss(),\n });\n }\n\n /**\n * Removes this notification from the active stack and destroys it.\n *\n * @remarks A dismissed toast is discarded and never re-shown — `show`\n * returns `void`, so no consumer ever holds the instance — so this is a\n * pure destroy with no consumer-visible contract change.\n */\n private finishDismiss(): void {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n\n this.dispose();\n\n Notification.restack();\n }\n\n /**\n * Recalculates the Y position of every active notification so they\n * stack upward from the bottom-right corner without overlapping.\n */\n private static restack(): void {\n const vp = DOM.source.getViewportSize();\n const x = vp.width - Notification.WIDTH - Notification.MARGIN;\n\n let y = vp.height - Notification.MARGIN;\n\n for (let i = Notification.activeNotifications.length - 1; i >= 0; i--) {\n const n = Notification.activeNotifications[i];\n\n y -= Notification.HEIGHT;\n n.setX(x);\n n.setY(y);\n y -= Notification.MARGIN;\n }\n }\n\n /**\n * Positions the badge glyph, message label, and close button within the\n * notification body.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const closeX = Notification.WIDTH - Notification.CLOSE_SIZE - 4;\n const badgeX = Notification.H_PADDING;\n const badgeY = Notification.V_PADDING + 2;\n const msgX = badgeX + Notification.BADGE_SIZE + Notification.BADGE_TEXT_GAP;\n const msgWidth = closeX - msgX - 4;\n const msgHeight = Notification.HEIGHT - Notification.V_PADDING * 2;\n\n this._badge.setX(badgeX);\n this._badge.setY(badgeY);\n this._badge.setWidth(Notification.BADGE_SIZE);\n this._badge.setHeight(Notification.BADGE_SIZE);\n\n this._messageText.setX(msgX);\n this._messageText.setY(Notification.V_PADDING);\n this._messageText.setWidth(msgWidth);\n this._messageText.setHeight(msgHeight);\n\n this._closeButton.setX(closeX);\n this._closeButton.setY(4);\n this._closeButton.setWidth(Notification.CLOSE_SIZE);\n this._closeButton.setHeight(Notification.CLOSE_SIZE);\n // Cascade the size change down to the times-glyph through the\n // close button's internal Fit/HBox layout.\n this._closeButton.doLayout();\n\n return this;\n }\n\n /**\n * Cancels any in-flight entrance / dismiss animation, then defers to the\n * base class. Cancelling first keeps their fallback timers from firing\n * after `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._dismissAnimation?.cancel();\n this._dismissAnimation = null;\n\n // `finishDismiss` is the only place a notification leaves the static\n // active list, and cancelling above suppressed it. That list outlives\n // every teardown, and `restack` writes setX/setY to each entry — so a\n // disposed notification left in it is positioned through the element\n // handle released below. Re-stack afterwards so the survivors close the\n // gap, exactly as a completed dismiss would have left them.\n if (Notification.activeNotifications.includes(this)) {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n Notification.restack();\n }\n\n super.destructor();\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { MenuButton, MenuButtonOptions } from \"~/component/button/MenuButton.js\";\nimport { MenuItemConfig } from \"~/component/container/MenuItem.js\";\nimport { Notification, BADGE_GLYPH } from \"~/overlay/Notification.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { clock_rotate_left } from \"~/glyphs/solid/clock_rotate_left.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(clock_rotate_left);\n\nconst MINUTE_MS = 60_000;\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\n\n/**\n * Formats the age of a notification as a compact relative-time string\n * (`\"just now\"`, `\"5m ago\"`, `\"2h ago\"`, `\"3d ago\"`). A future timestamp\n * (clock skew) clamps to `\"just now\"`. Module-internal (not barrel-exported, so\n * it stays out of the public API) — exported only so the unit tests can pin the\n * bucket boundaries directly.\n *\n * @param timestampMs - When the notification was shown (epoch ms).\n * @param nowMs - The current time (epoch ms).\n * @returns The relative-time label.\n */\nexport function formatRelativeTime(timestampMs: number, nowMs: number): string {\n const delta = Math.max(0, nowMs - timestampMs);\n\n if (delta < MINUTE_MS) {\n return \"just now\";\n }\n\n if (delta < HOUR_MS) {\n return `${Math.floor(delta / MINUTE_MS)}m ago`;\n }\n\n if (delta < DAY_MS) {\n return `${Math.floor(delta / HOUR_MS)}h ago`;\n }\n\n return `${Math.floor(delta / DAY_MS)}d ago`;\n}\n\n/**\n * Builds the menu item configs from the current notification history in\n * chronological order (oldest first, latest at the bottom). Returns a single\n * disabled placeholder when the history is empty. Module-level (uses only\n * statics), passed through {@link NotificationHistoryButton}'s subclass\n * defaults bag so it is re-invoked on every open. Module-internal (not\n * barrel-exported, so it stays out of the public API) — exported only so the\n * unit tests can pin its pure mapping directly.\n *\n * @returns The menu item descriptors for the current history.\n */\nexport function buildHistoryItems(): MenuItemConfig[] {\n const history = Notification.getHistory();\n\n if (history.length === 0) {\n return [{ text: \"No notifications yet\", enabled: false }];\n }\n\n const now = Date.now();\n\n // History is stored oldest-first; keep that order so the latest entries\n // sit at the bottom (the menu opens scrolled there).\n return history.map(record => ({\n glyph: BADGE_GLYPH[record.type],\n // Tint the badge with the severity's border token — the same colour\n // the live toast's badge uses.\n glyphColor: `var(--ts-ui-notification-${record.type}-border)`,\n text: record.message,\n shortcut: formatRelativeTime(record.timestamp, now),\n action: () => Notification.showDetail(record.message, record.type),\n }));\n}\n\n/**\n * Construction-time options for {@link NotificationHistoryButton}. Inherits\n * every {@link MenuButtonOptions} field; a consumer-supplied `glyph` overrides\n * the default clock icon.\n *\n * @category Components\n */\nexport interface NotificationHistoryButtonOptions extends MenuButtonOptions {}\n\n/**\n * A trigger button that opens a menu of recent notifications. The menu lists the\n * in-session {@link Notification} history in chronological order (latest at the\n * bottom) and opens scrolled to the bottom, so the most recent entries are\n * visible first — each row showing the notification's severity badge, its\n * message, and how long ago it was shown. Activating a row re-opens that\n * notification's full message in the same modal detail dialog a live toast opens\n * on double-click; it does not re-show a toast, so browsing history has no effect\n * on the history itself.\n *\n * Place one wherever a persistent affordance for reviewing past notifications is\n * wanted, e.g. in a toolbar.\n *\n * @example\n * ```typescript\n * toolbar.addComponent(new NotificationHistoryButton());\n * ```\n *\n * @category Components\n */\nclass NotificationHistoryButton extends MenuButton<NotificationHistoryButtonOptions> {\n\n /**\n * Creates a NotificationHistoryButton seeded with the `clock-rotate-left`\n * glyph, the history provider, and scroll-to-bottom-on-show. A\n * consumer-supplied option in `options` still wins over these seeds.\n *\n * @param options - Optional button configuration.\n */\n constructor(options?: NotificationHistoryButtonOptions) {\n // Seeds live in the defaults bag so a caller's options still win.\n // History is chronological (latest at the bottom), so open scrolled to\n // the bottom; the provider re-runs per open so relative times stay current.\n super(undefined, options, {\n glyph: \"clock-rotate-left\",\n menuItems: buildHistoryItems,\n scrollToBottomOnShow: true,\n });\n\n this.getAria().setLabel(\"Notification history\");\n\n // MenuButton wires the bag only for a plain MenuButton; as a subclass we\n // wire our own so a consumer `listeners` option is not silently dropped.\n this.applyListeners(options?.listeners);\n }\n}\n\nconst NotificationHistoryButtonCallable = callable(NotificationHistoryButton);\ntype NotificationHistoryButtonCallable = NotificationHistoryButton;\nexport {\n NotificationHistoryButton as _NotificationHistoryButton,\n NotificationHistoryButtonCallable as NotificationHistoryButton,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { DialogBackdrop } from \"~/component/container/DialogBackdrop.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Viewport edge a {@link Drawer} anchors to and slides in from. Reuses the\n * framework's compass primitive\n * [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`, which is\n * meaningless for an edge-anchored panel — excluding it makes the illegal state\n * unrepresentable at compile time.\n *\n * @category Core\n */\nexport type DrawerEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Events emitted by a {@link Drawer}. `\"open\"` and `\"close\"` fire after the\n * corresponding transition is committed; `\"beforeclose\"` fires before a close\n * begins and can be vetoed via its {@link DrawerCloseController}.\n *\n * @category Core\n */\nexport type DrawerEvent = \"open\" | \"close\" | \"beforeclose\";\n\n/**\n * Controller handed to a `\"beforeclose\"` listener. Calling `preventDefault()`\n * aborts the in-progress close — letting a host veto dismissal (e.g. an\n * unsaved-changes guard).\n *\n * @category Core\n */\nexport interface DrawerCloseController {\n /** Aborts the close that is about to run. */\n preventDefault(): void;\n}\n\n/**\n * Construction-time options for {@link Drawer}.\n *\n * @category Core\n */\nexport interface DrawerOptions extends ComponentOptions {\n /**\n * Viewport edge the drawer rests against and slides in from.\n *\n * @defaultValue Placement.WEST\n */\n edge?: DrawerEdge;\n\n /**\n * When true, render a blocking scrim behind the panel and close on\n * scrim-click or Escape. When false, the surrounding UI stays interactive\n * and the drawer closes only via its public API.\n *\n * @defaultValue false\n */\n modal?: boolean;\n\n /**\n * Drawer extent along its slide axis, in pixels: width for left/right\n * edges, height for top/bottom edges.\n *\n * @defaultValue 320\n */\n size?: number;\n\n /**\n * Slide (and scrim-fade) duration in milliseconds.\n *\n * @defaultValue 220\n */\n durationMs?: number;\n\n /** Construction-time event listeners dispatched to {@link Drawer.on}. */\n listeners?: {\n open?: () => void;\n close?: () => void;\n beforeclose?: (controller: DrawerCloseController) => void;\n };\n}\n\n/**\n * Default drawer extent (px) along the slide axis. A component-level constant\n * rather than a theme token because it is a layout-affecting measurement, not a\n * colour — matching how `Dialog` keeps its `MIN_*` sizes and `Notification` its\n * `WIDTH` / `HEIGHT` out of `Theme.ts`. 320 is the conventional side-panel\n * width (navigation rails, filter panels) and stays comfortably under a narrow\n * viewport so the scrim/content behind it remains visible.\n */\nconst DEFAULT_DRAWER_SIZE_PX: number = 320;\n\n/**\n * Default slide / fade duration (ms). Tuned slightly longer than the dialog's\n * 150 ms because a drawer travels a full panel-width rather than a small\n * scale/opacity delta, so the same wall-clock feel needs more time.\n */\nconst DEFAULT_DRAWER_DURATION_MS: number = 220;\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The four\n * behavioural fields seed the options bag so {@link Drawer.getEdge} and friends\n * return a defined value before the caller (or a setter) writes one. `overflow`\n * is `auto` so a drawer scrolls content that exceeds its extent; the panel\n * surface tokens (`background`, `shadow`) are edge-agnostic and applied here,\n * while the directional divider border is applied per-edge in {@link Drawer.open}.\n */\nconst _defaultDrawerOptions: Partial<DrawerOptions> = {\n edge: Placement.WEST,\n modal: false,\n size: DEFAULT_DRAWER_SIZE_PX,\n durationMs: DEFAULT_DRAWER_DURATION_MS,\n overflow: \"auto\",\n backgroundColor: \"var(--ts-ui-body-bg)\",\n shadow: \"var(--ts-ui-drawer-shadow)\",\n};\n\n/**\n * An edge-anchored panel that rests off-screen against a viewport edge and\n * slides into view when opened, overlaying the rest of the UI.\n *\n * The drawer is a bare content host: callers add their own children via the\n * inherited `addComponent` and supply any header / dismiss chrome themselves.\n * It mounts on `document.documentElement` and registers with\n * [`LayerManager`](/api/core/classes/LayerManager) as a\n * [`DismissableLayer`](/api/core/interfaces/DismissableLayer), so Escape,\n * outside-click capture, and z-stacking behave like every other portaled\n * surface. A modal drawer additionally draws a\n * [`DialogBackdrop`](/api/component/container/classes/DialogBackdrop) scrim and\n * closes on scrim-click or Escape; a non-modal drawer leaves the surrounding UI\n * interactive and closes only through its public API.\n *\n * @example\n * ```typescript\n * import { Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n * import { VBox } from '@jimka/typescript-ui/layout';\n *\n * const drawer = Drawer({ edge: Placement.EAST, modal: true, layoutManager: VBox() });\n * drawer.addComponent(myFilterForm);\n * drawer.open();\n * ```\n *\n * @category Core\n */\nclass Drawer extends Component<DrawerOptions> implements DismissableLayer {\n\n // In-flight panel / backdrop animations, cancelled on teardown so their\n // fallback timers cannot fire against released element handles.\n private _panelInAnimation : Animation.CancelHandle | null = null;\n private _panelOutAnimation : Animation.CancelHandle | null = null;\n private _backdropInAnimation : Animation.CancelHandle | null = null;\n private _backdropOutAnimation: Animation.CancelHandle | null = null;\n\n /** Whether the drawer is currently open (or mid-entrance). */\n private _open: boolean = false;\n\n /**\n * Whether an exit slide is in flight. Guards `close()` against re-entry\n * while the panel is sliding out — `_open` only flips to false in the exit\n * transition's completion callback, so without this flag a second `close()`\n * (via `toggle()`, a repeated dismiss click, or Esc) would re-emit `\"close\"`\n * and queue a redundant exit animation.\n */\n private _closing: boolean = false;\n\n /** The modal scrim, created lazily on each modal open and torn down on close. */\n private _backdrop: DialogBackdrop | null = null;\n\n /** Typed-event fan-out for `\"open\"` / `\"close\"` / `\"beforeclose\"`. */\n private _listeners: ListenerBag<DrawerEvent> = new ListenerBag<DrawerEvent>();\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.onViewportResize();\n\n /** Stable scrim-click handler reference; closes the drawer. */\n private _boundBackdropClose: () => void = (): void => {\n this.close();\n };\n\n /**\n * Constructs a drawer but does not display it. Call `open()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: DrawerOptions, subclassDefaults?: Partial<DrawerOptions>) {\n super(options, { ..._defaultDrawerOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like every other portaled surface.\n this.setPosition(Position.FIXED);\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies a {@link DrawerOptions} bag, dispatching the drawer-specific\n * fields after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This drawer, for method chaining.\n */\n protected applyOptions(options: DrawerOptions): this {\n super.applyOptions(options);\n\n // All four carry a class default and seed construction-time state, so\n // always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setModal(options.modal ?? this.isModal());\n this.setDrawerSize(options.size ?? this.getDrawerSize());\n this.setDurationMs(options.durationMs ?? this.getDurationMs());\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/CSS applied in open()) -----\n\n /**\n * Sets the viewport edge the drawer anchors to. Cached only — the resting\n * geometry and directional border are derived in `open()`, where the\n * element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This drawer, for method chaining.\n */\n setEdge(edge: DrawerEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the drawer anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): DrawerEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Sets whether the drawer is modal. A modal drawer draws a blocking scrim\n * and closes on scrim-click / Escape; a non-modal one does neither.\n *\n * @param value - True for a modal drawer.\n *\n * @returns This drawer, for method chaining.\n *\n * @remarks Takes effect on the next `open()`; changing it while open does\n * not retroactively add or remove the scrim.\n */\n setModal(value: boolean): this {\n this._options.modal = value;\n\n return this;\n }\n\n /**\n * Returns whether the drawer is modal.\n *\n * @returns True when modal.\n */\n isModal(): boolean {\n return this._options.modal ?? this._defaultOptions.modal!;\n }\n\n /**\n * Sets the drawer's extent along its slide axis (width for left/right,\n * height for top/bottom). Named `setDrawerSize` rather than overriding the\n * inherited `Component.setSize(size: Size)` — the two-axis geometry setter —\n * because a drawer's extent is a single number along one axis.\n *\n * @param value - The extent in pixels.\n *\n * @returns This drawer, for method chaining.\n */\n setDrawerSize(value: number): this {\n this._options.size = value;\n\n return this;\n }\n\n /**\n * Returns the drawer's extent along its slide axis, in pixels.\n *\n * @returns The current extent.\n */\n getDrawerSize(): number {\n return this._options.size ?? this._defaultOptions.size!;\n }\n\n /**\n * Sets the slide / scrim-fade duration in milliseconds.\n *\n * @param ms - The duration in milliseconds.\n *\n * @returns This drawer, for method chaining.\n */\n setDurationMs(ms: number): this {\n this._options.durationMs = ms;\n\n return this;\n }\n\n /**\n * Returns the slide / scrim-fade duration in milliseconds.\n *\n * @returns The current duration.\n */\n getDurationMs(): number {\n return this._options.durationMs ?? this._defaultOptions.durationMs!;\n }\n\n // ----- open / close API -----\n\n /**\n * Slides the drawer into view from its anchored edge. Registers with the\n * layer tree, draws the scrim when modal, mounts on `documentElement`, and\n * animates in. No-op if already open.\n *\n * @returns This drawer, for method chaining.\n */\n open(): this {\n if (this._open) {\n return this;\n }\n\n LayerManager.register(this);\n\n const panelZ = LayerManager.getZIndex(this);\n this.setZIndex(panelZ);\n\n if (this.isModal()) {\n this.openBackdrop(panelZ - 1);\n }\n\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n DOM.sink.appendChild(DOM.source.getDocumentElement(), this.getElement(true)!);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the drawer panel.\n trapWheel(this);\n\n this.scheduleLayout();\n\n this.animateIn();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._open = true;\n this.emit(\"open\");\n\n return this;\n }\n\n /**\n * Slides the drawer back off-screen and tears it down. Fires the cancelable\n * `\"beforeclose\"` event first; if a listener vetoes via `preventDefault()`\n * the close is aborted. No-op if already closed.\n *\n * @returns This drawer, for method chaining.\n */\n close(): this {\n if (!this._open || this._closing) {\n return this;\n }\n\n let prevented = false;\n const controller: DrawerCloseController = {\n preventDefault: (): void => {\n prevented = true;\n },\n };\n\n this.emit(\"beforeclose\", controller);\n\n if (prevented) {\n return this;\n }\n\n this._closing = true;\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this.animateOutAndFinalize();\n\n return this;\n }\n\n /**\n * Opens the drawer if closed, or closes it if open.\n *\n * @returns This drawer, for method chaining.\n */\n toggle(): this {\n return this._open ? this.close() : this.open();\n }\n\n /**\n * Returns whether the drawer is currently open.\n *\n * @returns True when open (or mid-entrance).\n */\n isOpen(): boolean {\n return this._open;\n }\n\n // ----- internal: geometry & animation -----\n\n /**\n * Computes the panel's on-screen resting rect from the current edge, size,\n * and viewport. WEST/EAST span the full viewport height at the chosen\n * width; NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - size, y: 0, width: size, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: size };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - size, width: vp.width, height: size };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: size, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the panel via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the slide's\n * start (entrance) and end (exit) state, translated one full extent past\n * the anchored edge.\n *\n * @returns A `translateX`/`translateY` CSS value.\n */\n private offscreenTransform(): string {\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return `translateX(${size}px)`;\n\n case Placement.NORTH:\n return `translateY(${-size}px)`;\n\n case Placement.SOUTH:\n return `translateY(${size}px)`;\n\n case Placement.WEST:\n default:\n return `translateX(${-size}px)`;\n }\n }\n\n /**\n * Applies the 1px divider border on the panel's inner edge — the side that\n * faces the rest of the UI — leaving the other three sides borderless.\n */\n private applyEdgeBorder(): void {\n const divider = \"1px solid var(--ts-ui-drawer-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: \"none\", borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: \"none\", borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: \"none\", borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: \"none\", borderRight: divider });\n\n break;\n }\n }\n\n /**\n * Slides the panel in from its off-screen transform to its resting\n * position. Honours `prefers-reduced-motion` via {@link Animation.play}.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._panelInAnimation?.cancel();\n this._panelInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n });\n }\n\n /**\n * Slides the panel back off-screen, then finalizes teardown (detach the\n * element, destroy the scrim, leave the layer tree, emit `\"close\"`) in the\n * transition's completion callback. Fades the scrim out concurrently. Under\n * reduced motion {@link Animation.play} runs the completion synchronously.\n */\n private animateOutAndFinalize(): void {\n const element = this.getElement();\n\n const finalize = (): void => {\n this.removeElement();\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n this.emit(\"close\");\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n onComplete: finalize,\n });\n\n this.fadeBackdropOut();\n }\n\n /**\n * Creates the modal scrim, stamps it one z-index below the panel, wires\n * scrim-click-to-close, mounts it, and fades it in.\n *\n * @param zIndex - The z-index for the scrim (panel z minus one).\n */\n private openBackdrop(zIndex: number): void {\n this._backdrop = new DialogBackdrop();\n this._backdrop.setZIndex(zIndex);\n this._backdrop.addClickListener(this._boundBackdropClose);\n\n const backdropEl = this._backdrop.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), backdropEl);\n\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = Animation.play(backdropEl, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Fades the modal scrim out, if one is present. The element is destroyed by\n * {@link teardownBackdrop} once the panel's exit completes.\n */\n private fadeBackdropOut(): void {\n const backdropEl = this._backdrop?.getElement();\n\n if (!backdropEl) {\n return;\n }\n\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = Animation.play(backdropEl, {\n to: { opacity: \"0\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Removes the modal scrim from the DOM and drops the reference, if one is\n * present.\n */\n private teardownBackdrop(): void {\n if (this._backdrop !== null) {\n this._backdrop.destroy();\n this._backdrop = null;\n }\n }\n\n /**\n * Re-derives the panel rect and resizes the scrim when the viewport\n * changes, keeping a full-height/width drawer flush with the new edges.\n */\n private onViewportResize(): void {\n if (this._backdrop !== null) {\n this._backdrop.resize();\n }\n\n this.applyRestingGeometry();\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the drawer's events.\n *\n * @param event - `\"open\"` / `\"close\"` fire after the matching transition;\n * `\"beforeclose\"` fires before a close and can veto it via its controller.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This drawer, for method chaining.\n */\n on(event: \"open\" | \"close\", listener: () => void): this;\n on(event: \"beforeclose\", listener: (controller: DrawerCloseController) => void): this;\n on(event: DrawerEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This drawer, for method chaining.\n */\n off(event: DrawerEvent, 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 payload - Forwarded to each listener.\n */\n protected emit(event: \"open\" | \"close\"): void;\n protected emit(event: \"beforeclose\", controller: DrawerCloseController): void;\n protected emit(event: DrawerEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the drawer panel's root element for the layer tree.\n *\n * @returns The drawer's element, or null when not yet rendered.\n */\n getLayerElement(): Handle | null {\n return this.getElement() ?? null;\n }\n\n /**\n * Returns the dismiss mode the document-level handlers consult: `\"modal\"`\n * for a modal drawer (captures outside interaction, Escape closes it) and\n * `\"manual\"` for a non-modal one (never auto-dismissed — closing is the\n * caller's job).\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return this.isModal() ? \"modal\" : \"manual\";\n }\n\n /**\n * Advisory close request from the layer manager (Escape on a modal\n * drawer). Routes to the public `close()`.\n */\n requestClose(): void {\n this.close();\n }\n\n /**\n * Drawers are independent top-level peers, not layers opened from another,\n * so each registers as a tree root.\n *\n * @returns Always `true`.\n */\n isLayerRoot(): boolean {\n return true;\n }\n\n /**\n * Mirrors a manager-reallocated z-index onto the panel (and the scrim, one\n * below) when the drawer is re-stamped.\n *\n * @param zIndex - The fresh z-index assigned by the manager.\n */\n onZIndexChanged(zIndex: number): void {\n this.setZIndex(zIndex);\n\n if (this._backdrop !== null) {\n this._backdrop.setZIndex(zIndex - 1);\n }\n }\n\n /**\n * Cancels any in-flight panel / backdrop animation, then defers to the base\n * class. Cancelling first keeps their fallback timers from firing after\n * `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._panelInAnimation?.cancel();\n this._panelInAnimation = null;\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = null;\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = null;\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = null;\n\n // The close animation's completion callback is the only place these are\n // released, and cancelling above suppressed it: the backdrop is a\n // private field rather than a registered child, so the base class's\n // recursion cannot reach it and it would stay mounted over the app.\n // Each of these is idempotent, so a drawer disposed while already\n // closed pays nothing. The `\"close\"` emit is deliberately NOT re-homed\n // — disposal is not a close, and emitting into consumer code from a\n // destructor invites re-entrancy.\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n\n super.destructor();\n }\n}\n\nconst DrawerCallable = callable(Drawer);\ntype DrawerCallable = Drawer;\nexport {\n Drawer as _Drawer,\n DrawerCallable as Drawer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { DOM } from \"~/core/DOM.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Button, ButtonOptions } from \"~/component/button/Button.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link RailHandle}.\n *\n * @category Core\n */\nexport interface RailHandleOptions extends ButtonOptions {\n /**\n * Whether the handle renders in its selected (target-open) wash.\n *\n * @defaultValue false\n */\n selected?: boolean;\n}\n\n/**\n * A single launcher button on a [`Rail`](/api/overlay/classes/Rail). A chromeless\n * [`Button`](/api/component/button/classes/Button) subclass that carries a\n * `selected` state — driven by the rail to mirror whether the handle's drawer\n * is open or its window is restored — rendered as a hover wash\n * (`--ts-ui-rail-handle-hover-bg`) and a selected wash\n * (`--ts-ui-rail-handle-selected-bg`).\n *\n * `RailHandle` is internal to the rail subsystem; the rail creates and owns its\n * handles, so consumers rarely construct one directly. It is exported for typing\n * and subclassing.\n *\n * @category Core\n */\nclass RailHandle extends Button<RailHandleOptions> {\n\n // Lazy `.selected` rule — the selected (target-open) wash. The slot is a\n // fast-path cache for the wrapper `createStyleRule` dedupes by suffix; see\n // Button's `_pressedStyleRule` for the full explanation.\n private declare _selectedRule?: StyleRule;\n private get selectedRule(): StyleRule {\n return this._selectedRule ??= this.createStyleRule(\".selected\");\n }\n\n // Lazy hover rule. `:not(.selected)` keeps the brighter selected wash\n // winning while the pointer is over an already-open handle.\n private declare _railHoverRule?: StyleRule;\n private get railHoverRule(): StyleRule {\n return this._railHoverRule ??= this.createStyleRule(\":hover:not(.selected)\");\n }\n\n /**\n * Builds a chromeless launcher handle and queues its hover / selected washes.\n *\n * @param options - Construction-time options (label `text`, leading `glyph`,\n * initial `selected` state).\n */\n constructor(options: RailHandleOptions = {}) {\n super(options.text, options, { chromeless: true });\n\n this.railHoverRule.set(\"backgroundColor\", \"var(--ts-ui-rail-handle-hover-bg)\");\n this.selectedRule.set(\"backgroundColor\", \"var(--ts-ui-rail-handle-selected-bg)\");\n }\n\n /**\n * Applies a {@link RailHandleOptions} bag, dispatching the `selected` state\n * after inherited Button/Component fields.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This handle, for method chaining.\n */\n protected applyOptions(options: RailHandleOptions): this {\n super.applyOptions(options);\n\n if (options.selected !== undefined) {\n this.setSelected(options.selected);\n }\n\n return this;\n }\n\n /**\n * Returns whether the handle is currently selected.\n *\n * @returns True when selected.\n */\n isSelected(): boolean {\n return this._options.selected ?? false;\n }\n\n /**\n * Sets the selected state: toggles the `.selected` class (carrying the\n * selected wash) and mirrors the state onto `aria-pressed`.\n *\n * @param value - True to select the handle, false to deselect it.\n *\n * @returns This handle, for method chaining.\n */\n setSelected(value: boolean): this {\n this._options.selected = value;\n\n this.getAria().setPressed(value);\n\n const element = this.getElement();\n if (element) {\n DOM.sink.apply(element, { toggleClass: { selected: value } });\n }\n\n return this;\n }\n\n /**\n * Renders the handle element, applying the `.selected` class when selected.\n *\n * @returns The created element.\n */\n render() {\n const element = super.render();\n DOM.sink.apply(element, { toggleClass: { selected: this.isSelected() } });\n\n return element;\n }\n}\n\nconst RailHandleCallable = callable(RailHandle);\ntype RailHandleCallable = RailHandle;\nexport {\n RailHandle as _RailHandle,\n RailHandleCallable as RailHandle,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { isUnbounded } from \"~/primitive/Size.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { BoxLayout } from \"~/layout/BoxLayout.js\";\nimport { RailHandle } from \"~/overlay/RailHandle.js\";\nimport { CollapseButton, CollapseDirection } from \"~/component/container/CollapseButton.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport type { Drawer, DrawerEdge } from \"~/overlay/Drawer.js\";\nimport type { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport type { ClickListener } from \"~/component/button/Button.js\";\n\n/**\n * Viewport edge a {@link Rail} anchors to. Structurally identical to\n * [`DrawerEdge`](/api/overlay/type-aliases/DrawerEdge) — the framework's compass\n * primitive [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`,\n * which is meaningless for an edge-anchored strip.\n *\n * @category Core\n */\nexport type RailEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Text orientation for handle labels on the vertical sides (WEST/EAST). Ignored\n * for NORTH/SOUTH, where handle text is always horizontal. Mirrors the\n * [`Tab`](/api/layout/classes/Tab) layout's orientation vocabulary.\n *\n * - `\"horizontal\"` — handles stack vertically but text stays upright.\n * - `\"vertical-cw\"` — text rotated 90° clockwise, reading top-to-bottom\n * (`writing-mode: sideways-rl`).\n * - `\"vertical-ccw\"` — text rotated the other way, reading bottom-to-top\n * (`writing-mode: sideways-lr`).\n *\n * @remarks Implemented with CSS `writing-mode` rather than `transform: rotate`\n * so the browser reports the rotated box through `getBoundingClientRect`,\n * keeping the handle's preferred-size measurement correct.\n *\n * @category Core\n */\nexport type RailOrientation = \"horizontal\" | \"vertical-cw\" | \"vertical-ccw\";\n\n/**\n * Events emitted by a {@link Rail}. `\"register\"` fires when a drawer or window\n * is added to the rail; `\"unregister\"` when it is removed.\n *\n * @category Core\n */\nexport type RailEvent = \"register\" | \"unregister\";\n\n/**\n * Per-drawer registration options for {@link Rail.registerDrawer}.\n *\n * @category Core\n */\nexport interface RailDrawerRegistration {\n /** Handle glyph (forwarded to the handle's leading icon). */\n glyph?: string;\n\n /** Handle label text. */\n text?: string;\n\n /**\n * When true (default), the rail sets the drawer's edge to its own edge so\n * the drawer slides out from the rail. Pass false to leave the drawer's\n * edge untouched.\n *\n * @defaultValue true\n */\n alignEdge?: boolean;\n}\n\n/**\n * Construction-time options for {@link Rail}.\n *\n * @category Core\n */\nexport interface RailOptions extends ComponentOptions {\n /**\n * Viewport edge the rail anchors to.\n *\n * @defaultValue Placement.WEST\n */\n edge?: RailEdge;\n\n /**\n * Explicit rail thickness in pixels — width for WEST/EAST edges, height for\n * NORTH/SOUTH edges (the main axis always spans the full viewport). Omit to\n * size the rail to its handles: the cross-axis fits the widest (or, on a\n * horizontal rail, tallest) handle, re-derived as handles are added/removed\n * and when the orientation changes.\n *\n * @defaultValue content-derived\n */\n thickness?: number;\n\n /**\n * Handle-label text orientation on the vertical (WEST/EAST) sides. Ignored\n * for NORTH/SOUTH.\n *\n * @defaultValue \"horizontal\"\n */\n orientation?: RailOrientation;\n\n /**\n * Whether the rail starts collapsed — minimized to a thin gutter strip along\n * the edge (handles hidden) that a click on its chevron expands.\n *\n * @defaultValue false\n */\n collapsed?: boolean;\n\n /** Construction-time event listeners dispatched to {@link Rail.on}. */\n listeners?: {\n register?: (target: Drawer | AbstractWindow) => void;\n unregister?: (target: Drawer | AbstractWindow) => void;\n };\n}\n\n/**\n * Fallback rail thickness (px) along the cross axis, used only when no explicit\n * `thickness` is set and the content cannot be measured yet (e.g. an empty rail\n * with no handles). Once handles exist the rail sizes to them. A component-level\n * constant rather than a theme token because it is a layout-affecting\n * measurement, not a colour — matching how `Drawer` keeps its\n * `DEFAULT_DRAWER_SIZE_PX` out of `Theme.ts`. 48 px is the conventional icon-rail\n * width (a comfortable square touch target for a single glyph handle).\n */\nconst DEFAULT_RAIL_THICKNESS_PX: number = 48;\n\n/**\n * Fixed z-index for the rail, a plain module constant just below the window\n * band (`Z_BAND_WINDOW = 9000` in `LayerManager`) — mirroring how the layer\n * manager's bands are plain constants because z-index is unthemed. The rail is\n * a persistent strip that windows, popovers, and dialogs still stack above, and\n * it is deliberately not a `DismissableLayer`, so it carries this stamp itself\n * rather than drawing a band from the layer manager.\n */\nconst RAIL_Z_INDEX: number = 8900;\n\n/**\n * Slide duration (ms) for the rail's mount / unmount animation. Matches\n * Drawer's slide feel — long enough to read as motion, short enough not to\n * delay the launcher. Honoured under `prefers-reduced-motion` by\n * {@link Animation.play}, which then snaps to the end state.\n */\nconst RAIL_ANIM_DURATION_MS: number = 200;\n\n/**\n * Cross-axis thickness (px) of the collapsed rail — the thin strip the rail\n * minimizes into, skinned like a `Split` / `Border` collapsed gutter; a click on\n * the chevron expands the rail again.\n */\nconst RAIL_COLLAPSED_THICKNESS_PX: number = 10;\n\n/**\n * Half the collapse chevron's grip width (`CollapseButton`'s 10px grip), used to\n * inset the chevron's centre from the rail's inner edge so the whole grip sits\n * just inside the strip. The chevron is centred on its anchor by the\n * `CollapseButton` transform, so anchoring its centre half a grip-width in from\n * the edge leaves it flush inside — never overhanging the `overflow: hidden`\n * rail (which would clip it and let clicks fall through to whatever is behind).\n */\nconst RAIL_CHEVRON_HALF_PX: number = 5;\n\n/**\n * Maps a rail edge to the chevron's collapse heading — the direction the rail\n * travels (and the chevron points) when collapsing: toward the outer screen\n * edge it anchors to. The restore heading is the opposite, handled by the\n * chevron itself.\n *\n * @param edge - The rail's edge.\n *\n * @returns The collapse-heading {@link CollapseDirection}.\n */\nfunction collapseHeadingFor(edge: RailEdge): CollapseDirection {\n switch (edge) {\n case Placement.EAST: return \"east\";\n case Placement.NORTH: return \"north\";\n case Placement.SOUTH: return \"south\";\n case Placement.WEST:\n default: return \"west\";\n }\n}\n\n/** The chevron's restore heading is the opposite of its collapse heading. */\nconst OPPOSITE_HEADING: Record<CollapseDirection, CollapseDirection> = {\n west: \"east\",\n east: \"west\",\n north: \"south\",\n south: \"north\",\n};\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The two\n * behavioural fields seed the options bag so {@link Rail.getEdge} /\n * {@link Rail.getThickness} return a defined value before a setter writes one;\n * the surface tokens skin the strip.\n */\nconst _defaultRailOptions: Partial<RailOptions> = {\n edge: Placement.WEST,\n orientation: \"horizontal\",\n backgroundColor: \"var(--ts-ui-body-bg)\",\n shadow: \"var(--ts-ui-rail-shadow)\",\n};\n\n/** Per-drawer bookkeeping: the handle and the exact listener references to remove. */\ninterface DrawerRegistration {\n handle: RailHandle;\n onOpen: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/** Per-window bookkeeping: the handle (null until minimized) and listener references. */\ninterface WindowRegistration {\n handle: RailHandle | null;\n onMinimize: () => void;\n onRestore: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/**\n * An edge-anchored launcher strip that floats over the app content along one\n * viewport edge, holding a column (WEST/EAST) or row (NORTH/SOUTH) of handle\n * buttons. Unlike a [`Drawer`](/api/overlay/classes/Drawer) it never slides\n * off-screen and is never auto-dismissed — it is the persistent counterpart to\n * the drawer.\n *\n * A rail hosts caller-created drawers (`registerDrawer`): each gets a handle\n * that toggles it, and the handle reflects the drawer's open/closed state by\n * subscribing through the drawer's public typed `on`. A window can also be told\n * to minimize *into* the rail (`AbstractWindow.setRail`): while minimized it is\n * represented by a rail handle that restores it on click.\n *\n * The rail mounts on `document.documentElement` as a `Position.FIXED` overlay\n * (the documented fixed carve-out) and carries a fixed z-index just below the\n * window band; it is deliberately *not* a layer-tree member.\n *\n * @example\n * ```typescript\n * import { Rail, Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n *\n * const rail = Rail({ edge: Placement.WEST }).mount();\n * rail.registerDrawer(Drawer(), { glyph: 'filter', text: 'Filters' });\n * ```\n *\n * @category Core\n */\nclass Rail extends Component<RailOptions> {\n\n // In-flight animations, cancelled on teardown so their fallback timers\n // cannot fire against this rail's released element handle.\n private _collapseAnimation: Animation.CancelHandle | null = null;\n private _slideOutAnimation: Animation.CancelHandle | null = null;\n private _slideInAnimation: Animation.CancelHandle | null = null;\n\n /** Typed-event fan-out for `\"register\"` / `\"unregister\"`. */\n private _listeners: ListenerBag<RailEvent> = new ListenerBag<RailEvent>();\n\n /** Registered drawers, keyed by drawer, holding the handle + listener refs. */\n private _drawers: Map<Drawer, DrawerRegistration> = new Map();\n\n /** Registered windows, keyed by window, holding the handle + listener refs. */\n private _windows: Map<AbstractWindow, WindowRegistration> = new Map();\n\n /** Whether the rail is currently mounted (attached to the document). */\n private _mounted: boolean = false;\n\n /** The collapse/restore chevron pinned to the rail's inner edge. */\n private _collapseButton: CollapseButton;\n\n /**\n * The content-fit thickness captured at collapse time. Hidden handles are\n * excluded from the preferred-size measurement, so the expand animation\n * tweens back to this remembered extent rather than a mis-measured one.\n */\n private _expandedThickness: number = DEFAULT_RAIL_THICKNESS_PX;\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.applyRestingGeometry();\n\n /**\n * Constructs a rail but does not display it. Call `mount()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: RailOptions, subclassDefaults?: Partial<RailOptions>) {\n super(options, { ..._defaultRailOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like Drawer and the other portaled\n // surfaces. The rail is not a DismissableLayer, so it stamps its own\n // fixed z-index rather than drawing one from the layer manager.\n this.setPosition(Position.FIXED);\n this.setZIndex(RAIL_Z_INDEX);\n\n // The collapse chevron sits at the rail's inner edge: double-clicking it\n // (matching the Split / Border gutter chevrons) collapses the rail to a\n // gutter, or restores it. Its heading points the way the rail travels on\n // collapse — toward the outer screen edge.\n this._collapseButton = new CollapseButton({\n direction: collapseHeadingFor(this.getEdge()),\n listeners: { collapse: (): void => { this.toggleCollapsed(); } },\n });\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n if (options?.listeners !== undefined) {\n this.applyListeners(options.listeners);\n }\n }\n\n /**\n * Applies a {@link RailOptions} bag, dispatching the rail-specific fields\n * after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This rail, for method chaining.\n */\n protected applyOptions(options: RailOptions): this {\n super.applyOptions(options);\n\n // edge and orientation carry a class default and seed construction-time\n // state, so always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setOrientation(options.orientation ?? this.getOrientation());\n\n if (options.thickness !== undefined) {\n this.setThickness(options.thickness);\n }\n\n if (options.collapsed !== undefined) {\n // Cache only during the cascade — the visual transition needs the\n // element + chevron + handles, which mount() applies once they exist.\n this._options.collapsed = options.collapsed;\n }\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/layout applied in mount()) -----\n\n /**\n * Sets the viewport edge the rail anchors to. Cached only — the resting\n * geometry, divider border, and handle-axis layout manager are derived in\n * `mount()`, where the element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This rail, for method chaining.\n */\n setEdge(edge: RailEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the rail anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): RailEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Pins the rail's cross-axis thickness (width for WEST/EAST, height for\n * NORTH/SOUTH) to an explicit pixel value, overriding the content-fit\n * default. Re-applies the resting geometry when mounted.\n *\n * @param px - The thickness in pixels.\n *\n * @returns This rail, for method chaining.\n */\n setThickness(px: number): this {\n this._options.thickness = px;\n this.adaptThickness();\n\n return this;\n }\n\n /**\n * Returns the rail's effective cross-axis thickness in pixels — the explicit\n * `thickness` when one was set, otherwise the content-fit measurement.\n *\n * @returns The current thickness.\n */\n getThickness(): number {\n if (this.isCollapsed()) {\n return RAIL_COLLAPSED_THICKNESS_PX;\n }\n\n if (this._options.thickness !== undefined) {\n return this._options.thickness;\n }\n\n return this.measureContentThickness();\n }\n\n /**\n * Measures the cross-axis extent the rail's handles need — the widest handle\n * for a vertical (WEST/EAST) rail, the tallest for a horizontal one — from\n * the layout manager's preferred size. Falls back to\n * {@link DEFAULT_RAIL_THICKNESS_PX} before the rail has a layout manager or\n * any handles (when the preferred cross-axis is unbounded or zero).\n *\n * @returns The content-fit thickness in pixels.\n */\n private measureContentThickness(): number {\n const preferred = this.getPreferredSize();\n if (!preferred) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n const cross = this.isVertical() ? preferred.width : preferred.height;\n if (cross <= 0 || isUnbounded(cross)) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n return Math.ceil(cross);\n }\n\n /**\n * Re-derives the content-fit thickness by re-applying the resting geometry.\n * No-op until mounted (mount applies the geometry itself); called whenever\n * the handle set or orientation changes so the rail tracks its content.\n */\n private adaptThickness(): void {\n if (this._mounted) {\n this.applyRestingGeometry();\n }\n }\n\n // ----- collapse / expand -----\n\n /**\n * Returns whether the rail is collapsed to its gutter strip.\n *\n * @returns True when collapsed.\n */\n isCollapsed(): boolean {\n return this._options.collapsed ?? false;\n }\n\n /**\n * Collapses the rail to a thin gutter strip (handles hidden) or restores it\n * to full size, animating the cross-axis between the two when mounted. No-op\n * if already in the requested state.\n *\n * @param value - True to collapse, false to expand.\n *\n * @returns This rail, for method chaining.\n */\n setCollapsed(value: boolean): this {\n if (value === this.isCollapsed()) {\n return this;\n }\n\n if (value) {\n // Capture the expanded extent first: hidden handles drop out of the\n // preferred-size measurement, so the expand tween reads this back\n // rather than re-measuring an empty rail.\n this._expandedThickness = this.getThickness();\n }\n\n this._options.collapsed = value;\n\n if (this._mounted) {\n this.animateCollapseTransition(value);\n }\n\n return this;\n }\n\n /**\n * Toggles the collapsed state.\n *\n * @returns This rail, for method chaining.\n */\n toggleCollapsed(): this {\n return this.setCollapsed(!this.isCollapsed());\n }\n\n /**\n * Flips the chevron heading, applies the collapsed/expanded skin, positions\n * the chevron, and (immediately, no animation) shows or hides the handles\n * for the current collapsed state. Used by `mount` to seed a rail\n * constructed `collapsed`.\n */\n private applyCollapseAppearance(): void {\n const collapsed = this.isCollapsed();\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n this.setAllHandlesDisplayed(!collapsed);\n }\n\n /**\n * Skins the strip for the collapsed or expanded state, mirroring how a\n * [`Split`](/api/layout/classes/Split) / [`Border`](/api/layout/classes/Border)\n * gutter paints its collapsed strip (`SplitGutter.setOpaque`): collapsed, the\n * rail reads as a themed button surface (the same fill, gradient, and border\n * the framework's buttons use) that invites a click to restore; expanded, it\n * returns to the rail background and its single inner-edge divider.\n *\n * @param collapsed - True for the collapsed strip skin, false for the\n * expanded rail skin.\n */\n private applyCollapseStyling(collapsed: boolean): void {\n if (collapsed) {\n this.setBackgroundColor(\"var(--ts-ui-button-bg, #e8e8e8)\");\n this.setBackgroundImage(\"var(--ts-ui-button-bg, linear-gradient(rgb(241, 241, 241), rgb(200, 200, 200)))\");\n this.setBorder(\"1px solid var(--ts-ui-button-border, #c8c8c8)\");\n } else {\n this.clearBackgroundImage();\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.applyEdgeBorder();\n }\n }\n\n /**\n * Places and sizes the collapse chevron for the current state, writing\n * `left` / `top` / `width` onto the chevron's own `#id` style rule (shared by\n * selector with the rule {@link CollapseButton} uses for its rotation, so the\n * two coexist) and overriding the shared `.CollapseButton` class rule.\n *\n * - **Collapsed:** centred in the strip and sized to fill its full thickness,\n * so the restore handle reads as the Split collapsed-gutter handle.\n * - **Expanded:** pinned just inside the rail's inner (content-facing) edge —\n * the side opposite the viewport edge it anchors to (right for WEST, left\n * for EAST, bottom for NORTH, top for SOUTH) — at the narrow grip width,\n * its centre inset by {@link RAIL_CHEVRON_HALF_PX} so the whole grip sits\n * flush inside the strip rather than overhanging it.\n *\n * In both states the chevron is raised above the handles (`z-index`) so a\n * click always lands on it, never on a handle laid out beneath it; and it is\n * kept fully inside the `overflow: hidden` rail so a click never falls through\n * to whatever sits behind an overhang. Percentages (not pixels) for the\n * placement so the chevron tracks the rail's size and position through the\n * collapse tween and viewport resizes. `width` maps to the across-gutter axis\n * for every edge because {@link CollapseButton}'s rotation orients its box.\n *\n * @param collapsed - True for the collapsed (centred, strip-filling) chevron,\n * false for the expanded (inner-edge, grip) chevron.\n */\n private positionChevron(collapsed: boolean): void {\n let left: string;\n let top: string;\n\n if (collapsed) {\n // Centred in the strip — the CollapseButton class-rule default.\n left = \"50%\";\n top = \"50%\";\n } else {\n // Flush just inside the inner edge: anchor the chevron's centre half\n // a grip-width in from the edge (see RAIL_CHEVRON_HALF_PX).\n const inset = `calc(100% - ${RAIL_CHEVRON_HALF_PX}px)`;\n const near = `${RAIL_CHEVRON_HALF_PX}px`;\n\n switch (this.getEdge()) {\n case Placement.EAST:\n left = near;\n top = \"50%\";\n\n break;\n\n case Placement.NORTH:\n left = \"50%\";\n top = inset;\n\n break;\n\n case Placement.SOUTH:\n left = \"50%\";\n top = near;\n\n break;\n\n case Placement.WEST:\n default:\n left = inset;\n top = \"50%\";\n\n break;\n }\n }\n\n // Fill the strip thickness when collapsed; clear to the CollapseButton\n // grip width (its class-rule default) when expanded.\n const width = collapsed ? RAIL_COLLAPSED_THICKNESS_PX + \"px\" : null;\n\n new StyleRule({\n scope: \"component\",\n name: this._collapseButton.getId(),\n styles: { left, top, width, zIndex: \"1\" },\n });\n }\n\n /**\n * Animates the cross-axis between the full and collapsed extents. The chevron\n * heading, skin, and placement flip up front.\n *\n * The handles stay shown and laid out at the full extent across the whole\n * tween in both directions; because the rail clips its overflow, the moving\n * edge reveals them as the strip widens (expand) and clips them away as it\n * narrows (collapse), so they follow the animation symmetrically rather than\n * popping in or out. On collapse they are hidden only once the strip has\n * closed.\n *\n * @param collapsed - True when collapsing, false when expanding.\n */\n private animateCollapseTransition(collapsed: boolean): void {\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n\n const element = this.getElement();\n\n // The visible extent the tween starts from — the live cross-axis size\n // (full when collapsing, the collapsed strip when expanding). Captured\n // before the expand path lays out the full geometry below.\n const fromThickness = this.isVertical() ? this.getWidth() : this.getHeight();\n const toThickness = collapsed ? RAIL_COLLAPSED_THICKNESS_PX : this._expandedThickness;\n\n if (!collapsed) {\n // Reveal and lay the handles out at the full extent up front; the\n // tween's `from` immediately shrinks the visible strip back to the\n // collapsed width, so they wipe into view as it grows. (Collapse\n // keeps the already-laid-out handles in place so the narrowing strip\n // wipes them out; they are hidden in `finalize`.)\n this.setAllHandlesDisplayed(true);\n this.applyRestingGeometry();\n this.scheduleLayout();\n }\n\n const finalize = (): void => {\n // Now that the strip has closed, drop the handles out of the layout.\n if (collapsed) {\n this.setAllHandlesDisplayed(false);\n }\n\n this.applyRestingGeometry();\n\n if (!collapsed) {\n this.scheduleLayout();\n }\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n const tween = this.collapseTween(fromThickness, toThickness);\n\n this._collapseAnimation?.cancel();\n this._collapseAnimation = Animation.play(element, {\n from: tween.from,\n to: tween.to,\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: tween.properties,\n onComplete: finalize,\n });\n }\n\n /**\n * Builds the from/to inline styles for the collapse/expand tween: the\n * cross-axis dimension (and, for EAST/SOUTH rails whose anchored corner\n * moves, the matching `left`/`top`) between two explicit thicknesses.\n *\n * @param fromThickness - The cross-axis extent the tween starts from.\n * @param toThickness - The cross-axis extent the tween ends at.\n *\n * @returns The `from` / `to` style partials and the animated property names.\n */\n private collapseTween(fromThickness: number, toThickness: number): {\n from: Partial<CSSStyleDeclaration>;\n to: Partial<CSSStyleDeclaration>;\n properties: string[];\n } {\n const vp = DOM.source.getViewportSize();\n const from: Partial<CSSStyleDeclaration> = {};\n const to: Partial<CSSStyleDeclaration> = {};\n\n if (this.isVertical()) {\n from.width = fromThickness + \"px\";\n to.width = toThickness + \"px\";\n\n if (this.getEdge() === Placement.EAST) {\n from.left = (vp.width - fromThickness) + \"px\";\n to.left = (vp.width - toThickness) + \"px\";\n }\n } else {\n from.height = fromThickness + \"px\";\n to.height = toThickness + \"px\";\n\n if (this.getEdge() === Placement.SOUTH) {\n from.top = (vp.height - fromThickness) + \"px\";\n to.top = (vp.height - toThickness) + \"px\";\n }\n }\n\n return { from, to, properties: Object.keys(to) };\n }\n\n /**\n * Shows or hides every handle (drawer and window).\n *\n * @param displayed - True to show the handles, false to hide them.\n */\n private setAllHandlesDisplayed(displayed: boolean): void {\n for (const reg of this._drawers.values()) {\n reg.handle.setDisplayed(displayed);\n }\n\n for (const reg of this._windows.values()) {\n reg.handle?.setDisplayed(displayed);\n }\n }\n\n /**\n * Sets the handle-label text orientation for the vertical (WEST/EAST) sides\n * and re-applies the writing mode to every existing handle. Ignored visually\n * on NORTH/SOUTH, where handle text is always horizontal.\n *\n * @param orientation - The {@link RailOrientation} to apply.\n *\n * @returns This rail, for method chaining.\n */\n setOrientation(orientation: RailOrientation): this {\n this._options.orientation = orientation;\n\n // `_drawers` / `_windows` are class-field Maps initialised only after\n // super() returns, but `applyOptions` dispatches this setter during the\n // super() cascade. Skip the re-apply then — no handles exist yet, and\n // each one picks up the orientation as it is created (see\n // registerDrawer / showWindowHandle).\n if (this._drawers !== undefined) {\n this.applyOrientation();\n }\n\n return this;\n }\n\n /**\n * Returns the current handle-label text orientation.\n *\n * @returns The current orientation.\n */\n getOrientation(): RailOrientation {\n return this._options.orientation ?? this._defaultOptions.orientation!;\n }\n\n // ----- mount / unmount -----\n\n /**\n * Mounts the rail on `document.documentElement`: installs the handle-axis\n * layout manager, applies the divider border and resting geometry, attaches\n * the element, and tracks viewport resizes. No-op if already mounted.\n *\n * @returns This rail, for method chaining.\n */\n mount(): this {\n if (this._mounted) {\n return this;\n }\n\n this.setLayoutManager(this.isVertical() ? new VBox() : new HBox());\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n const element = this.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), element);\n\n // The collapse chevron is a raw child (self-centred via its own class\n // rule), outside the handle layout, so it doesn't count toward the\n // content-fit thickness. Append once; a remount reuses the element.\n const chevron = this._collapseButton.getElement(true);\n if (chevron && DOM.source.getParentElement(chevron) !== element) {\n DOM.sink.appendChild(element, chevron);\n }\n\n // Seed the chevron heading, skin, placement, and handle visibility for\n // the initial (possibly collapsed) state.\n this.applyCollapseAppearance();\n\n this.scheduleLayout();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = true;\n this.animateIn();\n\n return this;\n }\n\n /**\n * Unmounts the rail: stops tracking viewport resizes and detaches the\n * element. Registered drawers and windows keep their subscriptions, so a\n * later `mount()` restores a working strip.\n *\n * @returns This rail, for method chaining.\n */\n unmount(): this {\n if (!this._mounted) {\n return this;\n }\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = false;\n\n // Slide the strip back off its edge, then detach. Under reduced motion\n // Animation.play runs the completion synchronously.\n const element = this.getElement();\n const detach = (): void => { this.removeElement(); };\n\n if (!element) {\n detach();\n\n return this;\n }\n\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n onComplete: detach,\n });\n\n return this;\n }\n\n /**\n * Slides the strip in from off its anchored edge to its resting position.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._slideInAnimation?.cancel();\n this._slideInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n });\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the strip\n * translated one full thickness past the edge it anchors to, the start\n * (mount) and end (unmount) state of the slide.\n *\n * @returns A `translateX` / `translateY` CSS value.\n */\n private offscreenTransform(): string {\n switch (this.getEdge()) {\n case Placement.EAST:\n return \"translateX(100%)\";\n\n case Placement.NORTH:\n return \"translateY(-100%)\";\n\n case Placement.SOUTH:\n return \"translateY(100%)\";\n\n case Placement.WEST:\n default:\n return \"translateX(-100%)\";\n }\n }\n\n // ----- drawer composition -----\n\n /**\n * Registers a drawer: adds a handle that toggles it, mirrors the drawer's\n * open/closed state onto the handle via the drawer's public `on`, and (by\n * default) aligns the drawer's edge to the rail's. No-op if already\n * registered.\n *\n * @param drawer - The drawer to host. The caller retains ownership of its\n * lifecycle.\n * @param reg - Per-registration options (handle glyph / text, edge\n * alignment).\n *\n * @returns This rail, for method chaining.\n */\n registerDrawer(drawer: Drawer, reg: RailDrawerRegistration = {}): this {\n if (this._drawers.has(drawer)) {\n return this;\n }\n\n const handle = new RailHandle({ text: reg.text, glyph: reg.glyph, selected: drawer.isOpen() });\n\n const onOpen: () => void = (): void => { handle.setSelected(true); };\n const onClose: () => void = (): void => { handle.setSelected(false); };\n const onAction: ClickListener = (): void => { drawer.toggle(); };\n\n drawer.on(\"open\", onOpen);\n drawer.on(\"close\", onClose);\n handle.on(\"action\", onAction);\n\n if (reg.alignEdge !== false) {\n drawer.setEdge(this.getEdge() as DrawerEdge);\n }\n\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n this._drawers.set(drawer, { handle, onOpen, onClose, onAction });\n\n this.adaptThickness();\n this.scheduleLayout();\n this.emit(\"register\", drawer);\n\n return this;\n }\n\n /**\n * Unregisters a drawer: removes its handle and detaches every subscription\n * (the exact listener references are removed so nothing leaks). Does not\n * close or destroy the drawer. No-op if not registered.\n *\n * @param drawer - The drawer to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterDrawer(drawer: Drawer): this {\n const reg = this._drawers.get(drawer);\n if (!reg) {\n return this;\n }\n\n drawer.off(\"open\", reg.onOpen);\n drawer.off(\"close\", reg.onClose);\n reg.handle.off(\"action\", reg.onAction);\n\n this.removeComponent(reg.handle);\n this._drawers.delete(drawer);\n\n this.adaptThickness();\n this.emit(\"unregister\", drawer);\n\n return this;\n }\n\n // ----- window-minimize composition -----\n\n /**\n * Registers a window so it can minimize into the rail. Subscribes to the\n * window's minimize / restore / close events; while the window is minimized\n * it is represented by a rail handle that restores it on click. Called by\n * {@link AbstractWindow.setRail}. No-op if already registered.\n *\n * @param window - The window to host.\n *\n * @returns This rail, for method chaining.\n */\n registerWindow(window: AbstractWindow): this {\n if (this._windows.has(window)) {\n return this;\n }\n\n const onMinimize: () => void = (): void => { this.showWindowHandle(window); };\n const onRestore: () => void = (): void => { this.removeWindowHandle(window); };\n const onClose: () => void = (): void => { this.unregisterWindow(window); };\n const onAction: ClickListener = (): void => { window.restore(); };\n\n window.on(\"minimize\", onMinimize);\n window.on(\"restore\", onRestore);\n window.on(\"close\", onClose);\n\n this._windows.set(window, { handle: null, onMinimize, onRestore, onClose, onAction });\n\n // A window registered while already minimized gets its handle now.\n if (window.isMinimized()) {\n this.showWindowHandle(window);\n }\n\n this.emit(\"register\", window);\n\n return this;\n }\n\n /**\n * Unregisters a window: removes any handle and detaches every subscription.\n * Does not close the window. No-op if not registered.\n *\n * @param window - The window to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterWindow(window: AbstractWindow): this {\n const reg = this._windows.get(window);\n if (!reg) {\n return this;\n }\n\n window.off(\"minimize\", reg.onMinimize);\n window.off(\"restore\", reg.onRestore);\n window.off(\"close\", reg.onClose);\n\n this.removeWindowHandle(window);\n this._windows.delete(window);\n\n this.emit(\"unregister\", window);\n\n return this;\n }\n\n /**\n * Creates and adds a handle representing a minimized window, bearing its\n * title and glyph, wired to restore it on click. No-op if a handle already\n * shows.\n *\n * @param window - The minimized window.\n */\n private showWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle !== null) {\n return;\n }\n\n const handle = new RailHandle({ text: window.getTitle(), glyph: window.getGlyph(), selected: true });\n handle.on(\"action\", reg.onAction);\n\n reg.handle = handle;\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Removes the handle representing a window, if one shows. No-op otherwise.\n *\n * @param window - The window whose handle to remove.\n */\n private removeWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle === null) {\n return;\n }\n\n reg.handle.off(\"action\", reg.onAction);\n this.removeComponent(reg.handle);\n reg.handle = null;\n\n this.adaptThickness();\n }\n\n // ----- internal: handle orientation -----\n\n /**\n * Re-applies the current orientation's writing mode to every handle (drawer\n * and window). Called when the orientation changes.\n */\n private applyOrientation(): void {\n for (const reg of this._drawers.values()) {\n this.applyHandleOrientation(reg.handle);\n }\n\n for (const reg of this._windows.values()) {\n if (reg.handle !== null) {\n this.applyHandleOrientation(reg.handle);\n }\n }\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Applies the orientation's writing mode to a single handle: a rotated\n * `writing-mode` on the vertical (WEST/EAST) sides, cleared otherwise (and\n * always on NORTH/SOUTH, where handle text stays horizontal). Mirrors the\n * `Tab` layout's `sideways-rl` / `sideways-lr` mapping.\n *\n * @param handle - The handle to orient.\n */\n private applyHandleOrientation(handle: RailHandle): void {\n const orientation = this.getOrientation();\n\n // `sideways-rl` reads top-to-bottom (clockwise); `sideways-lr` reads\n // bottom-to-top (counter-clockwise). Only meaningful on a vertical rail.\n const writingMode = orientation === \"vertical-cw\" ? \"sideways-rl\"\n : orientation === \"vertical-ccw\" ? \"sideways-lr\"\n : null;\n\n if (this.isVertical() && writingMode !== null) {\n handle.setWritingMode(writingMode);\n } else {\n handle.clearWritingMode();\n }\n }\n\n // ----- internal: geometry -----\n\n /**\n * Opts the rail out of content-derived size clamping. Like\n * [`Container`](/api/core/classes/Container) / [`Panel`](/api/core/classes/Panel),\n * the rail sizes itself explicitly — its main axis spans the viewport and\n * its cross axis is the thickness it computes — so {@link Component.setWidth} /\n * {@link Component.setHeight} must not be clamped back to the layout\n * manager's content size. Without this, collapsing (which hides every\n * handle) would empty the handle layout, drive its content max toward zero,\n * and clamp the rail's viewport-spanning main axis to nothing — the strip\n * would vanish instead of resting at {@link RAIL_COLLAPSED_THICKNESS_PX}.\n *\n * @returns Always `false`.\n */\n protected clampsToContentSize(): boolean {\n return false;\n }\n\n /**\n * Returns whether the rail lays its handles out vertically — true for the\n * WEST and EAST edges (a column at a fixed width).\n *\n * @returns True for a vertical (WEST/EAST) rail.\n */\n private isVertical(): boolean {\n const edge = this.getEdge();\n\n return edge === Placement.WEST || edge === Placement.EAST;\n }\n\n /**\n * Returns the main-axis viewport offset of a registered window's rail handle —\n * where the genie animation should aim. When the handle exists (restore, or an\n * already-minimized window) this is its laid-out position; when it does not yet\n * exist (the collapse genie runs before the handle is created) this is the\n * predicted append slot: after the last existing handle plus one gap, or 0 for\n * an empty rail. Main axis is Y for a vertical (WEST/EAST) rail, X for a\n * horizontal (NORTH/SOUTH) one; the rail's main-axis viewport origin is 0.\n *\n * @param window - The window to locate, registered or not.\n *\n * @returns The main-axis viewport offset in pixels.\n */\n handleMainAxisOffset(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainPos = (c: Component): number => vertical ? c.getY() : c.getX();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n // Restore path (and an already-minimized window): the handle exists — its\n // laid-out main-axis position is the target directly.\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainPos(handle);\n }\n\n // Collapse path: the handle is created after this runs, appended last.\n // Predict its slot from the current last handle's trailing edge + gap.\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n const last = handles[handles.length - 1];\n\n return mainPos(last) + mainExtent(last) + this.handleSpacing();\n }\n\n /**\n * Returns the main-axis length of a registered window's rail handle — the\n * span the genie centres the shrinking window along. When the handle exists\n * (restore, or an already-minimized window) this is its laid-out extent; when\n * it does not yet exist (the collapse genie runs before the handle is\n * created) it is predicted from the current last handle, since sibling\n * handles run close in size. An empty rail has no handle to sample, so this\n * returns 0 and the caller keeps the window at the slot's leading edge. Main\n * axis is the height for a vertical (WEST/EAST) rail, the width for a\n * horizontal (NORTH/SOUTH) one.\n *\n * @param window - The window whose handle to measure, registered or not.\n *\n * @returns The main-axis handle length in pixels, or 0 when unpredictable.\n */\n handleMainAxisExtent(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainExtent(handle);\n }\n\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n return mainExtent(handles[handles.length - 1]);\n }\n\n /**\n * Returns the inter-handle gap the rail's `VBox` / `HBox` layout manager\n * applies between handles, or `0` before the layout manager exists.\n *\n * @returns The component spacing in pixels.\n */\n private handleSpacing(): number {\n const lm = this.getLayoutManager();\n\n return lm instanceof BoxLayout ? lm.getComponentSpacing() : 0;\n }\n\n /**\n * Computes the rail's on-screen rect from the current edge, thickness, and\n * viewport. WEST/EAST span the full viewport height at the chosen width;\n * NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const thickness = this.getThickness();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - thickness, y: 0, width: thickness, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: thickness };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - thickness, width: vp.width, height: thickness };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: thickness, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the rail via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Applies the expanded-state border: a 1px divider (`--ts-ui-rail-border`) on\n * the rail's inner edge — the side facing the rest of the UI — with the other\n * three sides a 1px *transparent* border rather than no border.\n *\n * Reserving the same 1px box on every side that the collapsed strip's button\n * border occupies keeps the rail's border-box geometry identical across the\n * collapse/expand transition, so the handles and chevron don't jump by a\n * pixel when the visible border appears or disappears.\n */\n private applyEdgeBorder(): void {\n const transparent = \"1px solid transparent\";\n const divider = \"1px solid var(--ts-ui-rail-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: transparent, borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: transparent, borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: transparent, borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: transparent, borderRight: divider });\n\n break;\n }\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the rail's events.\n *\n * @param event - `\"register\"` fires when a drawer/window is added,\n * `\"unregister\"` when one is removed.\n * @param listener - The callback, receiving the affected drawer or window.\n *\n * @returns This rail, for method chaining.\n */\n on(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This rail, for method chaining.\n */\n off(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): 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 target - The affected drawer or window, forwarded to each listener.\n */\n protected emit(event: RailEvent, target: Drawer | AbstractWindow): void {\n this._listeners.fire(event, target);\n }\n\n /**\n * Cancels any in-flight collapse / slide animation and disposes the\n * collapse chevron, then defers to the base class. Cancelling the\n * animations first keeps their fallback timers from firing after\n * `super.destructor()` has released this rail's element handle. The\n * chevron needs its own disposal because `mount()` appends it straight to\n * this rail's element rather than registering it as a child, so\n * `super.destructor()`'s recursion cannot reach it.\n */\n protected destructor(): void {\n this._collapseAnimation?.cancel();\n this._collapseAnimation = null;\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = null;\n this._slideInAnimation?.cancel();\n this._slideInAnimation = null;\n\n this._collapseButton.dispose();\n\n super.destructor();\n }\n}\n\nconst RailCallable = callable(Rail);\ntype RailCallable = Rail;\nexport {\n Rail as _Rail,\n RailCallable as Rail,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport { Component } from \"~/core/Component.js\";\nimport type { ComponentFactory } from \"~/core/Component.js\";\nimport { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport { TabWindow } from \"~/overlay/TabWindow.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Tab } from \"~/layout/Tab.js\";\nimport { Split } from \"~/layout/Split.js\";\nimport { DockRegion } from \"~/layout/DockRegion.js\";\nimport { LayoutConstraints } from \"~/layout/LayoutConstraints.js\";\nimport { serializeLayout, restoreLayout, LayoutState } from \"~/layout/LayoutSerialization.js\";\nimport { DragManager, DragEventDetail, tabDragRegistry } from \"~/overlay/DragManager.js\";\nimport { DropZoneOverlay } from \"~/overlay/DropZoneOverlay.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { AxisOrientation } from \"~/primitive/Axis.js\";\n\n/**\n * Declarative description of one dockable content panel.\n *\n * @category Core\n */\nexport interface DockPanelSpec {\n /**\n * Stable identity. `Dock` builds an identity frame whose `getId()` is this\n * value (set at the frame's construction), and serialization keys on it — so\n * restored layouts round-trip against it. Keep it stable across sessions.\n */\n id: string;\n /**\n * Visible tab label and tear-off window title. Set as the identity frame's\n * `getName()`; it rides on the frame (not a per-container constraint), so it\n * survives the re-home a restore performs.\n */\n title: string;\n /** Optional registry glyph name shown leading the tab label. */\n glyph?: string;\n /** Optional hover-tooltip text shown over the tab button. */\n tooltip?: string;\n /** Whether the tab shows a close button. Defaults to `true`. */\n closeable?: boolean;\n /**\n * Whether closing this panel's tab destroys its content. Defaults to\n * `true`. Pass `false` when `content` is a live component you hold and\n * intend to re-add later; a factory needs no opt-out, since a re-add\n * rebuilds through it.\n */\n disposeOnClose?: boolean;\n /**\n * The content: a live component, or a factory built on first resolve. It is\n * placed inside the identity frame, never mutated. A factory returning a\n * promise is accepted only by {@link Dock.addLazyPanel}, which shows a\n * spinner for the whole wait; {@link Dock.addPanel} throws on one.\n */\n content: Component | ComponentFactory;\n}\n\n/**\n * A node in the declarative initial arrangement: a leaf panel, a split of\n * regions, or a tab group.\n *\n * @category Core\n */\nexport type DockLayoutSpec =\n | DockPanelSpec\n | { split: AxisOrientation; children: DockLayoutSpec[] }\n | { tabs: DockPanelSpec[] };\n\n/**\n * Construction options for a {@link Dock}.\n *\n * @category Core\n */\nexport interface DockOptions extends ContainerOptions {\n /** Initial arrangement, compiled to the region tree at construction. Omit for an empty dock. */\n layout?: DockLayoutSpec;\n /**\n * Placeholder shown only while the dock holds no live panel (tiled or\n * floated) — a start-page for an empty dock. It is shown as a single\n * non-closeable tab in the empty region and is chrome, not a panel: it is\n * never serialized. See {@link Dock.setEmptyContent}.\n */\n emptyContent?: Component;\n /**\n * Construction-time listener bag — the declarative form of {@link Dock.on},\n * so a consumer can wire dock events (notably `emptychange`, the empty↔\n * populated aggregate) in the options bag instead of a separate post-build\n * `on(...)` call. Each entry is registered via `on` after construction; the\n * born-empty state is adopted without an emit, so read {@link Dock.isEmpty}\n * for the initial value.\n */\n listeners?: {\n attach?: (event: DockPanelEvent) => void;\n detach?: (event: DockPanelEvent) => void;\n move?: (event: DockPanelEvent) => void;\n focus?: (event: DockPanelEvent | null) => void;\n close?: (event: DockPanelEvent) => void;\n emptychange?: (event: DockEmptyEvent) => void;\n exception?: (event: DockExceptionEvent) => void;\n };\n}\n\n/**\n * String-literal union of the events a {@link Dock} emits across a panel's\n * lifecycle.\n *\n * The model is *host-centric*: a live panel always occupies one Dock-managed\n * *host* — the *tiled tree* (the main dock) or a *float window* — and the events\n * name the host transitions. `\"attach\"` fires when a panel **enters** a host (a\n * fresh `addPanel`/restore into the tiled tree, or a tear-off into a fresh\n * float); `\"detach\"` fires when it **leaves** a host while staying alive. The\n * two pair up across a move: a tear-off is `\"detach\"`(tiled) then\n * `\"attach\"`(float), and a re-dock — whether dropped on a region body/edge or\n * merged onto an existing tab bar — is `\"detach\"`(float) then `\"attach\"`(tiled).\n * `\"move\"` fires when a panel **relocates within** its current host — a\n * different region in the same tiled tree, or repositioned in the same float —\n * without changing host; it never accompanies a host change (that is\n * `\"detach\"`+`\"attach\"`) nor a first appearance (that is `\"attach\"` alone), and\n * a pure reorder within one strip is silent. `\"focus\"` fires when the dock-wide\n * active panel changes (across tiled tabs and floats; `null` when nothing is\n * focused), and `\"close\"` when a panel is destroyed. The\n * {@link DockPanelEvent.window} field names *which* host the panel entered,\n * left, occupies, or moved within. See {@link DockPanelEvent} for the payload.\n *\n * A separate `\"emptychange\"` event is a *dock-wide aggregate*, not a per-panel\n * event: it fires once each time the dock transitions between holding no live\n * panel anywhere and holding at least one, carrying a {@link DockEmptyEvent}.\n *\n * `\"exception\"` reports that a lazy panel's content factory rejected, carrying a\n * {@link DockExceptionEvent} rather than a {@link DockPanelEvent} — a panel that\n * never built has no content to name. It follows that panel's own `\"close\"`,\n * because the failure tears the whole docked panel down; the panel stays\n * registered, so re-adding the same id rebuilds it and retries.\n *\n * @category Core\n */\nexport type DockEvent = \"attach\" | \"detach\" | \"move\" | \"focus\" | \"close\" | \"emptychange\" | \"exception\";\n\n/**\n * Payload for a {@link Dock} lifecycle event, identifying the panel by its\n * stable {@link DockPanelSpec.id}, carrying its Dock-owned identity frame, and\n * naming the host the event concerns.\n *\n * @category Core\n */\nexport interface DockPanelEvent {\n /** The stable id of the panel (its {@link DockPanelSpec.id}). */\n id: string;\n /** The panel's Dock-owned identity frame. */\n content: Component;\n /**\n * The host the panel entered (`\"attach\"`), left (`\"detach\"`), moved within\n * (`\"move\"` — same host before and after), or currently occupies\n * (`\"focus\"`): `null` denotes the tiled tree / main dock, otherwise the float\n * window. Always `null` for `\"close\"` — a destroy is not a host transition,\n * but the field is always present so the payload stays flat.\n */\n window: AbstractWindow | null;\n}\n\n/**\n * Payload for a {@link Dock} `\"emptychange\"` event, reporting whether the dock\n * just became empty. Emitted once per real transition, not per panel.\n *\n * @category Core\n */\nexport interface DockEmptyEvent {\n /** `true` when the dock just became empty (no live panels anywhere), `false` when it became populated. */\n empty: boolean;\n}\n\n/**\n * Payload for a {@link Dock} `\"exception\"` event: a lazy panel's content\n * factory rejected. The panel has already been closed and its `\"close\"` event\n * already emitted by the time this fires.\n *\n * @category Core\n */\nexport interface DockExceptionEvent {\n /** The stable id of the panel that failed (its {@link DockPanelSpec.id}). */\n id: string;\n /** The value the content factory's promise rejected with. */\n error: unknown;\n}\n\n/**\n * Per-region drag-and-drop wiring tracked by the re-wire sweep.\n */\ninterface RegionWiring {\n /** The region's edge/centre drop coordinator (torn down on teardown). */\n dockRegion: DockRegion;\n /** Whether `setReorderable(true)` + the prune-on-`\"empty\"` subscription were applied (Tab regions only). */\n tabWired: boolean;\n}\n\n/**\n * A user-configurable, rearrangeable panel layout — the VS Code / GoldenLayout\n * style dock. It hosts a tree of [`Split`](/api/layout/classes/Split) /\n * [`Tab`](/api/layout/classes/Tab) regions whose panels the user can reorder,\n * tear off into floating [`Window`](/api/overlay/classes/Window)s, drop on region\n * edges to split, and save/restore.\n *\n * `Dock` is **glue**, not new drag mechanics: tab reorder + tear-off come from\n * `Tab`'s reorderable wiring, edge-split-on-drop from\n * [`DockRegion`](/api/layout/classes/DockRegion), every re-parent from\n * [`Component.moveComponent`](/api/core/classes/Component#movecomponent), and\n * persistence from\n * [`serializeLayout`](/api/layout/functions/serializeLayout) /\n * [`restoreLayout`](/api/layout/functions/restoreLayout). `Dock` owns the panel\n * registry (the serialization factory), the declarative initial-layout\n * compiler, and the re-wire sweep that keeps **every** region dockable —\n * including the regions a drop creates mid-gesture.\n *\n * @category Core\n */\nclass Dock extends Container<DockOptions> {\n\n // panelId -> spec; the single source the serialization factory resolves from.\n private _panels: Map<string, DockPanelSpec> = new Map<string, DockPanelSpec>();\n // panelId -> the Dock-owned identity frame built for that spec (cached so a\n // lazy factory runs once and every resolve returns the same instance).\n private _frames: Map<string, Component> = new Map<string, Component>();\n // panelId -> deferred content factory for a lazy panel (see addLazyPanel).\n // resolvePanel reads it to give the frame a lazy Tab layout (Tab.addLazyTab),\n // which owns the once-only materialization; the frame caches in _frames after.\n private _lazyFactories: Map<string, ComponentFactory> = new Map<string, ComponentFactory>();\n // region container -> its DnD wiring; the sweep's idempotence + teardown ledger.\n private _wiring: Map<Component, RegionWiring> = new Map<Component, RegionWiring>();\n // rAF coalescing latch so a burst of moves in one gesture yields one sweep.\n private _sweepScheduled: boolean = false;\n\n // Panel-lifecycle event bag. A field initialiser is safe here: no\n // cascade-dispatched setter touches it, and Dock exposes no construction-time\n // `listeners` option (no DockEvent is a build-time gesture — addPanel /\n // compileLayout run after super()), so it is never dispatched from\n // applyOptions. Consumers wire post-construction via on(...).\n private _listeners: ListenerBag<DockEvent> = new ListenerBag<DockEvent>();\n // The dock-wide focused panel id, or null when nothing is focused. The single\n // source of truth gating every \"focus\" emit so a re-activation is silent.\n private _focusedPanelId: string | null = null;\n // panelId -> last-observed host (null = tiled tree, else the float window);\n // the source of the attach/detach diff. A host change across a sweep emits\n // detach(old host) then attach(new host); a first appearance emits attach only.\n private _panelHost: Map<string, AbstractWindow | null> = new Map<string, AbstractWindow | null>();\n // panelId -> the Tab region last observed hosting it; lets a close recompute\n // the surviving sibling's focus after the region re-selects.\n private _frameRegion: Map<string, Component> = new Map<string, Component>();\n // The Tab region the user most recently focused (or that last received a\n // programmatic add). `addPanel` docks new panels here when it is still a live\n // region, so a new tab opens beside whatever tab-bar the user last worked in\n // rather than always the first/primary region. Null until the first add.\n private _lastActiveRegion: Component | null = null;\n // Float windows whose lifecycle events are already subscribed; the tracked-set\n // guard that stops a re-sweep stacking duplicate listeners.\n private _floatSubscribed: Set<AbstractWindow> = new Set<AbstractWindow>();\n\n // Overlay highlighting the dock as a drop target while it is empty (every\n // panel torn off) and a tab is dragged over it.\n private _emptyDropOverlay: DropZoneOverlay = new DropZoneOverlay();\n\n // Empty-state latch: whether the dock currently holds no live panel. Gates\n // the \"emptychange\" emit so it fires only on a real transition. Seeded true\n // because a dock is born empty: the first reconcile on a still-empty dock then\n // finds no transition (silent), while a dock born with a layout — or the first\n // addPanel — correctly flips it to false and emits emptychange(false). A plain\n // initializer is safe — no cascade-dispatched setter writes it.\n private _empty: boolean = true;\n // Whether reconcileEmptyState has run at least once. Gates only the\n // \"emptychange\" emit: the first run adopts the born emptiness without an emit\n // (being born empty or populated is not a transition). The placeholder itself\n // is re-asserted every sweep from the main-region emptiness, independent of\n // this latch.\n private _emptyReconciled: boolean = false;\n\n // Named, bound listener reference for the DockRegion post-drop callback,\n // routing through one removable handler that coalesces via scheduleSweep.\n private requestSweep: () => void = (): void => {\n this.scheduleSweep();\n };\n\n // Named, bound \"dock\" handler for every wired Tab: a foreign tab was merged\n // into a region's strip. The merge bypasses DockRegion, so it lands a sweep\n // here — the host-diff reconcile then emits the attach. The content arg is\n // unused (the reconcile re-derives every frame's host) but matches the\n // listener signature.\n private onPanelDocked: (content: Component) => void = (_content: Component): void => {\n this.requestSweep();\n };\n\n /**\n * Constructs a Dock, compiling `options.layout` into the region tree (or an\n * empty `Tab` region when omitted) and running the initial re-wire sweep.\n *\n * @param options - Construction options; `layout` seeds the arrangement.\n * @param subclassDefaults - Optional defaults a subclass injects before the caller's options.\n */\n constructor(options?: DockOptions, subclassDefaults?: Partial<DockOptions>) {\n super(options, { layoutManager: new Fit(), ...subclassDefaults });\n\n const root = options?.layout ? this.compileLayout(options.layout) : this.newTabRegion();\n\n this.addComponent(root);\n this.scheduleSweep();\n this.wireEmptyDropTarget();\n\n // Wire the declarative listener bag here, not in applyOptions: applyOptions\n // runs inside super() before this._listeners' field initializer, so on()\n // would target an undefined bag. scheduleSweep only reconciles emptiness\n // asynchronously, so no emptychange is missed by wiring after it.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies inherited options first, then routes the `emptyContent` placeholder\n * through its setter. The setter only caches (no DOM work) because it is\n * dispatched from within `super()`; the placeholder attaches from the first\n * post-construction reconcile.\n *\n * @param options - The construction options.\n *\n * @returns This dock.\n */\n protected applyOptions(options: DockOptions): this {\n super.applyOptions(options);\n\n if (options.emptyContent !== undefined) {\n this.setEmptyContent(options.emptyContent);\n }\n\n return this;\n }\n\n /**\n * Sets (or clears, with `null`) the placeholder shown while the dock holds no\n * live panel — a start-page for an empty dock. The placeholder is shown as a\n * single non-closeable tab in the empty root region, and is chrome: it is\n * never serialized (excluded from a saved arrangement) and never enters the\n * panel registry. When no placeholder is set, the empty region hides its tab\n * strip entirely; either way the dock still reports emptiness and fires\n * `\"emptychange\"`.\n *\n * When the dock is already empty, the shown placeholder is swapped immediately;\n * otherwise the value is only cached and shown on the next empty transition.\n *\n * @param component - The placeholder component, or `null` to clear it.\n *\n * @returns This dock, for chaining.\n */\n setEmptyContent(component: Component | null): this {\n // Hot-swap only once the state machine is live (first reconcile has run):\n // during the super()/applyOptions cascade _emptyReconciled is still false,\n // keeping the setter cache-only so no DOM work happens at construction. The\n // placeholder is shown while the main region is empty (all tabs closed or\n // torn off), so gate the swap on that, not on dock-wide emptiness.\n const showing = this._emptyReconciled && this.mainRegionEmpty();\n\n if (showing) {\n this.hideEmptyState(); // remove the outgoing placeholder tab\n }\n\n this._options.emptyContent = component ?? undefined;\n\n if (showing) {\n this.showEmptyState(); // show the incoming placeholder tab\n }\n\n return this;\n }\n\n /**\n * The placeholder shown while the dock is empty, or `null` when none is set.\n *\n * @returns The placeholder component, or `null`.\n */\n getEmptyContent(): Component | null {\n return this._options.emptyContent ?? null;\n }\n\n /**\n * Whether the dock holds no live panel anywhere — tiled or floated. A dock\n * whose only panels are torn off into floats is *not* empty (the floats are\n * still live panels of this dock), so this reports `false` for it, and\n * `\"emptychange\"` fires off this aggregate. The empty-state placeholder is a\n * separate, *visual* concern that tracks the main region alone, so it can show\n * over an all-floated dock while this still reports `false`.\n *\n * @returns `true` when no live panel exists.\n */\n isEmpty(): boolean {\n return this._frames.size === 0;\n }\n\n /**\n * Registers the dock itself as a drop target that is active only while the\n * dock is empty — every panel torn off, so no region remains to carry a\n * `DockRegion`. Dropping a tab onto the bare dock then builds a fresh root\n * region to hold it, making an emptied dock its own re-dock surface rather\n * than leaving a placeholder region behind. While non-empty the predicate\n * declines, so the regions' own `DockRegion`s (nested deeper, hit first by\n * the hit-test) keep handling drops.\n */\n private wireEmptyDropTarget(): void {\n DragManager.makeDropTarget(this, {\n accepts: (detail: DragEventDetail): boolean =>\n detail.dragData[\"tabDrag\"] === true && this.getComponents().length === 0,\n // The full-region blue overlay is the only feedback here; suppress the\n // manager's whole-target tint so it does not stack with it.\n suppressValidityTint: true,\n onDragOver: (): null => {\n this._emptyDropOverlay.attachTo(this);\n this._emptyDropOverlay.highlightFull();\n\n return null;\n },\n onDragLeave: (): void => {\n this._emptyDropOverlay.detach();\n },\n onDrop: (detail: DragEventDetail): void => {\n this._emptyDropOverlay.detach();\n\n const panel = tabDragRegistry.get(detail.dragData[\"componentId\"] as string);\n\n if (!panel) {\n return;\n }\n\n const region = this.newTabRegion();\n\n this.addComponent(region);\n region.moveComponent(panel);\n this.scheduleSweep();\n },\n });\n }\n\n /**\n * Registers a panel and adds it as a tab in the active region, then schedules\n * a re-wire sweep so a newly-created region is made dockable.\n *\n * @param spec - The panel to register and dock.\n *\n * @returns This dock, for chaining.\n */\n addPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(content, undefined, this.leafConstraints(spec));\n\n // Activate the freshly added panel so opening it shows it. The tab\n // cell is created lazily on the region's next doLayout, so this may\n // defer (Tab.setActiveContent) until that pass.\n (region.getLayoutManager() as Tab).setActiveContent(content);\n\n // The panel just entered the tiled tree. The ledger is left without an\n // entry for this id so the next sweep's host diff sees a first\n // appearance and emits attach(tiled) — the same reconcile path a\n // dragged-in dock flows through, so a programmatic add and a drop\n // produce the event from identical code.\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Adds a panel whose content is built lazily — on first activation — instead\n * of up front. The tab appears immediately (the identity frame is created\n * empty, so re-open dedup via {@link focusPanel} and layout serialization keep\n * working); on first activation a centred spinner shows while the spec's\n * `content` factory runs, then the built content fades in. The frame's own\n * (strip-hidden) Tab layout drives this via {@link Tab.addLazyTab}, so the\n * spinner/content cross-fade is hosted by the same materialize path every lazy\n * tab uses. Use it for panels whose content is expensive to build or fetches\n * data, so opening one never blocks the tab from appearing.\n *\n * @param spec - The panel to register and dock; `content` is treated as the\n * lazy factory (a live component is wrapped in one).\n *\n * @returns This dock, for chaining.\n */\n addLazyPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n this._lazyFactories.set(\n spec.id,\n typeof spec.content === \"function\" ? spec.content : () => spec.content as Component\n );\n\n const frame = this.resolvePanel(spec.id);\n\n if (frame) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(frame, undefined, this.leafConstraints(spec));\n\n // Activate it so the tab shows; activation drives realizeLazyContent\n // (via the region's \"activate\" -> onPanelFocused) to materialize the\n // deferred content behind a spinner.\n (region.getLayoutManager() as Tab).setActiveContent(frame);\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Returns the root region container (a `Container` carrying a `Split`/`Tab`\n * manager). Derived live as the sole `Fit` child rather than cached, because\n * an edge drop onto the root swaps that child for a fresh `Split` wrapper.\n *\n * @returns The root region.\n */\n getRootRegion(): Component {\n return this.getComponents()[0];\n }\n\n /**\n * Captures the current arrangement (delegates to\n * [`serializeLayout`](/api/layout/functions/serializeLayout)).\n *\n * @returns The captured layout state.\n */\n getLayoutState(): LayoutState {\n return serializeLayout(this.getRootRegion());\n }\n\n /**\n * Restores a captured arrangement, sourcing leaves from the panel registry\n * (delegates to [`restoreLayout`](/api/layout/functions/restoreLayout)), then\n * schedules a sweep to wire the fresh regions restore created.\n *\n * The region ledger is cleared first: `restoreLayout` tears the region tree\n * down and rebuilds it, so every surviving panel lands in a fresh region\n * object. Without clearing, the post-restore sweep would read each panel's\n * stale (now-destroyed) region and spuriously fire `\"move\"` for every panel.\n * A restore is not a user-visible relocation, so it stays silent for\n * `\"move\"`; the sweep re-seeds the ledger from the rebuilt tree.\n *\n * @param state - A layout state from {@link getLayoutState}.\n *\n * @returns This dock, for chaining.\n */\n setLayoutState(state: LayoutState): this {\n this._frameRegion.clear();\n restoreLayout(this.getRootRegion(), state, (id: string) => this.resolvePanel(id));\n this.scheduleSweep();\n\n return this;\n }\n\n /**\n * The serialization factory: resolves a panel id to its Dock-owned identity\n * frame, building it once (running a lazy content factory at most once) and\n * caching it so every resolve returns the same instance.\n *\n * The frame is a `Container` constructed with the stable `id` (the serialization\n * key, read back via `getId()`) and the `title` (the visible tab label, via\n * `getName()`) set **at construction** — the caller's content is placed\n * inside it and never mutated. The id must be set at construction, not via a\n * later `setId`, so the frame's `#id`-scoped CSS rule (which carries\n * `position: absolute`) binds to the element; a post-construction `setId`\n * would leave that rule on the old id and the frame would collapse to\n * `position: static`.\n *\n * @param id - The panel id to resolve.\n *\n * @returns The identity frame, or `null` when the id is unknown.\n */\n private resolvePanel(id: string): Component | null {\n const spec = this._panels.get(id);\n\n if (!spec) {\n return null;\n }\n\n let frame = this._frames.get(id);\n\n if (!frame) {\n const factory = this._lazyFactories.get(id);\n\n if (factory) {\n // A lazy panel defers its content to first activation, and the\n // frame's own Tab layout owns that deferral: Tab.addLazyTab mounts a\n // spinner during the two-frame yield and fades the built content in,\n // hosting the transient spinner/content overlap the way every lazy\n // tab does — the strip hidden so the single content fills the frame.\n // Delegating to the Tab is why the frame is not a plain Fit here: a\n // single-child Fit faults the instant the spinner and content coexist.\n const tab = new Tab();\n\n tab.setBarVisible(false);\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: tab });\n\n // The frame's Tab is a local and unreachable from outside, so\n // this subscription is the only thing that can turn a failed\n // content build into a Dock-level event. Named reference, not an\n // inline arrow, per the listener rule in ARCHITECTURE.md — the\n // same shape `wireRegion`'s `onEmpty` uses for a per-frame\n // handler.\n const onFailed: (error: unknown) => void = (error: unknown): void => {\n this.failPanel(spec.id, error);\n };\n\n // The frame's Tab is a local and its strip is hidden, so this\n // subscription is the only thing that can put a docked panel's\n // load on screen. Named reference, not an inline arrow, per the\n // listener rule in ARCHITECTURE.md.\n const onBusyChanged: (busy: boolean) => void = (busy: boolean): void => {\n this.setFrameBusy(spec.id, busy);\n };\n\n tab.on(\"exception\", onFailed);\n tab.on(\"busychange\", onBusyChanged);\n tab.addLazyTab(factory, spec.title ?? spec.id);\n } else {\n // A normal panel builds its content now; the frame exists at once so\n // the tab shows and the id dedups / serializes like any other.\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: new Fit() });\n\n // A live component or a synchronous factory. A promise-returning\n // factory raises Component.addComponent's Error here: a Fit frame\n // has no spinner and nothing to own the wait.\n frame.addComponent(spec.content);\n }\n\n this._frames.set(id, frame);\n }\n\n return frame;\n }\n\n /**\n * Builds the glyph-only layout constraints for a leaf, or `undefined` when\n * the spec has no glyph. The glyph is the one presentation hint `Tab` reads\n * from the constraint; identity and label ride on the component itself.\n *\n * @param spec - The panel spec.\n *\n * @returns The constraints, or `undefined`.\n */\n private leafConstraints(spec: DockPanelSpec): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n // Dock tabs are closeable by default; a spec may opt out.\n constraints.closeable = spec.closeable ?? true;\n constraints.disposeOnClose = spec.disposeOnClose ?? true;\n\n if (spec.glyph) {\n constraints.glyph = spec.glyph;\n }\n\n if (spec.tooltip) {\n constraints.tooltip = spec.tooltip;\n }\n\n return constraints;\n }\n\n /**\n * Resolves the region `addPanel` docks into: the root if it is a `Tab`, else\n * the first `Tab` region found depth-first, else the root wrapped in a fresh\n * `Tab` (only reachable for an externally-crafted tab-less tree — `Dock`'s\n * own compiler and `DockRegion` always keep leaves in `Tab` stacks).\n *\n * @returns A `Tab` region to add a tab to.\n */\n private activeTabRegion(): Component {\n const root = this.getRootRegion();\n\n // Prefer the region the user last focused, when it is still a live Tab in\n // the current tree, so a new panel opens beside the last-used tab-bar.\n const last = this._lastActiveRegion;\n if (last && this.isTab(last) && this.containsRegion(root, last)) {\n return last;\n }\n\n if (this.isTab(root)) {\n return root;\n }\n\n return this.firstTabRegion(root) ?? this.wrapRootInTab();\n }\n\n /**\n * Depth-first membership test: whether `target` is `region` or nested under\n * it. Used to confirm `_lastActiveRegion` still lives in the dock's tree\n * before docking into it (a collapsed split or a layout restore can retire a\n * region object).\n *\n * @param region - The subtree root to search.\n * @param target - The region to find.\n *\n * @returns `true` when `target` is within `region`.\n */\n private containsRegion(region: Component, target: Component): boolean {\n if (region === target) {\n return true;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child) && this.containsRegion(child, target)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Depth-first search for the first `Tab` region at or under `region`.\n *\n * @param region - The region to search from.\n *\n * @returns The first `Tab` region, or `null` when none exists.\n */\n private firstTabRegion(region: Component): Component | null {\n if (this.isTab(region)) {\n return region;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n const found = this.firstTabRegion(child);\n\n if (found) {\n return found;\n }\n }\n }\n\n return null;\n }\n\n /**\n * Installs a fresh `Tab` region as the dock's single `Fit` child and\n * re-homes the existing root into it, returning the new region. The safety\n * net for a tab-less tree (see {@link activeTabRegion}).\n *\n * @returns The fresh `Tab` region now holding the old root.\n */\n private wrapRootInTab(): Component {\n const region = this.newTabRegion();\n const oldRoot = this.getRootRegion();\n\n this.addComponent(region);\n region.moveComponent(oldRoot);\n\n return region;\n }\n\n /**\n * Builds an empty region: a `Container` carrying a fresh `Tab` manager.\n *\n * @returns The new `Tab` region.\n */\n private newTabRegion(): Component {\n return new Container({ layoutManager: new Tab({ reorderable: true, compact: true }) });\n }\n\n /**\n * Compiles a declarative layout spec into a region `Component`, registering\n * every leaf in the panel registry along the way. The only structural build\n * code unique to `Dock`; runtime restructuring belongs to `DockRegion`.\n *\n * @param spec - The layout spec to compile.\n *\n * @returns The compiled region.\n */\n private compileLayout(spec: DockLayoutSpec): Component {\n if (\"split\" in spec) {\n const region = new Container({ layoutManager: new Split({ orientation: spec.split }) });\n\n for (const child of spec.children) {\n region.addComponent(this.compileRegion(child));\n }\n\n return region;\n }\n\n if (\"tabs\" in spec) {\n return this.compileTabs(spec.tabs);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Compiles a child of a split: a leaf becomes its own single-tab stack so a\n * split pane is always a draggable region, never a bare leaf.\n *\n * @param spec - The child spec.\n *\n * @returns The compiled region.\n */\n private compileRegion(spec: DockLayoutSpec): Component {\n if (\"split\" in spec || \"tabs\" in spec) {\n return this.compileLayout(spec);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Builds a `Tab` region holding the given leaves, registering and stamping\n * each one.\n *\n * @param specs - The leaf panels to stack.\n *\n * @returns The `Tab` region.\n */\n private compileTabs(specs: DockPanelSpec[]): Component {\n const region = this.newTabRegion();\n\n for (const spec of specs) {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n region.addComponent(content, this.leafConstraints(spec));\n\n // A compiled panel starts tiled; seed the host ledger with null\n // so the first sweep's host diff is silent (no transition to\n // attach from). Construction therefore emits nothing.\n this._panelHost.set(spec.id, null);\n this._frameRegion.set(spec.id, region);\n }\n }\n\n return region;\n }\n\n /**\n * Schedules one coalesced re-wire sweep on the next animation frame. Repeated\n * calls within a frame collapse to a single sweep.\n */\n private scheduleSweep(): void {\n if (this._sweepScheduled) {\n return;\n }\n\n this._sweepScheduled = true;\n\n DOM.sink.requestAnimationFrame(() => {\n this._sweepScheduled = false;\n this.runSweep();\n });\n }\n\n /**\n * Runs the idempotent sweep: adopt every owned float window into a wired\n * region tree, wire the in-dock root and each float region, then tear down\n * the coordinators of regions that have vanished from the combined live tree.\n */\n private runSweep(): void {\n const root = this.getRootRegion();\n\n if (!root) {\n return;\n }\n\n const floatRegions = this.ownedFloatWindows()\n .map(win => this.adoptFloat(win))\n .filter((region): region is Component => region !== null);\n\n this.wireRegion(root);\n\n for (const region of floatRegions) {\n this.wireRegion(region);\n }\n\n this.subscribeFloatWindows();\n this.reconcileHosts(root);\n this.teardownVanished(root, floatRegions);\n this.reconcileEmptyState();\n }\n\n /**\n * Reconciles the two independent empty concerns each sweep:\n *\n * - **Placeholder / bar** track the *main region's* emptiness — whether the\n * root region holds no tiled panel. This is true both when every tab was\n * closed and when they were all torn off into floats, so the start page\n * shows in either case. Re-asserted idempotently every sweep (not just on a\n * transition) so it re-shows after a `setLayoutState` restore clears the\n * root region wholesale.\n * - **`\"emptychange\"`** tracks the *dock-wide* emptiness — {@link isEmpty},\n * `true` only when no panel exists anywhere (tiled or floated). Latched on\n * `_empty` and emitted exactly once per real transition; a no-op sweep is\n * silent. Called at the end of every sweep and reached from every close via\n * {@link scheduleSweep}, so the emit is once per settled transition.\n */\n private reconcileEmptyState(): void {\n if (this.mainRegionEmpty()) {\n this.showEmptyState();\n } else {\n this.hideEmptyState();\n }\n\n const empty = this.isEmpty();\n\n // First reconcile: sync the born state without an emit (being born empty\n // or populated is not a transition). A born-with-layout dock differs from\n // the true=seed latch and falls through to emit emptychange(false) once.\n if (!this._emptyReconciled) {\n this._emptyReconciled = true;\n\n if (empty === this._empty) {\n return;\n }\n }\n\n if (empty === this._empty) {\n return;\n }\n\n this._empty = empty;\n this.emit(\"emptychange\", { empty });\n }\n\n /**\n * Whether the main (tiled) region holds no panel — every tab closed, or all\n * torn off into floats. The placeholder is excluded so it never counts as\n * content. This drives the placeholder, distinct from {@link isEmpty} (which\n * counts floated panels as live).\n *\n * @returns `true` when the root region shows no tiled panel.\n */\n private mainRegionEmpty(): boolean {\n const region = this.getRootRegion();\n\n if (!region) {\n return false;\n }\n\n const placeholder = this.getEmptyContent();\n\n return region.getComponents().every(child => child === placeholder);\n }\n\n /**\n * Shows the empty-state chrome in the empty root region. With a placeholder\n * set, it is docked as a single non-closeable, `transient` (never-serialized)\n * tab; without one, the region's tab strip is hidden so an emptied dock is a\n * clean surface rather than a dangling empty strip. Idempotent — safe to call\n * on every sweep — and a no-op when the root region is not a `Tab`.\n */\n private showEmptyState(): void {\n const region = this.getRootRegion();\n const placeholder = this.getEmptyContent();\n\n if (!region) {\n return;\n }\n\n if (placeholder) {\n this.rootTab()?.setBarVisible(true);\n\n if (placeholder.getParentComponent() !== region) {\n region.moveComponent(placeholder, undefined, this.placeholderConstraints());\n }\n } else {\n this.rootTab()?.setBarVisible(false);\n }\n }\n\n /**\n * Removes the empty-state chrome: detaches the placeholder tab when present\n * and restores the root region's tab strip. Idempotent — safe to call on\n * every sweep and when nothing is shown.\n */\n private hideEmptyState(): void {\n const placeholder = this.getEmptyContent();\n const parent = placeholder?.getParentComponent();\n const manager = parent?.getLayoutManager();\n\n // Remove the placeholder from wherever it currently sits. A `Tab` host must\n // go through `closeTab` (not `removeComponent`): `Tab.doLayout` reconciles\n // only *added* children, so a bare `removeComponent` would orphan the strip\n // cell and leave a stale placeholder tab. `closeTab`'s `\"tabclose\"` is\n // ignored by the dock (the placeholder is not a registered frame) and its\n // drain-`\"empty\"` is absorbed by the root-region prune guard. `closeTab`\n // returns false when no cell exists yet (added but not laid out); then a\n // plain `removeComponent` suffices — there is no cell to orphan.\n if (placeholder) {\n const closed = manager instanceof Tab && manager.closeTab(placeholder);\n\n if (!closed) {\n parent?.removeComponent(placeholder);\n }\n }\n\n this.rootTab()?.setBarVisible(true);\n }\n\n /**\n * The root region's `Tab` manager, or `null` when the root region is absent or\n * carries a different manager (e.g. an externally-crafted `Split` root). An\n * empty dock's root is always a `Tab` — the prune path keeps a single empty\n * `Tab` region — so the placeholder always finds one.\n *\n * @returns The root `Tab` manager, or `null`.\n */\n private rootTab(): Tab | null {\n const manager = this.getRootRegion()?.getLayoutManager();\n\n return manager instanceof Tab ? manager : null;\n }\n\n /**\n * The layout constraints the placeholder tab is docked under: non-closeable\n * (the start page cannot be closed) and `transient` (shown as a tab but never\n * captured by serialization). The tab's label is the placeholder component's\n * own name.\n *\n * @returns The placeholder tab constraints.\n */\n private placeholderConstraints(): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n constraints.closeable = false;\n constraints.transient = true;\n // The placeholder is consumer-owned chrome that `hideEmptyState` closes\n // through `closeTab` and `showEmptyState` re-mounts — a close must not\n // destroy it.\n constraints.disposeOnClose = false;\n\n return constraints;\n }\n\n /**\n * Idempotently subscribes the lifecycle events of every float window that\n * currently hosts one of this dock's frames — both the adopted bare\n * `Window` mini-docks and the self-contained `TabWindow` tear-offs the sweep\n * does not adopt. A `TabWindow`'s internal `Tab` is never wired by\n * `wireRegion`, so its `\"activate\"` / `\"tabclose\"` / `\"detach\"` /\n * `\"dock\"` are subscribed here explicitly; both float kinds get the\n * window's `\"activate\"` / `\"close\"`. The tracked set stops a re-sweep\n * stacking duplicate listeners.\n */\n private subscribeFloatWindows(): void {\n for (const win of this.floatWindowsHoldingFrames()) {\n if (this._floatSubscribed.has(win)) {\n continue;\n }\n\n const onFloatActivate: () => void = (): void => { this.onFloatActivated(win); };\n const onFloatClose: () => void = (): void => { this.onFloatClosed(win); };\n\n win.on(\"activate\", onFloatActivate);\n win.on(\"close\", onFloatClose);\n\n if (win instanceof TabWindow) {\n const tab = win.getLayoutManager() as Tab;\n\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n }\n\n this._floatSubscribed.add(win);\n }\n\n this.pruneClosedFloatSubscriptions();\n }\n\n /**\n * Drops closed windows from the float-subscription tracking set so a future\n * window object never collides with a stale entry. The listeners themselves\n * die with the closed window, so only the set bookkeeping is needed.\n */\n private pruneClosedFloatSubscriptions(): void {\n const open = new Set<AbstractWindow>(AbstractWindow.getOpenWindows());\n\n for (const win of this._floatSubscribed) {\n if (!open.has(win)) {\n this._floatSubscribed.delete(win);\n }\n }\n }\n\n /**\n * Open float windows hosting one of this dock's frames, including the\n * self-contained `TabWindow` tear-offs (which `ownedFloatWindows` excludes\n * because the sweep does not adopt them), and excluding the window the dock\n * itself lives in. The subscription targets for the panel lifecycle.\n *\n * @returns The float windows holding this dock's frames.\n */\n private floatWindowsHoldingFrames(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Recomputes each registered frame's host (`null` when it sits under the\n * in-dock tiled tree, else the float window holding it) and region, and diffs\n * both against their ledgers, emitting the lifecycle events the change\n * implies: a first appearance (no host ledger entry — a fresh `addPanel` or a\n * restore) emits `\"attach\"` alone; a change from one host to another (a\n * tear-off, a re-dock by either drop path, a float-to-float move) emits\n * `\"detach\"`(old host) then `\"attach\"`(new host); a same-host change of\n * region (a relocation to a different region within one host) emits\n * `\"move\"`; an unchanged host and region is silent. This is the single\n * source of every `\"attach\"`/`\"detach\"`/`\"move\"`, so the events are\n * identical regardless of which DnD path landed the sweep.\n *\n * Only frames still registered and still cached are visited, so a panel whose\n * frame a close handler already evicted produces no phantom `\"detach\"`.\n *\n * @param root - The current root region.\n */\n private reconcileHosts(root: Component): void {\n for (const [id, frame] of this._frames) {\n if (!this._panels.has(id)) {\n continue;\n }\n\n const host = this.hostForFrame(frame, root);\n const had = this._panelHost.has(id);\n const prev = this._panelHost.get(id) ?? null;\n const region = this.regionForFrame(frame);\n const prevRegion = this._frameRegion.get(id) ?? null;\n\n if (!had) {\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (host !== prev) {\n this.emit(\"detach\", { id, content: frame, window: prev });\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (region && prevRegion && region !== prevRegion) {\n // Same host, different region: the panel relocated within its\n // host. The `region && prevRegion` guard keeps a frame transiently\n // out of any region (mid-teardown) silent rather than spurious.\n this.emit(\"move\", { id, content: frame, window: host });\n }\n\n this._panelHost.set(id, host);\n\n if (region) {\n this._frameRegion.set(id, region);\n }\n }\n }\n\n /**\n * The host a frame currently occupies: `null` when it sits under the tiled\n * tree (the main dock), otherwise the float window holding it. Reuses the\n * existing tiled test and float lookup so the reconcile and the\n * focus-payload construction derive the host the same way.\n *\n * @param frame - The identity frame to locate.\n * @param root - The current root region.\n *\n * @returns The host window, or `null` for the tiled tree.\n */\n private hostForFrame(frame: Component, root: Component): AbstractWindow | null {\n return this.isUnder(root, frame) ? null : this.floatForFrame(frame);\n }\n\n /**\n * Open windows whose content subtree holds one of this dock's identity frames\n * — the floats torn off from this dock — excluding the window the dock itself\n * lives in. Re-derived each sweep (never cached) so a closed float drops out\n * naturally, mirroring the derived-live root in {@link getRootRegion}.\n *\n * A {@link TabWindow} is excluded: a default tear-off opens one as a\n * self-contained floating tabbed window, re-dockable via its own tab DnD and\n * self-closing when emptied. It is never adopted into the dock's region tree,\n * so the sweep must leave it alone — only the Shift-torn bare {@link Window}\n * floats become adoptable mini-docks.\n *\n * @returns The owned float windows.\n */\n private ownedFloatWindows(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !(win instanceof TabWindow) &&\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Whether `node` lies within `win`'s subtree — walks `node`'s ancestor chain\n * looking for `win`. Used both to detect a float hosting a frame and to\n * exclude the dock's own host window (which contains the dock, hence every\n * still-docked frame).\n *\n * @param win - The candidate ancestor window.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `win` is an ancestor of `node`.\n */\n private windowContains(win: AbstractWindow, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === win) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * A window's first non-chrome child — its content panel — or `null` when the\n * window has none yet.\n *\n * @param win - The window to inspect.\n *\n * @returns The content component, or `null`.\n */\n private windowContent(win: AbstractWindow): Component | null {\n return win.getComponents().find(child => !win.isChromeComponent(child)) ?? null;\n }\n\n /**\n * Ensures a float window's content is a wired-able region tree and returns\n * that region. A freshly torn-off float holds its bare identity frame as the\n * window's content; this wraps it in a single fresh `Tab` region so it is a\n * proper region leaf with a draggable handle, turning the window into a\n * mini-dock. Idempotent: once the content is a region container (already\n * adopted, or restored as a tree) it is returned unchanged, so re-sweeps\n * after edge-splits inside the float do not re-wrap.\n *\n * @param win - The float window to adopt.\n *\n * @returns The float's content region, or `null` when the window has no content.\n */\n private adoptFloat(win: AbstractWindow): Component | null {\n const content = this.windowContent(win);\n\n if (!content) {\n return null;\n }\n\n if (this.isRegionContainer(content)) {\n return content;\n }\n\n const region = this.newTabRegion();\n\n // A fresh region carries no constraint, so the window's Border fills it as\n // an unplaced→CENTER child — the same way the bare frame filled before.\n win.moveComponent(region);\n region.moveComponent(content);\n\n return region;\n }\n\n /**\n * Idempotently wires a region and recurses into its child regions: makes a\n * `Tab` region reorderable and prunes it when its last tab leaves, and gives\n * every region a `DockRegion` so it accepts edge/centre drops and notifies\n * the dock after a drop mutates the tree.\n *\n * @param region - The region to wire.\n */\n private wireRegion(region: Component): void {\n let wiring = this._wiring.get(region);\n\n if (!wiring) {\n wiring = { dockRegion: new DockRegion(region, this.requestSweep), tabWired: false };\n\n this._wiring.set(region, wiring);\n }\n\n const manager = region.getLayoutManager();\n\n if (this.isTab(region) && !wiring.tabWired) {\n const tab: Tab = manager as Tab;\n\n tab.setReorderable(true);\n // Per-region prune; the named const carries the region the shared\n // handler set otherwise could not (ARCHITECTURE: a listener is a named\n // reference, never an inline arrow).\n const onEmpty: () => void = (): void => { this.pruneRegion(region); };\n\n tab.on(\"empty\", onEmpty);\n // The lifecycle handlers are shared bound methods: their payloads (the\n // closed/activated content, the torn-off window) carry the identity\n // they need, so no per-region capture is required.\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n\n wiring.tabWired = true;\n }\n\n for (const child of region.getComponents()) {\n // A lazy panel's identity frame carries a Tab manager, which would\n // otherwise make this sweep wire the panel itself as a drop-taking,\n // prunable region — so its inner strip draining would prune the\n // frame out of its parent and leave a phantom tab behind.\n if (this.isRegionContainer(child) && this._frames.get(child.getId()) !== child) {\n this.wireRegion(child);\n }\n }\n }\n\n /**\n * Removes a `Tab` region once its last tab has left, then collapses a `Split`\n * that the removal leaves with a single pane, and schedules a sweep to tear\n * down the removed region's coordinator. Wired to every `Tab` region's\n * `\"empty\"` event so regions the dock itself built (the `compileLayout` /\n * `restoreLayout` tree) are pruned, not just the stacks `DockRegion` mints.\n * No-op when the region was already detached (a `DockRegion`-created stack\n * pruned itself first) or still holds a tab.\n *\n * @param region - The region whose `Tab` fired `\"empty\"`.\n */\n private pruneRegion(region: Component): void {\n const parent = region.getParentComponent();\n\n if (!parent || region.getComponents().length > 0) {\n return;\n }\n\n // Keep an emptied root region (its parent is the dock) as the dock's\n // add/drop target — removing it would leave the dock with no region and\n // crash the next addPanel.\n if (parent === this) {\n return;\n }\n\n parent.removeComponent(region);\n this.collapseSinglePaneSplit(parent);\n this.closeFloatIfEmpty(parent);\n this.scheduleSweep();\n }\n\n /**\n * Closes a float window whose mini-dock just emptied. When a region was the\n * direct content of a float window and pruning it leaves the window with no\n * content, the float has nothing left to host — close it, matching the\n * auto-close a strip-mode tear-off window performs when its last tab leaves.\n * A no-op when `container` is an in-dock region (its parent is the dock or a\n * `Split`, never a window) or the window still holds content.\n *\n * @param container - The container the pruned region was removed from.\n */\n private closeFloatIfEmpty(container: Component): void {\n if (!AbstractWindow.getOpenWindows().includes(container as AbstractWindow)) {\n return;\n }\n\n if (!this.windowContent(container as AbstractWindow)) {\n (container as AbstractWindow).requestClose();\n }\n }\n\n /**\n * Collapses a `Split` left with a single child: the lone child is hoisted\n * into the grandparent at the `Split`'s slot (via `moveComponent`) and the\n * emptied `Split` removed. A no-op unless `container` is a single-child\n * `Split` with a grandparent (a root single-pane `Split` renders its child\n * full-bleed). The swap keeps the grandparent's child count unchanged, so the\n * collapse does not cascade.\n *\n * @param container - The container that just lost a child.\n */\n private collapseSinglePaneSplit(container: Component): void {\n if (this.regionKind(container) !== \"Split\") {\n return;\n }\n\n const children = container.getComponents();\n\n if (children.length !== 1) {\n return;\n }\n\n const grandparent = container.getParentComponent();\n\n if (!grandparent) {\n return;\n }\n\n const index = grandparent.getComponents().indexOf(container);\n\n // Carry the collapsing Split's stored size onto the hoisted child so the\n // slot keeps the user's dragged ratio (when the grandparent is a Split).\n if (this.regionKind(grandparent) === \"Split\") {\n (grandparent.getLayoutManager() as Split).transferPaneSize(container, children[0]);\n }\n\n grandparent.moveComponent(children[0], index);\n grandparent.removeComponent(container);\n }\n\n /**\n * Destroys the wiring of every tracked region no longer reachable from the\n * combined live tree (the in-dock root plus every owned float's region tree),\n * releasing the drop targets a removed region's coordinator held. Seeding the\n * reachable set from the float regions too is what keeps a float's drop\n * targets alive across sweeps driven by unrelated in-dock moves.\n *\n * @param root - The current root region.\n * @param floatRegions - The adopted content region of each owned float window.\n */\n private teardownVanished(root: Component, floatRegions: Component[]): void {\n const reachable = new Set<Component>();\n\n this.collectRegions(root, reachable);\n\n for (const region of floatRegions) {\n this.collectRegions(region, reachable);\n }\n\n for (const [region, wiring] of this._wiring) {\n if (!reachable.has(region)) {\n wiring.dockRegion.destroy();\n this._wiring.delete(region);\n }\n }\n }\n\n /**\n * Collects every region container at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The set to populate.\n */\n private collectRegions(region: Component, into: Set<Component>): void {\n into.add(region);\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectRegions(child, into);\n }\n }\n }\n\n /**\n * Whether a component is a region container — a `Container` carrying a `Split`\n * or `Tab` manager. Discriminates on the stripped runtime class name (no\n * `instanceof`, avoiding an import cycle), matching how serialization keys\n * its node kinds.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Split`/`Tab` region.\n */\n private isRegionContainer(component: Component): boolean {\n const kind = this.regionKind(component);\n\n return kind === \"Split\" || kind === \"Tab\";\n }\n\n /**\n * Whether a component is a `Tab` region.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Tab` region.\n */\n private isTab(component: Component): boolean {\n return this.regionKind(component) === \"Tab\";\n }\n\n /**\n * The stripped runtime class name of a component's layout manager, or the\n * empty string when it has none (a leaf content component).\n *\n * @param component - The component to inspect.\n *\n * @returns The manager's class name without its `_` export-alias prefix.\n */\n private regionKind(component: Component): string {\n const manager = component.getLayoutManager() as (Tab | Split | undefined);\n\n return manager ? manager.getClassName().replace(/^_/, \"\") : \"\";\n }\n\n // ----- panel lifecycle -----\n\n /**\n * `\"tabclose\"` handler for every wired `Tab` (tiled or float): a registered\n * panel was genuinely closed. Emits `\"close\"`, evicts the cached frame so a\n * re-`addPanel` rebuilds it via the lazy factory (keeping the `_panels`\n * registration), and — when the closed panel was the dock-wide focused one —\n * recomputes focus once the source `Tab` has re-selected a survivor.\n *\n * @param content - The closed tab's content (a Dock identity frame).\n */\n private onPanelClosed = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n const region = this._frameRegion.get(id) ?? null;\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content, window: null });\n\n // Route the close through the one reconcile site so \"emptychange\" fires\n // even when no structural prune scheduled a sweep. The latch-diff makes a\n // second reconcile in the same settled state a no-op, so this cannot\n // double-fire alongside pruneRegion's own scheduleSweep.\n this.scheduleSweep();\n\n if (this._focusedPanelId === id) {\n this.scheduleFocusRecompute(region);\n }\n };\n\n /**\n * Closes a lazy panel whose content factory rejected, then reports the\n * failure as this dock's `\"exception\"`. The panel's own `\"close\"` event\n * fires first, from the shared close path.\n *\n * The panel stays registered, so re-adding the same id rebuilds its frame\n * and runs the factory again — that is the retry path.\n *\n * @param id - The id of the panel whose factory rejected.\n * @param error - The value the factory's promise rejected with.\n */\n private failPanel(id: string, error: unknown): void {\n // The panel was closed while its factory was still in flight: the close\n // path already evicted its frame, so there is nothing left to tear down\n // and nobody left to report to. Closing a docked panel closes the tab in\n // the *outer* region and never touches the frame's own entry, so the\n // inner staleness check cannot see this — the registry is what knows.\n if (!this._frames.has(id)) {\n return;\n }\n\n // A frame that is registered but sits in no Tab region cannot be closed\n // through the shared path; evict it directly so a re-add rebuilds it.\n if (!this.removePanel(id)) {\n this._frames.delete(id);\n }\n\n this.emit(\"exception\", { id, error });\n }\n\n /**\n * Re-points a lazy panel's busy state from its hidden inner strip onto the tab\n * that actually shows it: the panel's tab in whichever region hosts its frame.\n * A no-op while the frame sits in no `Tab` region — registered but never\n * docked, torn off into a float, or mid-teardown.\n *\n * @param id - The id of the panel whose content started or finished loading.\n * @param busy - True while the panel's content factory is still in flight.\n */\n private setFrameBusy(id: string, busy: boolean): void {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return;\n }\n\n (region.getLayoutManager() as Tab).setTabBusy(frame, busy);\n }\n\n /**\n * `\"activate\"` handler for every wired `Tab`: the active tab changed via a\n * click or `setActiveTabIndex`. Emits `\"focus\"` for the now-active panel,\n * gated on a genuine focused-panel change.\n *\n * @param content - The now-active tab's content (a Dock identity frame).\n */\n private onPanelFocused = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n // Remember the focused panel's region so the next add docks beside the\n // tab-bar the user just worked in (see activeTabRegion).\n const region = this._frameRegion.get(id);\n if (region) {\n this._lastActiveRegion = region;\n }\n\n this.setFocus(id);\n };\n\n /**\n * `\"detach\"` handler for every wired `Tab`: a tab was torn off into a new\n * float window. Schedules a sweep so the new float is wired (and any\n * Shift-torn bare `Window` adopted); the sweep's host-diff reconcile observes\n * the torn-off frame's tiled -> float transition and emits the\n * `\"detach\"`(tiled) + `\"attach\"`(float) pair itself, so this handler emits\n * nothing directly — the reconcile is the single source of those events.\n *\n * @param _window - The float window the tab was torn off into (unused; the\n * reconcile re-derives every frame's host).\n */\n private onPanelDetached = (_window: AbstractWindow): void => {\n this.scheduleSweep();\n };\n\n /**\n * Window `\"activate\"` handler for an owned float: the float became the active\n * layer. Emits `\"focus\"` for the float's active panel, gated on a genuine\n * focused-panel change.\n *\n * @param window - The float window that was activated.\n */\n private onFloatActivated(window: AbstractWindow): void {\n const frame = this.activeFrameInFloat(window);\n\n if (frame) {\n this.setFocus(frame.getId());\n }\n }\n\n /**\n * Window `\"close\"` handler for an owned float: the float's chrome ✕ closed\n * it. Emits one `\"close\"` per registered frame the float held (a bare-`Window`\n * mini-dock can hold several) — read before the window tears down — and\n * recomputes focus when a closed frame was the focused panel.\n *\n * @param window - The float window being closed.\n */\n private onFloatClosed(window: AbstractWindow): void {\n let focusLost = false;\n\n for (const frame of this.framesInWindow(window)) {\n const id = frame.getId();\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content: frame, window: null });\n\n if (this._focusedPanelId === id) {\n focusLost = true;\n }\n }\n\n // Route the close through the reconcile so a last-float close flips the\n // empty latch and fires \"emptychange\" (a float close schedules no\n // structural sweep of its own).\n this.scheduleSweep();\n\n if (focusLost) {\n this.scheduleFocusRecompute(null);\n }\n }\n\n /**\n * Sets the dock-wide focused panel and emits `\"focus\"` only on a genuine\n * change, so re-activating the already-focused panel is silent. A `null` id\n * clears focus and emits `focus(null)`.\n *\n * @param id - The newly-focused panel id, or `null` when none is focused.\n */\n private setFocus(id: string | null): void {\n if (id === this._focusedPanelId) {\n return;\n }\n\n this._focusedPanelId = id;\n\n if (id === null) {\n this.emit(\"focus\", null);\n\n return;\n }\n\n const frame = this._frames.get(id);\n\n if (frame) {\n const host = this.hostForFrame(frame, this.getRootRegion());\n\n this.emit(\"focus\", { id, content: frame, window: host });\n }\n }\n\n /**\n * Schedules a deferred focus recompute after a close. The source `Tab`\n * re-selects a survivor (visually, with no event) *after* its `\"tabclose\"`\n * fires, so the new active tab is only readable on the next frame.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private scheduleFocusRecompute(region: Component | null): void {\n DOM.sink.requestAnimationFrame(() => this.recomputeFocusAfterClose(region));\n }\n\n /**\n * Recomputes the dock-wide focus after the focused panel was closed: when\n * panels remain in `region`, focus the survivor the region re-selected; when\n * no panel remains anywhere, emit `focus(null)`.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private recomputeFocusAfterClose(region: Component | null): void {\n if (this._frames.size === 0) {\n this.setFocus(null);\n\n return;\n }\n\n if (!region || !this.isTab(region) || region.getComponents().length === 0) {\n this.setFocus(null);\n\n return;\n }\n\n const frame = (region.getLayoutManager() as Tab).getActiveContent();\n\n this.setFocus(frame ? frame.getId() : null);\n }\n\n /**\n * Activates the tab hosting `id` and raises its host float when it lives in\n * one, so a buried floated panel surfaces. A successful activation drives the\n * host `Tab`'s active-tab change and the float raise, each of which emits a\n * `\"focus\"`.\n *\n * @param id - The panel id to focus.\n *\n * @returns `true` when the panel was found and activated, `false` for an\n * unknown id or one in no `Tab` region (registered but never docked).\n */\n focusPanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n const index = (region.getLayoutManager() as Tab).indexOfContent(frame);\n\n if (index < 0) {\n return false;\n }\n\n this.floatForFrame(frame)?.bringToFront();\n (region.getLayoutManager() as Tab).setActiveTabIndex(index);\n\n return true;\n }\n\n /**\n * Closes the panel `id` through the same user-close path a tab ✕ takes, so it\n * emits exactly one `\"close\"` through the shared `\"tabclose\"` subscription.\n *\n * @param id - The panel id to close.\n *\n * @returns `true` when the panel was found and closed, `false` for an unknown\n * id or one in no `Tab` region.\n */\n removePanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n return (region.getLayoutManager() as Tab).closeTab(frame);\n }\n\n /**\n * The `Tab` region currently hosting `frame` — searched across the in-dock\n * tiled tree and every float window's region tree — or `null` when no `Tab`\n * region holds it (registered but never docked, or mid-teardown).\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host `Tab` region, or `null`.\n */\n private regionForFrame(frame: Component): Component | null {\n for (const region of this.allTabRegions()) {\n if ((region.getLayoutManager() as Tab).indexOfContent(frame) >= 0) {\n return region;\n }\n }\n\n return null;\n }\n\n /**\n * The float window currently hosting `frame`, or `null` when it lives in the\n * in-dock tiled tree (or nowhere). Used to raise a buried float on focus.\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host float window, or `null`.\n */\n private floatForFrame(frame: Component): AbstractWindow | null {\n return this.floatWindowsHoldingFrames().find(win => this.windowContains(win, frame)) ?? null;\n }\n\n /**\n * Every `Tab` region across the combined live tree: the in-dock root plus\n * each float window (an adopted bare-`Window` mini-dock's region tree, and a\n * `TabWindow` whose own layout manager is the `Tab`).\n *\n * @returns The live `Tab` regions.\n */\n private allTabRegions(): Component[] {\n const regions: Component[] = [];\n const root = this.getRootRegion();\n\n if (root) {\n this.collectTabRegions(root, regions);\n }\n\n for (const win of this.floatWindowsHoldingFrames()) {\n if (win instanceof TabWindow) {\n regions.push(win as unknown as Component);\n\n continue;\n }\n\n const content = this.windowContent(win);\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n }\n\n return regions;\n }\n\n /**\n * Collects every `Tab` region at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The array to populate.\n */\n private collectTabRegions(region: Component, into: Component[]): void {\n if (this.isTab(region)) {\n into.push(region);\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectTabRegions(child, into);\n }\n }\n }\n\n /**\n * The registered frames of this dock that lie within `window`'s subtree — the\n * panels a float holds. Read at float-close time to fan out one `\"close\"` per\n * frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The registered frames inside the window.\n */\n private framesInWindow(window: AbstractWindow): Component[] {\n return [...this._frames.values()].filter(frame => this.windowContains(window, frame));\n }\n\n /**\n * The active panel frame inside a float window: a `TabWindow`'s own active\n * tab, or the active tab of the first `Tab` region inside a bare-`Window`\n * mini-dock. `null` when none resolves to a registered frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The active registered frame, or `null`.\n */\n private activeFrameInFloat(window: AbstractWindow): Component | null {\n let tab: Tab | null = null;\n\n if (window instanceof TabWindow) {\n tab = window.getLayoutManager() as Tab;\n } else {\n const content = this.windowContent(window);\n const regions: Component[] = [];\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n\n tab = regions.length > 0 ? (regions[0].getLayoutManager() as Tab) : null;\n }\n\n const frame = tab ? tab.getActiveContent() : null;\n\n return frame && this._frames.get(frame.getId()) === frame ? frame : null;\n }\n\n /**\n * Whether `node` lies at or under `ancestor`'s component subtree.\n *\n * @param ancestor - The candidate ancestor component.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `ancestor` is `node` or one of its ancestors.\n */\n private isUnder(ancestor: Component, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === ancestor) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Registers a listener for a panel-lifecycle event. The `\"attach\"`,\n * `\"detach\"`, `\"move\"`, and `\"close\"` events always carry a\n * {@link DockPanelEvent}; `\"focus\"` carries a `DockPanelEvent` or `null` when\n * nothing is focused. The payload's `window` field names the host: `null` for\n * the tiled tree, otherwise the float window the panel entered (`\"attach\"`),\n * left (`\"detach\"`), or moved within (`\"move\"`, same host before and after);\n * it is always `null` for `\"close\"`.\n *\n * @param event - `\"attach\"` / `\"detach\"` / `\"move\"` / `\"close\"`.\n * @param listener - Invoked with the affected panel.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Registers a listener for the `\"focus\"` event, which fires when the\n * dock-wide active panel changes, carrying the now-focused panel or `null`\n * when nothing is focused (e.g. the last panel closed).\n *\n * @param event - The `\"focus\"` event.\n * @param listener - Invoked with the now-focused panel, or `null`.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Registers a listener for the `\"emptychange\"` event, which fires once each\n * time the dock transitions between empty (no live panel anywhere) and\n * populated, carrying `{ empty }`.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - Invoked with the new emptiness state.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Registers a listener for the `\"exception\"` event, which fires when a lazy\n * panel's content factory rejected. The panel has already been closed and\n * its `\"close\"` event already emitted, so a listener must not call\n * {@link Dock.removePanel} for that id. The panel stays registered, so\n * re-adding the same id rebuilds its frame and retries the factory.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - Invoked with the failed panel's id and the rejection value.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n on(event: DockEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Removes a previously registered `\"focus\"` listener.\n *\n * @param event - The `\"focus\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Removes a previously registered `\"emptychange\"` listener.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Removes a previously registered `\"exception\"` listener.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - The exact listener reference passed to `on`.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n off(event: DockEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event` with `payload`, in\n * registration order.\n *\n * @param event - The event to emit.\n * @param payload - The lifecycle payload (`null` only for `\"focus\"`).\n */\n protected emit(event: \"attach\" | \"detach\" | \"move\" | \"close\", payload: DockPanelEvent): void;\n protected emit(event: \"focus\", payload: DockPanelEvent | null): void;\n protected emit(event: \"emptychange\", payload: DockEmptyEvent): void;\n protected emit(event: \"exception\", payload: DockExceptionEvent): void;\n protected emit(event: DockEvent, payload: DockPanelEvent | DockEmptyEvent | DockExceptionEvent | null): void {\n this._listeners.fire(event, payload);\n }\n\n /**\n * Disposes the empty-state drop overlay before the base destructor runs.\n * `attachTo` raw-appends the overlay onto this dock's element rather than\n * registering it as a child, so the inherited child recursion never\n * reaches it; the `detach()` calls in the drop-target callbacks are\n * mid-drag hides of a still-live overlay, not teardown.\n */\n protected destructor(): void {\n this._emptyDropOverlay.dispose();\n\n // Each wired region owns a DockRegion holding its own drop-zone\n // overlay. `destroy()` is otherwise reached only by the\n // unreachable-region sweep, so without this every region a drag ever\n // hovered would keep its overlay's rules when the dock goes away.\n for (const wiring of this._wiring.values()) {\n wiring.dockRegion.destroy();\n }\n\n this._wiring.clear();\n\n super.destructor();\n }\n}\n\nconst DockCallable = callable(Dock);\ntype DockCallable = Dock;\nexport {\n Dock as _Dock,\n DockCallable as Dock,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"overlay.es.js","names":[],"sources":["../../src/typescript/lib/overlay/PopupPanel.ts","../../src/typescript/lib/overlay/Notification.ts","../../src/typescript/lib/overlay/NotificationHistoryButton.ts","../../src/typescript/lib/overlay/Drawer.ts","../../src/typescript/lib/overlay/RailHandle.ts","../../src/typescript/lib/overlay/Rail.ts","../../src/typescript/lib/overlay/Dock.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AnimatedDropdown, AnimatedDropdownOptions } from \"~/core/AnimatedDropdown.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle, Rect } from \"~/core/DOM.js\";\nimport { positionAnchoredFlexible, AnchoredFlexiblePlacement } from \"~/core/OverlayPosition.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport type { Size } from \"~/primitive/Size.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport type { StyleBag } from \"~/core/ClassStyleRules.js\";\n\n/**\n * Construction-time options for {@link PopupPanel}. Adds no fields of its\n * own — content, layout, insets, and a pinned size all come from the\n * inherited {@link AnimatedDropdownOptions} / `ComponentOptions` fields\n * (`layoutManager`, `components`, `insets`, `preferredSize`).\n *\n * @category Components\n */\nexport interface PopupPanelOptions extends AnimatedDropdownOptions {}\n\n/** Pixels kept between a clamped panel and the viewport edge so the panel\n * border and shadow are never flush against the screen. Mirrors the small\n * inset used by other floating panels (e.g. {@link Menu}); purely cosmetic\n * breathing room. */\nconst VIEWPORT_MARGIN = 4;\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * Split out from the constructor's inline literal (which also carries\n * fresh-per-instance `layoutManager`/`insets` values) so the CSS-relevant\n * subset alone can double as this class's `ownClassStyleDefaults` — sharing\n * a `VBox` layout manager across every panel would be a real bug, so that\n * field (and `insets`) stays inline at the call site.\n */\nconst _defaultPopupPanelOptions: Partial<PopupPanelOptions> = {\n backgroundColor: \"var(--ts-ui-autocomplete-bg, rgb(255, 255, 255))\",\n border: \"var(--ts-ui-input-border)\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n shadow: \"var(--ts-ui-autocomplete-shadow, 2px 4px 8px rgba(0,0,0,0.15))\",\n};\n\n/**\n * A floating panel that sizes itself to its content, places itself against a\n * trigger rect, and caps its height to the room available there — the\n * building block for a custom popup with no overlay plumbing of its own.\n * Content, layout, insets, and a pinned size are supplied through the\n * inherited `ComponentOptions` fields; `PopupPanel` adds only measurement,\n * placement, and the height cap on top of what {@link AnimatedDropdown}\n * already provides (the fade, the portal mount, and the\n * [`DismissableLayer`](/api/core/interfaces/DismissableLayer) contract).\n *\n * Every {@link showAt} recomputes `maxSize` from the room at the anchor and\n * commits the measured content height, so an over-tall panel is capped and\n * scrolls (`overflow-y: auto`) instead of running off-screen. The native\n * scrollbar overlaps the trailing edge of scrolled content — `PopupPanel`\n * reserves no gutter for it, unlike {@link Menu}, because its insets belong\n * to the consumer. A consumer whose popup routinely overflows should wrap\n * its content in a `Panel({ autoScroll: \"y\" })` under a `Fit` layout, whose\n * own gutter machinery insets correctly.\n *\n * @example\n * ```typescript\n * import { PopupPanel } from '@jimka/typescript-ui/overlay';\n * import { VBox } from '@jimka/typescript-ui/layout';\n * import { Checkbox } from '@jimka/typescript-ui/component/input';\n *\n * const panel = new PopupPanel({\n * layoutManager: new VBox({ spacing: 4, stretching: true }),\n * components: [ new Checkbox({ label: 'Show archived' }) ],\n * });\n *\n * panel.toggleFor(triggerEl, DOM.source.getViewportRect(trigger));\n * ```\n *\n * @category Components\n */\nclass PopupPanel<TOptions extends PopupPanelOptions = PopupPanelOptions> extends AnimatedDropdown<TOptions> {\n\n // Own contribution to the hierarchy-aware class tier — see\n // plans/implemented/class-hierarchy-cascade.md. `PopupPanel` deviates\n // from `AnimatedDropdown` on `backgroundColor`/`border`/`borderRadius`/\n // `shadow` (`AnimatedDropdown` itself declares none of these), so it\n // needs its own registration or the hierarchy walk would silently pass\n // through to `AnimatedDropdown`'s shared rule and lose its entire\n // visible chrome.\n protected static readonly ownClassStyleDefaults: StyleBag = _defaultPopupPanelOptions;\n\n // Opener currently driving the panel via toggleFor, or null when the panel\n // was opened some other way (a direct showAt) or is closed. A plain\n // initializer is correct here — no cascade-dispatched setter writes it.\n private _currentOpener: Handle | null = null;\n\n /**\n * @param options - Optional construction-time options.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults. Forwarded even though no subclass exists yet, per\n * the framework's `subclassDefaults` convention.\n */\n constructor(options?: PopupPanelOptions, subclassDefaults?: Partial<PopupPanelOptions>) {\n super(\n options as TOptions,\n {\n // Constructed inline (never hoisted to a module constant) so\n // every panel gets its own layout manager instance.\n layoutManager: new VBox({ stretching: true }),\n insets: new Insets(4, 4, 4, 4),\n ..._defaultPopupPanelOptions,\n ...(subclassDefaults ?? {}),\n } as Partial<TOptions>,\n );\n\n this.getAria().setRole(\"dialog\");\n this.setContain(\"layout\");\n\n // Native vertical scroll for over-tall content — the \"y\" case of\n // Panel.setAutoScroll, replicated here because PopupPanel is not a\n // Panel. Reading the manager back through getLayoutManager() (rather\n // than the local default above) is what makes the flag land on a\n // caller-supplied manager too.\n this.setOverflowX(\"hidden\");\n this.setOverflowY(\"auto\");\n this.getLayoutManager()?.setOverflowing(false, true);\n }\n\n /**\n * Measures the panel's content, places it against `anchorRect`, caps its\n * height to the room available there, mounts it, and plays the entrance\n * fade.\n *\n * @param anchorRect - The trigger's bounding rect to place against.\n * @returns This panel, for method chaining.\n */\n showAt(anchorRect: Rect): this {\n // Realise the element before any layout pass: getInnerSize() is null\n // while detached, so a Fit-style manager would size children to 0 on\n // first open.\n this.getElement(true);\n\n // A reused panel still carries the previous open's height cap; clear\n // it before measuring or the content is capped at the old room.\n this.setMaxSize({ width: Number.MAX_VALUE, height: Number.MAX_VALUE });\n\n const preferred = this.getPreferredSize();\n const width = preferred?.width ?? this.getWidth();\n const height = preferred?.height ?? this.getHeight();\n const viewport = DOM.source.getViewportSize();\n const placement = this.resolvePlacement(anchorRect, { width, height }, viewport);\n\n this.setWidth(width);\n this.setMaxSize({ width: Number.MAX_VALUE, height: Math.max(0, placement.available) });\n this.setHeight(height);\n this.setX(placement.x);\n this.setY(placement.y);\n\n this.showAnimated();\n this.doLayout();\n\n return this;\n }\n\n /**\n * Opens the panel anchored at `anchorRect` for `openerEl`, or closes it\n * when `openerEl` already opened it — the toggle-identity contract every\n * dropdown-style trigger uses (see {@link Menu.toggleFor}). Toggling for a\n * *different* opener while open re-shows the panel at the new rect rather\n * than closing it. `setAnchorElement` runs before {@link showAt} so the\n * layer manager excludes the trigger from its outside-pointerdown test.\n *\n * @param openerEl - The trigger element driving the toggle.\n * @param anchorRect - The trigger's bounding rect to place against.\n * @returns This panel, for method chaining.\n */\n toggleFor(openerEl: Handle, anchorRect: Rect): this {\n if (this.isOpen() && this._currentOpener === openerEl) {\n this.hideAnimated();\n\n return this;\n }\n\n this._currentOpener = openerEl;\n this.setAnchorElement(openerEl);\n\n const openerId = DOM.source.getId(openerEl);\n if (openerId !== \"\") {\n this.getAria().setLabelledBy(openerId);\n }\n\n return this.showAt(anchorRect);\n }\n\n /**\n * Clears the opener identity, then defers to\n * [`AnimatedDropdown.hideAnimated`](/api/core/classes/AnimatedDropdown#hideanimated)\n * for the exit fade and detach. Forgetting the opener here (rather than\n * only in {@link toggleFor}'s own toggle-shut branch) is what makes a\n * `hideAnimated` from any path — an outside dismissal via\n * [`AnimatedDropdown.requestClose`](/api/core/classes/AnimatedDropdown#requestclose),\n * or a direct call — read as a plain close rather than half of a toggle,\n * so the next `toggleFor` for the same opener opens instead of toggling\n * shut.\n *\n * @returns This panel, for method chaining.\n */\n hideAnimated(): this {\n this._currentOpener = null;\n\n return super.hideAnimated();\n }\n\n /**\n * Resolves the panel's placement against `anchorRect`. The override\n * point a subclass replaces for different geometry; defaults to\n * {@link positionAnchoredFlexible}.\n *\n * @param anchorRect - The trigger's bounding rect to place against.\n * @param size - The panel's measured (unclamped) preferred width/height.\n * @param viewport - The current viewport size.\n * @returns The resolved top-left coordinate and the room available there.\n */\n protected resolvePlacement(anchorRect: Rect, size: Size, viewport: Size): AnchoredFlexiblePlacement {\n return positionAnchoredFlexible(anchorRect, size, viewport, VIEWPORT_MARGIN);\n }\n}\n\nconst PopupPanelCallable = callable(PopupPanel);\ntype PopupPanelCallable<TOptions extends PopupPanelOptions = PopupPanelOptions> = PopupPanel<TOptions>;\nexport {\n PopupPanel as _PopupPanel,\n PopupPanelCallable as PopupPanel,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager } from \"~/core/LayerManager.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { SelectableText } from \"~/component/input/SelectableText.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { _Dialog, DialogButtons } from \"~/overlay/Dialog.js\";\nimport { circle_info } from \"~/glyphs/solid/circle_info.js\";\nimport { circle_check } from \"~/glyphs/solid/circle_check.js\";\nimport { triangle_exclamation } from \"~/glyphs/solid/triangle_exclamation.js\";\nimport { circle_exclamation } from \"~/glyphs/solid/circle_exclamation.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { ThemeManager } from \"~/core/Theme.js\";\n\n/**\n * Square edge length used for the notification's severity badge glyph — the\n * theme's `glyphXl` standalone-icon step (20px at the shipped base): read on\n * its own, not beside text. Read per call, not frozen in a module constant,\n * so a theme that raises `scale.base` moves the icon with it.\n */\nfunction badgeSizePx(): number {\n return ThemeManager.getResolvedScale().glyphXl;\n}\n\nGlyph.register(circle_info, circle_check, triangle_exclamation, circle_exclamation, xmark);\n\n/**\n * The visual severity of a notification.\n *\n * @category Core\n */\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * A single captured notification, retained in the in-session history returned\n * by {@link Notification.getHistory}.\n *\n * @category Core\n */\nexport interface NotificationRecord {\n /** The full (un-truncated) message text passed to {@link Notification.show}. */\n readonly message: string;\n /** The severity type the toast was shown with. */\n readonly type: NotificationType;\n /** Epoch milliseconds (`Date.now()`) when the toast was shown. */\n readonly timestamp: number;\n}\n\n/**\n * Maps a notification severity to its registry glyph name. Exported (module,\n * not barrel — so it stays out of the public API docs) so the notification\n * history menu reuses the same severity-icon mapping rather than duplicating it.\n */\nexport const BADGE_GLYPH: Record<NotificationType, string> = {\n info: \"circle-info\",\n success: \"circle-check\",\n warning: \"triangle-exclamation\",\n error: \"circle-exclamation\",\n};\n\nconst DETAIL_TITLE: Record<NotificationType, string> = {\n info: \"Information\",\n success: \"Success\",\n warning: \"Warning\",\n error: \"Error\",\n};\n\nconst MIN_RESUMED_MS: number = 8000;\nconst DISMISS_DURATION_MS: number = 200;\nconst ENTRANCE_DURATION_MS: number = 200;\n\n/**\n * A lightweight toast-style notification that appears in the bottom-right corner\n * of the viewport and auto-dismisses after a configurable duration.\n *\n * Multiple notifications stack upward. Each can also be dismissed manually via\n * the × button. Long messages are clipped to two lines with an ellipsis; a\n * double-click on the body opens a modal detail dialog containing the full\n * message.\n *\n * @example\n * ```typescript\n * Notification.show('Record saved.', 'success');\n * Notification.show('Connection lost.', 'error', 0); // persistent\n * ```\n *\n * @category Core\n */\nexport class Notification extends Component {\n\n // In-flight entrance / dismiss animations, cancelled on teardown so their\n // fallback timers cannot fire against this notification's released handle.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _dismissAnimation: Animation.CancelHandle | null = null;\n\n private static readonly WIDTH: number = 320;\n private static readonly HEIGHT: number = 64;\n private static readonly MARGIN: number = 16;\n private static readonly H_PADDING: number = 12;\n private static readonly V_PADDING: number = 10;\n private static readonly CLOSE_SIZE: number = 20;\n private static readonly BADGE_TEXT_GAP: number = 8;\n // Stacking z-index for toasts. Sits just above the managed dropdown band\n // (`LayerManager.Band.Dropdown` = 10000) so a toast floats over open pickers\n // and menus, yet below the Dialog band (11000) so the modal detail dialog a\n // toast can open covers it. A fixed literal rather than a `Band` allocation\n // because a `Notification` is not a registered layer — it never joins the\n // dismiss / stacking tree, so it has no node for the manager to stamp.\n private static readonly Z_INDEX: number = 10002;\n\n private static activeNotifications: Notification[] = [];\n\n // The most-recent notifications retained by the in-session history. A fixed\n // ring cap keeps memory trivial and the history menu scrollable-but-finite;\n // oldest entries are evicted first.\n private static readonly HISTORY_CAP: number = 50;\n private static history: NotificationRecord[] = [];\n\n // The auto-dismiss timer of every visible notification is paused while\n // either of these counters is positive. `hoverCount` tracks how many\n // notifications the pointer is currently over (so hovering one freezes\n // the whole stack until the pointer leaves the last one); `modalCount`\n // tracks outstanding calls to Notification.pauseAll() (a balanced pair\n // with resumeAll() that callers use to bracket their own modal flows).\n // When the combined paused state transitions back to false, every\n // notification's timer is restarted with at least `MIN_RESUMED_MS` of\n // remaining duration whenever a modal hold was the last released.\n private static hoverCount: number = 0;\n private static modalCount: number = 0;\n\n private readonly _type: NotificationType;\n private readonly _fullMessage: string;\n private readonly _badge: Glyph;\n private readonly _messageText: Text;\n private readonly _closeButton: Button;\n private _dismissTimer: ReturnType<typeof setTimeout> | null = null;\n private _remainingDuration: number = 0;\n private _timerStartedAt: number = 0;\n private _dismissing: boolean = false;\n\n // Named listener refs (removable, grep-able, named in stack traces) for the\n // close button's action, the body double-click, and the hover hold pair.\n private readonly _boundOnCloseAction: () => Event.ListenerResult = () => {\n this.dismiss();\n\n // Prevent the click from contributing to a double-click on the body.\n return true;\n };\n private readonly _boundOnDblClick: () => void = () => this.openDetail();\n private readonly _boundOnMouseOver: (e: MouseEvent) => void = (e) => Notification.acquireHoverHold(e, this.getElement());\n private readonly _boundOnMouseOut: (e: MouseEvent) => void = (e) => Notification.releaseHoverHold(e, this.getElement());\n\n /**\n * Private — use `Notification.show()` to create and display instances.\n *\n * @param message - The text to display inside the notification.\n * @param type - The severity type that controls the colour scheme.\n */\n private constructor(message: string, type: NotificationType) {\n super();\n\n this._type = type;\n this._fullMessage = message;\n\n this.setPosition(Position.FIXED);\n this.setZIndex(Notification.Z_INDEX);\n this.setWidth(Notification.WIDTH);\n this.setHeight(Notification.HEIGHT);\n this.setOverflow(\"hidden\");\n // Fixed size, fixed position, hidden overflow — full strict containment.\n this.setContain(\"strict\");\n\n // Live-region semantics so screen readers announce the toast when it is\n // inserted. Errors and warnings interrupt (`alert`/`assertive`);\n // informational and success toasts wait their turn (`status`/`polite`).\n const assertive = type === 'error' || type === 'warning';\n this.getAria().setRole(assertive ? \"alert\" : \"status\");\n this.getAria().setLive(assertive ? \"assertive\" : \"polite\");\n\n const bgVar = `var(--ts-ui-notification-${type}-bg)`;\n const borderVar = `var(--ts-ui-notification-${type}-border)`;\n const shadowVar = `var(--ts-ui-notification-shadow)`;\n\n this.setBackgroundColor(bgVar);\n this.setBorder({ border: `1px solid ${borderVar}` });\n this.setShadow(shadowVar);\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n const badgePx = badgeSizePx();\n\n this._badge = new Glyph(BADGE_GLYPH[type]);\n this._badge.setForegroundColor(borderVar);\n this._badge.setPreferredSize({ width: badgePx, height: badgePx });\n this._badge.setPointerEvents(\"none\");\n // Decorative severity icon — its meaning is already carried by the\n // message text, so keep it out of the announced live-region content.\n this._badge.getAria().setHidden(true);\n this.addComponent(this._badge);\n\n this._messageText = new SelectableText(message);\n // Webkit-prefixed line clamp is now cross-browser (Chrome, Edge, Safari,\n // Firefox 68+). Clamped to two lines so a long message gets a trailing\n // ellipsis — the full text is reachable via double-click → detail dialog.\n this._messageText.setLineClamp(2);\n this._messageText.setWhiteSpace(\"normal\");\n this._messageText.setWordBreak(\"break-word\");\n this.addComponent(this._messageText);\n\n this._closeButton = new Button({ glyph: \"xmark\" });\n this._closeButton.setInsets(new Insets(0, 0, 0, 0));\n this._closeButton.setBorder(\"none\");\n this._closeButton.clearBackgroundImage();\n this._closeButton.clearHoverBackgroundImage();\n this._closeButton.clearPressedBackgroundImage();\n this._closeButton.setBackgroundColor(\"transparent\");\n this._closeButton.clearShadow();\n this._closeButton.clearPressedShadow();\n this._closeButton.setForegroundColor(\"var(--ts-ui-text-color, rgb(0, 0, 0))\");\n this._closeButton.setPreferredSize({ width: Notification.CLOSE_SIZE, height: Notification.CLOSE_SIZE });\n this._closeButton.getAria().setLabel(\"Dismiss notification\");\n this.addComponent(this._closeButton);\n\n // Route through the button's own `\"action\"` surface rather than reaching\n // into its DOM `click` via the Event API (a component must not listen to\n // another component's events through Event). The handler's returned `true`\n // stops propagation, so the dblclick-suppressing consume is preserved.\n this._closeButton.on(\"action\", this._boundOnCloseAction);\n\n // addSubtreeListener so double-clicks on the badge / text bubble up.\n Event.addSubtreeListener(this, \"dblclick\", this._boundOnDblClick);\n\n // Subtree mouseover / mouseout on the root. These bubble from every\n // descendant of the toast, so the handlers below filter out\n // intra-element movements via `relatedTarget`. mouseenter / mouseleave\n // look cleaner on paper but proved unreliable here in practice —\n // mouseleave didn't always fire on a root carrying a non-empty\n // `transition` CSS rule left over from the entrance animation.\n // Subtree listeners route through `Event`'s window-level base\n // listener, so `e.currentTarget` resolves to `window` (which has no\n // `.contains` method) — pass the toast root explicitly instead.\n Event.addSubtreeListener(this, \"mouseover\", this._boundOnMouseOver);\n Event.addSubtreeListener(this, \"mouseout\", this._boundOnMouseOut);\n }\n\n /**\n * Displays a notification toast in the bottom-right corner of the viewport.\n *\n * @param message - The text to display.\n * @param type - The severity type; controls background and border colour. Defaults to `'info'`.\n * @param duration - How long in milliseconds before the notification auto-dismisses.\n * Pass `0` for a persistent notification. Defaults to `3000`.\n */\n static show(message: string, type: NotificationType = 'info', duration: number = 3000): void {\n Notification.record(message, type);\n\n const n = new Notification(message, type);\n\n Notification.activeNotifications.push(n);\n\n const el = n.getElement(true)!;\n\n n.scheduleLayout();\n\n LayerManager.mount(el);\n\n Notification.restack();\n n.animateIn();\n\n if (duration > 0) {\n n.startTimer(duration);\n\n // If another toast is currently hovered, or a modal hold is open,\n // freeze the brand-new toast too so it doesn't dismiss out from\n // under the user while they're still reading the stack.\n if (Notification.isPaused()) {\n n.pauseTimer();\n }\n }\n }\n\n /**\n * Appends one entry to the in-session history, evicting the oldest once the\n * {@link HISTORY_CAP} ceiling is exceeded. Called by {@link show} for every\n * toast; browsing history via {@link showDetail} deliberately does not\n * record, so \"history = everything ever shown this session, one entry per\n * `show()`\".\n *\n * @param message - The full message text.\n * @param type - The severity type.\n */\n private static record(message: string, type: NotificationType): void {\n Notification.history.push({ message, type, timestamp: Date.now() });\n\n if (Notification.history.length > Notification.HISTORY_CAP) {\n Notification.history.shift();\n }\n }\n\n /**\n * Returns the in-session notification history, oldest first, capped at the\n * most recent 50 entries. The returned array is a defensive copy — mutating\n * it does not affect the retained history.\n *\n * @returns A copy of the history entries, oldest first.\n */\n static getHistory(): readonly NotificationRecord[] {\n return [...Notification.history];\n }\n\n /**\n * Slides the notification in from the right while fading from `opacity: 0`\n * to `opacity: 1` over 200ms. No-op when `prefers-reduced-motion: reduce`\n * is set — the toast snaps into place immediately.\n */\n private animateIn(): void {\n const el = this.getElement();\n\n if (!el) {\n return;\n }\n\n this._showAnimation?.cancel();\n this._showAnimation = Animation.play(el, {\n from: { transform: \"translateX(100%)\", opacity: \"0\" },\n to: { transform: \"translateX(0)\", opacity: \"1\" },\n durationMs: ENTRANCE_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n });\n }\n\n /**\n * Pauses the auto-dismiss timer of every currently visible notification.\n * Balanced with {@link Notification.resumeAll}; nested pause/resume pairs\n * compose, and the combined paused state is released only when every\n * acquired hold has been released.\n *\n * @remarks Intended for use by code that opens a modal flow during which\n * the user is unable to read or interact with active notifications.\n * Resumed timers are then clamped to a minimum of 8 seconds.\n */\n static pauseAll(): void {\n const wasPaused = Notification.isPaused();\n Notification.modalCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Releases one outstanding {@link Notification.pauseAll} hold. When the\n * last modal hold AND every hover hold is released, the stack's timers\n * are restarted with at least 8 seconds of remaining duration so the\n * user has time to read the toasts after the modal dismissal.\n */\n static resumeAll(): void {\n if (Notification.modalCount === 0) {\n return;\n }\n\n Notification.modalCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(true);\n }\n }\n\n /**\n * Increments the hover refcount when the supplied `mouseover` event\n * represents an actual entry into a notification (not a movement\n * between two of its children). The first hover on any notification\n * pauses the entire stack so the user can read without timers eating\n * notifications mid-glance.\n *\n * @param e - The native `mouseover` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static acquireHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n const wasPaused = Notification.isPaused();\n Notification.hoverCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Decrements the hover refcount when the supplied `mouseout` event\n * represents an actual exit (cursor moving outside the notification,\n * not just between two of its children). When the last hover hold AND\n * every outstanding modal hold has been released, every notification's\n * timer is restarted with whatever remaining duration was captured at\n * pause time (no minimum clamp on the hover-only path).\n *\n * @param e - The native `mouseout` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static releaseHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n if (Notification.hoverCount === 0) {\n return;\n }\n\n Notification.hoverCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(false);\n }\n }\n\n /**\n * Returns `true` when any hover or modal hold is currently active.\n */\n private static isPaused(): boolean {\n return Notification.hoverCount > 0 || Notification.modalCount > 0;\n }\n\n /**\n * Pauses every active notification's auto-dismiss timer.\n */\n private static pauseAllTimers(): void {\n for (const n of Notification.activeNotifications) {\n n.pauseTimer();\n }\n }\n\n /**\n * Restarts every active notification's auto-dismiss timer with its\n * captured remaining duration. When `clampMin` is true, the remaining\n * duration is bumped to at least {@link MIN_RESUMED_MS} so a modal\n * release leaves the user enough time to read the stack.\n */\n private static resumeAllTimers(clampMin: boolean): void {\n for (const n of Notification.activeNotifications) {\n n.restartTimer(clampMin);\n }\n }\n\n /**\n * Starts (or restarts) the auto-dismiss timer with the given duration and\n * records when it began so remaining time can be calculated on hover.\n *\n * @param ms - Milliseconds until the notification is dismissed.\n */\n private startTimer(ms: number): void {\n this._remainingDuration = ms;\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), ms);\n }\n\n /**\n * Pauses the auto-dismiss timer when the pointer enters the notification.\n * Stores how many milliseconds were left so `resumeTimer` can pick up where it left off.\n */\n private pauseTimer(): void {\n if (this._dismissTimer === null) {\n return;\n }\n\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n this._remainingDuration = Math.max(0, this._remainingDuration - (Date.now() - this._timerStartedAt));\n }\n\n /**\n * Restarts this notification's auto-dismiss timer with its captured\n * remaining duration. When `clampMin` is true, the remaining duration\n * is bumped to {@link MIN_RESUMED_MS} so the user gets enough time to\n * read what's left after a modal release.\n *\n * @param clampMin - Apply the modal-resume minimum clamp.\n */\n private restartTimer(clampMin: boolean): void {\n if (this._dismissing) {\n return;\n }\n\n if (this._remainingDuration <= 0 || this._dismissTimer !== null) {\n return;\n }\n\n if (clampMin) {\n this._remainingDuration = Math.max(this._remainingDuration, MIN_RESUMED_MS);\n }\n\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), this._remainingDuration);\n }\n\n /**\n * Opens the modal detail dialog for this toast's full (un-truncated) message.\n * Delegates to {@link showDetail}; a double-click on the toast body routes\n * here.\n */\n private openDetail(): void {\n Notification.showDetail(this._fullMessage, this._type);\n }\n\n /**\n * Opens a modal detail dialog showing the full message text — the same dialog\n * a live toast opens on double-click. Active notification timers are paused\n * while the dialog is open and clamped to a minimum of 8 seconds when the\n * dialog is dismissed. Does not itself record a history entry, so re-opening a\n * past notification from the history menu leaves the history unchanged.\n *\n * @param message - The full message text to display.\n * @param type - The severity type; controls the title and title-bar tint.\n */\n static showDetail(message: string, type: NotificationType): void {\n Notification.pauseAll();\n\n const content = new SelectableText(message);\n content.setAutoMeasure(false);\n content.setWhiteSpace(\"pre-wrap\");\n content.setWordBreak(\"break-word\");\n content.setPadding(new Insets(16, 16, 16, 16));\n\n const dialog = new _Dialog({\n title: DETAIL_TITLE[type],\n contentComponent: content,\n buttons: [{ ...DialogButtons.Close, primary: true }],\n width: 420,\n height: 220,\n });\n\n // Tint the title bar to match the notification's severity colours.\n const titleBar = dialog.getTitleBar();\n titleBar.setBackgroundColor(`var(--ts-ui-notification-${type}-bg)`);\n titleBar.getTitleText().setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n titleBar.setGlyph(BADGE_GLYPH[type]);\n\n const titleGlyph = titleBar.getGlyph();\n\n if (titleGlyph !== null) {\n titleGlyph.setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n }\n\n dialog.show().then(() => Notification.resumeAll());\n }\n\n /**\n * Slides the notification rightward while fading it out, then removes the\n * element from the DOM and restacks the remaining notifications.\n */\n private dismiss(): void {\n if (this._dismissing) {\n return;\n }\n\n this._dismissing = true;\n\n if (this._dismissTimer !== null) {\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n }\n\n const el = this.getElement();\n\n if (!el) {\n this.finishDismiss();\n return;\n }\n\n this._dismissAnimation?.cancel();\n this._dismissAnimation = Animation.play(el, {\n to: { transform: \"translateX(100%)\", opacity: \"0\" },\n durationMs: DISMISS_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n onComplete: () => this.finishDismiss(),\n });\n }\n\n /**\n * Removes this notification from the active stack and destroys it.\n *\n * @remarks A dismissed toast is discarded and never re-shown — `show`\n * returns `void`, so no consumer ever holds the instance — so this is a\n * pure destroy with no consumer-visible contract change.\n */\n private finishDismiss(): void {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n\n this.dispose();\n\n Notification.restack();\n }\n\n /**\n * Recalculates the Y position of every active notification so they\n * stack upward from the bottom-right corner without overlapping.\n */\n private static restack(): void {\n const vp = DOM.source.getViewportSize();\n const x = vp.width - Notification.WIDTH - Notification.MARGIN;\n\n let y = vp.height - Notification.MARGIN;\n\n for (let i = Notification.activeNotifications.length - 1; i >= 0; i--) {\n const n = Notification.activeNotifications[i];\n\n y -= Notification.HEIGHT;\n n.setX(x);\n n.setY(y);\n y -= Notification.MARGIN;\n }\n }\n\n /**\n * Positions the badge glyph, message label, and close button within the\n * notification body.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const badgePx = badgeSizePx();\n const closeX = Notification.WIDTH - Notification.CLOSE_SIZE - 4;\n const badgeX = Notification.H_PADDING;\n const badgeY = Notification.V_PADDING + 2;\n const msgX = badgeX + badgePx + Notification.BADGE_TEXT_GAP;\n const msgWidth = closeX - msgX - 4;\n const msgHeight = Notification.HEIGHT - Notification.V_PADDING * 2;\n\n this._badge.setX(badgeX);\n this._badge.setY(badgeY);\n this._badge.setWidth(badgePx);\n this._badge.setHeight(badgePx);\n\n this._messageText.setX(msgX);\n this._messageText.setY(Notification.V_PADDING);\n this._messageText.setWidth(msgWidth);\n this._messageText.setHeight(msgHeight);\n\n this._closeButton.setX(closeX);\n this._closeButton.setY(4);\n this._closeButton.setWidth(Notification.CLOSE_SIZE);\n this._closeButton.setHeight(Notification.CLOSE_SIZE);\n // Cascade the size change down to the times-glyph through the\n // close button's internal Fit/HBox layout.\n this._closeButton.doLayout();\n\n return this;\n }\n\n /**\n * Cancels any in-flight entrance / dismiss animation, then defers to the\n * base class. Cancelling first keeps their fallback timers from firing\n * after `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._dismissAnimation?.cancel();\n this._dismissAnimation = null;\n\n // `finishDismiss` is the only place a notification leaves the static\n // active list, and cancelling above suppressed it. That list outlives\n // every teardown, and `restack` writes setX/setY to each entry — so a\n // disposed notification left in it is positioned through the element\n // handle released below. Re-stack afterwards so the survivors close the\n // gap, exactly as a completed dismiss would have left them.\n if (Notification.activeNotifications.includes(this)) {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n Notification.restack();\n }\n\n super.destructor();\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { MenuButton, MenuButtonOptions } from \"~/component/button/MenuButton.js\";\nimport { MenuItemConfig } from \"~/component/container/MenuItem.js\";\nimport { Notification, BADGE_GLYPH } from \"~/overlay/Notification.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { clock_rotate_left } from \"~/glyphs/solid/clock_rotate_left.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(clock_rotate_left);\n\nconst MINUTE_MS = 60_000;\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\n\n/**\n * Formats the age of a notification as a compact relative-time string\n * (`\"just now\"`, `\"5m ago\"`, `\"2h ago\"`, `\"3d ago\"`). A future timestamp\n * (clock skew) clamps to `\"just now\"`. Module-internal (not barrel-exported, so\n * it stays out of the public API) — exported only so the unit tests can pin the\n * bucket boundaries directly.\n *\n * @param timestampMs - When the notification was shown (epoch ms).\n * @param nowMs - The current time (epoch ms).\n * @returns The relative-time label.\n */\nexport function formatRelativeTime(timestampMs: number, nowMs: number): string {\n const delta = Math.max(0, nowMs - timestampMs);\n\n if (delta < MINUTE_MS) {\n return \"just now\";\n }\n\n if (delta < HOUR_MS) {\n return `${Math.floor(delta / MINUTE_MS)}m ago`;\n }\n\n if (delta < DAY_MS) {\n return `${Math.floor(delta / HOUR_MS)}h ago`;\n }\n\n return `${Math.floor(delta / DAY_MS)}d ago`;\n}\n\n/**\n * Builds the menu item configs from the current notification history in\n * chronological order (oldest first, latest at the bottom). Returns a single\n * disabled placeholder when the history is empty. Module-level (uses only\n * statics), passed through {@link NotificationHistoryButton}'s subclass\n * defaults bag so it is re-invoked on every open. Module-internal (not\n * barrel-exported, so it stays out of the public API) — exported only so the\n * unit tests can pin its pure mapping directly.\n *\n * @returns The menu item descriptors for the current history.\n */\nexport function buildHistoryItems(): MenuItemConfig[] {\n const history = Notification.getHistory();\n\n if (history.length === 0) {\n return [{ text: \"No notifications yet\", enabled: false }];\n }\n\n const now = Date.now();\n\n // History is stored oldest-first; keep that order so the latest entries\n // sit at the bottom (the menu opens scrolled there).\n return history.map(record => ({\n glyph: BADGE_GLYPH[record.type],\n // Tint the badge with the severity's border token — the same colour\n // the live toast's badge uses.\n glyphColor: `var(--ts-ui-notification-${record.type}-border)`,\n text: record.message,\n shortcut: formatRelativeTime(record.timestamp, now),\n action: () => Notification.showDetail(record.message, record.type),\n }));\n}\n\n/**\n * Construction-time options for {@link NotificationHistoryButton}. Inherits\n * every {@link MenuButtonOptions} field; a consumer-supplied `glyph` overrides\n * the default clock icon.\n *\n * @category Components\n */\nexport interface NotificationHistoryButtonOptions extends MenuButtonOptions {}\n\n/**\n * A trigger button that opens a menu of recent notifications. The menu lists the\n * in-session {@link Notification} history in chronological order (latest at the\n * bottom) and opens scrolled to the bottom, so the most recent entries are\n * visible first — each row showing the notification's severity badge, its\n * message, and how long ago it was shown. Activating a row re-opens that\n * notification's full message in the same modal detail dialog a live toast opens\n * on double-click; it does not re-show a toast, so browsing history has no effect\n * on the history itself.\n *\n * Place one wherever a persistent affordance for reviewing past notifications is\n * wanted, e.g. in a toolbar.\n *\n * @example\n * ```typescript\n * toolbar.addComponent(new NotificationHistoryButton());\n * ```\n *\n * @category Components\n */\nclass NotificationHistoryButton extends MenuButton<NotificationHistoryButtonOptions> {\n\n /**\n * Creates a NotificationHistoryButton seeded with the `clock-rotate-left`\n * glyph, the history provider, and scroll-to-bottom-on-show. A\n * consumer-supplied option in `options` still wins over these seeds.\n *\n * @param options - Optional button configuration.\n */\n constructor(options?: NotificationHistoryButtonOptions) {\n // Seeds live in the defaults bag so a caller's options still win.\n // History is chronological (latest at the bottom), so open scrolled to\n // the bottom; the provider re-runs per open so relative times stay current.\n super(undefined, options, {\n glyph: \"clock-rotate-left\",\n menuItems: buildHistoryItems,\n scrollToBottomOnShow: true,\n });\n\n this.getAria().setLabel(\"Notification history\");\n\n // MenuButton wires the bag only for a plain MenuButton; as a subclass we\n // wire our own so a consumer `listeners` option is not silently dropped.\n this.applyListeners(options?.listeners);\n }\n}\n\nconst NotificationHistoryButtonCallable = callable(NotificationHistoryButton);\ntype NotificationHistoryButtonCallable = NotificationHistoryButton;\nexport {\n NotificationHistoryButton as _NotificationHistoryButton,\n NotificationHistoryButtonCallable as NotificationHistoryButton,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { DialogBackdrop } from \"~/component/container/DialogBackdrop.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Viewport edge a {@link Drawer} anchors to and slides in from. Reuses the\n * framework's compass primitive\n * [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`, which is\n * meaningless for an edge-anchored panel — excluding it makes the illegal state\n * unrepresentable at compile time.\n *\n * @category Core\n */\nexport type DrawerEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Events emitted by a {@link Drawer}. `\"open\"` and `\"close\"` fire after the\n * corresponding transition is committed; `\"beforeclose\"` fires before a close\n * begins and can be vetoed via its {@link DrawerCloseController}.\n *\n * @category Core\n */\nexport type DrawerEvent = \"open\" | \"close\" | \"beforeclose\";\n\n/**\n * Controller handed to a `\"beforeclose\"` listener. Calling `preventDefault()`\n * aborts the in-progress close — letting a host veto dismissal (e.g. an\n * unsaved-changes guard).\n *\n * @category Core\n */\nexport interface DrawerCloseController {\n /** Aborts the close that is about to run. */\n preventDefault(): void;\n}\n\n/**\n * Construction-time options for {@link Drawer}.\n *\n * @category Core\n */\nexport interface DrawerOptions extends ComponentOptions {\n /**\n * Viewport edge the drawer rests against and slides in from.\n *\n * @defaultValue Placement.WEST\n */\n edge?: DrawerEdge;\n\n /**\n * When true, render a blocking scrim behind the panel and close on\n * scrim-click or Escape. When false, the surrounding UI stays interactive\n * and the drawer closes only via its public API.\n *\n * @defaultValue false\n */\n modal?: boolean;\n\n /**\n * Drawer extent along its slide axis, in pixels: width for left/right\n * edges, height for top/bottom edges.\n *\n * @defaultValue 320\n */\n size?: number;\n\n /**\n * Slide (and scrim-fade) duration in milliseconds.\n *\n * @defaultValue 220\n */\n durationMs?: number;\n\n /** Construction-time event listeners dispatched to {@link Drawer.on}. */\n listeners?: {\n open?: () => void;\n close?: () => void;\n beforeclose?: (controller: DrawerCloseController) => void;\n };\n}\n\n/**\n * Default drawer extent (px) along the slide axis. A component-level constant\n * rather than a theme token because it is a layout-affecting measurement, not a\n * colour — matching how `Dialog` keeps its `MIN_*` sizes and `Notification` its\n * `WIDTH` / `HEIGHT` out of `Theme.ts`. 320 is the conventional side-panel\n * width (navigation rails, filter panels) and stays comfortably under a narrow\n * viewport so the scrim/content behind it remains visible.\n */\nconst DEFAULT_DRAWER_SIZE_PX: number = 320;\n\n/**\n * Default slide / fade duration (ms). Tuned slightly longer than the dialog's\n * 150 ms because a drawer travels a full panel-width rather than a small\n * scale/opacity delta, so the same wall-clock feel needs more time.\n */\nconst DEFAULT_DRAWER_DURATION_MS: number = 220;\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The four\n * behavioural fields seed the options bag so {@link Drawer.getEdge} and friends\n * return a defined value before the caller (or a setter) writes one. `overflow`\n * is `auto` so a drawer scrolls content that exceeds its extent; the panel\n * surface tokens (`background`, `shadow`) are edge-agnostic and applied here,\n * while the directional divider border is applied per-edge in {@link Drawer.open}.\n */\nconst _defaultDrawerOptions: Partial<DrawerOptions> = {\n edge: Placement.WEST,\n modal: false,\n size: DEFAULT_DRAWER_SIZE_PX,\n durationMs: DEFAULT_DRAWER_DURATION_MS,\n overflow: \"auto\",\n backgroundColor: \"var(--ts-ui-body-bg)\",\n shadow: \"var(--ts-ui-drawer-shadow)\",\n};\n\n/**\n * An edge-anchored panel that rests off-screen against a viewport edge and\n * slides into view when opened, overlaying the rest of the UI.\n *\n * The drawer is a bare content host: callers add their own children via the\n * inherited `addComponent` and supply any header / dismiss chrome themselves.\n * It mounts on `document.documentElement` and registers with\n * [`LayerManager`](/api/core/classes/LayerManager) as a\n * [`DismissableLayer`](/api/core/interfaces/DismissableLayer), so Escape,\n * outside-click capture, and z-stacking behave like every other portaled\n * surface. A modal drawer additionally draws a\n * [`DialogBackdrop`](/api/component/container/classes/DialogBackdrop) scrim and\n * closes on scrim-click or Escape; a non-modal drawer leaves the surrounding UI\n * interactive and closes only through its public API.\n *\n * @example\n * ```typescript\n * import { Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n * import { VBox } from '@jimka/typescript-ui/layout';\n *\n * const drawer = Drawer({ edge: Placement.EAST, modal: true, layoutManager: VBox() });\n * drawer.addComponent(myFilterForm);\n * drawer.open();\n * ```\n *\n * @category Core\n */\nclass Drawer extends Component<DrawerOptions> implements DismissableLayer {\n\n // In-flight panel / backdrop animations, cancelled on teardown so their\n // fallback timers cannot fire against released element handles.\n private _panelInAnimation : Animation.CancelHandle | null = null;\n private _panelOutAnimation : Animation.CancelHandle | null = null;\n private _backdropInAnimation : Animation.CancelHandle | null = null;\n private _backdropOutAnimation: Animation.CancelHandle | null = null;\n\n /** Whether the drawer is currently open (or mid-entrance). */\n private _open: boolean = false;\n\n /**\n * Whether an exit slide is in flight. Guards `close()` against re-entry\n * while the panel is sliding out — `_open` only flips to false in the exit\n * transition's completion callback, so without this flag a second `close()`\n * (via `toggle()`, a repeated dismiss click, or Esc) would re-emit `\"close\"`\n * and queue a redundant exit animation.\n */\n private _closing: boolean = false;\n\n /** The modal scrim, created lazily on each modal open and torn down on close. */\n private _backdrop: DialogBackdrop | null = null;\n\n /** Typed-event fan-out for `\"open\"` / `\"close\"` / `\"beforeclose\"`. */\n private _listeners: ListenerBag<DrawerEvent> = this.registerListenerBag(new ListenerBag<DrawerEvent>());\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.onViewportResize();\n\n /** Stable scrim-click handler reference; closes the drawer. */\n private _boundBackdropClose: () => void = (): void => {\n this.close();\n };\n\n /**\n * Constructs a drawer but does not display it. Call `open()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: DrawerOptions, subclassDefaults?: Partial<DrawerOptions>) {\n super(options, { ..._defaultDrawerOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like every other portaled surface.\n this.setPosition(Position.FIXED);\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies a {@link DrawerOptions} bag, dispatching the drawer-specific\n * fields after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This drawer, for method chaining.\n */\n protected applyOptions(options: DrawerOptions): this {\n super.applyOptions(options);\n\n // All four carry a class default and seed construction-time state, so\n // always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setModal(options.modal ?? this.isModal());\n this.setDrawerSize(options.size ?? this.getDrawerSize());\n this.setDurationMs(options.durationMs ?? this.getDurationMs());\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/CSS applied in open()) -----\n\n /**\n * Sets the viewport edge the drawer anchors to. Cached only — the resting\n * geometry and directional border are derived in `open()`, where the\n * element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This drawer, for method chaining.\n */\n setEdge(edge: DrawerEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the drawer anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): DrawerEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Sets whether the drawer is modal. A modal drawer draws a blocking scrim\n * and closes on scrim-click / Escape; a non-modal one does neither.\n *\n * @param value - True for a modal drawer.\n *\n * @returns This drawer, for method chaining.\n *\n * @remarks Takes effect on the next `open()`; changing it while open does\n * not retroactively add or remove the scrim.\n */\n setModal(value: boolean): this {\n this._options.modal = value;\n\n return this;\n }\n\n /**\n * Returns whether the drawer is modal.\n *\n * @returns True when modal.\n */\n isModal(): boolean {\n return this._options.modal ?? this._defaultOptions.modal!;\n }\n\n /**\n * Sets the drawer's extent along its slide axis (width for left/right,\n * height for top/bottom). Named `setDrawerSize` rather than overriding the\n * inherited `Component.setSize(size: Size)` — the two-axis geometry setter —\n * because a drawer's extent is a single number along one axis.\n *\n * @param value - The extent in pixels.\n *\n * @returns This drawer, for method chaining.\n */\n setDrawerSize(value: number): this {\n this._options.size = value;\n\n return this;\n }\n\n /**\n * Returns the drawer's extent along its slide axis, in pixels.\n *\n * @returns The current extent.\n */\n getDrawerSize(): number {\n return this._options.size ?? this._defaultOptions.size!;\n }\n\n /**\n * Sets the slide / scrim-fade duration in milliseconds.\n *\n * @param ms - The duration in milliseconds.\n *\n * @returns This drawer, for method chaining.\n */\n setDurationMs(ms: number): this {\n this._options.durationMs = ms;\n\n return this;\n }\n\n /**\n * Returns the slide / scrim-fade duration in milliseconds.\n *\n * @returns The current duration.\n */\n getDurationMs(): number {\n return this._options.durationMs ?? this._defaultOptions.durationMs!;\n }\n\n // ----- open / close API -----\n\n /**\n * Slides the drawer into view from its anchored edge. Registers with the\n * layer tree, draws the scrim when modal, mounts on `documentElement`, and\n * animates in. No-op if already open.\n *\n * @returns This drawer, for method chaining.\n */\n open(): this {\n if (this._open) {\n return this;\n }\n\n LayerManager.register(this);\n\n const panelZ = LayerManager.getZIndex(this);\n this.setZIndex(panelZ);\n\n if (this.isModal()) {\n this.openBackdrop(panelZ - 1);\n }\n\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n DOM.sink.appendChild(DOM.source.getDocumentElement(), this.getElement(true)!);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the drawer panel.\n trapWheel(this);\n\n this.scheduleLayout();\n\n this.animateIn();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._open = true;\n this.emit(\"open\");\n\n return this;\n }\n\n /**\n * Slides the drawer back off-screen and tears it down. Fires the cancelable\n * `\"beforeclose\"` event first; if a listener vetoes via `preventDefault()`\n * the close is aborted. No-op if already closed.\n *\n * @returns This drawer, for method chaining.\n */\n close(): this {\n if (!this._open || this._closing) {\n return this;\n }\n\n let prevented = false;\n const controller: DrawerCloseController = {\n preventDefault: (): void => {\n prevented = true;\n },\n };\n\n this.emit(\"beforeclose\", controller);\n\n if (prevented) {\n return this;\n }\n\n this._closing = true;\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this.animateOutAndFinalize();\n\n return this;\n }\n\n /**\n * Opens the drawer if closed, or closes it if open.\n *\n * @returns This drawer, for method chaining.\n */\n toggle(): this {\n return this._open ? this.close() : this.open();\n }\n\n /**\n * Returns whether the drawer is currently open.\n *\n * @returns True when open (or mid-entrance).\n */\n isOpen(): boolean {\n return this._open;\n }\n\n // ----- internal: geometry & animation -----\n\n /**\n * Computes the panel's on-screen resting rect from the current edge, size,\n * and viewport. WEST/EAST span the full viewport height at the chosen\n * width; NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - size, y: 0, width: size, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: size };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - size, width: vp.width, height: size };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: size, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the panel via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the slide's\n * start (entrance) and end (exit) state, translated one full extent past\n * the anchored edge.\n *\n * @returns A `translateX`/`translateY` CSS value.\n */\n private offscreenTransform(): string {\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return `translateX(${size}px)`;\n\n case Placement.NORTH:\n return `translateY(${-size}px)`;\n\n case Placement.SOUTH:\n return `translateY(${size}px)`;\n\n case Placement.WEST:\n default:\n return `translateX(${-size}px)`;\n }\n }\n\n /**\n * Applies the 1px divider border on the panel's inner edge — the side that\n * faces the rest of the UI — leaving the other three sides borderless.\n */\n private applyEdgeBorder(): void {\n const divider = \"1px solid var(--ts-ui-drawer-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: \"none\", borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: \"none\", borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: \"none\", borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: \"none\", borderRight: divider });\n\n break;\n }\n }\n\n /**\n * Slides the panel in from its off-screen transform to its resting\n * position. Honours `prefers-reduced-motion` via {@link Animation.play}.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._panelInAnimation?.cancel();\n this._panelInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n });\n }\n\n /**\n * Slides the panel back off-screen, then finalizes teardown (detach the\n * element, destroy the scrim, leave the layer tree, emit `\"close\"`) in the\n * transition's completion callback. Fades the scrim out concurrently. Under\n * reduced motion {@link Animation.play} runs the completion synchronously.\n */\n private animateOutAndFinalize(): void {\n const element = this.getElement();\n\n const finalize = (): void => {\n this.removeElement();\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n this.emit(\"close\");\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n onComplete: finalize,\n });\n\n this.fadeBackdropOut();\n }\n\n /**\n * Creates the modal scrim, stamps it one z-index below the panel, wires\n * scrim-click-to-close, mounts it, and fades it in.\n *\n * @param zIndex - The z-index for the scrim (panel z minus one).\n */\n private openBackdrop(zIndex: number): void {\n this._backdrop = new DialogBackdrop();\n this._backdrop.setZIndex(zIndex);\n this._backdrop.addClickListener(this._boundBackdropClose);\n\n const backdropEl = this._backdrop.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), backdropEl);\n\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = Animation.play(backdropEl, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Fades the modal scrim out, if one is present. The element is destroyed by\n * {@link teardownBackdrop} once the panel's exit completes.\n */\n private fadeBackdropOut(): void {\n const backdropEl = this._backdrop?.getElement();\n\n if (!backdropEl) {\n return;\n }\n\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = Animation.play(backdropEl, {\n to: { opacity: \"0\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Removes the modal scrim from the DOM and drops the reference, if one is\n * present.\n */\n private teardownBackdrop(): void {\n if (this._backdrop !== null) {\n this._backdrop.destroy();\n this._backdrop = null;\n }\n }\n\n /**\n * Re-derives the panel rect and resizes the scrim when the viewport\n * changes, keeping a full-height/width drawer flush with the new edges.\n */\n private onViewportResize(): void {\n if (this._backdrop !== null) {\n this._backdrop.resize();\n }\n\n this.applyRestingGeometry();\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the drawer's events.\n *\n * @param event - `\"open\"` / `\"close\"` fire after the matching transition;\n * `\"beforeclose\"` fires before a close and can veto it via its controller.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This drawer, for method chaining.\n */\n on(event: \"open\" | \"close\", listener: () => void): this;\n on(event: \"beforeclose\", listener: (controller: DrawerCloseController) => void): this;\n on(event: DrawerEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This drawer, for method chaining.\n */\n off(event: DrawerEvent, 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 payload - Forwarded to each listener.\n */\n protected emit(event: \"open\" | \"close\"): void;\n protected emit(event: \"beforeclose\", controller: DrawerCloseController): void;\n protected emit(event: DrawerEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the drawer panel's root element for the layer tree.\n *\n * @returns The drawer's element, or null when not yet rendered.\n */\n getLayerElement(): Handle | null {\n return this.getElement() ?? null;\n }\n\n /**\n * Returns the dismiss mode the document-level handlers consult: `\"modal\"`\n * for a modal drawer (captures outside interaction, Escape closes it) and\n * `\"manual\"` for a non-modal one (never auto-dismissed — closing is the\n * caller's job).\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return this.isModal() ? \"modal\" : \"manual\";\n }\n\n /**\n * Advisory close request from the layer manager (Escape on a modal\n * drawer). Routes to the public `close()`.\n */\n requestClose(): void {\n this.close();\n }\n\n /**\n * Drawers are independent top-level peers, not layers opened from another,\n * so each registers as a tree root.\n *\n * @returns Always `true`.\n */\n isLayerRoot(): boolean {\n return true;\n }\n\n /**\n * Mirrors a manager-reallocated z-index onto the panel (and the scrim, one\n * below) when the drawer is re-stamped.\n *\n * @param zIndex - The fresh z-index assigned by the manager.\n */\n onZIndexChanged(zIndex: number): void {\n this.setZIndex(zIndex);\n\n if (this._backdrop !== null) {\n this._backdrop.setZIndex(zIndex - 1);\n }\n }\n\n /**\n * Cancels any in-flight panel / backdrop animation, then defers to the base\n * class. Cancelling first keeps their fallback timers from firing after\n * `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._panelInAnimation?.cancel();\n this._panelInAnimation = null;\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = null;\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = null;\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = null;\n\n // The close animation's completion callback is the only place these are\n // released, and cancelling above suppressed it: the backdrop is a\n // private field rather than a registered child, so the base class's\n // recursion cannot reach it and it would stay mounted over the app.\n // Each of these is idempotent, so a drawer disposed while already\n // closed pays nothing. The `\"close\"` emit is deliberately NOT re-homed\n // — disposal is not a close, and emitting into consumer code from a\n // destructor invites re-entrancy.\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n\n super.destructor();\n }\n}\n\nconst DrawerCallable = callable(Drawer);\ntype DrawerCallable = Drawer;\nexport {\n Drawer as _Drawer,\n DrawerCallable as Drawer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { DOM } from \"~/core/DOM.js\";\nimport type { StyleBag, StyleStateSpec } from \"~/core/ClassStyleRules.js\";\nimport { Button, ButtonOptions } from \"~/component/button/Button.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * `.selected`'s wash. `backgroundImage` / `shadow` are neutralised alongside\n * it in the extract below so the selected rule also outranks\n * `.Button:hover:not(.pressed)` on a handle that is selected *and* hovered —\n * see plans/implemented/railhandle-chromeless-dedup.md's Architecture\n * Decisions.\n */\nconst RAIL_HANDLE_SELECTED_BACKGROUND_COLOR = \"var(--ts-ui-rail-handle-selected-bg)\";\n\n/**\n * Resting + pressed + hover defaults for {@link RailHandle}. The resting bag\n * (transparent background, no border, no shadow) restates what\n * `chromeless: true` used to compute imperatively in\n * `Button.applyChromeOptions`'s chromeless branch; the `pressedX` fields\n * restate what `pinPressedToResting` used to pin per instance, so a press\n * still shows no visual change. `pressedForegroundColor` restates the same\n * literal token `Button`'s own resting default uses\n * (`_defaultButtonOptions.foregroundColor` in Button.ts — module-private, so\n * not importable). Unlike `PickerButton`, the `hoverX` fields are a *real*\n * wash, not a pin to the resting values: a rail handle does highlight on\n * hover. `borderRadius: undefined` is an explicit key, not an omission, so it\n * wins over Button's own non-empty default in the `subclassDefaults` spread\n * merge below.\n */\nconst _defaultRailHandleOptions: Partial<ButtonOptions> = {\n backgroundColor: \"transparent\",\n backgroundImage: \"none\",\n border: \"none\",\n borderRadius: undefined,\n shadow: \"none\",\n pressedForegroundColor: \"var(--ts-ui-text-color, black)\",\n pressedBackgroundColor: \"transparent\",\n pressedBackgroundImage: \"none\",\n pressedShadow: \"none\",\n hoverBackgroundColor: \"var(--ts-ui-rail-handle-hover-bg)\",\n hoverBackgroundImage: \"none\",\n hoverShadow: \"none\",\n};\n\n/**\n * Construction-time options for {@link RailHandle}.\n *\n * @category Core\n */\nexport interface RailHandleOptions extends ButtonOptions {\n /**\n * Whether the handle renders in its selected (target-open) wash.\n *\n * @defaultValue false\n */\n selected?: boolean;\n}\n\n/**\n * A single launcher button on a [`Rail`](/api/overlay/classes/Rail). A flat-chromed\n * [`Button`](/api/component/button/classes/Button) subclass that carries a\n * `selected` state — driven by the rail to mirror whether the handle's drawer\n * is open or its window is restored — rendered as a hover wash\n * (`--ts-ui-rail-handle-hover-bg`) and a selected wash\n * (`--ts-ui-rail-handle-selected-bg`).\n *\n * Declares its own resting chrome (transparent, no border/shadow) instead of\n * `chromeless: true`, and its `.pressed` / `.selected` / `:hover` looks as\n * declared style states — see plans/implemented/railhandle-chromeless-dedup.md.\n *\n * `RailHandle` is internal to the rail subsystem; the rail creates and owns its\n * handles, so consumers rarely construct one directly. It is exported for typing\n * and subclassing.\n *\n * @category Core\n */\nclass RailHandle extends Button<RailHandleOptions> {\n\n protected static readonly ownClassStyleDefaults: StyleBag = _defaultRailHandleOptions;\n\n // Declares Button's two states with RailHandle's own content, plus\n // `.selected`, ordered `[.pressed, .selected, :hover]`. Array order is\n // priority: putting `.selected` ahead of `:hover` generates the guard\n // `:hover:not(.pressed):not(.selected)`, reproducing the hand-written\n // `:hover:not(.selected)` rule this class used before — the selected\n // wash keeps winning while the pointer is over an already-open handle.\n // That is the reverse of `ToggleButton`'s order, deliberately; see\n // plans/implemented/railhandle-chromeless-dedup.md's Architecture\n // Decisions. Each extract names every key its Button-level counterpart\n // carries — four for `.pressed`, three for `:hover` — because state\n // content merges over the parent level, so an unnamed key would inherit\n // Button's raised gradient or drop shadow. `.selected` has no parent\n // entry, but names the same three so its rule also outranks\n // `.Button:hover:not(.pressed)` on a selected *and* hovered handle.\n protected static readonly ownStyleStates: readonly StyleStateSpec[] = [\n {\n selector: \".pressed\",\n extract: (): StyleBag => ({\n foregroundColor: _defaultRailHandleOptions.pressedForegroundColor,\n backgroundColor: _defaultRailHandleOptions.pressedBackgroundColor,\n backgroundImage: _defaultRailHandleOptions.pressedBackgroundImage,\n shadow: _defaultRailHandleOptions.pressedShadow,\n }),\n },\n {\n selector: \".selected\",\n extract: (): StyleBag => ({\n backgroundColor: RAIL_HANDLE_SELECTED_BACKGROUND_COLOR,\n backgroundImage: \"none\",\n shadow: \"none\",\n }),\n },\n {\n selector: \":hover\",\n extract: (): StyleBag => ({\n backgroundColor: _defaultRailHandleOptions.hoverBackgroundColor,\n backgroundImage: _defaultRailHandleOptions.hoverBackgroundImage,\n shadow: _defaultRailHandleOptions.hoverShadow,\n }),\n },\n ];\n\n /**\n * Builds a launcher handle.\n *\n * @param options - Construction-time options (label `text`, leading `glyph`,\n * initial `selected` state).\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults; forwarded so a subclass can seed a default without\n * editing this class's own constant.\n */\n constructor(options: RailHandleOptions = {}, subclassDefaults?: Partial<ButtonOptions>) {\n super(options.text, options, { ..._defaultRailHandleOptions, ...(subclassDefaults ?? {}) });\n }\n\n /**\n * Applies a {@link RailHandleOptions} bag, dispatching the `selected` state\n * after inherited Button/Component fields.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This handle, for method chaining.\n */\n protected applyOptions(options: RailHandleOptions): this {\n super.applyOptions(options);\n\n if (options.selected !== undefined) {\n this.setSelected(options.selected);\n }\n\n return this;\n }\n\n /**\n * Returns whether the handle is currently selected.\n *\n * @returns True when selected.\n */\n isSelected(): boolean {\n return this._options.selected ?? false;\n }\n\n /**\n * Sets the selected state: toggles the `.selected` class (carrying the\n * selected wash) and mirrors the state onto `aria-pressed`.\n *\n * @param value - True to select the handle, false to deselect it.\n *\n * @returns This handle, for method chaining.\n */\n setSelected(value: boolean): this {\n this._options.selected = value;\n\n this.getAria().setPressed(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 * Renders the handle element, applying the `.selected` class when selected.\n *\n * @returns The created element.\n */\n render() {\n const element = super.render();\n DOM.sink.apply(element, { toggleClass: { selected: this.isSelected() } });\n\n return element;\n }\n}\n\nconst RailHandleCallable = callable(RailHandle);\ntype RailHandleCallable = RailHandle;\nexport {\n RailHandle as _RailHandle,\n RailHandleCallable as RailHandle,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { isUnbounded } from \"~/primitive/Size.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { BoxLayout } from \"~/layout/BoxLayout.js\";\nimport { RailHandle } from \"~/overlay/RailHandle.js\";\nimport { CollapseButton, CollapseDirection } from \"~/component/container/CollapseButton.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport type { Drawer, DrawerEdge } from \"~/overlay/Drawer.js\";\nimport type { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport type { ClickListener } from \"~/component/button/Button.js\";\n\n/**\n * Viewport edge a {@link Rail} anchors to. Structurally identical to\n * [`DrawerEdge`](/api/overlay/type-aliases/DrawerEdge) — the framework's compass\n * primitive [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`,\n * which is meaningless for an edge-anchored strip.\n *\n * @category Core\n */\nexport type RailEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Text orientation for handle labels on the vertical sides (WEST/EAST). Ignored\n * for NORTH/SOUTH, where handle text is always horizontal. Mirrors the\n * [`Tab`](/api/layout/classes/Tab) layout's orientation vocabulary.\n *\n * - `\"horizontal\"` — handles stack vertically but text stays upright.\n * - `\"vertical-cw\"` — text rotated 90° clockwise, reading top-to-bottom\n * (`writing-mode: sideways-rl`).\n * - `\"vertical-ccw\"` — text rotated the other way, reading bottom-to-top\n * (`writing-mode: sideways-lr`).\n *\n * @remarks Implemented with CSS `writing-mode` rather than `transform: rotate`\n * so the browser reports the rotated box through `getBoundingClientRect`,\n * keeping the handle's preferred-size measurement correct.\n *\n * @category Core\n */\nexport type RailOrientation = \"horizontal\" | \"vertical-cw\" | \"vertical-ccw\";\n\n/**\n * Events emitted by a {@link Rail}. `\"register\"` fires when a drawer or window\n * is added to the rail; `\"unregister\"` when it is removed.\n *\n * @category Core\n */\nexport type RailEvent = \"register\" | \"unregister\";\n\n/**\n * Per-drawer registration options for {@link Rail.registerDrawer}.\n *\n * @category Core\n */\nexport interface RailDrawerRegistration {\n /** Handle glyph (forwarded to the handle's leading icon). */\n glyph?: string;\n\n /** Handle label text. */\n text?: string;\n\n /**\n * When true (default), the rail sets the drawer's edge to its own edge so\n * the drawer slides out from the rail. Pass false to leave the drawer's\n * edge untouched.\n *\n * @defaultValue true\n */\n alignEdge?: boolean;\n}\n\n/**\n * Construction-time options for {@link Rail}.\n *\n * @category Core\n */\nexport interface RailOptions extends ComponentOptions {\n /**\n * Viewport edge the rail anchors to.\n *\n * @defaultValue Placement.WEST\n */\n edge?: RailEdge;\n\n /**\n * Explicit rail thickness in pixels — width for WEST/EAST edges, height for\n * NORTH/SOUTH edges (the main axis always spans the full viewport). Omit to\n * size the rail to its handles: the cross-axis fits the widest (or, on a\n * horizontal rail, tallest) handle, re-derived as handles are added/removed\n * and when the orientation changes.\n *\n * @defaultValue content-derived\n */\n thickness?: number;\n\n /**\n * Handle-label text orientation on the vertical (WEST/EAST) sides. Ignored\n * for NORTH/SOUTH.\n *\n * @defaultValue \"horizontal\"\n */\n orientation?: RailOrientation;\n\n /**\n * Whether the rail starts collapsed — minimized to a thin gutter strip along\n * the edge (handles hidden) that a click on its chevron expands.\n *\n * @defaultValue false\n */\n collapsed?: boolean;\n\n /** Construction-time event listeners dispatched to {@link Rail.on}. */\n listeners?: {\n register?: (target: Drawer | AbstractWindow) => void;\n unregister?: (target: Drawer | AbstractWindow) => void;\n };\n}\n\n/**\n * Fallback rail thickness (px) along the cross axis, used only when no explicit\n * `thickness` is set and the content cannot be measured yet (e.g. an empty rail\n * with no handles). Once handles exist the rail sizes to them. A component-level\n * constant rather than a theme token because it is a layout-affecting\n * measurement, not a colour — matching how `Drawer` keeps its\n * `DEFAULT_DRAWER_SIZE_PX` out of `Theme.ts`. 48 px is the conventional icon-rail\n * width (a comfortable square touch target for a single glyph handle).\n */\nconst DEFAULT_RAIL_THICKNESS_PX: number = 48;\n\n/**\n * Fixed z-index for the rail, a plain module constant just below the window\n * band (`Z_BAND_WINDOW = 9000` in `LayerManager`) — mirroring how the layer\n * manager's bands are plain constants because z-index is unthemed. The rail is\n * a persistent strip that windows, popovers, and dialogs still stack above, and\n * it is deliberately not a `DismissableLayer`, so it carries this stamp itself\n * rather than drawing a band from the layer manager.\n */\nconst RAIL_Z_INDEX: number = 8900;\n\n/**\n * Slide duration (ms) for the rail's mount / unmount animation. Matches\n * Drawer's slide feel — long enough to read as motion, short enough not to\n * delay the launcher. Honoured under `prefers-reduced-motion` by\n * {@link Animation.play}, which then snaps to the end state.\n */\nconst RAIL_ANIM_DURATION_MS: number = 200;\n\n/**\n * Cross-axis thickness (px) of the collapsed rail — the thin strip the rail\n * minimizes into, skinned like a `Split` / `Border` collapsed gutter; a click on\n * the chevron expands the rail again.\n */\nconst RAIL_COLLAPSED_THICKNESS_PX: number = 10;\n\n/**\n * Half the collapse chevron's grip width (`CollapseButton`'s 10px grip), used to\n * inset the chevron's centre from the rail's inner edge so the whole grip sits\n * just inside the strip. The chevron is centred on its anchor by the\n * `CollapseButton` transform, so anchoring its centre half a grip-width in from\n * the edge leaves it flush inside — never overhanging the `overflow: hidden`\n * rail (which would clip it and let clicks fall through to whatever is behind).\n */\nconst RAIL_CHEVRON_HALF_PX: number = 5;\n\n/**\n * Maps a rail edge to the chevron's collapse heading — the direction the rail\n * travels (and the chevron points) when collapsing: toward the outer screen\n * edge it anchors to. The restore heading is the opposite, handled by the\n * chevron itself.\n *\n * @param edge - The rail's edge.\n *\n * @returns The collapse-heading {@link CollapseDirection}.\n */\nfunction collapseHeadingFor(edge: RailEdge): CollapseDirection {\n switch (edge) {\n case Placement.EAST: return \"east\";\n case Placement.NORTH: return \"north\";\n case Placement.SOUTH: return \"south\";\n case Placement.WEST:\n default: return \"west\";\n }\n}\n\n/** The chevron's restore heading is the opposite of its collapse heading. */\nconst OPPOSITE_HEADING: Record<CollapseDirection, CollapseDirection> = {\n west: \"east\",\n east: \"west\",\n north: \"south\",\n south: \"north\",\n};\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The two\n * behavioural fields seed the options bag so {@link Rail.getEdge} /\n * {@link Rail.getThickness} return a defined value before a setter writes one;\n * the surface tokens skin the strip.\n */\nconst _defaultRailOptions: Partial<RailOptions> = {\n edge: Placement.WEST,\n orientation: \"horizontal\",\n backgroundColor: \"var(--ts-ui-body-bg)\",\n shadow: \"var(--ts-ui-rail-shadow)\",\n};\n\n/** Per-drawer bookkeeping: the handle and the exact listener references to remove. */\ninterface DrawerRegistration {\n handle: RailHandle;\n onOpen: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/** Per-window bookkeeping: the handle (null until minimized) and listener references. */\ninterface WindowRegistration {\n handle: RailHandle | null;\n onMinimize: () => void;\n onRestore: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/**\n * An edge-anchored launcher strip that floats over the app content along one\n * viewport edge, holding a column (WEST/EAST) or row (NORTH/SOUTH) of handle\n * buttons. Unlike a [`Drawer`](/api/overlay/classes/Drawer) it never slides\n * off-screen and is never auto-dismissed — it is the persistent counterpart to\n * the drawer.\n *\n * A rail hosts caller-created drawers (`registerDrawer`): each gets a handle\n * that toggles it, and the handle reflects the drawer's open/closed state by\n * subscribing through the drawer's public typed `on`. A window can also be told\n * to minimize *into* the rail (`AbstractWindow.setRail`): while minimized it is\n * represented by a rail handle that restores it on click.\n *\n * The rail mounts on `document.documentElement` as a `Position.FIXED` overlay\n * (the documented fixed carve-out) and carries a fixed z-index just below the\n * window band; it is deliberately *not* a layer-tree member.\n *\n * @example\n * ```typescript\n * import { Rail, Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n *\n * const rail = Rail({ edge: Placement.WEST }).mount();\n * rail.registerDrawer(Drawer(), { glyph: 'filter', text: 'Filters' });\n * ```\n *\n * @category Core\n */\nclass Rail extends Component<RailOptions> {\n\n // In-flight animations, cancelled on teardown so their fallback timers\n // cannot fire against this rail's released element handle.\n private _collapseAnimation: Animation.CancelHandle | null = null;\n private _slideOutAnimation: Animation.CancelHandle | null = null;\n private _slideInAnimation: Animation.CancelHandle | null = null;\n\n /** Typed-event fan-out for `\"register\"` / `\"unregister\"`. */\n private _listeners: ListenerBag<RailEvent> = this.registerListenerBag(new ListenerBag<RailEvent>());\n\n /** Registered drawers, keyed by drawer, holding the handle + listener refs. */\n private _drawers: Map<Drawer, DrawerRegistration> = new Map();\n\n /** Registered windows, keyed by window, holding the handle + listener refs. */\n private _windows: Map<AbstractWindow, WindowRegistration> = new Map();\n\n /** Whether the rail is currently mounted (attached to the document). */\n private _mounted: boolean = false;\n\n /** The collapse/restore chevron pinned to the rail's inner edge. */\n private _collapseButton: CollapseButton;\n\n /**\n * The content-fit thickness captured at collapse time. Hidden handles are\n * excluded from the preferred-size measurement, so the expand animation\n * tweens back to this remembered extent rather than a mis-measured one.\n */\n private _expandedThickness: number = DEFAULT_RAIL_THICKNESS_PX;\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.applyRestingGeometry();\n\n /**\n * Constructs a rail but does not display it. Call `mount()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: RailOptions, subclassDefaults?: Partial<RailOptions>) {\n super(options, { ..._defaultRailOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like Drawer and the other portaled\n // surfaces. The rail is not a DismissableLayer, so it stamps its own\n // fixed z-index rather than drawing one from the layer manager.\n this.setPosition(Position.FIXED);\n this.setZIndex(RAIL_Z_INDEX);\n\n // The collapse chevron sits at the rail's inner edge: double-clicking it\n // (matching the Split / Border gutter chevrons) collapses the rail to a\n // gutter, or restores it. Its heading points the way the rail travels on\n // collapse — toward the outer screen edge.\n this._collapseButton = new CollapseButton({\n direction: collapseHeadingFor(this.getEdge()),\n listeners: { collapse: (): void => { this.toggleCollapsed(); } },\n });\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n if (options?.listeners !== undefined) {\n this.applyListeners(options.listeners);\n }\n }\n\n /**\n * Applies a {@link RailOptions} bag, dispatching the rail-specific fields\n * after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This rail, for method chaining.\n */\n protected applyOptions(options: RailOptions): this {\n super.applyOptions(options);\n\n // edge and orientation carry a class default and seed construction-time\n // state, so always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setOrientation(options.orientation ?? this.getOrientation());\n\n if (options.thickness !== undefined) {\n this.setThickness(options.thickness);\n }\n\n if (options.collapsed !== undefined) {\n // Cache only during the cascade — the visual transition needs the\n // element + chevron + handles, which mount() applies once they exist.\n this._options.collapsed = options.collapsed;\n }\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/layout applied in mount()) -----\n\n /**\n * Sets the viewport edge the rail anchors to. Cached only — the resting\n * geometry, divider border, and handle-axis layout manager are derived in\n * `mount()`, where the element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This rail, for method chaining.\n */\n setEdge(edge: RailEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the rail anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): RailEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Pins the rail's cross-axis thickness (width for WEST/EAST, height for\n * NORTH/SOUTH) to an explicit pixel value, overriding the content-fit\n * default. Re-applies the resting geometry when mounted.\n *\n * @param px - The thickness in pixels.\n *\n * @returns This rail, for method chaining.\n */\n setThickness(px: number): this {\n this._options.thickness = px;\n this.adaptThickness();\n\n return this;\n }\n\n /**\n * Returns the rail's effective cross-axis thickness in pixels — the explicit\n * `thickness` when one was set, otherwise the content-fit measurement.\n *\n * @returns The current thickness.\n */\n getThickness(): number {\n if (this.isCollapsed()) {\n return RAIL_COLLAPSED_THICKNESS_PX;\n }\n\n if (this._options.thickness !== undefined) {\n return this._options.thickness;\n }\n\n return this.measureContentThickness();\n }\n\n /**\n * Measures the cross-axis extent the rail's handles need — the widest handle\n * for a vertical (WEST/EAST) rail, the tallest for a horizontal one — from\n * the layout manager's preferred size. Falls back to\n * {@link DEFAULT_RAIL_THICKNESS_PX} before the rail has a layout manager or\n * any handles (when the preferred cross-axis is unbounded or zero).\n *\n * @returns The content-fit thickness in pixels.\n */\n private measureContentThickness(): number {\n const preferred = this.getPreferredSize();\n if (!preferred) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n const cross = this.isVertical() ? preferred.width : preferred.height;\n if (cross <= 0 || isUnbounded(cross)) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n return Math.ceil(cross);\n }\n\n /**\n * Re-derives the content-fit thickness by re-applying the resting geometry.\n * No-op until mounted (mount applies the geometry itself); called whenever\n * the handle set or orientation changes so the rail tracks its content.\n */\n private adaptThickness(): void {\n if (this._mounted) {\n this.applyRestingGeometry();\n }\n }\n\n // ----- collapse / expand -----\n\n /**\n * Returns whether the rail is collapsed to its gutter strip.\n *\n * @returns True when collapsed.\n */\n isCollapsed(): boolean {\n return this._options.collapsed ?? false;\n }\n\n /**\n * Collapses the rail to a thin gutter strip (handles hidden) or restores it\n * to full size, animating the cross-axis between the two when mounted. No-op\n * if already in the requested state.\n *\n * @param value - True to collapse, false to expand.\n *\n * @returns This rail, for method chaining.\n */\n setCollapsed(value: boolean): this {\n if (value === this.isCollapsed()) {\n return this;\n }\n\n if (value) {\n // Capture the expanded extent first: hidden handles drop out of the\n // preferred-size measurement, so the expand tween reads this back\n // rather than re-measuring an empty rail.\n this._expandedThickness = this.getThickness();\n }\n\n this._options.collapsed = value;\n\n if (this._mounted) {\n this.animateCollapseTransition(value);\n }\n\n return this;\n }\n\n /**\n * Toggles the collapsed state.\n *\n * @returns This rail, for method chaining.\n */\n toggleCollapsed(): this {\n return this.setCollapsed(!this.isCollapsed());\n }\n\n /**\n * Flips the chevron heading, applies the collapsed/expanded skin, positions\n * the chevron, and (immediately, no animation) shows or hides the handles\n * for the current collapsed state. Used by `mount` to seed a rail\n * constructed `collapsed`.\n */\n private applyCollapseAppearance(): void {\n const collapsed = this.isCollapsed();\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n this.setAllHandlesDisplayed(!collapsed);\n }\n\n /**\n * Skins the strip for the collapsed or expanded state, mirroring how a\n * [`Split`](/api/layout/classes/Split) / [`Border`](/api/layout/classes/Border)\n * gutter paints its collapsed strip (`SplitGutter.setOpaque`): collapsed, the\n * rail reads as a themed button surface (the same fill, gradient, and border\n * the framework's buttons use) that invites a click to restore; expanded, it\n * returns to the rail background and its single inner-edge divider.\n *\n * @param collapsed - True for the collapsed strip skin, false for the\n * expanded rail skin.\n */\n private applyCollapseStyling(collapsed: boolean): void {\n if (collapsed) {\n this.setBackgroundColor(\"var(--ts-ui-button-bg, #e8e8e8)\");\n this.setBackgroundImage(\"var(--ts-ui-button-bg, linear-gradient(rgb(241, 241, 241), rgb(200, 200, 200)))\");\n this.setBorder(\"1px solid var(--ts-ui-button-border, #c8c8c8)\");\n } else {\n this.clearBackgroundImage();\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.applyEdgeBorder();\n }\n }\n\n /**\n * Places and sizes the collapse chevron for the current state, writing\n * `left` / `top` / `width` onto the chevron's own `#id` style rule (shared by\n * selector with the rule {@link CollapseButton} uses for its rotation, so the\n * two coexist) and overriding the shared `.CollapseButton` class rule.\n *\n * - **Collapsed:** centred in the strip and sized to fill its full thickness,\n * so the restore handle reads as the Split collapsed-gutter handle.\n * - **Expanded:** pinned just inside the rail's inner (content-facing) edge —\n * the side opposite the viewport edge it anchors to (right for WEST, left\n * for EAST, bottom for NORTH, top for SOUTH) — at the narrow grip width,\n * its centre inset by {@link RAIL_CHEVRON_HALF_PX} so the whole grip sits\n * flush inside the strip rather than overhanging it.\n *\n * In both states the chevron is raised above the handles (`z-index`) so a\n * click always lands on it, never on a handle laid out beneath it; and it is\n * kept fully inside the `overflow: hidden` rail so a click never falls through\n * to whatever sits behind an overhang. Percentages (not pixels) for the\n * placement so the chevron tracks the rail's size and position through the\n * collapse tween and viewport resizes. `width` maps to the across-gutter axis\n * for every edge because {@link CollapseButton}'s rotation orients its box.\n *\n * @param collapsed - True for the collapsed (centred, strip-filling) chevron,\n * false for the expanded (inner-edge, grip) chevron.\n */\n private positionChevron(collapsed: boolean): void {\n let left: string;\n let top: string;\n\n if (collapsed) {\n // Centred in the strip — the CollapseButton class-rule default.\n left = \"50%\";\n top = \"50%\";\n } else {\n // Flush just inside the inner edge: anchor the chevron's centre half\n // a grip-width in from the edge (see RAIL_CHEVRON_HALF_PX).\n const inset = `calc(100% - ${RAIL_CHEVRON_HALF_PX}px)`;\n const near = `${RAIL_CHEVRON_HALF_PX}px`;\n\n switch (this.getEdge()) {\n case Placement.EAST:\n left = near;\n top = \"50%\";\n\n break;\n\n case Placement.NORTH:\n left = \"50%\";\n top = inset;\n\n break;\n\n case Placement.SOUTH:\n left = \"50%\";\n top = near;\n\n break;\n\n case Placement.WEST:\n default:\n left = inset;\n top = \"50%\";\n\n break;\n }\n }\n\n // Fill the strip thickness when collapsed; clear to the CollapseButton\n // grip width (its class-rule default) when expanded.\n const width = collapsed ? RAIL_COLLAPSED_THICKNESS_PX + \"px\" : null;\n\n new StyleRule({\n scope: \"component\",\n name: this._collapseButton.getId(),\n styles: { left, top, width, zIndex: \"1\" },\n });\n }\n\n /**\n * Animates the cross-axis between the full and collapsed extents. The chevron\n * heading, skin, and placement flip up front.\n *\n * The handles stay shown and laid out at the full extent across the whole\n * tween in both directions; because the rail clips its overflow, the moving\n * edge reveals them as the strip widens (expand) and clips them away as it\n * narrows (collapse), so they follow the animation symmetrically rather than\n * popping in or out. On collapse they are hidden only once the strip has\n * closed.\n *\n * @param collapsed - True when collapsing, false when expanding.\n */\n private animateCollapseTransition(collapsed: boolean): void {\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n\n const element = this.getElement();\n\n // The visible extent the tween starts from — the live cross-axis size\n // (full when collapsing, the collapsed strip when expanding). Captured\n // before the expand path lays out the full geometry below.\n const fromThickness = this.isVertical() ? this.getWidth() : this.getHeight();\n const toThickness = collapsed ? RAIL_COLLAPSED_THICKNESS_PX : this._expandedThickness;\n\n if (!collapsed) {\n // Reveal and lay the handles out at the full extent up front; the\n // tween's `from` immediately shrinks the visible strip back to the\n // collapsed width, so they wipe into view as it grows. (Collapse\n // keeps the already-laid-out handles in place so the narrowing strip\n // wipes them out; they are hidden in `finalize`.)\n this.setAllHandlesDisplayed(true);\n this.applyRestingGeometry();\n this.scheduleLayout();\n }\n\n const finalize = (): void => {\n // Now that the strip has closed, drop the handles out of the layout.\n if (collapsed) {\n this.setAllHandlesDisplayed(false);\n }\n\n this.applyRestingGeometry();\n\n if (!collapsed) {\n this.scheduleLayout();\n }\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n const tween = this.collapseTween(fromThickness, toThickness);\n\n this._collapseAnimation?.cancel();\n this._collapseAnimation = Animation.play(element, {\n from: tween.from,\n to: tween.to,\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: tween.properties,\n onComplete: finalize,\n });\n }\n\n /**\n * Builds the from/to inline styles for the collapse/expand tween: the\n * cross-axis dimension (and, for EAST/SOUTH rails whose anchored corner\n * moves, the matching `left`/`top`) between two explicit thicknesses.\n *\n * @param fromThickness - The cross-axis extent the tween starts from.\n * @param toThickness - The cross-axis extent the tween ends at.\n *\n * @returns The `from` / `to` style partials and the animated property names.\n */\n private collapseTween(fromThickness: number, toThickness: number): {\n from: Partial<CSSStyleDeclaration>;\n to: Partial<CSSStyleDeclaration>;\n properties: string[];\n } {\n const vp = DOM.source.getViewportSize();\n const from: Partial<CSSStyleDeclaration> = {};\n const to: Partial<CSSStyleDeclaration> = {};\n\n if (this.isVertical()) {\n from.width = fromThickness + \"px\";\n to.width = toThickness + \"px\";\n\n if (this.getEdge() === Placement.EAST) {\n from.left = (vp.width - fromThickness) + \"px\";\n to.left = (vp.width - toThickness) + \"px\";\n }\n } else {\n from.height = fromThickness + \"px\";\n to.height = toThickness + \"px\";\n\n if (this.getEdge() === Placement.SOUTH) {\n from.top = (vp.height - fromThickness) + \"px\";\n to.top = (vp.height - toThickness) + \"px\";\n }\n }\n\n return { from, to, properties: Object.keys(to) };\n }\n\n /**\n * Shows or hides every handle (drawer and window).\n *\n * @param displayed - True to show the handles, false to hide them.\n */\n private setAllHandlesDisplayed(displayed: boolean): void {\n for (const reg of this._drawers.values()) {\n reg.handle.setDisplayed(displayed);\n }\n\n for (const reg of this._windows.values()) {\n reg.handle?.setDisplayed(displayed);\n }\n }\n\n /**\n * Sets the handle-label text orientation for the vertical (WEST/EAST) sides\n * and re-applies the writing mode to every existing handle. Ignored visually\n * on NORTH/SOUTH, where handle text is always horizontal.\n *\n * @param orientation - The {@link RailOrientation} to apply.\n *\n * @returns This rail, for method chaining.\n */\n setOrientation(orientation: RailOrientation): this {\n this._options.orientation = orientation;\n\n // `_drawers` / `_windows` are class-field Maps initialised only after\n // super() returns, but `applyOptions` dispatches this setter during the\n // super() cascade. Skip the re-apply then — no handles exist yet, and\n // each one picks up the orientation as it is created (see\n // registerDrawer / showWindowHandle).\n if (this._drawers !== undefined) {\n this.applyOrientation();\n }\n\n return this;\n }\n\n /**\n * Returns the current handle-label text orientation.\n *\n * @returns The current orientation.\n */\n getOrientation(): RailOrientation {\n return this._options.orientation ?? this._defaultOptions.orientation!;\n }\n\n // ----- mount / unmount -----\n\n /**\n * Mounts the rail on `document.documentElement`: installs the handle-axis\n * layout manager, applies the divider border and resting geometry, attaches\n * the element, and tracks viewport resizes. No-op if already mounted.\n *\n * @returns This rail, for method chaining.\n */\n mount(): this {\n if (this._mounted) {\n return this;\n }\n\n this.setLayoutManager(this.isVertical() ? new VBox() : new HBox());\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n const element = this.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), element);\n\n // The collapse chevron is a raw child (self-centred via its own class\n // rule), outside the handle layout, so it doesn't count toward the\n // content-fit thickness. Append once; a remount reuses the element.\n const chevron = this._collapseButton.getElement(true);\n if (chevron && DOM.source.getParentElement(chevron) !== element) {\n DOM.sink.appendChild(element, chevron);\n }\n\n // Seed the chevron heading, skin, placement, and handle visibility for\n // the initial (possibly collapsed) state.\n this.applyCollapseAppearance();\n\n this.scheduleLayout();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = true;\n this.animateIn();\n\n return this;\n }\n\n /**\n * Unmounts the rail: stops tracking viewport resizes and detaches the\n * element. Registered drawers and windows keep their subscriptions, so a\n * later `mount()` restores a working strip.\n *\n * @returns This rail, for method chaining.\n */\n unmount(): this {\n if (!this._mounted) {\n return this;\n }\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = false;\n\n // Slide the strip back off its edge, then detach. Under reduced motion\n // Animation.play runs the completion synchronously.\n const element = this.getElement();\n const detach = (): void => { this.removeElement(); };\n\n if (!element) {\n detach();\n\n return this;\n }\n\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n onComplete: detach,\n });\n\n return this;\n }\n\n /**\n * Slides the strip in from off its anchored edge to its resting position.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._slideInAnimation?.cancel();\n this._slideInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n });\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the strip\n * translated one full thickness past the edge it anchors to, the start\n * (mount) and end (unmount) state of the slide.\n *\n * @returns A `translateX` / `translateY` CSS value.\n */\n private offscreenTransform(): string {\n switch (this.getEdge()) {\n case Placement.EAST:\n return \"translateX(100%)\";\n\n case Placement.NORTH:\n return \"translateY(-100%)\";\n\n case Placement.SOUTH:\n return \"translateY(100%)\";\n\n case Placement.WEST:\n default:\n return \"translateX(-100%)\";\n }\n }\n\n // ----- drawer composition -----\n\n /**\n * Registers a drawer: adds a handle that toggles it, mirrors the drawer's\n * open/closed state onto the handle via the drawer's public `on`, and (by\n * default) aligns the drawer's edge to the rail's. No-op if already\n * registered.\n *\n * @param drawer - The drawer to host. The caller retains ownership of its\n * lifecycle.\n * @param reg - Per-registration options (handle glyph / text, edge\n * alignment).\n *\n * @returns This rail, for method chaining.\n */\n registerDrawer(drawer: Drawer, reg: RailDrawerRegistration = {}): this {\n if (this._drawers.has(drawer)) {\n return this;\n }\n\n const handle = new RailHandle({ text: reg.text, glyph: reg.glyph, selected: drawer.isOpen() });\n\n const onOpen: () => void = (): void => { handle.setSelected(true); };\n const onClose: () => void = (): void => { handle.setSelected(false); };\n const onAction: ClickListener = (): void => { drawer.toggle(); };\n\n drawer.on(\"open\", onOpen);\n drawer.on(\"close\", onClose);\n handle.on(\"action\", onAction);\n\n if (reg.alignEdge !== false) {\n drawer.setEdge(this.getEdge() as DrawerEdge);\n }\n\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n this._drawers.set(drawer, { handle, onOpen, onClose, onAction });\n\n this.adaptThickness();\n this.scheduleLayout();\n this.emit(\"register\", drawer);\n\n return this;\n }\n\n /**\n * Unregisters a drawer: removes its handle and detaches every subscription\n * (the exact listener references are removed so nothing leaks). Does not\n * close or destroy the drawer. No-op if not registered.\n *\n * @param drawer - The drawer to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterDrawer(drawer: Drawer): this {\n const reg = this._drawers.get(drawer);\n if (!reg) {\n return this;\n }\n\n drawer.off(\"open\", reg.onOpen);\n drawer.off(\"close\", reg.onClose);\n reg.handle.off(\"action\", reg.onAction);\n\n this.removeComponent(reg.handle);\n this._drawers.delete(drawer);\n\n this.adaptThickness();\n this.emit(\"unregister\", drawer);\n\n return this;\n }\n\n // ----- window-minimize composition -----\n\n /**\n * Registers a window so it can minimize into the rail. Subscribes to the\n * window's minimize / restore / close events; while the window is minimized\n * it is represented by a rail handle that restores it on click. Called by\n * {@link AbstractWindow.setRail}. No-op if already registered.\n *\n * @param window - The window to host.\n *\n * @returns This rail, for method chaining.\n */\n registerWindow(window: AbstractWindow): this {\n if (this._windows.has(window)) {\n return this;\n }\n\n const onMinimize: () => void = (): void => { this.showWindowHandle(window); };\n const onRestore: () => void = (): void => { this.removeWindowHandle(window); };\n const onClose: () => void = (): void => { this.unregisterWindow(window); };\n const onAction: ClickListener = (): void => { window.restore(); };\n\n window.on(\"minimize\", onMinimize);\n window.on(\"restore\", onRestore);\n window.on(\"close\", onClose);\n\n this._windows.set(window, { handle: null, onMinimize, onRestore, onClose, onAction });\n\n // A window registered while already minimized gets its handle now.\n if (window.isMinimized()) {\n this.showWindowHandle(window);\n }\n\n this.emit(\"register\", window);\n\n return this;\n }\n\n /**\n * Unregisters a window: removes any handle and detaches every subscription.\n * Does not close the window. No-op if not registered.\n *\n * @param window - The window to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterWindow(window: AbstractWindow): this {\n const reg = this._windows.get(window);\n if (!reg) {\n return this;\n }\n\n window.off(\"minimize\", reg.onMinimize);\n window.off(\"restore\", reg.onRestore);\n window.off(\"close\", reg.onClose);\n\n this.removeWindowHandle(window);\n this._windows.delete(window);\n\n this.emit(\"unregister\", window);\n\n return this;\n }\n\n /**\n * Creates and adds a handle representing a minimized window, bearing its\n * title and glyph, wired to restore it on click. No-op if a handle already\n * shows.\n *\n * @param window - The minimized window.\n */\n private showWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle !== null) {\n return;\n }\n\n const handle = new RailHandle({ text: window.getTitle(), glyph: window.getGlyph(), selected: true });\n handle.on(\"action\", reg.onAction);\n\n reg.handle = handle;\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Removes the handle representing a window, if one shows. No-op otherwise.\n *\n * @param window - The window whose handle to remove.\n */\n private removeWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle === null) {\n return;\n }\n\n reg.handle.off(\"action\", reg.onAction);\n this.removeComponent(reg.handle);\n reg.handle = null;\n\n this.adaptThickness();\n }\n\n // ----- internal: handle orientation -----\n\n /**\n * Re-applies the current orientation's writing mode to every handle (drawer\n * and window). Called when the orientation changes.\n */\n private applyOrientation(): void {\n for (const reg of this._drawers.values()) {\n this.applyHandleOrientation(reg.handle);\n }\n\n for (const reg of this._windows.values()) {\n if (reg.handle !== null) {\n this.applyHandleOrientation(reg.handle);\n }\n }\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Applies the orientation's writing mode to a single handle: a rotated\n * `writing-mode` on the vertical (WEST/EAST) sides, cleared otherwise (and\n * always on NORTH/SOUTH, where handle text stays horizontal). Mirrors the\n * `Tab` layout's `sideways-rl` / `sideways-lr` mapping.\n *\n * @param handle - The handle to orient.\n */\n private applyHandleOrientation(handle: RailHandle): void {\n const orientation = this.getOrientation();\n\n // `sideways-rl` reads top-to-bottom (clockwise); `sideways-lr` reads\n // bottom-to-top (counter-clockwise). Only meaningful on a vertical rail.\n const writingMode = orientation === \"vertical-cw\" ? \"sideways-rl\"\n : orientation === \"vertical-ccw\" ? \"sideways-lr\"\n : null;\n\n if (this.isVertical() && writingMode !== null) {\n handle.setWritingMode(writingMode);\n } else {\n handle.clearWritingMode();\n }\n }\n\n // ----- internal: geometry -----\n\n /**\n * Opts the rail out of content-derived size clamping. Like\n * [`Container`](/api/core/classes/Container) / [`Panel`](/api/core/classes/Panel),\n * the rail sizes itself explicitly — its main axis spans the viewport and\n * its cross axis is the thickness it computes — so {@link Component.setWidth} /\n * {@link Component.setHeight} must not be clamped back to the layout\n * manager's content size. Without this, collapsing (which hides every\n * handle) would empty the handle layout, drive its content max toward zero,\n * and clamp the rail's viewport-spanning main axis to nothing — the strip\n * would vanish instead of resting at {@link RAIL_COLLAPSED_THICKNESS_PX}.\n *\n * @returns Always `false`.\n */\n protected clampsToContentSize(): boolean {\n return false;\n }\n\n /**\n * Returns whether the rail lays its handles out vertically — true for the\n * WEST and EAST edges (a column at a fixed width).\n *\n * @returns True for a vertical (WEST/EAST) rail.\n */\n private isVertical(): boolean {\n const edge = this.getEdge();\n\n return edge === Placement.WEST || edge === Placement.EAST;\n }\n\n /**\n * Returns the main-axis viewport offset of a registered window's rail handle —\n * where the genie animation should aim. When the handle exists (restore, or an\n * already-minimized window) this is its laid-out position; when it does not yet\n * exist (the collapse genie runs before the handle is created) this is the\n * predicted append slot: after the last existing handle plus one gap, or 0 for\n * an empty rail. Main axis is Y for a vertical (WEST/EAST) rail, X for a\n * horizontal (NORTH/SOUTH) one; the rail's main-axis viewport origin is 0.\n *\n * @param window - The window to locate, registered or not.\n *\n * @returns The main-axis viewport offset in pixels.\n */\n handleMainAxisOffset(window: AbstractWindow): number {\n const vertical = this.isVertical();\n // A handle `commitBounds` last placed via its size-stable position fast\n // path has its move riding on `getTranslateX`/`getTranslateY` while\n // `getX`/`getY` still report the pre-move value — folding the translate\n // in here keeps this accurate for a handle mid-fast-path (e.g. one a\n // sibling's removal just compacted into a new slot without resizing it).\n const mainPos = (c: Component): number => vertical ? c.getY() + c.getTranslateY() : c.getX() + c.getTranslateX();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n // Restore path (and an already-minimized window): the handle exists — its\n // laid-out main-axis position is the target directly.\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainPos(handle);\n }\n\n // Collapse path: the handle is created after this runs, appended last.\n // Predict its slot from the current last handle's trailing edge + gap.\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n const last = handles[handles.length - 1];\n\n return mainPos(last) + mainExtent(last) + this.handleSpacing();\n }\n\n /**\n * Returns the main-axis length of a registered window's rail handle — the\n * span the genie centres the shrinking window along. When the handle exists\n * (restore, or an already-minimized window) this is its laid-out extent; when\n * it does not yet exist (the collapse genie runs before the handle is\n * created) it is predicted from the current last handle, since sibling\n * handles run close in size. An empty rail has no handle to sample, so this\n * returns 0 and the caller keeps the window at the slot's leading edge. Main\n * axis is the height for a vertical (WEST/EAST) rail, the width for a\n * horizontal (NORTH/SOUTH) one.\n *\n * @param window - The window whose handle to measure, registered or not.\n *\n * @returns The main-axis handle length in pixels, or 0 when unpredictable.\n */\n handleMainAxisExtent(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainExtent(handle);\n }\n\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n return mainExtent(handles[handles.length - 1]);\n }\n\n /**\n * Returns the inter-handle gap the rail's `VBox` / `HBox` layout manager\n * applies between handles, or `0` before the layout manager exists.\n *\n * @returns The component spacing in pixels.\n */\n private handleSpacing(): number {\n const lm = this.getLayoutManager();\n\n return lm instanceof BoxLayout ? lm.getComponentSpacing() : 0;\n }\n\n /**\n * Computes the rail's on-screen rect from the current edge, thickness, and\n * viewport. WEST/EAST span the full viewport height at the chosen width;\n * NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const thickness = this.getThickness();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - thickness, y: 0, width: thickness, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: thickness };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - thickness, width: vp.width, height: thickness };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: thickness, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the rail via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Applies the expanded-state border: a 1px divider (`--ts-ui-rail-border`) on\n * the rail's inner edge — the side facing the rest of the UI — with the other\n * three sides a 1px *transparent* border rather than no border.\n *\n * Reserving the same 1px box on every side that the collapsed strip's button\n * border occupies keeps the rail's border-box geometry identical across the\n * collapse/expand transition, so the handles and chevron don't jump by a\n * pixel when the visible border appears or disappears.\n */\n private applyEdgeBorder(): void {\n const transparent = \"1px solid transparent\";\n const divider = \"1px solid var(--ts-ui-rail-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: transparent, borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: transparent, borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: transparent, borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: transparent, borderRight: divider });\n\n break;\n }\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the rail's events.\n *\n * @param event - `\"register\"` fires when a drawer/window is added,\n * `\"unregister\"` when one is removed.\n * @param listener - The callback, receiving the affected drawer or window.\n *\n * @returns This rail, for method chaining.\n */\n on(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This rail, for method chaining.\n */\n off(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): 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 target - The affected drawer or window, forwarded to each listener.\n */\n protected emit(event: RailEvent, target: Drawer | AbstractWindow): void {\n this._listeners.fire(event, target);\n }\n\n /**\n * Cancels any in-flight collapse / slide animation and disposes the\n * collapse chevron, then defers to the base class. Cancelling the\n * animations first keeps their fallback timers from firing after\n * `super.destructor()` has released this rail's element handle. The\n * chevron needs its own disposal because `mount()` appends it straight to\n * this rail's element rather than registering it as a child, so\n * `super.destructor()`'s recursion cannot reach it.\n */\n protected destructor(): void {\n this._collapseAnimation?.cancel();\n this._collapseAnimation = null;\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = null;\n this._slideInAnimation?.cancel();\n this._slideInAnimation = null;\n\n this._collapseButton.dispose();\n\n super.destructor();\n }\n}\n\nconst RailCallable = callable(Rail);\ntype RailCallable = Rail;\nexport {\n Rail as _Rail,\n RailCallable as Rail,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport { Component } from \"~/core/Component.js\";\nimport type { ComponentFactory } from \"~/core/Component.js\";\nimport { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport { TabWindow } from \"~/overlay/TabWindow.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Tab } from \"~/layout/Tab.js\";\nimport { Split } from \"~/layout/Split.js\";\nimport { DockRegion } from \"~/layout/DockRegion.js\";\nimport { LayoutConstraints } from \"~/layout/LayoutConstraints.js\";\nimport { serializeLayout, restoreLayout, LayoutState } from \"~/layout/LayoutSerialization.js\";\nimport { DragManager, DragEventDetail, tabDragRegistry } from \"~/overlay/DragManager.js\";\nimport { DropZoneOverlay } from \"~/overlay/DropZoneOverlay.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { AxisOrientation } from \"~/primitive/Axis.js\";\n\n/**\n * Declarative description of one dockable content panel.\n *\n * @category Core\n */\nexport interface DockPanelSpec {\n /**\n * Stable identity. `Dock` builds an identity frame whose `getId()` is this\n * value (set at the frame's construction), and serialization keys on it — so\n * restored layouts round-trip against it. Keep it stable across sessions.\n */\n id: string;\n /**\n * Visible tab label and tear-off window title. Set as the identity frame's\n * `getName()`; it rides on the frame (not a per-container constraint), so it\n * survives the re-home a restore performs.\n */\n title: string;\n /** Optional registry glyph name shown leading the tab label. */\n glyph?: string;\n /** Optional hover-tooltip text shown over the tab button. */\n tooltip?: string;\n /** Whether the tab shows a close button. Defaults to `true`. */\n closeable?: boolean;\n /**\n * Whether closing this panel's tab destroys its content. Defaults to\n * `true`. Pass `false` when `content` is a live component you hold and\n * intend to re-add later; a factory needs no opt-out, since a re-add\n * rebuilds through it.\n */\n disposeOnClose?: boolean;\n /**\n * The content: a live component, or a factory built on first resolve. It is\n * placed inside the identity frame, never mutated. A factory returning a\n * promise is accepted only by {@link Dock.addLazyPanel}, which shows a\n * spinner for the whole wait; {@link Dock.addPanel} throws on one.\n */\n content: Component | ComponentFactory;\n}\n\n/**\n * A node in the declarative initial arrangement: a leaf panel, a split of\n * regions, or a tab group.\n *\n * @category Core\n */\nexport type DockLayoutSpec =\n | DockPanelSpec\n | { split: AxisOrientation; children: DockLayoutSpec[] }\n | { tabs: DockPanelSpec[] };\n\n/**\n * Construction options for a {@link Dock}.\n *\n * @category Core\n */\nexport interface DockOptions extends ContainerOptions {\n /** Initial arrangement, compiled to the region tree at construction. Omit for an empty dock. */\n layout?: DockLayoutSpec;\n /**\n * Placeholder shown only while the dock holds no live panel (tiled or\n * floated) — a start-page for an empty dock. It is shown as a single\n * non-closeable tab in the empty region and is chrome, not a panel: it is\n * never serialized. See {@link Dock.setEmptyContent}.\n */\n emptyContent?: Component;\n /**\n * Construction-time listener bag — the declarative form of {@link Dock.on},\n * so a consumer can wire dock events (notably `emptychange`, the empty↔\n * populated aggregate) in the options bag instead of a separate post-build\n * `on(...)` call. Each entry is registered via `on` after construction; the\n * born-empty state is adopted without an emit, so read {@link Dock.isEmpty}\n * for the initial value.\n */\n listeners?: {\n attach?: (event: DockPanelEvent) => void;\n detach?: (event: DockPanelEvent) => void;\n move?: (event: DockPanelEvent) => void;\n focus?: (event: DockPanelEvent | null) => void;\n close?: (event: DockPanelEvent) => void;\n emptychange?: (event: DockEmptyEvent) => void;\n exception?: (event: DockExceptionEvent) => void;\n };\n}\n\n/**\n * String-literal union of the events a {@link Dock} emits across a panel's\n * lifecycle.\n *\n * The model is *host-centric*: a live panel always occupies one Dock-managed\n * *host* — the *tiled tree* (the main dock) or a *float window* — and the events\n * name the host transitions. `\"attach\"` fires when a panel **enters** a host (a\n * fresh `addPanel`/restore into the tiled tree, or a tear-off into a fresh\n * float); `\"detach\"` fires when it **leaves** a host while staying alive. The\n * two pair up across a move: a tear-off is `\"detach\"`(tiled) then\n * `\"attach\"`(float), and a re-dock — whether dropped on a region body/edge or\n * merged onto an existing tab bar — is `\"detach\"`(float) then `\"attach\"`(tiled).\n * `\"move\"` fires when a panel **relocates within** its current host — a\n * different region in the same tiled tree, or repositioned in the same float —\n * without changing host; it never accompanies a host change (that is\n * `\"detach\"`+`\"attach\"`) nor a first appearance (that is `\"attach\"` alone), and\n * a pure reorder within one strip is silent. `\"focus\"` fires when the dock-wide\n * active panel changes (across tiled tabs and floats; `null` when nothing is\n * focused), and `\"close\"` when a panel is destroyed. The\n * {@link DockPanelEvent.window} field names *which* host the panel entered,\n * left, occupies, or moved within. See {@link DockPanelEvent} for the payload.\n *\n * A separate `\"emptychange\"` event is a *dock-wide aggregate*, not a per-panel\n * event: it fires once each time the dock transitions between holding no live\n * panel anywhere and holding at least one, carrying a {@link DockEmptyEvent}.\n *\n * `\"exception\"` reports that a lazy panel's content factory rejected, carrying a\n * {@link DockExceptionEvent} rather than a {@link DockPanelEvent} — a panel that\n * never built has no content to name. It follows that panel's own `\"close\"`,\n * because the failure tears the whole docked panel down; the panel stays\n * registered, so re-adding the same id rebuilds it and retries.\n *\n * @category Core\n */\nexport type DockEvent = \"attach\" | \"detach\" | \"move\" | \"focus\" | \"close\" | \"emptychange\" | \"exception\";\n\n/**\n * Payload for a {@link Dock} lifecycle event, identifying the panel by its\n * stable {@link DockPanelSpec.id}, carrying its Dock-owned identity frame, and\n * naming the host the event concerns.\n *\n * @category Core\n */\nexport interface DockPanelEvent {\n /** The stable id of the panel (its {@link DockPanelSpec.id}). */\n id: string;\n /** The panel's Dock-owned identity frame. */\n content: Component;\n /**\n * The host the panel entered (`\"attach\"`), left (`\"detach\"`), moved within\n * (`\"move\"` — same host before and after), or currently occupies\n * (`\"focus\"`): `null` denotes the tiled tree / main dock, otherwise the float\n * window. Always `null` for `\"close\"` — a destroy is not a host transition,\n * but the field is always present so the payload stays flat.\n */\n window: AbstractWindow | null;\n}\n\n/**\n * Payload for a {@link Dock} `\"emptychange\"` event, reporting whether the dock\n * just became empty. Emitted once per real transition, not per panel.\n *\n * @category Core\n */\nexport interface DockEmptyEvent {\n /** `true` when the dock just became empty (no live panels anywhere), `false` when it became populated. */\n empty: boolean;\n}\n\n/**\n * Payload for a {@link Dock} `\"exception\"` event: a lazy panel's content\n * factory rejected. The panel has already been closed and its `\"close\"` event\n * already emitted by the time this fires.\n *\n * @category Core\n */\nexport interface DockExceptionEvent {\n /** The stable id of the panel that failed (its {@link DockPanelSpec.id}). */\n id: string;\n /** The value the content factory's promise rejected with. */\n error: unknown;\n}\n\n/**\n * Per-region drag-and-drop wiring tracked by the re-wire sweep.\n */\ninterface RegionWiring {\n /** The region's edge/centre drop coordinator (torn down on teardown). */\n dockRegion: DockRegion;\n /** Whether `setReorderable(true)` + the prune-on-`\"empty\"` subscription were applied (Tab regions only). */\n tabWired: boolean;\n}\n\n/**\n * A user-configurable, rearrangeable panel layout — the VS Code / GoldenLayout\n * style dock. It hosts a tree of [`Split`](/api/layout/classes/Split) /\n * [`Tab`](/api/layout/classes/Tab) regions whose panels the user can reorder,\n * tear off into floating [`Window`](/api/overlay/classes/Window)s, drop on region\n * edges to split, and save/restore.\n *\n * `Dock` is **glue**, not new drag mechanics: tab reorder + tear-off come from\n * `Tab`'s reorderable wiring, edge-split-on-drop from\n * [`DockRegion`](/api/layout/classes/DockRegion), every re-parent from\n * [`Component.moveComponent`](/api/core/classes/Component#movecomponent), and\n * persistence from\n * [`serializeLayout`](/api/layout/functions/serializeLayout) /\n * [`restoreLayout`](/api/layout/functions/restoreLayout). `Dock` owns the panel\n * registry (the serialization factory), the declarative initial-layout\n * compiler, and the re-wire sweep that keeps **every** region dockable —\n * including the regions a drop creates mid-gesture.\n *\n * @category Core\n */\nclass Dock extends Container<DockOptions> {\n\n // panelId -> spec; the single source the serialization factory resolves from.\n private _panels: Map<string, DockPanelSpec> = new Map<string, DockPanelSpec>();\n // panelId -> the Dock-owned identity frame built for that spec (cached so a\n // lazy factory runs once and every resolve returns the same instance).\n private _frames: Map<string, Component> = new Map<string, Component>();\n // panelId -> deferred content factory for a lazy panel (see addLazyPanel).\n // resolvePanel reads it to give the frame a lazy Tab layout (Tab.addLazyTab),\n // which owns the once-only materialization; the frame caches in _frames after.\n private _lazyFactories: Map<string, ComponentFactory> = new Map<string, ComponentFactory>();\n // region container -> its DnD wiring; the sweep's idempotence + teardown ledger.\n private _wiring: Map<Component, RegionWiring> = new Map<Component, RegionWiring>();\n // rAF coalescing latch so a burst of moves in one gesture yields one sweep.\n private _sweepScheduled: boolean = false;\n\n // Panel-lifecycle event bag. A field initialiser is safe here: no\n // cascade-dispatched setter touches it, and Dock exposes no construction-time\n // `listeners` option (no DockEvent is a build-time gesture — addPanel /\n // compileLayout run after super()), so it is never dispatched from\n // applyOptions. Consumers wire post-construction via on(...).\n private _listeners: ListenerBag<DockEvent> = this.registerListenerBag(new ListenerBag<DockEvent>());\n // The dock-wide focused panel id, or null when nothing is focused. The single\n // source of truth gating every \"focus\" emit so a re-activation is silent.\n private _focusedPanelId: string | null = null;\n // panelId -> last-observed host (null = tiled tree, else the float window);\n // the source of the attach/detach diff. A host change across a sweep emits\n // detach(old host) then attach(new host); a first appearance emits attach only.\n private _panelHost: Map<string, AbstractWindow | null> = new Map<string, AbstractWindow | null>();\n // panelId -> the Tab region last observed hosting it; lets a close recompute\n // the surviving sibling's focus after the region re-selects.\n private _frameRegion: Map<string, Component> = new Map<string, Component>();\n // The Tab region the user most recently focused (or that last received a\n // programmatic add). `addPanel` docks new panels here when it is still a live\n // region, so a new tab opens beside whatever tab-bar the user last worked in\n // rather than always the first/primary region. Null until the first add.\n private _lastActiveRegion: Component | null = null;\n // Float windows whose lifecycle events are already subscribed; the tracked-set\n // guard that stops a re-sweep stacking duplicate listeners.\n private _floatSubscribed: Set<AbstractWindow> = new Set<AbstractWindow>();\n\n // Overlay highlighting the dock as a drop target while it is empty (every\n // panel torn off) and a tab is dragged over it.\n private _emptyDropOverlay: DropZoneOverlay = new DropZoneOverlay();\n\n // Empty-state latch: whether the dock currently holds no live panel. Gates\n // the \"emptychange\" emit so it fires only on a real transition. Seeded true\n // because a dock is born empty: the first reconcile on a still-empty dock then\n // finds no transition (silent), while a dock born with a layout — or the first\n // addPanel — correctly flips it to false and emits emptychange(false). A plain\n // initializer is safe — no cascade-dispatched setter writes it.\n private _empty: boolean = true;\n // Whether reconcileEmptyState has run at least once. Gates only the\n // \"emptychange\" emit: the first run adopts the born emptiness without an emit\n // (being born empty or populated is not a transition). The placeholder itself\n // is re-asserted every sweep from the main-region emptiness, independent of\n // this latch.\n private _emptyReconciled: boolean = false;\n\n // Named, bound listener reference for the DockRegion post-drop callback,\n // routing through one removable handler that coalesces via scheduleSweep.\n private requestSweep: () => void = (): void => {\n this.scheduleSweep();\n };\n\n // Named, bound \"dock\" handler for every wired Tab: a foreign tab was merged\n // into a region's strip. The merge bypasses DockRegion, so it lands a sweep\n // here — the host-diff reconcile then emits the attach. The content arg is\n // unused (the reconcile re-derives every frame's host) but matches the\n // listener signature.\n private onPanelDocked: (content: Component) => void = (_content: Component): void => {\n this.requestSweep();\n };\n\n /**\n * Constructs a Dock, compiling `options.layout` into the region tree (or an\n * empty `Tab` region when omitted) and running the initial re-wire sweep.\n *\n * @param options - Construction options; `layout` seeds the arrangement.\n * @param subclassDefaults - Optional defaults a subclass injects before the caller's options.\n */\n constructor(options?: DockOptions, subclassDefaults?: Partial<DockOptions>) {\n super(options, { layoutManager: new Fit(), ...subclassDefaults });\n\n const root = options?.layout ? this.compileLayout(options.layout) : this.newTabRegion();\n\n this.addComponent(root);\n this.scheduleSweep();\n this.wireEmptyDropTarget();\n\n // Wire the declarative listener bag here, not in applyOptions: applyOptions\n // runs inside super() before this._listeners' field initializer, so on()\n // would target an undefined bag. scheduleSweep only reconciles emptiness\n // asynchronously, so no emptychange is missed by wiring after it.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies inherited options first, then routes the `emptyContent` placeholder\n * through its setter. The setter only caches (no DOM work) because it is\n * dispatched from within `super()`; the placeholder attaches from the first\n * post-construction reconcile.\n *\n * @param options - The construction options.\n *\n * @returns This dock.\n */\n protected applyOptions(options: DockOptions): this {\n super.applyOptions(options);\n\n if (options.emptyContent !== undefined) {\n this.setEmptyContent(options.emptyContent);\n }\n\n return this;\n }\n\n /**\n * Sets (or clears, with `null`) the placeholder shown while the dock holds no\n * live panel — a start-page for an empty dock. The placeholder is shown as a\n * single non-closeable tab in the empty root region, and is chrome: it is\n * never serialized (excluded from a saved arrangement) and never enters the\n * panel registry. When no placeholder is set, the empty region hides its tab\n * strip entirely; either way the dock still reports emptiness and fires\n * `\"emptychange\"`.\n *\n * When the dock is already empty, the shown placeholder is swapped immediately;\n * otherwise the value is only cached and shown on the next empty transition.\n *\n * @param component - The placeholder component, or `null` to clear it.\n *\n * @returns This dock, for chaining.\n */\n setEmptyContent(component: Component | null): this {\n // Hot-swap only once the state machine is live (first reconcile has run):\n // during the super()/applyOptions cascade _emptyReconciled is still false,\n // keeping the setter cache-only so no DOM work happens at construction. The\n // placeholder is shown while the main region is empty (all tabs closed or\n // torn off), so gate the swap on that, not on dock-wide emptiness.\n const showing = this._emptyReconciled && this.mainRegionEmpty();\n\n if (showing) {\n this.hideEmptyState(); // remove the outgoing placeholder tab\n }\n\n this._options.emptyContent = component ?? undefined;\n\n if (showing) {\n this.showEmptyState(); // show the incoming placeholder tab\n }\n\n return this;\n }\n\n /**\n * The placeholder shown while the dock is empty, or `null` when none is set.\n *\n * @returns The placeholder component, or `null`.\n */\n getEmptyContent(): Component | null {\n return this._options.emptyContent ?? null;\n }\n\n /**\n * Whether the dock holds no live panel anywhere — tiled or floated. A dock\n * whose only panels are torn off into floats is *not* empty (the floats are\n * still live panels of this dock), so this reports `false` for it, and\n * `\"emptychange\"` fires off this aggregate. The empty-state placeholder is a\n * separate, *visual* concern that tracks the main region alone, so it can show\n * over an all-floated dock while this still reports `false`.\n *\n * @returns `true` when no live panel exists.\n */\n isEmpty(): boolean {\n return this._frames.size === 0;\n }\n\n /**\n * Registers the dock itself as a drop target that is active only while the\n * dock is empty — every panel torn off, so no region remains to carry a\n * `DockRegion`. Dropping a tab onto the bare dock then builds a fresh root\n * region to hold it, making an emptied dock its own re-dock surface rather\n * than leaving a placeholder region behind. While non-empty the predicate\n * declines, so the regions' own `DockRegion`s (nested deeper, hit first by\n * the hit-test) keep handling drops.\n */\n private wireEmptyDropTarget(): void {\n DragManager.makeDropTarget(this, {\n accepts: (detail: DragEventDetail): boolean =>\n detail.dragData[\"tabDrag\"] === true && this.getComponents().length === 0,\n // The full-region blue overlay is the only feedback here; suppress the\n // manager's whole-target tint so it does not stack with it.\n suppressValidityTint: true,\n onDragOver: (): null => {\n this._emptyDropOverlay.attachTo(this);\n this._emptyDropOverlay.highlightFull();\n\n return null;\n },\n onDragLeave: (): void => {\n this._emptyDropOverlay.detach();\n },\n onDrop: (detail: DragEventDetail): void => {\n this._emptyDropOverlay.detach();\n\n const panel = tabDragRegistry.get(detail.dragData[\"componentId\"] as string);\n\n if (!panel) {\n return;\n }\n\n const region = this.newTabRegion();\n\n this.addComponent(region);\n region.moveComponent(panel);\n this.scheduleSweep();\n },\n });\n }\n\n /**\n * Registers a panel and adds it as a tab in the active region, then schedules\n * a re-wire sweep so a newly-created region is made dockable.\n *\n * @param spec - The panel to register and dock.\n *\n * @returns This dock, for chaining.\n */\n addPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(content, undefined, this.leafConstraints(spec));\n\n // Activate the freshly added panel so opening it shows it. The tab\n // cell is created lazily on the region's next doLayout, so this may\n // defer (Tab.setActiveContent) until that pass.\n (region.getLayoutManager() as Tab).setActiveContent(content);\n\n // The panel just entered the tiled tree. The ledger is left without an\n // entry for this id so the next sweep's host diff sees a first\n // appearance and emits attach(tiled) — the same reconcile path a\n // dragged-in dock flows through, so a programmatic add and a drop\n // produce the event from identical code.\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Adds a panel whose content is built lazily — on first activation — instead\n * of up front. The tab appears immediately (the identity frame is created\n * empty, so re-open dedup via {@link focusPanel} and layout serialization keep\n * working); on first activation a centred spinner shows while the spec's\n * `content` factory runs, then the built content fades in. The frame's own\n * (strip-hidden) Tab layout drives this via {@link Tab.addLazyTab}, so the\n * spinner/content cross-fade is hosted by the same materialize path every lazy\n * tab uses. Use it for panels whose content is expensive to build or fetches\n * data, so opening one never blocks the tab from appearing.\n *\n * @param spec - The panel to register and dock; `content` is treated as the\n * lazy factory (a live component is wrapped in one).\n *\n * @returns This dock, for chaining.\n */\n addLazyPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n this._lazyFactories.set(\n spec.id,\n typeof spec.content === \"function\" ? spec.content : () => spec.content as Component\n );\n\n const frame = this.resolvePanel(spec.id);\n\n if (frame) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(frame, undefined, this.leafConstraints(spec));\n\n // Activate it so the tab shows; activation drives realizeLazyContent\n // (via the region's \"activate\" -> onPanelFocused) to materialize the\n // deferred content behind a spinner.\n (region.getLayoutManager() as Tab).setActiveContent(frame);\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Returns the root region container (a `Container` carrying a `Split`/`Tab`\n * manager). Derived live as the sole `Fit` child rather than cached, because\n * an edge drop onto the root swaps that child for a fresh `Split` wrapper.\n *\n * @returns The root region.\n */\n getRootRegion(): Component {\n return this.getComponents()[0];\n }\n\n /**\n * Captures the current arrangement (delegates to\n * [`serializeLayout`](/api/layout/functions/serializeLayout)).\n *\n * @returns The captured layout state.\n */\n getLayoutState(): LayoutState {\n return serializeLayout(this.getRootRegion());\n }\n\n /**\n * Restores a captured arrangement, sourcing leaves from the panel registry\n * (delegates to [`restoreLayout`](/api/layout/functions/restoreLayout)), then\n * schedules a sweep to wire the fresh regions restore created.\n *\n * The region ledger is cleared first: `restoreLayout` tears the region tree\n * down and rebuilds it, so every surviving panel lands in a fresh region\n * object. Without clearing, the post-restore sweep would read each panel's\n * stale (now-destroyed) region and spuriously fire `\"move\"` for every panel.\n * A restore is not a user-visible relocation, so it stays silent for\n * `\"move\"`; the sweep re-seeds the ledger from the rebuilt tree.\n *\n * @param state - A layout state from {@link getLayoutState}.\n *\n * @returns This dock, for chaining.\n */\n setLayoutState(state: LayoutState): this {\n this._frameRegion.clear();\n restoreLayout(this.getRootRegion(), state, (id: string) => this.resolvePanel(id));\n this.scheduleSweep();\n\n return this;\n }\n\n /**\n * The serialization factory: resolves a panel id to its Dock-owned identity\n * frame, building it once (running a lazy content factory at most once) and\n * caching it so every resolve returns the same instance.\n *\n * The frame is a `Container` constructed with the stable `id` (the serialization\n * key, read back via `getId()`) and the `title` (the visible tab label, via\n * `getName()`) set **at construction** — the caller's content is placed\n * inside it and never mutated. The id must be set at construction, not via a\n * later `setId`, so the frame's `#id`-scoped CSS rule (which carries\n * `position: absolute`) binds to the element; a post-construction `setId`\n * would leave that rule on the old id and the frame would collapse to\n * `position: static`.\n *\n * @param id - The panel id to resolve.\n *\n * @returns The identity frame, or `null` when the id is unknown.\n */\n private resolvePanel(id: string): Component | null {\n const spec = this._panels.get(id);\n\n if (!spec) {\n return null;\n }\n\n let frame = this._frames.get(id);\n\n if (!frame) {\n const factory = this._lazyFactories.get(id);\n\n if (factory) {\n // A lazy panel defers its content to first activation, and the\n // frame's own Tab layout owns that deferral: Tab.addLazyTab mounts a\n // spinner during the two-frame yield and fades the built content in,\n // hosting the transient spinner/content overlap the way every lazy\n // tab does — the strip hidden so the single content fills the frame.\n // Delegating to the Tab is why the frame is not a plain Fit here: a\n // single-child Fit faults the instant the spinner and content coexist.\n const tab = new Tab();\n\n tab.setBarVisible(false);\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: tab });\n\n // The frame's Tab is a local and unreachable from outside, so\n // this subscription is the only thing that can turn a failed\n // content build into a Dock-level event. Named reference, not an\n // inline arrow, per the listener rule in ARCHITECTURE.md — the\n // same shape `wireRegion`'s `onEmpty` uses for a per-frame\n // handler.\n const onFailed: (error: unknown) => void = (error: unknown): void => {\n this.failPanel(spec.id, error);\n };\n\n // The frame's Tab is a local and its strip is hidden, so this\n // subscription is the only thing that can put a docked panel's\n // load on screen. Named reference, not an inline arrow, per the\n // listener rule in ARCHITECTURE.md.\n const onBusyChanged: (busy: boolean) => void = (busy: boolean): void => {\n this.setFrameBusy(spec.id, busy);\n };\n\n tab.on(\"exception\", onFailed);\n tab.on(\"busychange\", onBusyChanged);\n tab.addLazyTab(factory, spec.title ?? spec.id);\n } else {\n // A normal panel builds its content now; the frame exists at once so\n // the tab shows and the id dedups / serializes like any other.\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: new Fit() });\n\n // A live component or a synchronous factory. A promise-returning\n // factory raises Component.addComponent's Error here: a Fit frame\n // has no spinner and nothing to own the wait.\n frame.addComponent(spec.content);\n }\n\n this._frames.set(id, frame);\n }\n\n return frame;\n }\n\n /**\n * Builds the glyph-only layout constraints for a leaf, or `undefined` when\n * the spec has no glyph. The glyph is the one presentation hint `Tab` reads\n * from the constraint; identity and label ride on the component itself.\n *\n * @param spec - The panel spec.\n *\n * @returns The constraints, or `undefined`.\n */\n private leafConstraints(spec: DockPanelSpec): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n // Dock tabs are closeable by default; a spec may opt out.\n constraints.closeable = spec.closeable ?? true;\n constraints.disposeOnClose = spec.disposeOnClose ?? true;\n\n if (spec.glyph) {\n constraints.glyph = spec.glyph;\n }\n\n if (spec.tooltip) {\n constraints.tooltip = spec.tooltip;\n }\n\n return constraints;\n }\n\n /**\n * Resolves the region `addPanel` docks into: the root if it is a `Tab`, else\n * the first `Tab` region found depth-first, else the root wrapped in a fresh\n * `Tab` (only reachable for an externally-crafted tab-less tree — `Dock`'s\n * own compiler and `DockRegion` always keep leaves in `Tab` stacks).\n *\n * @returns A `Tab` region to add a tab to.\n */\n private activeTabRegion(): Component {\n const root = this.getRootRegion();\n\n // Prefer the region the user last focused, when it is still a live Tab in\n // the current tree, so a new panel opens beside the last-used tab-bar.\n const last = this._lastActiveRegion;\n if (last && this.isTab(last) && this.containsRegion(root, last)) {\n return last;\n }\n\n if (this.isTab(root)) {\n return root;\n }\n\n return this.firstTabRegion(root) ?? this.wrapRootInTab();\n }\n\n /**\n * Depth-first membership test: whether `target` is `region` or nested under\n * it. Used to confirm `_lastActiveRegion` still lives in the dock's tree\n * before docking into it (a collapsed split or a layout restore can retire a\n * region object).\n *\n * @param region - The subtree root to search.\n * @param target - The region to find.\n *\n * @returns `true` when `target` is within `region`.\n */\n private containsRegion(region: Component, target: Component): boolean {\n if (region === target) {\n return true;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child) && this.containsRegion(child, target)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Depth-first search for the first `Tab` region at or under `region`.\n *\n * @param region - The region to search from.\n *\n * @returns The first `Tab` region, or `null` when none exists.\n */\n private firstTabRegion(region: Component): Component | null {\n if (this.isTab(region)) {\n return region;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n const found = this.firstTabRegion(child);\n\n if (found) {\n return found;\n }\n }\n }\n\n return null;\n }\n\n /**\n * Installs a fresh `Tab` region as the dock's single `Fit` child and\n * re-homes the existing root into it, returning the new region. The safety\n * net for a tab-less tree (see {@link activeTabRegion}).\n *\n * @returns The fresh `Tab` region now holding the old root.\n */\n private wrapRootInTab(): Component {\n const region = this.newTabRegion();\n const oldRoot = this.getRootRegion();\n\n this.addComponent(region);\n region.moveComponent(oldRoot);\n\n return region;\n }\n\n /**\n * Builds an empty region: a `Container` carrying a fresh `Tab` manager.\n *\n * @returns The new `Tab` region.\n */\n private newTabRegion(): Component {\n return new Container({ layoutManager: new Tab({ reorderable: true, compact: true }) });\n }\n\n /**\n * Compiles a declarative layout spec into a region `Component`, registering\n * every leaf in the panel registry along the way. The only structural build\n * code unique to `Dock`; runtime restructuring belongs to `DockRegion`.\n *\n * @param spec - The layout spec to compile.\n *\n * @returns The compiled region.\n */\n private compileLayout(spec: DockLayoutSpec): Component {\n if (\"split\" in spec) {\n const region = new Container({ layoutManager: new Split({ orientation: spec.split }) });\n\n for (const child of spec.children) {\n region.addComponent(this.compileRegion(child));\n }\n\n return region;\n }\n\n if (\"tabs\" in spec) {\n return this.compileTabs(spec.tabs);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Compiles a child of a split: a leaf becomes its own single-tab stack so a\n * split pane is always a draggable region, never a bare leaf.\n *\n * @param spec - The child spec.\n *\n * @returns The compiled region.\n */\n private compileRegion(spec: DockLayoutSpec): Component {\n if (\"split\" in spec || \"tabs\" in spec) {\n return this.compileLayout(spec);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Builds a `Tab` region holding the given leaves, registering and stamping\n * each one.\n *\n * @param specs - The leaf panels to stack.\n *\n * @returns The `Tab` region.\n */\n private compileTabs(specs: DockPanelSpec[]): Component {\n const region = this.newTabRegion();\n\n for (const spec of specs) {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n region.addComponent(content, this.leafConstraints(spec));\n\n // A compiled panel starts tiled; seed the host ledger with null\n // so the first sweep's host diff is silent (no transition to\n // attach from). Construction therefore emits nothing.\n this._panelHost.set(spec.id, null);\n this._frameRegion.set(spec.id, region);\n }\n }\n\n return region;\n }\n\n /**\n * Schedules one coalesced re-wire sweep on the next animation frame. Repeated\n * calls within a frame collapse to a single sweep.\n */\n private scheduleSweep(): void {\n if (this._sweepScheduled) {\n return;\n }\n\n this._sweepScheduled = true;\n\n DOM.sink.requestAnimationFrame(() => {\n this._sweepScheduled = false;\n this.runSweep();\n });\n }\n\n /**\n * Runs the idempotent sweep: adopt every owned float window into a wired\n * region tree, wire the in-dock root and each float region, then tear down\n * the coordinators of regions that have vanished from the combined live tree.\n */\n private runSweep(): void {\n const root = this.getRootRegion();\n\n if (!root) {\n return;\n }\n\n const floatRegions = this.ownedFloatWindows()\n .map(win => this.adoptFloat(win))\n .filter((region): region is Component => region !== null);\n\n this.wireRegion(root);\n\n for (const region of floatRegions) {\n this.wireRegion(region);\n }\n\n this.subscribeFloatWindows();\n this.reconcileHosts(root);\n this.teardownVanished(root, floatRegions);\n this.reconcileEmptyState();\n }\n\n /**\n * Reconciles the two independent empty concerns each sweep:\n *\n * - **Placeholder / bar** track the *main region's* emptiness — whether the\n * root region holds no tiled panel. This is true both when every tab was\n * closed and when they were all torn off into floats, so the start page\n * shows in either case. Re-asserted idempotently every sweep (not just on a\n * transition) so it re-shows after a `setLayoutState` restore clears the\n * root region wholesale.\n * - **`\"emptychange\"`** tracks the *dock-wide* emptiness — {@link isEmpty},\n * `true` only when no panel exists anywhere (tiled or floated). Latched on\n * `_empty` and emitted exactly once per real transition; a no-op sweep is\n * silent. Called at the end of every sweep and reached from every close via\n * {@link scheduleSweep}, so the emit is once per settled transition.\n */\n private reconcileEmptyState(): void {\n if (this.mainRegionEmpty()) {\n this.showEmptyState();\n } else {\n this.hideEmptyState();\n }\n\n const empty = this.isEmpty();\n\n // First reconcile: sync the born state without an emit (being born empty\n // or populated is not a transition). A born-with-layout dock differs from\n // the true=seed latch and falls through to emit emptychange(false) once.\n if (!this._emptyReconciled) {\n this._emptyReconciled = true;\n\n if (empty === this._empty) {\n return;\n }\n }\n\n if (empty === this._empty) {\n return;\n }\n\n this._empty = empty;\n this.emit(\"emptychange\", { empty });\n }\n\n /**\n * Whether the main (tiled) region holds no panel — every tab closed, or all\n * torn off into floats. The placeholder is excluded so it never counts as\n * content. This drives the placeholder, distinct from {@link isEmpty} (which\n * counts floated panels as live).\n *\n * @returns `true` when the root region shows no tiled panel.\n */\n private mainRegionEmpty(): boolean {\n const region = this.getRootRegion();\n\n if (!region) {\n return false;\n }\n\n const placeholder = this.getEmptyContent();\n\n return region.getComponents().every(child => child === placeholder);\n }\n\n /**\n * Shows the empty-state chrome in the empty root region. With a placeholder\n * set, it is docked as a single non-closeable, `transient` (never-serialized)\n * tab; without one, the region's tab strip is hidden so an emptied dock is a\n * clean surface rather than a dangling empty strip. Idempotent — safe to call\n * on every sweep — and a no-op when the root region is not a `Tab`.\n */\n private showEmptyState(): void {\n const region = this.getRootRegion();\n const placeholder = this.getEmptyContent();\n\n if (!region) {\n return;\n }\n\n if (placeholder) {\n this.rootTab()?.setBarVisible(true);\n\n if (placeholder.getParentComponent() !== region) {\n region.moveComponent(placeholder, undefined, this.placeholderConstraints());\n }\n } else {\n this.rootTab()?.setBarVisible(false);\n }\n }\n\n /**\n * Removes the empty-state chrome: detaches the placeholder tab when present\n * and restores the root region's tab strip. Idempotent — safe to call on\n * every sweep and when nothing is shown.\n */\n private hideEmptyState(): void {\n const placeholder = this.getEmptyContent();\n const parent = placeholder?.getParentComponent();\n const manager = parent?.getLayoutManager();\n\n // Remove the placeholder from wherever it currently sits. A `Tab` host must\n // go through `closeTab` (not `removeComponent`): `Tab.doLayout` reconciles\n // only *added* children, so a bare `removeComponent` would orphan the strip\n // cell and leave a stale placeholder tab. `closeTab`'s `\"tabclose\"` is\n // ignored by the dock (the placeholder is not a registered frame) and its\n // drain-`\"empty\"` is absorbed by the root-region prune guard. `closeTab`\n // returns false when no cell exists yet (added but not laid out); then a\n // plain `removeComponent` suffices — there is no cell to orphan.\n if (placeholder) {\n const closed = manager instanceof Tab && manager.closeTab(placeholder);\n\n if (!closed) {\n parent?.removeComponent(placeholder);\n }\n }\n\n this.rootTab()?.setBarVisible(true);\n }\n\n /**\n * The root region's `Tab` manager, or `null` when the root region is absent or\n * carries a different manager (e.g. an externally-crafted `Split` root). An\n * empty dock's root is always a `Tab` — the prune path keeps a single empty\n * `Tab` region — so the placeholder always finds one.\n *\n * @returns The root `Tab` manager, or `null`.\n */\n private rootTab(): Tab | null {\n const manager = this.getRootRegion()?.getLayoutManager();\n\n return manager instanceof Tab ? manager : null;\n }\n\n /**\n * The layout constraints the placeholder tab is docked under: non-closeable\n * (the start page cannot be closed) and `transient` (shown as a tab but never\n * captured by serialization). The tab's label is the placeholder component's\n * own name.\n *\n * @returns The placeholder tab constraints.\n */\n private placeholderConstraints(): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n constraints.closeable = false;\n constraints.transient = true;\n // The placeholder is consumer-owned chrome that `hideEmptyState` closes\n // through `closeTab` and `showEmptyState` re-mounts — a close must not\n // destroy it.\n constraints.disposeOnClose = false;\n\n return constraints;\n }\n\n /**\n * Idempotently subscribes the lifecycle events of every float window that\n * currently hosts one of this dock's frames — both the adopted bare\n * `Window` mini-docks and the self-contained `TabWindow` tear-offs the sweep\n * does not adopt. A `TabWindow`'s internal `Tab` is never wired by\n * `wireRegion`, so its `\"activate\"` / `\"tabclose\"` / `\"detach\"` /\n * `\"dock\"` are subscribed here explicitly; both float kinds get the\n * window's `\"activate\"` / `\"close\"`. The tracked set stops a re-sweep\n * stacking duplicate listeners.\n */\n private subscribeFloatWindows(): void {\n for (const win of this.floatWindowsHoldingFrames()) {\n if (this._floatSubscribed.has(win)) {\n continue;\n }\n\n const onFloatActivate: () => void = (): void => { this.onFloatActivated(win); };\n const onFloatClose: () => void = (): void => { this.onFloatClosed(win); };\n\n win.on(\"activate\", onFloatActivate);\n win.on(\"close\", onFloatClose);\n\n if (win instanceof TabWindow) {\n const tab = win.getLayoutManager() as Tab;\n\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n }\n\n this._floatSubscribed.add(win);\n }\n\n this.pruneClosedFloatSubscriptions();\n }\n\n /**\n * Drops closed windows from the float-subscription tracking set so a future\n * window object never collides with a stale entry. The listeners themselves\n * die with the closed window, so only the set bookkeeping is needed.\n */\n private pruneClosedFloatSubscriptions(): void {\n const open = new Set<AbstractWindow>(AbstractWindow.getOpenWindows());\n\n for (const win of this._floatSubscribed) {\n if (!open.has(win)) {\n this._floatSubscribed.delete(win);\n }\n }\n }\n\n /**\n * Open float windows hosting one of this dock's frames, including the\n * self-contained `TabWindow` tear-offs (which `ownedFloatWindows` excludes\n * because the sweep does not adopt them), and excluding the window the dock\n * itself lives in. The subscription targets for the panel lifecycle.\n *\n * @returns The float windows holding this dock's frames.\n */\n private floatWindowsHoldingFrames(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Recomputes each registered frame's host (`null` when it sits under the\n * in-dock tiled tree, else the float window holding it) and region, and diffs\n * both against their ledgers, emitting the lifecycle events the change\n * implies: a first appearance (no host ledger entry — a fresh `addPanel` or a\n * restore) emits `\"attach\"` alone; a change from one host to another (a\n * tear-off, a re-dock by either drop path, a float-to-float move) emits\n * `\"detach\"`(old host) then `\"attach\"`(new host); a same-host change of\n * region (a relocation to a different region within one host) emits\n * `\"move\"`; an unchanged host and region is silent. This is the single\n * source of every `\"attach\"`/`\"detach\"`/`\"move\"`, so the events are\n * identical regardless of which DnD path landed the sweep.\n *\n * Only frames still registered and still cached are visited, so a panel whose\n * frame a close handler already evicted produces no phantom `\"detach\"`.\n *\n * @param root - The current root region.\n */\n private reconcileHosts(root: Component): void {\n for (const [id, frame] of this._frames) {\n if (!this._panels.has(id)) {\n continue;\n }\n\n const host = this.hostForFrame(frame, root);\n const had = this._panelHost.has(id);\n const prev = this._panelHost.get(id) ?? null;\n const region = this.regionForFrame(frame);\n const prevRegion = this._frameRegion.get(id) ?? null;\n\n if (!had) {\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (host !== prev) {\n this.emit(\"detach\", { id, content: frame, window: prev });\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (region && prevRegion && region !== prevRegion) {\n // Same host, different region: the panel relocated within its\n // host. The `region && prevRegion` guard keeps a frame transiently\n // out of any region (mid-teardown) silent rather than spurious.\n this.emit(\"move\", { id, content: frame, window: host });\n }\n\n this._panelHost.set(id, host);\n\n if (region) {\n this._frameRegion.set(id, region);\n }\n }\n }\n\n /**\n * The host a frame currently occupies: `null` when it sits under the tiled\n * tree (the main dock), otherwise the float window holding it. Reuses the\n * existing tiled test and float lookup so the reconcile and the\n * focus-payload construction derive the host the same way.\n *\n * @param frame - The identity frame to locate.\n * @param root - The current root region.\n *\n * @returns The host window, or `null` for the tiled tree.\n */\n private hostForFrame(frame: Component, root: Component): AbstractWindow | null {\n return this.isUnder(root, frame) ? null : this.floatForFrame(frame);\n }\n\n /**\n * Open windows whose content subtree holds one of this dock's identity frames\n * — the floats torn off from this dock — excluding the window the dock itself\n * lives in. Re-derived each sweep (never cached) so a closed float drops out\n * naturally, mirroring the derived-live root in {@link getRootRegion}.\n *\n * A {@link TabWindow} is excluded: a default tear-off opens one as a\n * self-contained floating tabbed window, re-dockable via its own tab DnD and\n * self-closing when emptied. It is never adopted into the dock's region tree,\n * so the sweep must leave it alone — only the Shift-torn bare {@link Window}\n * floats become adoptable mini-docks.\n *\n * @returns The owned float windows.\n */\n private ownedFloatWindows(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !(win instanceof TabWindow) &&\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Whether `node` lies within `win`'s subtree — walks `node`'s ancestor chain\n * looking for `win`. Used both to detect a float hosting a frame and to\n * exclude the dock's own host window (which contains the dock, hence every\n * still-docked frame).\n *\n * @param win - The candidate ancestor window.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `win` is an ancestor of `node`.\n */\n private windowContains(win: AbstractWindow, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === win) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * A window's first non-chrome child — its content panel — or `null` when the\n * window has none yet.\n *\n * @param win - The window to inspect.\n *\n * @returns The content component, or `null`.\n */\n private windowContent(win: AbstractWindow): Component | null {\n return win.getComponents().find(child => !win.isChromeComponent(child)) ?? null;\n }\n\n /**\n * Ensures a float window's content is a wired-able region tree and returns\n * that region. A freshly torn-off float holds its bare identity frame as the\n * window's content; this wraps it in a single fresh `Tab` region so it is a\n * proper region leaf with a draggable handle, turning the window into a\n * mini-dock. Idempotent: once the content is a region container (already\n * adopted, or restored as a tree) it is returned unchanged, so re-sweeps\n * after edge-splits inside the float do not re-wrap.\n *\n * @param win - The float window to adopt.\n *\n * @returns The float's content region, or `null` when the window has no content.\n */\n private adoptFloat(win: AbstractWindow): Component | null {\n const content = this.windowContent(win);\n\n if (!content) {\n return null;\n }\n\n if (this.isRegionContainer(content)) {\n return content;\n }\n\n const region = this.newTabRegion();\n\n // A fresh region carries no constraint, so the window's Border fills it as\n // an unplaced→CENTER child — the same way the bare frame filled before.\n win.moveComponent(region);\n region.moveComponent(content);\n\n return region;\n }\n\n /**\n * Idempotently wires a region and recurses into its child regions: makes a\n * `Tab` region reorderable and prunes it when its last tab leaves, and gives\n * every region a `DockRegion` so it accepts edge/centre drops and notifies\n * the dock after a drop mutates the tree.\n *\n * @param region - The region to wire.\n */\n private wireRegion(region: Component): void {\n let wiring = this._wiring.get(region);\n\n if (!wiring) {\n wiring = { dockRegion: new DockRegion(region, this.requestSweep), tabWired: false };\n\n this._wiring.set(region, wiring);\n }\n\n const manager = region.getLayoutManager();\n\n if (this.isTab(region) && !wiring.tabWired) {\n const tab: Tab = manager as Tab;\n\n tab.setReorderable(true);\n // Per-region prune; the named const carries the region the shared\n // handler set otherwise could not (ARCHITECTURE: a listener is a named\n // reference, never an inline arrow).\n const onEmpty: () => void = (): void => { this.pruneRegion(region); };\n\n tab.on(\"empty\", onEmpty);\n // The lifecycle handlers are shared bound methods: their payloads (the\n // closed/activated content, the torn-off window) carry the identity\n // they need, so no per-region capture is required.\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n\n wiring.tabWired = true;\n }\n\n for (const child of region.getComponents()) {\n // A lazy panel's identity frame carries a Tab manager, which would\n // otherwise make this sweep wire the panel itself as a drop-taking,\n // prunable region — so its inner strip draining would prune the\n // frame out of its parent and leave a phantom tab behind.\n if (this.isRegionContainer(child) && this._frames.get(child.getId()) !== child) {\n this.wireRegion(child);\n }\n }\n }\n\n /**\n * Removes a `Tab` region once its last tab has left, then collapses a `Split`\n * that the removal leaves with a single pane, and schedules a sweep to tear\n * down the removed region's coordinator. Wired to every `Tab` region's\n * `\"empty\"` event so regions the dock itself built (the `compileLayout` /\n * `restoreLayout` tree) are pruned, not just the stacks `DockRegion` mints.\n * No-op when the region was already detached (a `DockRegion`-created stack\n * pruned itself first) or still holds a tab.\n *\n * @param region - The region whose `Tab` fired `\"empty\"`.\n */\n private pruneRegion(region: Component): void {\n const parent = region.getParentComponent();\n\n if (!parent || region.getComponents().length > 0) {\n return;\n }\n\n // Keep an emptied root region (its parent is the dock) as the dock's\n // add/drop target — removing it would leave the dock with no region and\n // crash the next addPanel.\n if (parent === this) {\n return;\n }\n\n parent.removeComponent(region);\n this.collapseSinglePaneSplit(parent);\n this.closeFloatIfEmpty(parent);\n this.scheduleSweep();\n }\n\n /**\n * Closes a float window whose mini-dock just emptied. When a region was the\n * direct content of a float window and pruning it leaves the window with no\n * content, the float has nothing left to host — close it, matching the\n * auto-close a strip-mode tear-off window performs when its last tab leaves.\n * A no-op when `container` is an in-dock region (its parent is the dock or a\n * `Split`, never a window) or the window still holds content.\n *\n * @param container - The container the pruned region was removed from.\n */\n private closeFloatIfEmpty(container: Component): void {\n if (!AbstractWindow.getOpenWindows().includes(container as AbstractWindow)) {\n return;\n }\n\n if (!this.windowContent(container as AbstractWindow)) {\n (container as AbstractWindow).requestClose();\n }\n }\n\n /**\n * Collapses a `Split` left with a single child: the lone child is hoisted\n * into the grandparent at the `Split`'s slot (via `moveComponent`) and the\n * emptied `Split` removed. A no-op unless `container` is a single-child\n * `Split` with a grandparent (a root single-pane `Split` renders its child\n * full-bleed). The swap keeps the grandparent's child count unchanged, so the\n * collapse does not cascade.\n *\n * @param container - The container that just lost a child.\n */\n private collapseSinglePaneSplit(container: Component): void {\n if (this.regionKind(container) !== \"Split\") {\n return;\n }\n\n const children = container.getComponents();\n\n if (children.length !== 1) {\n return;\n }\n\n const grandparent = container.getParentComponent();\n\n if (!grandparent) {\n return;\n }\n\n const index = grandparent.getComponents().indexOf(container);\n\n // Carry the collapsing Split's stored size onto the hoisted child so the\n // slot keeps the user's dragged ratio (when the grandparent is a Split).\n if (this.regionKind(grandparent) === \"Split\") {\n (grandparent.getLayoutManager() as Split).transferPaneSize(container, children[0]);\n }\n\n grandparent.moveComponent(children[0], index);\n grandparent.removeComponent(container);\n }\n\n /**\n * Destroys the wiring of every tracked region no longer reachable from the\n * combined live tree (the in-dock root plus every owned float's region tree),\n * releasing the drop targets a removed region's coordinator held. Seeding the\n * reachable set from the float regions too is what keeps a float's drop\n * targets alive across sweeps driven by unrelated in-dock moves.\n *\n * @param root - The current root region.\n * @param floatRegions - The adopted content region of each owned float window.\n */\n private teardownVanished(root: Component, floatRegions: Component[]): void {\n const reachable = new Set<Component>();\n\n this.collectRegions(root, reachable);\n\n for (const region of floatRegions) {\n this.collectRegions(region, reachable);\n }\n\n for (const [region, wiring] of this._wiring) {\n if (!reachable.has(region)) {\n wiring.dockRegion.destroy();\n this._wiring.delete(region);\n }\n }\n }\n\n /**\n * Collects every region container at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The set to populate.\n */\n private collectRegions(region: Component, into: Set<Component>): void {\n into.add(region);\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectRegions(child, into);\n }\n }\n }\n\n /**\n * Whether a component is a region container — a `Container` carrying a `Split`\n * or `Tab` manager. Discriminates on the stripped runtime class name (no\n * `instanceof`, avoiding an import cycle), matching how serialization keys\n * its node kinds.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Split`/`Tab` region.\n */\n private isRegionContainer(component: Component): boolean {\n const kind = this.regionKind(component);\n\n return kind === \"Split\" || kind === \"Tab\";\n }\n\n /**\n * Whether a component is a `Tab` region.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Tab` region.\n */\n private isTab(component: Component): boolean {\n return this.regionKind(component) === \"Tab\";\n }\n\n /**\n * The stripped runtime class name of a component's layout manager, or the\n * empty string when it has none (a leaf content component).\n *\n * @param component - The component to inspect.\n *\n * @returns The manager's class name without its `_` export-alias prefix.\n */\n private regionKind(component: Component): string {\n const manager = component.getLayoutManager() as (Tab | Split | undefined);\n\n return manager ? manager.getClassName().replace(/^_/, \"\") : \"\";\n }\n\n // ----- panel lifecycle -----\n\n /**\n * `\"tabclose\"` handler for every wired `Tab` (tiled or float): a registered\n * panel was genuinely closed. Emits `\"close\"`, evicts the cached frame so a\n * re-`addPanel` rebuilds it via the lazy factory (keeping the `_panels`\n * registration), and — when the closed panel was the dock-wide focused one —\n * recomputes focus once the source `Tab` has re-selected a survivor.\n *\n * @param content - The closed tab's content (a Dock identity frame).\n */\n private onPanelClosed = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n const region = this._frameRegion.get(id) ?? null;\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content, window: null });\n\n // Route the close through the one reconcile site so \"emptychange\" fires\n // even when no structural prune scheduled a sweep. The latch-diff makes a\n // second reconcile in the same settled state a no-op, so this cannot\n // double-fire alongside pruneRegion's own scheduleSweep.\n this.scheduleSweep();\n\n if (this._focusedPanelId === id) {\n this.scheduleFocusRecompute(region);\n }\n };\n\n /**\n * Closes a lazy panel whose content factory rejected, then reports the\n * failure as this dock's `\"exception\"`. The panel's own `\"close\"` event\n * fires first, from the shared close path.\n *\n * The panel stays registered, so re-adding the same id rebuilds its frame\n * and runs the factory again — that is the retry path.\n *\n * @param id - The id of the panel whose factory rejected.\n * @param error - The value the factory's promise rejected with.\n */\n private failPanel(id: string, error: unknown): void {\n // The panel was closed while its factory was still in flight: the close\n // path already evicted its frame, so there is nothing left to tear down\n // and nobody left to report to. Closing a docked panel closes the tab in\n // the *outer* region and never touches the frame's own entry, so the\n // inner staleness check cannot see this — the registry is what knows.\n if (!this._frames.has(id)) {\n return;\n }\n\n // A frame that is registered but sits in no Tab region cannot be closed\n // through the shared path; evict it directly so a re-add rebuilds it.\n if (!this.removePanel(id)) {\n this._frames.delete(id);\n }\n\n this.emit(\"exception\", { id, error });\n }\n\n /**\n * Re-points a lazy panel's busy state from its hidden inner strip onto the tab\n * that actually shows it: the panel's tab in whichever region hosts its frame.\n * A no-op while the frame sits in no `Tab` region — registered but never\n * docked, torn off into a float, or mid-teardown.\n *\n * @param id - The id of the panel whose content started or finished loading.\n * @param busy - True while the panel's content factory is still in flight.\n */\n private setFrameBusy(id: string, busy: boolean): void {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return;\n }\n\n (region.getLayoutManager() as Tab).setTabBusy(frame, busy);\n }\n\n /**\n * `\"activate\"` handler for every wired `Tab`: the active tab changed via a\n * click or `setActiveTabIndex`. Emits `\"focus\"` for the now-active panel,\n * gated on a genuine focused-panel change.\n *\n * @param content - The now-active tab's content (a Dock identity frame).\n */\n private onPanelFocused = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n // Remember the focused panel's region so the next add docks beside the\n // tab-bar the user just worked in (see activeTabRegion).\n const region = this._frameRegion.get(id);\n if (region) {\n this._lastActiveRegion = region;\n }\n\n this.setFocus(id);\n };\n\n /**\n * `\"detach\"` handler for every wired `Tab`: a tab was torn off into a new\n * float window. Schedules a sweep so the new float is wired (and any\n * Shift-torn bare `Window` adopted); the sweep's host-diff reconcile observes\n * the torn-off frame's tiled -> float transition and emits the\n * `\"detach\"`(tiled) + `\"attach\"`(float) pair itself, so this handler emits\n * nothing directly — the reconcile is the single source of those events.\n *\n * @param _window - The float window the tab was torn off into (unused; the\n * reconcile re-derives every frame's host).\n */\n private onPanelDetached = (_window: AbstractWindow): void => {\n this.scheduleSweep();\n };\n\n /**\n * Window `\"activate\"` handler for an owned float: the float became the active\n * layer. Emits `\"focus\"` for the float's active panel, gated on a genuine\n * focused-panel change.\n *\n * @param window - The float window that was activated.\n */\n private onFloatActivated(window: AbstractWindow): void {\n const frame = this.activeFrameInFloat(window);\n\n if (frame) {\n this.setFocus(frame.getId());\n }\n }\n\n /**\n * Window `\"close\"` handler for an owned float: the float's chrome ✕ closed\n * it. Emits one `\"close\"` per registered frame the float held (a bare-`Window`\n * mini-dock can hold several) — read before the window tears down — and\n * recomputes focus when a closed frame was the focused panel.\n *\n * @param window - The float window being closed.\n */\n private onFloatClosed(window: AbstractWindow): void {\n let focusLost = false;\n\n for (const frame of this.framesInWindow(window)) {\n const id = frame.getId();\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content: frame, window: null });\n\n if (this._focusedPanelId === id) {\n focusLost = true;\n }\n }\n\n // Route the close through the reconcile so a last-float close flips the\n // empty latch and fires \"emptychange\" (a float close schedules no\n // structural sweep of its own).\n this.scheduleSweep();\n\n if (focusLost) {\n this.scheduleFocusRecompute(null);\n }\n }\n\n /**\n * Sets the dock-wide focused panel and emits `\"focus\"` only on a genuine\n * change, so re-activating the already-focused panel is silent. A `null` id\n * clears focus and emits `focus(null)`.\n *\n * @param id - The newly-focused panel id, or `null` when none is focused.\n */\n private setFocus(id: string | null): void {\n if (id === this._focusedPanelId) {\n return;\n }\n\n this._focusedPanelId = id;\n\n if (id === null) {\n this.emit(\"focus\", null);\n\n return;\n }\n\n const frame = this._frames.get(id);\n\n if (frame) {\n const host = this.hostForFrame(frame, this.getRootRegion());\n\n this.emit(\"focus\", { id, content: frame, window: host });\n }\n }\n\n /**\n * Schedules a deferred focus recompute after a close. The source `Tab`\n * re-selects a survivor (visually, with no event) *after* its `\"tabclose\"`\n * fires, so the new active tab is only readable on the next frame.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private scheduleFocusRecompute(region: Component | null): void {\n DOM.sink.requestAnimationFrame(() => this.recomputeFocusAfterClose(region));\n }\n\n /**\n * Recomputes the dock-wide focus after the focused panel was closed: when\n * panels remain in `region`, focus the survivor the region re-selected; when\n * no panel remains anywhere, emit `focus(null)`.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private recomputeFocusAfterClose(region: Component | null): void {\n if (this._frames.size === 0) {\n this.setFocus(null);\n\n return;\n }\n\n if (!region || !this.isTab(region) || region.getComponents().length === 0) {\n this.setFocus(null);\n\n return;\n }\n\n const frame = (region.getLayoutManager() as Tab).getActiveContent();\n\n this.setFocus(frame ? frame.getId() : null);\n }\n\n /**\n * Activates the tab hosting `id` and raises its host float when it lives in\n * one, so a buried floated panel surfaces. A successful activation drives the\n * host `Tab`'s active-tab change and the float raise, each of which emits a\n * `\"focus\"`.\n *\n * @param id - The panel id to focus.\n *\n * @returns `true` when the panel was found and activated, `false` for an\n * unknown id or one in no `Tab` region (registered but never docked).\n */\n focusPanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n const index = (region.getLayoutManager() as Tab).indexOfContent(frame);\n\n if (index < 0) {\n return false;\n }\n\n this.floatForFrame(frame)?.bringToFront();\n (region.getLayoutManager() as Tab).setActiveTabIndex(index);\n\n return true;\n }\n\n /**\n * Closes the panel `id` through the same user-close path a tab ✕ takes, so it\n * emits exactly one `\"close\"` through the shared `\"tabclose\"` subscription.\n *\n * @param id - The panel id to close.\n *\n * @returns `true` when the panel was found and closed, `false` for an unknown\n * id or one in no `Tab` region.\n */\n removePanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n return (region.getLayoutManager() as Tab).closeTab(frame);\n }\n\n /**\n * The `Tab` region currently hosting `frame` — searched across the in-dock\n * tiled tree and every float window's region tree — or `null` when no `Tab`\n * region holds it (registered but never docked, or mid-teardown).\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host `Tab` region, or `null`.\n */\n private regionForFrame(frame: Component): Component | null {\n for (const region of this.allTabRegions()) {\n if ((region.getLayoutManager() as Tab).indexOfContent(frame) >= 0) {\n return region;\n }\n }\n\n return null;\n }\n\n /**\n * The float window currently hosting `frame`, or `null` when it lives in the\n * in-dock tiled tree (or nowhere). Used to raise a buried float on focus.\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host float window, or `null`.\n */\n private floatForFrame(frame: Component): AbstractWindow | null {\n return this.floatWindowsHoldingFrames().find(win => this.windowContains(win, frame)) ?? null;\n }\n\n /**\n * Every `Tab` region across the combined live tree: the in-dock root plus\n * each float window (an adopted bare-`Window` mini-dock's region tree, and a\n * `TabWindow` whose own layout manager is the `Tab`).\n *\n * @returns The live `Tab` regions.\n */\n private allTabRegions(): Component[] {\n const regions: Component[] = [];\n const root = this.getRootRegion();\n\n if (root) {\n this.collectTabRegions(root, regions);\n }\n\n for (const win of this.floatWindowsHoldingFrames()) {\n if (win instanceof TabWindow) {\n regions.push(win as unknown as Component);\n\n continue;\n }\n\n const content = this.windowContent(win);\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n }\n\n return regions;\n }\n\n /**\n * Collects every `Tab` region at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The array to populate.\n */\n private collectTabRegions(region: Component, into: Component[]): void {\n if (this.isTab(region)) {\n into.push(region);\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectTabRegions(child, into);\n }\n }\n }\n\n /**\n * The registered frames of this dock that lie within `window`'s subtree — the\n * panels a float holds. Read at float-close time to fan out one `\"close\"` per\n * frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The registered frames inside the window.\n */\n private framesInWindow(window: AbstractWindow): Component[] {\n return [...this._frames.values()].filter(frame => this.windowContains(window, frame));\n }\n\n /**\n * The active panel frame inside a float window: a `TabWindow`'s own active\n * tab, or the active tab of the first `Tab` region inside a bare-`Window`\n * mini-dock. `null` when none resolves to a registered frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The active registered frame, or `null`.\n */\n private activeFrameInFloat(window: AbstractWindow): Component | null {\n let tab: Tab | null = null;\n\n if (window instanceof TabWindow) {\n tab = window.getLayoutManager() as Tab;\n } else {\n const content = this.windowContent(window);\n const regions: Component[] = [];\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n\n tab = regions.length > 0 ? (regions[0].getLayoutManager() as Tab) : null;\n }\n\n const frame = tab ? tab.getActiveContent() : null;\n\n return frame && this._frames.get(frame.getId()) === frame ? frame : null;\n }\n\n /**\n * Whether `node` lies at or under `ancestor`'s component subtree.\n *\n * @param ancestor - The candidate ancestor component.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `ancestor` is `node` or one of its ancestors.\n */\n private isUnder(ancestor: Component, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === ancestor) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Registers a listener for a panel-lifecycle event. The `\"attach\"`,\n * `\"detach\"`, `\"move\"`, and `\"close\"` events always carry a\n * {@link DockPanelEvent}; `\"focus\"` carries a `DockPanelEvent` or `null` when\n * nothing is focused. The payload's `window` field names the host: `null` for\n * the tiled tree, otherwise the float window the panel entered (`\"attach\"`),\n * left (`\"detach\"`), or moved within (`\"move\"`, same host before and after);\n * it is always `null` for `\"close\"`.\n *\n * @param event - `\"attach\"` / `\"detach\"` / `\"move\"` / `\"close\"`.\n * @param listener - Invoked with the affected panel.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Registers a listener for the `\"focus\"` event, which fires when the\n * dock-wide active panel changes, carrying the now-focused panel or `null`\n * when nothing is focused (e.g. the last panel closed).\n *\n * @param event - The `\"focus\"` event.\n * @param listener - Invoked with the now-focused panel, or `null`.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Registers a listener for the `\"emptychange\"` event, which fires once each\n * time the dock transitions between empty (no live panel anywhere) and\n * populated, carrying `{ empty }`.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - Invoked with the new emptiness state.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Registers a listener for the `\"exception\"` event, which fires when a lazy\n * panel's content factory rejected. The panel has already been closed and\n * its `\"close\"` event already emitted, so a listener must not call\n * {@link Dock.removePanel} for that id. The panel stays registered, so\n * re-adding the same id rebuilds its frame and retries the factory.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - Invoked with the failed panel's id and the rejection value.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n on(event: DockEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Removes a previously registered `\"focus\"` listener.\n *\n * @param event - The `\"focus\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Removes a previously registered `\"emptychange\"` listener.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Removes a previously registered `\"exception\"` listener.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - The exact listener reference passed to `on`.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n off(event: DockEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event` with `payload`, in\n * registration order.\n *\n * @param event - The event to emit.\n * @param payload - The lifecycle payload (`null` only for `\"focus\"`).\n */\n protected emit(event: \"attach\" | \"detach\" | \"move\" | \"close\", payload: DockPanelEvent): void;\n protected emit(event: \"focus\", payload: DockPanelEvent | null): void;\n protected emit(event: \"emptychange\", payload: DockEmptyEvent): void;\n protected emit(event: \"exception\", payload: DockExceptionEvent): void;\n protected emit(event: DockEvent, payload: DockPanelEvent | DockEmptyEvent | DockExceptionEvent | null): void {\n this._listeners.fire(event, payload);\n }\n\n /**\n * Disposes the empty-state drop overlay before the base destructor runs.\n * `attachTo` raw-appends the overlay onto this dock's element rather than\n * registering it as a child, so the inherited child recursion never\n * reaches it; the `detach()` calls in the drop-target callbacks are\n * mid-drag hides of a still-live overlay, not teardown.\n */\n protected destructor(): void {\n this._emptyDropOverlay.dispose();\n\n // Each wired region owns a DockRegion holding its own drop-zone\n // overlay. `destroy()` is otherwise reached only by the\n // unreachable-region sweep, so without this every region a drag ever\n // hovered would keep its overlay's rules when the dock goes away.\n for (const wiring of this._wiring.values()) {\n wiring.dockRegion.destroy();\n }\n\n this._wiring.clear();\n\n super.destructor();\n }\n}\n\nconst DockCallable = callable(Dock);\ntype DockCallable = Dock;\nexport {\n Dock as _Dock,\n DockCallable as Dock,\n};\n"],"mappings":"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"}
|