@jimka/typescript-ui 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/THIRD-PARTY-NOTICES.md +7 -3
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js → AbstractBooleanInput-C3O4kv7_.js} +2 -2
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js.map → AbstractBooleanInput-C3O4kv7_.js.map} +1 -1
- package/dist/lib/{AbstractInput-CfjW_HM2.js → AbstractInput-DFWniHqq.js} +2 -2
- package/dist/lib/{AbstractInput-CfjW_HM2.js.map → AbstractInput-DFWniHqq.js.map} +1 -1
- package/dist/lib/AnchorConstraints-CSUcj8Wx.js +2 -0
- package/dist/lib/AnchorConstraints-CSUcj8Wx.js.map +1 -0
- package/dist/lib/AnimatedDropdown-e9urCXK1.js +2 -0
- package/dist/lib/AnimatedDropdown-e9urCXK1.js.map +1 -0
- package/dist/lib/Border-Dq6dXkmS.js +2 -0
- package/dist/lib/Border-Dq6dXkmS.js.map +1 -0
- package/dist/lib/Button-B36rBMKC.js +2 -0
- package/dist/lib/Button-B36rBMKC.js.map +1 -0
- package/dist/lib/{Card-AKIUAWYS.js → Card-P8iUH2-C.js} +2 -2
- package/dist/lib/{Card-AKIUAWYS.js.map → Card-P8iUH2-C.js.map} +1 -1
- package/dist/lib/ComboBox-BjAhXXkY.js +2 -0
- package/dist/lib/ComboBox-BjAhXXkY.js.map +1 -0
- package/dist/lib/Component-CHTzJxEf.js +2 -0
- package/dist/lib/Component-CHTzJxEf.js.map +1 -0
- package/dist/lib/Container-xUbHk6l_.js +2 -0
- package/dist/lib/{Container-3KsvsJ6S.js.map → Container-xUbHk6l_.js.map} +1 -1
- package/dist/lib/DOM-BeR95tE8.js +2 -0
- package/dist/lib/DOM-BeR95tE8.js.map +1 -0
- package/dist/lib/{DragManager-c6M_mE3e.js → DragManager-T5Oljd0F.js} +2 -2
- package/dist/lib/{DragManager-tPMnbjeO.js.map → DragManager-T5Oljd0F.js.map} +1 -1
- package/dist/lib/FieldDecorator-BaTGfKiW.js +2 -0
- package/dist/lib/{FieldDecorator-Bl0h9UEF.js.map → FieldDecorator-BaTGfKiW.js.map} +1 -1
- package/dist/lib/{Fit-CFIr4P9R.js → Fit-E-EIZv1k.js} +2 -2
- package/dist/lib/{Fit-CUS1JXy3.js.map → Fit-E-EIZv1k.js.map} +1 -1
- package/dist/lib/Glyph-CnNslL95.js +2 -0
- package/dist/lib/Glyph-CnNslL95.js.map +1 -0
- package/dist/lib/{Grid-Czux--Mr.js → Grid-BXS2T8MT.js} +2 -2
- package/dist/lib/{Grid-D8luwRPI.js.map → Grid-BXS2T8MT.js.map} +1 -1
- package/dist/lib/GridConstraints-D_kbd-h2.js +2 -0
- package/dist/lib/GridConstraints-D_kbd-h2.js.map +1 -0
- package/dist/lib/HBox-Dm3xgsDE.js +2 -0
- package/dist/lib/{HBox-BWMf9h6r.js.map → HBox-Dm3xgsDE.js.map} +1 -1
- package/dist/lib/{Header-CQjomjo0.js → Header-CqFc2OUx.js} +2 -2
- package/dist/lib/{Header-CQjomjo0.js.map → Header-CqFc2OUx.js.map} +1 -1
- package/dist/lib/{IconText-BXJK0MOq.js → IconText-CTM2r0Tv.js} +2 -2
- package/dist/lib/{IconText-BXJK0MOq.js.map → IconText-CTM2r0Tv.js.map} +1 -1
- package/dist/lib/LayoutConstraints-DkctJZXB.js +2 -0
- package/dist/lib/LayoutConstraints-DkctJZXB.js.map +1 -0
- package/dist/lib/LayoutSerialization-D1OHmxSB.js +2 -0
- package/dist/lib/LayoutSerialization-D1OHmxSB.js.map +1 -0
- package/dist/lib/LayoutSizes-CvMmMgHJ.js +2 -0
- package/dist/lib/LayoutSizes-CvMmMgHJ.js.map +1 -0
- package/dist/lib/List-DHeOPAK2.js +2 -0
- package/dist/lib/List-DHeOPAK2.js.map +1 -0
- package/dist/lib/MemoryStore-DT6iWWco.js +2 -0
- package/dist/lib/MemoryStore-DT6iWWco.js.map +1 -0
- package/dist/lib/Menu-B6ippw-p.js +2 -0
- package/dist/lib/Menu-B6ippw-p.js.map +1 -0
- package/dist/lib/{MenuButton-D15cSbMf.js → MenuButton-Bjm1OyB-.js} +2 -2
- package/dist/lib/{MenuButton-D15cSbMf.js.map → MenuButton-Bjm1OyB-.js.map} +1 -1
- package/dist/lib/Panel-D9lTLGqP.js +2 -0
- package/dist/lib/Panel-D9lTLGqP.js.map +1 -0
- package/dist/lib/Position-DRSlWn4Z.js +2 -0
- package/dist/lib/Position-DRSlWn4Z.js.map +1 -0
- package/dist/lib/ProgressSpinner-glv-29eK.js +2 -0
- package/dist/lib/ProgressSpinner-glv-29eK.js.map +1 -0
- package/dist/lib/RadioButton-R4S3c9WD.js +2 -0
- package/dist/lib/RadioButton-R4S3c9WD.js.map +1 -0
- package/dist/lib/{RovingTabIndex-CFIzIjjU.js → RovingTabIndex-Dgt01LOE.js} +2 -2
- package/dist/lib/{RovingTabIndex-D-zOuptw.js.map → RovingTabIndex-Dgt01LOE.js.map} +1 -1
- package/dist/lib/Scrollbar-CiVV3axj.js +2 -0
- package/dist/lib/Scrollbar-CiVV3axj.js.map +1 -0
- package/dist/lib/Slider-_JVvuQ8M.js +2 -0
- package/dist/lib/Slider-_JVvuQ8M.js.map +1 -0
- package/dist/lib/{Spacer-BVQncB5B.js → Spacer-B57i2-_G.js} +2 -2
- package/dist/lib/{Spacer-BVQncB5B.js.map → Spacer-B57i2-_G.js.map} +1 -1
- package/dist/lib/{TabButton-DqYDfN7P.js → TabButton-DEypdlZf.js} +2 -2
- package/dist/lib/{TabButton-DqYDfN7P.js.map → TabButton-DEypdlZf.js.map} +1 -1
- package/dist/lib/Text-Bni4icFY.js +2 -0
- package/dist/lib/Text-Bni4icFY.js.map +1 -0
- package/dist/lib/Tooltip-BUy6uFKN.js +3 -0
- package/dist/lib/Tooltip-BUy6uFKN.js.map +1 -0
- package/dist/lib/VBox-Dr7Ge9M6.js +2 -0
- package/dist/lib/VBox-Dr7Ge9M6.js.map +1 -0
- package/dist/lib/{VirtualScroller-BKMvTisA.js → VirtualScroller-BPy6KmSX.js} +2 -2
- package/dist/lib/{VirtualScroller-BKMvTisA.js.map → VirtualScroller-BPy6KmSX.js.map} +1 -1
- package/dist/lib/component/button.es.js +1 -1
- package/dist/lib/component/button.es.js.map +1 -1
- package/dist/lib/component/chart.es.js +1 -1
- package/dist/lib/component/chart.es.js.map +1 -1
- package/dist/lib/component/container.es.js +1 -1
- package/dist/lib/component/container.es.js.map +1 -1
- package/dist/lib/component/diagram.es.js +1 -1
- package/dist/lib/component/diagram.es.js.map +1 -1
- package/dist/lib/component/display.es.js +1 -1
- package/dist/lib/component/display.es.js.map +1 -1
- package/dist/lib/component/editor.es.js +3 -3
- 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/menubar.es.js +1 -1
- package/dist/lib/component/menubar.es.js.map +1 -1
- package/dist/lib/component/table.es.js +2 -2
- package/dist/lib/component/table.es.js.map +1 -1
- package/dist/lib/component/tree.es.js +1 -1
- package/dist/lib/component/tree.es.js.map +1 -1
- package/dist/lib/core.es.js +1 -1
- package/dist/lib/core.es.js.map +1 -1
- package/dist/lib/data.es.js +1 -1
- package/dist/lib/data.es.js.map +1 -1
- 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.es.js +1 -1
- package/dist/lib/router.es.js +1 -1
- package/dist/lib/router.es.js.map +1 -1
- package/dist/lib/{VirtualRowView-C18j90n5.js → selectionsEqual-2PJrcimg.js} +2 -2
- package/dist/lib/{VirtualRowView-C18j90n5.js.map → selectionsEqual-2PJrcimg.js.map} +1 -1
- package/dist/lib/types/component/button/Button.d.ts +4 -3
- package/dist/lib/types/component/chart/AbstractChart.d.ts +1 -1
- package/dist/lib/types/component/chart/ChartLegend.d.ts +1 -1
- package/dist/lib/types/component/container/DialogBackdrop.d.ts +2 -1
- package/dist/lib/types/component/container/MenuItem.d.ts +1 -1
- package/dist/lib/types/component/container/ScrollStrip.d.ts +1 -0
- package/dist/lib/types/component/container/SplitGutter.d.ts +3 -2
- package/dist/lib/types/component/container/TabBar.d.ts +1 -2
- package/dist/lib/types/component/container/TabPanel.d.ts +6 -3
- package/dist/lib/types/component/container/WindowBorder.d.ts +2 -1
- package/dist/lib/types/component/container/WindowHeader.d.ts +2 -1
- package/dist/lib/types/component/diagram/DiagramEdgeLayer.d.ts +14 -1
- package/dist/lib/types/component/diagram/DiagramModel.d.ts +1 -0
- package/dist/lib/types/component/diagram/DiagramNode.d.ts +4 -0
- package/dist/lib/types/component/diagram/DiagramView.d.ts +76 -5
- package/dist/lib/types/component/diagram/ElkLayoutEngine.d.ts +14 -1
- package/dist/lib/types/component/diagram/index.d.ts +2 -2
- package/dist/lib/types/component/display/Glyph.d.ts +2 -1
- package/dist/lib/types/component/display/Markdown.d.ts +1 -1
- package/dist/lib/types/component/display/PaginationBar.d.ts +1 -1
- package/dist/lib/types/component/display/SpinnerWrap.d.ts +2 -0
- package/dist/lib/types/component/display/Video.d.ts +1 -1
- package/dist/lib/types/component/display/VideoPlayer.d.ts +1 -1
- package/dist/lib/types/component/editor/CodeEditor.d.ts +5 -3
- package/dist/lib/types/component/editor/MarkdownEditor.d.ts +1 -1
- package/dist/lib/types/component/input/Checkbox.d.ts +2 -1
- package/dist/lib/types/component/input/Link.d.ts +1 -1
- package/dist/lib/types/component/input/RadioButton.d.ts +2 -1
- package/dist/lib/types/component/input/Slider.d.ts +2 -1
- package/dist/lib/types/component/input/Text.d.ts +4 -4
- package/dist/lib/types/component/input/TextInput.d.ts +3 -2
- package/dist/lib/types/component/list/AbstractSelectableList.d.ts +2 -1
- package/dist/lib/types/component/menubar/MenuBarButton.d.ts +1 -1
- package/dist/lib/types/component/shared/selectionsEqual.d.ts +1 -0
- 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/Header.d.ts +1 -0
- package/dist/lib/types/component/table/Table.d.ts +20 -0
- package/dist/lib/types/component/table/cell/Header.d.ts +1 -0
- package/dist/lib/types/component/table/index.d.ts +1 -1
- package/dist/lib/types/component/tree/Tree.d.ts +1 -0
- package/dist/lib/types/core/AnimatedDropdown.d.ts +6 -2
- package/dist/lib/types/core/Animation.d.ts +9 -6
- package/dist/lib/types/core/Aria.d.ts +0 -2
- package/dist/lib/types/core/Body.d.ts +1 -0
- package/dist/lib/types/core/ClassStyleRules.d.ts +17 -0
- package/dist/lib/types/core/Component.d.ts +26 -13
- package/dist/lib/types/core/ComponentDefaults.d.ts +1 -0
- package/dist/lib/types/core/DOM.d.ts +19 -2
- package/dist/lib/types/core/ElementAttributes.d.ts +15 -0
- package/dist/lib/types/core/Event.d.ts +12 -6
- package/dist/lib/types/core/PendingTransitions.d.ts +4 -0
- package/dist/lib/types/core/StyleTarget.d.ts +1 -0
- package/dist/lib/types/core/Util.d.ts +2 -0
- package/dist/lib/types/core/index.d.ts +3 -2
- package/dist/lib/types/layout/Accordion.d.ts +2 -0
- package/dist/lib/types/layout/Border.d.ts +2 -0
- package/dist/lib/types/layout/CollapseSupport.d.ts +5 -1
- package/dist/lib/types/layout/LayoutConstraints.d.ts +1 -0
- package/dist/lib/types/layout/LayoutManager.d.ts +2 -0
- package/dist/lib/types/layout/Split.d.ts +1 -0
- package/dist/lib/types/layout/Tab.d.ts +13 -3
- package/dist/lib/types/layout/Table.d.ts +1 -0
- package/dist/lib/types/overlay/AbstractWindow.d.ts +10 -2
- package/dist/lib/types/overlay/Dialog.d.ts +6 -0
- package/dist/lib/types/overlay/Dock.d.ts +12 -2
- package/dist/lib/types/overlay/Drawer.d.ts +5 -0
- package/dist/lib/types/overlay/Menu.d.ts +3 -1
- package/dist/lib/types/overlay/Notification.d.ts +3 -0
- package/dist/lib/types/overlay/Popover.d.ts +3 -1
- package/dist/lib/types/overlay/Rail.d.ts +7 -0
- package/dist/lib/types/overlay/Tooltip.d.ts +3 -0
- package/dist/lib/types/overlay/index.d.ts +1 -1
- package/dist/lib/types/router/RoutePattern.d.ts +7 -0
- package/dist/lib/types/router/Router.d.ts +13 -2
- package/dist/lib/types/router/index.d.ts +1 -1
- package/dist/lib/validation.es.js +1 -1
- package/llms.txt +2 -2
- package/package.json +4 -8
- package/dist/lib/AbstractBooleanInput-C6CidMMX.js +0 -2
- package/dist/lib/AbstractBooleanInput-C6CidMMX.js.map +0 -1
- package/dist/lib/AbstractInput-DD-NpicS.js +0 -2
- package/dist/lib/AbstractInput-DD-NpicS.js.map +0 -1
- package/dist/lib/AnimatedDropdown-BFz-DO7R.js +0 -2
- package/dist/lib/AnimatedDropdown-BFz-DO7R.js.map +0 -1
- package/dist/lib/AnimatedDropdown-BbGCs70E.js +0 -2
- package/dist/lib/AnimatedDropdown-BbGCs70E.js.map +0 -1
- package/dist/lib/Border-5Ki2DCWz.js +0 -2
- package/dist/lib/Border-5Ki2DCWz.js.map +0 -1
- package/dist/lib/Border-oZC_SDy8.js +0 -2
- package/dist/lib/Border-oZC_SDy8.js.map +0 -1
- package/dist/lib/BoxLayout-8ryMD4j5.js +0 -2
- package/dist/lib/BoxLayout-8ryMD4j5.js.map +0 -1
- package/dist/lib/BoxLayout-DgZukB5_.js +0 -2
- package/dist/lib/BoxLayout-DgZukB5_.js.map +0 -1
- package/dist/lib/Button-Cdq2ySPL.js +0 -2
- package/dist/lib/Button-Cdq2ySPL.js.map +0 -1
- package/dist/lib/Button-DEUtBqK4.js +0 -2
- package/dist/lib/Button-DEUtBqK4.js.map +0 -1
- package/dist/lib/Card-UF0fq8cQ.js +0 -2
- package/dist/lib/Card-UF0fq8cQ.js.map +0 -1
- package/dist/lib/ComboBox-GOBOAJ1N.js +0 -2
- package/dist/lib/ComboBox-GOBOAJ1N.js.map +0 -1
- package/dist/lib/ComboBox-e_YVsWBr.js +0 -2
- package/dist/lib/ComboBox-e_YVsWBr.js.map +0 -1
- package/dist/lib/Component-BR2f0Itb.js +0 -2
- package/dist/lib/Component-BR2f0Itb.js.map +0 -1
- package/dist/lib/Component-Bs6TvaGZ.js +0 -2
- package/dist/lib/Component-Bs6TvaGZ.js.map +0 -1
- package/dist/lib/Container-3KsvsJ6S.js +0 -2
- package/dist/lib/Container-CzyZeMyZ.js +0 -2
- package/dist/lib/Container-CzyZeMyZ.js.map +0 -1
- package/dist/lib/DOM-DeF_dWe2.js +0 -2
- package/dist/lib/DOM-DeF_dWe2.js.map +0 -1
- package/dist/lib/DragManager-c6M_mE3e.js.map +0 -1
- package/dist/lib/DragManager-tPMnbjeO.js +0 -2
- package/dist/lib/FieldDecorator-Bl0h9UEF.js +0 -2
- package/dist/lib/FieldDecorator-E7WzutjO.js +0 -2
- package/dist/lib/FieldDecorator-E7WzutjO.js.map +0 -1
- package/dist/lib/Fit-CFIr4P9R.js.map +0 -1
- package/dist/lib/Fit-CUS1JXy3.js +0 -2
- package/dist/lib/Glyph-6Ki3A1_a.js +0 -2
- package/dist/lib/Glyph-6Ki3A1_a.js.map +0 -1
- package/dist/lib/Glyph-WUqTF_Z9.js +0 -2
- package/dist/lib/Glyph-WUqTF_Z9.js.map +0 -1
- package/dist/lib/Grid-Czux--Mr.js.map +0 -1
- package/dist/lib/Grid-D8luwRPI.js +0 -2
- package/dist/lib/GridConstraints-CzC0w4i-.js +0 -2
- package/dist/lib/GridConstraints-CzC0w4i-.js.map +0 -1
- package/dist/lib/GridConstraints-Q3wvuovN.js +0 -2
- package/dist/lib/GridConstraints-Q3wvuovN.js.map +0 -1
- package/dist/lib/HBox-BWMf9h6r.js +0 -2
- package/dist/lib/HBox-Q8y1PHlk.js +0 -2
- package/dist/lib/HBox-Q8y1PHlk.js.map +0 -1
- package/dist/lib/Header-zPdvC2AX.js +0 -2
- package/dist/lib/Header-zPdvC2AX.js.map +0 -1
- package/dist/lib/IconText-BvGfu7eq.js +0 -2
- package/dist/lib/IconText-BvGfu7eq.js.map +0 -1
- package/dist/lib/LayoutConstraints-CHI2lhPd.js +0 -2
- package/dist/lib/LayoutConstraints-CHI2lhPd.js.map +0 -1
- package/dist/lib/LayoutSerialization-CWU0iEuV.js +0 -2
- package/dist/lib/LayoutSerialization-CWU0iEuV.js.map +0 -1
- package/dist/lib/LayoutSerialization-C_zcwaN3.js +0 -2
- package/dist/lib/LayoutSerialization-C_zcwaN3.js.map +0 -1
- package/dist/lib/LayoutSizes-BW4wyGDI.js +0 -2
- package/dist/lib/LayoutSizes-BW4wyGDI.js.map +0 -1
- package/dist/lib/LayoutSizes-XdhpAOmc.js +0 -2
- package/dist/lib/LayoutSizes-XdhpAOmc.js.map +0 -1
- package/dist/lib/List-BgxnLWnl.js +0 -2
- package/dist/lib/List-BgxnLWnl.js.map +0 -1
- package/dist/lib/List-D-GhoVgB.js +0 -2
- package/dist/lib/List-D-GhoVgB.js.map +0 -1
- package/dist/lib/Menu-BdnwRY0M.js +0 -2
- package/dist/lib/Menu-BdnwRY0M.js.map +0 -1
- package/dist/lib/Menu-Wz4MK6sA.js +0 -2
- package/dist/lib/Menu-Wz4MK6sA.js.map +0 -1
- package/dist/lib/MenuButton-JQQaKThJ.js +0 -2
- package/dist/lib/MenuButton-JQQaKThJ.js.map +0 -1
- package/dist/lib/Panel-DApa7vA6.js +0 -2
- package/dist/lib/Panel-DApa7vA6.js.map +0 -1
- package/dist/lib/Panel-DIsdaqc2.js +0 -2
- package/dist/lib/Panel-DIsdaqc2.js.map +0 -1
- package/dist/lib/Position-B-WfZYAJ.js +0 -2
- package/dist/lib/Position-B-WfZYAJ.js.map +0 -1
- package/dist/lib/Position-DJKB0N3l.js +0 -2
- package/dist/lib/Position-DJKB0N3l.js.map +0 -1
- package/dist/lib/ProgressSpinner-CNYcsBhw.js +0 -2
- package/dist/lib/ProgressSpinner-CNYcsBhw.js.map +0 -1
- package/dist/lib/ProgressSpinner-Dzaka7_c.js +0 -2
- package/dist/lib/ProgressSpinner-Dzaka7_c.js.map +0 -1
- package/dist/lib/RadioButton-CepkneSA.js +0 -2
- package/dist/lib/RadioButton-CepkneSA.js.map +0 -1
- package/dist/lib/RadioButton-eI3-vXn3.js +0 -2
- package/dist/lib/RadioButton-eI3-vXn3.js.map +0 -1
- package/dist/lib/RovingTabIndex-CFIzIjjU.js.map +0 -1
- package/dist/lib/RovingTabIndex-D-zOuptw.js +0 -2
- package/dist/lib/Scrollbar-DQdCdj94.js +0 -2
- package/dist/lib/Scrollbar-DQdCdj94.js.map +0 -1
- package/dist/lib/Scrollbar-DchfiFKC.js +0 -2
- package/dist/lib/Scrollbar-DchfiFKC.js.map +0 -1
- package/dist/lib/Slider-DvDdVvRm.js +0 -2
- package/dist/lib/Slider-DvDdVvRm.js.map +0 -1
- package/dist/lib/Slider-xRxNSjFN.js +0 -2
- package/dist/lib/Slider-xRxNSjFN.js.map +0 -1
- package/dist/lib/Spacer-BqiPiCqz.js +0 -2
- package/dist/lib/Spacer-BqiPiCqz.js.map +0 -1
- package/dist/lib/TabButton-pZKFYg7b.js +0 -2
- package/dist/lib/TabButton-pZKFYg7b.js.map +0 -1
- package/dist/lib/Text-D1RrBY52.js +0 -2
- package/dist/lib/Text-D1RrBY52.js.map +0 -1
- package/dist/lib/Text-DJoUHukb.js +0 -2
- package/dist/lib/Text-DJoUHukb.js.map +0 -1
- package/dist/lib/Tooltip-4lU_tf6r.js +0 -3
- package/dist/lib/Tooltip-4lU_tf6r.js.map +0 -1
- package/dist/lib/Tooltip-5BR7yYEX.js +0 -3
- package/dist/lib/Tooltip-5BR7yYEX.js.map +0 -1
- package/dist/lib/VBox-CdV9VGxR.js +0 -2
- package/dist/lib/VBox-CdV9VGxR.js.map +0 -1
- package/dist/lib/VBox-D5B9ecw0.js +0 -2
- package/dist/lib/VBox-D5B9ecw0.js.map +0 -1
- package/dist/lib/VirtualRowView-Con9SWiU.js +0 -2
- package/dist/lib/VirtualRowView-Con9SWiU.js.map +0 -1
- package/dist/lib/VirtualScroller-BPDmO1ir.js +0 -2
- package/dist/lib/VirtualScroller-BPDmO1ir.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LayoutSizes-CvMmMgHJ.js","names":[],"sources":["../../src/typescript/lib/overlay/ButtonGroup.ts","../../src/typescript/lib/component/container/WindowBorder.ts","../../src/typescript/lib/core/WheelTrap.ts","../../src/typescript/lib/component/display/SpinnerWrap.ts","../../src/typescript/lib/overlay/AbstractWindow.ts","../../src/typescript/lib/overlay/windowControls.ts","../../src/typescript/lib/component/container/WindowHeader.ts","../../src/typescript/lib/overlay/Window.ts","../../src/typescript/lib/component/container/ScrollStrip.ts","../../src/typescript/lib/component/container/tabCloseTargets.ts","../../src/typescript/lib/component/container/TabBar.ts","../../src/typescript/lib/layout/Tab.ts","../../src/typescript/lib/overlay/TabWindow.ts","../../src/typescript/lib/layout/LayoutSizes.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { RadioButton } from \"~/component/input/RadioButton.js\";\nimport { RovingTabIndex } from \"~/core/RovingTabIndex.js\";\nimport { ToggleButton } from \"~/component/button/ToggleButton.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * String-literal union of the events emitted by {@link ButtonGroup}.\n *\n * @category Core\n */\nexport type ButtonGroupEvent = \"selection\";\n\n/**\n * Construction-time options for {@link ButtonGroup}.\n *\n * @category Core\n */\nexport interface ButtonGroupOptions {\n buttons?: Array<RadioButton | ToggleButton>;\n /**\n * When `true`, clicking the currently-selected button leaves it deselected\n * instead of re-selecting it — so the group can hold **nothing** selected.\n * Defaults to `false` (the classic radio invariant: exactly one selected once\n * a choice is made). Use `true` for a toggle rail where re-clicking the active\n * item is a meaningful \"turn it off\" gesture (e.g. a collapsible view switch).\n */\n allowDeselect?: boolean;\n /**\n * Multi-event listener bag dispatched to {@link ButtonGroup.on} at\n * construction time.\n */\n listeners?: {\n selection?: (button: RadioButton | ToggleButton) => void;\n };\n}\n\n/**\n * Manages mutual exclusivity among a set of {@link RadioButton} or {@link ToggleButton} instances.\n *\n * When a button in the group becomes selected, all other buttons in the group are automatically deselected.\n * For [`RadioButton`](/api/component/input/classes/RadioButton) groups, a shared `name` attribute is applied so the browser handles keyboard navigation\n * natively. For [`ToggleButton`](/api/component/button/classes/ToggleButton) groups, call `setContainer` to enable roving tabindex keyboard nav.\n *\n * @category Core\n */\nclass ButtonGroup {\n\n buttons: Array<RadioButton | ToggleButton> = new Array<RadioButton | ToggleButton>();\n\n private _groupId: string = 'bg-' + Math.random().toString(36).slice(2, 10);\n private _allowDeselect: boolean = false;\n private _rovingTabIndex: RovingTabIndex | null = null;\n private _listeners: ListenerBag<ButtonGroupEvent> = new ListenerBag<ButtonGroupEvent>();\n\n /**\n * Creates a ButtonGroup, optionally populated with an initial set of buttons.\n *\n * @param options - Optional. Construction-time options. `options.buttons` registers\n * the given buttons via {@link addButtons}.\n */\n constructor(options?: ButtonGroupOptions) {\n if (options?.allowDeselect !== undefined) this._allowDeselect = options.allowDeselect;\n\n if (options?.buttons !== undefined) this.addButtons(options.buttons);\n\n if (options?.listeners !== undefined) {\n const listeners = options.listeners;\n\n for (const event of Object.keys(listeners) as Array<keyof typeof listeners>) {\n const listener = listeners[event];\n\n if (listener !== undefined) {\n this.on(event, listener);\n }\n }\n }\n }\n\n /**\n * Reconciles the group after a member's click. When the initiator is now\n * selected, every other button is deselected (mutual exclusivity). When the\n * initiator was toggled off by its own click, the group re-selects it to keep\n * exactly one selected — unless {@link ButtonGroupOptions.allowDeselect} is\n * set, in which case the group is left with nothing selected. Either way the\n * `\"selection\"` event fires with the initiator (which may be deselected under\n * `allowDeselect`, so listeners should read its `isSelected()`).\n *\n * @param initiatorButton - The button whose click triggered the update.\n */\n private updateButtonStates(initiatorButton: RadioButton | ToggleButton): void {\n if (!initiatorButton.isSelected()) {\n // The initiator's own click toggled it off. Radio groups snap it back\n // on to preserve the one-always-selected invariant; a deselectable\n // group lets it stay off (nothing selected). Its siblings were already\n // deselected when it was first chosen, so no further sweep is needed.\n if (!this._allowDeselect) {\n initiatorButton.setSelected(true);\n }\n } else {\n this.buttons.forEach((button) => {\n if (button !== initiatorButton) {\n button.setSelected(false);\n }\n });\n }\n\n this.emit(\"selection\", initiatorButton);\n }\n\n /**\n * Toggles whether a click on the selected button may leave the group with\n * nothing selected. See {@link ButtonGroupOptions.allowDeselect}.\n *\n * @param value - `true` to allow deselect-to-none; `false` for the classic\n * one-always-selected radio invariant.\n *\n * @returns This group, for method chaining.\n */\n setAllowDeselect(value: boolean): this {\n this._allowDeselect = value;\n\n return this;\n }\n\n /**\n * Registers a listener for one of this group's events.\n *\n * @param event - `\"selection\"` fires whenever the group's selection changes,\n * receiving the button that was clicked. With `allowDeselect` that button\n * may now be deselected (the group holds nothing), so read its\n * `isSelected()` to tell a select from a deselect.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This group, for method chaining.\n */\n on(event: \"selection\", listener: (button: RadioButton | ToggleButton) => void): this;\n on(event: ButtonGroupEvent, 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 group, for method chaining.\n */\n off(event: ButtonGroupEvent, 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 - Forwarded to each listener.\n */\n protected emit(event: \"selection\", button: RadioButton | ToggleButton): void;\n protected emit(event: ButtonGroupEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Returns the group's buttons as a [`Component`](/api/core/classes/Component) array, suitable for passing\n * directly to [`Component.addComponents`](/api/core/classes/Component#addcomponents).\n *\n * @returns A new array of the buttons, widened to [`Component`](/api/core/classes/Component).\n */\n getButtons(): Array<Component> {\n return this.buttons.slice();\n }\n\n /**\n * Adds multiple buttons to the group in a single call.\n *\n * Each argument is either a button or an array of buttons; all forms can be freely mixed\n * in the same call. Each button is registered via {@link addButton}, so mutual-exclusivity\n * wiring and (for [`ToggleButton`](/api/component/button/classes/ToggleButton)) roving tabindex registration are applied to every entry.\n *\n * @param buttons - The buttons to add. Each entry is a bare button or an array of buttons.\n *\n * @returns This group, for method chaining.\n */\n addButtons(...buttons: Array<RadioButton | ToggleButton | Array<RadioButton | ToggleButton>>): this {\n for (const entry of buttons) {\n const items = Array.isArray(entry) ? entry : [entry];\n\n for (const button of items) {\n this.addButton(button);\n }\n }\n\n return this;\n }\n\n /**\n * Adds a button to the group and wires its selection to enforce mutual exclusivity.\n *\n * @remarks The shared group id is stored on each [`RadioButton`](/api/component/input/classes/RadioButton) (via `setRadioName`) for\n * back-compat with consumers that read `getRadioName()`. Group navigation is delegated\n * to {@link RovingTabIndex} once a container has been wired via {@link setContainer};\n * both [`ToggleButton`](/api/component/button/classes/ToggleButton) and [`RadioButton`](/api/component/input/classes/RadioButton) members are registered there.\n * @param button - The button to add to the group.\n */\n addButton(button: RadioButton | ToggleButton): this {\n this.buttons.push(button);\n\n button.on(\"action\", () => {\n this.updateButtonStates(button);\n });\n\n if (button instanceof RadioButton) {\n button.setRadioName(this._groupId);\n }\n\n if (this._rovingTabIndex !== null) {\n this._rovingTabIndex.add(button);\n }\n\n return this;\n }\n\n /**\n * Removes a button from the group.\n *\n * @param button - The button to remove.\n */\n removeButton(button: RadioButton | ToggleButton): this {\n let idx = this.buttons.indexOf(button);\n\n if (idx < 0) {\n return this;\n }\n\n this.buttons.splice(idx, 1);\n\n if (this._rovingTabIndex !== null) {\n this._rovingTabIndex.remove(button);\n }\n\n return this;\n }\n\n /**\n * Sets the container component for keyboard navigation of the group.\n *\n * @remarks Registers Left/Right/Up/Down arrow key handlers on the container via subtree listener.\n * Also initialises the {@link RovingTabIndex} and adds every already-registered\n * [`RadioButton`](/api/component/input/classes/RadioButton) or [`ToggleButton`](/api/component/button/classes/ToggleButton) member to it.\n * @param container - The component that wraps the buttons and should receive key events.\n */\n setContainer(container: Component): this {\n this._rovingTabIndex = new RovingTabIndex();\n\n for (const button of this.buttons) {\n this._rovingTabIndex.add(button);\n }\n\n Event.addSubtreeListener(container, \"keydown\", (e: KeyboardEvent) => {\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n this._rovingTabIndex!.moveNext();\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._rovingTabIndex!.movePrev();\n }\n });\n\n return this;\n }\n}\n\nconst ButtonGroupCallable = callable(ButtonGroup);\ntype ButtonGroupCallable = ButtonGroup;\nexport {\n ButtonGroup as _ButtonGroup,\n ButtonGroupCallable as ButtonGroup\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 { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { beginPointerDrag, endPointerDrag } from \"~/core/PointerDrag.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * String-literal union of the events emitted by {@link WindowBorder}.\n *\n * @category Components\n */\nexport type WindowBorderEvent = \"drag\";\n\n/**\n * The eight edge / corner positions used by {@link WindowBorder} to identify\n * which border strip the user is dragging.\n *\n * @category Components\n */\nexport enum Direction {\n NORTH,\n SOUTH,\n WEST,\n EAST,\n NORTHWEST,\n SOUTHEAST,\n SOUTHWEST,\n NORTHEAST\n}\n\n/**\n * Construction-time options for {@link WindowBorder}.\n *\n * @category Components\n */\nexport interface WindowBorderOptions extends ComponentOptions {\n /**\n * Multi-event listener bag dispatched to {@link WindowBorder.on} at\n * construction time.\n */\n listeners?: {\n drag?: (border: WindowBorder, e: MouseEvent) => void;\n };\n}\n\n/**\n * CSS class added to the DOM element of the currently snap-targeted strip.\n * Kept package-private (`@internal`) — the owning [`Window`](/api/overlay/classes/Window)\n * toggles it through {@link WindowBorder.setSnapTarget}.\n *\n * @internal\n */\nconst SNAP_TARGET_CLASS = \"snap-target\";\n\n/**\n * Class-level defaults forwarded to `super` so the cascade hits Component's\n * applyOptions with `{ tag: \"div\" }` already merged into `_defaultOptions`.\n */\nconst _defaultWindowBorderOptions: Partial<WindowBorderOptions> = {\n tag: \"div\",\n};\n\n/**\n * A resizable window border strip component.\n *\n * Each instance represents one edge or corner of a resizable window. It listens for\n * mouse/touch drag events and notifies registered listeners with the mouse event so\n * the parent window can compute and apply the new size.\n *\n * @category Components\n */\nclass WindowBorder extends Component<WindowBorderOptions> {\n\n private _direction: Direction = Direction.NORTH;\n private _listeners: ListenerBag<WindowBorderEvent> = new ListenerBag<WindowBorderEvent>();\n private _dragStartListener: Event.Listener;\n private _dragStopListener: Event.Listener;\n private _fireDragListener: Event.Listener;\n private _snapTarget: boolean = false;\n\n // Lazy `.snap-target` rule. The slot is a fast-path cache for the wrapper\n // returned by Component's `createStyleRule` builder, which dedupes by\n // selector suffix — see Button's `_pressedStyleRule` for the full\n // explanation.\n private declare _snapTargetStyleRule?: StyleRule;\n private get snapTargetStyleRule(): StyleRule {\n return this._snapTargetStyleRule ??= this.createStyleRule(\".\" + SNAP_TARGET_CLASS);\n }\n\n constructor(direction: Direction, options?: WindowBorderOptions) {\n super(options, _defaultWindowBorderOptions);\n\n if (direction) {\n this._direction = direction;\n }\n\n this._dragStartListener = this.onDragStart.bind(this);\n this._dragStopListener = this.onDragStop.bind(this);\n this._fireDragListener = this._dispatchDrag.bind(this);\n\n Event.addListener(this, 'mousedown', this._dragStartListener);\n\n this.applyListeners(options?.listeners);\n\n // Queue the snap-target highlight into the lazy state rule. Materialises\n // at render time through Component's batched style channel.\n this.snapTargetStyleRule.set(\"boxShadow\", \"var(--ts-ui-window-snap-glow, 0 0 0 2px rgba(30, 100, 200, 0.7))\");\n }\n\n /**\n * Returns the resize direction of this border strip.\n *\n * @returns The Direction enum value for this border.\n */\n getDirection() {\n return this._direction;\n }\n\n /**\n * Sets the resize direction, defaulting to NORTH if not provided.\n *\n * @param direction - The Direction enum value. Defaults to NORTH if falsy.\n */\n setDirection(direction: Direction) : this {\n if (!direction) {\n direction = Direction.NORTH;\n }\n\n this._direction = direction;\n\n return this;\n }\n\n /**\n * Registers a listener for one of this border's events.\n *\n * @param event - `\"drag\"` fires on each mousemove/touchmove during a\n * drag, receiving this border and the originating event.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This border, for method chaining.\n */\n on(event: \"drag\", listener: (border: WindowBorder, e: MouseEvent) => void): this;\n on(event: WindowBorderEvent, 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 border, for method chaining.\n */\n off(event: WindowBorderEvent, 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 - Forwarded to each listener.\n */\n protected emit(event: \"drag\", border: WindowBorder, e: MouseEvent): void;\n protected emit(event: WindowBorderEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Internal mousemove dispatch — fires the `drag` event with this border\n * and the originating mouse event.\n *\n * @param e - The MouseEvent observed by the viewport-mousemove listener.\n * @returns `true`, consuming the move so nothing else tracks the pointer mid-resize.\n */\n private _dispatchDrag(e: MouseEvent): Event.ListenerResult {\n this.emit(\"drag\", this, e);\n\n return true;\n }\n\n /**\n * Returns whether this border strip is currently flagged as the snap target.\n *\n * @returns True when the snap-target CSS class is applied.\n */\n isSnapTarget(): boolean {\n return this._snapTarget;\n }\n\n /**\n * Toggles the snap-target CSS class on this strip's DOM element. Called by\n * the owning [`Window`](/api/overlay/classes/Window) while the snap modifier\n * is held and the cursor is within the configured threshold.\n *\n * @param value - True to highlight this strip as the snap target.\n *\n * @returns This component, for method chaining.\n */\n setSnapTarget(value: boolean): this {\n if (this._snapTarget === value) {\n return this;\n }\n\n this._snapTarget = value;\n\n const element = this.getElement();\n if (element) {\n DOM.sink.apply(element, { toggleClass: { [SNAP_TARGET_CLASS]: value } });\n }\n\n return this;\n }\n\n /**\n * Attaches viewport mouse/touch move and stop listeners and disables body pointer events.\n */\n onDragStart() {\n Event.addViewportListener(this, 'mouseup', this._dragStopListener);\n Event.addViewportListener(this, 'touchend', this._dragStopListener);\n Event.addViewportListener(this, 'touchcancel', this._dragStopListener);\n Event.addViewportListener(this, 'mousemove', this._fireDragListener);\n Event.addViewportListener(this, 'touchmove', this._fireDragListener);\n\n // A direction with no resize cursor of its own still suppresses body\n // pointer events; it just has nothing to hold, so it keeps the default.\n beginPointerDrag(this.dragCursor() ?? \"default\");\n }\n\n /**\n * Removes viewport listeners and restores body pointer events when drag ends.\n *\n * @returns `true`, consuming the release that ends the border resize.\n */\n onDragStop(): Event.ListenerResult {\n Event.removeViewportListener(this, 'mouseup', this._dragStopListener);\n Event.removeViewportListener(this, 'touchend', this._dragStopListener);\n Event.removeViewportListener(this, 'touchcancel', this._dragStopListener);\n Event.removeViewportListener(this, 'mousemove', this._fireDragListener);\n Event.removeViewportListener(this, 'touchmove', this._fireDragListener);\n\n endPointerDrag();\n\n // Drop the snap-target highlight (if any) once the drag commits, so a\n // subsequent Ctrl-release on the same hover doesn't leave the strip glowing.\n this.setSnapTarget(false);\n\n return true;\n }\n\n /**\n * The resize cursor for the edge or corner this strip sits on, shared by the\n * hover state and the drag itself so the two can never disagree.\n *\n * @returns The CSS cursor naming the resize axis, or `undefined` for a\n * direction that does not resize.\n */\n private dragCursor(): string | undefined {\n switch (this._direction) {\n case Direction.NORTH:\n case Direction.SOUTH:\n return \"ns-resize\";\n case Direction.WEST:\n case Direction.EAST:\n return \"ew-resize\";\n case Direction.NORTHWEST:\n case Direction.SOUTHEAST:\n return \"nwse-resize\";\n case Direction.SOUTHWEST:\n case Direction.NORTHEAST:\n return \"nesw-resize\";\n }\n\n return undefined;\n }\n\n /**\n * Renders the border element and sets the appropriate resize cursor based on direction.\n *\n * @returns The created element with the correct resize cursor applied.\n */\n render() {\n let element = super.render();\n\n const cursor = this.dragCursor();\n if (cursor) {\n this.setCursor(cursor);\n }\n\n if (this._snapTarget) {\n DOM.sink.apply(element, { addClass: [SNAP_TARGET_CLASS] });\n }\n\n return element;\n }\n\n}\n\nconst WindowBorderCallable = callable(WindowBorder);\ntype WindowBorderCallable = WindowBorder;\nexport {\n WindowBorder as _WindowBorder,\n WindowBorderCallable as WindowBorder\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport type { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { consumeWheel } from \"~/core/SmoothScroller.js\";\n\n/**\n * Components that currently have the wheel trap installed. Used purely for\n * idempotency — the listener itself ({@link swallowUnconsumedWheel}) captures\n * nothing per component, so one shared reference serves every overlay. Keyed\n * weakly, mirroring how the layer registry keys per-layer state, so a closed\n * overlay's entry is collectable without an explicit purge.\n */\nconst _trapped = new WeakSet<Component>();\n\n/**\n * Subtree `wheel` handler shared by every trapped overlay: claims the event via\n * the framework's wheel once-marker and, when the claim succeeds (no inner\n * scroller took it first), `preventDefault()`s it so the native wheel never\n * reaches a scrollable ancestor behind the overlay.\n *\n * @param e - The wheel event reaching the overlay as the outermost ancestor.\n */\nfunction swallowUnconsumedWheel(e: WheelEvent): void {\n if (consumeWheel(e)) {\n e.preventDefault();\n }\n}\n\n/**\n * Registers a non-passive subtree `wheel` listener on `component` that\n * `preventDefault()`s any wheel the event reaches it unconsumed — trapping\n * wheels an inner scroller did not claim so they cannot fall through to content\n * behind a floating overlay. Idempotent per component.\n *\n * @param component - The overlay to install the trap on. Must already be mounted,\n * since the subtree listener resolves the component's element.\n *\n * @remarks Subtree events dispatch descendant-first, so any inner scroll\n * container under the pointer claims the wheel first and the overlay's handler,\n * firing last as the outermost ancestor, sees it already consumed and does\n * nothing — inner scroll proceeds untouched. The listener is `{ passive: false }`\n * to match the only other `wheel` registration (the inner scroll pipeline) and\n * because `preventDefault()` is a no-op on a passive listener.\n */\nexport function trapWheel(component: Component): void {\n if (_trapped.has(component)) {\n return;\n }\n\n _trapped.add(component);\n Event.addSubtreeListener(component, \"wheel\", swallowUnconsumedWheel, { passive: false });\n}\n\n/**\n * Removes the trap installed by {@link trapWheel}. Safe to call when none was\n * installed.\n *\n * @param component - The overlay whose trap should be removed.\n */\nexport function untrapWheel(component: Component): void {\n if (!_trapped.has(component)) {\n return;\n }\n\n _trapped.delete(component);\n Event.removeSubtreeListener(component, \"wheel\", swallowUnconsumedWheel);\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { FillType } from \"~/layout/FillType.js\";\nimport { ProgressSpinner } from \"~/component/display/ProgressSpinner.js\";\n\n/**\n * Shared visual recipe for the placeholder [`Animation.materialize`](/api/core/namespaces/Animation/functions/materialize)\n * mounts while it waits on a deferred build: {@link Tab}'s lazy-tab\n * activation and {@link AbstractWindow}'s `setContentFactory` both need the\n * same centred spinner, so the construction lives here as the single source\n * of truth rather than duplicated at each call site.\n *\n * @category Core\n */\n\n/**\n * Builds the spinner placeholder `Animation.materialize` mounts into its host:\n * a fixed-size `ProgressSpinner` wrapped in a [`Fit`](/api/layout/classes/Fit)\n * layout configured with `FillType.NONE` so the spinner sits at its preferred\n * size in the geometric centre of the host's content area. The diameter\n * (24 px) matches `TablePanel`'s store-loading spinner so a slow deferred\n * build and a slow data load look identical to the user.\n *\n * @returns A Component owning a single `ProgressSpinner` child.\n */\nexport function createSpinnerWrap(): Component {\n const wrap = new Component();\n wrap.setLayoutManager(new Fit({ fill: FillType.NONE }));\n wrap.addComponent(new ProgressSpinner(24));\n\n return wrap;\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { WindowBorder, Direction } from \"~/component/container/WindowBorder.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { createSpinnerWrap } from \"~/component/display/SpinnerWrap.js\";\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { Size } from \"~/primitive/Size.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport type { Rail } from \"~/overlay/Rail.js\";\n\n// Window body inset in pixels, set explicitly now that the base is Container\n// (zero default insets) rather than Panel (which supplied this 4px implicitly).\n// `doLayout` folds this inset into the resize-border thickness, so it both pads\n// the body content and reserves the grab band the WindowBorder handles sit in;\n// 4px preserves the exact resting geometry windows inherited from Panel before\n// the reparent.\nconst WINDOW_BODY_INSET_PX: number = 4;\nconst WINDOW_ANIM_DURATION_MS: number = 150;\nconst SNAP_DOCK_GAP_PX: number = 4;\nconst DEFAULT_MIN_DOCK_WIDTH_PX: number = 200;\n// Must-stay-visible slab of a window edge, used only when constrainToViewport is\n// disabled: in that fallback the drag keeps at least this many pixels of the\n// window inside the viewport so its header bar can never be dropped fully\n// off-screen and become ungrabbable. The default, full-window containment never\n// consults this. 24 px is wide enough to grab with a cursor yet narrow enough\n// not to feel restrictive.\nconst EDGE_MARGIN_PX: number = 24;\n// Fallback chrome-band height used before the window has laid out, when the\n// title chrome's measured height (`chromeHeight()`) still reads 0. 26 px is the\n// default chrome height a `Window` header renders at, so a pre-layout geometry\n// calculation lands on the same footprint the laid-out window will have.\nconst CHROME_HEIGHT_FLOOR_PX: number = 26;\n\n/**\n * Lifecycle state for an {@link AbstractWindow}. The three values are mutually\n * exclusive — a window is always exactly one of:\n *\n * - `\"normal\"` — free-floating; user can drag and resize it.\n * - `\"minimized\"` — docked along the bottom of the viewport at header height.\n * - `\"maximized\"` — filling the viewport (or its parent, per `maximizeBounds`).\n *\n * The state field carries no presentation state; the corresponding rect and\n * body-visibility are computed by the window's state-transition switch.\n *\n * @category Core\n */\nexport type WindowState = \"normal\" | \"minimized\" | \"maximized\";\n\n/**\n * Typed events an {@link AbstractWindow} emits. `\"minimize\"` fires when the\n * window enters `\"minimized\"`, `\"restore\"` when it leaves it, `\"close\"` when\n * the window is closed, and `\"activate\"` when the window becomes the active\n * layer (a raise / focus). A [`Rail`](/api/overlay/classes/Rail) subscribes to\n * the first three to mirror a window minimized into it as a launcher handle; a\n * [`Dock`](/api/overlay/classes/Dock) subscribes to `\"activate\"` to track which\n * floated panel is focused.\n *\n * @category Core\n */\nexport type WindowEvent = \"minimize\" | \"restore\" | \"close\" | \"activate\";\n\n/**\n * Snap-resize modifier key. Matches the matching property names exposed by\n * `KeyboardEvent`.\n *\n * @category Core\n */\nexport type WindowSnapModifier = \"ctrl\" | \"meta\" | \"alt\" | \"shift\";\n\n/**\n * Where to fill when entering the `\"maximized\"` state.\n *\n * - `\"viewport\"` — fill `window.innerWidth` / `window.innerHeight` (default;\n * matches the natural mount point since the window appends itself directly to\n * `document.documentElement`).\n * - `\"parent\"` — fill the window element's `parentElement` rect. Use when\n * the window has been re-parented into a regular Panel.\n *\n * @category Core\n */\nexport type WindowMaximizeBounds = \"viewport\" | \"parent\";\n\n/**\n * A window's rectangle in viewport pixels.\n *\n * @category Core\n */\nexport interface WindowRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Construction-time options for {@link AbstractWindow} and its subclasses.\n *\n * @category Core\n */\nexport interface WindowOptions extends ContainerOptions {\n headerText?: string;\n glyph?: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n contentFactory?: () => Component;\n onReady?: (component: Component) => void;\n closeable?: boolean;\n minimizable?: boolean;\n maximizable?: boolean;\n maximizeBounds?: WindowMaximizeBounds;\n windowState?: WindowState;\n snapResizeEnabled?: boolean;\n snapThreshold?: number;\n snapModifier?: WindowSnapModifier;\n constrainToViewport?: boolean;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins. `headerText`,\n * `glyph`, `contentFactory`, and `onReady` touch subclass chrome or the\n * `contentFactory` field (both initialised after `super`), so `applyOptions`\n * writes them pure into `_options` and the constructor body dispatches them\n * once the children/fields exist.\n */\nconst _defaultWindowOptions: Partial<WindowOptions> = {\n x: 50,\n y: 50,\n width: 400,\n height: 300,\n insets: new Insets(WINDOW_BODY_INSET_PX, WINDOW_BODY_INSET_PX, WINDOW_BODY_INSET_PX, WINDOW_BODY_INSET_PX),\n border: \"1px solid var(--ts-ui-border-color, black)\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n shadow: \"var(--ts-ui-window-shadow, 3px 3px 2px rgba(0, 0, 0, 0.4))\",\n backgroundColor: \"var(--ts-ui-body-bg, rgb(241, 241, 241))\",\n closeable: true,\n minimizable: true,\n maximizable: true,\n maximizeBounds: \"viewport\",\n windowState: \"normal\",\n snapResizeEnabled: true,\n snapThreshold: 12,\n snapModifier: \"ctrl\",\n constrainToViewport: true,\n};\n\n// z-index for the eight resize handles, lifting them above the window's content\n// layers so the edges and corners stay grabbable even when content paints over\n// the gutter — a TabWindow bar (`barIgnoreParentInsets`) or a Window header\n// (`ignoreParentInsets`) reaches the outer edge, and the layout-manager bar is\n// appended *after* the handles in DOM, so without an explicit z-index it would\n// win hit-testing. 10 sits comfortably above the tab bar's internal chrome\n// (tool group, indicator, scroll-arrow buttons top out at z-index 3).\nconst RESIZE_BORDER_Z_INDEX: number = 10;\n\n/**\n * Header-agnostic base for floating, resizable, draggable windows.\n *\n * Owns everything that does not name a title-bar header: the eight resize-handle\n * border strips, the move drag flow, the three-state lifecycle (`\"normal\"` /\n * `\"minimized\"` / `\"maximized\"`), the closeable / minimizable / maximizable\n * *state*, active-focus *state*, z-order, show/hide, the min-size seed\n * *mechanism*, and the open-windows registry. Everything that differs per\n * subclass — how the move gesture is wired, how state reflects into UI, how the\n * title reads, how content is added, how tall the chrome is, and which child is\n * chrome — is delegated to `protected`/`abstract` hooks.\n *\n * Two concrete subclasses extend it: {@link Window} (a `Border` layout with a\n * `WindowHeader`) and `TabWindow` (a headerless `Tab` layout).\n *\n * @category Core\n */\nexport abstract class AbstractWindow extends Container<WindowOptions> implements DismissableLayer {\n\n private static openWindows: Set<AbstractWindow> = new Set<AbstractWindow>();\n\n private _borderComponents: {\n west: WindowBorder,\n northwest: WindowBorder,\n north: WindowBorder,\n northeast: WindowBorder,\n east: WindowBorder,\n southeast: WindowBorder,\n south: WindowBorder,\n southwest: WindowBorder,\n };\n\n private _animationFrameId: number | null = null;\n private _pendingClientX: number = 0;\n private _pendingClientY: number = 0;\n private _pendingBorder: WindowBorder | null = null;\n private _resizeSessionActive: boolean = false;\n private _resizeOriginClientX: number = 0;\n private _resizeOriginClientY: number = 0;\n private _resizeOriginX: number = 0;\n private _resizeOriginY: number = 0;\n private _resizeOriginW: number = 0;\n private _resizeOriginH: number = 0;\n private _resizeFps: number = 60;\n private _lastFlushTime: number = 0;\n private _dragStartLeft: number = 0;\n private _dragStartTop: number = 0;\n private _dragOriginClientX: number = 0;\n private _dragOriginClientY: number = 0;\n private _dragDX: number = 0;\n private _dragDY: number = 0;\n // Lowest / highest top-left coordinate the window has reached during the\n // in-progress move drag, per axis. Seeded to the drag origin in\n // startMoveFrom and ratcheted in clampDragDelta so an off-screen edge can\n // only ever travel back toward the viewport, never further out — even\n // mid-drag after reversing direction.\n private _dragReachMinX: number = 0;\n private _dragReachMaxX: number = 0;\n private _dragReachMinY: number = 0;\n private _dragReachMaxY: number = 0;\n private _contentFactory: (() => Component) | null = null;\n private _contentReadyCallback: ((component: Component) => void) | null = null;\n\n protected _preMinimizeState: \"normal\" | \"maximized\" = \"normal\";\n private _restoreRect: WindowRect | null = null;\n private _bodyHost: Component | null = null;\n\n /** Rail this window minimizes into, or null for the built-in bottom strip. */\n private _rail: Rail | null = null;\n /** Typed-event fan-out for `\"minimize\"` / `\"restore\"` / `\"close\"`. */\n private _windowListeners: ListenerBag<WindowEvent> = new ListenerBag<WindowEvent>();\n private _stateAnimHandle: Animation.CancelHandle | null = null;\n\n // In-flight animations, cancelled on teardown so their fallback timers\n // cannot fire against this window's released element handle.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _materializeAnimation: Animation.CancelHandle | null = null;\n private _closeAnimation: Animation.CancelHandle | null = null;\n private _railCollapseAnimation: Animation.CancelHandle | null = null;\n private _railExpandAnimation: Animation.CancelHandle | null = null;\n private _viewportResizeBound: boolean = false;\n\n private _snapEnabled: boolean = false;\n private _snapKeysAttached: boolean = false;\n private _snapMoveAttached: boolean = false;\n private _snapTargetBorder: WindowBorder | null = null;\n\n private readonly _boundOnDrag: (e: MouseEvent) => Event.ListenerResult = (e: MouseEvent) => this.onDrag(e);\n private readonly _boundOnMouseUp: () => Event.ListenerResult = () => this.onMouseUp();\n private readonly _boundOnResizeEnd: () => Event.ListenerResult = () => this.onResizeEnd();\n private readonly _boundOnSnapKeyDown: (e: KeyboardEvent) => void = (e) => this.onSnapKeyDown(e);\n private readonly _boundOnSnapKeyUp: (e: KeyboardEvent) => void = (e) => this.onSnapKeyUp(e);\n private readonly _boundOnSnapMouseMove: (e: MouseEvent) => void = (e) => this.onSnapMouseMove(e);\n private readonly _boundOnSnapMouseDown: (e: MouseEvent) => Event.ListenerResult = (e) => this.onSnapMouseDown(e);\n private readonly _boundOnViewportResize: () => void = () => this.onViewportResize();\n private readonly _boundOnSnapBlur: () => void = () => this.clearSnapState();\n private readonly _boundOnBorderResize: (border: WindowBorder, e: MouseEvent) => void = (border, e) => this.onResize(border, e);\n private readonly _boundOnBringToFront: () => void = () => this.bringToFront();\n // Schedules a layout pass on theme change, so every `Text` under this\n // window re-measures lazily against the new theme's metrics — windows\n // are appended to `document.documentElement`, not to `Body`, so they need\n // their own root reflow subscription rather than sharing Body's.\n private readonly _boundOnThemeReflow: () => void = () => this.scheduleLayout();\n\n /**\n * Builds the chrome-agnostic, dereference-free part of a window: the eight\n * resize-border overlays, the hidden-until-shown / layout-containment flags,\n * and the bring-to-front subtree listener. The late state dispatch, the\n * `wireMoveTrigger()` call, and the min-size seed are deferred to\n * `initChrome`, which a subclass calls *after* it has\n * built its chrome — never here, where subclass chrome does not yet exist.\n *\n * @param options - The options bag carrying the window's configuration.\n */\n constructor(options?: WindowOptions) {\n super(options, _defaultWindowOptions);\n\n this.subscribeTheme(this._boundOnThemeReflow);\n\n this._borderComponents = {\n west: new WindowBorder(Direction.WEST),\n northwest: new WindowBorder(Direction.NORTHWEST),\n north: new WindowBorder(Direction.NORTH),\n northeast: new WindowBorder(Direction.NORTHEAST),\n east: new WindowBorder(Direction.EAST),\n southeast: new WindowBorder(Direction.SOUTHEAST),\n south: new WindowBorder(Direction.SOUTH),\n southwest: new WindowBorder(Direction.SOUTHWEST),\n };\n\n this._borderComponents.west.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.northwest.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.north.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.northeast.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.east.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.southeast.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.south.on(\"drag\", this._boundOnBorderResize);\n this._borderComponents.southwest.on(\"drag\", this._boundOnBorderResize);\n\n // Keep the resize handles on top of the window's content (see\n // RESIZE_BORDER_Z_INDEX) so a bar/header that paints over the gutter does\n // not steal their edge/corner hit area.\n for (const border of Object.values(this._borderComponents)) {\n border.setZIndex(RESIZE_BORDER_Z_INDEX);\n }\n\n this.setVisible(false);\n // Resizable — size containment unsafe; layout containment scopes reflow to the window subtree.\n this.setContain(\"layout\");\n\n // Focusable as a unit so activation can move keyboard focus to the\n // window (see onActivate / focusSelf). -1 keeps it out of the Tab order\n // — focus is driven programmatically on activation, not by tabbing.\n this.getAria().setTabIndex(-1);\n\n Event.addSubtreeListener(this, \"mousedown\", this._boundOnBringToFront);\n }\n\n /**\n * Runs the chrome-dependent late setup a subclass cannot do during\n * `super()`: dispatches the state-affecting option flags through the\n * `reflect*` hooks, registers the deferred content factory, installs the\n * move gesture via `wireMoveTrigger`, and seeds the\n * default min size from {@link AbstractWindow.minContentWidthSeed}. The\n * subclass constructor calls this *after* it has built its chrome (so the\n * hooks have something to reflect into), avoiding the class-field\n * super-cascade trap.\n *\n * @param options - The constructor's original options bag, consulted only\n * to skip the min-size seed when the caller supplied an explicit\n * `minSize`.\n */\n protected initChrome(options?: WindowOptions): void {\n if (this._options.contentFactory !== undefined) {\n this.setContentFactory(this._options.contentFactory, this._options.onReady);\n }\n\n // Late-built dispatch for state-affecting flags: these reflect into the\n // subclass chrome (minimizable / maximizable buttons or tools) or\n // trigger a tween (windowState), so they must run after the chrome is\n // built and the geometry fields are initialised.\n // All carry a class default, so dispatch the caller value (stashed in\n // `_options` by `applyOptions`) or the class default — never leave the\n // chrome unbuilt.\n this.setCloseable(this.isCloseable());\n this.setMinimizable(this.isMinimizable());\n this.setMaximizable(this.isMaximizable());\n this.setMaximizeBounds(this.getMaximizeBounds());\n this.setWindowState(this.getWindowState());\n\n this.wireMoveTrigger();\n\n // Default minSize: enough chrome room for the title content and trailing\n // controls plus a 200 px body floor. Skipped when the caller supplied an\n // explicit `minSize` in the options bag.\n if (options?.minSize === undefined) {\n this.setMinSize({ width: this.minContentWidthSeed(), height: 200 });\n }\n }\n\n /**\n * Applies a {@link WindowOptions} bag. Inherited Container/Component fields\n * cascade through `super.applyOptions`; `headerText` / `glyph` /\n * `contentFactory` / `onReady` are written pure into `_options` here and\n * dispatched later (from `initChrome`, or — for `glyph` — the subclass\n * constructor) once the chrome and the `contentFactory` field exist. `x`,\n * `y`, `width`, `height` cascade directly through their setters — they write\n * to local fields and skip the DOM until an element exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: WindowOptions): this {\n super.applyOptions(options);\n\n if (options.headerText !== undefined) this._options.headerText = options.headerText;\n if (options.glyph !== undefined) this._options.glyph = options.glyph;\n if (options.contentFactory !== undefined) this._options.contentFactory = options.contentFactory;\n if (options.onReady !== undefined) this._options.onReady = options.onReady;\n\n // Geometry carries a class default and seeds the window's first render,\n // so always dispatch the caller value or the class default. Resolved\n // inline (not via getX/getWidth) because those getters return the live,\n // post-drag geometry — not the construction-time option default.\n this.setX(options.x ?? this._defaultOptions.x!);\n this.setY(options.y ?? this._defaultOptions.y!);\n this.setWidth(options.width ?? this._defaultOptions.width!);\n this.setHeight(options.height ?? this._defaultOptions.height!);\n\n // State-affecting flags are written pure into `_options` here (caller\n // value only) and dispatched late from `initChrome`, which folds the\n // class default — the setters need subclass chrome (minimizable /\n // maximizable) or geometry (windowState) which only exist after super.\n if (options.closeable !== undefined) this._options.closeable = options.closeable;\n if (options.minimizable !== undefined) this._options.minimizable = options.minimizable;\n if (options.maximizable !== undefined) this._options.maximizable = options.maximizable;\n if (options.maximizeBounds !== undefined) this._options.maximizeBounds = options.maximizeBounds;\n if (options.windowState !== undefined) this._options.windowState = options.windowState;\n\n this.setSnapResizeEnabled(options.snapResizeEnabled ?? this.isSnapResizeEnabled());\n this.setSnapThreshold(options.snapThreshold ?? this.getSnapThreshold());\n this.setSnapModifier(options.snapModifier ?? this.getSnapModifier());\n\n this.setConstrainToViewport(options.constrainToViewport ?? this.isConstrainToViewport());\n\n return this;\n }\n\n // ----- subclass hooks (what differs per subclass) -----\n\n /**\n * Installs the move gesture that lets the user drag the window. The\n * subclass wires the press source (a header's `mousedown`, an empty\n * tab-bar press, …) to {@link AbstractWindow.startMoveFrom}. Called once\n * from `initChrome`.\n */\n protected abstract wireMoveTrigger(): void;\n\n /**\n * Pushes the closeable state into the subclass UI (a header close button,\n * a control tool, …). Called by the non-virtual {@link AbstractWindow.setCloseable}\n * after the `_options.closeable` write.\n *\n * @param value - True when the window can be closed by the user.\n */\n protected abstract reflectCloseable(value: boolean): void;\n\n /**\n * Pushes the minimizable state into the subclass UI. Called by the\n * non-virtual {@link AbstractWindow.setMinimizable} after the\n * `_options.minimizable` write.\n *\n * @param value - True when the minimize affordance is shown.\n */\n protected abstract reflectMinimizable(value: boolean): void;\n\n /**\n * Pushes the maximizable state into the subclass UI. Called by the\n * non-virtual {@link AbstractWindow.setMaximizable} after the\n * `_options.maximizable` write.\n *\n * @param value - True when the maximize affordance is shown.\n */\n protected abstract reflectMaximizable(value: boolean): void;\n\n /**\n * Reflects a window-state transition into the subclass UI — typically the\n * maximize/restore glyph swap. Called from {@link AbstractWindow.setWindowState}\n * as the state changes.\n *\n * @param state - The state being entered.\n */\n protected abstract reflectMaximizeState(state: WindowState): void;\n\n /**\n * Paints the active/inactive appearance (e.g. a header gradient). Called\n * from {@link AbstractWindow.onActivate}; the active *state* itself is\n * generic and lives on the base.\n *\n * @param active - True when this window is the active layer.\n */\n protected abstract paintActive(active: boolean): void;\n\n /**\n * Reads the window's title for serialization and any future title reader.\n * Read-only by design — there is no title *writer* on the base (a derived\n * title, e.g. a tab label, cannot be set).\n *\n * @returns The current title text.\n */\n abstract getTitle(): string;\n\n /**\n * Returns the min-content-width value seeded into the default min size by\n * `initChrome` (a header's required width, a tab bar's\n * min width, …).\n *\n * @returns The seed width in pixels.\n */\n protected abstract minContentWidthSeed(): number;\n\n /**\n * Returns the height of the window's title chrome, consulted by the generic\n * viewport-clamp / dock-rect / minimized-stack geometry. Defaulted to `0`;\n * the {@link CHROME_HEIGHT_FLOOR_PX} \"before the chrome laid out\" floor is\n * applied at each call site, not here. Subclasses override to return their\n * real chrome height.\n *\n * @returns The chrome height in pixels.\n */\n protected chromeHeight(): number {\n return 0;\n }\n\n /**\n * Returns the window's intrinsic chrome minimum — the smallest outer size\n * that still shows the title chrome (header / control tools / tab strip)\n * without crushing it. Consulted by {@link setWidth} / {@link setHeight} as\n * the resize floor instead of {@link Component.getMinSize}, which folds in\n * the body content's layout-manager minimum. A window is a `Container`\n * ({@link Container.clampsToContentSize} is `false`), so oversized body\n * content overflows rather than inflating the window past this chrome floor.\n *\n * Built from the two chrome seeds the subclasses already expose —\n * {@link minContentWidthSeed} and {@link chromeHeight} — converted from a\n * content-min to an outer-window min by adding the perimeter the body inset\n * folds into the resize-border band (mirroring {@link doLayout}'s\n * outer→inner arithmetic, which adds a single inset side per axis).\n *\n * @returns The chrome-only minimum outer size in pixels.\n */\n protected chromeMinSize(): Size {\n const border = this.getBorderSize();\n const insets = this.getInsets();\n\n const horizontalChrome = (Number(border.left) || 0) + (Number(border.right) || 0) + insets.getLeft();\n const verticalChrome = (Number(border.top) || 0) + (Number(border.bottom) || 0) + insets.getTop();\n\n return {\n width: this.minContentWidthSeed() + horizontalChrome,\n height: (this.chromeHeight() || CHROME_HEIGHT_FLOOR_PX) + verticalChrome,\n };\n }\n\n /**\n * Adds a content component to the window. The subclass decides how (a\n * `Border` CENTER region, a `Tab` entry, …).\n *\n * @param content - The content component to add.\n */\n protected abstract addContent(content: Component): void;\n\n /**\n * Reports whether `child` is the window's chrome rather than its content.\n * Used by the generic body-host discovery to skip the chrome. Defaulted to\n * `false` (every child is content); subclasses with a chrome child override.\n *\n * @param _child - The child component to classify.\n * @returns True when `child` is chrome.\n */\n isChromeComponent(_child: Component): boolean {\n return false;\n }\n\n /**\n * Sets the window width, clamping to the `chromeMinSize` floor so border\n * drags can't shrink the window below the chrome's required width (icon,\n * title budget, control tools). Oversized body content overflows per the\n * `Container` policy rather than holding the window open.\n *\n * @param width - Requested width in pixels.\n *\n * @returns This component, for method chaining.\n *\n * @remarks Clamps to the chrome minimum, NOT `getMinSize` — the latter\n * folds in the layout manager's (body content's) min, which would let a\n * tall/wide child hold the window open and contradicts\n * `Container.clampsToContentSize` being `false`. An explicit consumer\n * `minSize` is still enforced separately by `Component.setWidth`'s private\n * `clampWidth`, so a caller-set floor remains honoured. The clamp is\n * skipped until the window is rendered: `chromeMinSize` consults subclass\n * chrome (the header / strip) that does not exist yet while `applyOptions`\n * cascades `width` during `super()` — the same pre-render window in which\n * the setter already defers its DOM write.\n */\n setWidth(width: number): this {\n if (this.getElement()) {\n const min = this.chromeMinSize();\n\n if (width < min.width) {\n width = min.width;\n }\n }\n\n return super.setWidth(width);\n }\n\n /**\n * Sets the window height, clamping to the `chromeMinSize` floor for the\n * same reason described on {@link setWidth}.\n *\n * @param height - Requested height in pixels.\n *\n * @returns This component, for method chaining.\n */\n setHeight(height: number): this {\n if (this.getElement()) {\n const min = this.chromeMinSize();\n\n if (height < min.height) {\n height = min.height;\n }\n }\n\n return super.setHeight(height);\n }\n\n /**\n * Appends the window element to the document root, triggers layout, and makes it visible.\n *\n * @remarks When a content factory has been registered via\n * {@link AbstractWindow.setContentFactory}, the window opens with a centred\n * `ProgressSpinner` in its content area and the factory is invoked behind\n * a two-rAF yield via [`Animation.materialize`](/api/core/namespaces/Animation/functions/materialize).\n * The entrance scale-in and the spinner pause run concurrently, so by the\n * time the window finishes scaling in the spinner is already on screen.\n */\n show(): this {\n const el = this.getElement(true)!;\n\n // Join the central layer tree before bringToFront so the manager has\n // a node to re-stamp. A dropdown opened inside the window then\n // registers as the window's child and stacks above it.\n LayerManager.register(this);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the floating window.\n trapWheel(this);\n\n this.doLayout();\n this.bringToFront();\n\n AbstractWindow.openWindows.add(this);\n\n LayerManager.mount(el);\n\n this.setVisible(true);\n\n this._showAnimation?.cancel();\n this._showAnimation = Animation.play(el, {\n from: { opacity: \"0\", transform: \"scale(0.97)\" },\n to: { opacity: \"1\", transform: \"scale(1)\" },\n durationMs: WINDOW_ANIM_DURATION_MS,\n properties: [\"opacity\", \"transform\"],\n });\n\n if (this._contentFactory) {\n const factory = this._contentFactory;\n const onReady = this._contentReadyCallback;\n this._contentFactory = null;\n this._contentReadyCallback = null;\n\n const spinner = createSpinnerWrap();\n\n this._materializeAnimation?.cancel();\n this._materializeAnimation = Animation.materialize({\n host: this,\n factory: factory,\n spinnerComponent: spinner,\n onReady: (component: Component) => {\n this._bodyHost = component;\n if (onReady) {\n onReady(component);\n }\n }\n });\n } else {\n this._bodyHost = this.findBodyHost();\n }\n\n if (this.isSnapResizeEnabled()) {\n this.attachSnapKeyboardListeners();\n }\n\n return this;\n }\n\n /**\n * Registers a factory that produces the window's content on first paint\n * instead of at construction time, and optionally a callback to run once\n * the produced component is attached, laid out, and faded in.\n *\n * @param factory - Zero-argument function returning the content component.\n * @param onReady - Optional callback fired after the content fade-in\n * completes (or immediately, under `prefers-reduced-motion: reduce`).\n * Receives the component the factory returned, fully attached and sized.\n *\n * @returns This window, for method chaining.\n *\n * @remarks Use when the content tree is expensive to build. `show()`\n * opens the window immediately with a spinner in the content area and\n * invokes the factory after a two-rAF yield via\n * [`Animation.materialize`](/api/core/namespaces/Animation/functions/materialize),\n * so the open-animation reaches the screen before the main-thread build\n * cost is incurred. Calling `show()` without a factory keeps the eager\n * `addComponent` lifecycle unchanged.\n *\n * Use the `onReady` callback for work that must happen after the content\n * is on screen and sized — for example kicking off an async data load\n * whose loading spinner is rendered by the content tree itself. Running\n * such work before `onReady` would emit `loadingchange: true` before\n * the panel had subscribed (or before the target had a size).\n *\n * @example\n * ```typescript\n * const win = new Window(\"Heavy\");\n * win.setSize({ width: 800, height: 600 });\n * win.setContentFactory(\n * () => new TablePanel(store),\n * () => void store.load()\n * );\n * win.show();\n * ```\n */\n setContentFactory(\n factory: () => Component,\n onReady?: (component: Component) => void\n ): this {\n this._contentFactory = factory;\n this._contentReadyCallback = onReady ?? null;\n\n return this;\n }\n\n /**\n * Raises this window above all other windows by assigning it the highest z-index,\n * and marks it as the active window, updating the title bar appearance on both\n * the previously active window and this one.\n */\n bringToFront(): void {\n // Route the raise through the manager: it re-allocates the z-stamp\n // (mirrored back via onZIndexChanged) and marks this window active,\n // which deactivates the previously-active window's title bar through\n // onActivate. No local activeWindow / setActive bookkeeping is needed.\n LayerManager.bringToFront(this);\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the window's root element for the central layer tree.\n *\n * @returns The window'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. A window\n * is never dismissed by an outside interaction, so it stays `\"manual\"`.\n * Activation (the title-bar highlight) is orthogonal to dismissal — the\n * manager drives it through {@link AbstractWindow.onActivate} for any mode.\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return \"manual\";\n }\n\n /**\n * Reflects the active state onto the window chrome. The manager calls this\n * with `true` when a pointer / focus interaction lands inside the window\n * (or a layer opened inside it) and `false` when another layer takes over\n * or an empty-viewport click deactivates everything. The active *state* is\n * generic; the paint is delegated to `paintActive`.\n *\n * @param active - True when this window is the active layer.\n */\n onActivate(active: boolean): void {\n this.paintActive(active);\n\n if (active) {\n this.focusSelf();\n this.emit(\"activate\");\n }\n }\n\n /**\n * Moves keyboard focus to the window when it is activated, unless focus is\n * already inside it (so a click that lands on a child input keeps that\n * input focused rather than yanking focus up to the window root). Uses\n * `preventScroll` because the window is an absolutely-positioned overlay —\n * a native focus scroll would jolt any `overflow: hidden` ancestor.\n */\n private focusSelf(): void {\n const element = this.getElement();\n if (!element || DOM.source.contains(element, DOM.source.getActiveElement())) {\n return;\n }\n\n this.focus(true);\n }\n\n /**\n * Advisory close request from the manager. A window owns its own close\n * affordance, so this routes to the same teardown.\n */\n requestClose(): void {\n this.onExitAction();\n }\n\n /**\n * Returns the window's z-index band so unrelated windows stack beneath\n * popovers, dropdowns, and dialogs.\n *\n * @returns The window band base.\n */\n getBand(): number {\n return LayerManager.Band.Window;\n }\n\n /**\n * Windows are independent top-level peers, not layers opened from one\n * another, so each registers as a tree root. This keeps raising one window\n * from dragging another up with it: {@link LayerManager.bringToFront}\n * re-stamps a node together with its descendants, which is correct for a\n * dropdown nested inside a window but wrong for two sibling windows.\n *\n * @returns Always `true`.\n */\n isLayerRoot(): boolean {\n return true;\n }\n\n /**\n * Mirrors a manager-allocated z-index onto the element when the window\n * (or a layer opened inside it) is raised via\n * {@link LayerManager.bringToFront}.\n *\n * @param zIndex - The fresh z-index assigned by the manager.\n */\n onZIndexChanged(zIndex: number): void {\n this.setZIndex(zIndex);\n }\n\n /**\n * Hides the window and destroys its DOM element when the close button is clicked.\n */\n onExitAction(): void {\n // Notify subscribers (e.g. a Rail) before teardown, so a rail handle\n // representing this window is removed as the window closes.\n this.emit(\"close\");\n\n if (this._animationFrameId !== null) {\n DOM.sink.cancelAnimationFrame(this._animationFrameId);\n this._animationFrameId = null;\n }\n\n this._stateAnimHandle?.cancel();\n this._stateAnimHandle = null;\n\n // Drop any pending factory / onReady closure so its captured\n // references are free for GC if the window is closed before show()\n // ran the factory.\n this._contentFactory = null;\n this._contentReadyCallback = null;\n\n this.detachSnapKeyboardListeners();\n this.detachSnapMouseListeners();\n this.detachViewportResizeListener();\n this.clearSnapTargetBorder();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n AbstractWindow.openWindows.delete(this);\n\n // Re-layout the dock so any sibling minimized windows close any gap\n // this one's removal left behind.\n AbstractWindow.relayoutMinimizedStack();\n\n const el = this.getElement();\n const finalize = (): void => {\n this.setVisible(false);\n this.destructor();\n };\n\n if (!el) {\n finalize();\n return;\n }\n\n this._closeAnimation?.cancel();\n this._closeAnimation = Animation.play(el, {\n to: { opacity: \"0\", transform: \"scale(0.97)\" },\n durationMs: WINDOW_ANIM_DURATION_MS,\n properties: [\"opacity\", \"transform\"],\n onComplete: finalize,\n });\n }\n\n /**\n * Tears the eight resize-border strips down before the inherited destructor\n * runs. They are appended straight to the window element by `renderContent`\n * rather than registered as child components, so the base destructor's\n * recursion over the child list never reaches them — leaving each strip's\n * per-instance stylesheet rules on the shared sheet for the life of the\n * page, and the sheet growing with every open/close cycle.\n */\n protected destructor(): void {\n // Before `super.destructor()` releases this window's element handle,\n // which every one of these animations' fallback timers would write to.\n this._stateAnimHandle?.cancel();\n this._stateAnimHandle = null;\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._materializeAnimation?.cancel();\n this._materializeAnimation = null;\n this._closeAnimation?.cancel();\n this._closeAnimation = null;\n this._railCollapseAnimation?.cancel();\n this._railCollapseAnimation = null;\n this._railExpandAnimation?.cancel();\n this._railExpandAnimation = null;\n\n for (const border of Object.values(this._borderComponents)) {\n border.dispose();\n }\n\n super.destructor();\n }\n\n /**\n * Returns the current lifecycle state (`\"normal\"`, `\"minimized\"`, or\n * `\"maximized\"`).\n *\n * @returns The current {@link WindowState}.\n */\n getWindowState(): WindowState {\n return this._options.windowState ?? this._defaultOptions.windowState!;\n }\n\n /**\n * Returns a snapshot array of every currently-open window. Used by layout\n * serialization to capture the floating-window plane, which is mounted on\n * `document.documentElement` rather than inside any container tree.\n *\n * @returns The open windows, in insertion order.\n */\n static getOpenWindows(): AbstractWindow[] {\n return Array.from(AbstractWindow.openWindows);\n }\n\n /**\n * Returns the window's current rectangle in viewport pixels. The public form\n * of the internal current-rect read, for layout serialization.\n *\n * @returns The `{ x, y, width, height }` the window currently occupies.\n */\n getRect(): WindowRect {\n return this.currentRect();\n }\n\n /**\n * Applies a rectangle captured by {@link getRect}, in viewport pixels.\n * `setWidth`/`setHeight` clamp against the window's minimum size and the\n * position is constrained to the viewport on the next layout, so a saved\n * rect from a larger or differently-shaped screen is pulled back into view.\n *\n * @param rect - The `{ x, y, width, height }` to apply.\n * @returns This window, for method chaining.\n */\n applyRect(rect: WindowRect): this {\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n\n return this;\n }\n\n /**\n * Returns the cached normal-state rectangle a minimized or maximized window\n * un-collapses to, or `null` when the window is in its normal state.\n * Captured by serialization so a window serialized while minimized or\n * maximized round-trips back to the right normal geometry — on restore the\n * normal rect is applied first and `setWindowState` re-caches it.\n *\n * @returns The restore rect, or `null`.\n */\n getRestoreRect(): WindowRect | null {\n return this._restoreRect;\n }\n\n /**\n * Sets the lifecycle state. Tweens geometry between the current rect and\n * the rect implied by the target state over `WINDOW_ANIM_DURATION_MS`.\n * Under `prefers-reduced-motion: reduce` the tween collapses to a single\n * synchronous commit.\n *\n * @param state - One of `\"normal\"`, `\"minimized\"`, or `\"maximized\"`.\n *\n * @returns This window, for method chaining.\n *\n * @remarks Round-trips through `_restoreRect`: leaving `\"normal\"` caches\n * the current rect; returning to `\"normal\"` reads it back and clears the\n * cache. While minimized or maximized, drag and resize are suppressed\n * (see `startMoveFrom` and `onResize` early-returns).\n */\n setWindowState(state: WindowState): this {\n const from = this.getWindowState();\n if (from === state) {\n return this;\n }\n\n // If a drag is in flight, commit it first so `_restoreRect` captures\n // the post-drag position instead of the stale start position.\n if (this._animationFrameId !== null) {\n DOM.sink.cancelAnimationFrame(this._animationFrameId);\n this._animationFrameId = null;\n }\n\n this._options.windowState = state;\n\n // A rail-minimized window is hidden outright (the rail handle is its\n // minimized representation), so re-show it and play the reverse genie —\n // scaling/fading back up out of the rail — before the state branch runs.\n if (from === \"minimized\" && this._rail !== null) {\n this.setDisplayed(true);\n this.animateRailExpand();\n }\n\n if (state === \"normal\") {\n // Restore body visibility BEFORE the tween starts so the\n // animation plays against the body content, not against an\n // empty rect.\n this.setBodyHostDisplayed(true);\n this.reflectMaximizeState(\"normal\");\n\n const target = this._restoreRect ?? this.currentRect();\n this._restoreRect = null;\n\n this.detachViewportResizeListener();\n this.animateRect(target, () => AbstractWindow.relayoutMinimizedStack());\n } else if (state === \"minimized\") {\n if (from === \"normal\") {\n this._restoreRect = this.currentRect();\n }\n this._preMinimizeState = from === \"maximized\" ? \"maximized\" : \"normal\";\n this.reflectMaximizeState(\"minimized\");\n this.detachViewportResizeListener();\n\n if (this._rail !== null) {\n // Rail-docked: skip the built-in bottom strip. Genie the window\n // — scaling and fading it into the rail's handle corner — then\n // hide it outright and let the rail show its handle (via the\n // deferred \"minimize\" event). The geometry is left untouched, so\n // the reverse genie on restore replays from the same rect.\n this.animateRailCollapse(() => {\n this.setDisplayed(false);\n this.emit(\"minimize\");\n });\n } else {\n const target = this.computeDockRect();\n this.animateRect(target, () => {\n this.setBodyHostDisplayed(false);\n AbstractWindow.relayoutMinimizedStack();\n });\n\n this.emit(\"minimize\");\n }\n } else {\n // maximized\n if (from === \"normal\") {\n this._restoreRect = this.currentRect();\n }\n this.setBodyHostDisplayed(true);\n this.reflectMaximizeState(\"maximized\");\n\n const target = this.computeMaximizeRect();\n this.animateRect(target, () => {\n this.attachViewportResizeListener();\n AbstractWindow.relayoutMinimizedStack();\n });\n }\n\n // Notify subscribers (e.g. a Rail) when the window leaves the minimized\n // state. The \"minimize\" counterpart is emitted from the branch above —\n // deferred to the rail fly-in's completion on the rail path.\n if (from === \"minimized\" && state !== \"minimized\") {\n this.emit(\"restore\");\n }\n\n return this;\n }\n\n /**\n * Returns whether the window is currently in the `\"maximized\"` state.\n *\n * @returns True when the current state is `\"maximized\"`.\n */\n isMaximized(): boolean {\n return this.getWindowState() === \"maximized\";\n }\n\n /**\n * Returns whether the window is currently in the `\"minimized\"` state.\n *\n * @returns True when the current state is `\"minimized\"`.\n */\n isMinimized(): boolean {\n return this.getWindowState() === \"minimized\";\n }\n\n /**\n * Toggles between `\"normal\"` and `\"minimized\"`. No-ops when the window is\n * not minimizable, or when transitioning from `\"maximized\"` (use\n * `setWindowState(\"minimized\")` directly for that path).\n */\n toggleMinimize(): void {\n if (!this.isMinimizable()) {\n return;\n }\n\n if (this.getWindowState() === \"minimized\") {\n this.setWindowState(\"normal\");\n } else {\n this.setWindowState(\"minimized\");\n }\n }\n\n /**\n * Minimizes the window. Sugar over `setWindowState(\"minimized\")`.\n *\n * @returns This window, for method chaining.\n */\n minimize(): this {\n return this.setWindowState(\"minimized\");\n }\n\n /**\n * Restores the window from a minimized state to whatever it was before\n * (`\"normal\"` or `\"maximized\"`) and brings it to the front so it becomes\n * the active, focused window. No-op when the window is not minimized.\n *\n * @remarks Restoring is typically driven from a rail handle — a click\n * outside the window, which never activates it on its own. The window also\n * usually stays the layer manager's active layer while minimized (nothing\n * else took over), so `bringToFront` is an activation no-op and `onActivate`\n * never re-fires. Both the raise and the keyboard focus are therefore made\n * explicit here.\n *\n * @returns This window, for method chaining.\n */\n restore(): this {\n if (!this.isMinimized()) {\n return this;\n }\n\n this.setWindowState(this._preMinimizeState);\n this.bringToFront();\n this.focus(true);\n\n return this;\n }\n\n /**\n * Attaches a {@link Rail} this window minimizes into, replacing the built-in\n * bottom-of-viewport dock strip: while minimized the window is hidden and\n * represented by a handle on the rail that restores it on click. Pass `null`\n * to detach and fall back to the built-in strip. The rail subscribes to the\n * window's minimize / restore / close events.\n *\n * @param rail - The rail to minimize into, or `null` to detach.\n *\n * @returns This window, for method chaining.\n */\n setRail(rail: Rail | null): this {\n if (this._rail === rail) {\n return this;\n }\n\n if (this._rail !== null) {\n this._rail.unregisterWindow(this);\n }\n\n this._rail = rail;\n\n if (rail !== null) {\n rail.registerWindow(this);\n }\n\n return this;\n }\n\n /**\n * Returns the rail this window minimizes into, or `null` when it uses the\n * built-in bottom dock strip.\n *\n * @returns The attached rail, or `null`.\n */\n getRail(): Rail | null {\n return this._rail;\n }\n\n /**\n * Returns the window's leading glyph, or `undefined` when none was set. Read\n * by a {@link Rail} to label the handle for a window minimized into it.\n *\n * @returns The glyph name, or `undefined`.\n */\n getGlyph(): string | undefined {\n return this._options.glyph;\n }\n\n /**\n * Registers a listener for one of the window's lifecycle events.\n *\n * @param event - `\"minimize\"` / `\"restore\"` / `\"close\"` / `\"activate\"`.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This window, for method chaining.\n */\n on(event: WindowEvent, listener: () => void): this {\n this._windowListeners.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 window, for method chaining.\n */\n off(event: WindowEvent, listener: () => void): this {\n this._windowListeners.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 */\n protected emit(event: WindowEvent): void {\n this._windowListeners.fire(event);\n }\n\n /**\n * Toggles between `\"normal\"` and `\"maximized\"`. No-ops when the window is\n * not maximizable.\n */\n toggleMaximize(): void {\n if (!this.isMaximizable()) {\n return;\n }\n\n if (this.getWindowState() === \"maximized\") {\n this.setWindowState(\"normal\");\n } else {\n this.setWindowState(\"maximized\");\n }\n }\n\n /**\n * Enables or disables the close affordance. When disabled the window can no\n * longer be torn down by the user — only programmatically via\n * {@link requestClose} — so its content cannot be destroyed by a stray\n * click. Minimize / maximize are unaffected. Stores the state and reflects\n * it into the subclass UI via `reflectCloseable`.\n *\n * @param value - True to enable the close affordance.\n *\n * @returns This window, for method chaining.\n */\n setCloseable(value: boolean): this {\n this._options.closeable = value;\n this.reflectCloseable(value);\n\n return this;\n }\n\n /**\n * Returns whether the close affordance is enabled.\n *\n * @returns True when the close affordance is enabled.\n */\n isCloseable(): boolean {\n return this._options.closeable ?? this._defaultOptions.closeable!;\n }\n\n /**\n * Toggles whether the minimize affordance is shown. Stores the state and\n * reflects it via `reflectMinimizable`.\n *\n * @param value - True to show the minimize affordance.\n *\n * @returns This window, for method chaining.\n */\n setMinimizable(value: boolean): this {\n this._options.minimizable = value;\n this.reflectMinimizable(value);\n\n return this;\n }\n\n /**\n * Returns whether the minimize affordance is shown.\n *\n * @returns True when the minimize affordance is shown.\n */\n isMinimizable(): boolean {\n return this._options.minimizable ?? this._defaultOptions.minimizable!;\n }\n\n /**\n * Toggles whether the maximize affordance is shown. Stores the state and\n * reflects it via `reflectMaximizable`.\n *\n * @param value - True to show the maximize affordance.\n *\n * @returns This window, for method chaining.\n */\n setMaximizable(value: boolean): this {\n this._options.maximizable = value;\n this.reflectMaximizable(value);\n\n return this;\n }\n\n /**\n * Returns whether the maximize affordance is shown.\n *\n * @returns True when the maximize affordance is shown.\n */\n isMaximizable(): boolean {\n return this._options.maximizable ?? this._defaultOptions.maximizable!;\n }\n\n /**\n * Sets the rect the window fills when entering the `\"maximized\"` state.\n *\n * @param value - `\"viewport\"` to fill the browser viewport,\n * `\"parent\"` to fill the window element's parent rect.\n *\n * @returns This window, for method chaining.\n */\n setMaximizeBounds(value: WindowMaximizeBounds): this {\n this._options.maximizeBounds = value;\n\n return this;\n }\n\n /**\n * Returns the current maximize-bounds policy.\n *\n * @returns Either `\"viewport\"` or `\"parent\"`.\n */\n getMaximizeBounds(): WindowMaximizeBounds {\n return this._options.maximizeBounds ?? this._defaultOptions.maximizeBounds!;\n }\n\n /**\n * Enables or disables the Ctrl-snap resize affordance. When disabled, the\n * keyboard listeners are detached and the snap-target border (if any) is\n * cleared.\n *\n * @param value - True to enable snap-to-edge detection.\n *\n * @returns This window, for method chaining.\n */\n setSnapResizeEnabled(value: boolean): this {\n if (this._options.snapResizeEnabled === value) {\n return this;\n }\n\n this._options.snapResizeEnabled = value;\n\n if (!value) {\n this.clearSnapState();\n this.detachSnapKeyboardListeners();\n } else if (this.getElement()) {\n this.attachSnapKeyboardListeners();\n }\n\n return this;\n }\n\n /**\n * Returns whether the snap-resize affordance is enabled.\n *\n * @returns True when snap-resize is enabled.\n */\n isSnapResizeEnabled(): boolean {\n return this._options.snapResizeEnabled ?? this._defaultOptions.snapResizeEnabled!;\n }\n\n /**\n * Controls how far a window may be dragged. When enabled (the default) the\n * whole window is kept inside the viewport — every border stops at the\n * viewport edge. When disabled the window may travel off-screen but its\n * header stays grabbable, so it can never be dropped fully out of reach.\n *\n * @param value - True to keep the entire window inside the viewport.\n *\n * @returns This window, for method chaining.\n */\n setConstrainToViewport(value: boolean): this {\n this._options.constrainToViewport = value;\n\n return this;\n }\n\n /**\n * Returns whether the whole window is constrained to the viewport while dragging.\n *\n * @returns True when the entire window is kept inside the viewport.\n */\n isConstrainToViewport(): boolean {\n return this._options.constrainToViewport ?? this._defaultOptions.constrainToViewport!;\n }\n\n /**\n * Sets the cursor-to-edge distance (in pixels) under which a border strip\n * is treated as the snap target while the modifier key is held.\n *\n * @param px - Threshold distance in pixels.\n *\n * @returns This window, for method chaining.\n */\n setSnapThreshold(px: number): this {\n this._options.snapThreshold = px;\n\n return this;\n }\n\n /**\n * Returns the current snap-detection threshold in pixels.\n *\n * @returns The threshold in pixels.\n */\n getSnapThreshold(): number {\n return this._options.snapThreshold ?? this._defaultOptions.snapThreshold!;\n }\n\n /**\n * Sets the modifier key that activates snap-resize detection.\n *\n * @param key - `\"ctrl\"`, `\"meta\"`, `\"alt\"`, or `\"shift\"`.\n *\n * @returns This window, for method chaining.\n */\n setSnapModifier(key: WindowSnapModifier): this {\n this._options.snapModifier = key;\n\n return this;\n }\n\n /**\n * Returns the modifier key currently activating snap-resize detection.\n *\n * @returns One of `\"ctrl\"`, `\"meta\"`, `\"alt\"`, `\"shift\"`.\n */\n getSnapModifier(): WindowSnapModifier {\n return this._options.snapModifier ?? this._defaultOptions.snapModifier!;\n }\n\n /**\n * Handles a window-move press by delegating to {@link AbstractWindow.startMoveFrom}.\n * Wired by each subclass's `wireMoveTrigger` to the\n * appropriate press source.\n *\n * @param e - The mousedown event whose pointer coordinate anchors the drag.\n */\n onMouseDown(e: MouseEvent): void {\n this.startMoveFrom(e);\n }\n\n /**\n * Begins a window move from a press anywhere a subclass routes here.\n * Snapshots the start position and pointer origin, pre-promotes a\n * compositor layer, and registers the viewport move/up listeners. No-ops\n * for a Shift-modified press (reserved for re-dock gestures) or when the\n * window is not in the `\"normal\"` state.\n *\n * @param e - The mousedown event whose pointer coordinate anchors the drag.\n *\n * @remarks The event argument is required — `e.clientX`/`e.clientY` seed the\n * absolute-pointer drag origin read by `onDrag`.\n */\n startMoveFrom(e: MouseEvent): void {\n // Shift+drag is a re-dock gesture (handled by a subclass drag source),\n // not a window move — don't start the move. (Ctrl is left free for the\n // snap-resize affordance.)\n if (e.shiftKey) {\n return;\n }\n\n if (this.getWindowState() !== \"normal\") {\n return;\n }\n\n // Snapshot the start position and pointer origin so onDrag derives the move from\n // (current pointer - origin) absolutely rather than accumulating per-move deltas,\n // and onMouseUp can commit (start + delta) back to left/top.\n this._dragStartLeft = this.getX();\n this._dragStartTop = this.getY();\n this._dragOriginClientX = e.clientX;\n this._dragOriginClientY = e.clientY;\n this._dragDX = 0;\n this._dragDY = 0;\n\n // Seed the per-axis reach ratchet at the start position so the first\n // frame's allowed range spans the origin (no snap on grab) and tightens\n // from there.\n this._dragReachMinX = this._dragStartLeft;\n this._dragReachMaxX = this._dragStartLeft;\n this._dragReachMinY = this._dragStartTop;\n this._dragReachMaxY = this._dragStartTop;\n\n // Pre-promote to a compositor layer so the first mousemove translate doesn't\n // pay a layer-creation cost mid-drag. Released in onMouseUp.\n this.setWillChange(\"transform\");\n\n // Viewport listeners are required (rather than direct document listeners) because\n // Event.baseViewportListener stops mouseup propagation at window capture phase\n // whenever any viewport listener for the type exists (e.g. SpinButton registers\n // one at construction), which would prevent document-level handlers from firing.\n Event.addViewportListener(this, 'mouseup', this._boundOnMouseUp);\n Event.addViewportListener(this, 'mousemove', this._boundOnDrag);\n }\n\n /**\n * Adjusts the window's position and size based on the dragged border direction.\n *\n * @param border - The border handle that triggered the resize.\n * @param e - The mouse event carrying the absolute pointer coordinate.\n *\n * @remarks On the first move of a drag session the window's origin geometry\n * (pointer `clientX`/`clientY`, position, and size) is captured so that\n * `flushResize` can derive the new size from `origin + offset` rather\n * than accumulating per-move deltas. `WindowBorder` exposes no mousedown\n * hook to the window, so the capture is lazy and a viewport `mouseup`\n * listener clears the session flag when the drag ends.\n */\n onResize(border: WindowBorder, e: MouseEvent): void {\n if (this.getWindowState() !== \"normal\") {\n return;\n }\n\n e.preventDefault();\n\n if (!this._resizeSessionActive) {\n this._resizeSessionActive = true;\n this._resizeOriginClientX = e.clientX;\n this._resizeOriginClientY = e.clientY;\n this._resizeOriginX = this.getX();\n this._resizeOriginY = this.getY();\n this._resizeOriginW = this.getWidth();\n this._resizeOriginH = this.getHeight();\n\n Event.addViewportListener(this, 'mouseup', this._boundOnResizeEnd);\n Event.addViewportListener(this, 'touchend', this._boundOnResizeEnd);\n Event.addViewportListener(this, 'touchcancel', this._boundOnResizeEnd);\n }\n\n this._pendingClientX = e.clientX;\n this._pendingClientY = e.clientY;\n this._pendingBorder = border;\n\n if (this._animationFrameId === null) {\n this._animationFrameId = DOM.sink.requestAnimationFrame((ts) => this.flushResize(ts));\n }\n }\n\n /**\n * Clears the resize-session origin capture when a border drag ends, so the\n * next drag re-captures a fresh origin. Detaches the viewport listeners it\n * was registered with.\n *\n * @returns `true`, consuming the release that ends the border resize.\n */\n private onResizeEnd(): Event.ListenerResult {\n this._resizeSessionActive = false;\n\n Event.removeViewportListener(this, 'mouseup', this._boundOnResizeEnd);\n Event.removeViewportListener(this, 'touchend', this._boundOnResizeEnd);\n Event.removeViewportListener(this, 'touchcancel', this._boundOnResizeEnd);\n\n return true;\n }\n\n /**\n * Returns the maximum number of layout passes per second during a resize drag.\n *\n * @returns The current frames-per-second cap.\n */\n getResizeFps(): number {\n return this._resizeFps;\n }\n\n /**\n * Sets the maximum number of layout passes per second during a resize drag.\n *\n * @param fps - Frames per second cap (e.g. 30 or 20). Defaults to 60.\n *\n * @returns This window, for method chaining.\n */\n setResizeFps(fps: number): this {\n this._resizeFps = fps;\n\n return this;\n }\n\n /**\n * Commits a throttled resize frame: derives the new geometry from the\n * captured origin and pointer offset, clamps it to the viewport edge, and\n * lays out. Re-schedules itself when called inside the per-frame interval.\n *\n * @param timestamp - The rAF timestamp for this frame.\n */\n private flushResize(timestamp: number): void {\n if (timestamp - this._lastFlushTime < 1000 / this._resizeFps) {\n this._animationFrameId = DOM.sink.requestAnimationFrame((ts) => this.flushResize(ts));\n return;\n }\n\n this._lastFlushTime = timestamp;\n this._animationFrameId = null;\n\n const border = this._pendingBorder;\n\n this._pendingBorder = null;\n\n if (!border) {\n return;\n }\n\n // Offset of the pointer from where the drag began. The new size is\n // `origin ± offset` clamped by setWidth/setHeight; WEST/NORTH edges\n // additionally re-derive position from the *clamped* size so the\n // opposite (fixed) edge stays put and over-travel past the minimum is\n // absorbed instead of decoupling the dragged edge from the cursor.\n const offsetX = this._pendingClientX - this._resizeOriginClientX;\n const offsetY = this._pendingClientY - this._resizeOriginClientY;\n\n const originRight = this._resizeOriginX + this._resizeOriginW;\n const originBottom = this._resizeOriginY + this._resizeOriginH;\n\n // Viewport size caps so a dragged edge can't leave the screen. Each is the\n // largest size that keeps the moving edge inside the viewport given the fixed\n // opposite edge: east/south grow toward the far viewport edge, west/north grow\n // toward 0. Applied as an extra Math.min before setWidth/setHeight clamp to\n // their own min/max, so the WEST/NORTH position re-derivation below stays\n // consistent with the clamped size.\n const vp = DOM.source.getViewportSize();\n const eastWidthCap = vp.width - this._resizeOriginX;\n const westWidthCap = originRight;\n const southHeightCap = vp.height - this._resizeOriginY;\n const northHeightCap = originBottom;\n\n this.setAutoCommitStyle(false);\n switch (border.getDirection()) {\n case Direction.NORTHWEST:\n this.setWidth(Math.min(this._resizeOriginW - offsetX, westWidthCap));\n this.setHeight(Math.min(this._resizeOriginH - offsetY, northHeightCap));\n this.setX(originRight - this.getWidth());\n this.setY(originBottom - this.getHeight());\n\n break;\n case Direction.NORTH:\n this.setHeight(Math.min(this._resizeOriginH - offsetY, northHeightCap));\n this.setY(originBottom - this.getHeight());\n\n break;\n case Direction.NORTHEAST:\n this.setWidth(Math.min(this._resizeOriginW + offsetX, eastWidthCap));\n this.setHeight(Math.min(this._resizeOriginH - offsetY, northHeightCap));\n this.setY(originBottom - this.getHeight());\n\n break;\n case Direction.EAST:\n this.setWidth(Math.min(this._resizeOriginW + offsetX, eastWidthCap));\n\n break;\n case Direction.SOUTHEAST:\n this.setWidth(Math.min(this._resizeOriginW + offsetX, eastWidthCap));\n this.setHeight(Math.min(this._resizeOriginH + offsetY, southHeightCap));\n\n break;\n case Direction.SOUTH:\n this.setHeight(Math.min(this._resizeOriginH + offsetY, southHeightCap));\n\n break;\n case Direction.SOUTHWEST:\n this.setWidth(Math.min(this._resizeOriginW - offsetX, westWidthCap));\n this.setHeight(Math.min(this._resizeOriginH + offsetY, southHeightCap));\n this.setX(originRight - this.getWidth());\n\n break;\n case Direction.WEST:\n this.setWidth(Math.min(this._resizeOriginW - offsetX, westWidthCap));\n this.setX(originRight - this.getWidth());\n\n break;\n }\n\n this.doLayout();\n this.setAutoCommitStyle(true);\n }\n\n /**\n * The min/max top-left position that keeps the window inside the viewport,\n * honouring {@link isConstrainToViewport} (whole window in view) or the\n * header-reachable fallback (at least an `EDGE_MARGIN_PX` strip and the full\n * chrome band visible). Shared by drag clamping and {@link clampPositionToViewport}.\n *\n * @returns The position bounds in viewport pixels.\n */\n private viewportPositionBounds(): { minX: number; maxX: number; minY: number; maxY: number } {\n const w = this.getWidth();\n const vp = DOM.source.getViewportSize();\n const vw = vp.width;\n const vh = vp.height;\n\n if (this.isConstrainToViewport()) {\n // Whole window inside the viewport: every border stops at the edge.\n return { minX: 0, maxX: vw - w, minY: 0, maxY: vh - this.getHeight() };\n }\n\n // Header-reachable fallback: keep at least an EDGE_MARGIN_PX strip visible\n // horizontally and the whole chrome band visible vertically. The\n // CHROME_HEIGHT_FLOOR_PX floor covers the pre-layout window before the\n // chrome has measured a real height.\n const headerH = this.chromeHeight() || CHROME_HEIGHT_FLOOR_PX;\n\n return { minX: EDGE_MARGIN_PX - w, maxX: vw - EDGE_MARGIN_PX, minY: 0, maxY: vh - headerH };\n }\n\n /**\n * Clamps the pending drag delta so the window's top-left stays within\n * {@link viewportPositionBounds}. Mutates `_dragDX`/`_dragDY` in place so\n * both the live translate and the `onMouseUp` commit read the clamped\n * values from one chokepoint.\n */\n private clampDragDelta(): void {\n const { minX, maxX, minY, maxY } = this.viewportPositionBounds();\n\n // A window can start beyond a bound — larger than the viewport, or\n // opened with an edge off-screen. A plain [min, max] clamp would snap it\n // back to the bound on grab, jerking it from under the cursor. Instead\n // ratchet the allowed range from the reach extremes: the far end follows\n // the window as it travels toward the viewport (`max(maxBound, reachMin)`\n // floored at the real bound) so an off-screen edge can move in but never\n // back out — disallowing southward/eastward travel for as long as that\n // edge stays outside, even after reversing mid-drag. Symmetric on the\n // near end. For a window that started in-bounds the reach extremes sit\n // within [min, max], so this collapses to the plain clamp.\n const hiX = Math.max(maxX, this._dragReachMinX);\n const loX = Math.min(minX, this._dragReachMaxX);\n const hiY = Math.max(maxY, this._dragReachMinY);\n const loY = Math.min(minY, this._dragReachMaxY);\n\n const targetX = this._dragStartLeft + this._dragDX;\n const targetY = this._dragStartTop + this._dragDY;\n\n const clampedX = Util.clamp(targetX, loX, hiX);\n const clampedY = Util.clamp(targetY, loY, hiY);\n\n // Record the new reach so the next frame's range can only tighten.\n this._dragReachMinX = Math.min(this._dragReachMinX, clampedX);\n this._dragReachMaxX = Math.max(this._dragReachMaxX, clampedX);\n this._dragReachMinY = Math.min(this._dragReachMinY, clampedY);\n this._dragReachMaxY = Math.max(this._dragReachMaxY, clampedY);\n\n this._dragDX = clampedX - this._dragStartLeft;\n this._dragDY = clampedY - this._dragStartTop;\n }\n\n /**\n * Clamps the window's current position so the window sits inside the\n * viewport, by the same rule the move-drag uses. Call after positioning a\n * window programmatically — e.g. opening one at a tear-off release point that\n * may lie past the viewport edge — so it can never land off-screen. Set the\n * window's size first, since the bounds depend on it.\n *\n * @returns This window, for method chaining.\n */\n clampPositionToViewport(): this {\n const { minX, maxX, minY, maxY } = this.viewportPositionBounds();\n\n // High-first clamp (not Util.clamp): when the window is larger than the\n // viewport, maxX < minX, and this must pin the leading edge on-screen\n // (yield minX) rather than the low-first form's trailing edge (maxX).\n this.setX(Math.max(Math.min(this.getX(), maxX), minX));\n this.setY(Math.max(Math.min(this.getY(), maxY), minY));\n\n return this;\n }\n\n /**\n * Moves the window to follow the pointer while dragging.\n *\n * @param e - The mouse event carrying the absolute pointer coordinate.\n * @returns `{ stop: true, prevent: true }`, consuming the move and suppressing the browser's default text selection while dragging.\n */\n onDrag(e: MouseEvent): Event.ListenerResult {\n // Derive the delta from the absolute pointer offset (not accumulated movementX)\n // so clampDragDelta's writeback can't drift: when the window is pinned at an edge\n // the over-travel is absorbed and the window re-attaches to the cursor on reverse.\n this._dragDX = e.clientX - this._dragOriginClientX;\n this._dragDY = e.clientY - this._dragOriginClientY;\n\n this.clampDragDelta();\n\n // Compositor-only translate during drag; the cached left/top stay at the start\n // position so the field-DOM invariant holds (left === style.left throughout).\n this.setTranslate(this._dragDX, this._dragDY);\n\n return { stop: true, prevent: true };\n }\n\n /**\n * Detaches the document-level drag listeners when the mouse button is released.\n *\n * @returns `true`, consuming the release that ends the window drag.\n */\n onMouseUp(): Event.ListenerResult {\n // Commit the in-progress translate back to left/top so subsequent layout passes\n // operate from the new position. setTranslate(0, 0) frees the compositor layer.\n this.setX(this._dragStartLeft + this._dragDX);\n this.setY(this._dragStartTop + this._dragDY);\n this.setTranslate(0, 0);\n this.setWillChange(null);\n\n Event.removeViewportListener(this, 'mouseup', this._boundOnMouseUp);\n Event.removeViewportListener(this, 'mousemove', this._boundOnDrag);\n\n return true;\n }\n\n /**\n * Runs the border layout and positions all eight resize-handle strips around the window.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n let borderSize = this.getBorderSize();\n let insets = this.getInsets();\n let horisontallBorderWidth = (Number(borderSize.left) || 0) + (Number(borderSize.right) || 0) + insets.getLeft();\n let verticalBorderWidth = (Number(borderSize.top) || 0) + (Number(borderSize.bottom) || 0) + insets.getTop();\n let size = this.getSize();\n if (!size) {\n throw new Error(\"Component doesn't seem to be rendered.\");\n }\n\n let innerSize = this.getInnerSize();\n if (!innerSize) {\n throw new Error(\"Component doesn't seem to be rendered.\");\n }\n\n this._borderComponents.west.setAutoCommitStyle(false);\n this._borderComponents.northwest.setAutoCommitStyle(false);\n this._borderComponents.north.setAutoCommitStyle(false);\n this._borderComponents.northeast.setAutoCommitStyle(false);\n this._borderComponents.east.setAutoCommitStyle(false);\n this._borderComponents.southeast.setAutoCommitStyle(false);\n this._borderComponents.south.setAutoCommitStyle(false);\n this._borderComponents.southwest.setAutoCommitStyle(false);\n\n this._borderComponents.west.setX(0);\n this._borderComponents.west.setY(insets.getTop());\n this._borderComponents.west.setWidth(insets.getLeft());\n this._borderComponents.west.setHeight(innerSize.height);\n\n this._borderComponents.northwest.setX(0);\n this._borderComponents.northwest.setY(0);\n this._borderComponents.northwest.setWidth(insets.getLeft());\n this._borderComponents.northwest.setHeight(insets.getTop());\n\n this._borderComponents.north.setX(insets.getLeft());\n this._borderComponents.north.setY(0);\n this._borderComponents.north.setWidth(innerSize.width);\n this._borderComponents.north.setHeight(insets.getTop());\n\n this._borderComponents.northeast.setX(size.width - horisontallBorderWidth);\n this._borderComponents.northeast.setY(0);\n this._borderComponents.northeast.setWidth(insets.getRight());\n this._borderComponents.northeast.setHeight(insets.getTop());\n\n this._borderComponents.east.setX(size.width - horisontallBorderWidth);\n this._borderComponents.east.setY(insets.getTop());\n this._borderComponents.east.setWidth(insets.getRight());\n this._borderComponents.east.setHeight(innerSize.height);\n\n this._borderComponents.southeast.setX(size.width - horisontallBorderWidth);\n this._borderComponents.southeast.setY(size.height - verticalBorderWidth);\n this._borderComponents.southeast.setWidth(insets.getRight());\n this._borderComponents.southeast.setHeight(insets.getBottom());\n\n this._borderComponents.south.setX(insets.getLeft());\n this._borderComponents.south.setY(size.height - verticalBorderWidth);\n this._borderComponents.south.setWidth(innerSize.width);\n this._borderComponents.south.setHeight(insets.getRight());\n\n this._borderComponents.southwest.setX(0);\n this._borderComponents.southwest.setY(size.height - verticalBorderWidth);\n this._borderComponents.southwest.setWidth(insets.getLeft());\n this._borderComponents.southwest.setHeight(insets.getBottom());\n\n this._borderComponents.west.setAutoCommitStyle(true);\n this._borderComponents.northwest.setAutoCommitStyle(true);\n this._borderComponents.north.setAutoCommitStyle(true);\n this._borderComponents.northeast.setAutoCommitStyle(true);\n this._borderComponents.east.setAutoCommitStyle(true);\n this._borderComponents.southeast.setAutoCommitStyle(true);\n this._borderComponents.south.setAutoCommitStyle(true);\n this._borderComponents.southwest.setAutoCommitStyle(true);\n\n return this;\n }\n\n /**\n * Creates the window element and appends all eight resize-handle border elements.\n *\n * @returns The root HTMLElement for this window.\n */\n render(): Handle {\n let element = super.render();\n\n DOM.sink.appendChild(element, this._borderComponents.west.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.northwest.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.north.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.northeast.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.east.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.southeast.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.south.getElement(true)!);\n DOM.sink.appendChild(element, this._borderComponents.southwest.getElement(true)!);\n\n return element;\n }\n\n // ----- state-transition helpers -----\n\n /**\n * Returns the window's current rect from its live geometry fields.\n *\n * @returns The current `{ x, y, width, height }`.\n */\n private currentRect(): WindowRect {\n return {\n x: this.getX(),\n y: this.getY(),\n width: this.getWidth(),\n height: this.getHeight(),\n };\n }\n\n /**\n * Returns the first non-chrome child component, if any. Used to hide the\n * body while minimized so the docked strip shows only the title bar.\n *\n * @returns The body host component, or `null`.\n */\n protected findBodyHost(): Component | null {\n const children = this.getComponents();\n for (const child of children) {\n if (!this.isChromeComponent(child)) {\n return child;\n }\n }\n return null;\n }\n\n /**\n * Shows or hides the window's body host, finding it lazily on first use.\n *\n * @param displayed - True to show the body, false to hide it.\n */\n private setBodyHostDisplayed(displayed: boolean): void {\n if (!this._bodyHost) {\n this._bodyHost = this.findBodyHost();\n }\n if (this._bodyHost) {\n this._bodyHost.setDisplayed(displayed);\n }\n }\n\n /**\n * Computes the rect the window fills when maximized — the parent rect for\n * `\"parent\"` bounds, otherwise the full viewport.\n *\n * @returns The maximize target rect.\n */\n private computeMaximizeRect(): WindowRect {\n if (this.getMaximizeBounds() === \"parent\") {\n const el = this.getElement();\n const parent = el ? DOM.source.getParentElement(el) : null;\n if (parent && parent !== DOM.source.getDocumentElement()) {\n const r = DOM.source.getElementRect(parent);\n return { x: 0, y: 0, width: r.width, height: r.height };\n }\n }\n\n const vp = DOM.source.getViewportSize();\n\n return {\n x: 0,\n y: 0,\n width: vp.width,\n height: vp.height,\n };\n }\n\n /**\n * Computes the docked rect for a minimized window — a chrome-height slab\n * along the bottom of the viewport, in the window's dock slot.\n *\n * @returns The minimized dock rect.\n */\n private computeDockRect(): WindowRect {\n const dockWidth = this.getMinDockWidth();\n const slotIndex = this.computeDockSlotIndex();\n const headerHeight = this.chromeHeight() || CHROME_HEIGHT_FLOOR_PX;\n const x = slotIndex * (dockWidth + SNAP_DOCK_GAP_PX);\n const y = DOM.source.getViewportSize().height - headerHeight;\n\n return { x, y, width: dockWidth, height: headerHeight };\n }\n\n /**\n * Builds the genie `transform` that collapses the window into its own rail\n * handle: it translates the window onto the rail edge (cross-axis) centred\n * along the handle's length (main-axis, from `Rail.handleMainAxisOffset` and\n * `Rail.handleMainAxisExtent`) and scales it down to roughly the rail\n * thickness, so the window appears to shrink into its own spot in the handle\n * stack. Paired with `transform-origin: 0 0` and an opacity fade by the\n * collapse/expand animations. Assumes a rail is attached.\n *\n * @returns A `translate(...) scale(...)` CSS transform value.\n */\n private railGenieTransform(): string {\n const rail = this._rail as Rail;\n const thickness = rail.getThickness();\n const cur = this.currentRect();\n // Shrink to roughly the rail's thickness — clamped so an already-narrow\n // window still visibly collapses rather than scaling up.\n const scale = Math.min(0.5, thickness / Math.max(cur.width, 1));\n\n // Centre the shrunken window along its handle's length rather than\n // pinning its leading corner to the slot: aim the window's own centre at\n // the handle's centre (offset + half the handle) by pulling the corner\n // back half the scaled window. When no handle can be measured or\n // predicted (the first minimise into an empty rail) the length is 0 and\n // the window stays at the slot's leading edge. The cross axis is left\n // alone — the scaled window already fits the thickness.\n const edge = rail.getEdge();\n const vertical = edge === Placement.EAST || edge === Placement.WEST;\n const mainOffset = rail.handleMainAxisOffset(this);\n const handleExtent = rail.handleMainAxisExtent(this);\n const scaledMain = (vertical ? cur.height : cur.width) * scale;\n const mainTarget = handleExtent > 0\n ? mainOffset + (handleExtent - scaledMain) / 2\n : mainOffset;\n\n // Each edge sets its main axis to the centred target and its cross axis to\n // the rail edge; the two initialise to 0 so the axis an edge leaves\n // untouched keeps the correct cross value (0 is the top/left edge for\n // WEST/NORTH).\n let targetX = 0;\n let targetY = 0;\n\n switch (edge) {\n case Placement.EAST:\n targetX = DOM.source.getViewportSize().width - thickness;\n targetY = mainTarget;\n\n break;\n\n case Placement.WEST:\n targetY = mainTarget;\n\n break;\n\n case Placement.SOUTH:\n targetX = mainTarget;\n targetY = DOM.source.getViewportSize().height - thickness;\n\n break;\n\n case Placement.NORTH:\n targetX = mainTarget;\n\n break;\n\n default:\n targetY = mainTarget;\n\n break;\n }\n\n const tx = targetX - cur.x;\n const ty = targetY - cur.y;\n\n return `translate(${tx}px, ${ty}px) scale(${scale})`;\n }\n\n /**\n * Plays the minimize genie: scales and fades the window into its rail's\n * handle corner over `WINDOW_ANIM_DURATION_MS`, then runs `onDone` (which\n * hides the window and lets the rail show its handle). Honours\n * `prefers-reduced-motion` via {@link Animation.play}.\n *\n * @param onDone - Callback fired once the collapse completes.\n */\n private animateRailCollapse(onDone: () => void): void {\n const element = this.getElement();\n\n if (!element) {\n onDone();\n\n return;\n }\n\n this._railCollapseAnimation?.cancel();\n this._railCollapseAnimation = Animation.play(element, {\n from: { transformOrigin: \"0 0\", transform: \"translate(0, 0) scale(1)\", opacity: \"1\" },\n to: { transform: this.railGenieTransform(), opacity: \"0\" },\n durationMs: WINDOW_ANIM_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n onComplete: onDone,\n });\n }\n\n /**\n * Plays the reverse genie on restore: scales and fades the window back up\n * out of its rail's handle corner to its resting rect. The geometry never\n * moved while minimized, so this replays from the same collapsed transform.\n */\n private animateRailExpand(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._railExpandAnimation?.cancel();\n this._railExpandAnimation = Animation.play(element, {\n from: { transformOrigin: \"0 0\", transform: this.railGenieTransform(), opacity: \"0\" },\n to: { transform: \"translate(0, 0) scale(1)\", opacity: \"1\" },\n durationMs: WINDOW_ANIM_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n });\n }\n\n /**\n * Returns the per-slot width of the minimized dock, read from the\n * `--ts-ui-window-min-dock-width` CSS variable with a built-in fallback.\n *\n * @returns The dock slot width in pixels.\n */\n private getMinDockWidth(): number {\n const cssVar = DOM.source.getThemeVar(\"--ts-ui-window-min-dock-width\");\n if (cssVar) {\n const parsed = parseFloat(cssVar);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n\n return DEFAULT_MIN_DOCK_WIDTH_PX;\n }\n\n /**\n * Returns this window's slot index in the minimized dock — the count of\n * minimized windows ahead of it in the open-windows order.\n *\n * @returns The zero-based dock slot index.\n */\n private computeDockSlotIndex(): number {\n let index = 0;\n for (const win of AbstractWindow.openWindows) {\n if (win === this) {\n return index;\n }\n if (win.getWindowState() === \"minimized\") {\n index++;\n }\n }\n return index;\n }\n\n /**\n * Re-positions every minimized window into a gap-free row along the bottom\n * of the viewport. Run after any change to the open/minimized set.\n */\n private static relayoutMinimizedStack(): void {\n let index = 0;\n for (const win of AbstractWindow.openWindows) {\n if (win.getWindowState() !== \"minimized\") {\n continue;\n }\n const dockWidth = win.getMinDockWidth();\n const headerHeight = win.chromeHeight() || CHROME_HEIGHT_FLOOR_PX;\n const x = index * (dockWidth + SNAP_DOCK_GAP_PX);\n const y = DOM.source.getViewportSize().height - headerHeight;\n\n win.setAutoCommitStyle(false);\n win.setX(x);\n win.setY(y);\n win.setWidth(dockWidth);\n win.setHeight(headerHeight);\n win.doLayout();\n win.setAutoCommitStyle(true);\n\n index++;\n }\n }\n\n /**\n * Tweens the window's `x`, `y`, `width`, `height` from the current rect to\n * `target` over `WINDOW_ANIM_DURATION_MS`. Honours `prefers-reduced-motion`\n * by skipping straight to the final rect in one frame.\n *\n * @param target - The rect to tween to.\n * @param onDone - Optional callback fired once the tween completes.\n */\n private animateRect(target: WindowRect, onDone?: () => void): void {\n this._stateAnimHandle?.cancel();\n this._stateAnimHandle = null;\n\n const commit = (rect: WindowRect): void => {\n this.setAutoCommitStyle(false);\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n this.doLayout();\n this.setAutoCommitStyle(true);\n };\n\n this._stateAnimHandle = Animation.tween<WindowRect>({\n from: this.currentRect(),\n to: target,\n durationMs: WINDOW_ANIM_DURATION_MS,\n onStep: commit,\n onComplete: (): void => {\n this._stateAnimHandle = null;\n onDone?.();\n },\n });\n }\n\n // ----- viewport-resize handling while maximized -----\n\n /**\n * Attaches the viewport-resize listener that keeps a maximized window\n * filling the viewport. Idempotent.\n */\n private attachViewportResizeListener(): void {\n if (this._viewportResizeBound) {\n return;\n }\n\n Event.addViewportListener(this, \"resize\", this._boundOnViewportResize);\n this._viewportResizeBound = true;\n }\n\n /**\n * Detaches the viewport-resize listener. Idempotent.\n */\n private detachViewportResizeListener(): void {\n if (!this._viewportResizeBound) {\n return;\n }\n\n Event.removeViewportListener(this, \"resize\", this._boundOnViewportResize);\n this._viewportResizeBound = false;\n }\n\n /**\n * Re-fills the viewport when the browser window resizes while this window is\n * maximized; a no-op in any other state.\n */\n private onViewportResize(): void {\n if (this.getWindowState() !== \"maximized\") {\n return;\n }\n\n const rect = this.computeMaximizeRect();\n this.setAutoCommitStyle(false);\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n this.doLayout();\n this.setAutoCommitStyle(true);\n\n AbstractWindow.relayoutMinimizedStack();\n }\n\n // ----- snap-resize listeners -----\n\n /**\n * Attaches the keyboard listeners that arm the snap-resize affordance.\n * Idempotent.\n */\n private attachSnapKeyboardListeners(): void {\n if (this._snapKeysAttached) {\n return;\n }\n\n Event.addViewportListener(this, \"keydown\", this._boundOnSnapKeyDown);\n Event.addViewportListener(this, \"keyup\", this._boundOnSnapKeyUp);\n Event.addViewportListener(this, \"blur\", this._boundOnSnapBlur);\n this._snapKeysAttached = true;\n }\n\n /**\n * Detaches the snap-resize keyboard listeners. Idempotent.\n */\n private detachSnapKeyboardListeners(): void {\n if (!this._snapKeysAttached) {\n return;\n }\n\n Event.removeViewportListener(this, \"keydown\", this._boundOnSnapKeyDown);\n Event.removeViewportListener(this, \"keyup\", this._boundOnSnapKeyUp);\n Event.removeViewportListener(this, \"blur\", this._boundOnSnapBlur);\n this._snapKeysAttached = false;\n }\n\n /**\n * Attaches the mouse listeners that track the snap-target border while the\n * modifier is held. Idempotent.\n */\n private attachSnapMouseListeners(): void {\n if (this._snapMoveAttached) {\n return;\n }\n\n Event.addViewportListener(this, \"mousemove\", this._boundOnSnapMouseMove);\n Event.addViewportListener(this, \"mousedown\", this._boundOnSnapMouseDown);\n this._snapMoveAttached = true;\n }\n\n /**\n * Detaches the snap-resize mouse listeners. Idempotent.\n */\n private detachSnapMouseListeners(): void {\n if (!this._snapMoveAttached) {\n return;\n }\n\n Event.removeViewportListener(this, \"mousemove\", this._boundOnSnapMouseMove);\n Event.removeViewportListener(this, \"mousedown\", this._boundOnSnapMouseDown);\n this._snapMoveAttached = false;\n }\n\n /**\n * Clears any highlighted snap-target border.\n */\n private clearSnapTargetBorder(): void {\n if (this._snapTargetBorder) {\n this._snapTargetBorder.setSnapTarget(false);\n this._snapTargetBorder = null;\n }\n }\n\n /**\n * Tears down the whole snap-resize session — disarms it, detaches the mouse\n * listeners, and clears the target border.\n */\n private clearSnapState(): void {\n this._snapEnabled = false;\n this.detachSnapMouseListeners();\n this.clearSnapTargetBorder();\n }\n\n /**\n * Arms snap detection when the watched modifier goes down while the window\n * is normal and snap-resize is enabled.\n *\n * @param e - The keydown event.\n */\n private onSnapKeyDown(e: KeyboardEvent): void {\n if (!this.isSnapResizeEnabled()) {\n return;\n }\n if (this.getWindowState() !== \"normal\") {\n return;\n }\n if (!this.modifierMatches(e)) {\n return;\n }\n if (this._snapEnabled) {\n return;\n }\n\n this._snapEnabled = true;\n this.attachSnapMouseListeners();\n }\n\n /**\n * Disarms snap detection when the watched modifier is released.\n *\n * @param e - The keyup event.\n */\n private onSnapKeyUp(e: KeyboardEvent): void {\n if (!this._snapEnabled) {\n return;\n }\n // Some browsers fire keyup with a different key field when modifiers\n // chord; only release when the watched modifier is no longer active.\n if (this.modifierStillHeld(e)) {\n return;\n }\n\n this.clearSnapState();\n }\n\n /**\n * Returns whether the configured snap modifier is active in `e`.\n *\n * @param e - The keyboard event to inspect.\n * @returns True when the watched modifier is held.\n */\n private modifierMatches(e: KeyboardEvent): boolean {\n switch (this.getSnapModifier()) {\n case \"ctrl\": return e.ctrlKey;\n case \"meta\": return e.metaKey;\n case \"alt\": return e.altKey;\n case \"shift\": return e.shiftKey;\n }\n }\n\n /**\n * Returns whether the watched snap modifier is still held in `e`.\n *\n * @param e - The keyboard event to inspect.\n * @returns True when the watched modifier remains active.\n */\n private modifierStillHeld(e: KeyboardEvent): boolean {\n return this.modifierMatches(e);\n }\n\n /**\n * Tracks the nearest border under the cursor while snap is armed, updating\n * the highlighted snap-target border.\n *\n * @param e - The mousemove event.\n */\n private onSnapMouseMove(e: MouseEvent): void {\n if (!this._snapEnabled) {\n return;\n }\n\n const winner = this.pickSnapBorder(e.clientX, e.clientY);\n if (winner === this._snapTargetBorder) {\n return;\n }\n\n if (this._snapTargetBorder) {\n this._snapTargetBorder.setSnapTarget(false);\n }\n this._snapTargetBorder = winner;\n if (winner) {\n winner.setSnapTarget(true);\n }\n }\n\n /**\n * Picks the closest border strip to a cursor point within the snap\n * threshold, preferring corners over edges on ties.\n *\n * @param cx - Cursor x in viewport pixels.\n * @param cy - Cursor y in viewport pixels.\n * @returns The nearest border within threshold, or `null`.\n */\n private pickSnapBorder(cx: number, cy: number): WindowBorder | null {\n const candidates: WindowBorder[] = [\n // Corners first so ties go to corners over edges.\n this._borderComponents.northwest,\n this._borderComponents.northeast,\n this._borderComponents.southeast,\n this._borderComponents.southwest,\n this._borderComponents.north,\n this._borderComponents.east,\n this._borderComponents.south,\n this._borderComponents.west,\n ];\n\n let best: WindowBorder | null = null;\n let bestDist = this.getSnapThreshold();\n\n for (const border of candidates) {\n const el = border.getElement();\n if (!el) {\n continue;\n }\n const rect = DOM.source.getElementRect(el);\n const dx = Math.max(rect.left - cx, 0, cx - rect.right);\n const dy = Math.max(rect.top - cy, 0, cy - rect.bottom);\n const dist = Math.hypot(dx, dy);\n\n if (dist <= bestDist) {\n best = border;\n bestDist = dist;\n }\n }\n\n return best;\n }\n\n /**\n * Forwards a press near (but outside) the highlighted snap-target border\n * into that border's own drag flow.\n *\n * @param e - The mousedown event.\n * @returns `true` when the press starts a snap drag; nothing when it does not, so the event keeps propagating.\n */\n private onSnapMouseDown(e: MouseEvent): Event.ListenerResult {\n const target = this._snapTargetBorder;\n if (!target) {\n return;\n }\n\n // If the mousedown landed directly on the highlighted border, the\n // strip's own `mousedown` listener (registered in WindowBorder's\n // constructor) will already fire onDragStart — forwarding here would\n // double-register the viewport listeners. The snap forwarding is only\n // useful when the cursor sits *outside* the 4 px strip.\n const targetEl = target.getElement();\n if (targetEl && DOM.source.isNode(e.target) && DOM.source.contains(targetEl, DOM.source.intern(e.target))) {\n this._snapTargetBorder = null;\n return;\n }\n\n // Forward into the border's own drag flow. The snap-target highlight\n // is cleared in the matching onDragStop hook the border owns.\n target.onDragStart();\n this._snapTargetBorder = null;\n\n return true;\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Button } from \"~/component/button/Button.js\";\nimport { ComponentStyleRuleSpec } from \"~/core/Component.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\n\n/**\n * Shared style rules for a window control button (minimize / maximize / close),\n * painted entirely from the theme's `window.control` tokens so the look switches\n * live with the theme: flat themes (modern / dark) blend the controls into the\n * surface (no border, no shadow), while the classic theme renders them as\n * standard raised buttons (gradient fill, border, drop shadow). The `background`\n * shorthand (not `backgroundColor`) is used because the classic fills are\n * gradients.\n *\n * Shared by both window kinds — {@link TabWindow}'s trailing tools and the\n * `WindowHeader`'s trailing buttons — so the two cannot visually drift.\n */\nconst WINDOW_CONTROL_STYLE_RULES: ComponentStyleRuleSpec[] = [\n { suffix: \"\", styles: { background: \"var(--ts-ui-window-control-bg)\", border: \"var(--ts-ui-window-control-border)\", boxShadow: \"var(--ts-ui-window-control-shadow)\" } },\n { suffix: \":hover:not(:active)\", styles: { background: \"var(--ts-ui-window-control-hover-bg)\" } },\n { suffix: \":active\", styles: { background: \"var(--ts-ui-window-control-active-bg)\" } },\n];\n\n/**\n * Shared style rules for the decorative leading window glyph (the title icon).\n * A transparent base background lets the bar / header surface show through in\n * every theme, so it reads as a title icon rather than a clickable button; the\n * `1px solid transparent` border is invisible in all themes yet reserves the\n * same 1px border box the controls' `window.control.border` does (1px wide\n * across every theme), keeping the icon a true size/inset peer of the controls.\n * The styling is baked into the rules (CSS) rather than a post-construct\n * `setBackground`, which the pre-init `applyStyle` cascade would replay away.\n */\nconst WINDOW_LEAD_GLYPH_STYLE_RULES: ComponentStyleRuleSpec[] = [\n { suffix: \"\", styles: { background: \"transparent\", border: \"1px solid transparent\", boxShadow: \"none\" } },\n];\n\n/**\n * Builds a window control button (minimize / maximize / close) shared by\n * {@link TabWindow} and `WindowHeader`. Chromeless, painted from\n * {@link WINDOW_CONTROL_STYLE_RULES}, with the 2px control insets. Callers wire\n * the `\"action\"` listener and may override the insets to match their container's\n * box (a `TabWindow`'s bar re-sets them to the compact tool inset).\n *\n * @param glyph - The registry glyph name to show.\n *\n * @returns The configured control button.\n */\nexport function createWindowControlButton(glyph: string): Button {\n return new Button({ glyph, chromeless: true, styleRules: WINDOW_CONTROL_STYLE_RULES, insets: new Insets(2, 2, 2, 2) });\n}\n\n/**\n * Builds the decorative leading window glyph (title icon) shared by\n * {@link TabWindow} and `WindowHeader`. Same chromeless control box as\n * {@link createWindowControlButton} so it is a size/inset peer, but painted from\n * {@link WINDOW_LEAD_GLYPH_STYLE_RULES} (transparent) and made pointer-transparent\n * so a press falls through to the window-move gesture.\n *\n * @param glyph - The registry glyph name to show.\n *\n * @returns The configured decorative leading glyph button.\n */\nexport function createWindowLeadGlyphButton(glyph: string): Button {\n const button = new Button({ glyph, chromeless: true, styleRules: WINDOW_LEAD_GLYPH_STYLE_RULES, insets: new Insets(2, 2, 2, 2) });\n button.setPointerEvents(\"none\");\n\n return button;\n}\n\n/**\n * Toggles a set of window control buttons between their opaque themed fill\n * (focused) and `\"transparent\"` (blurred), so a blurred window flattens its\n * controls. The `background` shorthand resets every layer, so the classic\n * gradient clears on blur too, leaving the border/shadow and hover/press rules\n * intact. Shared by {@link TabWindow}'s and `WindowHeader`'s focus hooks.\n *\n * @param buttons - The control buttons to flatten or restore.\n * @param active - True to restore the control fill, false to flatten it.\n */\nexport function setWindowControlsActive(buttons: Button[], active: boolean): void {\n const background = active ? \"var(--ts-ui-window-control-bg)\" : \"transparent\";\n\n for (const button of buttons) {\n button.setBackground(background);\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Header, HeaderOptions } from \"~/component/display/Header.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Button, ClickListener } from \"~/component/button/Button.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ThemeManager } from \"~/core/Theme.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { FillType } from \"~/layout/FillType.js\";\nimport { AnchorType } from \"~/layout/AnchorType.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport {\n createWindowControlButton,\n setWindowControlsActive,\n} from \"~/overlay/windowControls.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { window_maximize } from \"~/glyphs/solid/window_maximize.js\";\nimport { window_minimize } from \"~/glyphs/solid/window_minimize.js\";\nimport { window_restore } from \"~/glyphs/solid/window_restore.js\";\n\nGlyph.register(xmark, window_maximize, window_minimize, window_restore);\n\n// Equal top/bottom gap (px) around the title line inside the header band. The\n// header thickness is `textHeight + 2 * CHROME_MARGIN`; the stretched controls\n// fill that full-height band edge-to-edge (no overflow, so the region clip frame\n// contains them), and the Fit-centred title sits with this margin above and\n// below. Tuned so the thickness matches the established ~26px window-chrome height\n// (title line ≈ 18px + 2×4), keeping the redesign visually surgical.\nconst CHROME_MARGIN: number = 4;\n\n/**\n * Construction-time options for {@link WindowHeader}.\n *\n * @category Components\n */\nexport interface WindowHeaderOptions extends HeaderOptions {\n closeable?: boolean;\n minimizable?: boolean;\n maximizable?: boolean;\n glyph?: string;\n}\n\n/**\n * A window title bar component with an embedded `xmark` glyph close button\n * and an optional title icon shown to the west of the title text.\n *\n * Extends [`Header`](/api/component/display/classes/Header) by anchoring a\n * trailing row of minimize / maximize / close buttons to the east side and\n * exposing an optional title-icon slot on the west.\n *\n * @category Components\n */\nclass WindowHeader extends Header {\n\n private _exitButton: Button;\n private _minimizeButton: Button;\n private _maximizeButton: Button;\n private _trailingRow: Component;\n private _activeBackground: string;\n private _titleGlyph: Glyph | null = null;\n private _titleRow: Component;\n private _closeable: boolean = true;\n private _minimizable: boolean = true;\n private _maximizable: boolean = true;\n\n constructor(text: string, options?: WindowHeaderOptions) {\n super(text);\n\n // Focused fill: the dedicated window-header surface, valued equal to the\n // tab-toolbar fill a TabWindow's bar uses so a header Window and a headerless\n // TabWindow share one window-chrome colour (both also share the gutter fill\n // when blurred), while staying independently themeable. A solid colour, not\n // the button gradient that used to sit here.\n this._activeBackground = \"var(--ts-ui-window-header-bg, #eee)\";\n this.setBackgroundColor(this._activeBackground);\n\n // Horizontal-only padding: the header is a strip whose vertical extent is\n // the chrome thickness (see updatePreferredSize), so the internal Border\n // band spans the full height and the stretched trailing controls reach the\n // bottom edge where the content panel begins. A vertical inset would shrink\n // the band away from that edge and re-open the gap the redesign closes.\n this.setInsets(new Insets(0, 4, 0, 4));\n\n // Reparent the inherited title text into a permanent HBox row that\n // owns the Border WEST slot. setGlyph then only has to swap the\n // optional leading glyph child in or out of this row.\n const title = this.getText();\n this.removeComponent(title);\n\n this._titleRow = new Component();\n this._titleRow.setLayoutManager(new HBox({ spacing: 8 }));\n // Leading inset so the title icon sits the same distance from the left edge\n // as the trailing controls sit from the right (and as a TabWindow's leading\n // glyph) — without it the icon hugs the corner, closer in than the controls.\n this._titleRow.setInsets(new Insets(0, 0, 0, 5));\n this._titleRow.setPointerEvents(\"none\");\n this._titleRow.addComponent(title);\n\n // Centre the title line vertically in the full-height band: a Fit cell\n // fills the WEST region's width and centres its single child (the title\n // row) on the cross axis. A WEST `AnchorType.CENTER` would not work — the\n // non-collapsible Border region commits via the clip-frame path, which\n // bypasses anchor resolution — so the centring lives in this layout, not\n // the placement anchor.\n const titleCell = new Component();\n titleCell.setLayoutManager(new Fit({ fill: FillType.HORIZONTAL }));\n titleCell.setPointerEvents(\"none\");\n titleCell.addComponent(this._titleRow);\n\n this.addComponent(titleCell, {\n placement: Placement.WEST,\n anchor: AnchorType.WEST,\n fill: FillType.HORIZONTAL\n });\n\n // Control buttons built from the shared window-control factory so the\n // header's trailing controls match a TabWindow's exactly: the themed\n // `window.control` fill (white in modern, raised in classic). Like a\n // TabWindow's controls — stretched to the strip thickness — these are\n // stretched to the full header band (see the trailing row below), so their\n // `Insets(0,4,0,4)` only sets the within-box horizontal padding; the stretch\n // supplies the height. They fill the band edge-to-edge (no overflow, so the\n // clip frame contains them) and meet the content panel at the bottom edge.\n this._minimizeButton = createWindowControlButton(\"window-minimize\");\n this._maximizeButton = createWindowControlButton(\"window-maximize\");\n this._exitButton = createWindowControlButton(\"xmark\");\n\n for (const control of [this._minimizeButton, this._maximizeButton, this._exitButton]) {\n control.setInsets(new Insets(0, 4, 0, 4));\n }\n\n this._trailingRow = new Component();\n this._trailingRow.setLayoutManager(new HBox({ spacing: 0, stretching: true }));\n this._trailingRow.setInsets(new Insets(0, 0, 0, 0));\n this._trailingRow.addComponent(this._minimizeButton);\n this._trailingRow.addComponent(this._maximizeButton);\n this._trailingRow.addComponent(this._exitButton);\n\n this.addComponent(this._trailingRow, { placement: Placement.EAST });\n\n // Default title icon: applied unless an explicit glyph name was passed.\n // Call clearGlyph() on the resulting WindowHeader to opt out entirely.\n if (options?.glyph === undefined) {\n this.setGlyph(\"window-maximize\");\n }\n\n if (options) {\n this.applyOptions(options);\n }\n }\n\n /**\n * Derives the header's preferred height as a chrome-band thickness —\n * `textHeight + 2 * CHROME_MARGIN` — rather than the base `textHeight +\n * vertical insets`. With the vertical insets zeroed (so the band reaches the\n * content edge for the stretched controls), the base derivation would collapse\n * the header to the bare text height; this override supplies the breathing band\n * that seats the control box and centres the title line. Runs at construction\n * and on every theme change (the base wires both to this method), so the\n * thickness survives a theme toggle.\n *\n * @remarks Touches the `_titleGlyph` subclass field only behind an\n * `if (this._titleGlyph)` guard, so it stays safe when called via `super()`\n * before subclass fields init: during the super-cascade the field is\n * `undefined`, the guard skips the re-pin, and the rest reads only the\n * inherited text and the module constant.\n */\n protected updatePreferredSize(): void {\n // 20px mirrors the base Header's pre-measurement fallback (a sane default\n // line height before the text element has measured); the real height takes\n // over on the first measured pass.\n const textSize = this.getText().getPreferredSize();\n const textHeight = textSize ? textSize.height : 20;\n\n this.setPreferredSize({ width: 100, height: textHeight + 2 * CHROME_MARGIN });\n\n // Re-pin the title glyph's ink whenever the header re-measures — the\n // inherited Header theme listener drives this method on construction and\n // every theme change, so an SVG title glyph follows a base-size change\n // with no listener of our own. Guarded because the field is undefined\n // during the super() cascade that first runs this method.\n if (this._titleGlyph) {\n const ink = this.resolveTitleGlyphInk();\n this._titleGlyph.setPreferredSize({ width: ink, height: ink });\n }\n }\n\n /**\n * Reads the title-glyph ink size in px from the active theme's resolved\n * scale snapshot. Read at render time (the `updatePreferredSize` re-pin and\n * `setGlyph`); a base- or theme-change re-resolves the snapshot in `setTheme`\n * and the next layout pass picks it up.\n *\n * @returns The title-glyph ink size in pixels.\n */\n private resolveTitleGlyphInk(): number {\n return ThemeManager.getResolvedScale().titleGlyph;\n }\n\n /**\n * Applies a {@link WindowHeaderOptions} bag, dispatching the closeable,\n * minimizable, and maximizable flags and the optional title-glyph name\n * after inherited Header fields.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: WindowHeaderOptions): this {\n super.applyOptions(options);\n\n if (options.closeable !== undefined) {\n this.setCloseable(options.closeable);\n }\n\n if (options.minimizable !== undefined) {\n this.setMinimizable(options.minimizable);\n }\n\n if (options.maximizable !== undefined) {\n this.setMaximizable(options.maximizable);\n }\n\n if (options.glyph !== undefined) {\n this.setGlyph(options.glyph);\n }\n\n return this;\n }\n\n /**\n * Sets or clears the title icon shown to the west of the header text.\n *\n * @param name - Registry glyph name to display, or `null` to remove the icon.\n *\n * @returns This component, for method chaining.\n *\n * @remarks\n * Swaps the optional leading glyph child of the permanent title row built\n * by the constructor. The inherited title text always lives inside that\n * row, so the two never overlap and Border's WEST slot only ever tracks\n * one component.\n */\n setGlyph(name: string): this {\n if (this._titleGlyph) {\n this._titleRow.removeComponent(this._titleGlyph);\n this._titleGlyph = null;\n }\n\n // A bare decorative glyph (pointer-through) sized to the same ink the\n // TabWindow's leading glyph renders, so the title icon matches it. Kept a\n // plain Glyph rather than a control-peer button so it baseline-aligns with\n // the title text in this shared row (a taller control box would desync the\n // text's vertical centring); the leading inset on the title row supplies the\n // corner offset that mirrors the TabWindow's.\n const glyph = new Glyph(name);\n const ink = this.resolveTitleGlyphInk();\n glyph.setPreferredSize({ width: ink, height: ink });\n glyph.setPointerEvents(\"none\");\n this._titleGlyph = glyph;\n\n this._titleRow.insertComponent(glyph, 0);\n\n return this;\n }\n\n /**\n * Removes the title icon from the header, if one is present.\n *\n * @returns This component, for method chaining.\n */\n clearGlyph(): this {\n if (this._titleGlyph) {\n this._titleRow.removeComponent(this._titleGlyph);\n this._titleGlyph = null;\n }\n\n return this;\n }\n\n /**\n * Returns the current title-glyph component, or null if none is set.\n *\n * @returns The title [`Glyph`](/api/component/display/classes/Glyph), or null.\n */\n getGlyph(): Glyph | null {\n return this._titleGlyph;\n }\n\n /**\n * Toggles the title bar appearance between the focused and unfocused states.\n *\n * @param active - True to show the focused tab-toolbar background; false for the unfocused gutter background.\n */\n setActive(active: boolean): this {\n this.setBackgroundColor(active\n ? this._activeBackground\n : \"var(--ts-ui-gutter-bg, rgb(200, 200, 200))\");\n\n // Flatten the controls to transparent on blur (and restore the themed fill\n // on focus) exactly like a TabWindow's bar, via the shared helper.\n setWindowControlsActive([this._minimizeButton, this._maximizeButton, this._exitButton], active);\n\n return this;\n }\n\n /**\n * Enables or disables the close (exit) button. Disabling greys it out and\n * stops it firing, rather than hiding it — so a non-closeable window keeps a\n * full, evenly-spaced button row instead of a gap where the close button was.\n *\n * @param value - True to enable the close button, false to disable it.\n *\n * @returns This component, for method chaining.\n */\n setCloseable(value: boolean): this {\n this._closeable = value;\n this._exitButton.setEnabled(value);\n\n return this;\n }\n\n /**\n * Returns whether the close button is enabled.\n *\n * @returns True when the close button is enabled.\n */\n isCloseable(): boolean {\n return this._closeable;\n }\n\n /**\n * Toggles the visibility of the minimize button.\n *\n * @param value - True to show the minimize button, false to hide it.\n *\n * @returns This component, for method chaining.\n */\n setMinimizable(value: boolean): this {\n this._minimizable = value;\n this._minimizeButton.setVisible(value);\n\n return this;\n }\n\n /**\n * Returns whether the minimize button is visible.\n *\n * @returns True when the minimize button is shown.\n */\n isMinimizable(): boolean {\n return this._minimizable;\n }\n\n /**\n * Toggles the visibility of the maximize button.\n *\n * @param value - True to show the maximize button, false to hide it.\n *\n * @returns This component, for method chaining.\n */\n setMaximizable(value: boolean): this {\n this._maximizable = value;\n this._maximizeButton.setVisible(value);\n\n return this;\n }\n\n /**\n * Returns whether the maximize button is visible.\n *\n * @returns True when the maximize button is shown.\n */\n isMaximizable(): boolean {\n return this._maximizable;\n }\n\n /**\n * Swaps the maximize button's glyph between the \"maximize\" and \"restore\"\n * icons. Called by the owning [`Window`](/api/overlay/classes/Window) when\n * transitioning between the `\"normal\"` and `\"maximized\"` states.\n *\n * @param name - Either `\"window-maximize\"` or `\"window-restore\"`.\n *\n * @returns This component, for method chaining.\n */\n setMaximizeButtonGlyph(name: \"window-maximize\" | \"window-restore\"): this {\n this._maximizeButton.setGlyph(name);\n\n return this;\n }\n\n /**\n * Swaps the minimize button's glyph between the \"minimize\" and \"restore\"\n * icons. Called by the owning [`Window`](/api/overlay/classes/Window) when\n * transitioning into and out of the `\"minimized\"` state — while minimized the\n * window stays docked at header height with the button visible, so a restore\n * glyph signals that pressing it un-minimizes (mirroring the maximize button's\n * restore swap).\n *\n * @param name - Either `\"window-minimize\"` or `\"window-restore\"`.\n *\n * @returns This component, for method chaining.\n */\n setMinimizeButtonGlyph(name: \"window-minimize\" | \"window-restore\"): this {\n this._minimizeButton.setGlyph(name);\n\n return this;\n }\n\n /**\n * Registers a click listener on the window close button.\n *\n * @param listener - The callback to invoke when the close button is clicked.\n */\n addExitButtonListener(listener: Function) : this {\n this._exitButton.on(\"action\", listener as ClickListener);\n\n return this;\n }\n\n /**\n * Registers a click listener on the window minimize button.\n *\n * @param listener - The callback to invoke when the minimize button is clicked.\n */\n addMinimizeButtonListener(listener: Function): this {\n this._minimizeButton.on(\"action\", listener as ClickListener);\n\n return this;\n }\n\n /**\n * Registers a click listener on the window maximize button.\n *\n * @param listener - The callback to invoke when the maximize button is clicked.\n */\n addMaximizeButtonListener(listener: Function): this {\n this._maximizeButton.on(\"action\", listener as ClickListener);\n\n return this;\n }\n\n /**\n * Registers a `dblclick` listener on the header bar itself, used by the\n * owning [`Window`](/api/overlay/classes/Window) to toggle the maximize state\n * when the user double-clicks the title (but not the trailing buttons).\n *\n * @param listener - The callback to invoke on `dblclick`.\n */\n addHeaderDoubleClickListener(listener: Event.Listener): this {\n // Subtree, not exact-target: the title row sits inside a Border clip-frame\n // wrapper, so a double-click on the title text/glyph targets that wrapper\n // rather than the bare header element. The owning Window's handler already\n // filters out the trailing buttons via targetIsInTrailingButton.\n Event.addSubtreeListener(this, \"dblclick\", listener);\n\n return this;\n }\n\n /**\n * Computes the natural required width of the header: title glyph width\n * (or 0 when no glyph is set) plus the supplied text budget plus the\n * three trailing buttons' row width. Used by the owning\n * [`Window`](/api/overlay/classes/Window) to derive a minSize that keeps\n * the title icon, some text space, and the trailing buttons all visible\n * when the window is shrunk.\n *\n * @param textBudget - Pixels to reserve for the title text label.\n *\n * @returns The natural required header width in pixels.\n */\n getMinContentWidth(textBudget: number = 100): number {\n const glyphW = this._titleGlyph?.getPreferredSize()?.width ?? 0;\n const trailingW = this._trailingRow.getPreferredSize()?.width ?? 0;\n\n return glyphW + textBudget + trailingW;\n }\n\n /**\n * Returns the minimize button DOM element, if the header has been rendered.\n *\n * @returns The minimize button's HTMLElement, or undefined when the header\n * has not yet been rendered.\n *\n * @remarks Used by the owning [`Window`](/api/overlay/classes/Window) to\n * short-circuit `dblclick`-on-header maximize toggling when the click\n * target sits inside one of the trailing buttons.\n */\n getMinimizeButtonElement(): Handle | undefined {\n return this._minimizeButton.getElement();\n }\n\n /**\n * Returns the maximize button DOM element, if the header has been rendered.\n *\n * @returns The maximize button's HTMLElement, or undefined when the header\n * has not yet been rendered.\n */\n getMaximizeButtonElement(): Handle | undefined {\n return this._maximizeButton.getElement();\n }\n\n /**\n * Returns the close (exit) button DOM element, if the header has been rendered.\n *\n * @returns The exit button's HTMLElement, or undefined when the header\n * has not yet been rendered.\n */\n getExitButtonElement(): Handle | undefined {\n return this._exitButton.getElement();\n }\n}\n\nconst WindowHeaderCallable = callable(WindowHeader);\ntype WindowHeaderCallable = WindowHeader;\nexport {\n WindowHeader as _WindowHeader,\n WindowHeaderCallable as WindowHeader\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Border } from \"~/layout/Border.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { DOM, type Handle } from \"~/core/DOM.js\";\nimport { WindowHeader } from \"~/component/container/WindowHeader.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DragManager, DragData, DragEventDetail, TabDragData, tabDragRegistry } from \"~/overlay/DragManager.js\";\nimport {\n AbstractWindow,\n WindowOptions,\n WindowState,\n} from \"~/overlay/AbstractWindow.js\";\n\nexport type {\n WindowOptions,\n WindowState,\n WindowMaximizeBounds,\n WindowSnapModifier,\n WindowRect,\n} from \"~/overlay/AbstractWindow.js\";\n\n/**\n * A floating, resizable, and draggable window component.\n *\n * Renders a titled panel with eight border-handle strips that the user can\n * drag to resize the window from any edge or corner. Supports a three-state\n * lifecycle (`\"normal\"` / `\"minimized\"` / `\"maximized\"`) accessed through\n * {@link Window.setWindowState}, plus an opt-in Ctrl-snap resize affordance\n * that highlights the nearest border within a 12-pixel threshold so the\n * 4-pixel-wide grab strips are easier to land on.\n *\n * Extends {@link AbstractWindow}, which owns the header-agnostic window\n * machinery; `Window` adds the `Border` layout, a non-null\n * {@link WindowHeader} in NORTH, content in CENTER, and the Shift-drag re-dock\n * source. It implements the base's hooks via its header.\n *\n * @category Core\n */\nclass Window extends AbstractWindow {\n\n private _header: WindowHeader;\n\n // Shift-drag re-dock: a header press with Shift held starts a tab-dock drag\n // instead of a window move. Shift (not Ctrl) so it never collides with the\n // Ctrl snap-resize affordance. `_headerDragShift` captures the modifier at\n // press (the drag-source callbacks get no key state); `_headerDragComponentId`\n // stashes the registered content id so onDragEnd can clean the registry after\n // the dock already moved the content out.\n private _headerDragShift: boolean = false;\n private _headerDragComponentId: string = \"\";\n private readonly _boundCaptureHeaderShift: (e: MouseEvent) => void = (e: MouseEvent) => this.captureHeaderShift(e);\n\n /**\n * Builds a header window: a `Border` layout with a {@link WindowHeader} in\n * NORTH, wires the header buttons and the Shift-drag re-dock source, then\n * runs the base's late chrome setup via `initChrome`.\n *\n * @param headerText - The initial title-bar text (overridden by\n * `options.headerText` when both are supplied).\n * @param options - Optional window configuration.\n */\n constructor(headerText: string, options?: WindowOptions) {\n super(options);\n\n this.setLayoutManager(new Border());\n\n // Build the header with the effective text up front (consumer's\n // `options.headerText` from the cascade-written `_options`, falling\n // back to the positional argument, falling back to \"Window\"). The\n // late-built dispatch below skips `setHeaderText` because the header\n // already carries the right text — re-setting it would write the\n // same value twice.\n const effectiveHeaderText = this._options.headerText ?? headerText ?? \"Window\";\n this._header = new WindowHeader(effectiveHeaderText);\n this.addComponent(this._header, {\n placement: Placement.NORTH,\n ignoreParentInsets: true\n });\n this._header.addExitButtonListener(() => this.onExitAction());\n this._header.addMinimizeButtonListener(() => this.toggleMinimize());\n this._header.addMaximizeButtonListener(() => this.toggleMaximize());\n this._header.addHeaderDoubleClickListener((e: MouseEvent) => this.onHeaderDoubleClick(e));\n\n // Late-built state: the glyph field was written pure to `_options` by\n // the super-time cascade. Dispatch it now that `this._header` exists.\n // (The generic contentFactory / state dispatch runs in initChrome.)\n if (this._options.glyph !== undefined) {\n this._header.setGlyph(this._options.glyph);\n }\n\n this.initChrome(options);\n }\n\n /**\n * Returns the window's title-bar component.\n *\n * @returns The internal {@link WindowHeader} instance, exposing the close\n * button, title text, and optional title-icon slot.\n */\n getHeader(): WindowHeader {\n return this._header;\n }\n\n /**\n * Updates the text shown in the window's title bar.\n *\n * @param text - The new header label text.\n *\n * @returns This window, for method chaining.\n */\n setHeaderText(text: string): this {\n if (!this._header) {\n throw new Error(\"Window does not have a header.\");\n }\n\n this._header.getText().setText(text);\n\n return this;\n }\n\n /**\n * Sets the title-bar glyph (leading icon). Also caches it on the options bag\n * so {@link AbstractWindow.getGlyph} (read by a rail to label a minimized\n * window's handle) stays in sync with runtime changes.\n *\n * @param glyph - The glyph name to show before the title text.\n *\n * @returns This window, for method chaining.\n */\n setGlyph(glyph: string): this {\n this._options.glyph = glyph;\n this._header.setGlyph(glyph);\n\n return this;\n }\n\n // ----- AbstractWindow hook implementations -----\n\n /**\n * Wires the window-move gesture: a plain header `mousedown` starts a window\n * move, a Shift-held press is captured for the re-dock drag source, and the\n * header is registered as a tab-dock drag source.\n */\n protected wireMoveTrigger(): void {\n // Subtree listeners (not exact-target) because the header's title row sits\n // inside a Border clip-frame wrapper: a press on the title text or glyph\n // targets that wrapper, not the bare header element, so an exact-target\n // listener would miss it. Matches the drag source below, which already\n // observes the whole header subtree via addMouseDownSubtreeListener.\n Event.addSubtreeListener(this._header, \"mousedown\", (e: MouseEvent) => this.onMouseDown(e));\n\n // Shift-drag the header to re-dock the window's body content onto a Tab\n // strip. The capture listener records the modifier (registered before the\n // drag source so it runs first); the drag source vetoes a plain (no-Shift)\n // press so the normal window move runs.\n Event.addSubtreeListener(this._header, \"mousedown\", this._boundCaptureHeaderShift);\n DragManager.makeDragSource(this._header, {\n dragData: (): DragData => this.buildHeaderDragData(),\n onDragStart: (): boolean | void => this.onHeaderDragStart(),\n onDragEnd: (_detail: DragEventDetail, dropped: boolean): void => this.onHeaderDragEnd(dropped),\n });\n }\n\n /**\n * Reflects the closeable state onto the header close button.\n *\n * @param value - True to enable the close button.\n */\n protected reflectCloseable(value: boolean): void {\n this._header.setCloseable(value);\n }\n\n /**\n * Reflects the minimizable state onto the header minimize button.\n *\n * @param value - True to show the minimize button.\n */\n protected reflectMinimizable(value: boolean): void {\n this._header.setMinimizable(value);\n }\n\n /**\n * Reflects the maximizable state onto the header maximize button.\n *\n * @param value - True to show the maximize button.\n */\n protected reflectMaximizable(value: boolean): void {\n this._header.setMaximizable(value);\n }\n\n /**\n * Swaps the header control glyphs to match the window state: the maximize\n * button shows `window-restore` when maximized (else `window-maximize`), and\n * the minimize button shows `window-restore` when minimized (else\n * `window-minimize`). A minimized window stays docked at header height with\n * both buttons visible, so reflecting the minimized state onto the minimize\n * button keeps it symmetric with the maximize/restore swap.\n *\n * @param state - The window state being entered.\n */\n protected reflectMaximizeState(state: WindowState): void {\n this._header.setMaximizeButtonGlyph(state === \"maximized\" ? \"window-restore\" : \"window-maximize\");\n this._header.setMinimizeButtonGlyph(state === \"minimized\" ? \"window-restore\" : \"window-minimize\");\n }\n\n /**\n * Paints the active/inactive state onto the title bar.\n *\n * @param active - True when this window is the active layer.\n */\n protected paintActive(active: boolean): void {\n this._header.setActive(active);\n }\n\n /**\n * Returns the title-bar text for serialization.\n *\n * @returns The current header text.\n */\n getTitle(): string {\n return String(this._header.getText().getText());\n }\n\n /**\n * Returns the header's required width, seeded into the default min size.\n *\n * @returns The header min-content width in pixels.\n */\n protected minContentWidthSeed(): number {\n return this._header.getMinContentWidth();\n }\n\n /**\n * Returns the header's laid-out height — the window's title chrome height.\n *\n * @returns The header height in pixels.\n */\n protected chromeHeight(): number {\n return this._header.getHeight();\n }\n\n /**\n * Adds content to the window's CENTER region.\n *\n * @param content - The content component to add.\n */\n protected addContent(content: Component): void {\n this.addComponent(content, { placement: Placement.CENTER });\n }\n\n /**\n * Reports whether `child` is the window's chrome — true only for the header.\n *\n * @param child - The child component to classify.\n * @returns True when `child` is the header.\n */\n isChromeComponent(child: Component): boolean {\n return child === this._header;\n }\n\n // ----- header-specific gestures -----\n\n /**\n * Handles `dblclick` on the header bar. When minimized, restores to the\n * pre-minimize state (`\"normal\"` or `\"maximized\"`); otherwise toggles\n * maximize. Clicks on the trailing buttons are ignored — they own their\n * own handlers.\n *\n * @param e - The header `dblclick` event.\n */\n private onHeaderDoubleClick(e: MouseEvent): void {\n const target = e.target === null ? null : DOM.source.intern(e.target);\n if (target && this.targetIsInTrailingButton(target)) {\n return;\n }\n\n if (this.getWindowState() === \"minimized\") {\n this.setWindowState(this._preMinimizeState);\n return;\n }\n\n if (!this.isMaximizable()) {\n return;\n }\n this.toggleMaximize();\n }\n\n /**\n * Returns whether `target` lies inside one of the header's trailing\n * (minimize / maximize / exit) buttons.\n *\n * @param target - The event target handle to test.\n * @returns True when the target is inside a trailing button.\n */\n private targetIsInTrailingButton(target: Handle): boolean {\n const buttons: Array<Handle | undefined> = [\n this._header.getMinimizeButtonElement(),\n this._header.getMaximizeButtonElement(),\n this._header.getExitButtonElement(),\n ];\n for (const btn of buttons) {\n if (btn && DOM.source.contains(btn, target)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Records whether Shift was held at the header press, so the header drag\n * source — whose callbacks receive no key state — can tell a re-dock gesture\n * from a plain window move.\n *\n * @param e - The header `mousedown` event.\n */\n private captureHeaderShift(e: MouseEvent): void {\n this._headerDragShift = e.shiftKey;\n }\n\n /**\n * Builds the tab-dock payload for a Shift-drag of the header. `sourceTabId` is\n * the window's own id — never a strip's, so a drop target treats it as a\n * foreign dock rather than a same-strip reorder.\n *\n * @returns The {@link TabDragData} payload for the window's body content.\n */\n private buildHeaderDragData(): DragData {\n const content = this.findBodyHost();\n\n const data: TabDragData = {\n tabDrag: true,\n sourceTabId: this.getId(),\n componentId: content ? content.getId() : \"\",\n label: String(this._header.getText().getText()),\n };\n\n return { ...data };\n }\n\n /**\n * Vetoes the header drag unless Shift was held and the window has body\n * content. When it proceeds, it registers the live content so the\n * destination strip can resolve it from the id-only drag data.\n *\n * @returns `false` to veto (plain move / nothing to dock); otherwise `void`.\n */\n private onHeaderDragStart(): boolean | void {\n const shift = this._headerDragShift;\n this._headerDragShift = false;\n\n const content = this.findBodyHost();\n\n if (!shift || !content) {\n return false;\n }\n\n this._headerDragComponentId = content.getId();\n tabDragRegistry.set(content.getId(), content);\n }\n\n /**\n * Cleans up the registry entry once the gesture ends and, when the drop\n * docked the content elsewhere (so the window has emptied), closes the window.\n *\n * @param dropped - `true` when the release landed on a registered drop\n * target (whether it accepted or refused the drop); `false` on a release\n * over empty space. The window only closes when the content actually left.\n */\n private onHeaderDragEnd(dropped: boolean): void {\n tabDragRegistry.delete(this._headerDragComponentId);\n this._headerDragComponentId = \"\";\n\n if (dropped && this.findBodyHost() === null) {\n this.requestClose();\n }\n }\n}\n\nconst WindowCallable = callable(Window);\ntype WindowCallable = Window;\nexport {\n Window as _Window,\n WindowCallable as Window\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Panel, PanelOptions } from \"~/core/Panel.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { angle_left } from \"~/glyphs/solid/angle_left.js\";\nimport { angle_right } from \"~/glyphs/solid/angle_right.js\";\nimport { angle_up } from \"~/glyphs/solid/angle_up.js\";\nimport { angle_down } from \"~/glyphs/solid/angle_down.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Seed the registry with the four chevrons the arrows draw, independent of which\n// glyphs the consumer has imported (mirrors TabCloseButton's xmark seed).\nGlyph.register(angle_left, angle_right, angle_up, angle_down);\n\n/**\n * The per-end scroll-arrow gutter (px) reserved at each end of the strip when it\n * overflows. The arrows are square (gutter wide, strip-thickness tall), so this\n * is both the reserved main-axis space and the arrows' main-axis size. Matches\n * the tab-strip overflow chrome it was extracted from; an empirical fit for a\n * chevron glyph in a strip-height button.\n */\nconst SCROLL_ARROW_SIZE = 24;\n\n/**\n * Fallback per-click scroll step (px) when no step provider is wired and no\n * `arrowStep` is configured — roughly one wide control, so a click pages a\n * sensible amount before any content-derived step is available.\n */\nconst SCROLL_ARROW_STEP = 80;\n\n/**\n * The scroll axis of a {@link ScrollStrip}: `\"horizontal\"` lays the items in a\n * row (main axis X), `\"vertical\"` in a column (main axis Y).\n *\n * @category Components\n */\nexport type ScrollStripOrientation = \"horizontal\" | \"vertical\";\n\n/**\n * Construction-time options for {@link ScrollStrip}.\n *\n * @category Components\n */\nexport interface ScrollStripOptions extends PanelOptions {\n /** Scroll axis — `\"horizontal\"` (HBox) or `\"vertical\"` (VBox). Default `\"horizontal\"`. */\n orientation?: ScrollStripOrientation;\n\n /** When true, shows paging arrows on overflow; false clips only. Default `true`. */\n scrollable?: boolean;\n\n /** CSS background applied to the arrow buttons. Default the framework button background. */\n arrowBackground?: string;\n\n /** Fallback per-click step (px) used when no step provider is set. Defaults to roughly one wide control. */\n arrowStep?: number;\n}\n\n/**\n * A button rail that lays a row or column of items and scrolls them past its\n * edges when they overflow, showing lead/trail paging arrows in reserved gutters.\n *\n * `ScrollStrip` is a non-scrolling *band* element that hosts two things: an inner\n * `overflow:hidden` clip (the scroll-port) running an `HBox` (horizontal) or\n * `VBox` (vertical) over the items added via {@link addItem}, and the two paging\n * arrows. Splitting the band from the clip is what keeps the arrows fixed in their\n * gutters: they are children of the non-scrolling band, so they never inherit the\n * clip's scroll translation, while the items scroll inside the clip between them.\n * When the items overflow along the main axis the strip reserves a gutter at each\n * end (see {@link arrowReserve}); the owner sizes the band, and the strip sizes its\n * clip to the band minus the gutters and places the arrows ({@link layoutContent}).\n * The clip's native main-axis offset is the single source of truth — read via\n * {@link mainScroll}, written via {@link setMainScroll} — so any overlay\n * raw-appended into the clip element (via {@link getClipElement}) scrolls and\n * clips together with the items for free.\n *\n * The component is axis-generic and token-agnostic: it owns the scrolling and\n * arrow mechanic, while the owner keeps its own band geometry, theming (via\n * {@link setArrowBackground}), and per-item step ({@link setStepProvider}).\n *\n * @category Components\n */\nclass ScrollStrip extends Panel<ScrollStripOptions> {\n\n // The scroll axis. Written by `setOrientation` from `applyOptions` during the\n // super() cascade, so it must be `declare`d (a real initializer would revert\n // the cascade write). Seeded by the constructor body to \"horizontal\" before\n // any option dispatch when no orientation option is given.\n declare private _orientation: ScrollStripOrientation;\n\n // Whether the strip shows paging arrows on overflow. Cascade-written by\n // `setScrollable`, hence `declare`.\n declare private _scrollable: boolean;\n\n // Background applied to the arrow buttons. Cascade-written by\n // `setArrowBackground`, hence `declare`; null until a consumer themes them.\n declare private _arrowBackground: string | null;\n\n // Fallback per-click step (px). Cascade-written by `setArrowStep`, hence\n // `declare`.\n declare private _arrowStep: number;\n\n // Per-click step provider — when set, wins over `_arrowStep` so the owner can\n // track a live (font-derived) one-item extent. Not a cascade-written option\n // (it is a function the owner supplies after construction), so a plain field.\n private _stepProvider: (() => number) | null = null;\n\n // The inner clip: a Panel sized to the band minus the arrow gutters, with\n // overflow:hidden + native scroll. It — not this element — holds the box\n // children (the items) and any raw-appended overlays, so the items scroll and\n // clip inside it while the arrows (children of THIS non-scrolling band element)\n // hold their gutters. A nested Panel rather than this element because the clip\n // needs independent behaviour (its own scroll-port) from the band.\n private _clip: Panel = new Panel();\n\n // The two paging arrows, built lazily the first time the strip overflows while\n // scrollable. Raw-appended to THIS element (the non-scrolling band) so they sit\n // in the gutters and never inherit the inner clip's scroll translation.\n private _leadArrow: Button | null = null;\n private _trailArrow: Button | null = null;\n\n /**\n * Builds an empty scroll strip with the default horizontal orientation,\n * transparent background, and an inner overflow:hidden clip for the items.\n *\n * @param options - Optional construction-time options.\n */\n constructor(options?: ScrollStripOptions) {\n super(options);\n\n // Seed the cascade-written fields to their defaults. The super() cascade\n // already dispatched any option that was passed; these `??=`-style seeds\n // only fill an absent option, so an explicit option is never overwritten.\n this._orientation ??= \"horizontal\";\n this._scrollable ??= true;\n this._arrowBackground ??= null;\n this._arrowStep ??= SCROLL_ARROW_STEP;\n\n // ScrollStrip owns its own arrow paging + programmatic clip scroll, so\n // it never wants a synced overlay bar; force native to opt out of the\n // Panel default.\n this.setScrollbarStyle(\"native\");\n\n // The band element itself does not scroll (it hosts the fixed arrows); the\n // inner clip carries the overflow:hidden scroll-port. Both are transparent\n // so the owner's surface shows through.\n this.setBackgroundColor(\"transparent\");\n this.clearInsets();\n\n this._clip.setOverflow(\"hidden\");\n this._clip.setBackgroundColor(\"transparent\");\n this._clip.clearInsets();\n this.installBox(this._orientation);\n }\n\n /**\n * Raw-appends the inner clip into the band element at first render — the band\n * exists by then, unlike during construction.\n *\n * @param element - Optional. The element to initialise; falls back to `getElement()`.\n *\n * @returns This strip, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const host = element ?? this.getElement(true)!;\n DOM.sink.appendChild(host, this._clip.getElement(true)!);\n\n return this;\n }\n\n /**\n * Forwards the strip-only options through their typed setters. The base\n * `Panel`/`Component` options were already dispatched by `super()`.\n *\n * @param options - The construction options.\n *\n * @returns This strip, for method chaining.\n */\n protected applyOptions(options: ScrollStripOptions): this {\n super.applyOptions(options);\n\n if (options.orientation !== undefined) {\n this.setOrientation(options.orientation);\n }\n\n if (options.scrollable !== undefined) {\n this.setScrollable(options.scrollable);\n }\n\n if (options.arrowBackground !== undefined) {\n this.setArrowBackground(options.arrowBackground);\n }\n\n if (options.arrowStep !== undefined) {\n this.setArrowStep(options.arrowStep);\n }\n\n return this;\n }\n\n /**\n * Installs the box layout manager for the given orientation — `HBox` for\n * horizontal, `VBox` for vertical — matching the equal-mode, zero-spacing,\n * stretching configuration the strip's items expect.\n *\n * @param orientation - The orientation whose box to install.\n */\n private installBox(orientation: ScrollStripOrientation): void {\n // A cascade-dispatched `setOrientation` (from `applyOptions`, run inside\n // super()) can reach here before the `_clip` field initializer runs. Skip\n // it then — `_orientation` is already cached, and the constructor body\n // installs the box once `_clip` exists.\n if (!this._clip) {\n return;\n }\n\n const box = orientation === \"vertical\"\n ? new VBox({ mode: \"equal\", spacing: 0, stretching: true })\n : new HBox({ mode: \"equal\", spacing: 0, stretching: true });\n\n this._clip.setLayoutManager(box);\n }\n\n /**\n * Returns whether the strip scrolls along the Y axis (vertical) rather than X.\n *\n * @returns `true` for a vertical strip.\n */\n private isVertical(): boolean {\n return this._orientation === \"vertical\";\n }\n\n /**\n * Sets the scroll axis, swapping the box layout manager to match. A no-op\n * when already that orientation, so the box instance survives.\n *\n * @param orientation - The new scroll axis.\n *\n * @returns This strip, for method chaining.\n */\n setOrientation(orientation: ScrollStripOrientation): this {\n if (this._orientation === orientation) {\n return this;\n }\n\n this._orientation = orientation;\n this.installBox(orientation);\n\n return this;\n }\n\n /**\n * Returns the strip's scroll axis.\n *\n * @returns `\"horizontal\"` or `\"vertical\"`.\n */\n getOrientation(): ScrollStripOrientation {\n return this._orientation;\n }\n\n /**\n * Sets whether the strip shows paging arrows on overflow. When false,\n * {@link arrowReserve} is always 0 and the strip clips without arrows.\n *\n * @param value - True to enable paging arrows on overflow.\n *\n * @returns This strip, for method chaining.\n */\n setScrollable(value: boolean): this {\n this._scrollable = value;\n\n return this;\n }\n\n /**\n * Returns whether the strip shows paging arrows on overflow.\n *\n * @returns `true` when scrollable.\n */\n isScrollable(): boolean {\n return this._scrollable;\n }\n\n /**\n * Sets the CSS background applied to the arrow buttons, recolouring any that\n * are already built (so a consumer's focus-state swap repaints them).\n *\n * @param color - A CSS colour string.\n *\n * @returns This strip, for method chaining.\n */\n setArrowBackground(color: string): this {\n this._arrowBackground = color;\n\n this._leadArrow?.setBackgroundColor(color);\n this._trailArrow?.setBackgroundColor(color);\n\n return this;\n }\n\n /**\n * Sets the fallback per-click step (px) used when no step provider is wired.\n *\n * @param px - The per-click step in pixels.\n *\n * @returns This strip, for method chaining.\n */\n setArrowStep(px: number): this {\n this._arrowStep = px;\n\n return this;\n }\n\n /**\n * Sets a per-click step provider, consulted at click time so the step can\n * track a live (e.g. font-derived) item extent. Wins over {@link setArrowStep}\n * when set; pass `null` to fall back to the configured step.\n *\n * @param provider - A function returning the per-click step in px, or null.\n *\n * @returns This strip, for method chaining.\n */\n setStepProvider(provider: (() => number) | null): this {\n this._stepProvider = provider;\n\n return this;\n }\n\n /**\n * Resolves the per-click scroll step in px: the step provider's value when one\n * is wired, else the configured {@link setArrowStep} fallback.\n *\n * @returns The per-click step in pixels.\n */\n resolveStep(): number {\n return this._stepProvider ? this._stepProvider() : this._arrowStep;\n }\n\n /**\n * Adds an item as a box child of the inner clip — part of the scrolling\n * row/column.\n *\n * @param item - The item to append.\n *\n * @returns This strip, for method chaining.\n */\n addItem(item: Component): this {\n this._clip.addComponent(item);\n\n return this;\n }\n\n /**\n * Removes a previously-added item from the scrolling box.\n *\n * @param item - The item to remove.\n *\n * @returns This strip, for method chaining.\n */\n removeItem(item: Component): this {\n this._clip.removeComponent(item);\n\n return this;\n }\n\n /**\n * Moves an item to a new index within the scrolling box.\n *\n * @param item - The item to move.\n * @param toIndex - The destination index.\n *\n * @returns This strip, for method chaining.\n */\n moveItem(item: Component, toIndex: number): this {\n this._clip.moveComponent(item, toIndex);\n\n return this;\n }\n\n /**\n * Returns the items currently in the scrolling box, in order.\n *\n * @returns The clip's box children.\n */\n getItems(): Array<Component> {\n return this._clip.getComponents();\n }\n\n /**\n * Returns the inner clip's box layout manager — an `HBox` (horizontal) or\n * `VBox` (vertical) per the current orientation.\n *\n * @returns The clip's layout manager.\n */\n getContentBox(): ReturnType<Panel[\"getLayoutManager\"]> {\n return this._clip.getLayoutManager();\n }\n\n /**\n * Lays out the inner clip's box, sizing the items, then resyncs the strip's\n * cached scroll offset from the DOM (the browser may clamp the native offset on\n * its own when the content lays out smaller than the current offset). Call after\n * positioning the band (see {@link layoutContent}) and before reading the scroll.\n *\n * @returns This strip, for method chaining.\n */\n layoutItems(): this {\n this._clip.doLayout();\n this._clip.syncScrollOffsets();\n\n return this;\n }\n\n /**\n * Returns the inner clip element so an owner can raw-append overlays that must\n * scroll and clip with the items (e.g. a selection indicator).\n *\n * @param forceCreate - When true, realises the element if it does not exist yet.\n *\n * @returns The clip element handle, or null when not yet realised.\n */\n getClipElement(forceCreate?: boolean): Handle | null {\n return this._clip.getElement(forceCreate) ?? null;\n }\n\n /**\n * Returns the per-end scroll-arrow gutter (px) reserved when the strip is\n * scrollable and the items overflow the region along the main axis.\n *\n * A `+1` slop absorbs a flush fit so a strip that exactly fills its region\n * does not flicker the arrows: `content == region + 1` reserves nothing,\n * `content == region + 2` reserves a gutter.\n *\n * @param contentExtent - The predicted main-axis extent of the items (px).\n * @param regionExtent - The main-axis space available for the items (px).\n *\n * @returns 0, or the per-end gutter in px.\n */\n arrowReserve(contentExtent: number, regionExtent: number): number {\n if (!this._scrollable) {\n return 0;\n }\n\n return contentExtent > regionExtent + 1 ? SCROLL_ARROW_SIZE : 0;\n }\n\n /**\n * Lazily builds the two arrow buttons, styles them for the narrow gutter, wires\n * their paging actions, and raw-appends them above the box children.\n */\n private ensureArrows(): void {\n if (this._leadArrow && this._trailArrow) {\n return;\n }\n\n const lead = new Button({ glyph: \"angle-left\" });\n const trail = new Button({ glyph: \"angle-right\" });\n\n for (const button of [lead, trail]) {\n if (this._arrowBackground !== null) {\n button.setBackgroundColor(this._arrowBackground);\n }\n\n button.setBackgroundImage(\"none\");\n button.clearBorder();\n button.clearShadow();\n button.setBorderRadius(\"0\");\n // Drop the default button insets so the glyph fits the narrow gutter.\n button.clearInsets();\n // Above the box children so the arrows stay clickable at the strip\n // ends and cover any item that scrolls under them.\n button.setZIndex(3);\n }\n\n lead.on(\"action\", this.leadClicked);\n trail.on(\"action\", this.trailClicked);\n\n // Raw-append to the band element (this) — NOT the inner clip — so the\n // arrows stay fixed in their gutters and never inherit the clip's scroll\n // translation.\n const element = this.getElement(true)!;\n DOM.sink.appendChild(element, lead.getElement(true)!);\n DOM.sink.appendChild(element, trail.getElement(true)!);\n\n this._leadArrow = lead;\n this._trailArrow = trail;\n }\n\n /**\n * Lays out the strip's content within its own (owner-positioned) band: sizes\n * the inner clip to the band minus a gutter at each end, places and enables the\n * arrows into those gutters, runs the clip's box, and resyncs the cached scroll\n * offset. The band is the strip's own width/height; the gutters carry the fixed\n * arrows while the clip scrolls the items between them. An `endGap` trailing-\n * aligns the items by insetting the clip's leading edge.\n *\n * @param reserve - The per-end arrow gutter in px (0 = no arrows, clip spans the band).\n * @param endGap - The leading inset (px) that trailing-aligns the items (0 otherwise).\n *\n * @returns This strip, for method chaining.\n */\n layoutContent(reserve: number, endGap: number): this {\n const vertical = this.isVertical();\n const bandMain = vertical ? this.getHeight() : this.getWidth();\n const thickness = vertical ? this.getWidth() : this.getHeight();\n const clipMain = bandMain - 2 * reserve;\n\n // Size the inner clip to the region between the gutters and fold the\n // end-align gap into its leading inset, so the box trailing-aligns natively.\n if (vertical) {\n this._clip.setX(0);\n this._clip.setY(reserve);\n this._clip.setWidth(thickness);\n this._clip.setHeight(clipMain);\n this._clip.setInsets(new Insets(endGap, 0, 0, 0));\n } else {\n this._clip.setX(reserve);\n this._clip.setY(0);\n this._clip.setWidth(clipMain);\n this._clip.setHeight(thickness);\n this._clip.setInsets(new Insets(0, 0, 0, endGap));\n }\n\n this.layoutItems();\n this.layoutArrows(bandMain, thickness, reserve);\n\n return this;\n }\n\n /**\n * Positions, sizes, and enables the two arrows in the gutters at each end of the\n * band, or hides them when the band carries no reserve. The lead gutter sits at\n * band origin 0, the trail gutter at `bandMain - reserve`, both spanning the\n * cross-axis thickness. Each arrow is disabled (not hidden) at its scroll limit\n * so the chrome never shifts as the items scroll between them.\n *\n * @param bandMain - The band's main-axis extent (px).\n * @param thickness - The band's cross-axis thickness (px).\n * @param reserve - The per-end gutter (the arrows' main-axis size) in px; 0 hides them.\n */\n private layoutArrows(bandMain: number, thickness: number, reserve: number): void {\n if (!this._scrollable || reserve <= 0) {\n this.hideArrows();\n\n return;\n }\n\n this.ensureArrows();\n\n const lead = this._leadArrow as Button;\n const trail = this._trailArrow as Button;\n const vertical = this.isVertical();\n\n lead.setGlyph(vertical ? \"angle-up\" : \"angle-left\");\n trail.setGlyph(vertical ? \"angle-down\" : \"angle-right\");\n\n lead.setVisible(true);\n trail.setVisible(true);\n\n this.refreshArrows();\n\n const trailPos = bandMain - reserve;\n\n for (const button of [lead, trail]) {\n if (vertical) {\n // Pin the main-axis (height) to the gutter; fill the thickness.\n button.setMinSize({ width: 0, height: reserve });\n button.setMaxSize({ width: Number.MAX_VALUE, height: reserve });\n button.setX(0);\n button.setWidth(thickness);\n button.setHeight(reserve);\n } else {\n button.setMinSize({ width: reserve, height: 0 });\n button.setMaxSize({ width: reserve, height: Number.MAX_VALUE });\n button.setY(0);\n button.setHeight(thickness);\n button.setWidth(reserve);\n }\n }\n\n if (vertical) {\n lead.setY(0);\n trail.setY(trailPos);\n } else {\n lead.setX(0);\n trail.setX(trailPos);\n }\n }\n\n /**\n * Hides both arrows if they have been built.\n */\n private hideArrows(): void {\n this._leadArrow?.setVisible(false);\n this._trailArrow?.setVisible(false);\n }\n\n /**\n * Reads the clip's native scroll offset on the main axis — the single source\n * of truth for the scroll position.\n *\n * @returns The current main-axis scroll offset in px.\n */\n mainScroll(): number {\n return this.isVertical() ? this._clip.getScrollTop() : this._clip.getScrollLeft();\n }\n\n /**\n * Returns the clip's maximum native scroll offset on the main axis, derived\n * live from the laid-out content.\n *\n * @returns The last-page scroll offset in px (0 when nothing overflows).\n */\n private mainScrollMax(): number {\n return this.isVertical() ? this._clip.getMaxScrollTop() : this._clip.getMaxScrollLeft();\n }\n\n /**\n * Writes the clip's native main-axis scroll offset; the browser clamps to the\n * scrollable range, and the cross axis is left untouched.\n *\n * @param value - The desired main-axis scroll offset in px.\n */\n setMainScroll(value: number): void {\n if (this.isVertical()) {\n this._clip.setScrollTop(value);\n } else {\n this._clip.setScrollLeft(value);\n }\n }\n\n /**\n * Resets the clip's native scroll to the origin on both axes — used when the\n * scroll axis itself changes (e.g. an owner side-switch) so the strip starts\n * the new axis unscrolled.\n *\n * @returns This strip, for method chaining.\n */\n resetScroll(): this {\n this._clip.setScrollLeft(0);\n this._clip.setScrollTop(0);\n\n return this;\n }\n\n /**\n * Re-derives the arrows' enabled state from the live native scroll position:\n * the lead arrow is dead at the start, the trail arrow at the last page (with a\n * 1px slop so a flush-to-end strip disables cleanly despite sub-pixel rounding).\n */\n refreshArrows(): void {\n const lead = this._leadArrow;\n const trail = this._trailArrow;\n\n if (!lead || !trail) {\n return;\n }\n\n const scroll = this.mainScroll();\n\n lead.setEnabled(scroll > 0);\n trail.setEnabled(scroll < this.mainScrollMax() - 1);\n }\n\n /**\n * Scrolls the strip by `delta` px along the main axis through native scroll,\n * then refreshes the arrows. No relayout: native scroll moves the items and any\n * clip-element overlays together for free.\n *\n * @param delta - Signed pixel amount (negative = toward the start).\n */\n private scrollBy(delta: number): void {\n this.setMainScroll(this.mainScroll() + delta);\n this.refreshArrows();\n }\n\n /**\n * Handles a lead-arrow click: pages one step toward the start, resolving the\n * step at click time so it tracks a live step provider.\n */\n private leadClicked = (): void => {\n this.scrollBy(-this.resolveStep());\n };\n\n /**\n * Handles a trail-arrow click: pages one step toward the end.\n */\n private trailClicked = (): void => {\n this.scrollBy(this.resolveStep());\n };\n\n /**\n * Nudges the native scroll the minimum amount needed to bring the given item\n * element fully into view, measured from the *laid-out* DOM rects. A\n * fully-visible item produces no scroll. The owner gates when to call this (the\n * one-shot reveal-on-select policy is the owner's concern).\n *\n * @param itemElement - The element of the item to reveal.\n */\n revealItem(itemElement: Handle): void {\n const clipElement = this._clip.getElement();\n\n if (!clipElement) {\n return;\n }\n\n const vertical = this.isVertical();\n const clip = DOM.source.getElementRect(clipElement);\n const item = DOM.source.getElementRect(itemElement);\n\n const clipStart = vertical ? clip.top : clip.left;\n const clipEnd = vertical ? clip.bottom : clip.right;\n const itemStart = vertical ? item.top : item.left;\n const itemEnd = vertical ? item.bottom : item.right;\n\n let delta = 0;\n\n if (itemStart < clipStart) {\n delta = itemStart - clipStart;\n } else if (itemEnd > clipEnd) {\n delta = itemEnd - clipEnd;\n }\n\n // getBoundingClientRect already reflects the current scroll, so `delta` is\n // the screen-space correction; apply it to the native offset, then refresh\n // the arrows. Like scrollBy, a reveal that moves the scroll must re-derive\n // the arrows' enabled state — reveal-on-select can jump from the start to\n // the end (e.g. selecting the last tab while scrolled fully to the start),\n // which flips both arrows' limits.\n if (delta !== 0) {\n this.setMainScroll(this.mainScroll() + delta);\n this.refreshArrows();\n }\n }\n\n /**\n * Returns whether the given event target is one of the strip's arrow buttons —\n * for an owner's hit test that must distinguish chrome from blank area.\n *\n * @param target - The event target to test.\n *\n * @returns `true` when the target lies within either arrow.\n */\n containsArrow(target: EventTarget | null): boolean {\n if (!DOM.source.isNode(target)) {\n return false;\n }\n\n const handle = DOM.source.intern(target);\n\n for (const arrow of [this._leadArrow, this._trailArrow]) {\n const element = arrow?.getElement() ?? null;\n\n if (element && DOM.source.contains(element, handle)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Disposes the scrolling clip (and every item added via `addItem` it\n * holds) and the paging arrows, when built, then defers to the base\n * class for the rest of teardown.\n *\n * @remarks `_clip` / `_leadArrow` / `_trailArrow` are raw-appended to\n * this strip's own element rather than registered via `addComponent`\n * (see the constructor and `ensureArrows`), so the base class's\n * recursive teardown cannot reach them — or, through `_clip`, the items\n * it hosts.\n */\n protected destructor(): void {\n this._clip.dispose();\n this._leadArrow?.dispose();\n this._trailArrow?.dispose();\n\n super.destructor();\n }\n}\n\nconst ScrollStripCallable = callable(ScrollStrip);\ntype ScrollStripCallable = ScrollStrip;\nexport {\n ScrollStrip as _ScrollStrip,\n ScrollStripCallable as ScrollStrip,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\n/**\n * The set of tabs a bulk-close context-menu action targets, relative to the\n * right-clicked tab.\n *\n * - `\"all\"` — every tab.\n * - `\"others\"` — every tab except the clicked one.\n * - `\"right\"` — the tabs after the clicked one in strip order.\n * - `\"left\"` — the tabs before the clicked one in strip order.\n *\n * @category Components\n */\nexport type BulkCloseScope = \"others\" | \"right\" | \"left\" | \"all\";\n\n/**\n * Computes the ordered tab ids a bulk-close action should close, pre-filtered to\n * closeable tabs. Pure and framework-free so it is unit-testable outside the DOM\n * harness; the result is a stable snapshot the caller emits over without ever\n * re-reading the live tab list (each close mutates that list).\n *\n * @param ids - The tab ids in strip order.\n * @param clickedIndex - The right-clicked tab's index within `ids`.\n * @param isCloseable - Predicate reporting whether the tab with a given id is closeable.\n * @param scope - Which tabs, relative to the clicked one, the action targets.\n *\n * @returns The closeable ids in strip order for `scope`; `\"all\"` includes the\n * clicked tab, `\"others\"` excludes it.\n */\nexport function computeBulkCloseIds(\n ids: readonly string[],\n clickedIndex: number,\n isCloseable: (id: string) => boolean,\n scope: BulkCloseScope,\n): string[] {\n const inScope = ids.filter((_id, i) => {\n switch (scope) {\n case \"all\": return true;\n case \"others\": return i !== clickedIndex;\n case \"right\": return i > clickedIndex;\n case \"left\": return i < clickedIndex;\n }\n });\n\n return inScope.filter(isCloseable);\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport { Panel } from \"~/core/Panel.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { LayoutConstraints } from \"~/layout/LayoutConstraints.js\";\nimport { ToggleButton } from \"~/component/button/ToggleButton.js\";\nimport { TabButton } from \"~/component/button/TabButton.js\";\nimport { TabCloseButton } from \"~/component/button/TabCloseButton.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { ScrollStrip } from \"~/component/container/ScrollStrip.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ThemeManager } from \"~/core/Theme.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { ButtonGroup } from \"~/overlay/ButtonGroup.js\";\nimport { RovingTabIndex } from \"~/core/RovingTabIndex.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { BoxLayout } from \"~/layout/BoxLayout.js\";\nimport { Menu } from \"~/overlay/Menu.js\";\nimport { Tooltip } from \"~/overlay/Tooltip.js\";\nimport { MenuItemConfig } from \"~/component/container/MenuItem.js\";\nimport { computeBulkCloseIds } from \"~/component/container/tabCloseTargets.js\";\nimport type { BulkCloseScope } from \"~/component/container/tabCloseTargets.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { DragManager, DragEventDetail, DragData, TabDragData, SPRING_RAISE_DELAY_MS } from \"~/overlay/DragManager.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport type { TabWidthMode, TabSide, TabOrientation } from \"~/layout/Tab.js\";\nimport type { AxisPosition, AxisEnd } from \"~/primitive/Axis.js\";\n\n/**\n * Duration (ms) of the selection indicator's slide. Matches `AnimatedDropdown`'s\n * default so the bar's indicator and the ComboBox caret animate at the same pace\n * (and the owning `Tab`'s cross-tab content fade, which keeps its own copy).\n */\nconst TAB_FADE_DURATION_MS = 120;\n\n/**\n * Minimum strip thickness (px) on the cross axis — the *floor* `stripThickness`\n * falls back to before any tab has reported a preferred size (an empty or\n * pre-first-layout strip). Once buttons measure, the strip grows past this to fit\n * the font (see {@link TabBar.stripThickness}); this is no longer the value, only\n * the pre-measurement floor. Kept at the legacy `setPreferredSize({ width: 0, height: 30 })` seed\n * the top-only strip was tuned against so an unmeasured strip looks unchanged.\n * Reduced to `STRIP_THICKNESS_COMPACT` when the strip is `compact`.\n */\nconst STRIP_THICKNESS = 30;\n\n/** Reduced cross-axis strip thickness floor (px) used when the strip is `compact`. */\nconst STRIP_THICKNESS_COMPACT = 24;\n\n/**\n * Extra main-axis gap (px) between the leading widget's box and the first tab,\n * folded into the reserved lead extent (never into the widget's own box). Small\n * because the leading widget already carries the same tool inset as the trailing\n * controls (its box has a main-axis pad of its own), and the first tab carries\n * its tab inset — so the boxes nearly butt, with their own insets supplying most\n * of the breathing room and this knob adding a hair of separation.\n */\nconst LEAD_GLYPH_GAP = 4;\n\n/**\n * Floor (px) for the per-overflow-arrow-click scroll step, used only before a\n * tab has measured. {@link TabBar.scrollStepExtent} derives the live step from\n * the first tab's predicted extent so a click pages by ≈ one tab at any font\n * size; this kicks in when no tab has a preferred size yet (the predicted extent\n * is 0). Kept at the legacy fixed step so a pre-measurement click still nudges.\n */\nconst SCROLL_ARROW_STEP = 80;\n\n/**\n * String-literal union of the framework-custom events a {@link TabBar} emits to\n * its content owner. The bar interprets each DOM gesture into a window-agnostic\n * intent and the owner (typically a [`Tab`](/api/layout/classes/Tab)) reacts; the bar itself never\n * touches the content, a `Window`, or a `Tab`.\n *\n * - `\"tabpressed\"(id)` — a cell was activated; the owner swaps content / runs lazy-load.\n * - `\"reorder\"(fromId, toIndex)` — an in-strip reorder committed; the owner\n * re-derives its content order from {@link TabBar.getEntryIds}.\n * - `\"tabclose\"(id)` — a cell's ✕ was clicked; the owner removes the content.\n * - `\"dockrequested\"(componentId, slot)` — a foreign tab was dropped here; the\n * owner resolves and docks the live content keyed by `componentId`.\n * - `\"tabdragstart\"(id)` — a cell's drag committed; the owner registers its live\n * content so a foreign strip's drop can resolve it.\n * - `\"tearoffrequested\"(id, clientX, clientY, forceBare)` — a cell was released\n * over empty space; the owner tears it off (only if its content is ready).\n * - `\"detach\"(id)` — a cell's drag was released onto a target; the owner drops\n * the cell only if the content left its container (a within-strip reorder is a\n * no-op for the owner).\n * - `\"dockhover\"()` — a foreign tab has dwelt over this strip long enough to\n * spring-load a raise; the owner surfaces the strip's window so a backgrounded\n * float can be aimed at.\n *\n * @category Components\n */\nexport type TabBarEvent =\n \"tabpressed\" | \"reorder\" | \"tabclose\" | \"dockrequested\" | \"tabdragstart\" | \"tearoffrequested\" | \"detach\" | \"dockhover\";\n\n/**\n * Declares a strip tool that also surfaces in the tab context menu's `Tools`\n * submenu. Passed to {@link TabBar.addTool} (or the owning `Tab`'s `addTool`),\n * which builds both the strip {@link Button} and the menu row from this single\n * descriptor — so glyph, label, and action are declared exactly once.\n *\n * @category Components\n */\nexport interface TabToolDescriptor {\n /** Tooltip on the strip button and label on the menu row (required). */\n label: string;\n\n /** Optional registry glyph name for the button and menu row (matches {@link MenuItemConfig.glyph}). */\n glyph?: string;\n\n /** Invoked by both the strip button's action and the menu row — the same reference. */\n action: () => void;\n}\n\n/**\n * Construction-time options for {@link TabBar} — the bar-only subset of the\n * owning `Tab`'s options (no content / tear-off / lazy-load fields).\n *\n * @category Components\n */\nexport interface TabBarOptions extends ContainerOptions {\n /** Multi-event listener bag dispatched to {@link TabBar.on} at construction time. */\n listeners?: {\n tabpressed?: (id: string) => void;\n reorder?: (fromId: string, toIndex: number) => void;\n tabclose?: (id: string) => void;\n dockrequested?: (componentId: string, slot: number) => void;\n tabdragstart?: (id: string) => void;\n tearoffrequested?: (id: string, clientX: number, clientY: number, forceBare: boolean) => void;\n detach?: (id: string) => void;\n dockhover?: () => void;\n };\n\n /** Tab-button width strategy; defaults to `\"equal\"`. */\n widthMode?: TabWidthMode;\n\n /** Per-tab maximum width in px for `\"content\"` / `\"equal\"` modes; `null` (the default) leaves tabs uncapped. */\n maxWidth?: number | null;\n\n /** Per-tab width in px for `\"fixed\"` mode; defaults to `120`. */\n fixedWidth?: number;\n\n /**\n * Whether the 1px strip under-border runs edge-to-edge. When omitted, follows\n * the active theme's `tab.underBorderFullWidth`; setting it explicitly pins\n * the value and stops it tracking the theme.\n */\n underBorderFullWidth?: boolean;\n\n /** Which edge the tab strip sits on; defaults to `\"north\"`. */\n side?: TabSide;\n\n /** Main-axis alignment of the tab-button group; defaults to `\"start\"`. */\n align?: AxisEnd;\n\n /** Text orientation on the vertical sides; defaults to `\"horizontal\"`. */\n orientation?: TabOrientation;\n\n /**\n * Whether an overflowing strip scrolls (leading/trailing arrow buttons, tabs\n * kept at preferred size) instead of compressing the tabs to fit. Defaults to\n * `false`.\n */\n scrollable?: boolean;\n\n /**\n * Tools pinned at the far end of the strip, opposite the tabs. A plain\n * {@link Component} is a bare strip tool; a {@link TabToolDescriptor} also\n * surfaces in the context menu's `Tools` submenu.\n */\n tools?: (Component | TabToolDescriptor)[];\n\n /** Reduce tab-button insets for a denser strip. Defaults to `false`. */\n compact?: boolean;\n\n /** Enable within-strip header drag-reorder. Defaults to `false`. */\n reorderable?: boolean;\n\n /** Tab-label justification (strip-wide); defaults to `\"center\"`. */\n textAlign?: AxisPosition;\n}\n\n/**\n * Bookkeeping record for one bar cell — the bar half of the old `TabEntry`. Holds\n * everything the bar needs to render the cell, run its DnD, and report which cell\n * was acted on. Carries **no** content reference: the owner keys its own content\n * record by the same stable `id`, and `contentId` is a plain string (the live\n * content's component id, `\"\"` until it materializes) the bar puts on the drag\n * payload so a foreign strip can resolve the content.\n */\ninterface BarEntry {\n id: string;\n button: TabButton;\n closeButton?: TabCloseButton;\n /** The cell's display label — the same `name` the button was built with. */\n name: string;\n constraints?: LayoutConstraints;\n /**\n * The button's `contextmenu` subtree listener, retained so {@link TabBar.removeBarEntry}\n * can remove it. Subtree listeners are keyed by component id in a module-level\n * map (see {@link Event.addSubtreeListener}); removing the button's element\n * does not purge that map, so the listener must be torn down explicitly or it\n * (and the entry it closes over) leaks across open/close churn.\n */\n contextMenuListener: (e: MouseEvent) => void;\n /** The live content's component id, supplied by the owner via {@link TabBar.setEntryContentId}; `\"\"` until set. */\n contentId: string;\n}\n\n/**\n * The single shared selection bar that slides along the active tab. Styles\n * itself entirely from theme tokens; {@link TabBar} drives its position and\n * extent via {@link slideTo} on each layout pass.\n *\n * @remarks Lives as a raw-appended overlay inside the tab clip frame's element\n * rather than a laid-out child, so the box never allocates it a tab-cell slot.\n * The bar pins to the strip's *inner* edge (bottom for north, top for south,\n * right for west, left for east) and slides along the strip's main axis (X for\n * north/south, Y for west/east).\n */\nclass TabIndicator extends Component {\n private _mainPos: number = 0;\n private _mainExtent: number = 0;\n private _side: TabSide = \"north\";\n\n /**\n * Builds the indicator. Colour and the slide transition go through the\n * framework-tracked setters so they survive `applyStyle`'s inline-style\n * wipe; the token-driven edge geometry is re-applied in the `applyStyle`\n * override below, which the base setters don't know about.\n */\n constructor() {\n super();\n\n this.setBackgroundColor(\"var(--ts-ui-tab-indicator-color, #1a73e8)\");\n this.setTransition(`transform ${TAB_FADE_DURATION_MS}ms ease, width ${TAB_FADE_DURATION_MS}ms ease, height ${TAB_FADE_DURATION_MS}ms ease`);\n this.setPointerEvents(\"none\");\n // Raw-appended before any tab wrapper exists, so it sits first in DOM\n // order; lift it above the opaque tab buttons that would otherwise\n // paint over its 2px sliver.\n this.setZIndex(2);\n }\n\n /**\n * Positions the bar over the active tab cell along the strip's main axis.\n *\n * @param mainPos - The active cell's main-axis offset within the strip (X\n * for north/south, Y for west/east), in px.\n * @param mainExtent - The active cell's main-axis extent (width for\n * north/south, height for west/east), in px.\n * @param side - The active {@link TabSide}, selecting the pinned inner edge.\n *\n * @returns This indicator, for chaining.\n */\n slideTo(mainPos: number, mainExtent: number, side: TabSide): this {\n this._mainPos = mainPos;\n this._mainExtent = mainExtent;\n this._side = side;\n\n if (this.getElement()) {\n this.applyBarGeometry();\n }\n\n return this;\n }\n\n /**\n * Re-applies the token-driven bar geometry after the base re-render, which\n * strips inline styles and only replays the fields it tracks.\n *\n * @param element - The indicator's DOM element.\n *\n * @returns This indicator, for chaining.\n */\n applyStyle(element: Handle): this {\n super.applyStyle(element);\n this.applyBarGeometry();\n\n return this;\n }\n\n /**\n * Writes the inner-edge placement, token thickness, and main-axis extent —\n * the token-driven box styles the base `applyStyle` doesn't replay — and\n * slides the bar along the main axis through {@link Component.setTranslate}.\n * The translate goes through the framework-tracked transform channel (so\n * `getTranslateX` / `getTranslateY` report the slide and the base\n * `applyStyle` replays it), while the box edges are re-applied here on every\n * re-render. North/south draw a horizontal bar on the bottom/top edge sized\n * by `width` and slid with `translateX`; west/east draw a vertical bar on\n * the right/left edge sized by `height` and slid with `translateY`.\n *\n * @returns This indicator, for chaining.\n */\n private applyBarGeometry(): this {\n const thickness = \"var(--ts-ui-tab-indicator-thickness, 2px)\";\n const vertical = this._side === \"west\" || this._side === \"east\";\n\n if (vertical) {\n this.setTranslate(0, this._mainPos);\n\n return this.setElementStyles({\n top : \"0\",\n bottom : \"auto\",\n left : this._side === \"east\" ? \"0\" : \"auto\",\n right : this._side === \"west\" ? \"0\" : \"auto\",\n width : thickness,\n height : this._mainExtent + \"px\",\n });\n }\n\n this.setTranslate(this._mainPos, 0);\n\n return this.setElementStyles({\n left : \"0\",\n right : \"auto\",\n top : this._side === \"south\" ? \"0\" : \"auto\",\n bottom : this._side === \"north\" ? \"0\" : \"auto\",\n width : this._mainExtent + \"px\",\n height : thickness,\n });\n }\n}\n\n/**\n * The insertion rule shown during a within-strip tab drag-reorder. Like\n * {@link TabIndicator} it is a raw-appended overlay inside the clip frame element\n * (so the box never allocates it a cell) and is driven entirely by {@link TabBar}.\n * Unlike the indicator it is main-axis-aware: a thin vertical rule for\n * north/south strips, a thin horizontal rule for west/east strips.\n *\n * @remarks Geometry is written through the framework-tracked `setX` / `setY` /\n * `setWidth` / `setHeight` / `setVisible` setters, all of which the base\n * `applyStyle` replays, so no custom replay override is needed.\n */\nclass TabReorderBar extends Component {\n\n /** Insertion-rule thickness (px) along the strip's main axis. */\n private static readonly THICKNESS = 2;\n\n /**\n * Builds the rule: the shared drag-reorder colour, no pointer events, lifted\n * above the opaque tab buttons, hidden until a drag positions it.\n */\n constructor() {\n super();\n\n this.setBackgroundColor(\"var(--ts-ui-drag-reorder-color, #1a73e8)\");\n this.setPointerEvents(\"none\");\n this.setZIndex(2);\n this.setVisible(false);\n }\n\n /**\n * Positions the rule at a slot boundary and shows it.\n *\n * @param mainCoord - The boundary's leading-edge offset on the strip's main\n * axis (X for north/south, Y for west/east), in px.\n * @param crossExtent - The strip's cross-axis thickness the rule spans, in px.\n * @param vertical - `true` for west/east strips (a horizontal rule), `false`\n * for north/south strips (a vertical rule).\n *\n * @returns This reorder bar, for chaining.\n */\n placeAt(mainCoord: number, crossExtent: number, vertical: boolean): this {\n if (vertical) {\n this.setX(0);\n this.setY(mainCoord);\n this.setWidth(crossExtent);\n this.setHeight(TabReorderBar.THICKNESS);\n } else {\n this.setX(mainCoord);\n this.setY(0);\n this.setWidth(TabReorderBar.THICKNESS);\n this.setHeight(crossExtent);\n }\n\n this.setVisible(true);\n\n return this;\n }\n\n /**\n * Hides the insertion rule (drag left the strip, or the drop completed).\n *\n * @returns This reorder bar, for chaining.\n */\n hide(): this {\n this.setVisible(false);\n\n return this;\n }\n}\n\n/**\n * The faint full-strip wash shown while a tab-header drag hovers the strip — the\n * \"you can drop a tab here\" affordance, the strip's counterpart to the\n * [`DropZoneOverlay`](/api/overlay/classes/DropZoneOverlay) root tint a\n * [`DockRegion`](/api/layout/classes/DockRegion) paints over a region. It shares\n * the dock's drop-zone token so the whole-target droppable cue reads the same\n * blue everywhere; the precise insertion position is the brighter\n * {@link TabReorderBar} drawn above it (`z-index` 1 vs 2). Pure affordance — no\n * pointer events, hidden until a drag positions it.\n */\nclass TabDropTint extends Component {\n\n /** Builds the wash: faint drop-zone blue, below the reorder bar, hidden. */\n constructor() {\n super();\n\n this.setBackgroundColor(\"var(--ts-ui-drag-dropzone-bg)\");\n this.setPointerEvents(\"none\");\n this.setZIndex(1);\n this.setVisible(false);\n }\n\n /**\n * Sizes the wash to cover `strip` (the clip viewport it is parented in) and\n * shows it.\n *\n * @param strip - The clip frame whose visible box the wash should cover.\n *\n * @returns This wash, for chaining.\n */\n showOver(strip: Component): this {\n this.setX(0);\n this.setY(0);\n this.setWidth(strip.getWidth());\n this.setHeight(strip.getHeight());\n this.setVisible(true);\n\n return this;\n }\n\n /** Hides the wash (drag left the strip, or the drop completed). */\n hide(): this {\n this.setVisible(false);\n\n return this;\n }\n}\n\n/**\n * A standalone, window-agnostic tab **strip** — the toolbar element, the tab\n * buttons, the selection indicator, the reorder bar, the tool group, overflow\n * scrolling, and all tab drag-and-drop — with **no** content machinery. It is\n * the bar half extracted from the [`Tab`](/api/layout/classes/Tab) layout\n * manager, which now composes one and reacts to its events.\n *\n * `TabBar` owns one DOM element (the strip toolbar) and renders the cells the\n * owner registers through {@link createBarEntry}. It interprets each DOM gesture\n * (click, drag, drop, right-click, arrow key) into a window-agnostic\n * {@link TabBarEvent} the owner reacts to — the bar never touches content, a\n * `Window`, or a `Tab`, so it is a pure dependency sink. The strip is positioned\n * by its owner through {@link prepareStrip} → {@link stripThickness} →\n * {@link placeStrip} each layout pass.\n *\n * Extends [`Container`](/api/core/classes/Container) (not a bare `Component`) so\n * the strip fills its allocated edge: `clampsToContentSize()` is `false` (defined\n * on `Container`), so `setWidth` / `setHeight` accept the full container extent\n * instead of shrinking to the tab buttons' content max.\n *\n * @category Components\n */\nclass TabBar extends Container<TabBarOptions> {\n\n // Clips the tab region and owns its overflow chrome. A ScrollStrip whose own\n // element is the clip frame: it holds the tab wrappers (its box children) plus\n // the raw-appended selection indicator and reorder bar, and owns the paging\n // arrows in the gutters it reserves on overflow. Positioned to span the whole\n // tab band; overflow:hidden clips a scrolled tab — and its overlaid ✕ — at the\n // region edge instead of bleeding over the tool buttons.\n private _tabClip: ScrollStrip = new ScrollStrip({ scrollable: false });\n private _entries: Array<BarEntry> = [];\n private _buttonGroup: ButtonGroup = new ButtonGroup();\n // The strip owns the clip frame's native scroll explicitly (arrow clicks and\n // `revealSelectedIfRequested` are the only writers). Focus the active tab\n // without the browser also scrolling its `overflow:hidden` clip frame into\n // view, so that single scroll path stays authoritative.\n private _rovingTabIndex: RovingTabIndex = new RovingTabIndex({ preventScroll: true });\n // The active cell's id (the bar tracks selection by id, not index, so it\n // stays correct across reorder/dock/close — see the class docs). `null` until\n // the first cell is created.\n private _activeId: string | null = null;\n private _listeners: ListenerBag<TabBarEvent> = new ListenerBag<TabBarEvent>();\n\n private _widthMode: TabWidthMode = \"equal\";\n private _maxWidth: number | null = null;\n private _fixedWidth: number = 120;\n private _underBorderFullWidth: boolean = true;\n private _underBorderFromTheme: boolean = true;\n private _indicator: TabIndicator = new TabIndicator();\n\n private _side: TabSide = \"north\";\n private _align: AxisEnd = \"start\";\n private _orientation: TabOrientation = \"horizontal\";\n private _scrollable: boolean = false;\n private _compact: boolean = false;\n private _textAlign: AxisPosition = \"center\";\n\n // Shared rebuild-mode context menu reused across right-clicks, mirroring\n // Table's column-header menu. Rebuild-mode menus only attach to the DOM\n // during `show()` and self-dismiss on outside mousedown / `hide()`, so no\n // detach teardown is needed.\n private _contextMenu: Menu = new Menu();\n\n // Tool buttons pinned at the far end of the strip. Held in a hand-positioned\n // overlay (its own inner box) raw-appended to the strip element next to the\n // indicator, rather than enrolled as box children — so the tab wrappers stay\n // the clip frame's only box children and their indices line up 1:1 with\n // `_entries` for the reorder/indicator/close-button math.\n private _tools: Component[] = [];\n // Maps each descriptor-built tool button to its descriptor, so `openTabMenu`\n // can list only the tools that opted into a menu row (plain-Component tools\n // are absent) and `removeTool` can drop the row when its tool is removed.\n // `_tools` stays the ordering source of truth; this is keyed lookup only.\n private _toolMenuItems: Map<Component, TabToolDescriptor> = new Map();\n // Also a Panel so it fills its reserved slot rather than clamping to the tool\n // buttons' content max (same reason as the strip itself).\n private _toolGroup: Panel = new Panel();\n\n // Leading-slot group pinned at the start of the strip, align-independent —\n // unlike `_toolGroup`, which flips ends with `_align`. A stretching HBox/VBox\n // overlay that mirrors `_toolGroup` exactly, so the hosted widget is stretched\n // across the strip thickness and its glyph auto-syncs to the strip line-height\n // the same way a trailing tool's does (a bare raw-appended widget would skip\n // that stretch-and-sync and end up smaller than the controls). Transparent and\n // pointer-transparent: the hosted widget is decorative and presses fall through\n // to the empty-area window-move trigger.\n private _leadGroup: Panel = new Panel();\n // The single caller-supplied widget hosted in `_leadGroup`, or null until\n // `setLeadingWidget` is first called. Tracked separately so the extent/position\n // helpers can short-circuit to a no-op when the slot is empty.\n private _leadWidget: Component | null = null;\n\n // One-shot: scroll the selected tab into view on the next layout. Set when\n // scrolling first becomes active (enabling `scrollable`, or a side switch\n // while scrollable), so the selected tab isn't left clipped off-screen.\n private _scrollToSelected: boolean = false;\n\n // Within-strip drag-reorder wiring (see installTabDnD / teardownTabDnD).\n private _reorderable: boolean = false;\n private _reorderBar: TabReorderBar = new TabReorderBar();\n private _dropTint: TabDropTint = new TabDropTint();\n private _dndTeardowns: Array<() => void> = [];\n private _dragMouseTarget: EventTarget | null = null;\n\n // Teardown for the empty-bar-area window-move trigger (see installMoveTrigger).\n // Kept OUT of `_dndTeardowns` because that array is swept by teardownTabDnD on\n // every reorder/first-render install — which would kill the gesture. Drained\n // only by dispose().\n private _moveTriggerTeardown: (() => void) | null = null;\n\n // Whether Shift was held at the press that began a tab drag — captured at\n // mousedown (the drag callbacks get no key state) and read at tear-off so the\n // owner can force a bare detach window regardless of its detach mode.\n private _dragShiftHeld: boolean = false;\n private _dragInsertIndex: number = -1;\n\n // Pending spring-loaded host-window raise while a foreign tab dwells over the\n // strip; armed once per dwell in onDragOver and cleared the moment the cursor\n // leaves or drops. Fires \"dockhover\" so the owner raises the strip's window.\n private _springRaiseTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Creates a tab strip with an empty toolbar.\n *\n * @param options - Optional construction-time options.\n */\n constructor(options?: TabBarOptions) {\n super(options);\n\n // This bar's element is the strip toolbar (was Tab._toolbar). Configure\n // it, then build the raw-appended chrome overlays. The base options were\n // already dispatched by the Container/Component super cascade; the bar-only\n // options are dispatched at the end of this body once the sub-components\n // exist (so a `tools` / `listeners` option has somewhere to land).\n this.setLayoutManager(new HBox({ mode: \"equal\", spacing: 0, stretching: true }));\n this.setBackgroundColor(\"var(--ts-ui-tab-toolbar-bg, #eee)\");\n this._underBorderFullWidth = ThemeManager.getTheme().tab.underBorderFullWidth;\n this.applyUnderBorder();\n // Seeds the bar's own preferred size with the floor; the owning `Tab`\n // sizes the strip from `stripThickness()` directly (never from this\n // self-report), so this is only the bar's pre-layout self-size, kept at\n // the floor so it matches the unmeasured strip.\n this.setPreferredSize({ width: 0, height: STRIP_THICKNESS });\n this.getAria().setRole(\"tablist\");\n\n // The tab wrappers live in the clip frame (its box lays them out), not on\n // the strip directly. The ScrollStrip already configures its own\n // transparent, overflow:hidden clip and box; here the strip themes the\n // paging arrows to the toolbar surface and supplies the per-click step as\n // one tab's live extent (so a click pages by a tab at any font size).\n // Hand-positioned in `layoutChrome`, so it is raw-appended (not a box child).\n this._tabClip.setArrowBackground(\"var(--ts-ui-tab-toolbar-bg, #eee)\");\n this._tabClip.setStepProvider(() => this.scrollStepExtent());\n\n // The tool group is a hand-positioned overlay (not a strip box child); it\n // runs its own box to lay its buttons out along the strip's main axis,\n // stretching them across the strip thickness (the box's cross axis).\n this._toolGroup.setLayoutManager(new HBox({ spacing: 0, stretching: true }));\n // Opaque strip background (not transparent) so scrolled tabs slide behind\n // the tool group rather than showing through it.\n this._toolGroup.setBackgroundColor(\"var(--ts-ui-tab-toolbar-bg, #eee)\");\n this._toolGroup.clearInsets();\n // Lift the tool group above the tab wrappers (which are appended later in\n // DOM order) so its buttons stay clickable in their reserved slot.\n this._toolGroup.setZIndex(1);\n\n // The leading group mirrors the tool group at the opposite (start) end:\n // its own stretching box lays the hosted widget out across the strip\n // thickness so the widget's glyph syncs to the strip line-height exactly\n // like a trailing tool, making it a true size/inset peer of the controls.\n // Transparent (the strip start sits before the scrollable tab clip, so no\n // tab ever slides behind it) and pointer-transparent (decorative — presses\n // fall through to the empty-area window-move trigger).\n this._leadGroup.setLayoutManager(new HBox({ spacing: 0, stretching: true }));\n this._leadGroup.setBackgroundColor(\"transparent\");\n this._leadGroup.clearInsets();\n this._leadGroup.setZIndex(1);\n this._leadGroup.setPointerEvents(\"none\");\n\n // Follow the active theme's under-border default until a consumer pins it\n // explicitly. Released by the base class's dispose(). The owner\n // re-lays-out the strip on theme change (it owns the band geometry);\n // this only tracks the border.\n this.subscribeTheme(() => {\n if (!this._underBorderFromTheme) {\n return;\n }\n\n this._underBorderFullWidth = ThemeManager.getTheme().tab.underBorderFullWidth;\n this.applyUnderBorder();\n });\n\n this.dispatchBarOptions(options);\n }\n\n /**\n * Dispatches the bar-only options through their typed setters once the strip\n * sub-components exist. Run from the constructor body (not `applyOptions`,\n * which fires inside the Panel/Component `super()` cascade before the clip\n * frame, tool group, and listener bag are constructed).\n *\n * @param options - The construction options, or `undefined`.\n */\n private dispatchBarOptions(options?: TabBarOptions): void {\n if (!options) {\n return;\n }\n\n if (options.widthMode !== undefined) {\n this.setWidthMode(options.widthMode);\n }\n\n if (options.maxWidth !== undefined) {\n this.setMaxWidth(options.maxWidth);\n }\n\n if (options.fixedWidth !== undefined) {\n this.setFixedWidth(options.fixedWidth);\n }\n\n if (options.underBorderFullWidth !== undefined) {\n this.setUnderBorderFullWidth(options.underBorderFullWidth);\n }\n\n if (options.side !== undefined) {\n this.setSide(options.side);\n }\n\n if (options.align !== undefined) {\n this.setAlign(options.align);\n }\n\n if (options.orientation !== undefined) {\n this.setOrientation(options.orientation);\n }\n\n if (options.scrollable !== undefined) {\n this.setScrollable(options.scrollable);\n }\n\n if (options.compact !== undefined) {\n this.setCompact(options.compact);\n }\n\n if (options.reorderable !== undefined) {\n this.setReorderable(options.reorderable);\n }\n\n if (options.textAlign !== undefined) {\n this.setTextAlign(options.textAlign);\n }\n\n if (options.tools !== undefined) {\n // Dispatch each element with an explicit `instanceof Component` branch:\n // TypeScript overload resolution rejects a `Component | TabToolDescriptor`\n // union passed against the two separate non-union `addTool` signatures,\n // so each arm passes a narrowed type that matches one overload.\n for (const tool of options.tools) {\n if (tool instanceof Component) {\n this.addTool(tool);\n } else {\n this.addTool(tool);\n }\n }\n }\n\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Recolors every opaque toolbar surface of the bar — the strip Panel itself,\n * the tool-group overlay, and (when built) the two scroll-arrow buttons — so a\n * focus-state swap paints the whole bar uniformly. A recolor only; it does not\n * relayout.\n *\n * @param color - A CSS color string applied to every toolbar surface.\n *\n * @returns This tab strip, for method chaining.\n */\n setBarSurfaceColor(color: string): this {\n this.setBackgroundColor(color);\n this._toolGroup.setBackgroundColor(color);\n this._tabClip.setArrowBackground(color);\n\n return this;\n }\n\n /**\n * Raw-appends the chrome overlays (clip frame, tool group, indicator, reorder\n * bar) into the strip element, wires the toolbar keyboard handler, and\n * performs the deferred drag-reorder install. Runs once, at first render —\n * the strip element exists by then, unlike during construction.\n *\n * @param element - Optional. The element to initialise; falls back to `getElement()`.\n *\n * @returns This tab strip, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const host = element ?? this.getElement(true)!;\n\n // The clip frame (which holds the tab wrappers) and the tool group are\n // raw-appended overlays — positioned manually in `layoutChrome` rather\n // than enrolled in a box. The selection indicator and reorder bar go\n // *inside* the clip frame so they share the wrappers' coordinate space\n // (and clip / scroll with them) rather than the strip's.\n DOM.sink.appendChild(host, this._tabClip.getElement(true)!);\n DOM.sink.appendChild(host, this._toolGroup.getElement(true)!);\n DOM.sink.appendChild(host, this._leadGroup.getElement(true)!);\n\n const clip = this._tabClip.getClipElement(true)!;\n DOM.sink.appendChild(clip, this._indicator.getElement(true)!);\n DOM.sink.appendChild(clip, this._dropTint.getElement(true)!);\n DOM.sink.appendChild(clip, this._reorderBar.getElement(true)!);\n\n Event.addSubtreeListener(this, \"keydown\", this.onToolbarKeyDown);\n\n // The reorder option may have been set during construction before the\n // strip element existed; perform the deferred install now.\n if (this._reorderable) {\n this.installTabDnD();\n }\n\n return this;\n }\n\n /**\n * Tears down all drag wiring and the raw-appended chrome overlays (see\n * `init()`), then defers to the base class for the theme subscription,\n * the element, and everything else. Called by the owner when it detaches.\n *\n * @remarks `_tabClip` / `_toolGroup` / `_leadGroup` / `_indicator` /\n * `_dropTint` / `_reorderBar` are appended straight to the strip element\n * rather than registered via `addComponent` (mirroring `WindowBorder`'s\n * strips), so the base class's recursive teardown cannot reach them —\n * they, and everything they in turn hold (tab cells, tool buttons, the\n * lead widget), must be disposed explicitly here.\n */\n protected destructor(): void {\n this.teardownTabDnD();\n this.clearSpringRaise();\n this._moveTriggerTeardown?.();\n this._moveTriggerTeardown = null;\n\n this._tabClip.dispose();\n this._toolGroup.dispose();\n this._leadGroup.dispose();\n this._indicator.dispose();\n this._dropTint.dispose();\n this._reorderBar.dispose();\n\n super.destructor();\n }\n\n /**\n * Applies the current `_underBorderFullWidth` value to the strip: a full-width\n * 1px rule when set, no border when cleared.\n */\n private applyUnderBorder(): void {\n if (!this._underBorderFullWidth) {\n this.clearBorder();\n\n return;\n }\n\n const rule = \"1px solid var(--ts-ui-tab-toolbar-border, #e1e1e8)\";\n\n // The under-border is the single rule between the strip and the content\n // area, so it sits on the strip's *inner* edge — the one adjacent to the\n // content: bottom for north, top for south, right for west, left for\n // east. The other three edges stay borderless.\n this.setBorder({\n borderTop: this._side === \"south\" ? rule : \"none\",\n borderBottom: this._side === \"north\" ? rule : \"none\",\n borderLeft: this._side === \"east\" ? rule : \"none\",\n borderRight: this._side === \"west\" ? rule : \"none\",\n });\n }\n\n /**\n * Selects the tab-button width strategy and re-lays out the strip.\n *\n * @param mode - The width strategy to apply.\n *\n * @returns This tab strip, for method chaining.\n */\n setWidthMode(mode: TabWidthMode): this {\n this._widthMode = mode;\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current tab-button width strategy.\n *\n * @returns The active [`TabWidthMode`](/api/layout/type-aliases/TabWidthMode).\n */\n getWidthMode(): TabWidthMode {\n return this._widthMode;\n }\n\n /**\n * Sets the per-tab width cap used by the `\"content\"` and `\"equal\"` width\n * modes, then re-lays out the strip.\n *\n * @param px - The maximum width per tab in px, or `null` to remove the cap.\n *\n * @returns This tab strip, for method chaining.\n */\n setMaxWidth(px: number | null): this {\n this._maxWidth = px;\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current per-tab maximum width.\n *\n * @returns The cap in px, or `null` when tabs are uncapped.\n */\n getMaxWidth(): number | null {\n return this._maxWidth;\n }\n\n /**\n * Sets the per-tab width used by the `\"fixed\"` width mode, then re-lays out\n * the strip.\n *\n * @param px - The fixed width per tab in px.\n *\n * @returns This tab strip, for method chaining.\n */\n setFixedWidth(px: number): this {\n this._fixedWidth = px;\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the per-tab width used by the `\"fixed\"` width mode.\n *\n * @returns The fixed width in px.\n */\n getFixedWidth(): number {\n return this._fixedWidth;\n }\n\n /**\n * Toggles the edge-to-edge 1px rule under the tab strip. Pins the value for\n * this instance, so it no longer follows the active theme's\n * `tab.underBorderFullWidth` default on theme changes.\n *\n * @param full - `true` to draw the strip's full-width under-border, `false` to remove it.\n *\n * @returns This tab strip, for method chaining.\n */\n setUnderBorderFullWidth(full: boolean): this {\n this._underBorderFromTheme = false;\n this._underBorderFullWidth = full;\n\n this.applyUnderBorder();\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether the strip's under-border runs edge-to-edge.\n *\n * @returns `true` when the full-width under-border is drawn.\n */\n isUnderBorderFullWidth(): boolean {\n return this._underBorderFullWidth;\n }\n\n /**\n * Selects which edge of the content area the tab strip sits on and re-lays\n * out. Caches the value and resets the scroll; the orientation swap and\n * placement happen in the next layout pass.\n *\n * @param side - The [`TabSide`](/api/layout/type-aliases/TabSide) to place the strip on.\n *\n * @returns This tab strip, for method chaining.\n */\n setSide(side: TabSide): this {\n this._side = side;\n\n // The scroll axis flips with the side, so start the new side unscrolled,\n // then bring the selected tab into view if the new axis is scrollable.\n this._tabClip.resetScroll();\n\n if (this._scrollable) {\n this._scrollToSelected = true;\n }\n\n this.applyUnderBorder();\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the edge the tab strip currently sits on.\n *\n * @returns The active [`TabSide`](/api/layout/type-aliases/TabSide).\n */\n getSide(): TabSide {\n return this._side;\n }\n\n /**\n * Sets the main-axis alignment of the tab-button group within the strip and\n * re-lays out.\n *\n * @param align - The [`AxisEnd`](/api/primitive/type-aliases/AxisEnd) to apply.\n *\n * @returns This tab strip, for method chaining.\n */\n setAlign(align: AxisEnd): this {\n this._align = align;\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current tab-button-group alignment.\n *\n * @returns The active [`AxisEnd`](/api/primitive/type-aliases/AxisEnd).\n */\n getAlign(): AxisEnd {\n return this._align;\n }\n\n /**\n * Sets the tab-button text orientation for the vertical sides and re-lays\n * out. Caches the value only; the layout pass re-applies the `writing-mode`\n * to every tab button.\n *\n * @param orientation - The [`TabOrientation`](/api/layout/type-aliases/TabOrientation) to apply.\n *\n * @returns This tab strip, for method chaining.\n */\n setOrientation(orientation: TabOrientation): this {\n this._orientation = orientation;\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current vertical-side tab text orientation.\n *\n * @returns The active [`TabOrientation`](/api/layout/type-aliases/TabOrientation).\n */\n getOrientation(): TabOrientation {\n return this._orientation;\n }\n\n /**\n * Sets the strip-wide tab-label justification and re-lays out. Caches the\n * value only; the layout pass re-applies the `text-align` to every tab button.\n *\n * @param align - The [`AxisPosition`](/api/primitive/type-aliases/AxisPosition) to apply.\n *\n * @returns This tab strip, for method chaining.\n */\n setTextAlign(align: AxisPosition): this {\n this._textAlign = align;\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current tab-label justification.\n *\n * @returns The active [`AxisPosition`](/api/primitive/type-aliases/AxisPosition).\n */\n getTextAlign(): AxisPosition {\n return this._textAlign;\n }\n\n /**\n * Sets whether an overflowing strip scrolls (leading/trailing arrow buttons,\n * tabs kept at preferred size) instead of compressing the tabs to fit, and\n * re-lays out.\n *\n * @param value - `true` to scroll on overflow, `false` to compress.\n *\n * @returns This tab strip, for method chaining.\n */\n setScrollable(value: boolean): this {\n // Enabling from a non-scrolling state: reveal the selected tab on the next\n // layout rather than starting at offset 0 (it may be off-screen).\n if (value && !this._scrollable) {\n this._scrollToSelected = true;\n }\n\n this._scrollable = value;\n this._tabClip.setScrollable(value);\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether an overflowing strip scrolls instead of compressing.\n *\n * @returns `true` when the strip scrolls on overflow.\n */\n isScrollable(): boolean {\n return this._scrollable;\n }\n\n /**\n * Toggles reduced tab-button insets (a denser strip) and re-lays out. Caches\n * the value only; the layout pass re-derives every button's insets.\n *\n * @param value - `true` for compact insets, `false` for the default.\n *\n * @returns This tab strip, for method chaining.\n */\n setCompact(value: boolean): this {\n this._compact = value;\n\n // Compact changes every tab's width, which can leave the selected tab\n // partly clipped at the current scroll offset; nudge it back into view\n // (a no-op when it already fits, so the scroll position is otherwise kept).\n if (this._scrollable) {\n this._scrollToSelected = true;\n }\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether the strip uses reduced (compact) tab-button insets.\n *\n * @returns `true` when compact.\n */\n isCompact(): boolean {\n return this._compact;\n }\n\n /**\n * Enables or disables within-strip drag-reorder of tab headers. Unlike the\n * pure-placement setters this one installs / tears down the drag sources and\n * the drop target — but only when the strip element already exists (created at\n * first render). When called during construction it just caches the flag;\n * `init` performs the install if `_reorderable` is set.\n *\n * @param value - `true` to enable header drag-reorder.\n *\n * @returns This tab strip, for method chaining.\n */\n setReorderable(value: boolean): this {\n if (this._reorderable === value) {\n return this;\n }\n\n this._reorderable = value;\n\n if (!this.getElement()) {\n return this;\n }\n\n if (value) {\n this.installTabDnD();\n } else {\n this.teardownTabDnD();\n }\n\n return this;\n }\n\n /**\n * Returns whether within-strip header drag-reorder is enabled.\n *\n * @returns `true` when reorderable.\n */\n isReorderable(): boolean {\n return this._reorderable;\n }\n\n /**\n * Adds a tool at the far end of the strip, opposite the tab buttons.\n *\n * Two forms: a plain {@link Component} is added as a bare strip tool with no\n * context-menu entry (the original behaviour); a {@link TabToolDescriptor}\n * has its strip {@link Button} and its `Tools`-submenu row built from the one\n * descriptor, so glyph, label, and action are declared exactly once. The two\n * are told apart by `instanceof Component` — a descriptor is a plain object.\n *\n * @param button - The tool component to add (no menu row).\n *\n * @returns This tab strip, for method chaining.\n */\n addTool(button: Component): this;\n\n /**\n * @param descriptor - Declares a tool that also appears in the context menu's\n * `Tools` submenu; the strip button and menu row are built internally.\n */\n addTool(descriptor: TabToolDescriptor): this;\n\n addTool(arg: Component | TabToolDescriptor): this {\n const button = arg instanceof Component ? arg : this.buildDescriptorTool(arg);\n\n this._tools.push(button);\n this._toolGroup.addComponent(button);\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Builds the flat strip {@link Button} for a descriptor tool — glyph from the\n * descriptor, `label` as its tooltip, `action` as its press handler — and\n * registers its menu row in {@link _toolMenuItems} so {@link openTabMenu}\n * lists it. The button's press and the menu row invoke the same `action`\n * reference, so the two can never diverge.\n *\n * @param descriptor - The tool descriptor to build a strip button for.\n *\n * @returns The built, flat, tooltip'd tool button.\n */\n private buildDescriptorTool(descriptor: TabToolDescriptor): Button {\n const button = new Button({ glyph: descriptor.glyph });\n\n button.setFlat(true);\n Tooltip.attach(button, descriptor.label);\n button.on(\"action\", descriptor.action);\n this._toolMenuItems.set(button, descriptor);\n\n return button;\n }\n\n /**\n * Removes a previously-added tool button.\n *\n * @param button - The tool component to remove.\n *\n * @returns This tab strip, for method chaining.\n */\n removeTool(button: Component): this {\n const idx = this._tools.indexOf(button);\n\n if (idx < 0) {\n return this;\n }\n\n this._tools.splice(idx, 1);\n this._toolMenuItems.delete(button);\n this._toolGroup.removeComponent(button);\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Sets or clears the caller-supplied widget hosted in the leading slot (the\n * strip start, independent of `_align`). Removes any previous widget from the\n * leading group, then adds the new one as the group's child so the group's\n * stretching box lays it out across the strip thickness — the same treatment a\n * trailing tool gets. Passing `null` clears the slot.\n *\n * @param widget - The widget to host in the leading slot, or `null` to clear it.\n *\n * @returns This tab strip, for method chaining.\n *\n * @remarks\n * The hosting group is transparent and pointer-transparent, so the caller's\n * widget is decorative by default (a press on it falls through to the\n * empty-area window-move trigger). The widget is deliberately outside the\n * {@link setBarSurfaceColor} / `isBarChromeTarget` chrome set.\n */\n setLeadingWidget(widget: Component | null): this {\n if (this._leadWidget) {\n this._leadGroup.removeComponent(this._leadWidget);\n }\n\n this._leadWidget = widget;\n\n if (widget) {\n this._leadGroup.addComponent(widget);\n }\n\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current leading widget, or `null` when none is set.\n *\n * @returns The leading {@link Component}, or `null`.\n */\n getLeadingWidget(): Component | null {\n return this._leadWidget;\n }\n\n /**\n * Returns whether an event target lands on the bar's interactive chrome —\n * a tab wrapper, the tool group, or an overflow scroll-arrow button — as\n * opposed to the draggable blank area. The tab clip is deliberately NOT\n * treated as chrome: it spans the whole tab band and its blank remainder\n * between the last tab and the fixed chrome IS the empty bar area, so\n * vetoing it would swallow every empty-area gesture.\n *\n * @param target - The event target to test.\n *\n * @returns `true` when the target is interactive bar chrome.\n */\n private isBarChromeTarget(target: EventTarget | null): boolean {\n if (!DOM.source.isNode(target)) {\n return false;\n }\n\n const targetHandle = DOM.source.intern(target);\n\n for (const entry of this._entries) {\n const wrapperEl = entry.button.getElement();\n\n if (wrapperEl && DOM.source.contains(wrapperEl, targetHandle)) {\n return true;\n }\n }\n\n const toolGroupEl = this._toolGroup.getElement();\n\n if (toolGroupEl && DOM.source.contains(toolGroupEl, targetHandle)) {\n return true;\n }\n\n if (this._tabClip.containsArrow(target)) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Installs window-chrome gestures on the strip's blank area: a subtree\n * `mousedown` invoking `onEmptyPress` (to start a window move) and, when\n * `onEmptyDoubleClick` is given, a subtree `dblclick` invoking it (to toggle\n * maximize). Both fire only for a press/click on the bar's empty area — not\n * on a tab wrapper, the tool group, or a scroll-arrow button (see\n * `isBarChromeTarget`) — so a host window can drive move and\n * maximize from the bar's blank space. Shift-modified presses are skipped on\n * the move trigger (reserved for re-dock gestures).\n *\n * Idempotent: any previously-installed trigger is torn down first. The\n * teardown lives in `_moveTriggerTeardown` (never `_dndTeardowns`, which\n * is swept on reorder / first-render install) and is drained only by\n * {@link dispose}.\n *\n * @param onEmptyPress - Callback invoked with the originating `mousedown`\n * when an empty bar area is pressed.\n * @param onEmptyDoubleClick - Optional callback invoked with the originating\n * `dblclick` when an empty bar area is double-clicked.\n *\n * @returns This tab strip, for method chaining.\n */\n installMoveTrigger(onEmptyPress: (e: MouseEvent) => void, onEmptyDoubleClick?: (e: MouseEvent) => void): this {\n this._moveTriggerTeardown?.();\n this._moveTriggerTeardown = null;\n\n // Inline-closure handlers (deliberate named-listener deviation, matching\n // the local recordMouseTarget precedent in installTabDnD): they capture\n // the callbacks and read the per-event veto neighbourhood live.\n const onBarMouseDown = (e: MouseEvent): void => {\n // Shift+press is a re-dock gesture, not a window move.\n if (e.shiftKey || this.isBarChromeTarget(e.target)) {\n return;\n }\n\n onEmptyPress(e);\n };\n\n Event.addSubtreeListener(this, \"mousedown\", onBarMouseDown);\n\n const teardowns: Array<() => void> = [\n (): void => Event.removeSubtreeListener(this, \"mousedown\", onBarMouseDown),\n ];\n\n if (onEmptyDoubleClick) {\n const onBarDoubleClick = (e: MouseEvent): void => {\n if (this.isBarChromeTarget(e.target)) {\n return;\n }\n\n onEmptyDoubleClick(e);\n };\n\n Event.addSubtreeListener(this, \"dblclick\", onBarDoubleClick);\n teardowns.push((): void => Event.removeSubtreeListener(this, \"dblclick\", onBarDoubleClick));\n }\n\n this._moveTriggerTeardown = (): void => {\n for (const teardown of teardowns) {\n teardown();\n }\n };\n\n return this;\n }\n\n /**\n * Returns the cell ids in current strip order.\n *\n * @returns A copy of the ordered cell ids.\n */\n getEntryIds(): string[] {\n return this._entries.map(entry => entry.id);\n }\n\n /**\n * Returns the active cell's id, or `null` when the strip is empty.\n *\n * @returns The active cell id, or `null`.\n */\n getActiveEntryId(): string | null {\n return this._activeId;\n }\n\n /**\n * Reports whether the cell with `id` is closeable (its constraints carry\n * `closeable: true`).\n *\n * @param id - The cell id to query.\n *\n * @returns `true` when the cell is closeable; `false` for an unknown id.\n */\n isEntryCloseable(id: string): boolean {\n return this.entryById(id)?.constraints?.closeable === true;\n }\n\n /**\n * Returns the display label of the cell with `id`.\n *\n * @param id - The cell id to query.\n *\n * @returns The cell's label, or `\"\"` for an unknown id.\n */\n getEntryName(id: string): string {\n return this.entryById(id)?.name ?? \"\";\n }\n\n /**\n * Records the live content's component id on the cell with `id`. The id feeds\n * the drag payload (so a foreign strip can resolve the content from the shared\n * registry) and the cell button's ARIA `aria-controls` (so the button is\n * announced as controlling its panel) — both reference the same component id.\n * The owner pushes this when the content materializes.\n *\n * @param id - The cell id whose content became available.\n * @param contentId - The content component's id.\n *\n * @returns This tab strip, for method chaining.\n */\n setEntryContentId(id: string, contentId: string): this {\n const entry = this.entryById(id);\n\n if (entry) {\n entry.contentId = contentId;\n entry.button.getAria().setControls(contentId);\n }\n\n return this;\n }\n\n /**\n * Returns the id of the cell's tab button — the owner reads it to set the\n * content panel's ARIA `aria-labelledby` back to the button.\n *\n * @param id - The cell id to query.\n *\n * @returns The tab button's component id, or `\"\"` for an unknown id.\n */\n getEntryButtonId(id: string): string {\n return this.entryById(id)?.button.getId() ?? \"\";\n }\n\n /**\n * Returns the cell record for `id`, or `null` when no cell carries it.\n *\n * @param id - The cell id to look up.\n *\n * @returns The matching {@link BarEntry}, or `null`.\n */\n private entryById(id: string): BarEntry | null {\n return this._entries.find(entry => entry.id === id) ?? null;\n }\n\n /**\n * Returns the active cell record, or `null` when the strip is empty.\n *\n * @returns The active {@link BarEntry}, or `null`.\n */\n private activeEntry(): BarEntry | null {\n return this._activeId === null ? null : this.entryById(this._activeId);\n }\n\n /**\n * Builds the [`TabButton`](/api/component/button/classes/TabButton) (which\n * owns the per-tab styling and the optional overlaid close button) for one\n * cell, registers it with the\n * button group / roving tab index, and pushes the cell onto the strip.\n * The first cell created becomes the active one (matching\n * the legacy index-0 default); the owner re-selects later via\n * {@link setActiveEntry}.\n *\n * @param id - The owner-minted stable id linking this cell to its content.\n * @param name - The visible label for the tab button.\n * @param constraints - Optional layout constraints; `constraints.closeable` adds a close button.\n *\n * @returns This tab strip, for method chaining.\n */\n createBarEntry(id: string, name: string, constraints?: LayoutConstraints): this {\n // The TabButton owns the per-tab fill/hover/selected styling and, when\n // closeable, the overlaid ✕ — collapsing the styling replay and the Fit\n // wrapper that used to live here. It is itself the box child: it carries\n // its own background (so there is no transparent wrapper to stretch a\n // button across) and hosts the close overlay on its own element.\n let tabButton = new TabButton(name, {\n glyph: constraints?.glyph ?? undefined,\n closeable: constraints?.closeable,\n });\n\n tabButton.setInsets(this.computeTabButtonInsets(constraints));\n\n if (constraints?.tooltip) {\n Tooltip.attach(tabButton, constraints.tooltip);\n }\n\n tabButton.on(\"action\", () => this.onTabPressed(tabButton));\n\n const closeButton = tabButton.getCloseButton() ?? undefined;\n\n // Subtree listener so a right-click on the label, the glyph, or the\n // close ✕ all reach one handler that opens the tab context menu. Named\n // and stored on the entry so `removeBarEntry` can remove it (the closure\n // over `entry`, resolved at call time, is safe because right-clicks only\n // fire after the entry is fully built).\n const onContextMenu = (e: MouseEvent): void => {\n e.preventDefault();\n this.openTabMenu(entry, e.clientX, e.clientY);\n };\n\n const entry: BarEntry = {\n id,\n button: tabButton,\n closeButton,\n name,\n constraints,\n contextMenuListener: onContextMenu,\n contentId: \"\",\n };\n\n if (closeButton) {\n closeButton.on(\"action\", () => this.emit(\"tabclose\", id));\n }\n\n Event.addSubtreeListener(tabButton, \"contextmenu\", onContextMenu);\n\n this._entries.push(entry);\n\n // The first cell becomes active (the legacy index-0 default); thereafter\n // a cell joins inactive and the owner re-selects via setActiveEntry.\n const isSelected = this._activeId === null;\n\n if (isSelected) {\n tabButton.setSelected(true);\n this._activeId = id;\n }\n\n this._buttonGroup.addButton(tabButton);\n this._rovingTabIndex.add(tabButton);\n this._tabClip.addItem(tabButton);\n\n tabButton.getAria().setRole(\"tab\");\n tabButton.getAria().setSelected(isSelected);\n\n // A tab added while reorder is live (and the strip is rendered) needs its\n // own drag source; tabs added before render are wired by installTabDnD.\n if (this._reorderable && this.getElement()) {\n this._dndTeardowns.push(this.makeTabDragSource(entry));\n }\n\n return this;\n }\n\n /**\n * Removes the cell with `id` from the strip — the bar-side teardown (button\n * group, roving tab index, tab button, context-menu listener). Leaves the owner\n * to drop its content record. No-op for an unknown id.\n *\n * @param id - The cell id to remove.\n *\n * @returns This tab strip, for method chaining.\n */\n removeBarEntry(id: string): this {\n const idx = this._entries.findIndex(entry => entry.id === id);\n\n if (idx < 0) {\n return this;\n }\n\n const entry = this._entries[idx];\n\n this._buttonGroup.removeButton(entry.button);\n this._rovingTabIndex.remove(entry.button);\n this._entries.splice(idx, 1);\n this._tabClip.removeItem(entry.button);\n\n // Subtree listeners are keyed by component id in a module-level map;\n // removing the wrapper's element does not purge it, so tear it down or it\n // (and the entry it closes over) leaks.\n Event.removeSubtreeListener(entry.button, \"contextmenu\", entry.contextMenuListener);\n\n // The tooltip attachment is keyed by the button's id in a static map and\n // holds a reference to it; detach so a removed tab does not leak (a no-op\n // when no tooltip was attached).\n Tooltip.detach(entry.button);\n\n if (this._activeId === id) {\n this._activeId = null;\n }\n\n return this;\n }\n\n /**\n * Moves the cell with `id` to `toIndex` (clamped into range), keeping the\n * wrapper order in step. Used by the owner's dock path to land a freshly-added\n * cell at the drop slot.\n *\n * @param id - The cell id to move.\n * @param toIndex - The destination slot.\n *\n * @returns This tab strip, for method chaining.\n */\n moveBarEntry(id: string, toIndex: number): this {\n const from = this._entries.findIndex(entry => entry.id === id);\n\n if (from < 0) {\n return this;\n }\n\n const dest = Math.max(0, Math.min(toIndex, this._entries.length - 1));\n\n if (dest === from) {\n return this;\n }\n\n const entry = this._entries.splice(from, 1)[0];\n\n this._entries.splice(dest, 0, entry);\n this._tabClip.moveItem(entry.button, dest);\n\n return this;\n }\n\n /**\n * Activates the cell with `id` programmatically — the same selection sync a\n * click drives: the button group's pressed state, the roving tab index, the\n * indicator intent, and a `\"tabpressed\"` emit. No-op for an unknown id.\n *\n * @param id - The cell id to activate.\n *\n * @returns This tab strip, for method chaining.\n */\n setActiveEntry(id: string): this {\n const idx = this._entries.findIndex(entry => entry.id === id);\n\n if (idx < 0) {\n return this;\n }\n\n // Mirror the explicit button-group sync a programmatic switch needs: a\n // left-click drives the pressed state through the group, but a\n // programmatic activation must set it by hand or the target's content\n // moves while its button never looks pressed.\n this._entries.forEach(entry => entry.button.setSelected(false));\n this._entries[idx].button.setSelected(true);\n\n this.onTabPressed(this._entries[idx].button);\n\n return this;\n }\n\n /**\n * Re-selects the cell with `id` **visually only** — the button pressed state\n * and the active id, with no roving-focus move and no `\"tabpressed\"` emit.\n * Used by the owner to reinstate selection after a close, where the content\n * swap is the owner's own concern and re-firing selection would be wrong.\n *\n * @param id - The cell id to mark active.\n *\n * @returns This tab strip, for method chaining.\n */\n setActiveVisual(id: string): this {\n const idx = this._entries.findIndex(entry => entry.id === id);\n\n if (idx < 0) {\n return this;\n }\n\n this._entries.forEach(entry => entry.button.setSelected(false));\n this._entries[idx].button.setSelected(true);\n this._activeId = id;\n\n return this;\n }\n\n /**\n * Opens the shared context menu for a right-clicked tab. The layout is a\n * `Switch to` submenu (every tab, the active one inert) · the single `Close`\n * for the clicked tab · the four bulk closes (`Close others`, then the\n * before/after pair — `Close all to the left` / `... to the right` on a\n * horizontal strip, `Close all above` / `... below` on a vertical one — then\n * `Close all`) · and, only when a tool supplied a {@link TabToolDescriptor}, a\n * trailing `Tools` submenu. Every close reuses the `\"tabclose\"` emit and\n * switching reuses {@link setActiveEntry}, so no activation or close logic is\n * duplicated.\n *\n * @param entry - The cell that was right-clicked.\n * @param x - Horizontal viewport coordinate of the click.\n * @param y - Vertical viewport coordinate of the click.\n */\n private openTabMenu(entry: BarEntry, x: number, y: number): void {\n const activeEntry = this.activeEntry();\n const ids = this.getEntryIds();\n const clickedIdx = ids.indexOf(entry.id);\n const closeable = (id: string): boolean => this.isEntryCloseable(id);\n\n // The per-tab switch items move into their own submenu; disable only the\n // tab that's already showing (switching to it is a no-op).\n const switchItems: MenuItemConfig[] = this._entries.map(t => ({\n text: t.name,\n enabled: t !== activeEntry,\n action: () => this.setActiveEntry(t.id),\n }));\n\n // On a vertical strip (west/east) the tabs run top-to-bottom, so the\n // \"before\"/\"after\" scopes read as above/below rather than left/right.\n const vertical = this._side === \"west\" || this._side === \"east\";\n const beforeLabel = vertical ? \"Close all above\" : \"Close all to the left\";\n const afterLabel = vertical ? \"Close all below\" : \"Close all to the right\";\n\n const configs: MenuItemConfig[] = [\n { text: \"Switch to\", submenu: { label: \"Switch to\", items: switchItems } },\n { separator: true },\n {\n text: \"Close\",\n enabled: entry.constraints?.closeable === true,\n action: () => this.emit(\"tabclose\", entry.id),\n },\n this.bulkCloseItem(\"Close others\", ids, clickedIdx, closeable, \"others\"),\n this.bulkCloseItem(beforeLabel, ids, clickedIdx, closeable, \"left\"),\n this.bulkCloseItem(afterLabel, ids, clickedIdx, closeable, \"right\"),\n this.bulkCloseItem(\"Close all\", ids, clickedIdx, closeable, \"all\"),\n ];\n\n // Trailing `Tools` submenu, only when at least one tool opted in with a\n // descriptor. Iterate `_tools` so the rows keep strip order.\n const toolItems: MenuItemConfig[] = this._tools\n .filter(tool => this._toolMenuItems.has(tool))\n .map(tool => {\n const descriptor = this._toolMenuItems.get(tool)!;\n\n return { text: descriptor.label, glyph: descriptor.glyph, action: descriptor.action };\n });\n\n if (toolItems.length > 0) {\n configs.push({ separator: true });\n configs.push({ text: \"Tools\", submenu: { label: \"Tools\", items: toolItems } });\n }\n\n this._contextMenu.show(x, y, configs);\n }\n\n /**\n * Builds one bulk-close menu row: computes its closeable target ids up front\n * (a stable snapshot, so the action closure never re-reads the live `_entries`\n * that each `\"tabclose\"` emit mutates) and disables the row when that set is\n * empty.\n *\n * @param text - The row label.\n * @param ids - The tab ids in strip order.\n * @param clickedIdx - The right-clicked tab's index within `ids`.\n * @param closeable - Predicate reporting whether a tab id is closeable.\n * @param scope - Which tabs, relative to the clicked one, the row closes.\n *\n * @returns The menu-item config for the bulk-close row.\n */\n private bulkCloseItem(\n text: string,\n ids: readonly string[],\n clickedIdx: number,\n closeable: (id: string) => boolean,\n scope: BulkCloseScope,\n ): MenuItemConfig {\n const targets = computeBulkCloseIds(ids, clickedIdx, closeable, scope);\n\n return {\n text,\n enabled: targets.length > 0,\n action: () => {\n for (const id of targets) {\n this.emit(\"tabclose\", id);\n }\n },\n };\n }\n\n /**\n * Updates the active cell, syncs the roving tab index and scroll-into-view\n * intent, and emits `\"tabpressed\"` when a tab button is activated. The owner's\n * `\"tabpressed\"` handler performs the content swap and re-lays out.\n *\n * @param button - The tab button component that was pressed.\n */\n private onTabPressed(button: Component): void {\n const idx = this._entries.findIndex(entry => entry.button === button);\n\n if (idx >= 0) {\n this._activeId = this._entries[idx].id;\n this._rovingTabIndex.moveTo(idx);\n\n // Bring the newly-selected tab into view on the next pass. A\n // left-click targets an already-visible tab (a no-op reveal), but a\n // programmatic switch — the context menu or keyboard arrow nav — can\n // select a tab scrolled out of the strip's visible range.\n if (this._scrollable) {\n this._scrollToSelected = true;\n }\n\n this.emit(\"tabpressed\", this._entries[idx].id);\n }\n }\n\n /**\n * Derives a tab button's insets from the current `_compact` flag and the\n * active theme's resolved scale. The label gets two `pad` units of breathing\n * room per side (`pad` is the resolved `scale.tabButtonInset`, halved when\n * compact); a **closeable** tab additionally reserves the resolved\n * `scale.tabClose` close-button box on the edge where\n * {@link positionCloseButtons} pins the ✕ — the right for upright text\n * (north/south and west/east horizontal), the bottom or top for rotated\n * cw / ccw text. A non-closeable tab reserves nothing there, so its label\n * justification (see {@link setTextAlign}) has the whole box to work with;\n * a closeable tab justifies its label within the rect left beside the close\n * button's fixed slot. Both the reservation and the pad scale with the base,\n * while only the pad additionally shrinks in the dense strip, so the glyph\n * keeps its clearance.\n *\n * @param constraints - The tab's layout constraints; `constraints.closeable`\n * adds the close-button reservation.\n *\n * @returns The insets to apply to the tab button.\n */\n private computeTabButtonInsets(constraints?: LayoutConstraints): Insets {\n const scale = ThemeManager.getResolvedScale();\n const pad = this._compact ? Math.round(scale.tabButtonInset / 2) : scale.tabButtonInset;\n // Reserve the close-button gutter only on a tab that actually has a close\n // button. A non-closeable tab keeps its whole box so label justification\n // uses the full width; a closeable tab justifies its label within the\n // rect left beside the close button's fixed slot.\n const closeReserve = constraints?.closeable ? scale.tabClose : 0;\n\n if (this.isRotatedText()) {\n // Rotated label runs along the cell, ending where it stops reading:\n // the bottom for clockwise (top-to-bottom) text, the top for\n // counter-clockwise (bottom-to-top). Reserve the ✕ clearance there.\n return this._orientation === \"vertical-ccw\"\n ? new Insets(closeReserve + pad * 2, pad * 2, pad * 2, pad * 2)\n : new Insets(pad * 2, pad * 2, closeReserve + pad * 2, pad * 2);\n }\n\n if (this.isVertical()) {\n // West/east upright text: ✕ to the right of the label, with vertical\n // breathing so the stacked tabs aren't cramped.\n return new Insets(pad * 2, closeReserve + pad * 2, pad * 2, pad * 2);\n }\n\n // North/south: ✕ to the right; the strip thickness supplies the cross-\n // axis (vertical) room, so no top/bottom inset.\n return new Insets(0, closeReserve + pad * 2, 0, pad * 2);\n }\n\n /**\n * Derives a tool button's insets from the current `_compact` flag — the same\n * breathing pad as the tab buttons (no close reservation), so tools shrink in\n * lockstep when the strip goes compact. The cross-axis inset is zeroed (the\n * strip thickness supplies that dimension and the stretching tool group fills\n * the button to it), so only the main-axis pad tightens in compact mode.\n *\n * @returns The insets to apply to each tool button.\n */\n private computeToolButtonInsets(): Insets {\n const inset = ThemeManager.getResolvedScale().tabButtonInset;\n const pad = this._compact ? Math.round(inset / 2) : inset;\n\n if (this.isVertical()) {\n return new Insets(pad * 2, 0, pad * 2, 0);\n }\n\n return new Insets(0, pad * 2, 0, pad * 2);\n }\n\n /**\n * Clamps a wrapper's extent on the strip's *main* axis (width for north/south,\n * height for west/east) to `[min, max]`, leaving the cross axis unbounded so\n * the box stretches it to the strip thickness.\n *\n * @param wrapper - The tab wrapper to clamp.\n * @param min - The main-axis minimum in px.\n * @param max - The main-axis maximum in px (`Number.MAX_VALUE` for unbounded).\n */\n private clampWrapperMain(wrapper: Component, min: number, max: number): void {\n if (this.isVertical()) {\n wrapper.setMinSize({ width: 0, height: min });\n wrapper.setMaxSize({ width: Number.MAX_VALUE, height: max });\n } else {\n wrapper.setMinSize({ width: min, height: 0 });\n wrapper.setMaxSize({ width: max, height: Number.MAX_VALUE });\n }\n }\n\n /**\n * Reports whether the strip paints rotated text — a west/east side with a\n * `vertical-cw` / `vertical-ccw` orientation.\n *\n * @returns `true` for west/east with a vertical text orientation.\n */\n private isRotatedText(): boolean {\n return this.isVertical() && this._orientation !== \"horizontal\";\n }\n\n /**\n * Reads a tab button's preferred extent on the strip's **main** axis (width\n * for north/south, height for west/east).\n *\n * @param button - The tab button to measure.\n *\n * @returns The main-axis extent in px, or 0 before the button has reported a\n * preferred size.\n */\n private buttonMainExtent(button: ToggleButton): number {\n const preferred = button.getPreferredSize();\n\n if (!preferred) {\n return 0;\n }\n\n return this.isVertical() ? preferred.height : preferred.width;\n }\n\n /**\n * Reads a tab button's preferred extent on the strip's **cross** axis (the\n * strip's thickness contribution) — the complement of {@link buttonMainExtent}.\n *\n * @param button - The tab button to measure.\n *\n * @returns The cross-axis extent in px, or 0 before measurement.\n */\n private buttonCrossExtent(button: ToggleButton): number {\n const preferred = button.getPreferredSize();\n\n if (!preferred) {\n return 0;\n }\n\n return this.isVertical() ? preferred.width : preferred.height;\n }\n\n /**\n * Computes the strip's thickness (px) on its cross axis, derived from the\n * *measured* tab buttons so the strip grows with the label's font.\n * `STRIP_THICKNESS` / `STRIP_THICKNESS_COMPACT` act as the floor used before\n * any tab has reported a preferred size (an empty or pre-first-layout strip),\n * never as the value itself.\n *\n * North/south grow the floor to the tallest button's content height plus\n * `stripChrome` (the vertical breathing the strip supplies, since the\n * north/south tab button carries no top/bottom inset — see\n * `computeTabButtonInsets`). West/east grow the floor to the widest\n * button (or tool) cross extent, which already bakes its breathing into the\n * button's left/right insets, so no chrome is added there. In `\"fixed\"` width\n * mode with *upright* text the vertical strip's thickness is instead pinned to\n * `fixedWidth`. Rotated text reads along the main axis, so fixed sizes its\n * height and the thickness stays content-derived.\n *\n * @returns The strip thickness in px.\n */\n stripThickness(): number {\n const base = this._compact ? STRIP_THICKNESS_COMPACT : STRIP_THICKNESS;\n\n if (!this.isVertical()) {\n let maxCross = base;\n\n for (const entry of this._entries) {\n maxCross = Math.max(maxCross, this.buttonCrossExtent(entry.button) + this.stripChrome());\n }\n\n return maxCross;\n }\n\n if (this._widthMode === \"fixed\" && !this.isRotatedText()) {\n return Math.max(base, this._fixedWidth);\n }\n\n let maxCross = base;\n\n for (const entry of this._entries) {\n maxCross = Math.max(maxCross, this.buttonCrossExtent(entry.button));\n }\n\n const toolSize = this._tools.length > 0 ? this._toolGroup.getPreferredSize() : null;\n\n if (toolSize) {\n maxCross = Math.max(maxCross, toolSize.width);\n }\n\n return maxCross;\n }\n\n /**\n * The vertical breathing (px) the north/south strip adds around a tab\n * button's content box. The north/south tab button carries *zero* top/bottom\n * inset (see `computeTabButtonInsets`: the strip supplies the cross-axis\n * room), so the band that keeps the label off the strip edges must come from\n * the strip itself. It mirrors the west/east model, where the same `pad * 2`\n * per cross-side lives inside the button's insets instead — so a north/south\n * strip and a west/east strip give a label the same clearance, and the chrome\n * scales with the base and shrinks in lockstep with `compact` exactly as the\n * insets do.\n *\n * @returns The combined top+bottom chrome in px (`pad * 2` per side).\n */\n private stripChrome(): number {\n const inset = ThemeManager.getResolvedScale().tabButtonInset;\n const pad = this._compact ? Math.round(inset / 2) : inset;\n\n return pad * 2 * 2;\n }\n\n /**\n * Resolves a tab's fixed target extent on the strip's main axis for the active\n * width mode, or `0` when the mode imposes none (`\"fill\"`, or before the\n * buttons have reported a preferred size). `\"equal\"` and `\"fixed\"` return one\n * uniform value across all tabs; `\"content\"` returns the per-tab natural extent\n * capped at `maxWidth`.\n *\n * @param button - The tab button to measure (used only by `\"content\"`).\n *\n * @returns The target main-axis extent in px, or `0` for no fixed target.\n */\n private tabModeExtent(button: ToggleButton): number {\n switch (this._widthMode) {\n case \"fixed\":\n // Fixed sizes the extent in the text's reading direction. For\n // upright text on west/east that direction is the bar *thickness*\n // (handled in `stripThickness`), so the main axis stays content-\n // sized (return 0). North/south and rotated (vertical) text read\n // along the main axis, so they pin the main extent.\n return (this.isVertical() && !this.isRotatedText()) ? 0 : this._fixedWidth;\n case \"equal\": {\n let widest = 0;\n\n for (const entry of this._entries) {\n widest = Math.max(widest, this.buttonMainExtent(entry.button));\n }\n\n return Math.min(widest, this._maxWidth ?? Number.MAX_VALUE);\n }\n case \"content\":\n return Math.min(this.buttonMainExtent(button), this._maxWidth ?? Number.MAX_VALUE);\n default:\n return 0;\n }\n }\n\n /**\n * Applies the active width mode to the clip frame box and every tab wrapper,\n * generalised to the strip's main axis (width for north/south, height for\n * west/east). Called from `layoutChrome` before the box lays out.\n *\n * @remarks When `scrollable` is set the `\"equal\"`→`\"fill\"` collapse is skipped:\n * the box is switched to `\"preferred\"` and marked overflowing on the main axis\n * so buttons keep their preferred extent and the strip scrolls instead of\n * compressing.\n *\n * @param available - The strip's inner main-axis extent (px) the tabs must fit\n * within (already net of the tool-group reservation).\n */\n private applyTabWidths(available: number): void {\n const box = this._tabClip.getContentBox() as BoxLayout;\n const overflow = this._scrollable;\n\n // Scroll-on-overflow: keep tabs at their width-mode extent and let the\n // strip's own overflow carry the surplus, rather than compressing to fit.\n if (overflow) {\n box.setMode(\"preferred\");\n box.setOverflowing(!this.isVertical(), this.isVertical());\n\n for (const entry of this._entries) {\n const extent = this.tabModeExtent(entry.button);\n\n if (extent > 0) {\n this.clampWrapperMain(entry.button, extent, extent);\n } else {\n // \"fill\" / pre-measurement: keep each tab's own preferred\n // extent. Rotated text floors to the derived main extent (the\n // box would otherwise read the un-rotated size and clip).\n const floor = this.isRotatedText() ? this.buttonMainExtent(entry.button) : 0;\n this.clampWrapperMain(entry.button, floor, Number.MAX_VALUE);\n }\n }\n\n return;\n }\n\n box.setOverflowing(false, false);\n\n if (this._widthMode === \"fill\") {\n box.setMode(\"equal\");\n\n for (const entry of this._entries) {\n this.clampWrapperMain(entry.button, 0, Number.MAX_VALUE);\n }\n\n return;\n }\n\n box.setMode(\"preferred\");\n\n if (this._widthMode === \"content\") {\n const cap = this._maxWidth ?? Number.MAX_VALUE;\n\n for (const entry of this._entries) {\n // Rotated text: pin each wrapper to its derived natural main\n // extent (capped), since the box can't read the rotated extent\n // from the analytic preferred size. Upright text lets the box use\n // the wrapper's preferred main extent, capped at `cap`.\n if (this.isRotatedText()) {\n const extent = Math.min(this.buttonMainExtent(entry.button), cap);\n this.clampWrapperMain(entry.button, extent, extent);\n } else {\n this.clampWrapperMain(entry.button, 0, cap);\n }\n }\n\n return;\n }\n\n // \"equal\" / \"fixed\": pin every wrapper to a single uniform extent.\n const extent = this._entries.length > 0 ? this.tabModeExtent(this._entries[0].button) : 0;\n\n // Pre-measurement guard: fall back to natural extents until the tab\n // buttons have reported a real preferred size.\n if (extent <= 0) {\n for (const entry of this._entries) {\n this.clampWrapperMain(entry.button, 0, Number.MAX_VALUE);\n }\n\n return;\n }\n\n // \"equal\" shrinks to fit: when the uniform extent can't fit the strip,\n // collapse to fill so the tabs share the available space instead of\n // overflowing. \"fixed\" stays rigid (overflow is the consumer's intent).\n if (this._widthMode === \"equal\" && this._entries.length > 0 && extent * this._entries.length > available) {\n box.setMode(\"equal\");\n\n for (const entry of this._entries) {\n this.clampWrapperMain(entry.button, 0, Number.MAX_VALUE);\n }\n\n return;\n }\n\n for (const entry of this._entries) {\n this.clampWrapperMain(entry.button, extent, extent);\n }\n }\n\n /**\n * Reports whether the strip is on a vertical side (west/east), where the clip\n * frame stacks tabs in a `VBox` and the main axis is Y.\n *\n * @returns `true` for west/east, `false` for north/south.\n */\n private isVertical(): boolean {\n return this._side === \"west\" || this._side === \"east\";\n }\n\n /**\n * Swaps the clip frame's inner box (and the tool group's) between `HBox` and\n * `VBox` to match the current side, only when the orientation actually\n * differs. North/south stack tabs in an `HBox`; west/east in a `VBox`.\n */\n private syncToolbarOrientation(): void {\n const wantVertical = this.isVertical();\n\n this._tabClip.setOrientation(wantVertical ? \"vertical\" : \"horizontal\");\n\n if ((this._toolGroup.getLayoutManager() instanceof VBox) !== wantVertical) {\n this._toolGroup.setLayoutManager(wantVertical\n ? new VBox({ spacing: 0, stretching: true })\n : new HBox({ spacing: 0, stretching: true }));\n }\n\n if ((this._leadGroup.getLayoutManager() instanceof VBox) !== wantVertical) {\n this._leadGroup.setLayoutManager(wantVertical\n ? new VBox({ spacing: 0, stretching: true })\n : new HBox({ spacing: 0, stretching: true }));\n }\n }\n\n /**\n * Reads the tool group's preferred extent along the strip's main axis (width\n * for north/south, height for west/east).\n *\n * @returns The tool-group main extent in px, or 0 when there are no tools.\n */\n private toolGroupMainExtent(): number {\n const pref = this._toolGroup.getPreferredSize();\n\n if (!pref) {\n return 0;\n }\n\n return this.isVertical() ? pref.height : pref.width;\n }\n\n /**\n * Reads the leading group's reserved main-axis extent — its preferred\n * main-axis size plus the {@link LEAD_GLYPH_GAP} before the first tab. The\n * group's preferred size reflects the hosted widget stretched and glyph-synced\n * like a trailing tool, so the slot is a true main-axis peer of the tool group.\n *\n * @returns The reserved lead extent in px, or 0 when no leading widget is set.\n */\n private leadWidgetMainExtent(): number {\n if (!this._leadWidget) {\n return 0;\n }\n\n const pref = this._leadGroup.getPreferredSize();\n\n if (!pref) {\n return 0;\n }\n\n return (this.isVertical() ? pref.height : pref.width) + LEAD_GLYPH_GAP;\n }\n\n /**\n * Predicts the strip's combined main-axis tab extent before layout, used to\n * decide whether the \"arrows\" overflow chrome (and its gutter reservation) is\n * needed.\n *\n * @returns The predicted combined main-axis extent of all tabs in px.\n */\n private predictTabsExtent(): number {\n let total = 0;\n\n for (const entry of this._entries) {\n total += this.predictedTabExtent(entry.button);\n }\n\n return total;\n }\n\n /**\n * Predicts one tab's main-axis extent before layout — its width-mode extent\n * when the mode imposes one, else its own preferred extent.\n *\n * @param button - The tab button to measure.\n *\n * @returns The predicted main-axis extent in px.\n */\n private predictedTabExtent(button: ToggleButton): number {\n const extent = this.tabModeExtent(button);\n\n return extent > 0 ? extent : this.buttonMainExtent(button);\n }\n\n /**\n * Positions and sizes the strip's clip-frame band to span the whole tab band —\n * the strip region net of the tool group and the leading glyph slot, *including*\n * the scroll-arrow gutters at each end. The strip then sizes its own inner clip\n * to the band minus the gutters and places its arrows into them (see\n * {@link ScrollStrip.layoutContent}), so this only resolves the band rectangle.\n *\n * @param toolExtent - The tool group's main-axis extent in px.\n * @param leadExtent - The leading glyph's reserved main-axis extent in px (0 when no leading glyph).\n * @param thickness - The strip's cross-axis thickness in px.\n * @param mainInner - The strip's main-axis inner extent in px.\n * @param crossLead - The bar's leading cross-axis inset (0 unless the bar absorbed a parent inset).\n * @param mainLead - The bar's leading main-axis inset (0 unless the bar absorbed a parent inset).\n */\n private positionClipFrame(toolExtent: number, leadExtent: number, thickness: number, mainInner: number, crossLead: number, mainLead: number): void {\n const toolsLead = this._align === \"end\";\n const leadChrome = leadExtent + (toolsLead ? toolExtent : 0);\n const trailChrome = toolsLead ? 0 : toolExtent;\n const mainSize = mainInner - leadChrome - trailChrome;\n\n if (this.isVertical()) {\n this._tabClip.setX(crossLead);\n this._tabClip.setY(leadChrome + mainLead);\n this._tabClip.setWidth(thickness);\n this._tabClip.setHeight(mainSize);\n } else {\n this._tabClip.setX(leadChrome + mainLead);\n this._tabClip.setY(crossLead);\n this._tabClip.setWidth(mainSize);\n this._tabClip.setHeight(thickness);\n }\n }\n\n /**\n * Re-derives every tab button's insets from `_compact` and applies the\n * `writing-mode` for the current orientation (cleared on horizontal sides),\n * and tightens the tool buttons' insets — and the leading widget's, a\n * tool-peer at the opposite end — to match, so `setCompact` /\n * `setOrientation` take effect on the next pass without the setters touching\n * the DOM. Run before the width pass so the insets feed the buttons' measured\n * extents.\n */\n private applyTabButtonStyles(): void {\n // `sideways-rl`/`sideways-lr` rotate the whole run a quarter turn in\n // opposite directions — clockwise (reads top-to-bottom) and\n // counter-clockwise (reads bottom-to-top).\n const writingMode = this._orientation === \"vertical-cw\" ? \"sideways-rl\"\n : this._orientation === \"vertical-ccw\" ? \"sideways-lr\"\n : null;\n\n for (const entry of this._entries) {\n entry.button.setInsets(this.computeTabButtonInsets(entry.constraints));\n\n // Writing mode before text-align: the label justification maps to a\n // content anchor along the reading axis, so the button must already\n // know its orientation when setTextAlign resolves the anchor.\n if (this.isVertical() && writingMode) {\n entry.button.setWritingMode(writingMode);\n } else {\n entry.button.clearWritingMode();\n }\n\n entry.button.setTextAlign(this._textAlign);\n }\n\n const toolInsets = this.computeToolButtonInsets();\n\n for (const tool of this._tools) {\n tool.setInsets(toolInsets);\n }\n\n // The leading widget is a tool-peer at the opposite end, so it shares the\n // tool inset policy (cross-axis zeroed, main-axis pad) — without it the\n // widget keeps its own constructed insets and lays out a few px off the\n // trailing controls' box, breaking the symmetric edge inset.\n if (this._leadWidget) {\n this._leadWidget.setInsets(toolInsets);\n }\n }\n\n /**\n * Computes the leading gap (px) that pushes `\"end\"`-aligned tabs to the\n * trailing edge of their region. Folded into the clip frame's leading inset\n * (see {@link positionClipFrame}) so the inner box lays the tabs out at the\n * trailing edge natively. Zero for `\"start\"` alignment, `\"fill\"` width mode,\n * and whenever the tabs fill or overflow the region.\n *\n * @param available - The strip's inner main-axis extent net of tool and arrow\n * reservations (px) — the region the tabs occupy.\n *\n * @returns The leading gap in px, clamped to `0`.\n */\n private endAlignGap(available: number): number {\n if (this._align !== \"end\" || this._widthMode === \"fill\" || this._entries.length === 0) {\n return 0;\n }\n\n return Math.max(0, available - this.predictTabsExtent());\n }\n\n /**\n * Places the tool-group overlay in its reserved slot (the strip end opposite\n * the tabs) and lays out its buttons, or hides it when there are no tools.\n *\n * @param mainInner - The strip's main-axis inner extent in px.\n * @param toolExtent - The tool-group main extent in px.\n * @param thickness - The strip's cross-axis thickness in px.\n * @param crossLead - The bar's leading cross-axis inset (0 unless the bar absorbed a parent inset).\n * @param mainLead - The bar's leading main-axis inset (0 unless the bar absorbed a parent inset).\n */\n private positionToolGroup(mainInner: number, toolExtent: number, thickness: number, crossLead: number, mainLead: number): void {\n if (this._tools.length === 0 || toolExtent <= 0) {\n this._toolGroup.setVisible(false);\n\n return;\n }\n\n this._toolGroup.setVisible(true);\n\n const mainPos = (this._align === \"end\" ? 0 : mainInner - toolExtent) + mainLead;\n\n if (this.isVertical()) {\n this._toolGroup.setX(crossLead);\n this._toolGroup.setY(mainPos);\n this._toolGroup.setWidth(thickness);\n this._toolGroup.setHeight(toolExtent);\n } else {\n this._toolGroup.setX(mainPos);\n this._toolGroup.setY(crossLead);\n this._toolGroup.setWidth(toolExtent);\n this._toolGroup.setHeight(thickness);\n }\n\n this._toolGroup.doLayout();\n }\n\n /**\n * Places the leading group in its reserved slot at the strip's start (main\n * origin `mainLead`), sized to the strip thickness on the cross axis so it\n * mirrors the tool group at the opposite end, then lays out the hosted widget.\n * The group's stretching box stretches the widget across the thickness (so its\n * glyph syncs to the strip line-height like a trailing tool) and the widget\n * centres within that box, so its ink v-centres in the thickness and h-centres\n * in a box the same size as a control's — a true peer. Hides the group when no\n * leading widget is set.\n *\n * @param thickness - The strip's cross-axis thickness in px.\n * @param crossLead - The bar's leading cross-axis inset (0 unless the bar absorbed a parent inset).\n * @param mainLead - The bar's leading main-axis inset (0 unless the bar absorbed a parent inset).\n */\n private positionLeadGroup(thickness: number, crossLead: number, mainLead: number): void {\n // The group's preferred main size — its own box extent, excluding the\n // trailing LEAD_GLYPH_GAP that leadWidgetMainExtent folds in for the tabs.\n const pref = this._leadWidget ? this._leadGroup.getPreferredSize() : null;\n const mainExtent = pref ? (this.isVertical() ? pref.height : pref.width) : 0;\n\n if (mainExtent <= 0) {\n this._leadGroup.setVisible(false);\n\n return;\n }\n\n this._leadGroup.setVisible(true);\n\n if (this.isVertical()) {\n this._leadGroup.setX(crossLead);\n this._leadGroup.setY(mainLead);\n this._leadGroup.setWidth(thickness);\n this._leadGroup.setHeight(mainExtent);\n } else {\n this._leadGroup.setX(mainLead);\n this._leadGroup.setY(crossLead);\n this._leadGroup.setWidth(mainExtent);\n this._leadGroup.setHeight(thickness);\n }\n\n this._leadGroup.doLayout();\n }\n\n /**\n * Slides the selection indicator over the active tab cell along the strip's\n * main axis, pinned to the strip's inner edge per side.\n */\n private positionIndicator(): void {\n const wrapper = this.activeEntry()?.button;\n\n if (!wrapper) {\n return;\n }\n\n const vertical = this.isVertical();\n const mainExtent = vertical ? wrapper.getHeight() : wrapper.getWidth();\n\n if (mainExtent <= 0) {\n return;\n }\n\n const mainPos = vertical ? wrapper.getY() : wrapper.getX();\n\n this._indicator.slideTo(mainPos, mainExtent, this._side);\n }\n\n /**\n * Pins each closeable tab's overlaid ✕ to the end of its label's reading flow:\n * the right edge (vertically centred) for upright text — north/south and\n * west/east horizontal orientation — and, for rotated text (horizontally\n * centred), the bottom edge for clockwise or the top edge for counter-clockwise.\n */\n private positionCloseButtons(): void {\n const rotated = this.isRotatedText();\n\n // Read the resolved close-button box and ✕ ink, then size and re-pin every\n // button — re-pinning here is what lets an existing ✕ follow a base-size\n // change, since the entry-creation pin only sets the initial size. Both\n // setters no-op when the value is unchanged, so a same-scale pass costs\n // nothing.\n const scale = ThemeManager.getResolvedScale();\n const closeSize = scale.tabClose;\n const glyphSize = scale.tabCloseGlyph;\n\n for (const entry of this._entries) {\n const closeButton = entry.closeButton;\n\n if (!closeButton || entry.button.getWidth() <= 0) {\n continue;\n }\n\n closeButton.setWidth(closeSize);\n closeButton.setHeight(closeSize);\n closeButton.pinGlyphSize(glyphSize);\n\n if (rotated) {\n // Centre the ✕ across the strip thickness and pin it to the end\n // of the reading flow: the bottom for clockwise (top-to-bottom)\n // text, the top for counter-clockwise (bottom-to-top).\n closeButton.setX(Math.round((entry.button.getWidth() - closeSize) / 2));\n closeButton.setY(this._orientation === \"vertical-ccw\"\n ? 2\n : entry.button.getHeight() - closeSize - 2);\n } else {\n closeButton.setX(entry.button.getWidth() - closeSize - 2);\n closeButton.setY(Math.round((entry.button.getHeight() - closeSize) / 2));\n }\n }\n }\n\n /**\n * Pixels to scroll per overflow-arrow click ≈ one tab, so a click pages by a\n * tab at any font size. Derived from the first tab's predicted main-axis\n * extent (which collapses width-mode sizing and raw content width into the\n * laid-out width); falls back to the {@link SCROLL_ARROW_STEP} floor before\n * any tab has measured (`predictedTabExtent` returns 0, which `|| ` floors).\n *\n * @returns The per-click scroll step in px.\n */\n private scrollStepExtent(): number {\n return this._entries.length > 0\n ? this.predictedTabExtent(this._entries[0].button) || SCROLL_ARROW_STEP\n : SCROLL_ARROW_STEP;\n }\n\n /**\n * When a scroll-into-view was requested (enabling scrolling, a side switch, or\n * a `compact` toggle), asks the clip frame to bring the selected tab fully into\n * view from the *laid-out* DOM rects. Runs after the clip frame's layout;\n * one-shot, so it never fights the user's own scrolling.\n */\n private revealSelectedIfRequested(): void {\n if (!this._scrollToSelected) {\n return;\n }\n\n this._scrollToSelected = false;\n\n if (!this._scrollable) {\n return;\n }\n\n const wrapperElement = this.activeEntry()?.button.getElement();\n\n if (wrapperElement) {\n this._tabClip.revealItem(wrapperElement);\n }\n }\n\n /**\n * Prepares the strip for the owner's measurement pass: swaps the clip-frame /\n * tool-group box orientation to the current side, re-derives the tab/tool\n * button styles (insets, writing-mode, text-align), and syncs each button's\n * ARIA `selected` state. Must be called **before** {@link stripThickness} and\n * {@link placeStrip} each pass, because the button styles feed the measured\n * extents that the thickness reads.\n *\n * @returns This tab strip, for method chaining.\n */\n prepareStrip(): this {\n this.syncToolbarOrientation();\n this.applyTabButtonStyles();\n\n for (const entry of this._entries) {\n entry.button.getAria().setSelected(entry.id === this._activeId);\n }\n\n return this;\n }\n\n /**\n * Positions the strip at the rectangle the owner computed and lays out its\n * internal chrome (clip frame, tool group, indicator, close overlays, scroll\n * arrows) within it. The owner derives the rectangle from\n * {@link stripThickness} (call {@link prepareStrip} first).\n *\n * @param x - The strip's left in container coordinates.\n * @param y - The strip's top in container coordinates.\n * @param width - The strip's width in px.\n * @param height - The strip's height in px.\n *\n * @returns This tab strip, for method chaining.\n */\n placeStrip(x: number, y: number, width: number, height: number): this {\n this.setX(x);\n this.setY(y);\n this.setWidth(width);\n this.setHeight(height);\n\n this.layoutChrome(width, height);\n\n return this;\n }\n\n /**\n * Lays out the strip's internal chrome within the given box: sizes the clip\n * frame band to the tab region (between the tool group and the leading slot),\n * applies the width mode, then has the strip lay its inner clip and overflow\n * arrows within the band, reveals the selected tab, and positions the tool\n * group, indicator, and close overlays.\n *\n * @param width - The strip's box width in px (== outer width).\n * @param height - The strip's box height in px.\n */\n private layoutChrome(width: number, height: number): void {\n // The bar may carry per-side insets (the parent inset it absorbed when\n // `barIgnoreParentInsets` grew it to the container's outer edge). Derive\n // the leading offsets from the bar's own content frame so the\n // hand-positioned chrome lands within it; with zero insets (the default)\n // every offset is 0 and this reduces to the original placement.\n const ci = this.getContentInsets();\n const vertical = this.isVertical();\n const crossLead = vertical ? ci.getLeft() : ci.getTop();\n const mainLead = vertical ? ci.getTop() : ci.getLeft();\n const mainTrail = vertical ? ci.getBottom() : ci.getRight();\n\n const toolExtent = this._tools.length > 0 ? this.toolGroupMainExtent() : 0;\n const thickness = this.stripThickness();\n // Always reserved at the leading (start) edge, independent of `_align`; 0\n // when no leading widget is set, so every `+ leadExtent` reduces to `+ 0`\n // and the default path is byte-for-byte unchanged.\n const leadExtent = this.leadWidgetMainExtent();\n const mainOuter = vertical ? height : width;\n const mainInner = mainOuter - mainLead - mainTrail;\n\n // Place the clip-frame band between the tool slot and the leading glyph\n // slot. A scrollable strip that overflows reserves a scroll-arrow gutter at\n // each end (held by the strip inside the band); an \"end\"-align leading gap\n // trailing-aligns the tabs. The strip lays its inner clip + arrows within\n // the band so the tabs scroll, clipped, between the fixed arrows.\n const arrowReserve = this._tabClip.arrowReserve(this.predictTabsExtent(), mainInner - toolExtent - leadExtent);\n const available = mainInner - toolExtent - leadExtent - 2 * arrowReserve;\n const endGap = this.endAlignGap(available);\n this.positionClipFrame(toolExtent, leadExtent, thickness, mainInner, crossLead, mainLead);\n\n // Set the tab width mode/clamps before the strip runs its inner box, then\n // let the strip size its clip to the band minus the gutters, lay out the\n // tabs, place the arrows, and resync its cached scroll offset. The resync\n // matters because `applyTabWidths` can lay the content out narrower than the\n // current offset (e.g. compact while scrolled to the end), so the browser\n // clamps the native scroll on its own and `revealSelectedIfRequested` would\n // otherwise add its live-rect delta to a stale base and under-scroll.\n this.applyTabWidths(available);\n this._tabClip.layoutContent(arrowReserve, endGap);\n\n // Scroll-into-view moves the clip's native scroll against the now-laid-out\n // wrapper rects — a prediction before the box runs can't see a same-pass\n // width change. No relayout: native scroll shifts the content.\n this.revealSelectedIfRequested();\n\n this.positionToolGroup(mainInner, toolExtent, thickness, crossLead, mainLead);\n this.positionLeadGroup(thickness, crossLead, mainLead);\n this.positionIndicator();\n this.positionCloseButtons();\n }\n\n /**\n * Installs the within-strip drag-reorder wiring: one strip-wide mousedown\n * capture (so a press on a ✕ can veto the drag), a drag source per tab\n * wrapper, and a single drop target. Idempotent — clears any prior wiring\n * first. Called from `init` (when `_reorderable`) and from\n * {@link setReorderable}.\n */\n private installTabDnD(): void {\n this.teardownTabDnD();\n\n // One subtree mousedown capture records the pressed element so\n // `onDragStart` can veto a drag that began on a close button\n // (DragEventDetail carries no DOM target), plus the Shift state so a\n // tear-off can force a bare window.\n const recordMouseTarget = (e: MouseEvent): void => {\n this._dragMouseTarget = e.target;\n this._dragShiftHeld = e.shiftKey;\n };\n\n Event.addSubtreeListener(this, \"mousedown\", recordMouseTarget);\n this._dndTeardowns.push(() => Event.removeSubtreeListener(this, \"mousedown\", recordMouseTarget));\n\n for (const entry of this._entries) {\n this._dndTeardowns.push(this.makeTabDragSource(entry));\n }\n\n this._dndTeardowns.push(this.makeTabDropTarget());\n }\n\n /**\n * Registers a drag source on one tab wrapper, vetoing the gesture when the\n * press began inside that tab's close button. On commit it emits\n * `\"tabdragstart\"` so the owner can register the live content in the shared\n * drag registry, then routes the release through {@link onTabDragEnd}.\n *\n * @param entry - The cell whose wrapper becomes a drag source.\n *\n * @returns The source teardown closure.\n */\n private makeTabDragSource(entry: BarEntry): () => void {\n // Chrome entries (transient, e.g. a Dock start-page placeholder) are inert:\n // never a drag source, so they cannot be reordered, torn off, or dropped\n // elsewhere. Both wiring paths (createBarEntry and installTabDnD) route\n // through here, so this one guard covers them.\n if (entry.constraints?.transient) {\n return () => {};\n }\n\n return DragManager.makeDragSource(entry.button, {\n dragData: (): DragData => {\n const data: TabDragData = {\n tabDrag: true,\n sourceTabId: this.stripId(),\n componentId: entry.contentId,\n label: entry.name,\n };\n\n // Spread to an anonymous object literal: a typed interface value\n // is not assignable to DragData's index signature, but a literal is.\n return { ...data };\n },\n onDragStart: (): boolean | void => {\n const target = this._dragMouseTarget;\n this._dragMouseTarget = null;\n\n const closeElement = entry.closeButton?.getElement();\n\n if (closeElement && DOM.source.isNode(target) && DOM.source.contains(closeElement, DOM.source.intern(target))) {\n return false;\n }\n\n // The owner registers its live content (if ready) in the shared\n // registry so a foreign strip's drop can resolve it from the\n // id-only drag data.\n this.emit(\"tabdragstart\", entry.id);\n },\n onDragEnd: (detail: DragEventDetail, dropped: boolean): void => this.onTabDragEnd(entry, detail, dropped),\n });\n }\n\n /**\n * Registers the single drop target on the clip frame that drives the insertion\n * bar and commits the reorder. `onDragOver` returns `null` to suppress the drag\n * manager's own horizontal reorder indicator in favour of the main-axis\n * {@link TabReorderBar}.\n *\n * Feedback follows the framework's drag-and-drop colour convention: blue marks\n * where the drop lands, the green/red wash marks whole-target validity. So\n * `suppressValidityTint` turns off the manager's whole-strip green wash, and\n * the strip paints the two blue cues a dock region uses instead — a faint\n * {@link TabDropTint} \"droppable here\" wash over the whole strip plus the\n * brighter {@link TabReorderBar} marking the precise insertion slot.\n *\n * @returns The target teardown closure.\n */\n private makeTabDropTarget(): () => void {\n return DragManager.makeDropTarget(this._tabClip, {\n suppressValidityTint: true,\n accepts: (detail: DragEventDetail): boolean => this.isTabHeaderDrag(detail),\n onDragOver: (detail: DragEventDetail): number | null => {\n this._dropTint.showOver(this._tabClip);\n this.updateReorderSlot(detail);\n\n // Spring-load a host-window raise only for a foreign dock — a\n // within-strip reorder is already in the frontmost window, so its\n // raise would be a needless re-stamp.\n if (detail.dragData[\"sourceTabId\"] !== this.stripId()) {\n this.scheduleSpringRaise();\n }\n\n return null;\n },\n onDragLeave: (): void => {\n this.clearSpringRaise();\n this.hideDropAffordance();\n },\n onDrop: (detail: DragEventDetail): void => {\n this.clearSpringRaise();\n this.dropTabHeader(detail);\n },\n });\n }\n\n /**\n * Arms the spring-loaded host-window raise if one is not already pending, so a\n * foreign tab dwelling over the strip surfaces its window after\n * {@link SPRING_RAISE_DELAY_MS}. Idempotent across the repeated `onDragOver`\n * calls a single hover produces. Fires `\"dockhover\"` (gated on the drag still\n * being live) for the owner to act on — the bar itself never touches a window.\n */\n private scheduleSpringRaise(): void {\n if (this._springRaiseTimer !== null) {\n return;\n }\n\n this._springRaiseTimer = setTimeout(() => {\n this._springRaiseTimer = null;\n\n if (DragManager.isDragging()) {\n this.emit(\"dockhover\");\n }\n }, SPRING_RAISE_DELAY_MS);\n }\n\n /**\n * Cancels a pending spring-loaded raise — the cursor left or dropped before the\n * dwell elapsed.\n */\n private clearSpringRaise(): void {\n if (this._springRaiseTimer !== null) {\n clearTimeout(this._springRaiseTimer);\n this._springRaiseTimer = null;\n }\n }\n\n /**\n * Clears both drag-over cues — the faint {@link TabDropTint} wash and the\n * {@link TabReorderBar} insertion rule — when a drag leaves the strip or a\n * drop completes. The two are shown together in `onDragOver`, so they are\n * hidden together.\n */\n private hideDropAffordance(): void {\n this._reorderBar.hide();\n this._dropTint.hide();\n }\n\n /**\n * Tests whether a drag is a tab-header drag — either a within-strip reorder or\n * a dock from another strip.\n *\n * @param detail - The drag event detail.\n *\n * @returns `true` when the drag carries the tab-header marker.\n */\n private isTabHeaderDrag(detail: DragEventDetail): boolean {\n return detail.dragData[\"tabDrag\"] === true;\n }\n\n /**\n * Routes a tab-header drop: a drag from this strip reorders within it; a drag\n * from another strip emits `\"dockrequested\"` so the owner docks the live\n * content here as a new tab at the slot {@link updateReorderSlot} computed.\n *\n * @param detail - The drag event detail (carries the source strip id and the dragged component id).\n */\n private dropTabHeader(detail: DragEventDetail): void {\n if (detail.dragData[\"sourceTabId\"] === this.stripId()) {\n this.dropReorder(detail);\n\n return;\n }\n\n this.emit(\"dockrequested\", detail.dragData[\"componentId\"] as string, this._dragInsertIndex);\n\n this.hideDropAffordance();\n this._dragInsertIndex = -1;\n }\n\n /**\n * The strip's stable identity, stamped into {@link TabDragData.sourceTabId} and\n * compared by {@link dropTabHeader} to tell a within-strip reorder from a dock\n * from elsewhere. Uses the strip's own id — one per strip, stable for its\n * lifetime.\n *\n * @returns The strip component's id.\n */\n private stripId(): string {\n return this.getId();\n }\n\n /**\n * Source-side end of a header gesture. A release over empty space emits\n * `\"tearoffrequested\"`; a release a target consumed (`dropped`) emits\n * `\"detach\"`. The owner applies the content guards (whether the cell is\n * ready, whether its content actually left this container) and acts.\n *\n * @param entry - The dragged cell.\n * @param detail - The drag event detail (carries the release point).\n * @param dropped - `true` when the release landed on a registered drop target;\n * `false` only on a release over empty space, which tears the tab off.\n */\n private onTabDragEnd(entry: BarEntry, detail: DragEventDetail, dropped: boolean): void {\n if (!dropped) {\n this.emit(\"tearoffrequested\", entry.id, detail.clientX, detail.clientY, this._dragShiftHeld);\n\n return;\n }\n\n this.emit(\"detach\", entry.id);\n }\n\n /**\n * Computes the insertion slot from the cursor's main-axis position, caches it\n * in `_dragInsertIndex`, and places the insertion bar at the slot boundary.\n *\n * @param detail - The drag event detail (carries the viewport cursor).\n */\n private updateReorderSlot(detail: DragEventDetail): void {\n const element = this._tabClip.getClipElement();\n\n if (!element) {\n return;\n }\n\n const rect = DOM.source.getElementRect(element);\n const vertical = this.isVertical();\n\n // The clip frame scrolls its content natively, but `rect` is the border\n // box, which ignores that scroll. The wrappers' getX()/getY() are in the\n // scrolled content space, so add the scroll offset to land the cursor in\n // the same space — otherwise a scrolled strip maps it to the wrong slot.\n const cursorMain = (vertical ? detail.clientY - rect.top : detail.clientX - rect.left)\n + this._tabClip.mainScroll();\n\n let insertIndex = this._entries.length;\n\n for (let i = 0; i < this._entries.length; i++) {\n const wrapper = this._entries[i].button;\n const start = vertical ? wrapper.getY() : wrapper.getX();\n const extent = vertical ? wrapper.getHeight() : wrapper.getWidth();\n\n if (cursorMain < start + extent / 2) {\n insertIndex = i;\n\n break;\n }\n }\n\n this._dragInsertIndex = insertIndex;\n\n const boundary = this.slotBoundary(insertIndex, vertical);\n const thickness = vertical ? this._tabClip.getWidth() : this._tabClip.getHeight();\n\n this._reorderBar.placeAt(boundary, thickness, vertical);\n }\n\n /**\n * Resolves the main-axis coordinate of a slot boundary: the leading edge of\n * the wrapper at `insertIndex`, or the trailing edge of the last wrapper for an\n * append.\n *\n * @param insertIndex - The slot index in `[0, entries.length]`.\n * @param vertical - Whether the strip's main axis is Y.\n *\n * @returns The boundary's main-axis coordinate in px.\n */\n private slotBoundary(insertIndex: number, vertical: boolean): number {\n if (insertIndex < this._entries.length) {\n const wrapper = this._entries[insertIndex].button;\n\n return vertical ? wrapper.getY() : wrapper.getX();\n }\n\n if (this._entries.length > 0) {\n const wrapper = this._entries[this._entries.length - 1].button;\n\n return vertical ? wrapper.getY() + wrapper.getHeight() : wrapper.getX() + wrapper.getWidth();\n }\n\n return 0;\n }\n\n /**\n * Commits a header reorder drop: hides the bar, maps the drag source back to\n * its cell, and moves it to the cached insertion slot.\n *\n * @param detail - The drag event detail (carries the source id).\n */\n private dropReorder(detail: DragEventDetail): void {\n this.hideDropAffordance();\n\n const fromIdx = this._entries.findIndex(entry => entry.button.getId() === detail.sourceId);\n\n if (fromIdx < 0 || this._dragInsertIndex < 0) {\n return;\n }\n\n this.reorderTab(fromIdx, this._dragInsertIndex);\n this._dragInsertIndex = -1;\n }\n\n /**\n * Moves a cell from `fromIdx` to the insertion slot `toIdx`, reorders the\n * wrapper among the clip frame's children, and emits `\"reorder\"` so the\n * owner re-derives its content order. The active cell (tracked by id) stays\n * active across the move.\n *\n * @param fromIdx - The dragged cell's current index.\n * @param toIdx - The insertion slot in `[0, entries.length]`.\n */\n private reorderTab(fromIdx: number, toIdx: number): void {\n if (fromIdx < 0 || fromIdx >= this._entries.length) {\n return;\n }\n\n // An insertion slot past the source collapses by one once the source is\n // spliced out; clamp into the post-removal index range.\n let dest = toIdx > fromIdx ? toIdx - 1 : toIdx;\n dest = Math.max(0, Math.min(dest, this._entries.length - 1));\n\n if (dest === fromIdx) {\n return;\n }\n\n const fromId = this._entries[fromIdx].id;\n const entry = this._entries[fromIdx];\n\n this._entries.splice(fromIdx, 1);\n this._entries.splice(dest, 0, entry);\n\n this._tabClip.moveItem(entry.button, dest);\n\n this.emit(\"reorder\", fromId, dest);\n }\n\n /**\n * Tears down all drag sources, the drop target, and the mousedown capture, and\n * hides the insertion bar. Called from {@link dispose} and {@link setReorderable}.\n */\n private teardownTabDnD(): void {\n for (const teardown of this._dndTeardowns) {\n teardown();\n }\n\n this._dndTeardowns = [];\n this.hideDropAffordance();\n }\n\n /**\n * Handles ArrowLeft / ArrowRight to move tab focus and activate the adjacent tab.\n *\n * @param e - The keyboard event fired on the strip element.\n */\n private onToolbarKeyDown(e: KeyboardEvent): void {\n if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') {\n return;\n }\n\n const tabCount = this._entries.length;\n\n if (tabCount === 0) {\n return;\n }\n\n e.preventDefault();\n\n const activeIdx = this._entries.findIndex(entry => entry.id === this._activeId);\n const base = activeIdx >= 0 ? activeIdx : 0;\n\n const newIdx = e.key === 'ArrowRight'\n ? (base + 1) % tabCount\n : (base - 1 + tabCount) % tabCount;\n\n const newTab = this._entries[newIdx].button;\n\n this._entries.forEach(entry => entry.button.setSelected(false));\n newTab.setSelected(true);\n\n this.onTabPressed(newTab);\n }\n\n /**\n * Registers a listener for a {@link TabBarEvent}.\n *\n * @param event - The event to listen for.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This tab strip, for method chaining.\n */\n on(event: \"tabpressed\", listener: (id: string) => void): this;\n on(event: \"reorder\", listener: (fromId: string, toIndex: number) => void): this;\n on(event: \"tabclose\", listener: (id: string) => void): this;\n on(event: \"dockrequested\", listener: (componentId: string, slot: number) => void): this;\n on(event: \"tabdragstart\", listener: (id: string) => void): this;\n on(event: \"tearoffrequested\", listener: (id: string, clientX: number, clientY: number, forceBare: boolean) => void): this;\n on(event: \"detach\", listener: (id: string) => void): this;\n on(event: \"dockhover\", listener: () => void): this;\n on(event: TabBarEvent, 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 must\n * match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This tab strip, for method chaining.\n */\n off(event: TabBarEvent, 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 registration\n * order.\n *\n * @param event - The event to emit.\n * @param payload - Forwarded to each listener.\n */\n protected emit(event: \"tabpressed\", id: string): void;\n protected emit(event: \"reorder\", fromId: string, toIndex: number): void;\n protected emit(event: \"tabclose\", id: string): void;\n protected emit(event: \"dockrequested\", componentId: string, slot: number): void;\n protected emit(event: \"tabdragstart\", id: string): void;\n protected emit(event: \"tearoffrequested\", id: string, clientX: number, clientY: number, forceBare: boolean): void;\n protected emit(event: \"detach\", id: string): void;\n protected emit(event: \"dockhover\"): void;\n protected emit(event: TabBarEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n}\n\nconst TabBarCallable = callable(TabBar);\ntype TabBarCallable = TabBar;\nexport {\n TabBar as _TabBar,\n TabBarCallable as TabBar\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { LayoutManager, LayoutManagerOptions } from \"~/layout/LayoutManager.js\";\nimport { LayoutConstraints } from \"~/layout/LayoutConstraints.js\";\nimport { Size } from \"~/primitive/Size.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { Component } from \"~/core/Component.js\";\nimport type { ComponentFactory } from \"~/core/Component.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Window } from \"~/overlay/Window.js\";\nimport { TabWindow } from \"~/overlay/TabWindow.js\";\nimport { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport { ThemeManager } from \"~/core/Theme.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { FillType } from \"~/layout/FillType.js\";\nimport { createSpinnerWrap } from \"~/component/display/SpinnerWrap.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { tabDragRegistry } from \"~/overlay/DragManager.js\";\nimport { TabBar } from \"~/component/container/TabBar.js\";\nimport type { TabToolDescriptor } from \"~/component/container/TabBar.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { AxisPosition, AxisEnd } from \"~/primitive/Axis.js\";\n\n/**\n * String-literal union of the events emitted by {@link Tab}.\n *\n * `\"tabclose\"` fires when a tab is closed (carrying the removed content);\n * `\"empty\"` fires when the strip loses its last tab by any path — close,\n * tear-off, or re-dock — and carries no payload; `\"detach\"` fires when a tab\n * is torn off into a new floating window (carrying that window), the one\n * structural change that does *not* always empty the source strip; `\"dock\"`\n * fires when a foreign tab is dropped onto this strip (carrying the docked\n * content), the structural counterpart of `\"detach\"` — a tab arrived by drop;\n * `\"select\"` fires the moment a tab is picked by a click or\n * {@link Tab.setActiveTabIndex} (carrying its index and label), before any\n * deferred content is built — selection *intent*, which is what a router should\n * write its URL from; `\"activate\"` fires when the active tab's content is live\n * (carrying that content and its index), which for a lazy tab's first selection\n * is after its factory has run — completion, which is what a consumer needing\n * the component waits for. Neither fires on the silent post-close re-selection\n * of a surviving sibling;\n * `\"exception\"` fires when a deferred tab's asynchronous factory rejected\n * (carrying the rejection value and the tab's label), after that tab has already\n * been closed.\n *\n * @category Layouts\n */\nexport type TabEvent = \"tabclose\" | \"empty\" | \"detach\" | \"select\" | \"activate\" | \"dock\" | \"exception\";\n\n/**\n * How a torn-off tab's floating window hosts its content.\n *\n * - `\"strip\"` (default) — the window hosts a one-tab reorderable strip; the\n * tab re-docks onto another strip by dragging it out, and the emptied window\n * closes itself.\n * - `\"bare\"` — the live content fills the window body directly; it re-docks by\n * Ctrl-dragging the window header onto a strip.\n *\n * @category Layouts\n */\nexport type TabDetachWindowMode = \"bare\" | \"strip\";\n\n// Default size of the floating window a torn-off tab opens into. A tab has no\n// inherent window size, so we open at a comfortable working area rather than the\n// content's current extent (which spans the full tab body and would spawn an\n// oversized window); the user can resize from there.\nconst DETACH_WINDOW_WIDTH: number = 480;\nconst DETACH_WINDOW_HEIGHT: number = 360;\n\n/**\n * Tab-button width strategy for the {@link Tab} strip.\n *\n * - `\"fill\"` — tabs split the strip equally and stretch to fill it.\n * - `\"content\"` — each tab takes its own content width, capped at `maxWidth`.\n * - `\"equal\"` — every tab takes the width of the widest tab, capped at\n * `maxWidth` (default).\n * - `\"fixed\"` — every tab takes `fixedWidth`.\n *\n * Every mode except `\"fill\"` leaves the strip full-width with the tabs\n * left-aligned and any leftover space empty.\n *\n * @category Layouts\n */\nexport type TabWidthMode = \"fill\" | \"content\" | \"equal\" | \"fixed\";\n\n/**\n * Which edge of the content area the {@link Tab} strip sits on.\n *\n * - `\"north\"` — strip on top, content below (the default).\n * - `\"south\"` — strip on the bottom, content above.\n * - `\"west\"` — strip on the left (fixed width), content to the right.\n * - `\"east\"` — strip on the right, content to the left.\n *\n * @category Layouts\n */\nexport type TabSide = \"north\" | \"south\" | \"west\" | \"east\";\n\n/**\n * Text orientation for tab buttons on the vertical sides (west/east). Ignored\n * for north/south, where tab text is always horizontal.\n *\n * - `\"horizontal\"` — buttons stack vertically but text stays upright.\n * - `\"vertical-cw\"` — text rotated 90° clockwise (`writing-mode: vertical-rl`).\n * - `\"vertical-ccw\"` — text rotated the other way (`writing-mode: vertical-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 preferred-size measurement and hit-testing correct.\n *\n * @category Layouts\n */\nexport type TabOrientation = \"horizontal\" | \"vertical-cw\" | \"vertical-ccw\";\n\n/**\n * Duration (ms) of the cross-tab content fade-in. Matches `AnimatedDropdown`'s\n * default (and the strip indicator's slide, which {@link TabBar} keeps its own\n * copy of) so tabs and the ComboBox caret animate at the same pace.\n */\nconst TAB_FADE_DURATION_MS = 120;\n\n/**\n * Construction-time options for {@link Tab}.\n *\n * @category Layouts\n */\nexport interface TabOptions extends LayoutManagerOptions {\n /**\n * Multi-event listener bag dispatched to {@link Tab.on} at construction\n * time.\n */\n listeners?: {\n tabclose?: (component: Component) => void;\n /** Fires after the last tab leaves the strip by any path (close, tear-off, re-dock). */\n empty?: () => void;\n /** Fires after a deferred tab's async factory rejected and its tab was closed. */\n exception?: (error: unknown, label: string) => void;\n };\n\n /** Tab-button width strategy; defaults to `\"equal\"`. */\n widthMode?: TabWidthMode;\n\n /** Per-tab maximum width in px for `\"content\"` / `\"equal\"` modes; `null` (the default) leaves tabs uncapped. */\n maxWidth?: number | null;\n\n /** Per-tab width in px for `\"fixed\"` mode; defaults to `120`. */\n fixedWidth?: number;\n\n /**\n * Whether the 1px strip under-border runs edge-to-edge. When omitted, follows\n * the active theme's `tab.underBorderFullWidth` (Modern `false`, Classic/Dark\n * `true`); setting it explicitly pins the value and stops it tracking the theme.\n */\n underBorderFullWidth?: boolean;\n\n /** Which edge the tab strip sits on; defaults to `\"north\"`. */\n side?: TabSide;\n\n /** Main-axis alignment of the tab-button group; defaults to `\"start\"`. */\n align?: AxisEnd;\n\n /** Text orientation on the vertical sides; defaults to `\"horizontal\"`. */\n orientation?: TabOrientation;\n\n /**\n * Whether an overflowing strip scrolls (leading/trailing arrow buttons,\n * tabs kept at preferred size) instead of compressing the tabs to fit.\n * Defaults to `false`.\n */\n scrollable?: boolean;\n\n /**\n * Tools pinned at the far end of the strip, opposite the tabs. A plain\n * {@link Component} is a bare strip tool; a {@link TabToolDescriptor} also\n * surfaces in the context menu's `Tools` submenu.\n */\n tools?: (Component | TabToolDescriptor)[];\n\n /** Reduce tab-button insets for a denser strip. Defaults to `false`. */\n compact?: boolean;\n\n /** Enable within-strip header drag-reorder. Defaults to `false`. */\n reorderable?: boolean;\n\n /**\n * When true, the tab bar extends to the container's outer edges (ignoring\n * the container's content insets) while the tab content stays inset; the bar\n * absorbs the parent inset as its own inset so its chrome stays flush with the\n * content. Mirrors a Border NORTH region's `ignoreParentInsets`. Defaults to false.\n */\n barIgnoreParentInsets?: boolean;\n\n /**\n * How a torn-off tab's floating window hosts its content — a one-tab strip\n * (`\"strip\"`, the default) or the bare content (`\"bare\"`). Defaults to\n * `\"strip\"`. See {@link TabDetachWindowMode}.\n */\n detachWindowMode?: TabDetachWindowMode;\n\n /** Tab-label justification (strip-wide); defaults to `\"center\"`. */\n textAlign?: AxisPosition;\n}\n\n/**\n * Lifecycle state of a tab slot.\n *\n * - `\"ready\"` — `component` is built and attached. `getVisibleComponent`\n * returns it directly.\n * - `\"lazy\"` — `factory` is registered but has not run. First activation\n * transitions to `\"building\"`.\n * - `\"building\"` — the materialize helper has mounted `spinner` into the\n * container and queued the factory behind a two-rAF yield. `onReady`\n * moves the entry to `\"ready\"`. Re-entering this state is suppressed\n * so spam-clicks during a build do not start a second factory run.\n */\ntype TabEntryState = \"lazy\" | \"building\" | \"ready\";\n\n/**\n * The content half of a tab slot — the lazy-load state machine and the live\n * content. The bar half (button, wrapper, close button, label, constraints)\n * lives in the {@link TabBar} cell keyed by the same stable `id`; the two halves\n * never reference each other directly, only through the shared id.\n *\n * @remarks `component` is `null` for entries registered via `addLazyTab` until\n * the first activation; on materialization, the factory runs and the produced\n * component is cached here. Eager entries (created by `createTab`) populate\n * `component` immediately and leave `factory` null. The component reference is\n * stored on the entry rather than looked up by index in\n * `container.getComponents()` because lazy tabs may materialize out of order —\n * `Component.addComponent` always appends, so indices between `_contents[]` and\n * the container's component list do not stay aligned.\n *\n * `spinner` carries the placeholder Component that `Animation.materialize`\n * mounts into the container during the factory's two-rAF yield, so the layout\n * pass can surface it as the visible child while the build is in flight.\n */\ninterface ContentEntry {\n id: string;\n component: Component | null;\n factory: ComponentFactory | null;\n spinner: Component | null;\n state: TabEntryState;\n\n /**\n * In-flight `Animation.materialize` for a lazy entry, cancelled when the\n * entry leaves `_contents` or the manager detaches — otherwise its fade's\n * fallback timer outlives the element handle the teardown releases.\n */\n materializeAnimation: Animation.CancelHandle | null;\n\n /**\n * Set when this entry was selected while it had no `component` yet (a lazy\n * entry, or one already building): the `\"activate\"` announcement is owed\n * until materialization produces the content the event has to carry. Cleared\n * when materialization settles it — and dropped without emitting if the\n * selection has since moved on.\n */\n announceActivation: boolean;\n}\n\n/**\n * A layout manager that renders a row of tab buttons along one edge of the\n * container content area and shows exactly one child component at a time based on\n * the selected tab. Tab button labels resolve in priority order: the\n * per-placement `LayoutConstraints.name` override, then the component's intrinsic\n * [`name`](/api/core/classes/Component#getname), then its ID.\n *\n * `Tab` is the **content** manager: it owns the selected panel, lazy-load /\n * materialization, content swapping, tab tear-off into a floating\n * [`Window`](/api/overlay/classes/Window), and inter-strip docking. The **bar** —\n * the toolbar strip, the tab buttons, the selection indicator, the reorder bar,\n * the tool group, overflow scrolling, and tab drag-and-drop — is a composable\n * [`TabBar`](/api/component/container/classes/TabBar) it owns and reacts to: the\n * bar emits window-agnostic events and `Tab` performs the content / window work.\n *\n * @category Layouts\n */\nclass Tab extends LayoutManager {\n\n // The composable tab strip — the toolbar element, buttons, indicator, reorder\n // bar, tool group, overflow scroll, and tab DnD. `Tab` raw-appends its element\n // to the container and drives it through prepareStrip / stripThickness /\n // placeStrip each layout pass; the content panels stay the container's real\n // children, never enrolled in the bar.\n private _bar: TabBar = new TabBar();\n\n // Whether the tab strip is shown. When false, doLayout hides the bar element\n // and reserves zero strip thickness so the active content fills the whole\n // region — used to drop the dangling empty strip of a tab-less region.\n private _barVisible: boolean = true;\n\n // Content records, keyed by the same stable id as the bar cells. Holds the\n // lazy-load state machine and the live content — never any bar/DOM state.\n private _contents: Array<ContentEntry> = [];\n\n private _selectedTabIndex: number = 0;\n\n // A setActiveContent request for a child whose tab cell does not exist yet\n // (added but not laid out). doLayout applies it once it creates the cell.\n private _pendingActiveContent: Component | null = null;\n // Last tab index that was faded in during a doLayout pass. Compared\n // against `_selectedTabIndex` so the cross-tab fade fires only on actual\n // selection changes (not on every relayout, e.g. window resize).\n private _lastFadedTabIndex: number = -1;\n\n // In-flight cross-tab fade, cancelled on detach so its fallback timer\n // cannot fire against a released content-element handle.\n private _tabFadeAnimation: Animation.CancelHandle | null = null;\n private _listeners: ListenerBag<TabEvent> = new ListenerBag<TabEvent>();\n\n // When true, doLayout grows the bar to the container's outer edges and hands\n // it the absorbed parent inset as its own inset; the content stays inset.\n private _barIgnoreParentInsets: boolean = false;\n\n // How a torn-off tab's window hosts its content — consulted at tear-off time.\n private _detachWindowMode: TabDetachWindowMode = \"strip\";\n\n // Set only on the inner strip a `\"strip\"`-mode tear-off builds inside a\n // window, so that strip (and only it) closes its host window when emptied.\n private _closeHostWindowWhenEmpty: boolean = false;\n\n // Monotonic seed for the stable per-tab id that links a ContentEntry to its\n // TabBar cell. Unique within this strip — the only scope the link needs.\n private _idSeq: number = 0;\n\n // Re-lays out the content + strip on theme change (the bar tracks only its own\n // under-border default); torn down in detach.\n private _themeCleanup: (() => void) | null = null;\n\n /**\n * Creates a Tab layout manager with an empty strip.\n *\n * @param options - Optional construction-time options.\n */\n constructor(options?: TabOptions) {\n // LayoutManager's constructor takes no options; applied via applyOptions below.\n // eslint-disable-next-line local/forward-super-options\n super();\n\n // A theme change can change tab-button metrics (and thus the strip\n // thickness), so re-lay out the whole strip+content; the bar separately\n // refreshes its under-border default.\n this._themeCleanup = ThemeManager.onThemeChange(() => {\n this.getContainer()?.scheduleLayout();\n });\n\n if (options) {\n this.applyOptions(options);\n }\n }\n\n /**\n * Applies a {@link TabOptions} bag, dispatching the close callback and the\n * strip configuration after the inherited LayoutManager defaults. The strip\n * setters forward to the owned {@link TabBar}.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: TabOptions): void {\n super.applyOptions(options);\n\n if (options.listeners !== undefined) {\n const listeners = options.listeners;\n\n for (const event of Object.keys(listeners) as Array<keyof typeof listeners>) {\n const listener = listeners[event];\n\n if (listener !== undefined) {\n // The bag's field types pair each key with its matching\n // listener, but the union key can't select a single narrow\n // `on` overload — cast to the implementation signature.\n (this.on as (event: TabEvent, listener: Function) => this)(event, listener);\n }\n }\n }\n\n if (options.widthMode !== undefined) {\n this.setWidthMode(options.widthMode);\n }\n\n if (options.maxWidth !== undefined) {\n this.setMaxWidth(options.maxWidth);\n }\n\n if (options.fixedWidth !== undefined) {\n this.setFixedWidth(options.fixedWidth);\n }\n\n if (options.underBorderFullWidth !== undefined) {\n this.setUnderBorderFullWidth(options.underBorderFullWidth);\n }\n\n if (options.side !== undefined) {\n this.setSide(options.side);\n }\n\n if (options.align !== undefined) {\n this.setAlign(options.align);\n }\n\n if (options.orientation !== undefined) {\n this.setOrientation(options.orientation);\n }\n\n if (options.scrollable !== undefined) {\n this.setScrollable(options.scrollable);\n }\n\n if (options.compact !== undefined) {\n this.setCompact(options.compact);\n }\n\n if (options.reorderable !== undefined) {\n this.setReorderable(options.reorderable);\n }\n\n if (options.barIgnoreParentInsets !== undefined) {\n this.setBarIgnoreParentInsets(options.barIgnoreParentInsets);\n }\n\n if (options.detachWindowMode !== undefined) {\n this.setDetachWindowMode(options.detachWindowMode);\n }\n\n if (options.textAlign !== undefined) {\n this.setTextAlign(options.textAlign);\n }\n\n if (options.tools !== undefined) {\n // Dispatch each element through an explicit `instanceof Component`\n // branch: overload resolution rejects the `Component | TabToolDescriptor`\n // union passed against the two separate non-union `addTool` signatures.\n for (const tool of options.tools) {\n if (tool instanceof Component) {\n this.addTool(tool);\n } else {\n this.addTool(tool);\n }\n }\n }\n }\n\n /**\n * Selects the tab-button width strategy (see {@link TabWidthMode}) and\n * re-lays out the strip.\n *\n * @param mode - The width strategy to apply.\n *\n * @returns This layout manager, for chaining.\n */\n setWidthMode(mode: TabWidthMode): this {\n this._bar.setWidthMode(mode);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current tab-button width strategy.\n *\n * @returns The active {@link TabWidthMode}.\n */\n getWidthMode(): TabWidthMode {\n return this._bar.getWidthMode();\n }\n\n /**\n * Sets the per-tab width cap used by the `\"content\"` and `\"equal\"` width\n * modes, then re-lays out the strip.\n *\n * @param px - The maximum width per tab in px, or `null` to remove the cap.\n *\n * @returns This layout manager, for chaining.\n */\n setMaxWidth(px: number | null): this {\n this._bar.setMaxWidth(px);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current per-tab maximum width.\n *\n * @returns The cap in px, or `null` when tabs are uncapped.\n */\n getMaxWidth(): number | null {\n return this._bar.getMaxWidth();\n }\n\n /**\n * Sets the per-tab width used by the `\"fixed\"` width mode, then re-lays out\n * the strip.\n *\n * @param px - The fixed width per tab in px.\n *\n * @returns This layout manager, for chaining.\n */\n setFixedWidth(px: number): this {\n this._bar.setFixedWidth(px);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the per-tab width used by the `\"fixed\"` width mode.\n *\n * @returns The fixed width in px.\n */\n getFixedWidth(): number {\n return this._bar.getFixedWidth();\n }\n\n /**\n * Toggles the edge-to-edge 1px rule under the tab strip. Pins the value for\n * this instance, so it no longer follows the active theme's\n * `tab.underBorderFullWidth` default on theme changes.\n *\n * @param full - `true` to draw the strip's full-width under-border, `false` to remove it.\n *\n * @returns This layout manager, for chaining.\n */\n setUnderBorderFullWidth(full: boolean): this {\n this._bar.setUnderBorderFullWidth(full);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether the strip's under-border runs edge-to-edge.\n *\n * @returns `true` when the full-width under-border is drawn.\n */\n isUnderBorderFullWidth(): boolean {\n return this._bar.isUnderBorderFullWidth();\n }\n\n /**\n * Selects which edge of the content area the tab strip sits on and re-lays\n * out.\n *\n * @param side - The {@link TabSide} to place the strip on.\n *\n * @returns This layout manager, for chaining.\n */\n setSide(side: TabSide): this {\n this._bar.setSide(side);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the edge the tab strip currently sits on.\n *\n * @returns The active {@link TabSide}.\n */\n getSide(): TabSide {\n return this._bar.getSide();\n }\n\n /**\n * Sets the main-axis alignment of the tab-button group within the strip and\n * re-lays out.\n *\n * @param align - The {@link AxisEnd} to apply.\n *\n * @returns This layout manager, for chaining.\n */\n setAlign(align: AxisEnd): this {\n this._bar.setAlign(align);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current tab-button-group alignment.\n *\n * @returns The active {@link AxisEnd}.\n */\n getAlign(): AxisEnd {\n return this._bar.getAlign();\n }\n\n /**\n * Sets the tab-button text orientation for the vertical sides and re-lays\n * out.\n *\n * @param orientation - The {@link TabOrientation} to apply.\n *\n * @returns This layout manager, for chaining.\n */\n setOrientation(orientation: TabOrientation): this {\n this._bar.setOrientation(orientation);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current vertical-side tab text orientation.\n *\n * @returns The active {@link TabOrientation}.\n */\n getOrientation(): TabOrientation {\n return this._bar.getOrientation();\n }\n\n /**\n * Sets the strip-wide tab-label justification and re-lays out.\n *\n * @param align - The {@link AxisPosition} to apply.\n *\n * @returns This layout manager, for chaining.\n */\n setTextAlign(align: AxisPosition): this {\n this._bar.setTextAlign(align);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns the current tab-label justification.\n *\n * @returns The active {@link AxisPosition}.\n */\n getTextAlign(): AxisPosition {\n return this._bar.getTextAlign();\n }\n\n /**\n * Sets whether an overflowing strip scrolls (leading/trailing arrow buttons,\n * tabs kept at preferred size) instead of compressing the tabs to fit, and\n * re-lays out.\n *\n * @param value - `true` to scroll on overflow, `false` to compress.\n *\n * @returns This layout manager, for chaining.\n */\n setScrollable(value: boolean): this {\n this._bar.setScrollable(value);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether an overflowing strip scrolls instead of compressing.\n *\n * @returns `true` when the strip scrolls on overflow.\n */\n isScrollable(): boolean {\n return this._bar.isScrollable();\n }\n\n /**\n * Toggles reduced tab-button insets (a denser strip) and re-lays out.\n *\n * @param value - `true` for compact insets, `false` for the default.\n *\n * @returns This layout manager, for chaining.\n */\n setCompact(value: boolean): this {\n this._bar.setCompact(value);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether the strip uses reduced (compact) tab-button insets.\n *\n * @returns `true` when compact.\n */\n isCompact(): boolean {\n return this._bar.isCompact();\n }\n\n /**\n * Enables or disables within-strip drag-reorder of tab headers. Forwards to\n * the strip, which installs / tears down the drag wiring.\n *\n * @param value - `true` to enable header drag-reorder.\n *\n * @returns This layout manager, for chaining.\n */\n setReorderable(value: boolean): this {\n this._bar.setReorderable(value);\n\n return this;\n }\n\n /**\n * Returns whether within-strip header drag-reorder is enabled.\n *\n * @returns `true` when reorderable.\n */\n isReorderable(): boolean {\n return this._bar.isReorderable();\n }\n\n /**\n * Shows or hides the tab strip and re-lays out. When hidden, the strip is\n * removed from view and reserves no thickness, so the active content fills the\n * whole region — the way to drop a tab-less region's dangling empty strip\n * without destroying the region. The content and its selection are untouched.\n *\n * @param value - `true` to show the strip, `false` to hide it.\n *\n * @returns This layout manager, for chaining.\n */\n setBarVisible(value: boolean): this {\n if (this._barVisible === value) {\n return this;\n }\n\n this._barVisible = value;\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether the tab strip is shown.\n *\n * @returns `true` when the strip is visible.\n */\n isBarVisible(): boolean {\n return this._barVisible;\n }\n\n /**\n * Whether the tab holds no tabs of its own — concerned only with this tab's\n * content, not with anything the tabs may reference elsewhere.\n *\n * @returns `true` when the tab has no tabs.\n */\n isEmpty(): boolean {\n return this._contents.length === 0;\n }\n\n /**\n * Toggles whether the tab bar extends to the container's outer edges,\n * absorbing the container's content inset as the bar's own inset while the\n * content stays inset, then re-lays out.\n *\n * @param value - `true` to grow the bar to the outer edges, `false` to keep it inset.\n *\n * @returns This layout manager, for chaining.\n */\n setBarIgnoreParentInsets(value: boolean): this {\n this._barIgnoreParentInsets = value;\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether the tab bar extends to the container's outer edges.\n *\n * @returns `true` when the bar ignores the container's content insets.\n */\n isBarIgnoreParentInsets(): boolean {\n return this._barIgnoreParentInsets;\n }\n\n /**\n * Sets how a torn-off tab's floating window hosts its content. The mode is\n * consulted at the next tear-off, so this just caches the value — no layout\n * work, unlike {@link setReorderable}.\n *\n * @param mode - `\"strip\"` for a one-tab strip in the window, `\"bare\"` for the content directly.\n *\n * @returns This layout manager, for chaining.\n */\n setDetachWindowMode(mode: TabDetachWindowMode): this {\n this._detachWindowMode = mode;\n\n return this;\n }\n\n /**\n * Returns the tear-off window content mode.\n *\n * @returns The active {@link TabDetachWindowMode}.\n */\n getDetachWindowMode(): TabDetachWindowMode {\n return this._detachWindowMode;\n }\n\n /**\n * Flags this strip to close its host [`AbstractWindow`](/api/overlay/classes/AbstractWindow) once it empties\n * (its last tab is dragged out or closed). Set by a host window that builds a\n * `Tab` as its own layout manager — the same flag the auto-created tear-off\n * strip carries — so `hostWindow`/`closeHostWindowIfEmpty` resolve and\n * close that window. Caches the value; no layout work, like\n * {@link setDetachWindowMode}.\n *\n * @param value - True to close the host window when the strip empties.\n *\n * @returns This layout manager, for chaining.\n */\n setCloseHostWindowWhenEmpty(value: boolean): this {\n this._closeHostWindowWhenEmpty = value;\n\n return this;\n }\n\n /**\n * Adds a tool at the far end of the strip, opposite the tab buttons.\n *\n * Two forms: a plain {@link Component} is a bare strip tool with no context-menu\n * entry (a {@link Button} tool is forced into `flat` appearance so strip tools\n * read as flat icons regardless of how the caller configured them); a\n * {@link TabToolDescriptor} has its strip button and its `Tools`-submenu row\n * built from the one descriptor. The two are told apart by `instanceof\n * Component` — a descriptor is a plain object.\n *\n * @param button - The tool component to add (no menu row).\n *\n * @returns This layout manager, for chaining.\n */\n addTool(button: Component): this;\n\n /**\n * @param descriptor - Declares a tool that also appears in the context menu's\n * `Tools` submenu; the bar builds the strip button and menu row from it.\n */\n addTool(descriptor: TabToolDescriptor): this;\n\n addTool(arg: Component | TabToolDescriptor): this {\n if (arg instanceof Component) {\n // Plain tool: keep the flat-forcing for Button tools, then forward.\n if (arg instanceof Button) {\n arg.setFlat(true);\n }\n\n this._bar.addTool(arg);\n } else {\n // Descriptor tool: the bar builds + flats the Button and registers the menu row.\n this._bar.addTool(arg);\n }\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Removes a previously-added tool button.\n *\n * @param button - The tool component to remove.\n *\n * @returns This layout manager, for chaining.\n */\n removeTool(button: Component): this {\n this._bar.removeTool(button);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Sets or clears the bar's always-leading widget, forwarding to\n * {@link TabBar.setLeadingWidget}. Passing `null` clears the leading slot.\n *\n * @param widget - The widget to host in the leading slot, or `null` to clear it.\n *\n * @returns This layout manager, for chaining.\n */\n setBarLeadingWidget(widget: Component | null): this {\n this._bar.setLeadingWidget(widget);\n\n this.getContainer()?.scheduleLayout();\n\n return this;\n }\n\n /**\n * Recolors every opaque toolbar surface of the bar, forwarding to\n * {@link TabBar.setBarSurfaceColor}. A recolor only — it does not relayout.\n *\n * @param color - A CSS color string applied to every toolbar surface.\n *\n * @returns This layout manager, for chaining.\n */\n setBarBackgroundColor(color: string): this {\n this._bar.setBarSurfaceColor(color);\n\n return this;\n }\n\n /**\n * Installs empty-bar-area window-chrome gestures on the underlying strip,\n * forwarding to {@link TabBar.installMoveTrigger}. A press on the bar's blank\n * area (not on a tab, a tool, or a scroll arrow) invokes `onEmptyPress`; an\n * optional `onEmptyDoubleClick` fires on a double-click of the same area. A\n * host [`AbstractWindow`](/api/overlay/classes/AbstractWindow) uses these to move and maximize from the bar.\n * Mirrors the {@link Tab.addTool} forwarding idiom.\n *\n * @param onEmptyPress - Callback invoked with the originating `mousedown`\n * when an empty bar area is pressed.\n * @param onEmptyDoubleClick - Optional callback invoked on a double-click of\n * the empty bar area.\n *\n * @returns This layout manager, for chaining.\n */\n installBarMoveTrigger(onEmptyPress: (e: MouseEvent) => void, onEmptyDoubleClick?: (e: MouseEvent) => void): this {\n this._bar.installMoveTrigger(onEmptyPress, onEmptyDoubleClick);\n\n return this;\n }\n\n /**\n * Mints the next stable per-tab id linking a {@link ContentEntry} to its\n * {@link TabBar} cell.\n *\n * @returns A new id unique within this strip.\n */\n private mintId(): string {\n return \"tab-\" + (this._idSeq++);\n }\n\n /**\n * Attaches to a container and raw-appends the tab strip element to it, then\n * subscribes to the strip's window-agnostic events.\n *\n * @param container - The container component to attach to.\n */\n attach(container: Component): this {\n super.attach(container);\n\n DOM.sink.appendChild(container.getElement(true)!, this._bar.getElement(true)!);\n\n this._bar.on(\"tabpressed\", this._onBarTabPressed);\n this._bar.on(\"reorder\", this._onBarReordered);\n this._bar.on(\"tabclose\", this._onBarTabClose);\n this._bar.on(\"dockrequested\", this._onBarDockRequested);\n this._bar.on(\"tabdragstart\", this._onBarTabDragStart);\n this._bar.on(\"tearoffrequested\", this._onBarTearOffRequested);\n this._bar.on(\"detach\", this._onBarDetached);\n this._bar.on(\"dockhover\", this._onBarDockHover);\n\n return this;\n }\n\n /**\n * Detaches from the container: unsubscribes from the strip, tears the strip\n * down, removes the theme subscription, and removes the strip element.\n */\n detach(): this {\n this._tabFadeAnimation?.cancel();\n this._tabFadeAnimation = null;\n\n for (const entry of this._contents) {\n entry.materializeAnimation?.cancel();\n entry.materializeAnimation = null;\n }\n\n super.detach();\n\n if (this._themeCleanup) {\n this._themeCleanup();\n this._themeCleanup = null;\n }\n\n this._bar.off(\"tabpressed\", this._onBarTabPressed);\n this._bar.off(\"reorder\", this._onBarReordered);\n this._bar.off(\"tabclose\", this._onBarTabClose);\n this._bar.off(\"dockrequested\", this._onBarDockRequested);\n this._bar.off(\"tabdragstart\", this._onBarTabDragStart);\n this._bar.off(\"tearoffrequested\", this._onBarTearOffRequested);\n this._bar.off(\"detach\", this._onBarDetached);\n this._bar.off(\"dockhover\", this._onBarDockHover);\n\n this._bar.dispose();\n\n return this;\n }\n\n /**\n * Strip `\"tabpressed\"` handler: a cell was activated. Points the active\n * content index at the cell's content, kicks its lazy-load, and re-lays out.\n * The bar already synced its own button group, roving focus, and indicator\n * intent before emitting.\n *\n * @param id - The activated cell id.\n */\n private _onBarTabPressed = (id: string): void => {\n const idx = this._contents.findIndex(entry => entry.id === id);\n\n if (idx >= 0) {\n this._selectedTabIndex = idx;\n\n const entry = this._contents[idx];\n\n // Selection intent, reported before the content is built (or even\n // asked for) — a router writing its URL from this lands on the\n // destination while the spinner is still up, rather than trailing a\n // slow factory. \"activate\" below is the completion half.\n this.emit(\"select\", idx, this._bar.getEntryName(entry.id));\n\n if (entry.state === \"lazy\") {\n this.materializeAsync(idx);\n }\n\n // Fires for both a user tab-click (bar \"tabpressed\" → here) and a\n // programmatic setActiveTabIndex (→ setActiveEntry → \"tabpressed\" →\n // here). The post-close re-selection uses setActiveVisual, which does\n // not route through here, so a close never emits a spurious activation.\n if (entry.component) {\n this.emit(\"activate\", entry.component, idx);\n } else {\n // A deferred entry (lazy, or already building) has no content\n // for the event to carry yet, so the announcement is owed\n // rather than skipped — otherwise a first-time selection is\n // silently unreported and only the second one is seen.\n entry.announceActivation = true;\n }\n }\n\n this.getContainer()?.scheduleLayout();\n };\n\n /**\n * Strip `\"reorder\"` handler: an in-strip reorder committed. Re-derives the\n * content order from the strip's new id order, keeping the selected content\n * selected by identity, then re-lays out.\n *\n * @param _fromId - The reordered cell id (the order is re-derived, so this is informational).\n * @param _toIndex - The destination slot (informational).\n */\n private _onBarReordered = (_fromId: string, _toIndex: number): void => {\n const order = this._bar.getEntryIds();\n const active = this._contents[this._selectedTabIndex] ?? null;\n\n this._contents.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));\n\n if (active) {\n const newIndex = this._contents.indexOf(active);\n\n if (newIndex >= 0) {\n this._selectedTabIndex = newIndex;\n }\n }\n\n this.getContainer()?.scheduleLayout();\n };\n\n /**\n * Strip `\"tabclose\"` handler: a cell's ✕ was clicked. Removes the cell and its\n * content component, emits the public `\"tabclose\"`, then selects the next tab.\n *\n * @param id - The cell id to close.\n */\n private _onBarTabClose = (id: string): void => {\n this.closeEntry(id);\n };\n\n /**\n * Tears down the tab with cell id `id` — the single close implementation\n * shared by the user-✕ click ({@link Tab._onBarTabClose}) and the public\n * {@link closeTab}. Removes the cell and its content component, emits\n * `\"tabclose\"`, selects the next tab, re-lays out, syncs the host window's\n * closeable state, closes an emptied strip-mode tear-off window, and emits\n * `\"empty\"` once the strip is drained.\n *\n * @param id - The cell id to close.\n */\n private closeEntry(id: string): void {\n const container = this.getContainer();\n if (!container) {\n return;\n }\n\n const idx = this._contents.findIndex(entry => entry.id === id);\n if (idx < 0) {\n return;\n }\n\n // Capture before the splice mutates the array: only a closed *active* tab\n // forces a selection move; closing a background tab keeps it.\n const wasSelected = this._selectedTabIndex === idx;\n const content = this._contents[idx].component;\n const spinner = this._contents[idx].spinner;\n\n this._contents[idx].materializeAnimation?.cancel();\n\n this._bar.removeBarEntry(id);\n this._contents.splice(idx, 1);\n\n if (content) {\n container.removeComponent(content);\n }\n\n // An entry closed mid-build still owns a mounted spinner; leaving it in\n // the container makes it an entry-unowned child, and the next layout\n // pass mints a phantom tab for it.\n if (spinner) {\n container.removeComponent(spinner);\n spinner.dispose();\n }\n\n if (content) {\n this.emit(\"tabclose\", content);\n }\n\n this.selectNextContent(idx, wasSelected);\n this.getContainer()?.scheduleLayout();\n this.syncHostWindowCloseable();\n this.closeHostWindowIfEmpty();\n\n if (this._contents.length === 0) {\n this.emit(\"empty\");\n }\n }\n\n /**\n * Closes the tab hosting `content` through the same teardown a user ✕ click\n * performs — removes the cell and content, emits `\"tabclose\"`, selects the\n * next tab, and emits `\"empty\"` when the strip drains. The programmatic\n * entry point for a tree owner such as [`Dock`](/api/overlay/classes/Dock)\n * to close a panel by its content component.\n *\n * @param content - The content component whose tab to close.\n *\n * @returns `true` when a matching tab was closed, `false` when none matched.\n */\n closeTab(content: Component): boolean {\n const entry = this._contents.find(e => e.component === content);\n\n if (!entry) {\n return false;\n }\n\n this.closeEntry(entry.id);\n\n return true;\n }\n\n /**\n * Strip `\"dockrequested\"` handler: a foreign tab was dropped here. Resolves the\n * live content from the shared registry and docks it as a new tab.\n *\n * @param componentId - The dragged content component's id.\n * @param slot - The strip insertion slot the bar computed.\n */\n private _onBarDockRequested = (componentId: string, slot: number): void => {\n const content = tabDragRegistry.get(componentId);\n\n if (content) {\n this.dockComponent(content, slot);\n\n // A panel docked into this strip should bring the strip's window (if\n // any) to the front and focus it — a no-op for an in-document strip.\n // Resolves the full ancestor window, not just a tear-off strip's\n // window, so a re-dock into a tiled dock raises the dock's window too\n // (mirroring the region-body drop's raise in DockRegion).\n this.ancestorWindow()?.bringToFront();\n\n // Announce the merge so a tree owner can react to a tab-bar dock the\n // same way it reacts to a tear-off. The structural counterpart of\n // \"detach\": a tab arrived by drop rather than leaving by tear-off.\n this.emit(\"dock\", content);\n }\n };\n\n /**\n * Strip `\"dockhover\"` handler: a foreign tab has dwelt over this strip long\n * enough to spring-load a raise. Surfaces the strip's window so a backgrounded\n * float can be aimed at, mirroring the dwell-raise a dock region performs over\n * its body — a no-op for an in-document strip or a window already frontmost.\n */\n private _onBarDockHover = (): void => {\n this.ancestorWindow()?.bringToFront();\n };\n\n /**\n * Strip `\"tabdragstart\"` handler: a cell's drag committed. Registers the cell's\n * live content (if ready) in the shared registry so a foreign strip's drop can\n * resolve it from the id-only drag data.\n *\n * @param id - The dragged cell id.\n */\n private _onBarTabDragStart = (id: string): void => {\n const entry = this._contents.find(e => e.id === id);\n\n if (entry && entry.state === \"ready\" && entry.component) {\n tabDragRegistry.set(entry.component.getId(), entry.component);\n }\n };\n\n /**\n * Strip `\"tearoffrequested\"` handler: a cell was released over empty space.\n * Clears the registry entry and — when the content is ready — tears it off into\n * a floating window.\n *\n * @param id - The dragged cell id.\n * @param clientX - Viewport X of the release point.\n * @param clientY - Viewport Y of the release point.\n * @param forceBare - When `true`, open a bare window regardless of the detach mode.\n */\n private _onBarTearOffRequested = (id: string, clientX: number, clientY: number, forceBare: boolean): void => {\n const entry = this._contents.find(e => e.id === id);\n\n if (!entry) {\n return;\n }\n\n if (entry.component) {\n tabDragRegistry.delete(entry.component.getId());\n }\n\n if (entry.state !== \"ready\" || !entry.component) {\n return;\n }\n\n this.detachTabToWindow(id, entry.component, clientX, clientY, forceBare);\n };\n\n /**\n * Strip `\"detach\"` handler: a cell's drag was released onto a target. Clears\n * the registry entry; if the content actually moved out of this container (a\n * dock into another strip, not a within-strip reorder), drops its cell.\n *\n * @param id - The dragged cell id.\n */\n private _onBarDetached = (id: string): void => {\n const entry = this._contents.find(e => e.id === id);\n\n if (!entry) {\n return;\n }\n\n const content = entry.component;\n\n if (content) {\n tabDragRegistry.delete(content.getId());\n }\n\n if (entry.state !== \"ready\" || !content) {\n return;\n }\n\n // Docked into another strip moves the content out of this container; a\n // within-strip reorder leaves it here. Only the former orphans the cell.\n const stillMine = this.getContainer()?.getComponents().includes(content) ?? false;\n\n if (!stillMine) {\n this.removeEntryKeepingContent(id);\n }\n };\n\n /**\n * Returns the child component at the currently selected tab index, materializing\n * a lazily-registered panel on first access.\n *\n * @returns The visible component, or `null` if no entry is registered at the selected index or the container is not attached.\n */\n getVisibleComponent(): Component | null {\n let container = this.getContainer();\n if (!container) {\n return null;\n }\n\n const entry = this._contents[this._selectedTabIndex];\n if (entry) {\n // While a build is in flight the spinner stays the visible child\n // even though `component` may already be captured (see\n // `materializeAsync`): the built component fades in over the\n // spinner via opacity, so the spinner must hold the slot until\n // `onReady` flips the entry to \"ready\".\n if (entry.state === \"building\" && entry.spinner) {\n return entry.spinner;\n }\n\n if (entry.component) {\n return entry.component;\n }\n\n return null;\n }\n\n return container.getComponents()[this._selectedTabIndex] ?? null;\n }\n\n /**\n * Returns the preferred size: the visible component's preferred size plus the\n * strip thickness on the strip's axis.\n *\n * @returns The preferred `{width, height}`, or `null` if there is no container or visible component.\n */\n getPreferredSize(): Size | null {\n return this.composeSize(this.getVisibleComponent()?.getPreferredSize());\n }\n\n /**\n * Returns the minimum size: the visible component's minimum size plus the\n * strip thickness on the strip's axis.\n *\n * @returns The minimum `{width, height}`, or `null` if there is no container or visible component.\n */\n getMinSize(): Size | null {\n return this.composeSize(this.getVisibleComponent()?.getMinSize());\n }\n\n /**\n * Returns the maximum size: the visible component's maximum size plus the\n * strip thickness on the strip's axis.\n *\n * @returns The maximum `{width, height}`, or `null` if there is no container or visible component.\n */\n getMaxSize(): Size | null {\n return this.composeSize(this.getVisibleComponent()?.getMaxSize());\n }\n\n /**\n * Reports whether the strip sits on a vertical side (west/east), where it\n * occupies width rather than height.\n *\n * @returns `true` for west/east, `false` for north/south.\n */\n private isVertical(): boolean {\n const side = this._bar.getSide();\n\n return side === \"west\" || side === \"east\";\n }\n\n /**\n * Adds the strip thickness and the container perimeter to a visible-content\n * size, on the axis the strip occupies for the current side: height for\n * north/south, width for west/east.\n *\n * @param content - The visible component's preferred/min/max size, or\n * `null`/`undefined` when unavailable.\n *\n * @returns The composed size, or `null` when there is no container or content.\n */\n private composeSize(content: Size | null | undefined): Size | null {\n const container = this.getContainer();\n\n if (!container || !content) {\n return null;\n }\n\n const perimeter = container.getPerimeterSize();\n const outerWidth = perimeter.left + perimeter.right;\n const outerHeight = perimeter.top + perimeter.bottom;\n const thickness = this._bar.stripThickness();\n\n if (this.isVertical()) {\n return {\n width: content.width + thickness + outerWidth,\n height: content.height + outerHeight,\n };\n }\n\n return {\n width: content.width + outerWidth,\n height: content.height + thickness + outerHeight,\n };\n }\n\n /**\n * Creates a content record and a strip cell for a component.\n *\n * @param component - The content component for which a tab should be created.\n *\n * @remarks The button label resolves in priority order: the per-placement\n * `LayoutConstraints.name` override, then the component's intrinsic\n * [`name`](/api/core/classes/Component#getname), then its ID as a last\n * resort. When `constraints.closeable` is true, a close button is added to the\n * cell.\n */\n createTab(component: Component): void {\n let constraints = this.getLayoutConstraints(component);\n const name = constraints?.name ?? component.getName() ?? component.getId();\n const id = this.mintId();\n\n this._bar.createBarEntry(id, name, constraints);\n this._contents.push({\n id,\n component,\n factory: null,\n spinner: null,\n state: \"ready\",\n materializeAnimation: null,\n announceActivation: false,\n });\n\n this.wireComponentAria(id, component);\n\n // A tab added to a tear-off window's strip may be non-closeable; keep the\n // host window's close button in step.\n this.syncHostWindowCloseable();\n }\n\n /**\n * Wires the cross-seam ARIA so the panel is announced as the tabpanel\n * controlled by its tab button: the bar sets the button's `aria-controls` to\n * the content id (through {@link TabBar.setEntryContentId}, which also records\n * the drag content id), and `Tab` points the content's `aria-labelledby` back\n * at the button.\n *\n * @param id - The cell id whose content became available.\n * @param component - The content component to attach ARIA roles to.\n */\n private wireComponentAria(id: string, component: Component): void {\n this._bar.setEntryContentId(id, component.getId());\n\n component.getAria().setRole(\"tabpanel\");\n component.getAria().setTabIndex(-1);\n component.getAria().setLabelledBy(this._bar.getEntryButtonId(id));\n }\n\n /**\n * Claims an unbuilt child offered by\n * [`Component.addComponent`](/api/core/classes/Component#addcomponent),\n * registering it as a tab whose content is built on first activation rather\n * than at registration time. The tab button is created immediately so the\n * strip renders fully on first paint; the factory runs only when the tab is\n * first selected (or laid out as the initial tab).\n *\n * Deferral is the default: only `constraints.lazy === false` declines it,\n * which hands the factory back to the container to build immediately.\n *\n * The tab's label comes from `constraints.name`, falling back to the minted\n * tab id when no name was given — a deferred entry has no component to ask.\n *\n * @param factory - Produces the content component on first activation. May\n * be asynchronous: the spinner stays up for the whole wait, and a\n * rejection closes the tab and emits `\"exception\"`.\n * @param constraints - Optional layout constraints; also the source of the\n * tab's label, glyph and tooltip.\n *\n * @returns `true` when the factory was registered as a deferred tab,\n * `false` when `lazy: false` declined the deferral.\n *\n * @remarks No container child exists until the content materializes, which\n * is what keeps the layout pass from minting a second, id-labelled tab\n * alongside this one.\n */\n override addDeferredComponent(factory: ComponentFactory, constraints?: LayoutConstraints): boolean {\n // `lazy` defaults to true: only an explicit false declines the deferral.\n if (constraints?.lazy === false) {\n return false;\n }\n\n // Give any container child added before this call its tab first, so tab\n // order tracks call order across interleaved eager and lazy adds.\n this.syncUntabbedChildren();\n\n const id = this.mintId();\n\n this._bar.createBarEntry(id, constraints?.name ?? id, constraints);\n this._contents.push({\n id,\n component: null,\n factory,\n spinner: null,\n state: \"lazy\",\n materializeAnimation: null,\n announceActivation: false,\n });\n\n this.getContainer()?.scheduleLayout();\n\n return true;\n }\n\n /**\n * Registers a tab whose content component is built on first activation.\n *\n * An alias over the primary path, `container.addComponent(factory, { name })`\n * — prefer that form; this one remains for callers that hold the layout\n * manager rather than its container.\n *\n * @param factory - A zero-argument function that produces the content component on first activation.\n * @param name - The visible label for the tab button.\n * @param constraints - Optional layout constraints; forwarded to `container.addComponent` when the component is materialized.\n *\n * @remarks Materialization is asynchronous: on first activation the tab\n * strip selects the new tab immediately, a centred `ProgressSpinner` is\n * mounted into the container, and the factory runs after a two-rAF yield\n * via [`Animation.materialize`](/api/core/namespaces/Animation/functions/materialize)\n * so the spinner reaches the screen before the main-thread build cost is\n * incurred. The materialized component fades in over the spinner.\n *\n * Layout-sizing queries (`getPreferredSize` / `getMinSize` / `getMaxSize`)\n * do not trigger factory invocations — they observe the spinner placeholder\n * until the build completes.\n *\n * @example\n * ```typescript\n * const layout = new Tab();\n * body.setLayoutManager(layout);\n * layout.addLazyTab(() => new HeavyPanel(), \"Heavy\");\n * ```\n */\n addLazyTab(factory: ComponentFactory, name: string, constraints?: LayoutConstraints): void {\n // Copy onto a fresh instance so writing `name` never mutates a\n // caller-owned constraints object. `lazy` is forced on: this method's\n // whole contract is to defer, so a stray `lazy: false` riding in on a\n // shared constraints object must not silently register nothing.\n this.addDeferredComponent(factory, Object.assign(new LayoutConstraints(), constraints, { name, lazy: true }));\n }\n\n /**\n * Catches the tab strip up to any container child that no content entry owns\n * yet — the bare-`Panel` eager path, where a consumer called `addComponent`\n * directly and expects a tab to appear.\n *\n * `Animation.materialize` also injects children (the built lazy panels and\n * the transient spinner), but each of those is referenced by an existing\n * entry's `component`/`spinner`, so the ownership test skips them and they\n * never become phantom UUID-labelled tabs.\n *\n * Called from `doLayout` and again when a deferred child is registered, so\n * tab order tracks call order across interleaved eager and lazy adds.\n */\n private syncUntabbedChildren(): void {\n const container = this.getContainer();\n if (!container) {\n return;\n }\n\n const owned = new Set<Component>();\n\n for (const entry of this._contents) {\n if (entry.component) {\n owned.add(entry.component);\n }\n\n if (entry.spinner) {\n owned.add(entry.spinner);\n }\n }\n\n for (const component of container.getComponents()) {\n if (!owned.has(component)) {\n this.createTab(component);\n }\n }\n }\n\n /**\n * Mounts a spinner into the container, yields two animation frames so it\n * reaches the screen, then runs the entry's factory and fades the built\n * component in over the spinner. Re-entry while a build is in flight is\n * suppressed via the entry's `state` field.\n *\n * An asynchronous factory keeps the entry in `\"building\"` until its promise\n * settles, so the spinner covers the whole wait rather than construction\n * alone. A rejection closes the tab through the shared teardown and emits\n * `\"exception\"`; a promise that settles after the entry is already gone\n * reports nothing and tears nothing down.\n *\n * @param idx - Zero-based index into `this._contents`.\n *\n * @remarks Replaces the previous synchronous `materialize` path. Layout-\n * sizing queries (`getPreferredSize` / `getMinSize` / `getMaxSize`) no\n * longer trigger factory invocations — they observe the spinner placeholder\n * until the build completes.\n */\n private materializeAsync(idx: number): void {\n const entry = this._contents[idx];\n if (!entry || entry.state !== \"lazy\") {\n return;\n }\n\n const factory = entry.factory;\n if (!factory) {\n return;\n }\n\n const container = this.getContainer();\n if (!container) {\n return;\n }\n\n const spinner = createSpinnerWrap();\n entry.spinner = spinner;\n entry.state = \"building\";\n\n entry.materializeAnimation?.cancel();\n entry.materializeAnimation = Animation.materialize({\n host: container,\n factory: () => {\n const result = factory();\n\n // Capture the built component on the entry the instant it\n // exists — before `Animation.materialize` attaches it to the\n // container and schedules the layout that would otherwise see\n // an entry-unowned child and mint a phantom UUID tab for it.\n // `onReady` re-asserts this once the fade completes.\n if (result instanceof Promise) {\n return result.then((component) => {\n entry.component = component;\n\n return component;\n });\n }\n\n entry.component = result;\n\n return result;\n },\n spinnerComponent: spinner,\n // The entry leaves `_contents` when its tab is closed, so a factory\n // that settles after that has nobody left to report to.\n isStale: () => !this._contents.includes(entry),\n onError: (error) => this.failEntry(entry, error),\n onReady: (component) => {\n entry.component = component;\n entry.factory = null;\n entry.spinner = null;\n entry.state = \"ready\";\n\n this.wireComponentAria(entry.id, component);\n container.scheduleLayout();\n this.announcePendingActivation(entry);\n }\n });\n }\n\n /**\n * Settles the `\"activate\"` announcement a selection left owed because the\n * entry had no content to carry at the time. Emitted only while the entry is\n * still the selected one: a build that lands behind a newer selection would\n * otherwise report an active tab that is not the one on screen.\n *\n * @param entry - The freshly materialized content entry.\n */\n private announcePendingActivation(entry: ContentEntry): void {\n if (!entry.announceActivation || !entry.component) {\n return;\n }\n\n entry.announceActivation = false;\n\n if (this._contents[this._selectedTabIndex] === entry) {\n this.emit(\"activate\", entry.component, this._selectedTabIndex);\n }\n }\n\n /**\n * Tears down a deferred tab whose factory rejected, then reports the failure.\n * The spinner has already been removed by the materialize helper, so the\n * entry's reference is cleared first to keep the shared close path from\n * removing it a second time.\n *\n * @param entry - The failed content entry.\n * @param error - The value the factory's promise rejected with.\n */\n private failEntry(entry: ContentEntry, error: unknown): void {\n const label = this._bar.getEntryName(entry.id);\n\n entry.spinner = null;\n entry.factory = null;\n\n this.closeEntry(entry.id);\n this.emit(\"exception\", error, label);\n }\n\n /**\n * Computes the children's combined minSize for the *content area*: the\n * visible child's min size. The strip thickness is deliberately excluded —\n * the content area is already net of the strip, which reserves its own\n * thickness upstream. Used by `doLayout` to inflate the content area when\n * the host opts into scroll.\n *\n * @returns The visible child's min-size; `{ width: 0, height: 0 }` when the\n * container is absent or the visible child reports no min.\n */\n protected computeTotalMinSize(): Size {\n const container = this.getContainer();\n if (!container) {\n return { width: 0, height: 0 };\n }\n\n const visible = this.getVisibleComponent() ?? container.getComponents()[0];\n const min = visible?.getMinSize();\n\n return min ?? { width: 0, height: 0 };\n }\n\n /**\n * Creates tab buttons for new components, hides all but the selected child,\n * positions the strip, and places the visible component.\n *\n * @remarks Tab buttons are created lazily: only components that do not yet have\n * a corresponding entry receive one. The strip occupies a thickness-deep band\n * on the chosen edge; the visible component occupies the remaining space.\n */\n doLayout(): void {\n let container = this.getContainer();\n if (!container) {\n return;\n }\n\n let components = container.getComponents();\n let containerSize = container.getInnerSize();\n let containerInsets = container.getContentInsets();\n\n this.syncUntabbedChildren();\n\n // Apply a deferred setActiveContent now that this pass has created the\n // freshly-added child's tab cell, so a programmatic add can focus it.\n if (this._pendingActiveContent) {\n const pendingIndex = this.indexOfContent(this._pendingActiveContent);\n\n if (pendingIndex >= 0) {\n this._pendingActiveContent = null;\n this.setActiveTabIndex(pendingIndex);\n }\n }\n\n // The initial tab is never explicitly clicked, so its factory has to\n // be kicked off the first time we lay the container out. Subsequent\n // selections route through the strip's \"tabpressed\" event.\n const initialEntry = this._contents[this._selectedTabIndex];\n if (initialEntry && initialEntry.state === \"lazy\") {\n this.materializeAsync(this._selectedTabIndex);\n }\n\n for (const component of components) {\n component.setVisible(false);\n component.getAria().setHidden(true);\n }\n\n let component = this.getVisibleComponent();\n\n if (!component && components.length > 0) {\n component = components[0];\n }\n\n // Prepare the strip for measurement (box orientation, button styles, ARIA)\n // before reading its thickness, then position it. A hidden strip reserves\n // no thickness (content fills the region) and is not placed below.\n this._bar.setVisible(this._barVisible);\n\n if (this._barVisible) {\n this._bar.prepareStrip();\n }\n\n const cs = containerSize ?? { width: 0, height: 0 };\n const baseX = containerInsets.getLeft();\n const baseY = containerInsets.getTop();\n const thickness = this._barVisible ? this._bar.stripThickness() : 0;\n\n // Toolbar + content rectangles per side: the strip occupies a\n // `thickness`-deep band on the chosen edge; the content fills the rest.\n let toolbarX = baseX;\n let toolbarY = baseY;\n let toolbarW = cs.width;\n let toolbarH = thickness;\n let contentX = baseX;\n let contentY = baseY;\n let contentW = cs.width;\n let contentH = cs.height;\n\n switch (this._bar.getSide()) {\n case \"north\":\n contentY = baseY + thickness;\n contentH = cs.height - thickness;\n break;\n\n case \"south\":\n toolbarY = baseY + cs.height - thickness;\n contentH = cs.height - thickness;\n break;\n\n case \"west\":\n toolbarW = thickness;\n toolbarH = cs.height;\n contentX = baseX + thickness;\n contentW = cs.width - thickness;\n break;\n\n case \"east\":\n toolbarX = baseX + cs.width - thickness;\n toolbarW = thickness;\n toolbarH = cs.height;\n contentW = cs.width - thickness;\n break;\n }\n\n // When the bar ignores the parent insets, grow its rect to the\n // container's outer edges and hand it the absorbed parent inset as its\n // own inset (the content-facing edge stays 0), so `layoutChrome` keeps\n // its chrome flush with the content. The content rects are untouched.\n // When false, the bar's insets stay cleared to zero so `layoutChrome`'s\n // per-side offsets are all 0 — the original layout byte-for-byte.\n if (this._barIgnoreParentInsets) {\n const L = containerInsets.getLeft();\n const T = containerInsets.getTop();\n const R = containerInsets.getRight();\n const B = containerInsets.getBottom();\n\n switch (this._bar.getSide()) {\n case \"north\":\n toolbarX = 0;\n toolbarY = 0;\n toolbarW = cs.width + L + R;\n toolbarH = thickness + T;\n this._bar.setInsets(new Insets(T, R, 0, L));\n break;\n\n case \"south\":\n toolbarX = 0;\n toolbarY = baseY + cs.height - thickness;\n toolbarW = cs.width + L + R;\n toolbarH = thickness + B;\n this._bar.setInsets(new Insets(0, R, B, L));\n break;\n\n case \"west\":\n toolbarX = 0;\n toolbarY = 0;\n toolbarW = thickness + L;\n toolbarH = cs.height + T + B;\n this._bar.setInsets(new Insets(T, 0, B, L));\n break;\n\n case \"east\":\n toolbarX = baseX + cs.width - thickness;\n toolbarY = 0;\n toolbarW = thickness + R;\n toolbarH = cs.height + T + B;\n this._bar.setInsets(new Insets(T, R, B, 0));\n break;\n }\n } else {\n this._bar.clearInsets();\n }\n\n // A hidden strip is already hidden and reserves no thickness; skip placing it.\n if (this._barVisible) {\n this._bar.placeStrip(toolbarX, toolbarY, toolbarW, toolbarH);\n }\n\n if (!component) {\n return;\n }\n\n component.setVisible(true);\n component.getAria().setHidden(false);\n\n // Universal scroll: the content area honours the host's overflow flags\n // (Panel.setAutoScroll) independently of the tab strip's own overflow.\n // computeTotalMinSize reports the visible child's min (net of the strip,\n // which reserves its own thickness above), so the inflation targets the\n // content area exactly as the previous inline block did.\n const inflated = this.inflateForOverflow({ width: contentW, height: contentH });\n const contentWidth = inflated.width;\n const contentHeight = inflated.height;\n\n this.placeComponent(\n component,\n contentX,\n contentY,\n contentWidth,\n contentHeight,\n FillType.BOTH\n );\n\n // Fade the newly-visible child in only when the selection actually\n // changed since the last layout AND the entry is fully built — for a\n // lazy tab still mid-build, the spinner placeholder is what's on\n // screen and `Animation.materialize` runs the content fade itself.\n const selectedEntry = this._contents[this._selectedTabIndex];\n const isReady = selectedEntry?.state === \"ready\";\n\n if (isReady && this._lastFadedTabIndex !== this._selectedTabIndex) {\n this._lastFadedTabIndex = this._selectedTabIndex;\n\n const el = component.getElement();\n if (el) {\n this._tabFadeAnimation?.cancel();\n this._tabFadeAnimation = Animation.play(el, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: TAB_FADE_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n }\n }\n\n /**\n * Returns the zero-based index of the currently active tab. Captures the\n * active selection for serialization.\n *\n * @returns The active tab index.\n */\n getActiveTabIndex(): number {\n return this._selectedTabIndex;\n }\n\n /**\n * Returns the content component of the currently active tab, or `null` when\n * the strip is empty or the active tab is a not-yet-materialized lazy entry.\n * Resolves through the active entry's stored component rather than indexing\n * the container (lazy tabs materialize out of order, so the two index spaces\n * do not stay aligned).\n *\n * @returns The active tab's content component, or `null`.\n */\n getActiveContent(): Component | null {\n return this._contents[this._selectedTabIndex]?.component ?? null;\n }\n\n /**\n * Returns the zero-based index of the tab hosting `content`, or `-1` when no\n * tab does. The index space is the one {@link setActiveTabIndex} and\n * {@link getActiveTabIndex} use.\n *\n * @param content - The content component to locate.\n *\n * @returns The tab index, or `-1` when not found.\n */\n indexOfContent(content: Component): number {\n return this._contents.findIndex(entry => entry.component === content);\n }\n\n /**\n * Returns the display label of the currently active tab, or `null` when the\n * strip is empty. A read-only accessor over the bar's active cell, used by a\n * host [`AbstractWindow`](/api/overlay/classes/AbstractWindow) to derive its title from the active tab.\n *\n * @returns The active tab's label, or `null` when there is no active tab.\n */\n getActiveTabLabel(): string | null {\n const id = this._bar.getActiveEntryId();\n\n return id === null ? null : this._bar.getEntryName(id);\n }\n\n /**\n * Activates the tab at the given index programmatically — clamped to the\n * valid range — driving the same selection sync a click does through the\n * strip: the button group's pressed state, the roving tabindex, lazy\n * materialization of the target, and a re-layout. Used by layout restore to\n * reinstate the saved active tab.\n *\n * @param index - Zero-based tab index; clamped to `[0, tabCount - 1]`.\n * @returns This layout manager, for method chaining.\n */\n /**\n * Activates the tab hosting `content`. If its tab cell does not exist yet —\n * a child added but not laid out, whose tab {@link doLayout} creates lazily —\n * the request is deferred and applied on the next layout pass. Lets a\n * programmatic add focus the panel it just added.\n *\n * @param content - The content component whose tab should become active.\n * @returns This layout manager, for method chaining.\n */\n setActiveContent(content: Component): this {\n const index = this.indexOfContent(content);\n\n if (index >= 0) {\n this._pendingActiveContent = null;\n this.setActiveTabIndex(index);\n } else {\n this._pendingActiveContent = content;\n }\n\n return this;\n }\n\n setActiveTabIndex(index: number): this {\n if (this._contents.length === 0) {\n return this;\n }\n\n const clamped = Util.clamp(index, 0, this._contents.length - 1);\n\n // Drive the selection through the strip, which sets the button group,\n // roving focus, and indicator intent, then emits \"tabpressed\" — handled\n // by _onBarTabPressed, which moves the active content index and kicks\n // lazy materialization.\n this._bar.setActiveEntry(this._contents[clamped].id);\n\n return this;\n }\n\n /**\n * Docks a live content component dragged from another strip (or torn off into\n * a window) into this strip as a new, selected tab at `slot`. The content is\n * re-homed with [`moveComponent`](/api/core/classes/Component#movecomponent),\n * which carries its original layout constraints, so {@link createTab}\n * reproduces the tab faithfully (label, closeable, glyph).\n *\n * @param content - The live content component to dock.\n * @param slot - The strip insertion slot the bar computed.\n */\n private dockComponent(content: Component, slot: number): void {\n const container = this.getContainer();\n\n if (!container) {\n return;\n }\n\n container.moveComponent(content, slot);\n this.createTab(content);\n\n // createTab appends the entry; move it to the drop slot so it lands where\n // the insertion bar showed rather than at the end of the strip.\n const appendedIndex = this._contents.length - 1;\n const dest = Util.clamp(slot, 0, appendedIndex);\n\n if (dest !== appendedIndex) {\n const entry = this._contents[appendedIndex];\n\n this._contents.splice(appendedIndex, 1);\n this._contents.splice(dest, 0, entry);\n this._bar.moveBarEntry(entry.id, dest);\n }\n\n this.setActiveTabIndex(dest);\n container.scheduleLayout();\n }\n\n /**\n * Removes a tab while leaving its content component alone — the teardown the\n * close path performs minus the `container.removeComponent` (the content has\n * been moved elsewhere, not destroyed) and minus the `tabclose` emit (the tab\n * is relocated, not closed).\n *\n * @param id - The cell id to remove.\n */\n private removeEntryKeepingContent(id: string): void {\n const idx = this._contents.findIndex(entry => entry.id === id);\n\n if (idx < 0) {\n return;\n }\n\n const wasSelected = this._selectedTabIndex === idx;\n\n // The content is moving to another host; an in-flight materialize is\n // still pointed at this container, so it is abandoned rather than\n // carried across.\n this._contents[idx].materializeAnimation?.cancel();\n\n this._bar.removeBarEntry(id);\n this._contents.splice(idx, 1);\n\n this.selectNextContent(idx, wasSelected);\n this.getContainer()?.scheduleLayout();\n this.syncHostWindowCloseable();\n this.closeHostWindowIfEmpty();\n\n if (this._contents.length === 0) {\n this.emit(\"empty\");\n }\n }\n\n /**\n * Tears a tab off the strip into a floating [`Window`](/api/overlay/classes/Window) hosting its live\n * content, opened at the release point. The content is re-parented with\n * [`moveComponent`](/api/core/classes/Component#movecomponent) — not closed —\n * so its state survives, and the now-empty strip cell is removed without\n * emitting `tabclose`.\n *\n * @param id - The cell id being torn off.\n * @param content - The live content component to host.\n * @param clientX - Viewport X of the release point.\n * @param clientY - Viewport Y of the release point.\n * @param forceBare - When `true`, a bare window is opened regardless of `detachWindowMode` (Shift held during the drag).\n */\n private detachTabToWindow(id: string, content: Component, clientX: number, clientY: number, forceBare: boolean): void {\n // Holding Shift forces a bare window regardless of mode.\n const useStrip = !forceBare && this._detachWindowMode === \"strip\";\n\n // The window inherits the tab's closeable state: a non-closeable tab's\n // window has no close button, so the user can only re-dock it (never\n // destroy the content via the title-bar X), honouring the tab's contract.\n // A strip-mode tear-off is a headerless TabWindow whose interior IS a\n // Tab; a bare tear-off is an ordinary header Window.\n const win: AbstractWindow = useStrip\n ? new TabWindow({ closeable: this._bar.isEntryCloseable(id) })\n : new Window(this._bar.getEntryName(id), { closeable: this._bar.isEntryCloseable(id) });\n\n win.setX(clientX);\n win.setY(clientY);\n win.setSize({ width: DETACH_WINDOW_WIDTH, height: DETACH_WINDOW_HEIGHT });\n\n // Keep the window on-screen when released past the viewport edge — size is\n // set first since the clamp depends on it.\n win.clampPositionToViewport();\n\n // Position/size set before show() so the window does not flash at its\n // default spot first.\n if (useStrip) {\n // The TabWindow builds the bar entry and reflects title + closeable;\n // it already closes itself when its last tab leaves.\n this.fillWindowWithStrip(win as TabWindow, content);\n } else {\n // Bare: the live content fills the window body (Border CENTER).\n win.moveComponent(content);\n win.show();\n }\n\n this.removeEntryKeepingContent(id);\n\n // Activate the torn-off window last. Removing the source entry re-selects\n // a neighbour in the source strip and leaves the source window raised, but\n // the tear-off's result is the new window, so it should end up focused and\n // frontmost.\n win.bringToFront();\n\n // Announce the tear-off so a tree owner can fold the new window into its\n // model. `removeEntryKeepingContent` already fired `\"empty\"` when this was\n // the strip's last tab, but a tear-off that leaves siblings behind fires\n // nothing else — this is the signal that covers that case.\n this.emit(\"detach\", win);\n }\n\n /**\n * Populates a headerless [`TabWindow`](/api/overlay/classes/TabWindow) with `content` as its single tab\n * and shows it — the `\"strip\"` tear-off mode. The `TabWindow` *is* the strip\n * (its interior is a {@link Tab}), so there is no inner nesting; it also owns\n * the close-when-empty wiring, so the float disappears once its tab leaves.\n *\n * @param win - The freshly-constructed (not yet shown) host window.\n * @param content - The live content to host in the strip's single tab.\n */\n private fillWindowWithStrip(win: TabWindow, content: Component): void {\n win.createTab(content);\n win.show();\n }\n\n /**\n * The [`Window`](/api/overlay/classes/Window) this strip lives in, but only for the auto-created\n * one-tab strip a `\"strip\"`-mode tear-off builds (which sets\n * `_closeHostWindowWhenEmpty`). A general strip that merely sits inside a\n * window returns `null`, so the host-window helpers are cheap no-ops for it.\n *\n * @returns The owning tear-off window, or `null`.\n */\n private hostWindow(): AbstractWindow | null {\n if (!this._closeHostWindowWhenEmpty) {\n return null;\n }\n\n let ancestor: Component | null = this.getContainer();\n\n while (ancestor && !(ancestor instanceof AbstractWindow)) {\n ancestor = ancestor.getParentComponent();\n }\n\n return ancestor instanceof AbstractWindow ? ancestor : null;\n }\n\n /**\n * The nearest [`AbstractWindow`](/api/overlay/classes/AbstractWindow) ancestor\n * of this strip, or `null` when it sits directly in the document. Unlike\n * {@link Tab.hostWindow} — which resolves only the auto-created tear-off window\n * a `\"strip\"` detach builds — this walks the full container chain, so it also\n * finds the ordinary `Window` a tiled dock lives in. Used to raise whichever\n * window a cross-window dock or dock-hover lands in, mirroring the dock\n * region's host-window raise (a no-op for an in-document strip).\n *\n * @returns The owning window, or `null`.\n */\n private ancestorWindow(): AbstractWindow | null {\n for (let node: Component | null = this.getContainer(); node; node = node.getParentComponent()) {\n if (node instanceof AbstractWindow) {\n return node;\n }\n }\n\n return null;\n }\n\n /**\n * Closes this strip's host window once it has emptied (its last tab was\n * dragged out or closed).\n */\n private closeHostWindowIfEmpty(): void {\n if (this._contents.length > 0) {\n return;\n }\n\n this.hostWindow()?.requestClose();\n }\n\n /**\n * Keeps the host window's close button in step with the strip's contents: it\n * is closeable only while *every* tab is closeable, so a non-closeable tab\n * docked into the window disables the title-bar close (the window X would\n * otherwise destroy that tab's content). A no-op outside a tear-off window.\n */\n private syncHostWindowCloseable(): void {\n const win = this.hostWindow();\n\n if (!win) {\n return;\n }\n\n win.setCloseable(this._bar.getEntryIds().every(id => this._bar.isEntryCloseable(id)));\n }\n\n /**\n * Re-selects after the tab at `closedIndex` has been spliced out. When the\n * closed tab was the active one, falls back to its left neighbour (driving the\n * strip's visual selection); otherwise the active tab stays selected and only\n * its stored index shifts left when the removed tab sat to its left.\n *\n * @param closedIndex - The pre-splice index of the removed tab.\n * @param closedWasSelected - Whether the removed tab was the active one.\n */\n private selectNextContent(closedIndex: number, closedWasSelected: boolean): void {\n const count = this._contents.length;\n\n if (count === 0) {\n this._selectedTabIndex = 0;\n\n return;\n }\n\n if (!closedWasSelected) {\n // The active tab survives; its button keeps its selected state and\n // only its index moves when the closed tab was to its left.\n if (this._selectedTabIndex > closedIndex) {\n this._selectedTabIndex -= 1;\n }\n\n return;\n }\n\n const newIndex = closedIndex > 0 ? closedIndex - 1 : 0;\n this._selectedTabIndex = newIndex;\n\n this._bar.setActiveVisual(this._contents[newIndex].id);\n }\n\n /**\n * Registers a listener for the `\"tabclose\"` event, which fires after a tab\n * is closed, receiving the content component that was removed.\n *\n * @param event - The `\"tabclose\"` event.\n * @param listener - The callback to invoke when a tab is closed.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"tabclose\", listener: (component: Component) => void): this;\n /**\n * Registers a listener for the `\"empty\"` event, which fires after the strip\n * loses its last tab by any path (close, tear-off, or re-dock). It carries\n * no payload — a passive announcement; the subscriber decides what to do.\n *\n * @param event - The `\"empty\"` event.\n * @param listener - The zero-argument callback to invoke when the strip empties.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"empty\", listener: () => void): this;\n /**\n * Registers a listener for the `\"detach\"` event, which fires after a tab is\n * torn off into a new floating window, carrying that window. Unlike `\"empty\"`,\n * it fires whether or not the tear-off left the source strip empty — so a\n * tree owner such as [`Dock`](/api/overlay/classes/Dock) can react to *every*\n * tear-off, not just the ones that drain the strip.\n *\n * @param event - The `\"detach\"` event.\n * @param listener - Invoked with the torn-off window.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"detach\", listener: (window: AbstractWindow) => void): this;\n /**\n * Registers a listener for the `\"select\"` event, which fires the moment a\n * tab is picked by a click or {@link setActiveTabIndex}, carrying its\n * zero-based index and label. It reports selection *intent*: it fires before\n * a deferred tab's factory runs, so it carries no content component and\n * never waits on a build — which is what makes it the right signal for a\n * router, whose URL should name the destination while the spinner is still\n * up. Wait for {@link on | `\"activate\"`} instead when you need the live\n * content component.\n *\n * Like `\"activate\"`, it does not fire on the post-close re-selection of a\n * surviving sibling (that re-selection is visual-only).\n *\n * @param event - The `\"select\"` event.\n * @param listener - Invoked with the selected tab's index and label.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"select\", listener: (index: number, label: string) => void): this;\n /**\n * Registers a listener for the `\"activate\"` event, which fires when the\n * active tab changes via a click or {@link setActiveTabIndex}, carrying the\n * now-active content component and its zero-based index. It does *not* fire\n * on the post-close re-selection of a surviving sibling (that re-selection\n * is visual-only), so a tree owner such as\n * [`Dock`](/api/overlay/classes/Dock) can treat it as a genuine\n * active-tab-change signal.\n *\n * Selecting a lazy tab for the first time defers the event until its factory\n * has produced the content — the event always carries live content, never\n * the spinner placeholder. A build that finishes after the selection has\n * moved on is dropped rather than announced, so the reported tab is always\n * the one on screen. Listen for {@link on | `\"select\"`} instead when you\n * want the selection itself, unconditionally and without that delay.\n *\n * @param event - The `\"activate\"` event.\n * @param listener - Invoked with the now-active content and its index.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"activate\", listener: (content: Component, index: number) => void): this;\n /**\n * Registers a listener for the `\"dock\"` event, which fires after a foreign\n * tab is dropped onto this strip, carrying the docked content. It is the\n * structural counterpart of `\"detach\"` (a tab arrived by drop, vs. a tab\n * left by tear-off), so a tree owner such as\n * [`Dock`](/api/overlay/classes/Dock) can react to a tab-bar merge the same\n * way it reacts to a tear-off.\n *\n * @param event - The `\"dock\"` event.\n * @param listener - Invoked with the docked content.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"dock\", listener: (content: Component) => void): this;\n /**\n * Registers a listener for the `\"exception\"` event, which fires when a\n * deferred tab's asynchronous factory rejected. The tab has already been\n * closed and has left the strip by the time the listener runs, so a listener\n * inspecting the strip sees the final state.\n *\n * No error UI is shown — presenting the failure is the consumer's job.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - Invoked with the value the factory's promise rejected\n * with, and the label the failed tab carried.\n *\n * @returns This tab layout, for method chaining.\n */\n on(event: \"exception\", listener: (error: unknown, label: string) => void): this;\n on(event: TabEvent, 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 tab layout, for method chaining.\n */\n off(event: TabEvent, 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 - Forwarded to each listener.\n */\n protected emit(event: \"tabclose\", component: Component): void;\n protected emit(event: \"empty\"): void;\n protected emit(event: \"detach\", window: AbstractWindow): void;\n protected emit(event: \"select\", index: number, label: string): void;\n protected emit(event: \"activate\", content: Component, index: number): void;\n protected emit(event: \"dock\", content: Component): void;\n protected emit(event: \"exception\", error: unknown, label: string): void;\n protected emit(event: TabEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n}\n\nconst TabCallable = callable(Tab);\ntype TabCallable = Tab;\nexport {\n Tab as _Tab,\n TabCallable as Tab\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Tab } from \"~/layout/Tab.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport {\n createWindowControlButton,\n createWindowLeadGlyphButton,\n setWindowControlsActive,\n} from \"~/overlay/windowControls.js\";\nimport {\n AbstractWindow,\n WindowOptions,\n WindowState,\n} from \"~/overlay/AbstractWindow.js\";\n\n// Height (px) of the window's tab-bar chrome, seeded into the generic\n// viewport-clamp / dock-rect / minimized-stack geometry via `chromeHeight()`.\n// Mirrors the `TabBar` default strip thickness (`STRIP_THICKNESS = 30`): the\n// bar's real laid-out height is not exposed through `Tab`'s public surface\n// (the strip is the manager's private `_bar`), so this fixed analog stands in\n// for `Window`'s `_header.getHeight()`. The `|| 26` floor the call sites apply\n// is moot once this constant is returned.\nconst TAB_CHROME_HEIGHT_PX: number = 30;\n\n// Min content width (px) seeded into the default min size via\n// `minContentWidthSeed()`. `TabBar` exposes no min-content-width accessor (the\n// header analog `_header.getMinContentWidth()` has no public bar counterpart),\n// so this documented constant reserves room for the three trailing control\n// tools (minimize / maximize / close) plus a narrow tab. It mirrors the role\n// of `Window`'s header min width without reaching into the private bar.\nconst TAB_MIN_CONTENT_WIDTH_PX: number = 120;\n\n/**\n * A floating, resizable, draggable window whose interior *is* a\n * {@link Tab} layout — no separate title-bar header. The tab strip serves as\n * both the window's content switcher and its chrome: the active tab's label is\n * the window title, the strip's blank area is the move handle, and three\n * chromeless control buttons (minimize / maximize / close) are added to the\n * strip as trailing tools.\n *\n * Extends {@link AbstractWindow}, which owns the header-agnostic window\n * machinery (resize borders, move flow, three-state lifecycle, z-order,\n * show/hide). `TabWindow` implements the base's hooks via its `Tab`:\n *\n * - the move gesture is the strip's empty-area press\n * ({@link Tab.installBarMoveTrigger});\n * - closeable / minimizable / maximizable reflect onto the control tools;\n * - the title reads the active tab's label live;\n * - content is added as a window child *and* a tab entry.\n *\n * Closeable is **pushed** by the owning `Tab` through\n * `Tab.syncHostWindowCloseable` → {@link AbstractWindow.setCloseable} →\n * `reflectCloseable`, so there is no tab-event subscription. The title is\n * never cached — {@link getTitle} re-reads the active label on demand.\n *\n * @category Core\n */\nclass TabWindow extends AbstractWindow {\n\n private _tab: Tab;\n private _closeTool: Button;\n private _minTool: Button;\n private _maxTool: Button;\n private _leadGlyphBtn: Button;\n\n /**\n * Builds a headerless tab window: a reorderable {@link Tab} as the window's\n * own layout manager (flagged to close the window when its last tab\n * leaves), three chromeless control tools added to the strip, then the\n * base's late chrome setup via `initChrome`.\n *\n * @param options - Optional window configuration.\n */\n constructor(options?: WindowOptions) {\n super(options);\n\n this._tab = new Tab({ reorderable: true, widthMode: \"fixed\", textAlign: \"start\", compact: true, barIgnoreParentInsets: true });\n\n // This strip is the window's body, so it closes the window when emptied\n // (the last tab dragged out or closed) — the same contract the legacy\n // auto-created tear-off strip carries.\n this._tab.setCloseHostWindowWhenEmpty(true);\n this.setLayoutManager(this._tab);\n\n // Chromeless control tools wired to the inherited public window-state\n // actions, built from the shared window-control factory so the trailing\n // controls match an ordinary Window's header controls exactly (same\n // `window.control` theme tokens, same box). The bar later re-sets their\n // insets to the compact tool inset (see `Tab`/`TabBar`), so the factory's\n // 2px insets are the standalone default, not the rendered box.\n this._minTool = createWindowControlButton(\"window-minimize\");\n this._maxTool = createWindowControlButton(\"window-maximize\");\n this._closeTool = createWindowControlButton(\"xmark\");\n\n this._minTool.on(\"action\", () => this.toggleMinimize());\n this._maxTool.on(\"action\", () => this.toggleMaximize());\n this._closeTool.on(\"action\", () => this.requestClose());\n\n this._tab.addTool(this._minTool);\n this._tab.addTool(this._maxTool);\n this._tab.addTool(this._closeTool);\n\n // Leading window icon at the start of the bar, mirroring the title glyph an\n // ordinary Window shows in its header: an explicit `glyph` option wins, else\n // the `window-maximize` default. Built from the shared decorative leading-\n // glyph factory (transparent, pointer-through, control-peer box) so it\n // matches the header's title icon.\n this._leadGlyphBtn = createWindowLeadGlyphButton(this._options.glyph ?? \"window-maximize\");\n this._tab.setBarLeadingWidget(this._leadGlyphBtn);\n\n this.initChrome(options);\n }\n\n /**\n * Adds a content component as a new tab and shows it. Public entry point so\n * a tear-off caller can populate the window with `win.createTab(content)`.\n *\n * @param content - The content component to host in a new tab.\n *\n * @returns This window, for method chaining.\n */\n createTab(content: Component): this {\n this.addContent(content);\n\n return this;\n }\n\n /**\n * Sets the leading window glyph by swapping the glyph on the existing\n * decorative leading control (a peer of the trailing min/max/close controls),\n * mirroring {@link Window.setGlyph}.\n *\n * @param glyph - The glyph name to show at the start of the bar.\n *\n * @returns This window, for method chaining.\n */\n setGlyph(glyph: string): this {\n this._leadGlyphBtn.setGlyph(glyph);\n\n return this;\n }\n\n // ----- AbstractWindow hook implementations -----\n\n /**\n * Wires the window-move gesture to the strip's empty-area press via\n * {@link Tab.installBarMoveTrigger}, routing it to\n * {@link AbstractWindow.startMoveFrom}.\n */\n protected wireMoveTrigger(): void {\n this._tab.installBarMoveTrigger(\n (e: MouseEvent) => this.startMoveFrom(e),\n () => this.onBarDoubleClick(),\n );\n }\n\n /**\n * Handles a double-click on the strip's empty area, mirroring the header\n * double-click a {@link Window} provides: restores a minimized window to its\n * pre-minimize state, otherwise toggles maximize when the window is\n * maximizable. Presses on the tabs or control tools never reach here (the\n * move-trigger veto excludes them).\n */\n private onBarDoubleClick(): void {\n if (this.getWindowState() === \"minimized\") {\n this.setWindowState(this._preMinimizeState);\n\n return;\n }\n\n if (!this.isMaximizable()) {\n return;\n }\n\n this.toggleMaximize();\n }\n\n /**\n * Reflects the closeable state onto the close control tool by enabling or\n * disabling it. Driven by the owning `Tab`'s closeable push.\n *\n * @param value - True to enable the close tool.\n */\n protected reflectCloseable(value: boolean): void {\n this._closeTool.setEnabled(value);\n }\n\n /**\n * Reflects the minimizable state onto the minimize control tool's visibility.\n *\n * @param value - True to show the minimize tool.\n */\n protected reflectMinimizable(value: boolean): void {\n this._minTool.setVisible(value);\n }\n\n /**\n * Reflects the maximizable state onto the maximize control tool's visibility.\n *\n * @param value - True to show the maximize tool.\n */\n protected reflectMaximizable(value: boolean): void {\n this._maxTool.setVisible(value);\n }\n\n /**\n * No-op: a `TabWindow` has no maximize/restore glyph to swap. The maximize\n * control tool keeps a single glyph; the window state is conveyed by the\n * window geometry, not a glyph change.\n *\n * @param _state - The window state being entered (unused).\n */\n protected reflectMaximizeState(_state: WindowState): void {\n // Intentionally empty — see method JSDoc.\n }\n\n /**\n * Swaps the tab bar between the focused themed toolbar fill and the flat\n * unfocused gutter fill, then neutralizes (or restores) the control buttons,\n * so a blurred `TabWindow` flattens like a standard {@link Window} header.\n * Both fills are passed as CSS-var strings so a live theme change tracks.\n *\n * @param active - True when this window is the active layer.\n */\n protected paintActive(active: boolean): void {\n this._tab.setBarBackgroundColor(\n active ? \"var(--ts-ui-tab-toolbar-bg, #eee)\" : \"var(--ts-ui-gutter-bg, rgb(200, 200, 200))\"\n );\n\n this.setControlsActive(active);\n }\n\n /**\n * Flattens (or restores) the three control buttons via the shared\n * {@link setWindowControlsActive} helper, so a blurred `TabWindow` and a\n * blurred header `Window` neutralize their controls identically.\n *\n * @param active - True to restore the control fill, false to flatten it.\n */\n private setControlsActive(active: boolean): void {\n setWindowControlsActive([this._minTool, this._maxTool, this._closeTool], active);\n }\n\n /**\n * Returns the active tab's label as the window title, or `\"\"` when the strip\n * is empty. Read live — never cached.\n *\n * @returns The active tab label, or `\"\"`.\n */\n getTitle(): string {\n return this._tab.getActiveTabLabel() ?? \"\";\n }\n\n /**\n * Returns the min-content-width seed for the default min size — a documented\n * constant ({@link TAB_MIN_CONTENT_WIDTH_PX}) reserving room for the three\n * control tools and a narrow tab, since the bar exposes no min-width\n * accessor.\n *\n * @returns The seed width in pixels.\n */\n protected minContentWidthSeed(): number {\n return TAB_MIN_CONTENT_WIDTH_PX;\n }\n\n /**\n * Returns the tab-bar chrome height — the documented\n * {@link TAB_CHROME_HEIGHT_PX} analog of the bar's strip thickness, since\n * the laid-out bar height is not exposed through `Tab`'s public surface.\n *\n * @returns The chrome height in pixels.\n */\n protected chromeHeight(): number {\n return TAB_CHROME_HEIGHT_PX;\n }\n\n /**\n * Adds content as both a layout/serialization child of the window and a tab\n * entry — the `moveComponent`-then-`createTab` order the strip path uses, so\n * the content lays out and is discoverable by serialization.\n *\n * @param content - The content component to add.\n */\n protected addContent(content: Component): void {\n this.moveComponent(content);\n this._tab.createTab(content);\n }\n\n /**\n * Reports whether `child` is chrome. Always `false`: a `TabWindow`'s bar\n * belongs to the `Tab` *layout manager*, not to any child component, so\n * every child is content.\n *\n * @param _child - The child component to classify (unused).\n * @returns Always `false`.\n */\n isChromeComponent(_child: Component): boolean {\n return false;\n }\n}\n\nconst TabWindowCallable = callable(TabWindow);\ntype TabWindowCallable = TabWindow;\nexport {\n TabWindow as _TabWindow,\n TabWindowCallable as TabWindow\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\n/**\n * How a persisted {@link LayoutSize} entry is measured.\n *\n * @category Layouts\n */\nexport type LayoutSizeUnit = \"px\" | \"ratio\";\n\n/**\n * One pane's or section's persisted size. `px` entries are absolute and\n * restored verbatim; `ratio` entries are a share of the space left after the\n * px entries, and sum to ~1.0 across the ratio entries of one array.\n *\n * @category Layouts\n */\nexport interface LayoutSize {\n unit: LayoutSizeUnit;\n value: number;\n}\n\n/**\n * Builds a capture from raw stored values: `px` entries are reported\n * verbatim, `ratio` entries are normalised over the ratio subset only — a\n * `px` entry never enters the ratio denominator, so a resize-pinned pane's\n * absolute size cannot skew its siblings' reported shares.\n *\n * @param units - The live unit for each index, in child order.\n * @param stored - The raw stored value for each index, in the same order.\n * @returns One {@link LayoutSize} per index: `px` entries hold the\n * sanitised stored value; `ratio` entries hold that pane's share of the\n * ratio subset's stored total, or an equal split when the subset's total\n * is not positive.\n */\nexport function toLayoutSizes(units: LayoutSizeUnit[], stored: number[]): LayoutSize[] {\n const clean = (value: number): number => (Number.isFinite(value) && value > 0 ? value : 0);\n const ratioCount = units.filter(unit => unit === \"ratio\").length;\n\n let ratioTotal = 0;\n\n for (let idx = 0; idx < units.length; idx += 1) {\n if (units[idx] === \"ratio\") {\n ratioTotal += clean(stored[idx]);\n }\n }\n\n return units.map((unit, idx) => {\n if (unit === \"px\") {\n return { unit, value: clean(stored[idx]) };\n }\n\n // Equal split among the ratio entries when none carries a stored size —\n // the same fallback `normalizeRatios` applies to a whole-set capture.\n return { unit, value: ratioTotal > 0 ? clean(stored[idx]) / ratioTotal : 1 / ratioCount };\n });\n}\n\n/**\n * Converts a captured array back to stored values against a main-axis\n * budget: `px` entries are written verbatim; `ratio` entries are seeded\n * against the room the `px` entries leave (`budget − Σpx`), so a manager's\n * rescale pass lands on scale 1 and the restored geometry renders as saved.\n *\n * @param sizes - The persisted array to restore.\n * @param budget - The live main-axis budget to seed the ratio entries\n * against; `0` (or a budget the `px` entries alone overrun) falls back to\n * a unit base, which the manager's own scale-invariant rescale reconciles\n * on the next layout.\n * @returns One stored value per entry, in the same order as `sizes`.\n */\nexport function fromLayoutSizes(sizes: LayoutSize[], budget: number): number[] {\n let pxTotal = 0;\n let ratioTotal = 0;\n let ratioCount = 0;\n\n for (const size of sizes) {\n if (size.unit === \"px\") {\n pxTotal += size.value;\n } else {\n ratioTotal += size.value;\n ratioCount += 1;\n }\n }\n\n // Seed the weighted entries against what the px entries leave, so both\n // managers' rescale passes land on scale 1 and the restore renders as saved.\n // Fall back to a unit base when the container is unsized or the px entries\n // alone overrun it: both managers are scale-invariant over the weighted set,\n // so a unit base lands on the same rendering one pass later.\n const room = budget > 0 ? Math.max(0, budget - pxTotal) : 0;\n const base = room > 0 ? room : 1;\n\n return sizes.map(size => {\n if (size.unit === \"px\") {\n return size.value;\n }\n\n return ratioTotal > 0 ? (size.value / ratioTotal) * base : base / ratioCount;\n });\n}\n\n/**\n * Whether a persisted array is safe to restore against the live per-index\n * units: same length, non-empty, every entry's unit matches the live unit\n * at that index, every value is finite and non-negative, and at least one\n * value is positive. A stale array (wrong length, or a unit that no longer\n * matches a pane's weight) fails whole — the caller discards it entirely\n * rather than repairing individual entries.\n *\n * @param sizes - The persisted array to validate.\n * @param units - The live unit for each index, in child order.\n * @returns True when `sizes` is safe to restore as-is.\n */\nexport function isRestorableSizes(sizes: LayoutSize[], units: LayoutSizeUnit[]): boolean {\n if (units.length === 0 || sizes.length !== units.length) {\n return false;\n }\n\n const valid = sizes.every((size, idx) =>\n size != null\n && size.unit === units[idx]\n && Number.isFinite(size.value)\n && size.value >= 0);\n\n return valid && sizes.some(size => size.value > 0);\n}\n\n/**\n * Normalises `count` relative weights to ratios summing to 1.0. A\n * non-finite or non-positive weight is treated as `0`; when no weight is\n * positive, every index gets an equal share instead of dividing by a\n * zero sum.\n *\n * @param values - The relative weights, in index order.\n * @param count - The number of entries to normalise; `values` shorter than\n * this is padded with `0`.\n * @returns `count` ratios summing to ~1.0, or `[]` when `count` is `0`.\n */\nexport function normalizeRatios(values: number[], count: number): number[] {\n const weights: number[] = [];\n\n for (let idx = 0; idx < count; idx += 1) {\n const value = values[idx];\n\n weights.push(Number.isFinite(value) && value > 0 ? value : 0);\n }\n\n const sum = weights.reduce((total, weight) => total + weight, 0);\n\n return sum > 0 ? weights.map(weight => weight / sum) : weights.map(() => 1 / count);\n}\n"],"mappings":"khDAkDA,IAAM,YAAN,KAAkB,CAEd,QAA6C,CAAsC,EAEnF,SAA2B,MAAQ,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAG,EAAE,EACzE,eAAkC,GAClC,gBAAiD,KACjD,WAAoD,IAAI,EAQxD,YAAY,EAA8B,CAKtC,GAJI,GAAS,gBAAkB,IAAA,KAAW,KAAK,eAAiB,EAAQ,eAEpE,GAAS,UAAY,IAAA,IAAW,KAAK,WAAW,EAAQ,OAAO,EAE/D,GAAS,YAAc,IAAA,GAAW,CAClC,IAAM,EAAY,EAAQ,UAE1B,IAAK,IAAM,KAAS,OAAO,KAAK,CAAS,EAAoC,CACzE,IAAM,EAAW,EAAU,GAEvB,IAAa,IAAA,IACb,KAAK,GAAG,EAAO,CAAQ,CAE/B,CACJ,CACJ,CAaA,mBAA2B,EAAmD,CACrE,EAAgB,WAAW,EAS5B,KAAK,QAAQ,QAAS,GAAW,CACzB,IAAW,GACX,EAAO,YAAY,EAAK,CAEhC,CAAC,EARI,KAAK,gBACN,EAAgB,YAAY,EAAI,EAUxC,KAAK,KAAK,YAAa,CAAe,CAC1C,CAWA,iBAAiB,EAAsB,CAGnC,MAFA,MAAK,eAAiB,EAEf,IACX,CAcA,GAAG,EAAyB,EAA0B,CAGlD,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAWA,IAAI,EAAyB,EAA0B,CAGnD,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAUA,KAAe,EAAyB,GAAG,EAA0B,CACjE,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CAQA,YAA+B,CAC3B,OAAO,KAAK,QAAQ,MAAM,CAC9B,CAaA,WAAW,GAAG,EAAsF,CAChG,IAAK,IAAM,KAAS,EAAS,CACzB,IAAM,EAAQ,MAAM,QAAQ,CAAK,EAAI,EAAQ,CAAC,CAAK,EAEnD,IAAK,IAAM,KAAU,EACjB,KAAK,UAAU,CAAM,CAE7B,CAEA,OAAO,IACX,CAWA,UAAU,EAA0C,CAehD,OAdA,KAAK,QAAQ,KAAK,CAAM,EAExB,EAAO,GAAG,aAAgB,CACtB,KAAK,mBAAmB,CAAM,CAClC,CAAC,EAEG,aAAkB,GAClB,EAAO,aAAa,KAAK,QAAQ,EAGjC,KAAK,kBAAoB,MACzB,KAAK,gBAAgB,IAAI,CAAM,EAG5B,IACX,CAOA,aAAa,EAA0C,CACnD,IAAI,EAAM,KAAK,QAAQ,QAAQ,CAAM,EAYrC,OAVI,EAAM,EACC,MAGX,KAAK,QAAQ,OAAO,EAAK,CAAC,EAEtB,KAAK,kBAAoB,MACzB,KAAK,gBAAgB,OAAO,CAAM,EAG/B,KACX,CAUA,aAAa,EAA4B,CACrC,KAAK,gBAAkB,IAAI,EAE3B,IAAK,IAAM,KAAU,KAAK,QACtB,KAAK,gBAAgB,IAAI,CAAM,EAanC,OAVA,EAAM,mBAAmB,EAAW,UAAY,GAAqB,CAC7D,EAAE,MAAQ,cAAgB,EAAE,MAAQ,aACpC,EAAE,eAAe,EACjB,KAAK,gBAAiB,SAAS,IACxB,EAAE,MAAQ,aAAe,EAAE,MAAQ,aAC1C,EAAE,eAAe,EACjB,KAAK,gBAAiB,SAAS,EAEvC,CAAC,EAEM,IACX,CACJ,EAEM,EAAsB,EAAS,WAAW,ECrQpC,EAAL,SAAA,EAAA,OACH,GAAA,EAAA,MAAA,GAAA,QACA,EAAA,EAAA,MAAA,GAAA,QACA,EAAA,EAAA,KAAA,GAAA,OACA,EAAA,EAAA,KAAA,GAAA,OACA,EAAA,EAAA,UAAA,GAAA,YACA,EAAA,EAAA,UAAA,GAAA,YACA,EAAA,EAAA,UAAA,GAAA,YACA,EAAA,EAAA,UAAA,GAAA,aACJ,EAAA,CAAA,CAAA,EAwBM,EAAoB,cAMpB,GAA4D,CAC9D,IAAK,KACT,EAWM,aAAN,cAA2B,CAA+B,CAEtD,WAAQ,EACR,WAAqD,IAAI,EACzD,mBACA,kBACA,kBACA,YAA+B,GAO/B,IAAY,qBAAiC,CACzC,MAAO,MAAK,uBAAyB,KAAK,gBAAgB,cAAuB,CACrF,CAEA,YAAY,EAAsB,EAA+B,CAC7D,MAAM,EAAS,EAA2B,EAEtC,IACA,KAAK,WAAa,GAGtB,KAAK,mBAAqB,KAAK,YAAY,KAAK,IAAI,EACpD,KAAK,kBAAoB,KAAK,WAAW,KAAK,IAAI,EAClD,KAAK,kBAAoB,KAAK,cAAc,KAAK,IAAI,EAErD,EAAM,YAAY,KAAM,YAAa,KAAK,kBAAkB,EAE5D,KAAK,eAAe,GAAS,SAAS,EAItC,KAAK,oBAAoB,IAAI,YAAa,kEAAkE,CAChH,CAOA,cAAe,CACX,OAAO,KAAK,UAChB,CAOA,aAAa,EAA6B,CAOtC,MANA,CACI,IAAA,EAGJ,KAAK,WAAa,EAEX,IACX,CAYA,GAAG,EAA2B,EAA0B,CAGpD,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAWA,IAAI,EAA0B,EAA0B,CAGpD,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAUA,KAAe,EAA0B,GAAG,EAA0B,CAClE,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CASA,cAAsB,EAAqC,CAGvD,OAFA,KAAK,KAAK,OAAQ,KAAM,CAAC,EAElB,EACX,CAOA,cAAwB,CACpB,OAAO,KAAK,WAChB,CAWA,cAAc,EAAsB,CAChC,GAAI,KAAK,cAAgB,EACrB,OAAO,KAGX,KAAK,YAAc,EAEnB,IAAM,EAAU,KAAK,WAAW,EAKhC,OAJI,GACA,EAAI,KAAK,MAAM,EAAS,CAAE,YAAa,EAAG,GAAoB,CAAM,CAAE,CAAC,EAGpE,IACX,CAKA,aAAc,CACV,EAAM,oBAAoB,KAAM,UAAW,KAAK,iBAAiB,EACjE,EAAM,oBAAoB,KAAM,WAAY,KAAK,iBAAiB,EAClE,EAAM,oBAAoB,KAAM,cAAe,KAAK,iBAAiB,EACrE,EAAM,oBAAoB,KAAM,YAAa,KAAK,iBAAiB,EACnE,EAAM,oBAAoB,KAAM,YAAa,KAAK,iBAAiB,EAInE,EAAiB,KAAK,WAAW,GAAK,SAAS,CACnD,CAOA,YAAmC,CAa/B,OAZA,EAAM,uBAAuB,KAAM,UAAW,KAAK,iBAAiB,EACpE,EAAM,uBAAuB,KAAM,WAAY,KAAK,iBAAiB,EACrE,EAAM,uBAAuB,KAAM,cAAe,KAAK,iBAAiB,EACxE,EAAM,uBAAuB,KAAM,YAAa,KAAK,iBAAiB,EACtE,EAAM,uBAAuB,KAAM,YAAa,KAAK,iBAAiB,EAEtE,GAAe,EAIf,KAAK,cAAc,EAAK,EAEjB,EACX,CASA,YAAyC,CACrC,OAAQ,KAAK,WAAb,CACI,IAAA,GACA,IAAA,GACI,MAAO,YACX,IAAA,GACA,IAAA,GACI,MAAO,YACX,IAAA,GACA,IAAA,GACI,MAAO,cACX,IAAA,GACA,IAAA,GACI,MAAO,aACf,CAGJ,CAOA,QAAS,CACL,IAAI,EAAU,MAAM,OAAO,EAErB,EAAS,KAAK,WAAW,EAS/B,OARI,GACA,KAAK,UAAU,CAAM,EAGrB,KAAK,aACL,EAAI,KAAK,MAAM,EAAS,CAAE,SAAU,CAAC,CAAiB,CAAE,CAAC,EAGtD,CACX,CAEJ,EAEM,EAAuB,EAAS,YAAY,ECzS5C,EAAW,IAAI,QAUrB,SAAS,uBAAuB,EAAqB,CAC7C,EAAa,CAAC,GACd,EAAE,eAAe,CAEzB,CAkBA,SAAgB,UAAU,EAA4B,CAC9C,EAAS,IAAI,CAAS,IAI1B,EAAS,IAAI,CAAS,EACtB,EAAM,mBAAmB,EAAW,QAAS,uBAAwB,CAAE,QAAS,EAAM,CAAC,EAC3F,CAQA,SAAgB,YAAY,EAA4B,CAC/C,EAAS,IAAI,CAAS,IAI3B,EAAS,OAAO,CAAS,EACzB,EAAM,sBAAsB,EAAW,QAAS,sBAAsB,EAC1E,CCxCA,SAAgB,mBAA+B,CAC3C,IAAM,EAAO,IAAI,EAIjB,OAHA,EAAK,iBAAiB,IAAI,EAAI,CAAE,KAAM,EAAS,IAAK,CAAC,CAAC,EACtD,EAAK,aAAa,IAAI,EAAgB,EAAE,CAAC,EAElC,CACX,CCRA,IAAM,EAAmC,EACnC,EAAkC,IAClC,EAAmC,EACnC,EAAoC,IAOpC,EAAoC,GAKpC,EAAmC,GAiGnC,GAAgD,CAClD,EAAmB,GACnB,EAAmB,GACnB,MAAmB,IACnB,OAAmB,IACnB,OAAmB,IAAI,EAAO,EAAsB,EAAsB,EAAsB,CAAoB,EACpH,OAAmB,6CACnB,aAAmB,kCACnB,OAAmB,6DACnB,gBAAmB,2CACnB,UAAmB,GACnB,YAAmB,GACnB,YAAmB,GACnB,eAAmB,WACnB,YAAmB,SACnB,kBAAmB,GACnB,cAAmB,GACnB,aAAmB,OACnB,oBAAqB,EACzB,EASM,GAAgC,GAmBhB,EAAtB,MAAsB,uBAAuB,CAAqD,CAE9F,OAAe,YAAmC,IAAI,IAEtD,kBAWA,kBAA2C,KAC3C,gBAAkC,EAClC,gBAAkC,EAClC,eAA8C,KAC9C,qBAAwC,GACxC,qBAAuC,EACvC,qBAAuC,EACvC,eAAiC,EACjC,eAAiC,EACjC,eAAiC,EACjC,eAAiC,EACjC,WAA6B,GAC7B,eAAiC,EACjC,eAAiC,EACjC,cAAgC,EAChC,mBAAqC,EACrC,mBAAqC,EACrC,QAA0B,EAC1B,QAA0B,EAM1B,eAAiC,EACjC,eAAiC,EACjC,eAAiC,EACjC,eAAiC,EACjC,gBAAoD,KACpD,sBAAyE,KAEzE,kBAAuD,SACvD,aAAgD,KAChD,UAA+C,KAG/C,MAA0C,KAE1C,iBAAuD,IAAI,EAC3D,iBAA4D,KAI5D,eAAgE,KAChE,sBAAgE,KAChE,gBAAgE,KAChE,uBAAgE,KAChE,qBAAgE,KAChE,qBAAwC,GAExC,aAAsC,GACtC,kBAAsC,GACtC,kBAAsC,GACtC,kBAAkD,KAElD,aAA0E,GAAkB,KAAK,OAAO,CAAC,EACzG,oBAAqE,KAAK,UAAU,EACpF,sBAAuE,KAAK,YAAY,EACxF,oBAAsE,GAAM,KAAK,cAAc,CAAC,EAChG,kBAAsE,GAAM,KAAK,YAAY,CAAC,EAC9F,sBAAsE,GAAM,KAAK,gBAAgB,CAAC,EAClG,sBAAsF,GAAM,KAAK,gBAAgB,CAAC,EAClH,2BAA2E,KAAK,iBAAiB,EACjG,qBAA2E,KAAK,eAAe,EAC/F,sBAA0F,EAAQ,IAAM,KAAK,SAAS,EAAQ,CAAC,EAC/H,yBAA2E,KAAK,aAAa,EAK7F,wBAA2E,KAAK,eAAe,EAY/F,YAAY,EAAyB,CACjC,MAAM,EAAS,EAAqB,EAEpC,KAAK,eAAe,KAAK,mBAAmB,EAE5C,KAAK,kBAAoB,CACrB,KAAM,IAAI,EAAa,EAAU,IAAI,EACrC,UAAW,IAAI,EAAa,EAAU,SAAS,EAC/C,MAAO,IAAI,EAAa,EAAU,KAAK,EACvC,UAAW,IAAI,EAAa,EAAU,SAAS,EAC/C,KAAM,IAAI,EAAa,EAAU,IAAI,EACrC,UAAW,IAAI,EAAa,EAAU,SAAS,EAC/C,MAAO,IAAI,EAAa,EAAU,KAAK,EACvC,UAAW,IAAI,EAAa,EAAU,SAAS,CACnD,EAEA,KAAK,kBAAkB,KAAK,GAAG,OAAQ,KAAK,oBAAoB,EAChE,KAAK,kBAAkB,UAAU,GAAG,OAAQ,KAAK,oBAAoB,EACrE,KAAK,kBAAkB,MAAM,GAAG,OAAQ,KAAK,oBAAoB,EACjE,KAAK,kBAAkB,UAAU,GAAG,OAAQ,KAAK,oBAAoB,EACrE,KAAK,kBAAkB,KAAK,GAAG,OAAQ,KAAK,oBAAoB,EAChE,KAAK,kBAAkB,UAAU,GAAG,OAAQ,KAAK,oBAAoB,EACrE,KAAK,kBAAkB,MAAM,GAAG,OAAQ,KAAK,oBAAoB,EACjE,KAAK,kBAAkB,UAAU,GAAG,OAAQ,KAAK,oBAAoB,EAKrE,IAAK,IAAM,KAAU,OAAO,OAAO,KAAK,iBAAiB,EACrD,EAAO,UAAU,EAAqB,EAG1C,KAAK,WAAW,EAAK,EAErB,KAAK,WAAW,QAAQ,EAKxB,KAAK,QAAQ,CAAC,CAAC,YAAY,EAAE,EAE7B,EAAM,mBAAmB,KAAM,YAAa,KAAK,oBAAoB,CACzE,CAgBA,WAAqB,EAA+B,CAC5C,KAAK,SAAS,iBAAmB,IAAA,IACjC,KAAK,kBAAkB,KAAK,SAAS,eAAgB,KAAK,SAAS,OAAO,EAU9E,KAAK,aAAa,KAAK,YAAY,CAAC,EACpC,KAAK,eAAe,KAAK,cAAc,CAAC,EACxC,KAAK,eAAe,KAAK,cAAc,CAAC,EACxC,KAAK,kBAAkB,KAAK,kBAAkB,CAAC,EAC/C,KAAK,eAAe,KAAK,eAAe,CAAC,EAEzC,KAAK,gBAAgB,EAKjB,GAAS,UAAY,IAAA,IACrB,KAAK,WAAW,CAAE,MAAO,KAAK,oBAAoB,EAAG,OAAQ,GAAI,CAAC,CAE1E,CAaA,aAAuB,EAA8B,CAiCjD,OAhCA,MAAM,aAAa,CAAO,EAEtB,EAAQ,aAAmB,IAAA,KAAW,KAAK,SAAS,WAAiB,EAAQ,YAC7E,EAAQ,QAAmB,IAAA,KAAW,KAAK,SAAS,MAAiB,EAAQ,OAC7E,EAAQ,iBAAmB,IAAA,KAAW,KAAK,SAAS,eAAiB,EAAQ,gBAC7E,EAAQ,UAAmB,IAAA,KAAW,KAAK,SAAS,QAAiB,EAAQ,SAMjF,KAAK,KAAK,EAAQ,GAAK,KAAK,gBAAgB,CAAE,EAC9C,KAAK,KAAK,EAAQ,GAAK,KAAK,gBAAgB,CAAE,EAC9C,KAAK,SAAS,EAAQ,OAAS,KAAK,gBAAgB,KAAM,EAC1D,KAAK,UAAU,EAAQ,QAAU,KAAK,gBAAgB,MAAO,EAMzD,EAAQ,YAAmB,IAAA,KAAW,KAAK,SAAS,UAAiB,EAAQ,WAC7E,EAAQ,cAAmB,IAAA,KAAW,KAAK,SAAS,YAAiB,EAAQ,aAC7E,EAAQ,cAAmB,IAAA,KAAW,KAAK,SAAS,YAAiB,EAAQ,aAC7E,EAAQ,iBAAmB,IAAA,KAAW,KAAK,SAAS,eAAiB,EAAQ,gBAC7E,EAAQ,cAAmB,IAAA,KAAW,KAAK,SAAS,YAAiB,EAAQ,aAEjF,KAAK,qBAAqB,EAAQ,mBAAqB,KAAK,oBAAoB,CAAC,EACjF,KAAK,iBAAiB,EAAQ,eAAiB,KAAK,iBAAiB,CAAC,EACtE,KAAK,gBAAgB,EAAQ,cAAgB,KAAK,gBAAgB,CAAC,EAEnE,KAAK,uBAAuB,EAAQ,qBAAuB,KAAK,sBAAsB,CAAC,EAEhF,IACX,CAoFA,cAAiC,CAC7B,MAAO,EACX,CAmBA,eAAgC,CAC5B,IAAM,EAAS,KAAK,cAAc,EAC5B,EAAS,KAAK,UAAU,EAExB,GAAoB,OAAO,EAAO,IAAI,GAAK,IAAM,OAAO,EAAO,KAAK,GAAM,GAAK,EAAO,QAAQ,EAC9F,GAAoB,OAAO,EAAO,GAAG,GAAM,IAAM,OAAO,EAAO,MAAM,GAAK,GAAK,EAAO,OAAO,EAEnG,MAAO,CACH,MAAQ,KAAK,oBAAoB,EAAI,EACrC,QAAS,KAAK,aAAa,GAAK,GAA0B,CAC9D,CACJ,CAkBA,kBAAkB,EAA4B,CAC1C,MAAO,EACX,CAuBA,SAAS,EAAqB,CAC1B,GAAI,KAAK,WAAW,EAAG,CACnB,IAAM,EAAM,KAAK,cAAc,EAE3B,EAAQ,EAAI,QACZ,EAAQ,EAAI,MAEpB,CAEA,OAAO,MAAM,SAAS,CAAK,CAC/B,CAUA,UAAU,EAAsB,CAC5B,GAAI,KAAK,WAAW,EAAG,CACnB,IAAM,EAAM,KAAK,cAAc,EAE3B,EAAS,EAAI,SACb,EAAS,EAAI,OAErB,CAEA,OAAO,MAAM,UAAU,CAAM,CACjC,CAYA,MAAa,CACT,IAAM,EAAK,KAAK,WAAW,EAAI,EA4B/B,GAvBA,EAAa,SAAS,IAAI,EAI1B,UAAU,IAAI,EAEd,KAAK,SAAS,EACd,KAAK,aAAa,EAElB,eAAe,YAAY,IAAI,IAAI,EAEnC,EAAa,MAAM,CAAE,EAErB,KAAK,WAAW,EAAI,EAEpB,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,EAAU,KAAK,EAAI,CACrC,KAAY,CAAE,QAAS,IAAK,UAAW,aAAc,EACrD,GAAY,CAAE,QAAS,IAAK,UAAW,UAAc,EACrD,WAAY,EACZ,WAAY,CAAC,UAAW,WAAW,CACvC,CAAC,EAEG,KAAK,gBAAiB,CACtB,IAAM,EAAW,KAAK,gBAChB,EAAW,KAAK,sBACtB,KAAK,gBAAwB,KAC7B,KAAK,sBAAwB,KAE7B,IAAM,EAAU,kBAAkB,EAElC,KAAK,uBAAuB,OAAO,EACnC,KAAK,sBAAwB,EAAU,YAAY,CAC/C,KAAkB,KACA,UAClB,iBAAkB,EAClB,QAAmB,GAAyB,CACxC,KAAK,UAAY,EACb,GACA,EAAQ,CAAS,CAEzB,CACJ,CAAC,CACL,KACI,MAAK,UAAY,KAAK,aAAa,EAOvC,OAJI,KAAK,oBAAoB,GACzB,KAAK,4BAA4B,EAG9B,IACX,CAuCA,kBACI,EACA,EACI,CAIJ,MAHA,MAAK,gBAAwB,EAC7B,KAAK,sBAAwB,GAAW,KAEjC,IACX,CAOA,cAAqB,CAKjB,EAAa,aAAa,IAAI,CAClC,CASA,iBAAiC,CAC7B,OAAO,KAAK,WAAW,GAAK,IAChC,CAUA,gBAAmC,CAC/B,MAAO,QACX,CAWA,WAAW,EAAuB,CAC9B,KAAK,YAAY,CAAM,EAEnB,IACA,KAAK,UAAU,EACf,KAAK,KAAK,UAAU,EAE5B,CASA,WAA0B,CACtB,IAAM,EAAU,KAAK,WAAW,EAC5B,CAAC,GAAW,EAAI,OAAO,SAAS,EAAS,EAAI,OAAO,iBAAiB,CAAC,GAI1E,KAAK,MAAM,EAAI,CACnB,CAMA,cAAqB,CACjB,KAAK,aAAa,CACtB,CAQA,SAAkB,CACd,OAAO,EAAa,KAAK,MAC7B,CAWA,aAAuB,CACnB,MAAO,EACX,CASA,gBAAgB,EAAsB,CAClC,KAAK,UAAU,CAAM,CACzB,CAKA,cAAqB,CAGjB,KAAK,KAAK,OAAO,EAEb,KAAK,oBAAsB,OAC3B,EAAI,KAAK,qBAAqB,KAAK,iBAAiB,EACpD,KAAK,kBAAoB,MAG7B,KAAK,kBAAkB,OAAO,EAC9B,KAAK,iBAAmB,KAKxB,KAAK,gBAAwB,KAC7B,KAAK,sBAAwB,KAE7B,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAClC,KAAK,sBAAsB,EAE3B,EAAa,WAAW,IAAI,EAC5B,YAAY,IAAI,EAEhB,eAAe,YAAY,OAAO,IAAI,EAItC,eAAe,uBAAuB,EAEtC,IAAM,EAAK,KAAK,WAAW,EACrB,aAAuB,CACzB,KAAK,WAAW,EAAK,EACrB,KAAK,WAAW,CACpB,EAEA,GAAI,CAAC,EAAI,CACL,SAAS,EACT,MACJ,CAEA,KAAK,iBAAiB,OAAO,EAC7B,KAAK,gBAAkB,EAAU,KAAK,EAAI,CACtC,GAAY,CAAE,QAAS,IAAK,UAAW,aAAc,EACrD,WAAY,EACZ,WAAY,CAAC,UAAW,WAAW,EACnC,WAAY,QAChB,CAAC,CACL,CAUA,YAA6B,CAGzB,KAAK,kBAAkB,OAAO,EAC9B,KAAK,iBAAmB,KACxB,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KACtB,KAAK,uBAAuB,OAAO,EACnC,KAAK,sBAAwB,KAC7B,KAAK,iBAAiB,OAAO,EAC7B,KAAK,gBAAkB,KACvB,KAAK,wBAAwB,OAAO,EACpC,KAAK,uBAAyB,KAC9B,KAAK,sBAAsB,OAAO,EAClC,KAAK,qBAAuB,KAE5B,IAAK,IAAM,KAAU,OAAO,OAAO,KAAK,iBAAiB,EACrD,EAAO,QAAQ,EAGnB,MAAM,WAAW,CACrB,CAQA,gBAA8B,CAC1B,OAAO,KAAK,SAAS,aAAe,KAAK,gBAAgB,WAC7D,CASA,OAAO,gBAAmC,CACtC,OAAO,MAAM,KAAK,eAAe,WAAW,CAChD,CAQA,SAAsB,CAClB,OAAO,KAAK,YAAY,CAC5B,CAWA,UAAU,EAAwB,CAM9B,OALA,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,SAAS,EAAK,KAAK,EACxB,KAAK,UAAU,EAAK,MAAM,EAEnB,IACX,CAWA,gBAAoC,CAChC,OAAO,KAAK,YAChB,CAiBA,eAAe,EAA0B,CACrC,IAAM,EAAO,KAAK,eAAe,EACjC,GAAI,IAAS,EACT,OAAO,KAoBX,GAfI,KAAK,oBAAsB,OAC3B,EAAI,KAAK,qBAAqB,KAAK,iBAAiB,EACpD,KAAK,kBAAoB,MAG7B,KAAK,SAAS,YAAc,EAKxB,IAAS,aAAe,KAAK,QAAU,OACvC,KAAK,aAAa,EAAI,EACtB,KAAK,kBAAkB,GAGvB,IAAU,SAAU,CAIpB,KAAK,qBAAqB,EAAI,EAC9B,KAAK,qBAAqB,QAAQ,EAElC,IAAM,EAAS,KAAK,cAAgB,KAAK,YAAY,EACrD,KAAK,aAAe,KAEpB,KAAK,6BAA6B,EAClC,KAAK,YAAY,MAAc,eAAe,uBAAuB,CAAC,CAC1E,MAAO,GAAI,IAAU,YAQjB,GAPI,IAAS,WACT,KAAK,aAAe,KAAK,YAAY,GAEzC,KAAK,kBAAoB,IAAS,YAAc,YAAc,SAC9D,KAAK,qBAAqB,WAAW,EACrC,KAAK,6BAA6B,EAE9B,KAAK,QAAU,KAMf,KAAK,wBAA0B,CAC3B,KAAK,aAAa,EAAK,EACvB,KAAK,KAAK,UAAU,CACxB,CAAC,MACE,CACH,IAAM,EAAS,KAAK,gBAAgB,EACpC,KAAK,YAAY,MAAc,CAC3B,KAAK,qBAAqB,EAAK,EAC/B,eAAe,uBAAuB,CAC1C,CAAC,EAED,KAAK,KAAK,UAAU,CACxB,KACG,CAEC,IAAS,WACT,KAAK,aAAe,KAAK,YAAY,GAEzC,KAAK,qBAAqB,EAAI,EAC9B,KAAK,qBAAqB,WAAW,EAErC,IAAM,EAAS,KAAK,oBAAoB,EACxC,KAAK,YAAY,MAAc,CAC3B,KAAK,6BAA6B,EAClC,eAAe,uBAAuB,CAC1C,CAAC,CACL,CASA,OAJI,IAAS,aAAe,IAAU,aAClC,KAAK,KAAK,SAAS,EAGhB,IACX,CAOA,aAAuB,CACnB,OAAO,KAAK,eAAe,IAAM,WACrC,CAOA,aAAuB,CACnB,OAAO,KAAK,eAAe,IAAM,WACrC,CAOA,gBAAuB,CACd,KAAK,cAAc,IAIpB,KAAK,eAAe,IAAM,YAC1B,KAAK,eAAe,QAAQ,EAE5B,KAAK,eAAe,WAAW,EAEvC,CAOA,UAAiB,CACb,OAAO,KAAK,eAAe,WAAW,CAC1C,CAgBA,SAAgB,CASZ,OARK,KAAK,YAAY,GAItB,KAAK,eAAe,KAAK,iBAAiB,EAC1C,KAAK,aAAa,EAClB,KAAK,MAAM,EAAI,EAER,MAPI,IAQf,CAaA,QAAQ,EAAyB,CAe7B,OAdI,KAAK,QAAU,EACR,MAGP,KAAK,QAAU,MACf,KAAK,MAAM,iBAAiB,IAAI,EAGpC,KAAK,MAAQ,EAET,IAAS,MACT,EAAK,eAAe,IAAI,EAGrB,KACX,CAQA,SAAuB,CACnB,OAAO,KAAK,KAChB,CAQA,UAA+B,CAC3B,OAAO,KAAK,SAAS,KACzB,CAUA,GAAG,EAAoB,EAA4B,CAG/C,OAFA,KAAK,iBAAiB,IAAI,EAAO,CAAQ,EAElC,IACX,CAWA,IAAI,EAAoB,EAA4B,CAGhD,OAFA,KAAK,iBAAiB,OAAO,EAAO,CAAQ,EAErC,IACX,CAOA,KAAe,EAA0B,CACrC,KAAK,iBAAiB,KAAK,CAAK,CACpC,CAMA,gBAAuB,CACd,KAAK,cAAc,IAIpB,KAAK,eAAe,IAAM,YAC1B,KAAK,eAAe,QAAQ,EAE5B,KAAK,eAAe,WAAW,EAEvC,CAaA,aAAa,EAAsB,CAI/B,MAHA,MAAK,SAAS,UAAY,EAC1B,KAAK,iBAAiB,CAAK,EAEpB,IACX,CAOA,aAAuB,CACnB,OAAO,KAAK,SAAS,WAAa,KAAK,gBAAgB,SAC3D,CAUA,eAAe,EAAsB,CAIjC,MAHA,MAAK,SAAS,YAAc,EAC5B,KAAK,mBAAmB,CAAK,EAEtB,IACX,CAOA,eAAyB,CACrB,OAAO,KAAK,SAAS,aAAe,KAAK,gBAAgB,WAC7D,CAUA,eAAe,EAAsB,CAIjC,MAHA,MAAK,SAAS,YAAc,EAC5B,KAAK,mBAAmB,CAAK,EAEtB,IACX,CAOA,eAAyB,CACrB,OAAO,KAAK,SAAS,aAAe,KAAK,gBAAgB,WAC7D,CAUA,kBAAkB,EAAmC,CAGjD,MAFA,MAAK,SAAS,eAAiB,EAExB,IACX,CAOA,mBAA0C,CACtC,OAAO,KAAK,SAAS,gBAAkB,KAAK,gBAAgB,cAChE,CAWA,qBAAqB,EAAsB,CAcvC,OAbI,KAAK,SAAS,oBAAsB,EAC7B,MAGX,KAAK,SAAS,kBAAoB,EAE7B,EAGM,KAAK,WAAW,GACvB,KAAK,4BAA4B,GAHjC,KAAK,eAAe,EACpB,KAAK,4BAA4B,GAK9B,KACX,CAOA,qBAA+B,CAC3B,OAAO,KAAK,SAAS,mBAAqB,KAAK,gBAAgB,iBACnE,CAYA,uBAAuB,EAAsB,CAGzC,MAFA,MAAK,SAAS,oBAAsB,EAE7B,IACX,CAOA,uBAAiC,CAC7B,OAAO,KAAK,SAAS,qBAAuB,KAAK,gBAAgB,mBACrE,CAUA,iBAAiB,EAAkB,CAG/B,MAFA,MAAK,SAAS,cAAgB,EAEvB,IACX,CAOA,kBAA2B,CACvB,OAAO,KAAK,SAAS,eAAiB,KAAK,gBAAgB,aAC/D,CASA,gBAAgB,EAA+B,CAG3C,MAFA,MAAK,SAAS,aAAe,EAEtB,IACX,CAOA,iBAAsC,CAClC,OAAO,KAAK,SAAS,cAAgB,KAAK,gBAAgB,YAC9D,CASA,YAAY,EAAqB,CAC7B,KAAK,cAAc,CAAC,CACxB,CAcA,cAAc,EAAqB,CAI3B,EAAE,UAIF,KAAK,eAAe,IAAM,WAO9B,KAAK,eAAiB,KAAK,KAAK,EAChC,KAAK,cAAiB,KAAK,KAAK,EAChC,KAAK,mBAAqB,EAAE,QAC5B,KAAK,mBAAqB,EAAE,QAC5B,KAAK,QAAU,EACf,KAAK,QAAU,EAKf,KAAK,eAAiB,KAAK,eAC3B,KAAK,eAAiB,KAAK,eAC3B,KAAK,eAAiB,KAAK,cAC3B,KAAK,eAAiB,KAAK,cAI3B,KAAK,cAAc,WAAW,EAM9B,EAAM,oBAAoB,KAAM,UAAW,KAAK,eAAe,EAC/D,EAAM,oBAAoB,KAAM,YAAa,KAAK,YAAY,EAClE,CAeA,SAAS,EAAsB,EAAqB,CAC5C,KAAK,eAAe,IAAM,WAI9B,EAAE,eAAe,EAEZ,KAAK,uBACN,KAAK,qBAAuB,GAC5B,KAAK,qBAAuB,EAAE,QAC9B,KAAK,qBAAuB,EAAE,QAC9B,KAAK,eAAuB,KAAK,KAAK,EACtC,KAAK,eAAuB,KAAK,KAAK,EACtC,KAAK,eAAuB,KAAK,SAAS,EAC1C,KAAK,eAAuB,KAAK,UAAU,EAE3C,EAAM,oBAAoB,KAAM,UAAe,KAAK,iBAAiB,EACrE,EAAM,oBAAoB,KAAM,WAAe,KAAK,iBAAiB,EACrE,EAAM,oBAAoB,KAAM,cAAe,KAAK,iBAAiB,GAGzE,KAAK,gBAAkB,EAAE,QACzB,KAAK,gBAAkB,EAAE,QACzB,KAAK,eAAiB,EAElB,KAAK,oBAAsB,OAC3B,KAAK,kBAAoB,EAAI,KAAK,sBAAuB,GAAO,KAAK,YAAY,CAAE,CAAC,GAE5F,CASA,aAA4C,CAOxC,MANA,MAAK,qBAAuB,GAE5B,EAAM,uBAAuB,KAAM,UAAe,KAAK,iBAAiB,EACxE,EAAM,uBAAuB,KAAM,WAAe,KAAK,iBAAiB,EACxE,EAAM,uBAAuB,KAAM,cAAe,KAAK,iBAAiB,EAEjE,EACX,CAOA,cAAuB,CACnB,OAAO,KAAK,UAChB,CASA,aAAa,EAAmB,CAG5B,MAFA,MAAK,WAAa,EAEX,IACX,CASA,YAAoB,EAAyB,CACzC,GAAI,EAAY,KAAK,eAAiB,IAAO,KAAK,WAAY,CAC1D,KAAK,kBAAoB,EAAI,KAAK,sBAAuB,GAAO,KAAK,YAAY,CAAE,CAAC,EACpF,MACJ,CAEA,KAAK,eAAiB,EACtB,KAAK,kBAAoB,KAEzB,IAAM,EAAS,KAAK,eAIpB,GAFA,KAAK,eAAiB,KAElB,CAAC,EACD,OAQJ,IAAM,EAAU,KAAK,gBAAkB,KAAK,qBACtC,EAAU,KAAK,gBAAkB,KAAK,qBAEtC,EAAe,KAAK,eAAiB,KAAK,eAC1C,EAAe,KAAK,eAAiB,KAAK,eAQ1C,EAAiB,EAAI,OAAO,gBAAgB,EAC5C,EAAiB,EAAG,MAAS,KAAK,eAClC,EAAiB,EACjB,EAAiB,EAAG,OAAS,KAAK,eAClC,EAAiB,EAGvB,OADA,KAAK,mBAAmB,EAAK,EACrB,EAAO,aAAa,EAA5B,CACI,KAAK,EAAU,UACX,KAAK,SAAS,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAY,CAAC,EACnE,KAAK,UAAU,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAc,CAAC,EACtE,KAAK,KAAK,EAAc,KAAK,SAAS,CAAC,EACvC,KAAK,KAAK,EAAe,KAAK,UAAU,CAAC,EAEzC,MACJ,KAAK,EAAU,MACX,KAAK,UAAU,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAc,CAAC,EACtE,KAAK,KAAK,EAAe,KAAK,UAAU,CAAC,EAEzC,MACJ,KAAK,EAAU,UACX,KAAK,SAAS,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAY,CAAC,EACnE,KAAK,UAAU,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAc,CAAC,EACtE,KAAK,KAAK,EAAe,KAAK,UAAU,CAAC,EAEzC,MACJ,KAAK,EAAU,KACX,KAAK,SAAS,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAY,CAAC,EAEnE,MACJ,KAAK,EAAU,UACX,KAAK,SAAS,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAY,CAAC,EACnE,KAAK,UAAU,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAc,CAAC,EAEtE,MACJ,KAAK,EAAU,MACX,KAAK,UAAU,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAc,CAAC,EAEtE,MACJ,KAAK,EAAU,UACX,KAAK,SAAS,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAY,CAAC,EACnE,KAAK,UAAU,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAc,CAAC,EACtE,KAAK,KAAK,EAAc,KAAK,SAAS,CAAC,EAEvC,MACJ,KAAK,EAAU,KACX,KAAK,SAAS,KAAK,IAAI,KAAK,eAAiB,EAAS,CAAY,CAAC,EACnE,KAAK,KAAK,EAAc,KAAK,SAAS,CAAC,EAEvC,KACR,CAEA,KAAK,SAAS,EACd,KAAK,mBAAmB,EAAI,CAChC,CAUA,wBAA6F,CACzF,IAAM,EAAK,KAAK,SAAS,EACnB,EAAK,EAAI,OAAO,gBAAgB,EAChC,EAAK,EAAG,MACR,EAAK,EAAG,OAEd,GAAI,KAAK,sBAAsB,EAE3B,MAAO,CAAE,KAAM,EAAG,KAAM,EAAK,EAAG,KAAM,EAAG,KAAM,EAAK,KAAK,UAAU,CAAE,EAOzE,IAAM,EAAU,KAAK,aAAa,GAAK,EAEvC,MAAO,CAAE,KAAM,EAAiB,EAAG,KAAM,EAAK,EAAgB,KAAM,EAAG,KAAM,EAAK,CAAQ,CAC9F,CAQA,gBAA+B,CAC3B,GAAM,CAAE,OAAM,OAAM,OAAM,QAAS,KAAK,uBAAuB,EAYzD,EAAM,KAAK,IAAI,EAAM,KAAK,cAAc,EACxC,EAAM,KAAK,IAAI,EAAM,KAAK,cAAc,EACxC,EAAM,KAAK,IAAI,EAAM,KAAK,cAAc,EACxC,EAAM,KAAK,IAAI,EAAM,KAAK,cAAc,EAExC,EAAU,KAAK,eAAiB,KAAK,QACrC,EAAU,KAAK,cAAiB,KAAK,QAErC,EAAW,EAAK,MAAM,EAAS,EAAK,CAAG,EACvC,EAAW,EAAK,MAAM,EAAS,EAAK,CAAG,EAG7C,KAAK,eAAiB,KAAK,IAAI,KAAK,eAAgB,CAAQ,EAC5D,KAAK,eAAiB,KAAK,IAAI,KAAK,eAAgB,CAAQ,EAC5D,KAAK,eAAiB,KAAK,IAAI,KAAK,eAAgB,CAAQ,EAC5D,KAAK,eAAiB,KAAK,IAAI,KAAK,eAAgB,CAAQ,EAE5D,KAAK,QAAU,EAAW,KAAK,eAC/B,KAAK,QAAU,EAAW,KAAK,aACnC,CAWA,yBAAgC,CAC5B,GAAM,CAAE,OAAM,OAAM,OAAM,QAAS,KAAK,uBAAuB,EAQ/D,OAHA,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,EAAG,CAAI,EAAG,CAAI,CAAC,EACrD,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,EAAG,CAAI,EAAG,CAAI,CAAC,EAE9C,IACX,CAQA,OAAO,EAAqC,CAaxC,MATA,MAAK,QAAU,EAAE,QAAU,KAAK,mBAChC,KAAK,QAAU,EAAE,QAAU,KAAK,mBAEhC,KAAK,eAAe,EAIpB,KAAK,aAAa,KAAK,QAAS,KAAK,OAAO,EAErC,CAAE,KAAM,GAAM,QAAS,EAAK,CACvC,CAOA,WAAkC,CAW9B,OARA,KAAK,KAAK,KAAK,eAAiB,KAAK,OAAO,EAC5C,KAAK,KAAK,KAAK,cAAiB,KAAK,OAAO,EAC5C,KAAK,aAAa,EAAG,CAAC,EACtB,KAAK,cAAc,IAAI,EAEvB,EAAM,uBAAuB,KAAM,UAAW,KAAK,eAAe,EAClE,EAAM,uBAAuB,KAAM,YAAa,KAAK,YAAY,EAE1D,EACX,CAOA,UAAiB,CACb,MAAM,SAAS,EAEf,IAAI,EAAa,KAAK,cAAc,EAChC,EAAS,KAAK,UAAU,EACxB,GAA0B,OAAO,EAAW,IAAI,GAAK,IAAM,OAAO,EAAW,KAAK,GAAK,GAAK,EAAO,QAAQ,EAC3G,GAAuB,OAAO,EAAW,GAAG,GAAK,IAAM,OAAO,EAAW,MAAM,GAAK,GAAK,EAAO,OAAO,EACvG,EAAO,KAAK,QAAQ,EACxB,GAAI,CAAC,EACD,MAAU,MAAM,wCAAwC,EAG5D,IAAI,EAAY,KAAK,aAAa,EAClC,GAAI,CAAC,EACD,MAAU,MAAM,wCAAwC,EA6D5D,OA1DA,KAAK,kBAAkB,KAAK,mBAAmB,EAAK,EACpD,KAAK,kBAAkB,UAAU,mBAAmB,EAAK,EACzD,KAAK,kBAAkB,MAAM,mBAAmB,EAAK,EACrD,KAAK,kBAAkB,UAAU,mBAAmB,EAAK,EACzD,KAAK,kBAAkB,KAAK,mBAAmB,EAAK,EACpD,KAAK,kBAAkB,UAAU,mBAAmB,EAAK,EACzD,KAAK,kBAAkB,MAAM,mBAAmB,EAAK,EACrD,KAAK,kBAAkB,UAAU,mBAAmB,EAAK,EAEzD,KAAK,kBAAkB,KAAK,KAAK,CAAC,EAClC,KAAK,kBAAkB,KAAK,KAAK,EAAO,OAAO,CAAC,EAChD,KAAK,kBAAkB,KAAK,SAAS,EAAO,QAAQ,CAAC,EACrD,KAAK,kBAAkB,KAAK,UAAU,EAAU,MAAM,EAEtD,KAAK,kBAAkB,UAAU,KAAK,CAAC,EACvC,KAAK,kBAAkB,UAAU,KAAK,CAAC,EACvC,KAAK,kBAAkB,UAAU,SAAS,EAAO,QAAQ,CAAC,EAC1D,KAAK,kBAAkB,UAAU,UAAU,EAAO,OAAO,CAAC,EAE1D,KAAK,kBAAkB,MAAM,KAAK,EAAO,QAAQ,CAAC,EAClD,KAAK,kBAAkB,MAAM,KAAK,CAAC,EACnC,KAAK,kBAAkB,MAAM,SAAS,EAAU,KAAK,EACrD,KAAK,kBAAkB,MAAM,UAAU,EAAO,OAAO,CAAC,EAEtD,KAAK,kBAAkB,UAAU,KAAK,EAAK,MAAQ,CAAsB,EACzE,KAAK,kBAAkB,UAAU,KAAK,CAAC,EACvC,KAAK,kBAAkB,UAAU,SAAS,EAAO,SAAS,CAAC,EAC3D,KAAK,kBAAkB,UAAU,UAAU,EAAO,OAAO,CAAC,EAE1D,KAAK,kBAAkB,KAAK,KAAK,EAAK,MAAQ,CAAsB,EACpE,KAAK,kBAAkB,KAAK,KAAK,EAAO,OAAO,CAAC,EAChD,KAAK,kBAAkB,KAAK,SAAS,EAAO,SAAS,CAAC,EACtD,KAAK,kBAAkB,KAAK,UAAU,EAAU,MAAM,EAEtD,KAAK,kBAAkB,UAAU,KAAK,EAAK,MAAQ,CAAsB,EACzE,KAAK,kBAAkB,UAAU,KAAK,EAAK,OAAS,CAAmB,EACvE,KAAK,kBAAkB,UAAU,SAAS,EAAO,SAAS,CAAC,EAC3D,KAAK,kBAAkB,UAAU,UAAU,EAAO,UAAU,CAAC,EAE7D,KAAK,kBAAkB,MAAM,KAAK,EAAO,QAAQ,CAAC,EAClD,KAAK,kBAAkB,MAAM,KAAK,EAAK,OAAS,CAAmB,EACnE,KAAK,kBAAkB,MAAM,SAAS,EAAU,KAAK,EACrD,KAAK,kBAAkB,MAAM,UAAU,EAAO,SAAS,CAAC,EAExD,KAAK,kBAAkB,UAAU,KAAK,CAAC,EACvC,KAAK,kBAAkB,UAAU,KAAK,EAAK,OAAS,CAAmB,EACvE,KAAK,kBAAkB,UAAU,SAAS,EAAO,QAAQ,CAAC,EAC1D,KAAK,kBAAkB,UAAU,UAAU,EAAO,UAAU,CAAC,EAE7D,KAAK,kBAAkB,KAAK,mBAAmB,EAAI,EACnD,KAAK,kBAAkB,UAAU,mBAAmB,EAAI,EACxD,KAAK,kBAAkB,MAAM,mBAAmB,EAAI,EACpD,KAAK,kBAAkB,UAAU,mBAAmB,EAAI,EACxD,KAAK,kBAAkB,KAAK,mBAAmB,EAAI,EACnD,KAAK,kBAAkB,UAAU,mBAAmB,EAAI,EACxD,KAAK,kBAAkB,MAAM,mBAAmB,EAAI,EACpD,KAAK,kBAAkB,UAAU,mBAAmB,EAAI,EAEjD,IACX,CAOA,QAAiB,CACb,IAAI,EAAU,MAAM,OAAO,EAW3B,OATA,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,KAAK,WAAW,EAAI,CAAE,EAC3E,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,UAAU,WAAW,EAAI,CAAE,EAChF,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,MAAM,WAAW,EAAI,CAAE,EAC5E,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,UAAU,WAAW,EAAI,CAAE,EAChF,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,KAAK,WAAW,EAAI,CAAE,EAC3E,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,UAAU,WAAW,EAAI,CAAE,EAChF,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,MAAM,WAAW,EAAI,CAAE,EAC5E,EAAI,KAAK,YAAY,EAAS,KAAK,kBAAkB,UAAU,WAAW,EAAI,CAAE,EAEzE,CACX,CASA,aAAkC,CAC9B,MAAO,CACH,EAAQ,KAAK,KAAK,EAClB,EAAQ,KAAK,KAAK,EAClB,MAAQ,KAAK,SAAS,EACtB,OAAQ,KAAK,UAAU,CAC3B,CACJ,CAQA,cAA2C,CACvC,IAAM,EAAW,KAAK,cAAc,EACpC,IAAK,IAAM,KAAS,EAChB,GAAI,CAAC,KAAK,kBAAkB,CAAK,EAC7B,OAAO,EAGf,OAAO,IACX,CAOA,qBAA6B,EAA0B,CACnD,AACI,KAAK,YAAY,KAAK,aAAa,EAEnC,KAAK,WACL,KAAK,UAAU,aAAa,CAAS,CAE7C,CAQA,qBAA0C,CACtC,GAAI,KAAK,kBAAkB,IAAM,SAAU,CACvC,IAAM,EAAK,KAAK,WAAW,EACrB,EAAS,EAAK,EAAI,OAAO,iBAAiB,CAAE,EAAI,KACtD,GAAI,GAAU,IAAW,EAAI,OAAO,mBAAmB,EAAG,CACtD,IAAM,EAAI,EAAI,OAAO,eAAe,CAAM,EAC1C,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAE,MAAO,OAAQ,EAAE,MAAO,CAC1D,CACJ,CAEA,IAAM,EAAK,EAAI,OAAO,gBAAgB,EAEtC,MAAO,CACH,EAAQ,EACR,EAAQ,EACR,MAAQ,EAAG,MACX,OAAQ,EAAG,MACf,CACJ,CAQA,iBAAsC,CAClC,IAAM,EAAY,KAAK,gBAAgB,EACjC,EAAY,KAAK,qBAAqB,EACtC,EAAe,KAAK,aAAa,GAAK,EAI5C,MAAO,CAAE,EAHC,GAAa,EAAY,GAGvB,EAFF,EAAI,OAAO,gBAAgB,CAAC,CAAC,OAAS,EAEjC,MAAO,EAAW,OAAQ,CAAa,CAC1D,CAaA,oBAAqC,CACjC,IAAM,EAAa,KAAK,MAClB,EAAa,EAAK,aAAa,EAC/B,EAAa,KAAK,YAAY,EAG9B,EAAa,KAAK,IAAI,GAAK,EAAY,KAAK,IAAI,EAAI,MAAO,CAAC,CAAC,EAS7D,EAAe,EAAK,QAAQ,EAC5B,EAAe,IAAS,EAAU,MAAQ,IAAS,EAAU,KAC7D,EAAe,EAAK,qBAAqB,IAAI,EAC7C,EAAe,EAAK,qBAAqB,IAAI,EAC7C,GAAgB,EAAW,EAAI,OAAS,EAAI,OAAS,EACrD,EAAe,EAAe,EAC9B,GAAc,EAAe,GAAc,EAC3C,EAMF,EAAU,EACV,EAAU,EAEd,OAAQ,EAAR,CACI,KAAK,EAAU,KACX,EAAU,EAAI,OAAO,gBAAgB,CAAC,CAAC,MAAQ,EAC/C,EAAU,EAEV,MAEJ,KAAK,EAAU,KACX,EAAU,EAEV,MAEJ,KAAK,EAAU,MACX,EAAU,EACV,EAAU,EAAI,OAAO,gBAAgB,CAAC,CAAC,OAAS,EAEhD,MAEJ,KAAK,EAAU,MACX,EAAU,EAEV,MAEJ,QACI,EAAU,EAEV,KACR,CAKA,MAAO,aAHI,EAAU,EAAI,EAGF,MAFZ,EAAU,EAAI,EAEO,YAAY,EAAM,EACtD,CAUA,oBAA4B,EAA0B,CAClD,IAAM,EAAU,KAAK,WAAW,EAEhC,GAAI,CAAC,EAAS,CACV,EAAO,EAEP,MACJ,CAEA,KAAK,wBAAwB,OAAO,EACpC,KAAK,uBAAyB,EAAU,KAAK,EAAS,CAClD,KAAY,CAAE,gBAAiB,MAAO,UAAW,2BAA4B,QAAS,GAAI,EAC1F,GAAY,CAAE,UAAW,KAAK,mBAAmB,EAAG,QAAS,GAAI,EACjE,WAAY,EACZ,WAAY,CAAC,YAAa,SAAS,EACnC,WAAY,CAChB,CAAC,CACL,CAOA,mBAAkC,CAC9B,IAAM,EAAU,KAAK,WAAW,EAE3B,IAIL,KAAK,sBAAsB,OAAO,EAClC,KAAK,qBAAuB,EAAU,KAAK,EAAS,CAChD,KAAY,CAAE,gBAAiB,MAAO,UAAW,KAAK,mBAAmB,EAAG,QAAS,GAAI,EACzF,GAAY,CAAE,UAAW,2BAA4B,QAAS,GAAI,EAClE,WAAY,EACZ,WAAY,CAAC,YAAa,SAAS,CACvC,CAAC,EACL,CAQA,iBAAkC,CAC9B,IAAM,EAAS,EAAI,OAAO,YAAY,+BAA+B,EACrE,GAAI,EAAQ,CACR,IAAM,EAAS,WAAW,CAAM,EAChC,GAAI,CAAC,MAAM,CAAM,GAAK,EAAS,EAC3B,OAAO,CAEf,CAEA,OAAO,CACX,CAQA,sBAAuC,CACnC,IAAI,EAAQ,EACZ,IAAK,IAAM,KAAO,eAAe,YAAa,CAC1C,GAAI,IAAQ,KACR,OAAO,EAEP,EAAI,eAAe,IAAM,aACzB,GAER,CACA,OAAO,CACX,CAMA,OAAe,wBAA+B,CAC1C,IAAI,EAAQ,EACZ,IAAK,IAAM,KAAO,eAAe,YAAa,CAC1C,GAAI,EAAI,eAAe,IAAM,YACzB,SAEJ,IAAM,EAAc,EAAI,gBAAgB,EAClC,EAAe,EAAI,aAAa,GAAK,EACrC,EAAI,GAAS,EAAY,GACzB,EAAI,EAAI,OAAO,gBAAgB,CAAC,CAAC,OAAS,EAEhD,EAAI,mBAAmB,EAAK,EAC5B,EAAI,KAAK,CAAC,EACV,EAAI,KAAK,CAAC,EACV,EAAI,SAAS,CAAS,EACtB,EAAI,UAAU,CAAY,EAC1B,EAAI,SAAS,EACb,EAAI,mBAAmB,EAAI,EAE3B,GACJ,CACJ,CAUA,YAAoB,EAAoB,EAA2B,CAC/D,KAAK,kBAAkB,OAAO,EAC9B,KAAK,iBAAmB,KAExB,IAAM,OAAU,GAA2B,CACvC,KAAK,mBAAmB,EAAK,EAC7B,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,SAAS,EAAK,KAAK,EACxB,KAAK,UAAU,EAAK,MAAM,EAC1B,KAAK,SAAS,EACd,KAAK,mBAAmB,EAAI,CAChC,EAEA,KAAK,iBAAmB,EAAU,MAAkB,CAChD,KAAY,KAAK,YAAY,EAC7B,GAAY,EACZ,WAAY,EACZ,OAAY,OACZ,eAAwB,CACpB,KAAK,iBAAmB,KACxB,IAAS,CACb,CACJ,CAAC,CACL,CAQA,8BAA6C,CACrC,AAKJ,KAAK,wBADL,EAAM,oBAAoB,KAAM,SAAU,KAAK,sBAAsB,EACzC,GAChC,CAKA,8BAA6C,CACpC,AAKL,KAAK,wBADL,EAAM,uBAAuB,KAAM,SAAU,KAAK,sBAAsB,EAC5C,GAChC,CAMA,kBAAiC,CAC7B,GAAI,KAAK,eAAe,IAAM,YAC1B,OAGJ,IAAM,EAAO,KAAK,oBAAoB,EACtC,KAAK,mBAAmB,EAAK,EAC7B,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,SAAS,EAAK,KAAK,EACxB,KAAK,UAAU,EAAK,MAAM,EAC1B,KAAK,SAAS,EACd,KAAK,mBAAmB,EAAI,EAE5B,eAAe,uBAAuB,CAC1C,CAQA,6BAA4C,CACpC,AAOJ,KAAK,qBAHL,EAAM,oBAAoB,KAAM,UAAW,KAAK,mBAAmB,EACnE,EAAM,oBAAoB,KAAM,QAAW,KAAK,iBAAiB,EACjE,EAAM,oBAAoB,KAAM,OAAW,KAAK,gBAAgB,EACvC,GAC7B,CAKA,6BAA4C,CACnC,AAOL,KAAK,qBAHL,EAAM,uBAAuB,KAAM,UAAW,KAAK,mBAAmB,EACtE,EAAM,uBAAuB,KAAM,QAAW,KAAK,iBAAiB,EACpE,EAAM,uBAAuB,KAAM,OAAW,KAAK,gBAAgB,EAC1C,GAC7B,CAMA,0BAAyC,CACjC,AAMJ,KAAK,qBAFL,EAAM,oBAAoB,KAAM,YAAa,KAAK,qBAAqB,EACvE,EAAM,oBAAoB,KAAM,YAAa,KAAK,qBAAqB,EAC9C,GAC7B,CAKA,0BAAyC,CAChC,AAML,KAAK,qBAFL,EAAM,uBAAuB,KAAM,YAAa,KAAK,qBAAqB,EAC1E,EAAM,uBAAuB,KAAM,YAAa,KAAK,qBAAqB,EACjD,GAC7B,CAKA,uBAAsC,CAClC,AAEI,KAAK,qBADL,KAAK,kBAAkB,cAAc,EAAK,EACjB,KAEjC,CAMA,gBAA+B,CAC3B,KAAK,aAAe,GACpB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,CAC/B,CAQA,cAAsB,EAAwB,CACrC,KAAK,oBAAoB,GAG1B,KAAK,eAAe,IAAM,UAGzB,KAAK,gBAAgB,CAAC,IAGvB,KAAK,eAIT,KAAK,aAAe,GACpB,KAAK,yBAAyB,GAClC,CAOA,YAAoB,EAAwB,CACnC,KAAK,eAKN,KAAK,kBAAkB,CAAC,GAI5B,KAAK,eAAe,EACxB,CAQA,gBAAwB,EAA2B,CAC/C,OAAQ,KAAK,gBAAgB,EAA7B,CACI,IAAK,OAAS,OAAO,EAAE,QACvB,IAAK,OAAS,OAAO,EAAE,QACvB,IAAK,MAAS,OAAO,EAAE,OACvB,IAAK,QAAS,OAAO,EAAE,QAC3B,CACJ,CAQA,kBAA0B,EAA2B,CACjD,OAAO,KAAK,gBAAgB,CAAC,CACjC,CAQA,gBAAwB,EAAqB,CACzC,GAAI,CAAC,KAAK,aACN,OAGJ,IAAM,EAAS,KAAK,eAAe,EAAE,QAAS,EAAE,OAAO,EACnD,IAAW,KAAK,oBAIhB,KAAK,mBACL,KAAK,kBAAkB,cAAc,EAAK,EAE9C,KAAK,kBAAoB,EACrB,GACA,EAAO,cAAc,EAAI,EAEjC,CAUA,eAAuB,EAAY,EAAiC,CAChE,IAAM,EAA6B,CAE/B,KAAK,kBAAkB,UACvB,KAAK,kBAAkB,UACvB,KAAK,kBAAkB,UACvB,KAAK,kBAAkB,UACvB,KAAK,kBAAkB,MACvB,KAAK,kBAAkB,KACvB,KAAK,kBAAkB,MACvB,KAAK,kBAAkB,IAC3B,EAEI,EAA4B,KAC5B,EAAW,KAAK,iBAAiB,EAErC,IAAK,IAAM,KAAU,EAAY,CAC7B,IAAM,EAAK,EAAO,WAAW,EAC7B,GAAI,CAAC,EACD,SAEJ,IAAM,EAAO,EAAI,OAAO,eAAe,CAAE,EACnC,EAAK,KAAK,IAAI,EAAK,KAAO,EAAI,EAAG,EAAK,EAAK,KAAK,EAChD,EAAK,KAAK,IAAI,EAAK,IAAO,EAAI,EAAG,EAAK,EAAK,MAAM,EACjD,EAAO,KAAK,MAAM,EAAI,CAAE,EAE1B,GAAQ,IACR,EAAW,EACX,EAAW,EAEnB,CAEA,OAAO,CACX,CASA,gBAAwB,EAAqC,CACzD,IAAM,EAAS,KAAK,kBACpB,GAAI,CAAC,EACD,OAQJ,IAAM,EAAW,EAAO,WAAW,EACnC,GAAI,GAAY,EAAI,OAAO,OAAO,EAAE,MAAM,GAAK,EAAI,OAAO,SAAS,EAAU,EAAI,OAAO,OAAO,EAAE,MAAM,CAAC,EAAG,CACvG,KAAK,kBAAoB,KACzB,MACJ,CAOA,OAHA,EAAO,YAAY,EACnB,KAAK,kBAAoB,KAElB,EACX,CACJ,ECz9EM,GAAuD,CACzD,CAAE,OAAQ,GAAuB,OAAQ,CAAE,WAAY,iCAAkC,OAAQ,qCAAsC,UAAW,oCAAqC,CAAE,EACzL,CAAE,OAAQ,sBAAuB,OAAQ,CAAE,WAAY,sCAAuC,CAAE,EAChG,CAAE,OAAQ,UAAuB,OAAQ,CAAE,WAAY,uCAAwC,CAAE,CACrG,EAYM,GAA0D,CAC5D,CAAE,OAAQ,GAAI,OAAQ,CAAE,WAAY,cAAe,OAAQ,wBAAyB,UAAW,MAAO,CAAE,CAC5G,EAaA,SAAgB,0BAA0B,EAAuB,CAC7D,OAAO,IAAI,EAAO,CAAE,QAAO,WAAY,GAAM,WAAY,GAA4B,OAAQ,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAE,CAAC,CACzH,CAaA,SAAgB,4BAA4B,EAAuB,CAC/D,IAAM,EAAS,IAAI,EAAO,CAAE,QAAO,WAAY,GAAM,WAAY,GAA+B,OAAQ,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAE,CAAC,EAGhI,OAFA,EAAO,iBAAiB,MAAM,EAEvB,CACX,CAYA,SAAgB,wBAAwB,EAAmB,EAAuB,CAC9E,IAAM,EAAa,EAAS,iCAAmC,cAE/D,IAAK,IAAM,KAAU,EACjB,EAAO,cAAc,CAAU,CAEvC,CC9DA,EAAM,SAAS,GAAO,EAAiB,EAAiB,CAAc,EAQtE,IAAM,GAAwB,EAwBxB,aAAN,cAA2B,EAAO,CAE9B,YACA,gBACA,gBACA,aACA,kBACA,YAAyC,KACzC,UACA,WAAoC,GACpC,aAAoC,GACpC,aAAoC,GAEpC,YAAY,EAAc,EAA+B,CACrD,MAAM,CAAI,EAOV,KAAK,kBAAoB,sCACzB,KAAK,mBAAmB,KAAK,iBAAiB,EAO9C,KAAK,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAKrC,IAAM,EAAQ,KAAK,QAAQ,EAC3B,KAAK,gBAAgB,CAAK,EAE1B,KAAK,UAAY,IAAI,EACrB,KAAK,UAAU,iBAAiB,IAAI,EAAK,CAAE,QAAS,CAAE,CAAC,CAAC,EAIxD,KAAK,UAAU,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAC/C,KAAK,UAAU,iBAAiB,MAAM,EACtC,KAAK,UAAU,aAAa,CAAK,EAQjC,IAAM,EAAY,IAAI,EACtB,EAAU,iBAAiB,IAAI,EAAI,CAAE,KAAM,EAAS,UAAW,CAAC,CAAC,EACjE,EAAU,iBAAiB,MAAM,EACjC,EAAU,aAAa,KAAK,SAAS,EAErC,KAAK,aAAa,EAAW,CACzB,UAAW,EAAU,KACrB,OAAW,EAAW,KACtB,KAAW,EAAS,UACxB,CAAC,EAUD,KAAK,gBAAkB,0BAA0B,iBAAiB,EAClE,KAAK,gBAAkB,0BAA0B,iBAAiB,EAClE,KAAK,YAAkB,0BAA0B,OAAO,EAExD,IAAK,IAAM,IAAW,CAAC,KAAK,gBAAiB,KAAK,gBAAiB,KAAK,WAAW,EAC/E,EAAQ,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAG5C,KAAK,aAAe,IAAI,EACxB,KAAK,aAAa,iBAAiB,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,CAAC,EAC7E,KAAK,aAAa,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAClD,KAAK,aAAa,aAAa,KAAK,eAAe,EACnD,KAAK,aAAa,aAAa,KAAK,eAAe,EACnD,KAAK,aAAa,aAAa,KAAK,WAAW,EAE/C,KAAK,aAAa,KAAK,aAAc,CAAE,UAAW,EAAU,IAAK,CAAC,EAI9D,GAAS,QAAU,IAAA,IACnB,KAAK,SAAS,iBAAiB,EAG/B,GACA,KAAK,aAAa,CAAO,CAEjC,CAkBA,qBAAsC,CAIlC,IAAM,EAAW,KAAK,QAAQ,CAAC,CAAC,iBAAiB,EAC3C,EAAa,EAAW,EAAS,OAAS,GAShD,GAPA,KAAK,iBAAiB,CAAE,MAAO,IAAK,OAAQ,EAAa,EAAI,EAAc,CAAC,EAOxE,KAAK,YAAa,CAClB,IAAM,EAAM,KAAK,qBAAqB,EACtC,KAAK,YAAY,iBAAiB,CAAE,MAAO,EAAK,OAAQ,CAAI,CAAC,CACjE,CACJ,CAUA,sBAAuC,CACnC,OAAO,EAAa,iBAAiB,CAAC,CAAC,UAC3C,CASA,aAAuB,EAAoC,CAmBvD,OAlBA,MAAM,aAAa,CAAO,EAEtB,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGnC,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,QAAU,IAAA,IAClB,KAAK,SAAS,EAAQ,KAAK,EAGxB,IACX,CAeA,SAAS,EAAoB,CACzB,AAEI,KAAK,eADL,KAAK,UAAU,gBAAgB,KAAK,WAAW,EAC5B,MASvB,IAAM,EAAQ,IAAI,EAAM,CAAI,EACtB,EAAM,KAAK,qBAAqB,EAOtC,OANA,EAAM,iBAAiB,CAAE,MAAO,EAAK,OAAQ,CAAI,CAAC,EAClD,EAAM,iBAAiB,MAAM,EAC7B,KAAK,YAAc,EAEnB,KAAK,UAAU,gBAAgB,EAAO,CAAC,EAEhC,IACX,CAOA,YAAmB,CAMf,MALA,CAEI,KAAK,eADL,KAAK,UAAU,gBAAgB,KAAK,WAAW,EAC5B,MAGhB,IACX,CAOA,UAAyB,CACrB,OAAO,KAAK,WAChB,CAOA,UAAU,EAAuB,CAS7B,OARA,KAAK,mBAAmB,EAClB,KAAK,kBACL,4CAA4C,EAIlD,wBAAwB,CAAC,KAAK,gBAAiB,KAAK,gBAAiB,KAAK,WAAW,EAAG,CAAM,EAEvF,IACX,CAWA,aAAa,EAAsB,CAI/B,MAHA,MAAK,WAAa,EAClB,KAAK,YAAY,WAAW,CAAK,EAE1B,IACX,CAOA,aAAuB,CACnB,OAAO,KAAK,UAChB,CASA,eAAe,EAAsB,CAIjC,MAHA,MAAK,aAAe,EACpB,KAAK,gBAAgB,WAAW,CAAK,EAE9B,IACX,CAOA,eAAyB,CACrB,OAAO,KAAK,YAChB,CASA,eAAe,EAAsB,CAIjC,MAHA,MAAK,aAAe,EACpB,KAAK,gBAAgB,WAAW,CAAK,EAE9B,IACX,CAOA,eAAyB,CACrB,OAAO,KAAK,YAChB,CAWA,uBAAuB,EAAkD,CAGrE,OAFA,KAAK,gBAAgB,SAAS,CAAI,EAE3B,IACX,CAcA,uBAAuB,EAAkD,CAGrE,OAFA,KAAK,gBAAgB,SAAS,CAAI,EAE3B,IACX,CAOA,sBAAsB,EAA2B,CAG7C,OAFA,KAAK,YAAY,GAAG,SAAU,CAAyB,EAEhD,IACX,CAOA,0BAA0B,EAA0B,CAGhD,OAFA,KAAK,gBAAgB,GAAG,SAAU,CAAyB,EAEpD,IACX,CAOA,0BAA0B,EAA0B,CAGhD,OAFA,KAAK,gBAAgB,GAAG,SAAU,CAAyB,EAEpD,IACX,CASA,6BAA6B,EAAgC,CAOzD,OAFA,EAAM,mBAAmB,KAAM,WAAY,CAAQ,EAE5C,IACX,CAcA,mBAAmB,EAAqB,IAAa,CACjD,IAAM,EAAY,KAAK,aAAa,iBAAiB,CAAC,EAAE,OAAS,EAC3D,EAAY,KAAK,aAAa,iBAAiB,CAAC,EAAE,OAAU,EAElE,OAAO,EAAS,EAAa,CACjC,CAYA,0BAA+C,CAC3C,OAAO,KAAK,gBAAgB,WAAW,CAC3C,CAQA,0BAA+C,CAC3C,OAAO,KAAK,gBAAgB,WAAW,CAC3C,CAQA,sBAA2C,CACvC,OAAO,KAAK,YAAY,WAAW,CACvC,CACJ,EAEM,EAAuB,EAAS,YAAY,EC5d5C,OAAN,cAAqB,CAAe,CAEhC,QAQA,iBAAoC,GACpC,uBAAyC,GACzC,yBAAsE,GAAkB,KAAK,mBAAmB,CAAC,EAWjH,YAAY,EAAoB,EAAyB,CACrD,MAAM,CAAO,EAEb,KAAK,iBAAiB,IAAI,EAAQ,EAQlC,IAAM,EAAsB,KAAK,SAAS,YAAc,GAAc,SACtE,KAAK,QAAU,IAAI,EAAa,CAAmB,EACnD,KAAK,aAAa,KAAK,QAAS,CAC5B,UAAW,EAAU,MACrB,mBAAoB,EACxB,CAAC,EACD,KAAK,QAAQ,0BAA4B,KAAK,aAAa,CAAC,EAC5D,KAAK,QAAQ,8BAAgC,KAAK,eAAe,CAAC,EAClE,KAAK,QAAQ,8BAAgC,KAAK,eAAe,CAAC,EAClE,KAAK,QAAQ,6BAA8B,GAAkB,KAAK,oBAAoB,CAAC,CAAC,EAKpF,KAAK,SAAS,QAAU,IAAA,IACxB,KAAK,QAAQ,SAAS,KAAK,SAAS,KAAK,EAG7C,KAAK,WAAW,CAAO,CAC3B,CAQA,WAA0B,CACtB,OAAO,KAAK,OAChB,CASA,cAAc,EAAoB,CAC9B,GAAI,CAAC,KAAK,QACN,MAAU,MAAM,gCAAgC,EAKpD,OAFA,KAAK,QAAQ,QAAQ,CAAC,CAAC,QAAQ,CAAI,EAE5B,IACX,CAWA,SAAS,EAAqB,CAI1B,MAHA,MAAK,SAAS,MAAQ,EACtB,KAAK,QAAQ,SAAS,CAAK,EAEpB,IACX,CASA,iBAAkC,CAM9B,EAAM,mBAAmB,KAAK,QAAS,YAAc,GAAkB,KAAK,YAAY,CAAC,CAAC,EAM1F,EAAM,mBAAmB,KAAK,QAAS,YAAa,KAAK,wBAAwB,EACjF,EAAY,eAAe,KAAK,QAAS,CACrC,aAA0B,KAAK,oBAAoB,EACnD,gBAAmC,KAAK,kBAAkB,EAC1D,WAAY,EAA0B,IAA2B,KAAK,gBAAgB,CAAO,CACjG,CAAC,CACL,CAOA,iBAA2B,EAAsB,CAC7C,KAAK,QAAQ,aAAa,CAAK,CACnC,CAOA,mBAA6B,EAAsB,CAC/C,KAAK,QAAQ,eAAe,CAAK,CACrC,CAOA,mBAA6B,EAAsB,CAC/C,KAAK,QAAQ,eAAe,CAAK,CACrC,CAYA,qBAA+B,EAA0B,CACrD,KAAK,QAAQ,uBAAuB,IAAU,YAAc,iBAAmB,iBAAiB,EAChG,KAAK,QAAQ,uBAAuB,IAAU,YAAc,iBAAmB,iBAAiB,CACpG,CAOA,YAAsB,EAAuB,CACzC,KAAK,QAAQ,UAAU,CAAM,CACjC,CAOA,UAAmB,CACf,OAAO,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAClD,CAOA,qBAAwC,CACpC,OAAO,KAAK,QAAQ,mBAAmB,CAC3C,CAOA,cAAiC,CAC7B,OAAO,KAAK,QAAQ,UAAU,CAClC,CAOA,WAAqB,EAA0B,CAC3C,KAAK,aAAa,EAAS,CAAE,UAAW,EAAU,MAAO,CAAC,CAC9D,CAQA,kBAAkB,EAA2B,CACzC,OAAO,IAAU,KAAK,OAC1B,CAYA,oBAA4B,EAAqB,CAC7C,IAAM,EAAS,EAAE,SAAW,KAAO,KAAO,EAAI,OAAO,OAAO,EAAE,MAAM,EAChE,QAAU,KAAK,yBAAyB,CAAM,GAIlD,IAAI,KAAK,eAAe,IAAM,YAAa,CACvC,KAAK,eAAe,KAAK,iBAAiB,EAC1C,MACJ,CAEK,KAAK,cAAc,GAGxB,KAAK,eAAe,CALpB,CAMJ,CASA,yBAAiC,EAAyB,CACtD,IAAM,EAAqC,CACvC,KAAK,QAAQ,yBAAyB,EACtC,KAAK,QAAQ,yBAAyB,EACtC,KAAK,QAAQ,qBAAqB,CACtC,EACA,IAAK,IAAM,KAAO,EACd,GAAI,GAAO,EAAI,OAAO,SAAS,EAAK,CAAM,EACtC,MAAO,GAGf,MAAO,EACX,CASA,mBAA2B,EAAqB,CAC5C,KAAK,iBAAmB,EAAE,QAC9B,CASA,qBAAwC,CACpC,IAAM,EAAU,KAAK,aAAa,EASlC,MAAO,CANH,QAAa,GACb,YAAa,KAAK,MAAM,EACxB,YAAa,EAAU,EAAQ,MAAM,EAAI,GACzC,MAAa,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAGvC,CACrB,CASA,mBAA4C,CACxC,IAAM,EAAQ,KAAK,iBACnB,KAAK,iBAAmB,GAExB,IAAM,EAAU,KAAK,aAAa,EAElC,GAAI,CAAC,GAAS,CAAC,EACX,MAAO,GAGX,KAAK,uBAAyB,EAAQ,MAAM,EAC5C,EAAgB,IAAI,EAAQ,MAAM,EAAG,CAAO,CAChD,CAUA,gBAAwB,EAAwB,CAC5C,EAAgB,OAAO,KAAK,sBAAsB,EAClD,KAAK,uBAAyB,GAE1B,GAAW,KAAK,aAAa,IAAM,MACnC,KAAK,aAAa,CAE1B,CACJ,EAEM,EAAiB,EAAS,MAAM,EC1WtC,EAAM,SAAS,GAAY,GAAa,GAAU,EAAU,EAS5D,IAAM,GAAoB,GAOpB,GAAoB,GAqDpB,YAAN,cAA0B,CAA0B,CAuBhD,cAA+C,KAQ/C,MAAuB,IAAI,EAK3B,WAAoC,KACpC,YAAqC,KAQrC,YAAY,EAA8B,CACtC,MAAM,CAAO,EAKb,KAAK,eAAiB,aACtB,KAAK,cAAgB,GACrB,KAAK,mBAAqB,KAC1B,KAAK,aAAe,GAKpB,KAAK,kBAAkB,QAAQ,EAK/B,KAAK,mBAAmB,aAAa,EACrC,KAAK,YAAY,EAEjB,KAAK,MAAM,YAAY,QAAQ,EAC/B,KAAK,MAAM,mBAAmB,aAAa,EAC3C,KAAK,MAAM,YAAY,EACvB,KAAK,WAAW,KAAK,YAAY,CACrC,CAUA,KAAe,EAAwB,CACnC,MAAM,KAAK,CAAO,EAElB,IAAM,EAAO,GAAW,KAAK,WAAW,EAAI,EAG5C,OAFA,EAAI,KAAK,YAAY,EAAM,KAAK,MAAM,WAAW,EAAI,CAAE,EAEhD,IACX,CAUA,aAAuB,EAAmC,CAmBtD,OAlBA,MAAM,aAAa,CAAO,EAEtB,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,aAAe,IAAA,IACvB,KAAK,cAAc,EAAQ,UAAU,EAGrC,EAAQ,kBAAoB,IAAA,IAC5B,KAAK,mBAAmB,EAAQ,eAAe,EAG/C,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGhC,IACX,CASA,WAAmB,EAA2C,CAK1D,GAAI,CAAC,KAAK,MACN,OAGJ,IAAM,EAAM,IAAgB,WACtB,IAAI,EAAK,CAAE,KAAM,QAAS,QAAS,EAAG,WAAY,EAAK,CAAC,EACxD,IAAI,EAAK,CAAE,KAAM,QAAS,QAAS,EAAG,WAAY,EAAK,CAAC,EAE9D,KAAK,MAAM,iBAAiB,CAAG,CACnC,CAOA,YAA8B,CAC1B,OAAO,KAAK,eAAiB,UACjC,CAUA,eAAe,EAA2C,CAQtD,OAPI,KAAK,eAAiB,EACf,MAGX,KAAK,aAAe,EACpB,KAAK,WAAW,CAAW,EAEpB,KACX,CAOA,gBAAyC,CACrC,OAAO,KAAK,YAChB,CAUA,cAAc,EAAsB,CAGhC,MAFA,MAAK,YAAc,EAEZ,IACX,CAOA,cAAwB,CACpB,OAAO,KAAK,WAChB,CAUA,mBAAmB,EAAqB,CAMpC,MALA,MAAK,iBAAmB,EAExB,KAAK,YAAY,mBAAmB,CAAK,EACzC,KAAK,aAAa,mBAAmB,CAAK,EAEnC,IACX,CASA,aAAa,EAAkB,CAG3B,MAFA,MAAK,WAAa,EAEX,IACX,CAWA,gBAAgB,EAAuC,CAGnD,MAFA,MAAK,cAAgB,EAEd,IACX,CAQA,aAAsB,CAClB,OAAO,KAAK,cAAgB,KAAK,cAAc,EAAI,KAAK,UAC5D,CAUA,QAAQ,EAAuB,CAG3B,OAFA,KAAK,MAAM,aAAa,CAAI,EAErB,IACX,CASA,WAAW,EAAuB,CAG9B,OAFA,KAAK,MAAM,gBAAgB,CAAI,EAExB,IACX,CAUA,SAAS,EAAiB,EAAuB,CAG7C,OAFA,KAAK,MAAM,cAAc,EAAM,CAAO,EAE/B,IACX,CAOA,UAA6B,CACzB,OAAO,KAAK,MAAM,cAAc,CACpC,CAQA,eAAuD,CACnD,OAAO,KAAK,MAAM,iBAAiB,CACvC,CAUA,aAAoB,CAIhB,OAHA,KAAK,MAAM,SAAS,EACpB,KAAK,MAAM,kBAAkB,EAEtB,IACX,CAUA,eAAe,EAAsC,CACjD,OAAO,KAAK,MAAM,WAAW,CAAW,GAAK,IACjD,CAeA,aAAa,EAAuB,EAA8B,CAK9D,OAJK,KAAK,aAIH,EAAgB,EAAe,EAAI,GAAoB,CAClE,CAMA,cAA6B,CACzB,GAAI,KAAK,YAAc,KAAK,YACxB,OAGJ,IAAM,EAAO,IAAI,EAAO,CAAE,MAAO,YAAa,CAAC,EACzC,EAAQ,IAAI,EAAO,CAAE,MAAO,aAAc,CAAC,EAEjD,IAAK,IAAM,IAAU,CAAC,EAAM,CAAK,EACzB,KAAK,mBAAqB,MAC1B,EAAO,mBAAmB,KAAK,gBAAgB,EAGnD,EAAO,mBAAmB,MAAM,EAChC,EAAO,YAAY,EACnB,EAAO,YAAY,EACnB,EAAO,gBAAgB,GAAG,EAE1B,EAAO,YAAY,EAGnB,EAAO,UAAU,CAAC,EAGtB,EAAK,GAAG,SAAU,KAAK,WAAW,EAClC,EAAM,GAAG,SAAU,KAAK,YAAY,EAKpC,IAAM,EAAU,KAAK,WAAW,EAAI,EACpC,EAAI,KAAK,YAAY,EAAS,EAAK,WAAW,EAAI,CAAE,EACpD,EAAI,KAAK,YAAY,EAAS,EAAM,WAAW,EAAI,CAAE,EAErD,KAAK,WAAa,EAClB,KAAK,YAAc,CACvB,CAeA,cAAc,EAAiB,EAAsB,CACjD,IAAM,EAAW,KAAK,WAAW,EAC3B,EAAW,EAAW,KAAK,UAAU,EAAI,KAAK,SAAS,EACvD,EAAY,EAAW,KAAK,SAAS,EAAI,KAAK,UAAU,EACxD,EAAW,EAAW,EAAI,EAqBhC,OAjBI,GACA,KAAK,MAAM,KAAK,CAAC,EACjB,KAAK,MAAM,KAAK,CAAO,EACvB,KAAK,MAAM,SAAS,CAAS,EAC7B,KAAK,MAAM,UAAU,CAAQ,EAC7B,KAAK,MAAM,UAAU,IAAI,EAAO,EAAQ,EAAG,EAAG,CAAC,CAAC,IAEhD,KAAK,MAAM,KAAK,CAAO,EACvB,KAAK,MAAM,KAAK,CAAC,EACjB,KAAK,MAAM,SAAS,CAAQ,EAC5B,KAAK,MAAM,UAAU,CAAS,EAC9B,KAAK,MAAM,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAM,CAAC,GAGpD,KAAK,YAAY,EACjB,KAAK,aAAa,EAAU,EAAW,CAAO,EAEvC,IACX,CAaA,aAAqB,EAAkB,EAAmB,EAAuB,CAC7E,GAAI,CAAC,KAAK,aAAe,GAAW,EAAG,CACnC,KAAK,WAAW,EAEhB,MACJ,CAEA,KAAK,aAAa,EAElB,IAAM,EAAO,KAAK,WACZ,EAAQ,KAAK,YACb,EAAW,KAAK,WAAW,EAEjC,EAAK,SAAS,EAAW,WAAa,YAAY,EAClD,EAAM,SAAS,EAAW,aAAe,aAAa,EAEtD,EAAK,WAAW,EAAI,EACpB,EAAM,WAAW,EAAI,EAErB,KAAK,cAAc,EAEnB,IAAM,EAAW,EAAW,EAE5B,IAAK,IAAM,IAAU,CAAC,EAAM,CAAK,EACzB,GAEA,EAAO,WAAW,CAAE,MAAO,EAAG,OAAQ,CAAQ,CAAC,EAC/C,EAAO,WAAW,CAAE,MAAO,OAAO,UAAW,OAAQ,CAAQ,CAAC,EAC9D,EAAO,KAAK,CAAC,EACb,EAAO,SAAS,CAAS,EACzB,EAAO,UAAU,CAAO,IAExB,EAAO,WAAW,CAAE,MAAO,EAAS,OAAQ,CAAE,CAAC,EAC/C,EAAO,WAAW,CAAE,MAAO,EAAS,OAAQ,OAAO,SAAU,CAAC,EAC9D,EAAO,KAAK,CAAC,EACb,EAAO,UAAU,CAAS,EAC1B,EAAO,SAAS,CAAO,GAI3B,GACA,EAAK,KAAK,CAAC,EACX,EAAM,KAAK,CAAQ,IAEnB,EAAK,KAAK,CAAC,EACX,EAAM,KAAK,CAAQ,EAE3B,CAKA,YAA2B,CACvB,KAAK,YAAY,WAAW,EAAK,EACjC,KAAK,aAAa,WAAW,EAAK,CACtC,CAQA,YAAqB,CACjB,OAAO,KAAK,WAAW,EAAI,KAAK,MAAM,aAAa,EAAI,KAAK,MAAM,cAAc,CACpF,CAQA,eAAgC,CAC5B,OAAO,KAAK,WAAW,EAAI,KAAK,MAAM,gBAAgB,EAAI,KAAK,MAAM,iBAAiB,CAC1F,CAQA,cAAc,EAAqB,CAC3B,KAAK,WAAW,EAChB,KAAK,MAAM,aAAa,CAAK,EAE7B,KAAK,MAAM,cAAc,CAAK,CAEtC,CASA,aAAoB,CAIhB,OAHA,KAAK,MAAM,cAAc,CAAC,EAC1B,KAAK,MAAM,aAAa,CAAC,EAElB,IACX,CAOA,eAAsB,CAClB,IAAM,EAAO,KAAK,WACZ,EAAQ,KAAK,YAEnB,GAAI,CAAC,GAAQ,CAAC,EACV,OAGJ,IAAM,EAAS,KAAK,WAAW,EAE/B,EAAK,WAAW,EAAS,CAAC,EAC1B,EAAM,WAAW,EAAS,KAAK,cAAc,EAAI,CAAC,CACtD,CASA,SAAiB,EAAqB,CAClC,KAAK,cAAc,KAAK,WAAW,EAAI,CAAK,EAC5C,KAAK,cAAc,CACvB,CAMA,gBAAkC,CAC9B,KAAK,SAAS,CAAC,KAAK,YAAY,CAAC,CACrC,EAKA,iBAAmC,CAC/B,KAAK,SAAS,KAAK,YAAY,CAAC,CACpC,EAUA,WAAW,EAA2B,CAClC,IAAM,EAAc,KAAK,MAAM,WAAW,EAE1C,GAAI,CAAC,EACD,OAGJ,IAAM,EAAW,KAAK,WAAW,EAC3B,EAAO,EAAI,OAAO,eAAe,CAAW,EAC5C,EAAO,EAAI,OAAO,eAAe,CAAW,EAE5C,EAAY,EAAW,EAAK,IAAM,EAAK,KACvC,EAAU,EAAW,EAAK,OAAS,EAAK,MACxC,EAAY,EAAW,EAAK,IAAM,EAAK,KACvC,EAAU,EAAW,EAAK,OAAS,EAAK,MAE1C,EAAQ,EAER,EAAY,EACZ,EAAQ,EAAY,EACb,EAAU,IACjB,EAAQ,EAAU,GASlB,IAAU,IACV,KAAK,cAAc,KAAK,WAAW,EAAI,CAAK,EAC5C,KAAK,cAAc,EAE3B,CAUA,cAAc,EAAqC,CAC/C,GAAI,CAAC,EAAI,OAAO,OAAO,CAAM,EACzB,MAAO,GAGX,IAAM,EAAS,EAAI,OAAO,OAAO,CAAM,EAEvC,IAAK,IAAM,IAAS,CAAC,KAAK,WAAY,KAAK,WAAW,EAAG,CACrD,IAAM,EAAU,GAAO,WAAW,GAAK,KAEvC,GAAI,GAAW,EAAI,OAAO,SAAS,EAAS,CAAM,EAC9C,MAAO,EAEf,CAEA,MAAO,EACX,CAaA,YAA6B,CACzB,KAAK,MAAM,QAAQ,EACnB,KAAK,YAAY,QAAQ,EACzB,KAAK,aAAa,QAAQ,EAE1B,MAAM,WAAW,CACrB,CACJ,EAEM,EAAsB,EAAS,WAAW,ECxvBhD,SAAgB,oBACZ,EACA,EACA,EACA,EACQ,CAUR,OATgB,EAAI,QAAQ,EAAK,IAAM,CACnC,OAAQ,EAAR,CACI,IAAK,MAAU,MAAO,GACtB,IAAK,SAAU,OAAO,IAAM,EAC5B,IAAK,QAAU,OAAO,EAAI,EAC1B,IAAK,OAAU,OAAO,EAAI,CAC9B,CACJ,CAEO,CAAA,CAAQ,OAAO,CAAW,CACrC,CCRA,IAAM,EAAuB,IAWvB,EAAkB,GAGlB,GAA0B,GAU1B,GAAiB,EASjB,GAAoB,GA2JpB,aAAN,cAA2B,CAAU,CACjC,SAA2B,EAC3B,YAA8B,EAC9B,MAAyB,QAQzB,aAAc,CACV,MAAM,EAEN,KAAK,mBAAmB,2CAA2C,EACnE,KAAK,cAAc,aAAa,EAAqB,iBAAiB,EAAqB,kBAAkB,EAAqB,QAAQ,EAC1I,KAAK,iBAAiB,MAAM,EAI5B,KAAK,UAAU,CAAC,CACpB,CAaA,QAAQ,EAAiB,EAAoB,EAAqB,CAS9D,MARA,MAAK,SAAW,EAChB,KAAK,YAAc,EACnB,KAAK,MAAQ,EAET,KAAK,WAAW,GAChB,KAAK,iBAAiB,EAGnB,IACX,CAUA,WAAW,EAAuB,CAI9B,OAHA,MAAM,WAAW,CAAO,EACxB,KAAK,iBAAiB,EAEf,IACX,CAeA,kBAAiC,CAC7B,IAAM,EAAY,4CAkBlB,OAjBiB,KAAK,QAAU,QAAU,KAAK,QAAU,QAGrD,KAAK,aAAa,EAAG,KAAK,QAAQ,EAE3B,KAAK,iBAAiB,CACzB,IAAW,IACX,OAAW,OACX,KAAW,KAAK,QAAU,OAAS,IAAM,OACzC,MAAW,KAAK,QAAU,OAAS,IAAM,OACzC,MAAW,EACX,OAAW,KAAK,YAAc,IAClC,CAAC,IAGL,KAAK,aAAa,KAAK,SAAU,CAAC,EAE3B,KAAK,iBAAiB,CACzB,KAAW,IACX,MAAW,OACX,IAAW,KAAK,QAAU,QAAU,IAAM,OAC1C,OAAW,KAAK,QAAU,QAAU,IAAM,OAC1C,MAAW,KAAK,YAAc,KAC9B,OAAW,CACf,CAAC,EACL,CACJ,EAaM,GAAN,MAAM,sBAAsB,CAAU,CAGlC,OAAwB,UAAY,EAMpC,aAAc,CACV,MAAM,EAEN,KAAK,mBAAmB,0CAA0C,EAClE,KAAK,iBAAiB,MAAM,EAC5B,KAAK,UAAU,CAAC,EAChB,KAAK,WAAW,EAAK,CACzB,CAaA,QAAQ,EAAmB,EAAqB,EAAyB,CAerE,OAdI,GACA,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,CAAS,EACnB,KAAK,SAAS,CAAW,EACzB,KAAK,UAAU,cAAc,SAAS,IAEtC,KAAK,KAAK,CAAS,EACnB,KAAK,KAAK,CAAC,EACX,KAAK,SAAS,cAAc,SAAS,EACrC,KAAK,UAAU,CAAW,GAG9B,KAAK,WAAW,EAAI,EAEb,IACX,CAOA,MAAa,CAGT,OAFA,KAAK,WAAW,EAAK,EAEd,IACX,CACJ,EAYM,YAAN,cAA0B,CAAU,CAGhC,aAAc,CACV,MAAM,EAEN,KAAK,mBAAmB,+BAA+B,EACvD,KAAK,iBAAiB,MAAM,EAC5B,KAAK,UAAU,CAAC,EAChB,KAAK,WAAW,EAAK,CACzB,CAUA,SAAS,EAAwB,CAO7B,OANA,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,CAAC,EACX,KAAK,SAAS,EAAM,SAAS,CAAC,EAC9B,KAAK,UAAU,EAAM,UAAU,CAAC,EAChC,KAAK,WAAW,EAAI,EAEb,IACX,CAGA,MAAa,CAGT,OAFA,KAAK,WAAW,EAAK,EAEd,IACX,CACJ,EAwBM,OAAN,cAAqB,CAAyB,CAQ1C,SAAgC,IAAI,EAAY,CAAE,WAAY,EAAM,CAAC,EACrE,SAAoC,CAAC,EACrC,aAAoC,IAAI,EAKxC,gBAA0C,IAAI,EAAe,CAAE,cAAe,EAAK,CAAC,EAIpF,UAAmC,KACnC,WAA+C,IAAI,EAEnD,WAAmC,QACnC,UAAmC,KACnC,YAA8B,IAC9B,sBAAyC,GACzC,sBAAyC,GACzC,WAAmC,IAAI,aAEvC,MAAyB,QACzB,OAA0B,QAC1B,aAAuC,aACvC,YAA+B,GAC/B,SAA4B,GAC5B,WAAmC,SAMnC,aAA6B,IAAI,GAOjC,OAA8B,CAAC,EAK/B,eAA4D,IAAI,IAGhE,WAA4B,IAAI,EAUhC,WAA4B,IAAI,EAIhC,YAAwC,KAKxC,kBAAqC,GAGrC,aAAgC,GAChC,YAAqC,IAAI,GACzC,UAAiC,IAAI,YACrC,cAA2C,CAAC,EAC5C,iBAA+C,KAM/C,qBAAoD,KAKpD,eAAkC,GAClC,iBAAmC,GAKnC,kBAAkE,KAOlE,YAAY,EAAyB,CACjC,MAAM,CAAO,EAOb,KAAK,iBAAiB,IAAI,EAAK,CAAE,KAAM,QAAS,QAAS,EAAG,WAAY,EAAK,CAAC,CAAC,EAC/E,KAAK,mBAAmB,mCAAmC,EAC3D,KAAK,sBAAwB,EAAa,SAAS,CAAC,CAAC,IAAI,qBACzD,KAAK,iBAAiB,EAKtB,KAAK,iBAAiB,CAAE,MAAO,EAAG,OAAQ,CAAgB,CAAC,EAC3D,KAAK,QAAQ,CAAC,CAAC,QAAQ,SAAS,EAQhC,KAAK,SAAS,mBAAmB,mCAAmC,EACpE,KAAK,SAAS,oBAAsB,KAAK,iBAAiB,CAAC,EAK3D,KAAK,WAAW,iBAAiB,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,CAAC,EAG3E,KAAK,WAAW,mBAAmB,mCAAmC,EACtE,KAAK,WAAW,YAAY,EAG5B,KAAK,WAAW,UAAU,CAAC,EAS3B,KAAK,WAAW,iBAAiB,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,CAAC,EAC3E,KAAK,WAAW,mBAAmB,aAAa,EAChD,KAAK,WAAW,YAAY,EAC5B,KAAK,WAAW,UAAU,CAAC,EAC3B,KAAK,WAAW,iBAAiB,MAAM,EAMvC,KAAK,mBAAqB,CACjB,KAAK,wBAIV,KAAK,sBAAwB,EAAa,SAAS,CAAC,CAAC,IAAI,qBACzD,KAAK,iBAAiB,EAC1B,CAAC,EAED,KAAK,mBAAmB,CAAO,CACnC,CAUA,mBAA2B,EAA+B,CACjD,KAgDL,IA5CI,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGnC,EAAQ,WAAa,IAAA,IACrB,KAAK,YAAY,EAAQ,QAAQ,EAGjC,EAAQ,aAAe,IAAA,IACvB,KAAK,cAAc,EAAQ,UAAU,EAGrC,EAAQ,uBAAyB,IAAA,IACjC,KAAK,wBAAwB,EAAQ,oBAAoB,EAGzD,EAAQ,OAAS,IAAA,IACjB,KAAK,QAAQ,EAAQ,IAAI,EAGzB,EAAQ,QAAU,IAAA,IAClB,KAAK,SAAS,EAAQ,KAAK,EAG3B,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,aAAe,IAAA,IACvB,KAAK,cAAc,EAAQ,UAAU,EAGrC,EAAQ,UAAY,IAAA,IACpB,KAAK,WAAW,EAAQ,OAAO,EAG/B,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGnC,EAAQ,QAAU,IAAA,GAKlB,IAAK,IAAM,KAAQ,EAAQ,MACnB,aAAgB,EAChB,KAAK,QAAQ,CAAI,EAO7B,KAAK,eAAe,GAAS,SAAS,CALT,CAMjC,CAYA,mBAAmB,EAAqB,CAKpC,OAJA,KAAK,mBAAmB,CAAK,EAC7B,KAAK,WAAW,mBAAmB,CAAK,EACxC,KAAK,SAAS,mBAAmB,CAAK,EAE/B,IACX,CAYA,KAAe,EAAwB,CACnC,MAAM,KAAK,CAAO,EAElB,IAAM,EAAO,GAAW,KAAK,WAAW,EAAI,EAO5C,EAAI,KAAK,YAAY,EAAM,KAAK,SAAS,WAAW,EAAI,CAAE,EAC1D,EAAI,KAAK,YAAY,EAAM,KAAK,WAAW,WAAW,EAAI,CAAE,EAC5D,EAAI,KAAK,YAAY,EAAM,KAAK,WAAW,WAAW,EAAI,CAAE,EAE5D,IAAM,EAAO,KAAK,SAAS,eAAe,EAAI,EAa9C,OAZA,EAAI,KAAK,YAAY,EAAM,KAAK,WAAW,WAAW,EAAI,CAAE,EAC5D,EAAI,KAAK,YAAY,EAAM,KAAK,UAAU,WAAW,EAAI,CAAE,EAC3D,EAAI,KAAK,YAAY,EAAM,KAAK,YAAY,WAAW,EAAI,CAAE,EAE7D,EAAM,mBAAmB,KAAM,UAAW,KAAK,gBAAgB,EAI3D,KAAK,cACL,KAAK,cAAc,EAGhB,IACX,CAcA,YAA6B,CACzB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,qBAAuB,KAE5B,KAAK,SAAS,QAAQ,EACtB,KAAK,WAAW,QAAQ,EACxB,KAAK,WAAW,QAAQ,EACxB,KAAK,WAAW,QAAQ,EACxB,KAAK,UAAU,QAAQ,EACvB,KAAK,YAAY,QAAQ,EAEzB,MAAM,WAAW,CACrB,CAMA,kBAAiC,CAC7B,GAAI,CAAC,KAAK,sBAAuB,CAC7B,KAAK,YAAY,EAEjB,MACJ,CAEA,IAAM,EAAO,qDAMb,KAAK,UAAU,CACX,UAAc,KAAK,QAAU,QAAU,EAAO,OAC9C,aAAc,KAAK,QAAU,QAAU,EAAO,OAC9C,WAAc,KAAK,QAAU,OAAU,EAAO,OAC9C,YAAc,KAAK,QAAU,OAAU,EAAO,MAClD,CAAC,CACL,CASA,aAAa,EAA0B,CAKnC,MAJA,MAAK,WAAa,EAElB,KAAK,eAAe,EAEb,IACX,CAOA,cAA6B,CACzB,OAAO,KAAK,UAChB,CAUA,YAAY,EAAyB,CAKjC,MAJA,MAAK,UAAY,EAEjB,KAAK,eAAe,EAEb,IACX,CAOA,aAA6B,CACzB,OAAO,KAAK,SAChB,CAUA,cAAc,EAAkB,CAK5B,MAJA,MAAK,YAAc,EAEnB,KAAK,eAAe,EAEb,IACX,CAOA,eAAwB,CACpB,OAAO,KAAK,WAChB,CAWA,wBAAwB,EAAqB,CAQzC,MAPA,MAAK,sBAAwB,GAC7B,KAAK,sBAAwB,EAE7B,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEb,IACX,CAOA,wBAAkC,CAC9B,OAAO,KAAK,qBAChB,CAWA,QAAQ,EAAqB,CAezB,MAdA,MAAK,MAAQ,EAIb,KAAK,SAAS,YAAY,EAEtB,KAAK,cACL,KAAK,kBAAoB,IAG7B,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EAEb,IACX,CAOA,SAAmB,CACf,OAAO,KAAK,KAChB,CAUA,SAAS,EAAsB,CAK3B,MAJA,MAAK,OAAS,EAEd,KAAK,eAAe,EAEb,IACX,CAOA,UAAoB,CAChB,OAAO,KAAK,MAChB,CAWA,eAAe,EAAmC,CAK9C,MAJA,MAAK,aAAe,EAEpB,KAAK,eAAe,EAEb,IACX,CAOA,gBAAiC,CAC7B,OAAO,KAAK,YAChB,CAUA,aAAa,EAA2B,CAKpC,MAJA,MAAK,WAAa,EAElB,KAAK,eAAe,EAEb,IACX,CAOA,cAA6B,CACzB,OAAO,KAAK,UAChB,CAWA,cAAc,EAAsB,CAYhC,OATI,GAAS,CAAC,KAAK,cACf,KAAK,kBAAoB,IAG7B,KAAK,YAAc,EACnB,KAAK,SAAS,cAAc,CAAK,EAEjC,KAAK,eAAe,EAEb,IACX,CAOA,cAAwB,CACpB,OAAO,KAAK,WAChB,CAUA,WAAW,EAAsB,CAY7B,MAXA,MAAK,SAAW,EAKZ,KAAK,cACL,KAAK,kBAAoB,IAG7B,KAAK,eAAe,EAEb,IACX,CAOA,WAAqB,CACjB,OAAO,KAAK,QAChB,CAaA,eAAe,EAAsB,CAiBjC,OAhBI,KAAK,eAAiB,IAI1B,KAAK,aAAe,EAEhB,CAAC,KAAK,WAAW,KAIjB,EACA,KAAK,cAAc,EAEnB,KAAK,eAAe,GANb,IAUf,CAOA,eAAyB,CACrB,OAAO,KAAK,YAChB,CAuBA,QAAQ,EAA0C,CAC9C,IAAM,EAAS,aAAe,EAAY,EAAM,KAAK,oBAAoB,CAAG,EAO5E,OALA,KAAK,OAAO,KAAK,CAAM,EACvB,KAAK,WAAW,aAAa,CAAM,EAEnC,KAAK,eAAe,EAEb,IACX,CAaA,oBAA4B,EAAuC,CAC/D,IAAM,EAAS,IAAI,EAAO,CAAE,MAAO,EAAW,KAAM,CAAC,EAOrD,OALA,EAAO,QAAQ,EAAI,EACnB,EAAQ,OAAO,EAAQ,EAAW,KAAK,EACvC,EAAO,GAAG,SAAU,EAAW,MAAM,EACrC,KAAK,eAAe,IAAI,EAAQ,CAAU,EAEnC,CACX,CASA,WAAW,EAAyB,CAChC,IAAM,EAAM,KAAK,OAAO,QAAQ,CAAM,EAYtC,OAVI,EAAM,EACC,MAGX,KAAK,OAAO,OAAO,EAAK,CAAC,EACzB,KAAK,eAAe,OAAO,CAAM,EACjC,KAAK,WAAW,gBAAgB,CAAM,EAEtC,KAAK,eAAe,EAEb,KACX,CAmBA,iBAAiB,EAAgC,CAa7C,OAZI,KAAK,aACL,KAAK,WAAW,gBAAgB,KAAK,WAAW,EAGpD,KAAK,YAAc,EAEf,GACA,KAAK,WAAW,aAAa,CAAM,EAGvC,KAAK,eAAe,EAEb,IACX,CAOA,kBAAqC,CACjC,OAAO,KAAK,WAChB,CAcA,kBAA0B,EAAqC,CAC3D,GAAI,CAAC,EAAI,OAAO,OAAO,CAAM,EACzB,MAAO,GAGX,IAAM,EAAe,EAAI,OAAO,OAAO,CAAM,EAE7C,IAAK,IAAM,KAAS,KAAK,SAAU,CAC/B,IAAM,EAAY,EAAM,OAAO,WAAW,EAE1C,GAAI,GAAa,EAAI,OAAO,SAAS,EAAW,CAAY,EACxD,MAAO,EAEf,CAEA,IAAM,EAAc,KAAK,WAAW,WAAW,EAU/C,MAJA,GAJI,GAAe,EAAI,OAAO,SAAS,EAAa,CAAY,GAI5D,KAAK,SAAS,cAAc,CAAM,EAK1C,CAwBA,mBAAmB,EAAuC,EAAoD,CAC1G,KAAK,uBAAuB,EAC5B,KAAK,qBAAuB,KAK5B,IAAM,eAAkB,GAAwB,CAExC,EAAE,UAAY,KAAK,kBAAkB,EAAE,MAAM,GAIjD,EAAa,CAAC,CAClB,EAEA,EAAM,mBAAmB,KAAM,YAAa,cAAc,EAE1D,IAAM,EAA+B,KACrB,EAAM,sBAAsB,KAAM,YAAa,cAAc,CAC7E,EAEA,GAAI,EAAoB,CACpB,IAAM,iBAAoB,GAAwB,CAC1C,KAAK,kBAAkB,EAAE,MAAM,GAInC,EAAmB,CAAC,CACxB,EAEA,EAAM,mBAAmB,KAAM,WAAY,gBAAgB,EAC3D,EAAU,SAAiB,EAAM,sBAAsB,KAAM,WAAY,gBAAgB,CAAC,CAC9F,CAQA,MANA,MAAK,yBAAmC,CACpC,IAAK,IAAM,KAAY,EACnB,EAAS,CAEjB,EAEO,IACX,CAOA,aAAwB,CACpB,OAAO,KAAK,SAAS,IAAI,GAAS,EAAM,EAAE,CAC9C,CAOA,kBAAkC,CAC9B,OAAO,KAAK,SAChB,CAUA,iBAAiB,EAAqB,CAClC,OAAO,KAAK,UAAU,CAAE,CAAC,EAAE,aAAa,YAAc,EAC1D,CASA,aAAa,EAAoB,CAC7B,OAAO,KAAK,UAAU,CAAE,CAAC,EAAE,MAAQ,EACvC,CAcA,kBAAkB,EAAY,EAAyB,CACnD,IAAM,EAAQ,KAAK,UAAU,CAAE,EAO/B,OALI,IACA,EAAM,UAAY,EAClB,EAAM,OAAO,QAAQ,CAAC,CAAC,YAAY,CAAS,GAGzC,IACX,CAUA,iBAAiB,EAAoB,CACjC,OAAO,KAAK,UAAU,CAAE,CAAC,EAAE,OAAO,MAAM,GAAK,EACjD,CASA,UAAkB,EAA6B,CAC3C,OAAO,KAAK,SAAS,KAAK,GAAS,EAAM,KAAO,CAAE,GAAK,IAC3D,CAOA,aAAuC,CACnC,OAAO,KAAK,YAAc,KAAO,KAAO,KAAK,UAAU,KAAK,SAAS,CACzE,CAiBA,eAAe,EAAY,EAAc,EAAuC,CAM5E,IAAI,EAAY,IAAI,GAAU,EAAM,CAChC,MAAW,GAAa,OAAS,IAAA,GACjC,UAAW,GAAa,SAC5B,CAAC,EAED,EAAU,UAAU,KAAK,uBAAuB,CAAW,CAAC,EAExD,GAAa,SACb,EAAQ,OAAO,EAAW,EAAY,OAAO,EAGjD,EAAU,GAAG,aAAgB,KAAK,aAAa,CAAS,CAAC,EAEzD,IAAM,EAAc,EAAU,eAAe,GAAK,IAAA,GAO5C,cAAiB,GAAwB,CAC3C,EAAE,eAAe,EACjB,KAAK,YAAY,EAAO,EAAE,QAAS,EAAE,OAAO,CAChD,EAEM,EAAkB,CACpB,KACA,OAAQ,EACR,cACA,OACA,cACA,oBAAqB,cACrB,UAAW,EACf,EAEI,GACA,EAAY,GAAG,aAAgB,KAAK,KAAK,WAAY,CAAE,CAAC,EAG5D,EAAM,mBAAmB,EAAW,cAAe,aAAa,EAEhE,KAAK,SAAS,KAAK,CAAK,EAIxB,IAAM,EAAa,KAAK,YAAc,KAoBtC,OAlBI,IACA,EAAU,YAAY,EAAI,EAC1B,KAAK,UAAY,GAGrB,KAAK,aAAa,UAAU,CAAS,EACrC,KAAK,gBAAgB,IAAI,CAAS,EAClC,KAAK,SAAS,QAAQ,CAAS,EAE/B,EAAU,QAAQ,CAAC,CAAC,QAAQ,KAAK,EACjC,EAAU,QAAQ,CAAC,CAAC,YAAY,CAAU,EAItC,KAAK,cAAgB,KAAK,WAAW,GACrC,KAAK,cAAc,KAAK,KAAK,kBAAkB,CAAK,CAAC,EAGlD,IACX,CAWA,eAAe,EAAkB,CAC7B,IAAM,EAAM,KAAK,SAAS,UAAU,GAAS,EAAM,KAAO,CAAE,EAE5D,GAAI,EAAM,EACN,OAAO,KAGX,IAAM,EAAQ,KAAK,SAAS,GAqB5B,OAnBA,KAAK,aAAa,aAAa,EAAM,MAAM,EAC3C,KAAK,gBAAgB,OAAO,EAAM,MAAM,EACxC,KAAK,SAAS,OAAO,EAAK,CAAC,EAC3B,KAAK,SAAS,WAAW,EAAM,MAAM,EAKrC,EAAM,sBAAsB,EAAM,OAAQ,cAAe,EAAM,mBAAmB,EAKlF,EAAQ,OAAO,EAAM,MAAM,EAEvB,KAAK,YAAc,IACnB,KAAK,UAAY,MAGd,IACX,CAYA,aAAa,EAAY,EAAuB,CAC5C,IAAM,EAAO,KAAK,SAAS,UAAU,GAAS,EAAM,KAAO,CAAE,EAE7D,GAAI,EAAO,EACP,OAAO,KAGX,IAAM,EAAO,KAAK,IAAI,EAAG,KAAK,IAAI,EAAS,KAAK,SAAS,OAAS,CAAC,CAAC,EAEpE,GAAI,IAAS,EACT,OAAO,KAGX,IAAM,EAAQ,KAAK,SAAS,OAAO,EAAM,CAAC,CAAC,CAAC,GAK5C,OAHA,KAAK,SAAS,OAAO,EAAM,EAAG,CAAK,EACnC,KAAK,SAAS,SAAS,EAAM,OAAQ,CAAI,EAElC,IACX,CAWA,eAAe,EAAkB,CAC7B,IAAM,EAAM,KAAK,SAAS,UAAU,GAAS,EAAM,KAAO,CAAE,EAe5D,OAbI,EAAM,EACC,MAOX,KAAK,SAAS,QAAQ,GAAS,EAAM,OAAO,YAAY,EAAK,CAAC,EAC9D,KAAK,SAAS,EAAI,CAAC,OAAO,YAAY,EAAI,EAE1C,KAAK,aAAa,KAAK,SAAS,EAAI,CAAC,MAAM,EAEpC,KACX,CAYA,gBAAgB,EAAkB,CAC9B,IAAM,EAAM,KAAK,SAAS,UAAU,GAAS,EAAM,KAAO,CAAE,EAU5D,OARI,EAAM,EACC,MAGX,KAAK,SAAS,QAAQ,GAAS,EAAM,OAAO,YAAY,EAAK,CAAC,EAC9D,KAAK,SAAS,EAAI,CAAC,OAAO,YAAY,EAAI,EAC1C,KAAK,UAAY,EAEV,KACX,CAiBA,YAAoB,EAAiB,EAAW,EAAiB,CAC7D,IAAM,EAAc,KAAK,YAAY,EAC/B,EAAc,KAAK,YAAY,EAC/B,EAAc,EAAI,QAAQ,EAAM,EAAE,EAClC,UAAe,GAAwB,KAAK,iBAAiB,CAAE,EAI/D,EAAgC,KAAK,SAAS,IAAI,IAAM,CAC1D,KAAS,EAAE,KACX,QAAS,IAAM,EACf,WAAe,KAAK,eAAe,EAAE,EAAE,CAC3C,EAAE,EAII,EAAc,KAAK,QAAU,QAAU,KAAK,QAAU,OACtD,EAAc,EAAW,kBAAoB,wBAC7C,EAAc,EAAW,kBAAoB,yBAE7C,EAA4B,CAC9B,CAAE,KAAM,YAAa,QAAS,CAAE,MAAO,YAAa,MAAO,CAAY,CAAE,EACzE,CAAE,UAAW,EAAK,EAClB,CACI,KAAS,QACT,QAAS,EAAM,aAAa,YAAc,GAC1C,WAAe,KAAK,KAAK,WAAY,EAAM,EAAE,CACjD,EACA,KAAK,cAAc,eAAgB,EAAK,EAAY,UAAW,QAAQ,EACvE,KAAK,cAAc,EAAgB,EAAK,EAAY,UAAW,MAAM,EACrE,KAAK,cAAc,EAAgB,EAAK,EAAY,UAAW,OAAO,EACtE,KAAK,cAAc,YAAgB,EAAK,EAAY,UAAW,KAAK,CACxE,EAIM,EAA8B,KAAK,OACpC,OAAO,GAAQ,KAAK,eAAe,IAAI,CAAI,CAAC,CAAC,CAC7C,IAAI,GAAQ,CACT,IAAM,EAAa,KAAK,eAAe,IAAI,CAAI,EAE/C,MAAO,CAAE,KAAM,EAAW,MAAO,MAAO,EAAW,MAAO,OAAQ,EAAW,MAAO,CACxF,CAAC,EAED,EAAU,OAAS,IACnB,EAAQ,KAAK,CAAE,UAAW,EAAK,CAAC,EAChC,EAAQ,KAAK,CAAE,KAAM,QAAS,QAAS,CAAE,MAAO,QAAS,MAAO,CAAU,CAAE,CAAC,GAGjF,KAAK,aAAa,KAAK,EAAG,EAAG,CAAO,CACxC,CAgBA,cACI,EACA,EACA,EACA,EACA,EACc,CACd,IAAM,EAAU,oBAAoB,EAAK,EAAY,EAAW,CAAK,EAErE,MAAO,CACH,OACA,QAAS,EAAQ,OAAS,EAC1B,WAAe,CACX,IAAK,IAAM,KAAM,EACb,KAAK,KAAK,WAAY,CAAE,CAEhC,CACJ,CACJ,CASA,aAAqB,EAAyB,CAC1C,IAAM,EAAM,KAAK,SAAS,UAAU,GAAS,EAAM,SAAW,CAAM,EAEhE,GAAO,IACP,KAAK,UAAY,KAAK,SAAS,EAAI,CAAC,GACpC,KAAK,gBAAgB,OAAO,CAAG,EAM3B,KAAK,cACL,KAAK,kBAAoB,IAG7B,KAAK,KAAK,aAAc,KAAK,SAAS,EAAI,CAAC,EAAE,EAErD,CAsBA,uBAA+B,EAAyC,CACpE,IAAM,EAAQ,EAAa,iBAAiB,EACtC,EAAM,KAAK,SAAW,KAAK,MAAM,EAAM,eAAiB,CAAC,EAAI,EAAM,eAKnE,EAAe,GAAa,UAAY,EAAM,SAAW,EAmB/D,OAjBI,KAAK,cAAc,EAIZ,KAAK,eAAiB,eACvB,IAAI,EAAO,EAAe,EAAM,EAAG,EAAM,EAAG,EAAM,EAAG,EAAM,CAAC,EAC5D,IAAI,EAAO,EAAM,EAAG,EAAM,EAAG,EAAe,EAAM,EAAG,EAAM,CAAC,EAGlE,KAAK,WAAW,EAGT,IAAI,EAAO,EAAM,EAAG,EAAe,EAAM,EAAG,EAAM,EAAG,EAAM,CAAC,EAKhE,IAAI,EAAO,EAAG,EAAe,EAAM,EAAG,EAAG,EAAM,CAAC,CAC3D,CAWA,yBAA0C,CACtC,IAAM,EAAQ,EAAa,iBAAiB,CAAC,CAAC,eACxC,EAAM,KAAK,SAAW,KAAK,MAAM,EAAQ,CAAC,EAAI,EAMpD,OAJI,KAAK,WAAW,EACT,IAAI,EAAO,EAAM,EAAG,EAAG,EAAM,EAAG,CAAC,EAGrC,IAAI,EAAO,EAAG,EAAM,EAAG,EAAG,EAAM,CAAC,CAC5C,CAWA,iBAAyB,EAAoB,EAAa,EAAmB,CACrE,KAAK,WAAW,GAChB,EAAQ,WAAW,CAAE,MAAO,EAAG,OAAQ,CAAI,CAAC,EAC5C,EAAQ,WAAW,CAAE,MAAO,OAAO,UAAW,OAAQ,CAAI,CAAC,IAE3D,EAAQ,WAAW,CAAE,MAAO,EAAK,OAAQ,CAAE,CAAC,EAC5C,EAAQ,WAAW,CAAE,MAAO,EAAK,OAAQ,OAAO,SAAU,CAAC,EAEnE,CAQA,eAAiC,CAC7B,OAAO,KAAK,WAAW,GAAK,KAAK,eAAiB,YACtD,CAWA,iBAAyB,EAA8B,CACnD,IAAM,EAAY,EAAO,iBAAiB,EAM1C,OAJK,EAIE,KAAK,WAAW,EAAI,EAAU,OAAS,EAAU,MAH7C,CAIf,CAUA,kBAA0B,EAA8B,CACpD,IAAM,EAAY,EAAO,iBAAiB,EAM1C,OAJK,EAIE,KAAK,WAAW,EAAI,EAAU,MAAQ,EAAU,OAH5C,CAIf,CAqBA,gBAAyB,CACrB,IAAM,EAAO,KAAK,SAAW,GAA0B,EAEvD,GAAI,CAAC,KAAK,WAAW,EAAG,CACpB,IAAI,EAAW,EAEf,IAAK,IAAM,KAAS,KAAK,SACrB,EAAW,KAAK,IAAI,EAAU,KAAK,kBAAkB,EAAM,MAAM,EAAI,KAAK,YAAY,CAAC,EAG3F,OAAO,CACX,CAEA,GAAI,KAAK,aAAe,SAAW,CAAC,KAAK,cAAc,EACnD,OAAO,KAAK,IAAI,EAAM,KAAK,WAAW,EAG1C,IAAI,EAAW,EAEf,IAAK,IAAM,KAAS,KAAK,SACrB,EAAW,KAAK,IAAI,EAAU,KAAK,kBAAkB,EAAM,MAAM,CAAC,EAGtE,IAAM,EAAW,KAAK,OAAO,OAAS,EAAI,KAAK,WAAW,iBAAiB,EAAI,KAM/E,OAJI,IACA,EAAW,KAAK,IAAI,EAAU,EAAS,KAAK,GAGzC,CACX,CAeA,aAA8B,CAC1B,IAAM,EAAQ,EAAa,iBAAiB,CAAC,CAAC,eAG9C,OAFY,KAAK,SAAW,KAAK,MAAM,EAAQ,CAAC,EAAI,GAEvC,EAAI,CACrB,CAaA,cAAsB,EAA8B,CAChD,OAAQ,KAAK,WAAb,CACI,IAAK,QAMD,OAAQ,KAAK,WAAW,GAAK,CAAC,KAAK,cAAc,EAAK,EAAI,KAAK,YACnE,IAAK,QAAS,CACV,IAAI,EAAS,EAEb,IAAK,IAAM,KAAS,KAAK,SACrB,EAAS,KAAK,IAAI,EAAQ,KAAK,iBAAiB,EAAM,MAAM,CAAC,EAGjE,OAAO,KAAK,IAAI,EAAQ,KAAK,WAAa,OAAO,SAAS,CAC9D,CACA,IAAK,UACD,OAAO,KAAK,IAAI,KAAK,iBAAiB,CAAM,EAAG,KAAK,WAAa,OAAO,SAAS,EACrF,QACI,MAAO,EACf,CACJ,CAeA,eAAuB,EAAyB,CAC5C,IAAM,EAAM,KAAK,SAAS,cAAc,EAKxC,GAJiB,KAAK,YAIR,CACV,EAAI,QAAQ,WAAW,EACvB,EAAI,eAAe,CAAC,KAAK,WAAW,EAAG,KAAK,WAAW,CAAC,EAExD,IAAK,IAAM,KAAS,KAAK,SAAU,CAC/B,IAAM,EAAS,KAAK,cAAc,EAAM,MAAM,EAE9C,GAAI,EAAS,EACT,KAAK,iBAAiB,EAAM,OAAQ,EAAQ,CAAM,MAC/C,CAIH,IAAM,EAAQ,KAAK,cAAc,EAAI,KAAK,iBAAiB,EAAM,MAAM,EAAI,EAC3E,KAAK,iBAAiB,EAAM,OAAQ,EAAO,OAAO,SAAS,CAC/D,CACJ,CAEA,MACJ,CAIA,GAFA,EAAI,eAAe,GAAO,EAAK,EAE3B,KAAK,aAAe,OAAQ,CAC5B,EAAI,QAAQ,OAAO,EAEnB,IAAK,IAAM,KAAS,KAAK,SACrB,KAAK,iBAAiB,EAAM,OAAQ,EAAG,OAAO,SAAS,EAG3D,MACJ,CAIA,GAFA,EAAI,QAAQ,WAAW,EAEnB,KAAK,aAAe,UAAW,CAC/B,IAAM,EAAM,KAAK,WAAa,OAAO,UAErC,IAAK,IAAM,KAAS,KAAK,SAKrB,GAAI,KAAK,cAAc,EAAG,CACtB,IAAM,EAAS,KAAK,IAAI,KAAK,iBAAiB,EAAM,MAAM,EAAG,CAAG,EAChE,KAAK,iBAAiB,EAAM,OAAQ,EAAQ,CAAM,CACtD,MACI,KAAK,iBAAiB,EAAM,OAAQ,EAAG,CAAG,EAIlD,MACJ,CAGA,IAAM,EAAS,KAAK,SAAS,OAAS,EAAI,KAAK,cAAc,KAAK,SAAS,EAAE,CAAC,MAAM,EAAI,EAIxF,GAAI,GAAU,EAAG,CACb,IAAK,IAAM,KAAS,KAAK,SACrB,KAAK,iBAAiB,EAAM,OAAQ,EAAG,OAAO,SAAS,EAG3D,MACJ,CAKA,GAAI,KAAK,aAAe,SAAW,KAAK,SAAS,OAAS,GAAK,EAAS,KAAK,SAAS,OAAS,EAAW,CACtG,EAAI,QAAQ,OAAO,EAEnB,IAAK,IAAM,KAAS,KAAK,SACrB,KAAK,iBAAiB,EAAM,OAAQ,EAAG,OAAO,SAAS,EAG3D,MACJ,CAEA,IAAK,IAAM,KAAS,KAAK,SACrB,KAAK,iBAAiB,EAAM,OAAQ,EAAQ,CAAM,CAE1D,CAQA,YAA8B,CAC1B,OAAO,KAAK,QAAU,QAAU,KAAK,QAAU,MACnD,CAOA,wBAAuC,CACnC,IAAM,EAAe,KAAK,WAAW,EAErC,KAAK,SAAS,eAAe,EAAe,WAAa,YAAY,EAEhE,KAAK,WAAW,iBAAiB,YAAa,IAAU,GACzD,KAAK,WAAW,iBAAiB,EAC3B,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,EACzC,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,CAAC,EAG/C,KAAK,WAAW,iBAAiB,YAAa,IAAU,GACzD,KAAK,WAAW,iBAAiB,EAC3B,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,EACzC,IAAI,EAAK,CAAE,QAAS,EAAG,WAAY,EAAK,CAAC,CAAC,CAExD,CAQA,qBAAsC,CAClC,IAAM,EAAO,KAAK,WAAW,iBAAiB,EAM9C,OAJK,EAIE,KAAK,WAAW,EAAI,EAAK,OAAS,EAAK,MAHnC,CAIf,CAUA,sBAAuC,CACnC,GAAI,CAAC,KAAK,YACN,MAAO,GAGX,IAAM,EAAO,KAAK,WAAW,iBAAiB,EAM9C,OAJK,GAIG,KAAK,WAAW,EAAI,EAAK,OAAS,EAAK,OAAS,GAH7C,CAIf,CASA,mBAAoC,CAChC,IAAI,EAAQ,EAEZ,IAAK,IAAM,KAAS,KAAK,SACrB,GAAS,KAAK,mBAAmB,EAAM,MAAM,EAGjD,OAAO,CACX,CAUA,mBAA2B,EAA8B,CACrD,IAAM,EAAS,KAAK,cAAc,CAAM,EAExC,OAAO,EAAS,EAAI,EAAS,KAAK,iBAAiB,CAAM,CAC7D,CAgBA,kBAA0B,EAAoB,EAAoB,EAAmB,EAAmB,EAAmB,EAAwB,CAC/I,IAAM,EAAY,KAAK,SAAW,MAC5B,EAAa,GAAc,EAAY,EAAa,GACpD,EAAc,EAAY,EAAI,EAC9B,EAAW,EAAY,EAAa,EAEtC,KAAK,WAAW,GAChB,KAAK,SAAS,KAAK,CAAS,EAC5B,KAAK,SAAS,KAAK,EAAa,CAAQ,EACxC,KAAK,SAAS,SAAS,CAAS,EAChC,KAAK,SAAS,UAAU,CAAQ,IAEhC,KAAK,SAAS,KAAK,EAAa,CAAQ,EACxC,KAAK,SAAS,KAAK,CAAS,EAC5B,KAAK,SAAS,SAAS,CAAQ,EAC/B,KAAK,SAAS,UAAU,CAAS,EAEzC,CAWA,sBAAqC,CAIjC,IAAM,EAAc,KAAK,eAAiB,cAAgB,cACpD,KAAK,eAAiB,eAAiB,cACvC,KAEN,IAAK,IAAM,KAAS,KAAK,SACrB,EAAM,OAAO,UAAU,KAAK,uBAAuB,EAAM,WAAW,CAAC,EAKjE,KAAK,WAAW,GAAK,EACrB,EAAM,OAAO,eAAe,CAAW,EAEvC,EAAM,OAAO,iBAAiB,EAGlC,EAAM,OAAO,aAAa,KAAK,UAAU,EAG7C,IAAM,EAAa,KAAK,wBAAwB,EAEhD,IAAK,IAAM,KAAQ,KAAK,OACpB,EAAK,UAAU,CAAU,EAOzB,KAAK,aACL,KAAK,YAAY,UAAU,CAAU,CAE7C,CAcA,YAAoB,EAA2B,CAK3C,OAJI,KAAK,SAAW,OAAS,KAAK,aAAe,QAAU,KAAK,SAAS,SAAW,EACzE,EAGJ,KAAK,IAAI,EAAG,EAAY,KAAK,kBAAkB,CAAC,CAC3D,CAYA,kBAA0B,EAAmB,EAAoB,EAAmB,EAAmB,EAAwB,CAC3H,GAAI,KAAK,OAAO,SAAW,GAAK,GAAc,EAAG,CAC7C,KAAK,WAAW,WAAW,EAAK,EAEhC,MACJ,CAEA,KAAK,WAAW,WAAW,EAAI,EAE/B,IAAM,GAAW,KAAK,SAAW,MAAQ,EAAI,EAAY,GAAc,EAEnE,KAAK,WAAW,GAChB,KAAK,WAAW,KAAK,CAAS,EAC9B,KAAK,WAAW,KAAK,CAAO,EAC5B,KAAK,WAAW,SAAS,CAAS,EAClC,KAAK,WAAW,UAAU,CAAU,IAEpC,KAAK,WAAW,KAAK,CAAO,EAC5B,KAAK,WAAW,KAAK,CAAS,EAC9B,KAAK,WAAW,SAAS,CAAU,EACnC,KAAK,WAAW,UAAU,CAAS,GAGvC,KAAK,WAAW,SAAS,CAC7B,CAgBA,kBAA0B,EAAmB,EAAmB,EAAwB,CAGpF,IAAM,EAAO,KAAK,YAAc,KAAK,WAAW,iBAAiB,EAAI,KAC/D,EAAa,EAAQ,KAAK,WAAW,EAAI,EAAK,OAAS,EAAK,MAAS,EAE3E,GAAI,GAAc,EAAG,CACjB,KAAK,WAAW,WAAW,EAAK,EAEhC,MACJ,CAEA,KAAK,WAAW,WAAW,EAAI,EAE3B,KAAK,WAAW,GAChB,KAAK,WAAW,KAAK,CAAS,EAC9B,KAAK,WAAW,KAAK,CAAQ,EAC7B,KAAK,WAAW,SAAS,CAAS,EAClC,KAAK,WAAW,UAAU,CAAU,IAEpC,KAAK,WAAW,KAAK,CAAQ,EAC7B,KAAK,WAAW,KAAK,CAAS,EAC9B,KAAK,WAAW,SAAS,CAAU,EACnC,KAAK,WAAW,UAAU,CAAS,GAGvC,KAAK,WAAW,SAAS,CAC7B,CAMA,mBAAkC,CAC9B,IAAM,EAAU,KAAK,YAAY,CAAC,EAAE,OAEpC,GAAI,CAAC,EACD,OAGJ,IAAM,EAAW,KAAK,WAAW,EAC3B,EAAa,EAAW,EAAQ,UAAU,EAAI,EAAQ,SAAS,EAErE,GAAI,GAAc,EACd,OAGJ,IAAM,EAAU,EAAW,EAAQ,KAAK,EAAI,EAAQ,KAAK,EAEzD,KAAK,WAAW,QAAQ,EAAS,EAAY,KAAK,KAAK,CAC3D,CAQA,sBAAqC,CACjC,IAAM,EAAU,KAAK,cAAc,EAO7B,EAAQ,EAAa,iBAAiB,EACtC,EAAY,EAAM,SAClB,EAAY,EAAM,cAExB,IAAK,IAAM,KAAS,KAAK,SAAU,CAC/B,IAAM,EAAc,EAAM,YAEtB,CAAC,GAAe,EAAM,OAAO,SAAS,GAAK,IAI/C,EAAY,SAAS,CAAS,EAC9B,EAAY,UAAU,CAAS,EAC/B,EAAY,aAAa,CAAS,EAE9B,GAIA,EAAY,KAAK,KAAK,OAAO,EAAM,OAAO,SAAS,EAAI,GAAa,CAAC,CAAC,EACtE,EAAY,KAAK,KAAK,eAAiB,eACjC,EACA,EAAM,OAAO,UAAU,EAAI,EAAY,CAAC,IAE9C,EAAY,KAAK,EAAM,OAAO,SAAS,EAAI,EAAY,CAAC,EACxD,EAAY,KAAK,KAAK,OAAO,EAAM,OAAO,UAAU,EAAI,GAAa,CAAC,CAAC,GAE/E,CACJ,CAWA,kBAAmC,CAC/B,OAAO,KAAK,SAAS,OAAS,GACxB,KAAK,mBAAmB,KAAK,SAAS,EAAE,CAAC,MAAM,GAC/C,EACV,CAQA,2BAA0C,CAOtC,GANI,CAAC,KAAK,oBAIV,KAAK,kBAAoB,GAErB,CAAC,KAAK,aACN,OAGJ,IAAM,EAAiB,KAAK,YAAY,CAAC,EAAE,OAAO,WAAW,EAEzD,GACA,KAAK,SAAS,WAAW,CAAc,CAE/C,CAYA,cAAqB,CACjB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAE1B,IAAK,IAAM,KAAS,KAAK,SACrB,EAAM,OAAO,QAAQ,CAAC,CAAC,YAAY,EAAM,KAAO,KAAK,SAAS,EAGlE,OAAO,IACX,CAeA,WAAW,EAAW,EAAW,EAAe,EAAsB,CAQlE,OAPA,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,CAAC,EACX,KAAK,SAAS,CAAK,EACnB,KAAK,UAAU,CAAM,EAErB,KAAK,aAAa,EAAO,CAAM,EAExB,IACX,CAYA,aAAqB,EAAe,EAAsB,CAMtD,IAAM,EAAK,KAAK,iBAAiB,EAC3B,EAAW,KAAK,WAAW,EAC3B,EAAY,EAAW,EAAG,QAAQ,EAAI,EAAG,OAAO,EAChD,EAAW,EAAW,EAAG,OAAO,EAAI,EAAG,QAAQ,EAC/C,EAAY,EAAW,EAAG,UAAU,EAAI,EAAG,SAAS,EAEpD,EAAa,KAAK,OAAO,OAAS,EAAI,KAAK,oBAAoB,EAAI,EACnE,EAAY,KAAK,eAAe,EAIhC,EAAa,KAAK,qBAAqB,EAEvC,GADY,EAAW,EAAS,GACR,EAAW,EAOnC,EAAe,KAAK,SAAS,aAAa,KAAK,kBAAkB,EAAG,EAAY,EAAa,CAAU,EACvG,EAAY,EAAY,EAAa,EAAa,EAAI,EACtD,EAAS,KAAK,YAAY,CAAS,EACzC,KAAK,kBAAkB,EAAY,EAAY,EAAW,EAAW,EAAW,CAAQ,EASxF,KAAK,eAAe,CAAS,EAC7B,KAAK,SAAS,cAAc,EAAc,CAAM,EAKhD,KAAK,0BAA0B,EAE/B,KAAK,kBAAkB,EAAW,EAAY,EAAW,EAAW,CAAQ,EAC5E,KAAK,kBAAkB,EAAW,EAAW,CAAQ,EACrD,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,CAC9B,CASA,eAA8B,CAC1B,KAAK,eAAe,EAMpB,IAAM,kBAAqB,GAAwB,CAC/C,KAAK,iBAAmB,EAAE,OAC1B,KAAK,eAAiB,EAAE,QAC5B,EAEA,EAAM,mBAAmB,KAAM,YAAa,iBAAiB,EAC7D,KAAK,cAAc,SAAW,EAAM,sBAAsB,KAAM,YAAa,iBAAiB,CAAC,EAE/F,IAAK,IAAM,KAAS,KAAK,SACrB,KAAK,cAAc,KAAK,KAAK,kBAAkB,CAAK,CAAC,EAGzD,KAAK,cAAc,KAAK,KAAK,kBAAkB,CAAC,CACpD,CAYA,kBAA0B,EAA6B,CASnD,OAJI,EAAM,aAAa,cACN,CAAC,EAGX,EAAY,eAAe,EAAM,OAAQ,CAC5C,cAUW,CARH,QAAa,GACb,YAAa,KAAK,QAAQ,EAC1B,YAAa,EAAM,UACnB,MAAa,EAAM,IAKN,GAErB,gBAAmC,CAC/B,IAAM,EAAS,KAAK,iBACpB,KAAK,iBAAmB,KAExB,IAAM,EAAe,EAAM,aAAa,WAAW,EAEnD,GAAI,GAAgB,EAAI,OAAO,OAAO,CAAM,GAAK,EAAI,OAAO,SAAS,EAAc,EAAI,OAAO,OAAO,CAAM,CAAC,EACxG,MAAO,GAMX,KAAK,KAAK,eAAgB,EAAM,EAAE,CACtC,EACA,WAAY,EAAyB,IAA2B,KAAK,aAAa,EAAO,EAAQ,CAAO,CAC5G,CAAC,CACL,CAiBA,mBAAwC,CACpC,OAAO,EAAY,eAAe,KAAK,SAAU,CAC7C,qBAAsB,GACtB,QAAU,GAAqC,KAAK,gBAAgB,CAAM,EAC1E,WAAa,IACT,KAAK,UAAU,SAAS,KAAK,QAAQ,EACrC,KAAK,kBAAkB,CAAM,EAKzB,EAAO,SAAS,cAAmB,KAAK,QAAQ,GAChD,KAAK,oBAAoB,EAGtB,MAEX,gBAAyB,CACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,CAC5B,EACA,OAAS,GAAkC,CACvC,KAAK,iBAAiB,EACtB,KAAK,cAAc,CAAM,CAC7B,CACJ,CAAC,CACL,CASA,qBAAoC,CAC5B,KAAK,oBAAsB,OAI/B,KAAK,kBAAoB,eAAiB,CACtC,KAAK,kBAAoB,KAErB,EAAY,WAAW,GACvB,KAAK,KAAK,WAAW,CAE7B,EAAG,CAAqB,EAC5B,CAMA,kBAAiC,CACzB,KAAK,oBAAsB,OAC3B,aAAa,KAAK,iBAAiB,EACnC,KAAK,kBAAoB,KAEjC,CAQA,oBAAmC,CAC/B,KAAK,YAAY,KAAK,EACtB,KAAK,UAAU,KAAK,CACxB,CAUA,gBAAwB,EAAkC,CACtD,OAAO,EAAO,SAAS,UAAe,EAC1C,CASA,cAAsB,EAA+B,CACjD,GAAI,EAAO,SAAS,cAAmB,KAAK,QAAQ,EAAG,CACnD,KAAK,YAAY,CAAM,EAEvB,MACJ,CAEA,KAAK,KAAK,gBAAiB,EAAO,SAAS,YAA0B,KAAK,gBAAgB,EAE1F,KAAK,mBAAmB,EACxB,KAAK,iBAAmB,EAC5B,CAUA,SAA0B,CACtB,OAAO,KAAK,MAAM,CACtB,CAaA,aAAqB,EAAiB,EAAyB,EAAwB,CACnF,GAAI,CAAC,EAAS,CACV,KAAK,KAAK,mBAAoB,EAAM,GAAI,EAAO,QAAS,EAAO,QAAS,KAAK,cAAc,EAE3F,MACJ,CAEA,KAAK,KAAK,SAAU,EAAM,EAAE,CAChC,CAQA,kBAA0B,EAA+B,CACrD,IAAM,EAAU,KAAK,SAAS,eAAe,EAE7C,GAAI,CAAC,EACD,OAGJ,IAAM,EAAO,EAAI,OAAO,eAAe,CAAO,EACxC,EAAW,KAAK,WAAW,EAM3B,GAAc,EAAW,EAAO,QAAU,EAAK,IAAM,EAAO,QAAU,EAAK,MAC3E,KAAK,SAAS,WAAW,EAE3B,EAAc,KAAK,SAAS,OAEhC,IAAK,IAAI,EAAI,EAAG,EAAI,KAAK,SAAS,OAAQ,IAAK,CAC3C,IAAM,EAAU,KAAK,SAAS,EAAE,CAAC,OAIjC,GAAI,GAHU,EAAW,EAAQ,KAAK,EAAI,EAAQ,KAAK,IACxC,EAAW,EAAQ,UAAU,EAAI,EAAQ,SAAS,GAE/B,EAAG,CACjC,EAAc,EAEd,KACJ,CACJ,CAEA,KAAK,iBAAmB,EAExB,IAAM,EAAW,KAAK,aAAa,EAAa,CAAQ,EAClD,EAAY,EAAW,KAAK,SAAS,SAAS,EAAI,KAAK,SAAS,UAAU,EAEhF,KAAK,YAAY,QAAQ,EAAU,EAAW,CAAQ,CAC1D,CAYA,aAAqB,EAAqB,EAA2B,CACjE,GAAI,EAAc,KAAK,SAAS,OAAQ,CACpC,IAAM,EAAU,KAAK,SAAS,EAAY,CAAC,OAE3C,OAAO,EAAW,EAAQ,KAAK,EAAI,EAAQ,KAAK,CACpD,CAEA,GAAI,KAAK,SAAS,OAAS,EAAG,CAC1B,IAAM,EAAU,KAAK,SAAS,KAAK,SAAS,OAAS,EAAE,CAAC,OAExD,OAAO,EAAW,EAAQ,KAAK,EAAI,EAAQ,UAAU,EAAI,EAAQ,KAAK,EAAI,EAAQ,SAAS,CAC/F,CAEA,MAAO,EACX,CAQA,YAAoB,EAA+B,CAC/C,KAAK,mBAAmB,EAExB,IAAM,EAAU,KAAK,SAAS,UAAU,GAAS,EAAM,OAAO,MAAM,IAAM,EAAO,QAAQ,EAErF,EAAU,GAAK,KAAK,iBAAmB,IAI3C,KAAK,WAAW,EAAS,KAAK,gBAAgB,EAC9C,KAAK,iBAAmB,GAC5B,CAWA,WAAmB,EAAiB,EAAqB,CACrD,GAAI,EAAU,GAAK,GAAW,KAAK,SAAS,OACxC,OAKJ,IAAI,EAAO,EAAQ,EAAU,EAAQ,EAAI,EAGzC,GAFA,EAAO,KAAK,IAAI,EAAG,KAAK,IAAI,EAAM,KAAK,SAAS,OAAS,CAAC,CAAC,EAEvD,IAAS,EACT,OAGJ,IAAM,EAAS,KAAK,SAAS,EAAQ,CAAC,GAChC,EAAQ,KAAK,SAAS,GAE5B,KAAK,SAAS,OAAO,EAAS,CAAC,EAC/B,KAAK,SAAS,OAAO,EAAM,EAAG,CAAK,EAEnC,KAAK,SAAS,SAAS,EAAM,OAAQ,CAAI,EAEzC,KAAK,KAAK,UAAW,EAAQ,CAAI,CACrC,CAMA,gBAA+B,CAC3B,IAAK,IAAM,KAAY,KAAK,cACxB,EAAS,EAGb,KAAK,cAAgB,CAAC,EACtB,KAAK,mBAAmB,CAC5B,CAOA,iBAAyB,EAAwB,CAC7C,GAAI,EAAE,MAAQ,aAAe,EAAE,MAAQ,aACnC,OAGJ,IAAM,EAAW,KAAK,SAAS,OAE/B,GAAI,IAAa,EACb,OAGJ,EAAE,eAAe,EAEjB,IAAM,EAAY,KAAK,SAAS,UAAU,GAAS,EAAM,KAAO,KAAK,SAAS,EACxE,EAAO,GAAa,EAAI,EAAY,EAEpC,EAAS,EAAE,MAAQ,cAClB,EAAO,GAAK,GACZ,EAAO,EAAI,GAAY,EAExB,EAAS,KAAK,SAAS,EAAO,CAAC,OAErC,KAAK,SAAS,QAAQ,GAAS,EAAM,OAAO,YAAY,EAAK,CAAC,EAC9D,EAAO,YAAY,EAAI,EAEvB,KAAK,aAAa,CAAM,CAC5B,CAkBA,GAAG,EAAoB,EAA0B,CAG7C,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAWA,IAAI,EAAoB,EAA0B,CAG9C,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAiBA,KAAe,EAAoB,GAAG,EAA0B,CAC5D,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CACJ,EAEM,EAAiB,EAAS,MAAM,EC99FhC,EAA+B,IAC/B,GAA+B,IAmD/B,GAAuB,IA8JvB,IAAN,cAAkB,CAAc,CAO5B,KAAuB,IAAI,EAK3B,YAA+B,GAI/B,UAAyC,CAAC,EAE1C,kBAAoC,EAIpC,sBAAkD,KAIlD,mBAAqC,GAIrC,kBAA2D,KAC3D,WAA4C,IAAI,EAIhD,uBAA0C,GAG1C,kBAAiD,QAIjD,0BAA6C,GAI7C,OAAyB,EAIzB,cAA6C,KAO7C,YAAY,EAAsB,CAG9B,MAAM,EAKN,KAAK,cAAgB,EAAa,kBAAoB,CAClD,KAAK,aAAa,CAAC,EAAE,eAAe,CACxC,CAAC,EAEG,GACA,KAAK,aAAa,CAAO,CAEjC,CASA,aAAuB,EAA2B,CAG9C,GAFA,MAAM,aAAa,CAAO,EAEtB,EAAQ,YAAc,IAAA,GAAW,CACjC,IAAM,EAAY,EAAQ,UAE1B,IAAK,IAAM,KAAS,OAAO,KAAK,CAAS,EAAoC,CACzE,IAAM,EAAW,EAAU,GAEvB,IAAa,IAAA,IAIb,KAAM,GAAqD,EAAO,CAAQ,CAElF,CACJ,CAsDA,GApDI,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGnC,EAAQ,WAAa,IAAA,IACrB,KAAK,YAAY,EAAQ,QAAQ,EAGjC,EAAQ,aAAe,IAAA,IACvB,KAAK,cAAc,EAAQ,UAAU,EAGrC,EAAQ,uBAAyB,IAAA,IACjC,KAAK,wBAAwB,EAAQ,oBAAoB,EAGzD,EAAQ,OAAS,IAAA,IACjB,KAAK,QAAQ,EAAQ,IAAI,EAGzB,EAAQ,QAAU,IAAA,IAClB,KAAK,SAAS,EAAQ,KAAK,EAG3B,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,aAAe,IAAA,IACvB,KAAK,cAAc,EAAQ,UAAU,EAGrC,EAAQ,UAAY,IAAA,IACpB,KAAK,WAAW,EAAQ,OAAO,EAG/B,EAAQ,cAAgB,IAAA,IACxB,KAAK,eAAe,EAAQ,WAAW,EAGvC,EAAQ,wBAA0B,IAAA,IAClC,KAAK,yBAAyB,EAAQ,qBAAqB,EAG3D,EAAQ,mBAAqB,IAAA,IAC7B,KAAK,oBAAoB,EAAQ,gBAAgB,EAGjD,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGnC,EAAQ,QAAU,IAAA,GAIlB,IAAK,IAAM,KAAQ,EAAQ,MACnB,aAAgB,EAChB,KAAK,QAAQ,CAAI,CAMjC,CAUA,aAAa,EAA0B,CAKnC,OAJA,KAAK,KAAK,aAAa,CAAI,EAE3B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,cAA6B,CACzB,OAAO,KAAK,KAAK,aAAa,CAClC,CAUA,YAAY,EAAyB,CAKjC,OAJA,KAAK,KAAK,YAAY,CAAE,EAExB,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,aAA6B,CACzB,OAAO,KAAK,KAAK,YAAY,CACjC,CAUA,cAAc,EAAkB,CAK5B,OAJA,KAAK,KAAK,cAAc,CAAE,EAE1B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,eAAwB,CACpB,OAAO,KAAK,KAAK,cAAc,CACnC,CAWA,wBAAwB,EAAqB,CAKzC,OAJA,KAAK,KAAK,wBAAwB,CAAI,EAEtC,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,wBAAkC,CAC9B,OAAO,KAAK,KAAK,uBAAuB,CAC5C,CAUA,QAAQ,EAAqB,CAKzB,OAJA,KAAK,KAAK,QAAQ,CAAI,EAEtB,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,SAAmB,CACf,OAAO,KAAK,KAAK,QAAQ,CAC7B,CAUA,SAAS,EAAsB,CAK3B,OAJA,KAAK,KAAK,SAAS,CAAK,EAExB,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,UAAoB,CAChB,OAAO,KAAK,KAAK,SAAS,CAC9B,CAUA,eAAe,EAAmC,CAK9C,OAJA,KAAK,KAAK,eAAe,CAAW,EAEpC,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,gBAAiC,CAC7B,OAAO,KAAK,KAAK,eAAe,CACpC,CASA,aAAa,EAA2B,CAKpC,OAJA,KAAK,KAAK,aAAa,CAAK,EAE5B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,cAA6B,CACzB,OAAO,KAAK,KAAK,aAAa,CAClC,CAWA,cAAc,EAAsB,CAKhC,OAJA,KAAK,KAAK,cAAc,CAAK,EAE7B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,cAAwB,CACpB,OAAO,KAAK,KAAK,aAAa,CAClC,CASA,WAAW,EAAsB,CAK7B,OAJA,KAAK,KAAK,WAAW,CAAK,EAE1B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,WAAqB,CACjB,OAAO,KAAK,KAAK,UAAU,CAC/B,CAUA,eAAe,EAAsB,CAGjC,OAFA,KAAK,KAAK,eAAe,CAAK,EAEvB,IACX,CAOA,eAAyB,CACrB,OAAO,KAAK,KAAK,cAAc,CACnC,CAYA,cAAc,EAAsB,CAShC,OARI,KAAK,cAAgB,EACd,MAGX,KAAK,YAAc,EAEnB,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,KACX,CAOA,cAAwB,CACpB,OAAO,KAAK,WAChB,CAQA,SAAmB,CACf,OAAO,KAAK,UAAU,SAAW,CACrC,CAWA,yBAAyB,EAAsB,CAK3C,MAJA,MAAK,uBAAyB,EAE9B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAOA,yBAAmC,CAC/B,OAAO,KAAK,sBAChB,CAWA,oBAAoB,EAAiC,CAGjD,MAFA,MAAK,kBAAoB,EAElB,IACX,CAOA,qBAA2C,CACvC,OAAO,KAAK,iBAChB,CAcA,4BAA4B,EAAsB,CAG9C,MAFA,MAAK,0BAA4B,EAE1B,IACX,CAwBA,QAAQ,EAA0C,CAe9C,OAdI,aAAe,GAEX,aAAe,GACf,EAAI,QAAQ,EAAI,EAMpB,KAAK,KAAK,QAAQ,CAAG,EAGzB,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CASA,WAAW,EAAyB,CAKhC,OAJA,KAAK,KAAK,WAAW,CAAM,EAE3B,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAUA,oBAAoB,EAAgC,CAKhD,OAJA,KAAK,KAAK,iBAAiB,CAAM,EAEjC,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,IACX,CAUA,sBAAsB,EAAqB,CAGvC,OAFA,KAAK,KAAK,mBAAmB,CAAK,EAE3B,IACX,CAiBA,sBAAsB,EAAuC,EAAoD,CAG7G,OAFA,KAAK,KAAK,mBAAmB,EAAc,CAAkB,EAEtD,IACX,CAQA,QAAyB,CACrB,MAAO,OAAU,KAAK,QAC1B,CAQA,OAAO,EAA4B,CAc/B,OAbA,MAAM,OAAO,CAAS,EAEtB,EAAI,KAAK,YAAY,EAAU,WAAW,EAAI,EAAI,KAAK,KAAK,WAAW,EAAI,CAAE,EAE7E,KAAK,KAAK,GAAG,aAAoB,KAAK,gBAAgB,EACtD,KAAK,KAAK,GAAG,UAAkB,KAAK,eAAe,EACnD,KAAK,KAAK,GAAG,WAAoB,KAAK,cAAc,EACpD,KAAK,KAAK,GAAG,gBAAoB,KAAK,mBAAmB,EACzD,KAAK,KAAK,GAAG,eAAoB,KAAK,kBAAkB,EACxD,KAAK,KAAK,GAAG,mBAAoB,KAAK,sBAAsB,EAC5D,KAAK,KAAK,GAAG,SAAkB,KAAK,cAAc,EAClD,KAAK,KAAK,GAAG,YAAoB,KAAK,eAAe,EAE9C,IACX,CAMA,QAAe,CACX,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,KAEzB,IAAK,IAAM,KAAS,KAAK,UACrB,EAAM,sBAAsB,OAAO,EACnC,EAAM,qBAAuB,KAqBjC,OAlBA,MAAM,OAAO,EAEb,AAEI,KAAK,iBADL,KAAK,cAAc,EACE,MAGzB,KAAK,KAAK,IAAI,aAAoB,KAAK,gBAAgB,EACvD,KAAK,KAAK,IAAI,UAAkB,KAAK,eAAe,EACpD,KAAK,KAAK,IAAI,WAAoB,KAAK,cAAc,EACrD,KAAK,KAAK,IAAI,gBAAoB,KAAK,mBAAmB,EAC1D,KAAK,KAAK,IAAI,eAAoB,KAAK,kBAAkB,EACzD,KAAK,KAAK,IAAI,mBAAoB,KAAK,sBAAsB,EAC7D,KAAK,KAAK,IAAI,SAAkB,KAAK,cAAc,EACnD,KAAK,KAAK,IAAI,YAAoB,KAAK,eAAe,EAEtD,KAAK,KAAK,QAAQ,EAEX,IACX,CAUA,iBAA4B,GAAqB,CAC7C,IAAM,EAAM,KAAK,UAAU,UAAU,GAAS,EAAM,KAAO,CAAE,EAE7D,GAAI,GAAO,EAAG,CACV,KAAK,kBAAoB,EAEzB,IAAM,EAAQ,KAAK,UAAU,GAM7B,KAAK,KAAK,SAAU,EAAK,KAAK,KAAK,aAAa,EAAM,EAAE,CAAC,EAErD,EAAM,QAAU,QAChB,KAAK,iBAAiB,CAAG,EAOzB,EAAM,UACN,KAAK,KAAK,WAAY,EAAM,UAAW,CAAG,EAM1C,EAAM,mBAAqB,EAEnC,CAEA,KAAK,aAAa,CAAC,EAAE,eAAe,CACxC,EAUA,iBAA2B,EAAiB,IAA2B,CACnE,IAAM,EAAQ,KAAK,KAAK,YAAY,EAC9B,EAAS,KAAK,UAAU,KAAK,oBAAsB,KAIzD,GAFA,KAAK,UAAU,MAAM,EAAG,IAAM,EAAM,QAAQ,EAAE,EAAE,EAAI,EAAM,QAAQ,EAAE,EAAE,CAAC,EAEnE,EAAQ,CACR,IAAM,EAAW,KAAK,UAAU,QAAQ,CAAM,EAE1C,GAAY,IACZ,KAAK,kBAAoB,EAEjC,CAEA,KAAK,aAAa,CAAC,EAAE,eAAe,CACxC,EAQA,eAA0B,GAAqB,CAC3C,KAAK,WAAW,CAAE,CACtB,EAYA,WAAmB,EAAkB,CACjC,IAAM,EAAY,KAAK,aAAa,EACpC,GAAI,CAAC,EACD,OAGJ,IAAM,EAAM,KAAK,UAAU,UAAU,GAAS,EAAM,KAAO,CAAE,EAC7D,GAAI,EAAM,EACN,OAKJ,IAAM,EAAc,KAAK,oBAAsB,EACzC,EAAU,KAAK,UAAU,EAAI,CAAC,UAC9B,EAAU,KAAK,UAAU,EAAI,CAAC,QAEpC,KAAK,UAAU,EAAI,CAAC,sBAAsB,OAAO,EAEjD,KAAK,KAAK,eAAe,CAAE,EAC3B,KAAK,UAAU,OAAO,EAAK,CAAC,EAExB,GACA,EAAU,gBAAgB,CAAO,EAMjC,IACA,EAAU,gBAAgB,CAAO,EACjC,EAAQ,QAAQ,GAGhB,GACA,KAAK,KAAK,WAAY,CAAO,EAGjC,KAAK,kBAAkB,EAAK,CAAW,EACvC,KAAK,aAAa,CAAC,EAAE,eAAe,EACpC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAExB,KAAK,UAAU,SAAW,GAC1B,KAAK,KAAK,OAAO,CAEzB,CAaA,SAAS,EAA6B,CAClC,IAAM,EAAQ,KAAK,UAAU,KAAK,GAAK,EAAE,YAAc,CAAO,EAQ9D,OANK,GAIL,KAAK,WAAW,EAAM,EAAE,EAEjB,IALI,EAMf,CASA,qBAA+B,EAAqB,IAAuB,CACvE,IAAM,EAAU,EAAgB,IAAI,CAAW,EAE3C,IACA,KAAK,cAAc,EAAS,CAAI,EAOhC,KAAK,eAAe,CAAC,EAAE,aAAa,EAKpC,KAAK,KAAK,OAAQ,CAAO,EAEjC,EAQA,oBAAsC,CAClC,KAAK,eAAe,CAAC,EAAE,aAAa,CACxC,EASA,mBAA8B,GAAqB,CAC/C,IAAM,EAAQ,KAAK,UAAU,KAAK,GAAK,EAAE,KAAO,CAAE,EAE9C,GAAS,EAAM,QAAU,SAAW,EAAM,WAC1C,EAAgB,IAAI,EAAM,UAAU,MAAM,EAAG,EAAM,SAAS,CAEpE,EAYA,wBAAkC,EAAY,EAAiB,EAAiB,IAA6B,CACzG,IAAM,EAAQ,KAAK,UAAU,KAAK,GAAK,EAAE,KAAO,CAAE,EAE7C,IAID,EAAM,WACN,EAAgB,OAAO,EAAM,UAAU,MAAM,CAAC,EAG9C,IAAM,QAAU,SAAW,CAAC,EAAM,YAItC,KAAK,kBAAkB,EAAI,EAAM,UAAW,EAAS,EAAS,CAAS,EAC3E,EASA,eAA0B,GAAqB,CAC3C,IAAM,EAAQ,KAAK,UAAU,KAAK,GAAK,EAAE,KAAO,CAAE,EAElD,GAAI,CAAC,EACD,OAGJ,IAAM,EAAU,EAAM,UAElB,GACA,EAAgB,OAAO,EAAQ,MAAM,CAAC,EAGtC,IAAM,QAAU,SAAW,CAAC,MAMd,KAAK,aAAa,CAAC,EAAE,cAAc,CAAC,CAAC,SAAS,CAAO,GAAK,KAGxE,KAAK,0BAA0B,CAAE,EAEzC,EAQA,qBAAwC,CACpC,IAAI,EAAY,KAAK,aAAa,EAClC,GAAI,CAAC,EACD,OAAO,KAGX,IAAM,EAAQ,KAAK,UAAU,KAAK,mBAkBlC,OAjBI,EAMI,EAAM,QAAU,YAAc,EAAM,QAC7B,EAAM,QAGb,EAAM,UACC,EAAM,UAGV,KAGJ,EAAU,cAAc,CAAC,CAAC,KAAK,oBAAsB,IAChE,CAQA,kBAAgC,CAC5B,OAAO,KAAK,YAAY,KAAK,oBAAoB,CAAC,EAAE,iBAAiB,CAAC,CAC1E,CAQA,YAA0B,CACtB,OAAO,KAAK,YAAY,KAAK,oBAAoB,CAAC,EAAE,WAAW,CAAC,CACpE,CAQA,YAA0B,CACtB,OAAO,KAAK,YAAY,KAAK,oBAAoB,CAAC,EAAE,WAAW,CAAC,CACpE,CAQA,YAA8B,CAC1B,IAAM,EAAO,KAAK,KAAK,QAAQ,EAE/B,OAAO,IAAS,QAAU,IAAS,MACvC,CAYA,YAAoB,EAA+C,CAC/D,IAAM,EAAY,KAAK,aAAa,EAEpC,GAAI,CAAC,GAAa,CAAC,EACf,OAAO,KAGX,IAAM,EAAY,EAAU,iBAAiB,EACvC,EAAa,EAAU,KAAO,EAAU,MACxC,EAAc,EAAU,IAAM,EAAU,OACxC,EAAY,KAAK,KAAK,eAAe,EAS3C,OAPI,KAAK,WAAW,EACT,CACH,MAAQ,EAAQ,MAAQ,EAAY,EACpC,OAAQ,EAAQ,OAAS,CAC7B,EAGG,CACH,MAAQ,EAAQ,MAAQ,EACxB,OAAQ,EAAQ,OAAS,EAAY,CACzC,CACJ,CAaA,UAAU,EAA4B,CAClC,IAAI,EAAc,KAAK,qBAAqB,CAAS,EAC/C,EAAO,GAAa,MAAQ,EAAU,QAAQ,GAAK,EAAU,MAAM,EACnE,EAAK,KAAK,OAAO,EAEvB,KAAK,KAAK,eAAe,EAAI,EAAM,CAAW,EAC9C,KAAK,UAAU,KAAK,CAChB,KACA,YACA,QAAS,KACT,QAAS,KACT,MAAO,QACP,qBAAsB,KACtB,mBAAoB,EACxB,CAAC,EAED,KAAK,kBAAkB,EAAI,CAAS,EAIpC,KAAK,wBAAwB,CACjC,CAYA,kBAA0B,EAAY,EAA4B,CAC9D,KAAK,KAAK,kBAAkB,EAAI,EAAU,MAAM,CAAC,EAEjD,EAAU,QAAQ,CAAC,CAAC,QAAQ,UAAU,EACtC,EAAU,QAAQ,CAAC,CAAC,YAAY,EAAE,EAClC,EAAU,QAAQ,CAAC,CAAC,cAAc,KAAK,KAAK,iBAAiB,CAAE,CAAC,CACpE,CA6BA,qBAA8B,EAA2B,EAA0C,CAE/F,GAAI,GAAa,OAAS,GACtB,MAAO,GAKX,KAAK,qBAAqB,EAE1B,IAAM,EAAK,KAAK,OAAO,EAevB,OAbA,KAAK,KAAK,eAAe,EAAI,GAAa,MAAQ,EAAI,CAAW,EACjE,KAAK,UAAU,KAAK,CAChB,KACA,UAAW,KACX,UACA,QAAS,KACT,MAAO,OACP,qBAAsB,KACtB,mBAAoB,EACxB,CAAC,EAED,KAAK,aAAa,CAAC,EAAE,eAAe,EAE7B,EACX,CA+BA,WAAW,EAA2B,EAAc,EAAuC,CAKvF,KAAK,qBAAqB,EAAS,OAAO,OAAO,IAAI,GAAqB,EAAa,CAAE,OAAM,KAAM,EAAK,CAAC,CAAC,CAChH,CAeA,sBAAqC,CACjC,IAAM,EAAY,KAAK,aAAa,EACpC,GAAI,CAAC,EACD,OAGJ,IAAM,EAAQ,IAAI,IAElB,IAAK,IAAM,KAAS,KAAK,UACjB,EAAM,WACN,EAAM,IAAI,EAAM,SAAS,EAGzB,EAAM,SACN,EAAM,IAAI,EAAM,OAAO,EAI/B,IAAK,IAAM,KAAa,EAAU,cAAc,EACvC,EAAM,IAAI,CAAS,GACpB,KAAK,UAAU,CAAS,CAGpC,CAqBA,iBAAyB,EAAmB,CACxC,IAAM,EAAQ,KAAK,UAAU,GAC7B,GAAI,CAAC,GAAS,EAAM,QAAU,OAC1B,OAGJ,IAAM,EAAU,EAAM,QACtB,GAAI,CAAC,EACD,OAGJ,IAAM,EAAY,KAAK,aAAa,EACpC,GAAI,CAAC,EACD,OAGJ,IAAM,EAAU,kBAAkB,EAClC,EAAM,QAAU,EAChB,EAAM,MAAU,WAEhB,EAAM,sBAAsB,OAAO,EACnC,EAAM,qBAAuB,EAAU,YAAY,CAC/C,KAAkB,EAClB,YAAwB,CACpB,IAAM,EAAS,EAAQ,EAiBvB,OAVI,aAAkB,QACX,EAAO,KAAM,IAChB,EAAM,UAAY,EAEX,EACV,GAGL,EAAM,UAAY,EAEX,EACX,EACA,iBAAkB,EAGlB,YAAwB,CAAC,KAAK,UAAU,SAAS,CAAK,EACtD,QAAmB,GAAU,KAAK,UAAU,EAAO,CAAK,EACxD,QAAmB,GAAc,CAC7B,EAAM,UAAY,EAClB,EAAM,QAAY,KAClB,EAAM,QAAY,KAClB,EAAM,MAAY,QAElB,KAAK,kBAAkB,EAAM,GAAI,CAAS,EAC1C,EAAU,eAAe,EACzB,KAAK,0BAA0B,CAAK,CACxC,CACJ,CAAC,CACL,CAUA,0BAAkC,EAA2B,CACrD,CAAC,EAAM,oBAAsB,CAAC,EAAM,YAIxC,EAAM,mBAAqB,GAEvB,KAAK,UAAU,KAAK,qBAAuB,GAC3C,KAAK,KAAK,WAAY,EAAM,UAAW,KAAK,iBAAiB,EAErE,CAWA,UAAkB,EAAqB,EAAsB,CACzD,IAAM,EAAQ,KAAK,KAAK,aAAa,EAAM,EAAE,EAE7C,EAAM,QAAU,KAChB,EAAM,QAAU,KAEhB,KAAK,WAAW,EAAM,EAAE,EACxB,KAAK,KAAK,YAAa,EAAO,CAAK,CACvC,CAYA,qBAAsC,CAClC,IAAM,EAAY,KAAK,aAAa,EAQpC,OAPK,GAIW,KAAK,oBAAoB,GAAK,EAAU,cAAc,CAAC,CAAC,GAAA,EACnD,WAAW,GAElB,CAAE,MAAO,EAAG,OAAQ,CAAE,EANzB,CAAE,MAAO,EAAG,OAAQ,CAAE,CAOrC,CAUA,UAAiB,CACb,IAAI,EAAY,KAAK,aAAa,EAClC,GAAI,CAAC,EACD,OAGJ,IAAI,EAAa,EAAU,cAAc,EACrC,EAAgB,EAAU,aAAa,EACvC,EAAkB,EAAU,iBAAiB,EAMjD,GAJA,KAAK,qBAAqB,EAItB,KAAK,sBAAuB,CAC5B,IAAM,EAAe,KAAK,eAAe,KAAK,qBAAqB,EAE/D,GAAgB,IAChB,KAAK,sBAAwB,KAC7B,KAAK,kBAAkB,CAAY,EAE3C,CAKA,IAAM,EAAe,KAAK,UAAU,KAAK,mBACrC,GAAgB,EAAa,QAAU,QACvC,KAAK,iBAAiB,KAAK,iBAAiB,EAGhD,IAAK,IAAM,KAAa,EACpB,EAAU,WAAW,EAAK,EAC1B,EAAU,QAAQ,CAAC,CAAC,UAAU,EAAI,EAGtC,IAAI,EAAY,KAAK,oBAAoB,EAErC,CAAC,GAAa,EAAW,OAAS,IAClC,EAAY,EAAW,IAM3B,KAAK,KAAK,WAAW,KAAK,WAAW,EAEjC,KAAK,aACL,KAAK,KAAK,aAAa,EAG3B,IAAM,EAAK,GAAiB,CAAE,MAAO,EAAG,OAAQ,CAAE,EAC5C,EAAQ,EAAgB,QAAQ,EAChC,EAAQ,EAAgB,OAAO,EAC/B,EAAY,KAAK,YAAc,KAAK,KAAK,eAAe,EAAI,EAI9D,EAAW,EACX,EAAW,EACX,EAAW,EAAG,MACd,EAAW,EACX,EAAW,EACX,EAAW,EACX,EAAW,EAAG,MACd,EAAW,EAAG,OAElB,OAAQ,KAAK,KAAK,QAAQ,EAA1B,CACI,IAAK,QACD,EAAW,EAAQ,EACnB,EAAW,EAAG,OAAS,EACvB,MAEJ,IAAK,QACD,EAAW,EAAQ,EAAG,OAAS,EAC/B,EAAW,EAAG,OAAS,EACvB,MAEJ,IAAK,OACD,EAAW,EACX,EAAW,EAAG,OACd,EAAW,EAAQ,EACnB,EAAW,EAAG,MAAQ,EACtB,MAEJ,IAAK,OACD,EAAW,EAAQ,EAAG,MAAQ,EAC9B,EAAW,EACX,EAAW,EAAG,OACd,EAAW,EAAG,MAAQ,EACtB,KACR,CAQA,GAAI,KAAK,uBAAwB,CAC7B,IAAM,EAAI,EAAgB,QAAQ,EAC5B,EAAI,EAAgB,OAAO,EAC3B,EAAI,EAAgB,SAAS,EAC7B,EAAI,EAAgB,UAAU,EAEpC,OAAQ,KAAK,KAAK,QAAQ,EAA1B,CACI,IAAK,QACD,EAAW,EACX,EAAW,EACX,EAAW,EAAG,MAAQ,EAAI,EAC1B,EAAW,EAAY,EACvB,KAAK,KAAK,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAC1C,MAEJ,IAAK,QACD,EAAW,EACX,EAAW,EAAQ,EAAG,OAAS,EAC/B,EAAW,EAAG,MAAQ,EAAI,EAC1B,EAAW,EAAY,EACvB,KAAK,KAAK,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAC1C,MAEJ,IAAK,OACD,EAAW,EACX,EAAW,EACX,EAAW,EAAY,EACvB,EAAW,EAAG,OAAS,EAAI,EAC3B,KAAK,KAAK,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAC1C,MAEJ,IAAK,OACD,EAAW,EAAQ,EAAG,MAAQ,EAC9B,EAAW,EACX,EAAW,EAAY,EACvB,EAAW,EAAG,OAAS,EAAI,EAC3B,KAAK,KAAK,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAC1C,KACR,CACJ,MACI,KAAK,KAAK,YAAY,EAQ1B,GAJI,KAAK,aACL,KAAK,KAAK,WAAW,EAAU,EAAU,EAAU,CAAQ,EAG3D,CAAC,EACD,OAGJ,EAAU,WAAW,EAAI,EACzB,EAAU,QAAQ,CAAC,CAAC,UAAU,EAAK,EAOnC,IAAM,EAAW,KAAK,mBAAmB,CAAE,MAAO,EAAU,OAAQ,CAAS,CAAC,EACxE,EAAgB,EAAS,MACzB,EAAgB,EAAS,OAkB/B,GAhBA,KAAK,eACD,EACA,EACA,EACA,EACA,EACA,EAAS,IACb,EAMsB,KAAK,UAAU,KAAK,kBACpB,EAAe,QAAU,SAEhC,KAAK,qBAAuB,KAAK,kBAAmB,CAC/D,KAAK,mBAAqB,KAAK,kBAE/B,IAAM,EAAK,EAAU,WAAW,EAC5B,IACA,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,EAAU,KAAK,EAAI,CACxC,KAAY,CAAE,QAAS,GAAI,EAC3B,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,GACZ,WAAY,CAAC,SAAS,CAC1B,CAAC,EAET,CACJ,CAQA,mBAA4B,CACxB,OAAO,KAAK,iBAChB,CAWA,kBAAqC,CACjC,OAAO,KAAK,UAAU,KAAK,kBAAkB,EAAE,WAAa,IAChE,CAWA,eAAe,EAA4B,CACvC,OAAO,KAAK,UAAU,UAAU,GAAS,EAAM,YAAc,CAAO,CACxE,CASA,mBAAmC,CAC/B,IAAM,EAAK,KAAK,KAAK,iBAAiB,EAEtC,OAAO,IAAO,KAAO,KAAO,KAAK,KAAK,aAAa,CAAE,CACzD,CAqBA,iBAAiB,EAA0B,CACvC,IAAM,EAAQ,KAAK,eAAe,CAAO,EASzC,OAPI,GAAS,GACT,KAAK,sBAAwB,KAC7B,KAAK,kBAAkB,CAAK,GAE5B,KAAK,sBAAwB,EAG1B,IACX,CAEA,kBAAkB,EAAqB,CACnC,GAAI,KAAK,UAAU,SAAW,EAC1B,OAAO,KAGX,IAAM,EAAU,EAAK,MAAM,EAAO,EAAG,KAAK,UAAU,OAAS,CAAC,EAQ9D,OAFA,KAAK,KAAK,eAAe,KAAK,UAAU,EAAQ,CAAC,EAAE,EAE5C,IACX,CAYA,cAAsB,EAAoB,EAAoB,CAC1D,IAAM,EAAY,KAAK,aAAa,EAEpC,GAAI,CAAC,EACD,OAGJ,EAAU,cAAc,EAAS,CAAI,EACrC,KAAK,UAAU,CAAO,EAItB,IAAM,EAAgB,KAAK,UAAU,OAAS,EACxC,EAAO,EAAK,MAAM,EAAM,EAAG,CAAa,EAE9C,GAAI,IAAS,EAAe,CACxB,IAAM,EAAQ,KAAK,UAAU,GAE7B,KAAK,UAAU,OAAO,EAAe,CAAC,EACtC,KAAK,UAAU,OAAO,EAAM,EAAG,CAAK,EACpC,KAAK,KAAK,aAAa,EAAM,GAAI,CAAI,CACzC,CAEA,KAAK,kBAAkB,CAAI,EAC3B,EAAU,eAAe,CAC7B,CAUA,0BAAkC,EAAkB,CAChD,IAAM,EAAM,KAAK,UAAU,UAAU,GAAS,EAAM,KAAO,CAAE,EAE7D,GAAI,EAAM,EACN,OAGJ,IAAM,EAAc,KAAK,oBAAsB,EAK/C,KAAK,UAAU,EAAI,CAAC,sBAAsB,OAAO,EAEjD,KAAK,KAAK,eAAe,CAAE,EAC3B,KAAK,UAAU,OAAO,EAAK,CAAC,EAE5B,KAAK,kBAAkB,EAAK,CAAW,EACvC,KAAK,aAAa,CAAC,EAAE,eAAe,EACpC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAExB,KAAK,UAAU,SAAW,GAC1B,KAAK,KAAK,OAAO,CAEzB,CAeA,kBAA0B,EAAY,EAAoB,EAAiB,EAAiB,EAA0B,CAElH,IAAM,EAAW,CAAC,GAAa,KAAK,oBAAsB,QAOpD,EAAsB,EACtB,IAAI,EAAU,CAAE,UAAW,KAAK,KAAK,iBAAiB,CAAE,CAAE,CAAC,EAC3D,IAAI,EAAO,KAAK,KAAK,aAAa,CAAE,EAAG,CAAE,UAAW,KAAK,KAAK,iBAAiB,CAAE,CAAE,CAAC,EAE1F,EAAI,KAAK,CAAO,EAChB,EAAI,KAAK,CAAO,EAChB,EAAI,QAAQ,CAAE,MAAO,EAAqB,OAAQ,EAAqB,CAAC,EAIxE,EAAI,wBAAwB,EAIxB,EAGA,KAAK,oBAAoB,EAAkB,CAAO,GAGlD,EAAI,cAAc,CAAO,EACzB,EAAI,KAAK,GAGb,KAAK,0BAA0B,CAAE,EAMjC,EAAI,aAAa,EAMjB,KAAK,KAAK,SAAU,CAAG,CAC3B,CAWA,oBAA4B,EAAgB,EAA0B,CAClE,EAAI,UAAU,CAAO,EACrB,EAAI,KAAK,CACb,CAUA,YAA4C,CACxC,GAAI,CAAC,KAAK,0BACN,OAAO,KAGX,IAAI,EAA6B,KAAK,aAAa,EAEnD,KAAO,GAAY,EAAE,aAAoB,IACrC,EAAW,EAAS,mBAAmB,EAG3C,OAAO,aAAoB,EAAiB,EAAW,IAC3D,CAaA,gBAAgD,CAC5C,IAAK,IAAI,EAAyB,KAAK,aAAa,EAAG,EAAM,EAAO,EAAK,mBAAmB,EACxF,GAAI,aAAgB,EAChB,OAAO,EAIf,OAAO,IACX,CAMA,wBAAuC,CAC/B,KAAK,UAAU,OAAS,GAI5B,KAAK,WAAW,CAAC,EAAE,aAAa,CACpC,CAQA,yBAAwC,CACpC,IAAM,EAAM,KAAK,WAAW,EAEvB,GAIL,EAAI,aAAa,KAAK,KAAK,YAAY,CAAC,CAAC,MAAM,GAAM,KAAK,KAAK,iBAAiB,CAAE,CAAC,CAAC,CACxF,CAWA,kBAA0B,EAAqB,EAAkC,CAG7E,GAFc,KAAK,UAAU,SAEf,EAAG,CACb,KAAK,kBAAoB,EAEzB,MACJ,CAEA,GAAI,CAAC,EAAmB,CAGhB,KAAK,kBAAoB,GACzB,OAAK,kBAGT,MACJ,CAEA,IAAM,EAAW,EAAc,EAAI,EAAc,EAAI,EACrD,KAAK,kBAAoB,EAEzB,KAAK,KAAK,gBAAgB,KAAK,UAAU,EAAS,CAAC,EAAE,CACzD,CA0GA,GAAG,EAAmB,EAA0B,CAG5C,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAWA,IAAI,EAAiB,EAA0B,CAG3C,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAgBA,KAAe,EAAmB,GAAG,EAA0B,CAC3D,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CACJ,EAEM,EAAc,EAAS,GAAG,ECx0E1B,GAA+B,GAQ/B,GAAmC,IA2BnC,UAAN,cAAwB,CAAe,CAEnC,KACA,WACA,SACA,SACA,cAUA,YAAY,EAAyB,CACjC,MAAM,CAAO,EAEb,KAAK,KAAO,IAAI,EAAI,CAAE,YAAa,GAAM,UAAW,QAAS,UAAW,QAAS,QAAS,GAAM,sBAAuB,EAAK,CAAC,EAK7H,KAAK,KAAK,4BAA4B,EAAI,EAC1C,KAAK,iBAAiB,KAAK,IAAI,EAQ/B,KAAK,SAAa,0BAA0B,iBAAiB,EAC7D,KAAK,SAAa,0BAA0B,iBAAiB,EAC7D,KAAK,WAAa,0BAA0B,OAAO,EAEnD,KAAK,SAAS,GAAG,aAAkB,KAAK,eAAe,CAAC,EACxD,KAAK,SAAS,GAAG,aAAkB,KAAK,eAAe,CAAC,EACxD,KAAK,WAAW,GAAG,aAAgB,KAAK,aAAa,CAAC,EAEtD,KAAK,KAAK,QAAQ,KAAK,QAAQ,EAC/B,KAAK,KAAK,QAAQ,KAAK,QAAQ,EAC/B,KAAK,KAAK,QAAQ,KAAK,UAAU,EAOjC,KAAK,cAAgB,4BAA4B,KAAK,SAAS,OAAS,iBAAiB,EACzF,KAAK,KAAK,oBAAoB,KAAK,aAAa,EAEhD,KAAK,WAAW,CAAO,CAC3B,CAUA,UAAU,EAA0B,CAGhC,OAFA,KAAK,WAAW,CAAO,EAEhB,IACX,CAWA,SAAS,EAAqB,CAG1B,OAFA,KAAK,cAAc,SAAS,CAAK,EAE1B,IACX,CASA,iBAAkC,CAC9B,KAAK,KAAK,sBACL,GAAkB,KAAK,cAAc,CAAC,MACjC,KAAK,iBAAiB,CAChC,CACJ,CASA,kBAAiC,CAC7B,GAAI,KAAK,eAAe,IAAM,YAAa,CACvC,KAAK,eAAe,KAAK,iBAAiB,EAE1C,MACJ,CAEK,KAAK,cAAc,GAIxB,KAAK,eAAe,CACxB,CAQA,iBAA2B,EAAsB,CAC7C,KAAK,WAAW,WAAW,CAAK,CACpC,CAOA,mBAA6B,EAAsB,CAC/C,KAAK,SAAS,WAAW,CAAK,CAClC,CAOA,mBAA6B,EAAsB,CAC/C,KAAK,SAAS,WAAW,CAAK,CAClC,CASA,qBAA+B,EAA2B,CAE1D,CAUA,YAAsB,EAAuB,CACzC,KAAK,KAAK,sBACN,EAAS,oCAAsC,4CACnD,EAEA,KAAK,kBAAkB,CAAM,CACjC,CASA,kBAA0B,EAAuB,CAC7C,wBAAwB,CAAC,KAAK,SAAU,KAAK,SAAU,KAAK,UAAU,EAAG,CAAM,CACnF,CAQA,UAAmB,CACf,OAAO,KAAK,KAAK,kBAAkB,GAAK,EAC5C,CAUA,qBAAwC,CACpC,OAAO,EACX,CASA,cAAiC,CAC7B,OAAO,EACX,CASA,WAAqB,EAA0B,CAC3C,KAAK,cAAc,CAAO,EAC1B,KAAK,KAAK,UAAU,CAAO,CAC/B,CAUA,kBAAkB,EAA4B,CAC1C,MAAO,EACX,CACJ,EAEM,EAAoB,EAAS,SAAS,EC7Q5C,SAAgB,cAAc,EAAyB,EAAgC,CACnF,IAAM,MAAc,GAA2B,OAAO,SAAS,CAAK,GAAK,EAAQ,EAAI,EAAQ,EACvF,EAAa,EAAM,OAAO,GAAQ,IAAS,OAAO,CAAC,CAAC,OAEtD,EAAa,EAEjB,IAAK,IAAI,EAAM,EAAG,EAAM,EAAM,OAAQ,GAAO,EACrC,EAAM,KAAS,UACf,GAAc,MAAM,EAAO,EAAI,GAIvC,OAAO,EAAM,KAAK,EAAM,IAChB,IAAS,KACF,CAAE,OAAM,MAAO,MAAM,EAAO,EAAI,CAAE,EAKtC,CAAE,OAAM,MAAO,EAAa,EAAI,MAAM,EAAO,EAAI,EAAI,EAAa,EAAI,CAAW,CAC3F,CACL,CAeA,SAAgB,gBAAgB,EAAqB,EAA0B,CAC3E,IAAI,EAAa,EACb,EAAa,EACb,EAAa,EAEjB,IAAK,IAAM,KAAQ,EACX,EAAK,OAAS,KACd,GAAW,EAAK,OAEhB,GAAc,EAAK,MACnB,GAAc,GAStB,IAAM,EAAO,EAAS,EAAI,KAAK,IAAI,EAAG,EAAS,CAAO,EAAI,EACpD,EAAO,EAAO,EAAI,EAAO,EAE/B,OAAO,EAAM,IAAI,GACT,EAAK,OAAS,KACP,EAAK,MAGT,EAAa,EAAK,EAAK,MAAQ,EAAc,EAAO,EAAO,CACrE,CACL,CAcA,SAAgB,kBAAkB,EAAqB,EAAkC,CAWrF,OAVI,EAAM,SAAW,GAAK,EAAM,SAAW,EAAM,OACtC,GAGG,EAAM,OAAO,EAAM,IAC7B,GAAQ,MACL,EAAK,OAAS,EAAM,IACpB,OAAO,SAAS,EAAK,KAAK,GAC1B,EAAK,OAAS,CAEd,GAAS,EAAM,KAAK,GAAQ,EAAK,MAAQ,CAAC,CACrD,CAaA,SAAgB,gBAAgB,EAAkB,EAAyB,CACvE,IAAM,EAAoB,CAAC,EAE3B,IAAK,IAAI,EAAM,EAAG,EAAM,EAAO,GAAO,EAAG,CACrC,IAAM,EAAQ,EAAO,GAErB,EAAQ,KAAK,OAAO,SAAS,CAAK,GAAK,EAAQ,EAAI,EAAQ,CAAC,CAChE,CAEA,IAAM,EAAM,EAAQ,QAAQ,EAAO,IAAW,EAAQ,EAAQ,CAAC,EAE/D,OAAO,EAAM,EAAI,EAAQ,IAAI,GAAU,EAAS,CAAG,EAAI,EAAQ,QAAU,EAAI,CAAK,CACtF"}
|