@jimka/typescript-ui 0.3.0 → 0.4.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.
Files changed (221) hide show
  1. package/dist/lib/{AbstractBooleanInput-C3O4kv7_.js → AbstractBooleanInput-DSwKBAic.js} +2 -2
  2. package/dist/lib/{AbstractBooleanInput-C3O4kv7_.js.map → AbstractBooleanInput-DSwKBAic.js.map} +1 -1
  3. package/dist/lib/{AbstractInput-DFWniHqq.js → AbstractInput-BmWGJTs1.js} +2 -2
  4. package/dist/lib/{AbstractInput-DFWniHqq.js.map → AbstractInput-BmWGJTs1.js.map} +1 -1
  5. package/dist/lib/{AnchorConstraints-CSUcj8Wx.js → AnchorConstraints-CVrMP9rz.js} +2 -2
  6. package/dist/lib/{AnchorConstraints-CSUcj8Wx.js.map → AnchorConstraints-CVrMP9rz.js.map} +1 -1
  7. package/dist/lib/{AnimatedDropdown-e9urCXK1.js → AnimatedDropdown-DKas40B-.js} +2 -2
  8. package/dist/lib/{AnimatedDropdown-e9urCXK1.js.map → AnimatedDropdown-DKas40B-.js.map} +1 -1
  9. package/dist/lib/Border-Bz29z-_7.js +2 -0
  10. package/dist/lib/Border-Bz29z-_7.js.map +1 -0
  11. package/dist/lib/Button-bjpWruNc.js +2 -0
  12. package/dist/lib/{Button-B36rBMKC.js.map → Button-bjpWruNc.js.map} +1 -1
  13. package/dist/lib/{Card-P8iUH2-C.js → Card-BdfbvOD_.js} +2 -2
  14. package/dist/lib/{Card-P8iUH2-C.js.map → Card-BdfbvOD_.js.map} +1 -1
  15. package/dist/lib/ComboBox-CBw2VLLN.js +2 -0
  16. package/dist/lib/ComboBox-CBw2VLLN.js.map +1 -0
  17. package/dist/lib/Component-DHqrPwAw.js +2 -0
  18. package/dist/lib/Component-DHqrPwAw.js.map +1 -0
  19. package/dist/lib/Container-CAQpj4yb.js +2 -0
  20. package/dist/lib/{Container-xUbHk6l_.js.map → Container-CAQpj4yb.js.map} +1 -1
  21. package/dist/lib/{DOM-BeR95tE8.js → DOM-67StIm5G.js} +2 -2
  22. package/dist/lib/DOM-67StIm5G.js.map +1 -0
  23. package/dist/lib/DragChain-CYGuT7OA.js +2 -0
  24. package/dist/lib/DragChain-CYGuT7OA.js.map +1 -0
  25. package/dist/lib/DragManager-CHWCe3-8.js +2 -0
  26. package/dist/lib/DragManager-CHWCe3-8.js.map +1 -0
  27. package/dist/lib/{FieldDecorator-BaTGfKiW.js → FieldDecorator-CgAGcqCk.js} +2 -2
  28. package/dist/lib/{FieldDecorator-BaTGfKiW.js.map → FieldDecorator-CgAGcqCk.js.map} +1 -1
  29. package/dist/lib/{Fit-E-EIZv1k.js → Fit-DIYt2aQB.js} +2 -2
  30. package/dist/lib/{Fit-E-EIZv1k.js.map → Fit-DIYt2aQB.js.map} +1 -1
  31. package/dist/lib/Glyph-CeAIWyPB.js +2 -0
  32. package/dist/lib/Glyph-CeAIWyPB.js.map +1 -0
  33. package/dist/lib/{Grid-BXS2T8MT.js → Grid-ViA0joow.js} +2 -2
  34. package/dist/lib/{Grid-BXS2T8MT.js.map → Grid-ViA0joow.js.map} +1 -1
  35. package/dist/lib/GridConstraints-B1rxCw9B.js +2 -0
  36. package/dist/lib/GridConstraints-B1rxCw9B.js.map +1 -0
  37. package/dist/lib/Header-mKaqgfat.js +2 -0
  38. package/dist/lib/Header-mKaqgfat.js.map +1 -0
  39. package/dist/lib/IconText-BqwXA82-.js +2 -0
  40. package/dist/lib/IconText-BqwXA82-.js.map +1 -0
  41. package/dist/lib/{LayoutSerialization-D1OHmxSB.js → LayoutSerialization-D82UtnDD.js} +2 -2
  42. package/dist/lib/{LayoutSerialization-D1OHmxSB.js.map → LayoutSerialization-D82UtnDD.js.map} +1 -1
  43. package/dist/lib/LayoutSizes-DG9aW9k6.js +2 -0
  44. package/dist/lib/LayoutSizes-DG9aW9k6.js.map +1 -0
  45. package/dist/lib/List-CUntS8ux.js +2 -0
  46. package/dist/lib/List-CUntS8ux.js.map +1 -0
  47. package/dist/lib/MemoryStore-DT6iWWco.js.map +1 -1
  48. package/dist/lib/Menu-B_-SQj72.js +2 -0
  49. package/dist/lib/Menu-B_-SQj72.js.map +1 -0
  50. package/dist/lib/{MenuButton-Bjm1OyB-.js → MenuButton-C79l8Ysf.js} +2 -2
  51. package/dist/lib/{MenuButton-Bjm1OyB-.js.map → MenuButton-C79l8Ysf.js.map} +1 -1
  52. package/dist/lib/{Panel-D9lTLGqP.js → Panel-D2X33qR6.js} +2 -2
  53. package/dist/lib/{Panel-D9lTLGqP.js.map → Panel-D2X33qR6.js.map} +1 -1
  54. package/dist/lib/Position-Ck_KD4cR.js +2 -0
  55. package/dist/lib/Position-Ck_KD4cR.js.map +1 -0
  56. package/dist/lib/{ProgressSpinner-glv-29eK.js → ProgressSpinner-DCyq02pd.js} +2 -2
  57. package/dist/lib/{ProgressSpinner-glv-29eK.js.map → ProgressSpinner-DCyq02pd.js.map} +1 -1
  58. package/dist/lib/{RadioButton-R4S3c9WD.js → RadioButton-BJIAQkII.js} +2 -2
  59. package/dist/lib/{RadioButton-R4S3c9WD.js.map → RadioButton-BJIAQkII.js.map} +1 -1
  60. package/dist/lib/{RovingTabIndex-Dgt01LOE.js → RovingTabIndex-DFJyoveq.js} +2 -2
  61. package/dist/lib/{RovingTabIndex-Dgt01LOE.js.map → RovingTabIndex-DFJyoveq.js.map} +1 -1
  62. package/dist/lib/Scrollbar-CSFK-Y57.js +2 -0
  63. package/dist/lib/Scrollbar-CSFK-Y57.js.map +1 -0
  64. package/dist/lib/Slider-C-kQQYCj.js +2 -0
  65. package/dist/lib/Slider-C-kQQYCj.js.map +1 -0
  66. package/dist/lib/{Spacer-B57i2-_G.js → Spacer-D159iJy9.js} +2 -2
  67. package/dist/lib/{Spacer-B57i2-_G.js.map → Spacer-D159iJy9.js.map} +1 -1
  68. package/dist/lib/TabButton-CHkXBgCj.js +2 -0
  69. package/dist/lib/TabButton-CHkXBgCj.js.map +1 -0
  70. package/dist/lib/{Text-Bni4icFY.js → Text-D5pg7taZ.js} +2 -2
  71. package/dist/lib/{Text-Bni4icFY.js.map → Text-D5pg7taZ.js.map} +1 -1
  72. package/dist/lib/Tooltip-DzK6LxUt.js +3 -0
  73. package/dist/lib/{Tooltip-BUy6uFKN.js.map → Tooltip-DzK6LxUt.js.map} +1 -1
  74. package/dist/lib/VBox-BNegDPs_.js +2 -0
  75. package/dist/lib/VBox-BNegDPs_.js.map +1 -0
  76. package/dist/lib/VirtualScroller-zQr-vKrO.js +2 -0
  77. package/dist/lib/VirtualScroller-zQr-vKrO.js.map +1 -0
  78. package/dist/lib/component/button.es.js +1 -1
  79. package/dist/lib/component/chart.es.js +1 -1
  80. package/dist/lib/component/chart.es.js.map +1 -1
  81. package/dist/lib/component/container.es.js +1 -1
  82. package/dist/lib/component/container.es.js.map +1 -1
  83. package/dist/lib/component/diagram.es.js +1 -1
  84. package/dist/lib/component/diagram.es.js.map +1 -1
  85. package/dist/lib/component/display.es.js +1 -1
  86. package/dist/lib/component/display.es.js.map +1 -1
  87. package/dist/lib/component/editor.es.js +2 -2
  88. package/dist/lib/component/editor.es.js.map +1 -1
  89. package/dist/lib/component/input.es.js +1 -1
  90. package/dist/lib/component/input.es.js.map +1 -1
  91. package/dist/lib/component/list.es.js +1 -1
  92. package/dist/lib/component/list.es.js.map +1 -1
  93. package/dist/lib/component/menubar.es.js +1 -1
  94. package/dist/lib/component/menubar.es.js.map +1 -1
  95. package/dist/lib/component/table.es.js +2 -2
  96. package/dist/lib/component/table.es.js.map +1 -1
  97. package/dist/lib/component/tree.es.js +1 -1
  98. package/dist/lib/component/tree.es.js.map +1 -1
  99. package/dist/lib/core.es.js +1 -1
  100. package/dist/lib/core.es.js.map +1 -1
  101. package/dist/lib/layout.es.js +1 -1
  102. package/dist/lib/layout.es.js.map +1 -1
  103. package/dist/lib/overlay.es.js +1 -1
  104. package/dist/lib/overlay.es.js.map +1 -1
  105. package/dist/lib/primitive.es.js +1 -1
  106. package/dist/lib/router.es.js +1 -1
  107. package/dist/lib/selectionsEqual-4r5R7pTK.js +2 -0
  108. package/dist/lib/selectionsEqual-4r5R7pTK.js.map +1 -0
  109. package/dist/lib/types/component/button/TabCloseButton.d.ts +1 -1
  110. package/dist/lib/types/component/container/CollapseButton.d.ts +1 -1
  111. package/dist/lib/types/component/container/DialogBackdrop.d.ts +1 -1
  112. package/dist/lib/types/component/container/MenuItem.d.ts +4 -0
  113. package/dist/lib/types/component/container/MenuSeparator.d.ts +1 -1
  114. package/dist/lib/types/component/container/Scrollbar.d.ts +1 -0
  115. package/dist/lib/types/component/container/SplitGutter.d.ts +2 -2
  116. package/dist/lib/types/component/container/StatusBar.d.ts +1 -1
  117. package/dist/lib/types/component/container/VirtualScroller.d.ts +1 -0
  118. package/dist/lib/types/component/container/WindowBorder.d.ts +1 -1
  119. package/dist/lib/types/component/diagram/DiagramNode.d.ts +1 -1
  120. package/dist/lib/types/component/display/Canvas.d.ts +8 -2
  121. package/dist/lib/types/component/display/Glyph.d.ts +2 -1
  122. package/dist/lib/types/component/display/Header.d.ts +1 -1
  123. package/dist/lib/types/component/display/IconLabel.d.ts +1 -1
  124. package/dist/lib/types/component/display/IconText.d.ts +1 -1
  125. package/dist/lib/types/component/display/Image.d.ts +1 -1
  126. package/dist/lib/types/component/display/Video.d.ts +1 -1
  127. package/dist/lib/types/component/display/WebGLCanvas.d.ts +8 -2
  128. package/dist/lib/types/component/editor/CodeEditor.d.ts +1 -1
  129. package/dist/lib/types/component/input/AbstractPickerField.d.ts +1 -1
  130. package/dist/lib/types/component/input/AutoCompleteDropdown.d.ts +1 -1
  131. package/dist/lib/types/component/input/Link.d.ts +1 -1
  132. package/dist/lib/types/component/input/NumberSpinner.d.ts +1 -1
  133. package/dist/lib/types/component/input/PasswordField.d.ts +1 -1
  134. package/dist/lib/types/component/input/SpinButton.d.ts +1 -1
  135. package/dist/lib/types/component/input/TextArea.d.ts +1 -1
  136. package/dist/lib/types/component/input/TextField.d.ts +3 -1
  137. package/dist/lib/types/component/input/UsernameField.d.ts +1 -1
  138. package/dist/lib/types/component/list/AbstractMarkerList.d.ts +9 -1
  139. package/dist/lib/types/component/list/BulletedList.d.ts +1 -0
  140. package/dist/lib/types/component/list/ListItem.d.ts +10 -4
  141. package/dist/lib/types/component/list/NumberedList.d.ts +1 -0
  142. package/dist/lib/types/component/menubar/MenuBarButton.d.ts +1 -1
  143. package/dist/lib/types/component/menubar/ToolBar.d.ts +1 -1
  144. package/dist/lib/types/component/menubar/ToolBarSeparator.d.ts +1 -1
  145. package/dist/lib/types/component/shared/VirtualRowView.d.ts +13 -1
  146. package/dist/lib/types/component/table/Body.d.ts +15 -2
  147. package/dist/lib/types/component/table/CellGeometry.d.ts +6 -0
  148. package/dist/lib/types/component/table/Column.d.ts +4 -0
  149. package/dist/lib/types/component/table/ColumnConfig.d.ts +3 -0
  150. package/dist/lib/types/component/table/Header.d.ts +24 -2
  151. package/dist/lib/types/component/table/Row.d.ts +13 -2
  152. package/dist/lib/types/component/table/Table.d.ts +25 -4
  153. package/dist/lib/types/component/table/TableExporter.d.ts +1 -1
  154. package/dist/lib/types/component/table/TablePanel.d.ts +2 -1
  155. package/dist/lib/types/component/table/cell/Cell.d.ts +1 -1
  156. package/dist/lib/types/component/table/cell/Header.d.ts +3 -0
  157. package/dist/lib/types/component/table/index.d.ts +1 -1
  158. package/dist/lib/types/component/tree/Tree.d.ts +1 -0
  159. package/dist/lib/types/core/Aria.d.ts +1 -1
  160. package/dist/lib/types/core/Body.d.ts +8 -2
  161. package/dist/lib/types/core/Component.d.ts +6 -0
  162. package/dist/lib/types/core/DOM.d.ts +4 -0
  163. package/dist/lib/types/core/DragChain.d.ts +3 -0
  164. package/dist/lib/types/core/Favicon.d.ts +7 -0
  165. package/dist/lib/types/core/FirstLayoutGate.d.ts +5 -0
  166. package/dist/lib/types/core/Theme.d.ts +1 -0
  167. package/dist/lib/types/core/Util.d.ts +1 -0
  168. package/dist/lib/types/core/index.d.ts +2 -0
  169. package/dist/lib/types/layout/Accordion.d.ts +0 -1
  170. package/dist/lib/types/layout/FlowLayout.d.ts +4 -0
  171. package/dist/lib/types/layout/HFlow.d.ts +3 -0
  172. package/dist/lib/types/layout/VFlow.d.ts +1 -0
  173. package/dist/lib/types/overlay/AbstractWindow.d.ts +1 -1
  174. package/dist/lib/types/overlay/Dock.d.ts +1 -0
  175. package/dist/lib/types/overlay/DropZoneOverlay.d.ts +1 -0
  176. package/dist/lib/types/overlay/Popover.d.ts +1 -1
  177. package/dist/lib/validation.es.js +1 -1
  178. package/llms.txt +1 -0
  179. package/package.json +2 -2
  180. package/dist/lib/Border-Dq6dXkmS.js +0 -2
  181. package/dist/lib/Border-Dq6dXkmS.js.map +0 -1
  182. package/dist/lib/Button-B36rBMKC.js +0 -2
  183. package/dist/lib/ComboBox-BjAhXXkY.js +0 -2
  184. package/dist/lib/ComboBox-BjAhXXkY.js.map +0 -1
  185. package/dist/lib/Component-CHTzJxEf.js +0 -2
  186. package/dist/lib/Component-CHTzJxEf.js.map +0 -1
  187. package/dist/lib/Container-xUbHk6l_.js +0 -2
  188. package/dist/lib/DOM-BeR95tE8.js.map +0 -1
  189. package/dist/lib/DragManager-T5Oljd0F.js +0 -2
  190. package/dist/lib/DragManager-T5Oljd0F.js.map +0 -1
  191. package/dist/lib/Glyph-CnNslL95.js +0 -2
  192. package/dist/lib/Glyph-CnNslL95.js.map +0 -1
  193. package/dist/lib/GridConstraints-D_kbd-h2.js +0 -2
  194. package/dist/lib/GridConstraints-D_kbd-h2.js.map +0 -1
  195. package/dist/lib/HBox-Dm3xgsDE.js +0 -2
  196. package/dist/lib/HBox-Dm3xgsDE.js.map +0 -1
  197. package/dist/lib/Header-CqFc2OUx.js +0 -2
  198. package/dist/lib/Header-CqFc2OUx.js.map +0 -1
  199. package/dist/lib/IconText-CTM2r0Tv.js +0 -2
  200. package/dist/lib/IconText-CTM2r0Tv.js.map +0 -1
  201. package/dist/lib/LayoutSizes-CvMmMgHJ.js +0 -2
  202. package/dist/lib/LayoutSizes-CvMmMgHJ.js.map +0 -1
  203. package/dist/lib/List-DHeOPAK2.js +0 -2
  204. package/dist/lib/List-DHeOPAK2.js.map +0 -1
  205. package/dist/lib/Menu-B6ippw-p.js +0 -2
  206. package/dist/lib/Menu-B6ippw-p.js.map +0 -1
  207. package/dist/lib/Position-DRSlWn4Z.js +0 -2
  208. package/dist/lib/Position-DRSlWn4Z.js.map +0 -1
  209. package/dist/lib/Scrollbar-CiVV3axj.js +0 -2
  210. package/dist/lib/Scrollbar-CiVV3axj.js.map +0 -1
  211. package/dist/lib/Slider-_JVvuQ8M.js +0 -2
  212. package/dist/lib/Slider-_JVvuQ8M.js.map +0 -1
  213. package/dist/lib/TabButton-DEypdlZf.js +0 -2
  214. package/dist/lib/TabButton-DEypdlZf.js.map +0 -1
  215. package/dist/lib/Tooltip-BUy6uFKN.js +0 -3
  216. package/dist/lib/VBox-Dr7Ge9M6.js +0 -2
  217. package/dist/lib/VBox-Dr7Ge9M6.js.map +0 -1
  218. package/dist/lib/VirtualScroller-BPy6KmSX.js +0 -2
  219. package/dist/lib/VirtualScroller-BPy6KmSX.js.map +0 -1
  220. package/dist/lib/selectionsEqual-2PJrcimg.js +0 -2
  221. package/dist/lib/selectionsEqual-2PJrcimg.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.es.js","names":[],"sources":["../../src/typescript/lib/overlay/Popover.ts","../../src/typescript/lib/component/container/DialogBackdrop.ts","../../src/typescript/lib/overlay/Dialog.ts","../../src/typescript/lib/overlay/Notification.ts","../../src/typescript/lib/overlay/NotificationHistoryButton.ts","../../src/typescript/lib/overlay/Drawer.ts","../../src/typescript/lib/overlay/RailHandle.ts","../../src/typescript/lib/overlay/Rail.ts","../../src/typescript/lib/overlay/Dock.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { fadeShow, fadeHideAndDetach } from \"~/core/AnimatedDropdown.js\";\nimport type { Animation } from \"~/core/Animation.js\";\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport type { Edge } from \"~/primitive/Edge.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM, type Rect, type Handle } from \"~/core/DOM.js\";\n\n/** Fallback arrow side length used until the theme token is read. */\nconst DEFAULT_ARROW_SIZE_PX: number = 14;\n\n/**\n * Half of the visual extent of the rotated arrow along either axis. The\n * arrow is a square of side `DEFAULT_ARROW_SIZE_PX` rotated 45°, so its\n * bounding rectangle is `DEFAULT_ARROW_SIZE_PX * sqrt(2)` on each side and\n * each corner sits half that far from the centre.\n */\nconst ARROW_VISUAL_HALF: number = (DEFAULT_ARROW_SIZE_PX / 2) * Math.SQRT2;\n\n/** Pixel gap between the arrow tip and the anchor element. */\nconst ARROW_ANCHOR_GAP_PX: number = 2;\n\n/**\n * Pixel gap between the popover edge and the anchor element. Sized so the\n * arrow tip lands {@link ARROW_ANCHOR_GAP_PX} away from the anchor.\n */\nconst POPOVER_ANCHOR_GAP: number = Math.ceil(ARROW_VISUAL_HALF) + ARROW_ANCHOR_GAP_PX;\n\n/** Pixel inset preserved between the arrow and the popover corner. */\nconst ARROW_EDGE_INSET_PX: number = 6;\n\n/** Pixel inset preserved between the arrow / popover and any viewport edge. */\nconst VIEWPORT_EDGE_INSET_PX: number = 5;\n\n/** Fade duration matched to the rest of the floating-overlay family. */\nconst POPOVER_FADE_DURATION_MS: number = 120;\n\n/**\n * Placement of a {@link Popover} relative to its anchor element. `\"auto\"`\n * picks the side with the most viewport space at `show()` time.\n *\n * @category Core\n */\nexport type PopoverPlacement = Edge | \"auto\";\n\n/**\n * Strategy used to dismiss a {@link Popover}:\n *\n * - `\"click-outside\"`: closes on a `pointerdown` outside both the popover and\n * its anchor element (the anchor is excluded so the trigger click can\n * re-open without immediate re-close).\n * - `\"blur\"`: closes when focus or a pointer leaves the popover subtree —\n * including a portaled descendant layer such as a dropdown opened inside\n * the popover, which now keeps the popover open.\n * - `\"manual\"`: caller drives `hide()` explicitly.\n *\n * @remarks Dismissal is executed by {@link LayerManager}: the popover reports\n * this mode from its {@link DismissableLayer.getDismissMode} and the manager's\n * document-level handlers decide when to call `requestClose`. This is the\n * public option name; it maps 1:1 onto {@link LayerDismissMode}.\n *\n * @category Core\n */\nexport type PopoverDismissMode = \"click-outside\" | \"blur\" | \"manual\";\n\n/**\n * Construction-time options for {@link Popover}.\n *\n * @category Core\n */\nexport interface PopoverOptions extends ContainerOptions {\n /** Resolved placement relative to the anchor. Default `\"auto\"`. */\n placement?: PopoverPlacement;\n /** Dismiss strategy. Default `\"click-outside\"`. */\n dismissOn?: PopoverDismissMode;\n /** When `false`, the arrow tail is not rendered. Default `true`. */\n showArrow?: boolean;\n /** Optional title row rendered above the body. */\n title?: string;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each present setter once\n * with the final value, so any field the caller supplied wins.\n */\nconst _defaultPopoverOptions: Partial<PopoverOptions> = {\n insets: new Insets(5, 5, 5, 5),\n placement: \"auto\",\n dismissOn: \"click-outside\",\n showArrow: true,\n};\n\n/**\n * An anchored, non-modal floating bubble with a directional arrow tail. Use\n * `Popover` for click-triggered, interactive content (title, body, action\n * buttons, or any arbitrary subtree) — for ephemeral hover hints reach for\n * [`Tooltip`](/api/overlay/classes/Tooltip), and for modal containment reach\n * for [`Dialog`](/api/overlay/classes/Dialog).\n *\n * `Popover` extends {@link Container} so authors can compose freely via\n * `addComponent`; the `setTitle` / `setBody` / `addAction` conveniences are\n * sugar over the same container surface.\n *\n * Positioning is anchor-relative. `attachToComponent(Component)` records the\n * anchor. While the popover is open, `window` `resize` and each scrollable\n * ancestor's `scroll` event trigger a reposition so the bubble follows the\n * anchor.\n *\n * Fade-in / fade-out reuse the shared [`fadeShow`](/api/core/functions/fadeShow)\n * and [`fadeHideAndDetach`](/api/core/functions/fadeHideAndDetach) helpers,\n * inheriting the standard 120 ms `opacity + translateY` transition and the\n * re-entrancy guard against a fresh `show()` mid-fade.\n *\n * @example\n * ```typescript\n * const popover = new Popover({ placement: \"auto\" });\n * popover.setTitle(\"Confirm delete\");\n * popover.setBody(\"This action cannot be undone.\");\n * popover.addAction(\"Delete\", () => doDelete());\n * popover.addAction(\"Cancel\", () => popover.hide());\n *\n * Event.addListener(triggerButton, \"click\", () => {\n * popover.attachToComponent(triggerButton);\n * popover.show();\n * });\n * ```\n *\n * @category Core\n */\nclass Popover extends Container<PopoverOptions> implements DismissableLayer {\n\n // Option-backed fields use `declare` rather than initializers to dodge the\n // class-field super-cascade trap: an initializer runs *after* super()\n // returns, overwriting whatever the cascade-dispatched setter wrote\n // during the super-time `applyOptions` call. The applyOptions override\n // below always dispatches each setter with a fallback so the field is\n // seeded even when no caller option was supplied.\n declare private _placement: PopoverPlacement;\n private _resolvedPlacement: PopoverPlacement = \"bottom\";\n declare private _dismissOn: PopoverDismissMode;\n declare private _showArrow: boolean;\n declare private _title: string | null;\n declare private _titleComponent: Text | null;\n private _bodyComponent: Component | null = null;\n private _actionsRow: Component | null = null;\n\n // In-flight fades, cancelled on teardown so their fallback timers cannot\n // fire against this popover's released element handle.\n private _fadeShowAnimation: Animation.CancelHandle | null = null;\n private _fadeHideAnimation: Animation.CancelHandle | null = null;\n private _anchorElement: Handle | null = null;\n private _arrowComponent: Component | null = null;\n private _isOpen: boolean = false;\n private _scrollAncestors: Handle[] = [];\n\n private readonly _onWindowResize: () => void;\n private readonly _onScroll: () => void;\n\n /**\n * Creates a popover with a 5px layout-side inset and the default chrome\n * wired to the theme tokens.\n *\n * @param options - Optional construction-time options.\n */\n constructor(options?: PopoverOptions) {\n super(options as PopoverOptions, _defaultPopoverOptions);\n\n const vbox = new VBox();\n\n vbox.setStretching(true);\n this.setLayoutManager(vbox);\n\n // Theme-driven chrome.\n this.setBackgroundColor(\"var(--ts-ui-popover-bg, rgb(255, 255, 255))\");\n this.setForegroundColor(\"var(--ts-ui-popover-color, rgb(0, 0, 0))\");\n this.setBorder({ border: \"1px solid var(--ts-ui-popover-border, rgb(200, 200, 200))\" });\n this.setBorderRadius(\"var(--ts-ui-popover-radius, 6px)\");\n this.setShadow(\"var(--ts-ui-popover-shadow, 2px 4px 12px rgba(0, 0, 0, 0.18))\");\n\n // Overlay placement: top-level, viewport-fixed. The z-index is\n // stamped from LayerManager's Popover band at show() time, so no\n // static value is set here.\n this.setPosition(Position.FIXED);\n this.setVisible(false);\n\n // `paint` containment would clip the arrow tail (which straddles the\n // popover edge), so use `layout` containment only. The framework\n // default `overflow: hidden` would also clip the arrow, so opt out.\n this.setContain(\"layout\");\n this.setOverflow(\"visible\");\n this.getAria().setRole(\"dialog\");\n\n this._onWindowResize = () => this._reposition();\n this._onScroll = () => this._reposition();\n\n // Seed the `declare`-d title fields after the cascade has had its\n // chance to write them via setTitle. Without a `title` option the\n // cascade dispatch above skips setTitle entirely, so the fields\n // would otherwise remain undefined (declare allocates no default).\n this._title ??= null;\n this._titleComponent ??= null;\n }\n\n /**\n * Applies a {@link PopoverOptions} bag, dispatching popover-specific\n * fields after the inherited Container cascade.\n *\n * @param options - The options bag carrying the values to apply.\n * @returns This popover, for method chaining.\n */\n protected applyOptions(options: PopoverOptions): this {\n super.applyOptions(options);\n\n // placement/dismissOn/showArrow carry a class default and seed\n // construction-time state, so always dispatch the caller value or the\n // class default; title has no default.\n this.setPlacement(options.placement ?? this.getPlacement());\n this.setDismissOn(options.dismissOn ?? this.getDismissOn());\n this.setShowArrow(options.showArrow ?? this.isShowArrow());\n if (options.title !== undefined) this.setTitle(options.title);\n\n return this;\n }\n\n /**\n * Sets the requested placement relative to the anchor.\n *\n * @param p - One of `\"top\"`, `\"bottom\"`, `\"left\"`, `\"right\"`, or `\"auto\"`.\n * @returns This popover, for method chaining.\n */\n setPlacement(p: PopoverPlacement): this {\n this._placement = p;\n\n return this;\n }\n\n /**\n * Returns the configured placement.\n *\n * @returns The placement set via {@link setPlacement}, or `\"auto\"` by default.\n */\n getPlacement(): PopoverPlacement {\n return this._placement ?? this._defaultOptions.placement!;\n }\n\n /**\n * Sets the dismiss strategy.\n *\n * @param mode - One of `\"click-outside\"`, `\"blur\"`, or `\"manual\"`.\n * @returns This popover, for method chaining.\n */\n setDismissOn(mode: PopoverDismissMode): this {\n this._dismissOn = mode;\n\n return this;\n }\n\n /**\n * Returns the active dismiss strategy.\n *\n * @returns The dismiss strategy set via {@link setDismissOn}.\n */\n getDismissOn(): PopoverDismissMode {\n return this._dismissOn ?? this._defaultOptions.dismissOn!;\n }\n\n /**\n * Enables or disables the arrow tail.\n *\n * @param value - `true` to render the arrow, `false` to hide it.\n * @returns This popover, for method chaining.\n */\n setShowArrow(value: boolean): this {\n this._showArrow = value;\n\n if (this._arrowComponent) {\n this._arrowComponent.setVisible(value);\n }\n\n return this;\n }\n\n /**\n * Returns whether the arrow tail is currently enabled.\n *\n * @returns `true` if the arrow is rendered.\n */\n isShowArrow(): boolean {\n return this._showArrow ?? this._defaultOptions.showArrow!;\n }\n\n /**\n * Sets the title row text. Lazily creates the underlying\n * [`Text`](/api/component/input/classes/Text) child on first call. Passing\n * `null` clears the title.\n *\n * @param text - The title string, or `null` to clear.\n * @returns This popover, for method chaining.\n */\n setTitle(text: string | null): this {\n if (text === null) {\n return this.clearTitle();\n }\n\n this._title = text;\n\n if (!this._titleComponent) {\n this._titleComponent = new Text(text);\n this._titleComponent.setFontWeight(\"bold\");\n\n this.insertComponent(this._titleComponent, 0);\n } else {\n this._titleComponent.setText(text);\n }\n\n return this;\n }\n\n /**\n * Returns the current title string, or `null` if no title is set.\n *\n * @returns The title set via {@link setTitle}, or `null`.\n */\n getTitle(): string | null {\n return this._title;\n }\n\n /**\n * Removes the title row.\n *\n * @returns This popover, for method chaining.\n */\n clearTitle(): this {\n if (this._titleComponent) {\n this.removeComponent(this._titleComponent);\n this._titleComponent = null;\n }\n\n this._title = null;\n\n return this;\n }\n\n /**\n * Sets the body content. A `string` is wrapped in a\n * [`Text`](/api/component/input/classes/Text) child; a `Component` is used\n * directly. Replaces any previous body.\n *\n * @param content - The body string or component.\n * @returns This popover, for method chaining.\n */\n setBody(content: Component | string): this {\n if (this._bodyComponent) {\n this.removeComponent(this._bodyComponent);\n this._bodyComponent = null;\n }\n\n const next: Component = typeof content === \"string\"\n ? new Text(content)\n : content;\n\n this._bodyComponent = next;\n\n // Insert after the title (if any) and before the actions row (if any).\n const titleOffset = this._titleComponent ? 1 : 0;\n\n this.insertComponent(next, titleOffset);\n\n return this;\n }\n\n /**\n * Returns the current body component, or `null` when none has been set.\n *\n * @returns The body component, or `null`.\n */\n getBody(): Component | null {\n return this._bodyComponent;\n }\n\n /**\n * Appends an action button to the actions row. Lazily creates the row\n * (an [`HBox`](/api/layout/classes/HBox)-laid `Panel`) on first call.\n *\n * @param label - The button label.\n * @param onClick - Handler invoked when the button is activated.\n * @returns This popover, for method chaining.\n */\n addAction(label: string, onClick: () => void): this {\n if (!this._actionsRow) {\n this._actionsRow = new Component();\n this._actionsRow.setLayoutManager(new HBox());\n\n this.addComponent(this._actionsRow);\n }\n\n const button = new Button(label);\n\n button.on(\"action\", onClick);\n\n this._actionsRow.addComponent(button);\n\n return this;\n }\n\n /**\n * Removes every action button previously registered via {@link addAction}.\n *\n * @returns This popover, for method chaining.\n */\n clearActions(): this {\n if (this._actionsRow) {\n this.removeComponent(this._actionsRow);\n this._actionsRow = null;\n }\n\n return this;\n }\n\n /**\n * Records the raw DOM element used as the positioning anchor.\n *\n * @param el - The anchor element.\n * @returns This popover, for method chaining.\n */\n private _attachToElement(el: Handle): this {\n this._anchorElement = el;\n\n return this;\n }\n\n /**\n * Records the anchor by resolving the given component's element. Must be\n * called before {@link Popover.show}.\n *\n * @param c - The component whose element should be used as the anchor.\n * @returns This popover, for method chaining.\n */\n attachToComponent(c: Component): this {\n return this._attachToElement(c.getElement(true)!);\n }\n\n /**\n * Mounts the popover on `document.documentElement` (if not already\n * mounted), resolves the final placement, positions the bubble + arrow,\n * fades it in, and wires the dismiss listeners.\n *\n * @returns This popover, for method chaining.\n */\n show(): this {\n if (!this._anchorElement) {\n console.warn(\"Popover.show(): no anchor attached; call attachToComponent first.\");\n return this;\n }\n\n this._isOpen = true;\n\n // Join the central layer tree and mirror its band-based z-stamp so a\n // popover opened from inside a window or dropdown stacks correctly.\n LayerManager.register(this);\n this.setZIndex(LayerManager.getZIndex(this));\n\n const el = this.getElement(true)!;\n\n LayerManager.mount(el);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the popover.\n trapWheel(this);\n\n this.ensureArrow();\n this.setVisible(true);\n\n // First measurement pass: compute preferred size so we can resolve\n // placement against the real bubble dimensions.\n this.doLayout();\n this._reposition();\n\n this._fadeShowAnimation?.cancel();\n this._fadeShowAnimation = fadeShow(this, { durationMs: POPOVER_FADE_DURATION_MS });\n\n this.attachRepositionListeners();\n\n return this;\n }\n\n /**\n * Plays the exit fade, detaches the popover from the DOM, and removes\n * every dismiss / reposition listener.\n *\n * @returns This popover, for method chaining.\n */\n hide(): this {\n if (!this._isOpen) {\n return this;\n }\n\n this._isOpen = false;\n\n this.detachRepositionListeners();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._fadeHideAnimation?.cancel();\n this._fadeHideAnimation = fadeHideAndDetach(this, { durationMs: POPOVER_FADE_DURATION_MS });\n\n return this;\n }\n\n /**\n * Returns whether the popover is currently open (showing or fading in).\n *\n * @returns `true` when the popover is open.\n */\n isOpen(): boolean {\n return this._isOpen;\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the popover's root element for the central layer tree.\n *\n * @returns The popover'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, mapping\n * the public {@link PopoverDismissMode} directly onto the manager's\n * vocabulary (the two share the `\"click-outside\"` / `\"blur\"` / `\"manual\"`\n * names). The manager now executes dismissal; the `\"blur\"` mode works for\n * a nested dropdown because the dropdown registers as the popover's child.\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return this._dismissOn ?? this._defaultOptions.dismissOn!;\n }\n\n /**\n * Advisory close request from the manager — runs the standard\n * {@link Popover.hide} teardown, which unregisters the layer.\n */\n requestClose(): void {\n this.hide();\n }\n\n /**\n * Returns the anchor element excluded from outside-interaction tests so a\n * click on the trigger does not immediately re-close the popover.\n *\n * @returns The anchor element, or null when none is attached.\n */\n getAnchorElement(): Handle | null {\n return this._anchorElement;\n }\n\n /**\n * Returns the popover's z-index band so an unrelated top-level popover\n * stacks above windows but below dropdowns and dialogs.\n *\n * @returns The popover band base.\n */\n getBand(): number {\n return LayerManager.Band.Popover;\n }\n\n /**\n * Lays out children (delegating to the VBox) and positions the arrow\n * tail along the resolved edge.\n *\n * @returns This popover, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n if (this._arrowComponent && this._showArrow && this._anchorElement) {\n this.positionArrow();\n }\n\n return this;\n }\n\n /**\n * Releases per-instance resources. Closes the popover if it is still\n * open, disposes the raw-appended arrow, then defers to the base class\n * for the rest of teardown.\n */\n protected destructor(): void {\n if (this._isOpen) {\n this.hide();\n }\n\n // `_titleComponent` / `_bodyComponent` / `_actionsRow` are all\n // registered via `insertComponent` / `addComponent`, so\n // `super.destructor()`'s child recursion below already disposes\n // them — an explicit call here would run each one's destructor()\n // a second time. `_arrowComponent`, by contrast, is inserted\n // straight into the DOM via `ensureArrow()` (not registered in\n // `_components`), so recursion can never reach it and it must be\n // disposed explicitly.\n // After `hide()` above, since that starts a fresh fade-out whose\n // fallback timer would otherwise outlive the element handle\n // `super.destructor()` releases below.\n this._fadeShowAnimation?.cancel();\n this._fadeShowAnimation = null;\n this._fadeHideAnimation?.cancel();\n this._fadeHideAnimation = null;\n\n this._arrowComponent?.dispose();\n\n this._anchorElement = null;\n this._titleComponent = null;\n this._bodyComponent = null;\n this._actionsRow = null;\n this._arrowComponent = null;\n\n super.destructor();\n }\n\n /**\n * Resolves the requested placement against the viewport, anchor rect, and\n * own preferred size; updates the popover's `setX` / `setY` and the\n * resolved-placement field.\n */\n private _reposition(): void {\n if (!this._anchorElement) {\n return;\n }\n\n const anchorRect = DOM.source.getElementRect(this._anchorElement);\n\n // Anchor removed from the DOM or rendered with zero size — close.\n if (anchorRect.width === 0 && anchorRect.height === 0) {\n this.hide();\n return;\n }\n\n const preferred = this.getPreferredSize();\n const width = preferred?.width ?? this.getWidth();\n const height = preferred?.height ?? this.getHeight();\n const vp = DOM.source.getViewportSize();\n\n const resolved = this.resolvePlacement(anchorRect, width, height, vp);\n\n this._resolvedPlacement = resolved;\n\n let x: number = 0;\n let y: number = 0;\n\n if (resolved === \"top\") {\n x = anchorRect.left + (anchorRect.width - width) / 2;\n y = anchorRect.top - height - POPOVER_ANCHOR_GAP;\n } else if (resolved === \"bottom\") {\n x = anchorRect.left + (anchorRect.width - width) / 2;\n y = anchorRect.bottom + POPOVER_ANCHOR_GAP;\n } else if (resolved === \"left\") {\n x = anchorRect.left - width - POPOVER_ANCHOR_GAP;\n y = anchorRect.top + (anchorRect.height - height) / 2;\n } else { // \"right\"\n x = anchorRect.right + POPOVER_ANCHOR_GAP;\n y = anchorRect.top + (anchorRect.height - height) / 2;\n }\n\n // Clamp into viewport so the bubble stays on-screen. The arrow's\n // independent positioning keeps it pointing at the anchor centre\n // even when the bubble is shifted laterally. On the tip-direction\n // side, leave room for the arrow's outward visual extent so the tip\n // also stays at least VIEWPORT_EDGE_INSET_PX inside the viewport.\n let minX = VIEWPORT_EDGE_INSET_PX;\n let maxX = vp.width - width - VIEWPORT_EDGE_INSET_PX;\n let minY = VIEWPORT_EDGE_INSET_PX;\n let maxY = vp.height - height - VIEWPORT_EDGE_INSET_PX;\n\n if (resolved === \"right\") {\n minX += ARROW_VISUAL_HALF;\n } else if (resolved === \"left\") {\n maxX -= ARROW_VISUAL_HALF;\n } else if (resolved === \"bottom\") {\n minY += ARROW_VISUAL_HALF;\n } else {\n maxY -= ARROW_VISUAL_HALF;\n }\n\n x = Util.clamp(x, minX, maxX);\n y = Util.clamp(y, minY, maxY);\n\n this.setX(x);\n this.setY(y);\n this.setWidth(width);\n this.setHeight(height);\n\n if (this._arrowComponent && this._showArrow) {\n this.positionArrow();\n }\n }\n\n /**\n * Picks the placement to use given the configured value and the available\n * viewport space. `\"auto\"` selects the side with the most room; an\n * explicit side is honoured unless it would overflow, in which case the\n * opposite side is used and a console warning is logged.\n *\n * @param anchor - The anchor's `DOMRect`.\n * @param width - The popover's preferred width in pixels.\n * @param height - The popover's preferred height in pixels.\n * @param vp - The viewport size returned by `DOM.source.getViewportSize`.\n * @returns The resolved placement.\n */\n private resolvePlacement(\n anchor: Rect,\n width: number,\n height: number,\n vp: { width: number; height: number },\n ): PopoverPlacement {\n const spaceTop = anchor.top;\n const spaceBottom = vp.height - anchor.bottom;\n const spaceLeft = anchor.left;\n const spaceRight = vp.width - anchor.right;\n\n if (this._placement === \"auto\") {\n const needsH = height + POPOVER_ANCHOR_GAP;\n const needsV = width + POPOVER_ANCHOR_GAP;\n\n const candidates: Array<{ side: PopoverPlacement; space: number; need: number }> = [\n { side: \"bottom\", space: spaceBottom, need: needsH },\n { side: \"top\", space: spaceTop, need: needsH },\n { side: \"right\", space: spaceRight, need: needsV },\n { side: \"left\", space: spaceLeft, need: needsV },\n ];\n\n // Prefer a side that physically fits; otherwise pick the one\n // with the most absolute space.\n const fitting = candidates.filter(c => c.space >= c.need);\n const pool = fitting.length > 0 ? fitting : candidates;\n\n pool.sort((a, b) => b.space - a.space);\n\n return pool[0].side;\n }\n\n // Explicit placement — honour unless the requested side cannot fit.\n const fits = (side: PopoverPlacement): boolean => {\n if (side === \"top\") { return spaceTop >= height + POPOVER_ANCHOR_GAP; }\n if (side === \"bottom\") { return spaceBottom >= height + POPOVER_ANCHOR_GAP; }\n if (side === \"left\") { return spaceLeft >= width + POPOVER_ANCHOR_GAP; }\n\n return spaceRight >= width + POPOVER_ANCHOR_GAP;\n };\n\n if (fits(this._placement)) {\n return this._placement;\n }\n\n const opposite: Record<Exclude<PopoverPlacement, \"auto\">, Exclude<PopoverPlacement, \"auto\">> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n };\n\n const flipped = opposite[this._placement as Exclude<PopoverPlacement, \"auto\">];\n\n console.warn(`Popover: explicit placement \"${this._placement}\" overflows the viewport; falling back to \"${flipped}\".`);\n\n return flipped;\n }\n\n /**\n * Lazily creates the arrow tail component on first show.\n *\n * @remarks The arrow's element is inserted as the *first* DOM child of\n * the popover (not via `addComponent`) so the VBox layout manager does\n * not size it AND so it paints beneath subsequent sibling content. The\n * outward half of the diamond is still visible because it lies outside\n * the popover's bounds; the inward half is overlaid by the bubble's\n * children, preventing the arrow from obscuring the title/body text.\n */\n private ensureArrow(): void {\n if (this._arrowComponent) {\n return;\n }\n\n const arrow = new Component();\n const size = DEFAULT_ARROW_SIZE_PX;\n\n arrow.setBackgroundColor(\"var(--ts-ui-popover-bg, rgb(255, 255, 255))\");\n arrow.setWidth(size);\n arrow.setHeight(size);\n arrow.setTransform(\"rotate(45deg)\");\n arrow.setPointerEvents(\"none\");\n arrow.setVisible(this._showArrow);\n\n this._arrowComponent = arrow;\n\n const popoverEl = this.getElement(true)!;\n DOM.sink.insertBefore(popoverEl, arrow.getElement(true)!, DOM.source.getFirstChild(popoverEl));\n }\n\n /**\n * Positions the arrow tail along the resolved popover edge so it points\n * at the anchor centre, clamped to keep it within the popover's lateral\n * extent. Also re-applies the directional inset box-shadow that draws the\n * two outward-facing edges of the rotated diamond so the popover outline\n * appears continuous through the arrow.\n */\n private positionArrow(): void {\n if (!this._anchorElement || !this._arrowComponent) {\n return;\n }\n\n const anchorRect = DOM.source.getElementRect(this._anchorElement);\n const size = DEFAULT_ARROW_SIZE_PX;\n const half = size / 2;\n const popoverX = this.getX();\n const popoverY = this.getY();\n const popoverW = this.getWidth();\n const popoverH = this.getHeight();\n\n // High-first clamp (not Util.clamp): when the popover is larger than the\n // viewport, maxLocal < minLocal, and the arrow must pin to the leading\n // inset (minLocal), which the low-first Util.clamp would not do.\n const clamp = (value: number, min: number, max: number): number => Math.max(min, Math.min(value, max));\n\n // Inset box-shadow draws a 1px line on the two outward-facing edges\n // of the rotated diamond. The mapping below is given in\n // pre-rotation coordinates (the element's own CSS axes); after the\n // 45° clockwise rotation, original TOP/RIGHT/BOTTOM/LEFT edges map to\n // the rotated NE/SE/SW/NW edges.\n const borderColor = \"var(--ts-ui-popover-border, rgb(200, 200, 200))\";\n let arrowShadow: string;\n\n if (this._resolvedPlacement === \"bottom\") {\n arrowShadow = `inset 1px 0 0 ${borderColor}, inset 0 1px 0 ${borderColor}`;\n } else if (this._resolvedPlacement === \"top\") {\n arrowShadow = `inset -1px 0 0 ${borderColor}, inset 0 -1px 0 ${borderColor}`;\n } else if (this._resolvedPlacement === \"right\") {\n arrowShadow = `inset 1px 0 0 ${borderColor}, inset 0 -1px 0 ${borderColor}`;\n } else {\n arrowShadow = `inset 0 1px 0 ${borderColor}, inset -1px 0 0 ${borderColor}`;\n }\n\n this._arrowComponent.setShadow(arrowShadow);\n\n const vp = DOM.source.getViewportSize();\n const border = this.getBorderSize();\n\n // `position: absolute` measures from the popover's padding box (i.e.\n // inside the border). Subtracting the border width centres the\n // rotated diamond on the popover's *outer* edge, keeping the tip-to-\n // anchor gap predictable regardless of border thickness.\n if (this._resolvedPlacement === \"top\" || this._resolvedPlacement === \"bottom\") {\n const anchorCentreX = anchorRect.left + anchorRect.width / 2;\n const minLocalX = Math.max(\n ARROW_EDGE_INSET_PX,\n VIEWPORT_EDGE_INSET_PX - popoverX + ARROW_VISUAL_HALF - half,\n );\n const maxLocalX = Math.min(\n popoverW - size - ARROW_EDGE_INSET_PX,\n vp.width - VIEWPORT_EDGE_INSET_PX - popoverX - half - ARROW_VISUAL_HALF,\n );\n const localX = clamp(anchorCentreX - popoverX - half - border.left, minLocalX, maxLocalX);\n\n this._arrowComponent.setX(localX);\n\n // Pull the arrow half its own size outside the body so the\n // rotated diamond reads as a triangle straddling the edge.\n if (this._resolvedPlacement === \"bottom\") {\n this._arrowComponent.setY(-half - border.top);\n } else {\n this._arrowComponent.setY(popoverH - half - border.top);\n }\n } else {\n const anchorCentreY = anchorRect.top + anchorRect.height / 2;\n const minLocalY = Math.max(\n ARROW_EDGE_INSET_PX,\n VIEWPORT_EDGE_INSET_PX - popoverY + ARROW_VISUAL_HALF - half,\n );\n const maxLocalY = Math.min(\n popoverH - size - ARROW_EDGE_INSET_PX,\n vp.height - VIEWPORT_EDGE_INSET_PX - popoverY - half - ARROW_VISUAL_HALF,\n );\n const localY = clamp(anchorCentreY - popoverY - half - border.top, minLocalY, maxLocalY);\n\n this._arrowComponent.setY(localY);\n\n if (this._resolvedPlacement === \"right\") {\n this._arrowComponent.setX(-half - border.left);\n } else {\n this._arrowComponent.setX(popoverW - half - border.left);\n }\n }\n }\n\n /**\n * Registers `window` resize and `scroll` listeners on every scrollable\n * ancestor of the anchor so the popover follows the anchor while open.\n */\n private attachRepositionListeners(): void {\n Event.addViewportListener(this, \"resize\", this._onWindowResize);\n\n if (this._anchorElement) {\n this._scrollAncestors = this.collectScrollAncestors(this._anchorElement);\n\n for (const ancestor of this._scrollAncestors) {\n DOM.sink.addListener(ancestor, \"scroll\", this._onScroll, { passive: true });\n }\n }\n }\n\n /**\n * Detaches every reposition listener registered by {@link attachRepositionListeners}.\n */\n private detachRepositionListeners(): void {\n Event.removeViewportListener(this, \"resize\", this._onWindowResize);\n\n for (const ancestor of this._scrollAncestors) {\n DOM.sink.removeListener(ancestor, \"scroll\", this._onScroll);\n }\n\n this._scrollAncestors = [];\n }\n\n /**\n * Walks the anchor's ancestor chain up to `document.documentElement` and\n * collects every element whose computed `overflow` makes it scrollable.\n *\n * @param node - The starting element.\n * @returns The list of scrollable ancestors plus `document.documentElement`.\n */\n private collectScrollAncestors(node: Handle): Handle[] {\n const out: Handle[] = [];\n let cursor: Handle | null = DOM.source.getParentElement(node);\n\n while (cursor && cursor !== DOM.source.getDocumentElement()) {\n const style = DOM.source.getComputedOverflow(cursor);\n const overflow = style.overflow + style.overflowX + style.overflowY;\n\n if (/(auto|scroll|overlay)/.test(overflow)) {\n out.push(cursor);\n }\n\n cursor = DOM.source.getParentElement(cursor);\n }\n\n out.push(DOM.source.getDocumentElement());\n\n return out;\n }\n}\n\nconst PopoverCallable = callable(Popover);\ntype PopoverCallable = Popover;\nexport {\n Popover as _Popover,\n PopoverCallable as Popover\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link DialogBackdrop}.\n *\n * @category Components\n */\ninterface DialogBackdropOptions extends ComponentOptions {\n}\n\n/**\n * Empty subclass-default const so the super call follows the framework's\n * `(options, defaults)` shape uniformly.\n */\nconst _defaultDialogBackdropOptions: Partial<DialogBackdropOptions> = {};\n\n/**\n * A full-viewport fixed overlay that visually blocks content behind a modal dialog.\n *\n * Appended to `document.documentElement` as a sibling of the dialog panel at a lower\n * z-index, mirroring the pattern used by [`Notification`](/api/overlay/classes/Notification) and [`Menu`](/api/overlay/classes/Menu).\n */\nclass DialogBackdrop extends Component<DialogBackdropOptions> {\n\n /**\n * Creates the backdrop and applies viewport-filling fixed positioning.\n */\n constructor(options?: DialogBackdropOptions) {\n super(options, _defaultDialogBackdropOptions);\n\n const vp = DOM.source.getViewportSize();\n\n this.setPosition(Position.FIXED);\n this.setX(0);\n this.setY(0);\n this.setWidth(vp.width);\n this.setHeight(vp.height);\n this.setZIndex(10100);\n this.setBackgroundColor(\"var(--ts-ui-dialog-backdrop-bg)\");\n }\n\n /**\n * Registers a click listener on this backdrop element.\n *\n * @param listener - Called when the user clicks the backdrop area.\n */\n addClickListener(listener: Event.Listener): this {\n Event.addListener(this, \"click\", listener);\n\n return this;\n }\n\n /**\n * Updates backdrop dimensions to match the current viewport size.\n */\n resize(): void {\n const vp = DOM.source.getViewportSize();\n\n this.setWidth(vp.width);\n this.setHeight(vp.height);\n }\n\n /**\n * Removes the backdrop from the DOM and runs component cleanup.\n */\n destroy(): void {\n this.removeElement();\n this.destructor();\n }\n}\n\nconst DialogBackdropCallable = callable(DialogBackdrop);\ntype DialogBackdropCallable = DialogBackdrop;\nexport {\n DialogBackdrop as _DialogBackdrop,\n DialogBackdropCallable as DialogBackdrop\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Panel } from \"~/core/Panel.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { DialogBackdrop } from \"~/component/container/DialogBackdrop.js\";\nimport { Border as BorderLayout } from \"~/layout/Border.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { circle_check } from \"~/glyphs/solid/circle_check.js\";\nimport { circle_info } from \"~/glyphs/solid/circle_info.js\";\nimport { triangle_exclamation } from \"~/glyphs/solid/triangle_exclamation.js\";\nimport { circle_exclamation } from \"~/glyphs/solid/circle_exclamation.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\nGlyph.register(xmark, circle_check, circle_info, triangle_exclamation, circle_exclamation);\n\n/**\n * The result produced when a dialog is dismissed.\n *\n * @category Core\n */\nexport type DialogResult = 'confirm' | 'cancel' | 'close';\n\n/**\n * Configuration for a single button in a dialog's button row.\n *\n * @category Core\n */\nexport interface DialogButtonConfig {\n /** The label text displayed on the button. */\n text : string;\n /** The result value emitted when this button is clicked. Defaults to `'cancel'`. */\n result? : DialogResult;\n /** When true, renders the button with primary (confirm) styling. */\n primary?: boolean;\n /** Optional registry glyph name shown to the left of the button label. */\n glyph? : string;\n /**\n * Optional foreground colour applied to the button's leading glyph. Accepts\n * any CSS colour string — typically a theme variable reference such as\n * `'var(--ts-ui-dialog-confirm-color)'`. When omitted the glyph inherits\n * the button's `currentColor`. The {@link DialogButtons} presets supply\n * the appropriate tint by default; reach for this field at the call site\n * only when overriding a preset or building a one-off button.\n */\n tint? : string;\n}\n\n/**\n * Severity tone for a dialog's title bar, mirroring\n * [`NotificationType`](/api/core/type-aliases/NotificationType). When set it\n * tints the header and shows a matching leading glyph, taking precedence over\n * the tone otherwise derived from the buttons.\n *\n * @category Core\n */\nexport type DialogSeverity = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * Configuration object passed to `new Dialog(config)` or `Dialog.show(config)`.\n *\n * @category Core\n */\nexport interface DialogConfig {\n /** Text displayed in the dialog title bar. */\n title : string;\n /** Plain-text body message. Ignored when `contentComponent` is provided. */\n message? : string;\n /** A custom component rendered in the content area instead of a message label. */\n contentComponent?: Component;\n /**\n * Button definitions for the footer row.\n * Defaults to a single OK button that resolves with `'confirm'`.\n */\n buttons? : DialogButtonConfig[];\n /** Dialog panel width in pixels. Defaults to 480. */\n width? : number;\n /** Dialog panel height in pixels. When omitted the height is computed from content. */\n height? : number;\n /** When true, clicking the backdrop closes the dialog with result `'close'`. Defaults to false. */\n closeOnBackdrop? : boolean;\n /**\n * When `false`, the dialog is a mandatory modal: no title-bar close button,\n * Escape does not close it, and a backdrop click does not close it (regardless\n * of `closeOnBackdrop`). Defaults to `true`.\n */\n dismissable? : boolean;\n /**\n * Optional severity tone for the title bar (`'info'`, `'success'`,\n * `'warning'`, `'error'`). When set it tints the header and shows a matching\n * leading glyph, overriding the tone derived from the buttons. Omit for the\n * default button-derived chrome.\n */\n severity? : DialogSeverity;\n /**\n * Component to receive focus when the dialog opens, overriding the default\n * (the first focusable element in the content region). Use it when the field\n * that should take focus is not the first one — a form whose first control is\n * a read-only summary, say. Ignored when the component has no focusable\n * element on open, which falls back to the default order.\n */\n initialFocus? : Component;\n}\n\n// ---------------------------------------------------------------------------\n// Private constants\n// ---------------------------------------------------------------------------\n\nconst TITLE_HEIGHT : number = 36;\nconst BUTTON_HEIGHT : number = 52;\nconst BUTTON_WIDTH : number = 90;\nconst BUTTON_GAP : number = 8;\nconst BUTTON_V_PAD : number = 11;\nconst CLOSE_SIZE : number = 20;\nconst TITLE_H_PAD : number = 12;\nconst MIN_DIALOG_WIDTH : number = 320;\nconst MIN_DIALOG_HEIGHT: number = 160;\nconst MIN_CONTENT_HEIGHT: number = 80;\n// Minimum gap kept between a content-resized dialog and each viewport edge, so a\n// dialog grown to tall content never runs flush to the top/bottom of the screen.\nconst DIALOG_VIEWPORT_MARGIN: number = 24;\n\n// Leading title-bar glyph per severity tone, mirroring Notification's badges.\nconst SEVERITY_GLYPH: Record<DialogSeverity, string> = {\n info: \"circle-info\",\n success: \"circle-check\",\n warning: \"triangle-exclamation\",\n error: \"circle-exclamation\",\n};\n\n/**\n * Shared duration (ms) for the dialog entrance/exit transition. The backdrop\n * fade and the panel's opacity + scale all run for this many milliseconds.\n */\nconst DIALOG_ANIM_DURATION_MS: number = 150;\n\n/**\n * CSS selector matching the focusable elements inside a dialog — the Tab\n * focus-trap boundary set, the initial-focus candidates, and the primary-button\n * lookup all share it so the notion of \"focusable\" stays single-sourced.\n */\nconst FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Vertical breathing room reserved above and below the title text inside the\n * `TITLE_HEIGHT` row. 4 px on each side: keeps the bold label off the divider\n * border and gives a balanced visual cap height. Used both for the text's\n * line-height (via {@link Text.centerInHeight}) and for its laid-out height\n * inside the bar.\n */\nconst TITLE_V_PAD: number = 4;\n\n/**\n * Horizontal gap between the close button and the right edge of the title\n * bar, plus the gap between the title text and the close button. Two\n * separate offsets share the same value so the close-button column reads as\n * a balanced `[gap][icon][gap]` strip.\n */\nconst TITLE_RIGHT_GAP: number = 4;\n\n/**\n * Horizontal gap between the optional leading glyph and the title text.\n * Matches the 8 px gutter the WindowHeader uses between its title icon and\n * label so the two surfaces feel consistent.\n */\nconst TITLE_GLYPH_TEXT_GAP: number = 8;\n\n// ---------------------------------------------------------------------------\n// Private: DialogTitleBar\n// ---------------------------------------------------------------------------\n\n/**\n * Title bar that occupies the NORTH slot of a Dialog's border layout.\n * Contains a text label on the left, an optional leading glyph, and a glyph\n * close button on the right.\n *\n * @remarks\n * Reach this instance via [`Dialog.getTitleBar()`](/api/overlay/classes/Dialog#gettitlebar) — there is no public\n * constructor. The supported surface is `getTitleText()` (for tinting the\n * title text colour), `setGlyph()` / `getGlyph()` (for the optional leading\n * icon), and any inherited [`Component`](/api/core/classes/Component) setter\n * (e.g. `setBackgroundColor`).\n *\n * @category Core\n */\nclass DialogTitleBar extends Component {\n\n private readonly _titleText : Text;\n private _closeButton: Button | null = null;\n private _titleGlyph: Glyph | null = null;\n\n /**\n * @param title - Text to display in the title bar.\n * @param onClose - Called when the user clicks the close button.\n * @param dismissable - When `false`, the close button is not built at all\n * (mandatory-modal title bar).\n */\n constructor(title: string, onClose: () => void, dismissable: boolean) {\n super();\n\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.setBorder({\n border: \"none\",\n borderBottom: \"1px solid var(--ts-ui-dialog-border)\",\n });\n this.setPreferredSize({ width: 0, height: TITLE_HEIGHT });\n\n this._titleText = new Text(title);\n this._titleText.setFontWeight(\"bold\");\n this._titleText.setOverflow(\"hidden\");\n this._titleText.setTextOverflow(\"ellipsis\");\n this._titleText.setWhiteSpace(\"nowrap\");\n // Centre the label within the inner area of the row (TITLE_HEIGHT\n // minus the top + bottom TITLE_V_PAD breathing room).\n this._titleText.centerInHeight(TITLE_HEIGHT - TITLE_V_PAD * 2);\n this.addComponent(this._titleText);\n\n if (dismissable) {\n this._closeButton = new Button({ glyph: \"xmark\" });\n this._closeButton.setInsets(new Insets(0, 0, 0, 0));\n this._closeButton.setBorder(\"none\");\n this._closeButton.clearBackgroundImage();\n this._closeButton.setBackgroundColor(\"transparent\");\n this._closeButton.clearShadow();\n this._closeButton.clearPressedShadow();\n this._closeButton.clearHoverShadow();\n // Hover and pressed background swap out the framework's gray\n // var(--ts-ui-button-hover-bg, …) for a translucent overlay that\n // darkens whatever tinted header sits underneath without\n // imposing its own colour. Also drop the hover gradient so it\n // doesn't double up over the overlay.\n this._closeButton.setHoverBackgroundColor(\"var(--ts-ui-titlebar-btn-hover-bg, rgba(0, 0, 0, 0.08))\");\n this._closeButton.setPressedBackgroundColor(\"var(--ts-ui-titlebar-btn-active-bg, rgba(0, 0, 0, 0.16))\");\n this._closeButton.clearHoverBackgroundImage();\n this._closeButton.clearPressedBackgroundImage();\n this._closeButton.setPreferredSize({ width: CLOSE_SIZE, height: CLOSE_SIZE });\n this.addComponent(this._closeButton);\n\n this._closeButton.on(\"action\", onClose);\n }\n }\n\n /**\n * Returns the title bar's close button, or `null` when the title bar was\n * built non-dismissable (no close affordance).\n *\n * @returns The close [`Button`](/api/component/button/classes/Button), or `null`.\n */\n getCloseButton(): Button | null {\n return this._closeButton;\n }\n\n /**\n * Returns the title-text component, for callers that need to tint or\n * otherwise restyle it from outside the title bar.\n *\n * @returns The internal title [`Text`](/api/component/input/classes/Text) instance.\n */\n getTitleText(): Text {\n return this._titleText;\n }\n\n /**\n * Sets or clears an optional leading glyph shown to the left of the title text.\n *\n * @param name - Registry glyph name to display, or `null` to clear an existing glyph.\n *\n * @returns This component, for method chaining.\n *\n * @remarks\n * The current implementation positions the glyph in `doLayout`; only the\n * notification-detail path uses this slot, and the glyph never coexists with\n * other left-side decoration on a Dialog title bar.\n */\n setGlyph(name: string): this {\n if (this._titleGlyph) {\n this.removeComponent(this._titleGlyph);\n this._titleGlyph = null;\n }\n\n const glyph = new Glyph(name);\n glyph.setPointerEvents(\"none\");\n glyph.setPreferredSize({ width: 16, height: 16 });\n this._titleGlyph = glyph;\n this.addComponent(glyph);\n\n this.doLayout();\n\n return this;\n }\n\n /**\n * Removes the leading title-bar glyph from the dialog, if one is present.\n *\n * @returns This component, for method chaining.\n */\n clearGlyph(): this {\n if (this._titleGlyph) {\n this.removeComponent(this._titleGlyph);\n this._titleGlyph = null;\n this.doLayout();\n }\n\n return this;\n }\n\n /**\n * Returns the optional leading title-glyph component, or null if none is set.\n *\n * @returns The leading [`Glyph`](/api/component/display/classes/Glyph) instance, or null.\n */\n getGlyph(): Glyph | null {\n return this._titleGlyph;\n }\n\n /**\n * Positions the title label, optional leading glyph, and close button within\n * the title bar bounds.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const w = this.getWidth();\n const h = this.getHeight();\n const closeX = w - CLOSE_SIZE - TITLE_RIGHT_GAP;\n const centerY = Math.floor((h - CLOSE_SIZE) / 2);\n const rightBound = this._closeButton\n ? closeX // reserve the close-button slot\n : (w - TITLE_H_PAD); // no button: label runs to the right pad\n\n let labelX = TITLE_H_PAD;\n\n if (this._titleGlyph) {\n const glyphSize = this._titleGlyph.getPreferredSize() ?? { width: 16, height: 16 };\n const glyphY = Math.max(0, Math.floor((h - glyphSize.height) / 2));\n\n this._titleGlyph.setX(TITLE_H_PAD);\n this._titleGlyph.setY(glyphY);\n this._titleGlyph.setWidth(glyphSize.width);\n this._titleGlyph.setHeight(glyphSize.height);\n\n labelX = TITLE_H_PAD + glyphSize.width + TITLE_GLYPH_TEXT_GAP;\n }\n\n // Reserve TITLE_RIGHT_GAP of space between the label and the close button.\n const labelWidth = Math.max(0, rightBound - labelX - TITLE_RIGHT_GAP);\n const labelH = h - TITLE_V_PAD * 2;\n\n this._titleText.setX(labelX);\n this._titleText.setY(TITLE_V_PAD);\n this._titleText.setWidth(labelWidth);\n this._titleText.setHeight(labelH);\n\n if (this._closeButton) {\n this._closeButton.setX(closeX);\n this._closeButton.setY(centerY);\n this._closeButton.setWidth(CLOSE_SIZE);\n this._closeButton.setHeight(CLOSE_SIZE);\n // setX/setY/setWidth/setHeight don't cascade — explicitly relayout the\n // close button so its internal Fit layout sizes the times glyph.\n this._closeButton.doLayout();\n }\n\n return this;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private: DialogButtonRow\n// ---------------------------------------------------------------------------\n\n/**\n * Footer row that occupies the SOUTH slot of a Dialog's border layout.\n * Lays out one or more buttons, right-aligned.\n */\nclass DialogButtonRow extends Component {\n\n private readonly _buttons: Button[] = [];\n\n /**\n * @param configs - Button definitions to render.\n * @param onButton - Called with the resolved [`DialogResult`](/api/overlay/type-aliases/DialogResult) when any button is clicked.\n */\n constructor(configs: DialogButtonConfig[], onButton: (result: DialogResult) => void) {\n super();\n\n this.setBorder({\n border: \"none\",\n borderTop: \"1px solid var(--ts-ui-dialog-border)\",\n });\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.setPreferredSize({ width: 0, height: BUTTON_HEIGHT });\n\n for (const cfg of configs) {\n const btn = new Button(cfg.text, cfg.glyph !== undefined ? { glyph: cfg.glyph } : undefined);\n const result = cfg.result ?? 'cancel';\n\n if (cfg.primary) {\n btn.setBackgroundImage(\"var(--ts-ui-toggle-selected-bg, rgb(200, 200, 200))\");\n }\n\n if (cfg.glyph !== undefined) {\n const glyph = btn.getGlyph();\n const tint = cfg.tint;\n\n if (glyph !== null && tint) {\n glyph.setForegroundColor(tint);\n }\n }\n\n btn.on(\"action\", () => onButton(result));\n this._buttons.push(btn);\n this.addComponent(btn);\n }\n }\n\n /**\n * Positions buttons right-aligned within the footer row.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const w = this.getWidth();\n const h = this.getHeight();\n const btnH = h - BUTTON_V_PAD * 2;\n const totalW = this._buttons.length * BUTTON_WIDTH + (this._buttons.length - 1) * BUTTON_GAP;\n let x = Math.round((w - totalW) / 2);\n\n for (const btn of this._buttons) {\n btn.setX(x);\n btn.setY(BUTTON_V_PAD);\n btn.setWidth(BUTTON_WIDTH);\n btn.setHeight(btnH);\n\n btn.doLayout();\n\n x += BUTTON_WIDTH + BUTTON_GAP;\n }\n\n return this;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: Dialog\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical dialog button presets. Spread into the `buttons` array of a\n * {@link DialogConfig} to inherit the standard text / result / glyph mapping\n * for the three universal dismiss-row affordances; override `primary` per\n * call site since which button is default-focused is contextual (Cancel is\n * primary when paired with Confirm; Confirm is primary when it stands alone).\n *\n * Centralising the `glyph` here is the rule that prevents drift — the\n * checkmark/xmark mapping is bound to the button's identity, never re-typed\n * at the call site. {@link Dialog} also inspects the resolved result set to\n * pick the title-bar variant (only `confirm` → info header with leading\n * `circle-info`; `confirm` + `cancel` → affirm header).\n *\n * @category Core\n * @example\n * ```typescript\n * Dialog.show({\n * title: 'Delete record',\n * message: 'This cannot be undone.',\n * buttons: [\n * { ...DialogButtons.Cancel, primary: true },\n * DialogButtons.Confirm,\n * ],\n * });\n * ```\n */\nexport const DialogButtons = {\n /** Affirm an action — emits `'confirm'`, carries the green-tinted `circle-check` glyph. Paired with {@link Cancel} for action prompts. */\n Confirm: { text: 'Confirm', result: 'confirm', glyph: 'circle-check', tint: 'var(--ts-ui-dialog-confirm-color)' },\n /** Acknowledge information — emits `'confirm'`, carries the green-tinted `circle-check` glyph. Stands alone on informational dialogs. */\n Ok: { text: 'Ok', result: 'confirm', glyph: 'circle-check', tint: 'var(--ts-ui-dialog-confirm-color)' },\n /** Reject an action — emits `'cancel'`, carries the red-tinted `xmark` glyph. Paired with {@link Confirm}. */\n Cancel: { text: 'Cancel', result: 'cancel', glyph: 'xmark', tint: 'var(--ts-ui-dialog-cancel-color)' },\n /** Dismiss without choosing — emits `'close'`, carries the red-tinted `xmark` glyph (same icon family as cancel; the result value disambiguates). */\n Close: { text: 'Close', result: 'close', glyph: 'xmark', tint: 'var(--ts-ui-dialog-cancel-color)' },\n} as const satisfies Record<string, DialogButtonConfig>;\n\n/** Default button set when no buttons are supplied in config. */\nconst DEFAULT_BUTTONS: DialogButtonConfig[] = [\n { ...DialogButtons.Ok, primary: true },\n];\n\n/**\n * A modal dialog component with a title bar, scrollable content area, and button row.\n *\n * Use the static `Dialog.show(config)` convenience method for one-shot confirm/cancel\n * prompts, or construct an instance and call `show()` for fine-grained control.\n *\n * @example\n * ```typescript\n * const result = await Dialog.show({\n * title : 'Confirm deletion',\n * message: 'Are you sure you want to delete this record?',\n * buttons: [\n * { text: 'Delete', result: 'confirm', primary: true },\n * { text: 'Cancel', result: 'cancel' },\n * ],\n * });\n * if (result === 'confirm') { ... }\n * ```\n *\n * @category Core\n */\nclass Dialog extends Component implements DismissableLayer {\n\n private readonly _titleBar : DialogTitleBar;\n private readonly _contentContainer: Panel;\n private readonly _buttonRow : DialogButtonRow;\n private readonly _backdrop : DialogBackdrop;\n private readonly _config : DialogConfig;\n\n private _resolvePromise : ((result: DialogResult) => void) | null = null;\n private _previousFocus : Handle | null = null;\n private _boundKeyHandler : (e: KeyboardEvent) => Event.ListenerResult;\n private _boundResizeHandler: () => void;\n\n // In-flight entrance / dismiss animations for the panel and its backdrop,\n // cancelled on teardown so their fallback timers cannot fire against\n // released element handles.\n private _panelInAnimation : Animation.CancelHandle | null = null;\n private _backdropInAnimation : Animation.CancelHandle | null = null;\n private _panelOutAnimation : Animation.CancelHandle | null = null;\n private _backdropOutAnimation: Animation.CancelHandle | null = null;\n\n // True once `hide()`'s finalize has begun. `finalize` calls `destructor()`\n // partway through and finishes the rest afterwards, so the destructor uses\n // this to tell \"reached from a completing hide\" (leave the remainder to\n // finalize) from \"reached from a bare dispose\" (run it here, because the\n // cancelled dismiss animation will never call finalize at all).\n private _finalizing: boolean = false;\n\n /**\n * Constructs a Dialog but does not display it. Call `show()` to open.\n *\n * @param config - Dialog configuration.\n */\n constructor(config: DialogConfig) {\n super();\n\n this._config = config;\n\n const dialogWidth = Math.max(MIN_DIALOG_WIDTH, config.width ?? 480);\n const buttons = config.buttons ?? DEFAULT_BUTTONS;\n const contentHeight = Math.max(MIN_CONTENT_HEIGHT, this.computeContentHeight(config));\n const dialogHeight = Math.max(\n MIN_DIALOG_HEIGHT,\n config.height ?? (TITLE_HEIGHT + contentHeight + BUTTON_HEIGHT)\n );\n\n this.setPosition(Position.FIXED);\n this.setWidth(dialogWidth);\n this.setHeight(dialogHeight);\n // z-index is stamped from LayerManager's Dialog band at open() time\n // so stacked dialogs ascend monotonically; the backdrop is set one\n // below the panel there.\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n this.setShadow(\"var(--ts-ui-dialog-shadow)\");\n this.setOverflow(\"hidden\");\n // Fixed dimensions, hidden overflow, no escaping descendants — full strict containment.\n this.setContain(\"strict\");\n\n const layout = new BorderLayout();\n layout.setComponentSpacing(0);\n this.setLayoutManager(layout);\n\n this._titleBar = new DialogTitleBar(config.title, () => this.hide('close'), config.dismissable !== false);\n this.addComponent(this._titleBar, { placement: Placement.NORTH });\n\n // An explicit severity tone wins over the tone derived from the buttons,\n // so an error/warning dialog reads as such regardless of its footer.\n if (config.severity) {\n this.applySeverityHeader(config.severity);\n } else {\n this.applyHeaderVariant(this.computeHeaderVariant(buttons));\n }\n\n // A Panel (not a bare Component) so the content area is a real scroll\n // region: `autoScroll: \"y\"` opts it into the framework's overflow\n // handling, and — because Panel does not clamp itself to its content size\n // (`clampsToContentSize` is false) — a dialog capped shorter than its\n // content (see resizeToContent) shrinks this container to the available\n // space and scrolls, instead of a bare Component flooring to its content\n // height and clipping. Vertical only: a horizontal scrollbar would cover\n // the bottom rows of body text and is rarely useful for dialog content.\n // Zero insets so content sits flush, matching the former bare container.\n this._contentContainer = new Panel({\n autoScroll: \"y\",\n layoutManager: new Fit(),\n insets: new Insets(0, 0, 0, 0),\n });\n\n if (config.contentComponent) {\n this._contentContainer.addComponent(config.contentComponent);\n } else {\n const messageText = new Text(config.message ?? '');\n messageText.setWhiteSpace(\"normal\");\n messageText.setWordBreak(\"break-word\");\n messageText.setPadding(new Insets(16, 16, 16, 16));\n this._contentContainer.addComponent(messageText);\n }\n\n this.addComponent(this._contentContainer, { placement: Placement.CENTER });\n\n this._buttonRow = new DialogButtonRow(buttons, (result) => this.hide(result));\n this.addComponent(this._buttonRow, { placement: Placement.SOUTH });\n\n this._backdrop = new DialogBackdrop();\n\n this._boundKeyHandler = (e: KeyboardEvent) => this.onKeyDown(e);\n this._boundResizeHandler = () => this.onViewportResize();\n }\n\n /**\n * Picks the title-bar tint variant for this dialog by inspecting the set\n * of button results. The variant is derived from the buttons rather than\n * a config flag so callers cannot signal one intent via buttons and a\n * conflicting intent via a separate option.\n *\n * @param buttons - The resolved button configuration array.\n * @returns `'info'` for a single confirm-result button (informational\n * dialog), `'affirm'` when both confirm and cancel are present\n * (affirmative-action dialog), `'plain'` otherwise.\n */\n private computeHeaderVariant(buttons: DialogButtonConfig[]): 'info' | 'affirm' | 'plain' {\n const results = new Set(buttons.map(b => b.result ?? 'cancel'));\n\n if (results.size === 1 && results.has('confirm')) {\n return 'info';\n }\n\n if (results.has('confirm') && results.has('cancel')) {\n return 'affirm';\n }\n\n return 'plain';\n }\n\n /**\n * Applies the chosen header variant's background, title-text foreground,\n * and (for the info variant only) a leading `circle-info` glyph. The\n * close button inside the title bar is intentionally untouched — dismiss\n * affordances stay on `currentColor` per the modal-glyph-theming rule.\n *\n * @param variant - One of `'info'`, `'affirm'`, or `'plain'`.\n */\n private applyHeaderVariant(variant: 'info' | 'affirm' | 'plain'): void {\n if (variant === 'plain') {\n return;\n }\n\n const bgVar = variant === 'info' ? 'var(--ts-ui-dialog-info-bg)' : 'var(--ts-ui-dialog-affirm-bg)';\n const fgVar = variant === 'info' ? 'var(--ts-ui-dialog-info-fg)' : 'var(--ts-ui-dialog-affirm-fg)';\n\n this._titleBar.setBackgroundColor(bgVar);\n this._titleBar.getTitleText().setForegroundColor(fgVar);\n\n if (variant === 'info') {\n this._titleBar.setGlyph('circle-info');\n\n const headerGlyph = this._titleBar.getGlyph();\n\n if (headerGlyph !== null) {\n headerGlyph.setForegroundColor(fgVar);\n }\n }\n }\n\n /**\n * Tints the title bar for an explicit {@link DialogSeverity}, reusing the\n * notification severity palette and leading glyph (matching the\n * notification-detail dialog). The border colour doubles as the title-text\n * and glyph foreground, as it does for a notification.\n *\n * @param severity - The severity tone to apply.\n */\n private applySeverityHeader(severity: DialogSeverity): void {\n const bgVar = `var(--ts-ui-notification-${severity}-bg)`;\n const fgVar = `var(--ts-ui-notification-${severity}-border)`;\n\n this._titleBar.setBackgroundColor(bgVar);\n this._titleBar.getTitleText().setForegroundColor(fgVar);\n this._titleBar.setGlyph(SEVERITY_GLYPH[severity]);\n\n const headerGlyph = this._titleBar.getGlyph();\n\n if (headerGlyph !== null) {\n headerGlyph.setForegroundColor(fgVar);\n }\n }\n\n /**\n * Computes the default content area height based on config.\n *\n * @param config - Dialog configuration.\n * @returns Height in pixels for the content region.\n *\n * @remarks\n * Content whose height depends on its width — a wrapping {@link Text} only\n * knows its line count once laid out — reports a single-line height here,\n * before it has been sized. {@link resizeToContent}, scheduled after the\n * first layout in {@link open}, re-fits the dialog once the content has\n * settled at the dialog width.\n */\n private computeContentHeight(config: DialogConfig): number {\n if (config.contentComponent) {\n const ps = config.contentComponent.getPreferredSize();\n\n if (ps) {\n return ps.height;\n }\n }\n\n return 100;\n }\n\n /**\n * Displays the dialog, attaches event listeners, and returns a promise that\n * resolves when the dialog is dismissed.\n *\n * @returns A promise resolving to the [`DialogResult`](/api/overlay/type-aliases/DialogResult) of the closing action.\n */\n show(): Promise<DialogResult> {\n return new Promise((resolve) => {\n this._resolvePromise = resolve;\n this.open();\n });\n }\n\n /**\n * Re-fits the dialog's height to its content's current preferred size and\n * re-centres it. A dialog's height is otherwise fixed at construction, so a\n * form whose content grows or shrinks after `show()` (e.g. add/remove rows)\n * is stretched or clipped to the original box; call this after mutating the\n * content so the dialog tracks it.\n *\n * The new height is `TITLE_HEIGHT + content + BUTTON_HEIGHT`, floored at the\n * dialog minimum and capped so the panel keeps a margin from the top and\n * bottom viewport edges — past that cap the content area scrolls (its\n * container is already `overflow-y: auto`). No-op before `show()` (nothing to\n * re-centre) and when the height is unchanged. Width is untouched.\n *\n * @returns This dialog, for method chaining.\n */\n resizeToContent(): this {\n if (!this.getElement()) {\n return this;\n }\n\n const contentHeight = Math.max(MIN_CONTENT_HEIGHT, this.computeContentHeight(this._config));\n const target = Math.max(MIN_DIALOG_HEIGHT, TITLE_HEIGHT + contentHeight + BUTTON_HEIGHT);\n\n const vp = DOM.source.getViewportSize();\n const capped = Math.min(target, Math.max(MIN_DIALOG_HEIGHT, vp.height - DIALOG_VIEWPORT_MARGIN * 2));\n\n if (capped === this.getHeight()) {\n return this;\n }\n\n this.setHeight(capped);\n this.scheduleLayout();\n this.center();\n\n return this;\n }\n\n /**\n * Appends backdrop and dialog to the DOM, centers the panel, and captures focus.\n */\n private open(): void {\n this._previousFocus = DOM.source.getActiveElement();\n\n if (this._config.closeOnBackdrop && this._config.dismissable !== false) {\n this._backdrop.addClickListener(() => { this.hide('close'); });\n }\n\n // Join the central layer tree and stamp the panel from the Dialog\n // band; the backdrop sits one below the panel so stacked dialogs keep\n // each panel above its own backdrop.\n LayerManager.register(this);\n\n const panelZ = LayerManager.getZIndex(this);\n this.setZIndex(panelZ);\n this._backdrop.setZIndex(panelZ - 1);\n\n const backdropEl = this._backdrop.getElement(true)!;\n LayerManager.mount(backdropEl);\n\n const dialogEl = this.getElement(true)!;\n LayerManager.mount(dialogEl);\n\n // Trap wheels no inner scroller claimed so the content behind a modal\n // dialog stays inert, matching modality.\n trapWheel(this);\n\n this.scheduleLayout();\n this.center();\n this.animateIn();\n\n // The construction-time height assumed single-line content; once the\n // first layout has sized the content at the dialog width, content whose\n // height depends on width (wrapping Text) has settled, so re-fit to it.\n // A no-op for content whose height did not change (resizeToContent bails\n // when the height is unchanged), so only width-dependent content reflows.\n Component.afterNextLayout(() => this.resizeToContent());\n\n Event.addViewportListener(this, 'keydown', this._boundKeyHandler);\n Event.addViewportListener(this, 'resize', this._boundResizeHandler);\n\n // Deferred past the scheduled layout: focusing synchronously here does\n // land on the right element, but the first layout then wraps the content\n // in its frame and re-parents the subtree into it. Moving a focused\n // element out of the document blurs it — silently, with no blur event —\n // so the focus is undone a frame later and lands nowhere.\n Component.afterNextLayout(() => this.focusFirst());\n }\n\n /**\n * Fades the backdrop in and the dialog panel in from `opacity: 0` +\n * `scale(0.97)` to `opacity: 1` + `scale(1)` over 150ms. No-op when\n * `prefers-reduced-motion: reduce` is set.\n */\n private animateIn(): void {\n const el = this.getElement();\n const bdEl = this._backdrop.getElement();\n\n if (!el) {\n return;\n }\n\n this._panelInAnimation?.cancel();\n this._panelInAnimation = Animation.play(el, {\n from: { opacity: \"0\", transform: \"scale(0.97)\" },\n to: { opacity: \"1\", transform: \"scale(1)\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\", \"transform\"],\n });\n\n if (bdEl) {\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = Animation.play(bdEl, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n }\n\n /**\n * Centers the dialog panel within the viewport.\n */\n private center(): void {\n const vp = DOM.source.getViewportSize();\n const x = Math.max(0, Math.round((vp.width - this.getWidth()) / 2));\n const y = Math.max(0, Math.round((vp.height - this.getHeight()) / 2));\n\n this.setX(x);\n this.setY(y);\n }\n\n /**\n * Moves initial focus into the dialog. Prefers the configured\n * `initialFocus` component, then the first focusable element in the content\n * region, so a form field — not the title-bar close button, which is first in\n * DOM order — receives focus on open. Falls back to the primary action\n * button, then to the first focusable element anywhere in the dialog.\n *\n * @remarks Must run after the dialog's first layout, which re-parents the\n * content into its frame and would blur anything focused before it.\n */\n private focusFirst(): void {\n const requested = this.requestedFocusElement();\n\n if (requested) {\n DOM.sink.focus(requested);\n\n return;\n }\n\n const contentEl = this._contentContainer.getElement();\n const inContent = contentEl ? DOM.source.querySelectorAll(contentEl, FOCUSABLE_SELECTOR) : [];\n\n if (inContent.length > 0) {\n DOM.sink.focus(inContent[0]);\n\n return;\n }\n\n const primary = this.primaryButtonElement();\n\n if (primary) {\n DOM.sink.focus(primary);\n\n return;\n }\n\n const el = this.getElement();\n const focusable = el ? DOM.source.querySelectorAll(el, FOCUSABLE_SELECTOR) : [];\n\n if (focusable.length > 0) {\n DOM.sink.focus(focusable[0]);\n }\n }\n\n /**\n * Resolves the configured `initialFocus` component to the element that\n * should take focus: the component's own root element when it is itself\n * focusable (a `TextField` renders as the `<input>`), otherwise its first\n * focusable descendant (a `Panel` wrapping a field).\n *\n * @returns The element to focus, or `null` when no `initialFocus` is\n * configured or it offers nothing focusable — both of which fall through\n * to the default order.\n */\n private requestedFocusElement(): Handle | null {\n const component = this._config.initialFocus;\n const element = component?.getElement();\n\n if (!element) {\n return null;\n }\n\n if (DOM.source.matches(element, FOCUSABLE_SELECTOR)) {\n return element;\n }\n\n return DOM.source.querySelector(element, FOCUSABLE_SELECTOR);\n }\n\n /**\n * Returns the DOM element of the primary action button, used as the initial\n * focus target when the content region has nothing focusable. Resolves by\n * position: the index of the `primary` entry in the resolved button set maps\n * to the same-indexed focusable in the button row.\n *\n * @returns The primary button element, or `null` when none is primary or the\n * row is not yet rendered.\n */\n private primaryButtonElement(): Handle | null {\n const buttons = this._config.buttons ?? DEFAULT_BUTTONS;\n const index = buttons.findIndex(b => b.primary);\n\n if (index < 0) {\n return null;\n }\n\n const rowEl = this._buttonRow.getElement();\n const focusable = rowEl ? DOM.source.querySelectorAll(rowEl, FOCUSABLE_SELECTOR) : [];\n\n return focusable[index] ?? null;\n }\n\n /**\n * Collects all currently focusable elements inside the dialog.\n *\n * @returns An array of focusable elements in DOM order.\n */\n private getFocusable(): Handle[] {\n const el = this.getElement();\n\n if (!el) {\n return [];\n }\n\n return DOM.source.querySelectorAll(el, FOCUSABLE_SELECTOR)\n .filter(el => !DOM.source.hasAttribute(el, 'disabled'));\n }\n\n /**\n * Handles document-level keydown events for Escape and Tab focus trapping.\n *\n * @param e - The keyboard event.\n * @returns A stop-and-prevent disposition when the dialog handles the key (the Tab trap, or Enter); nothing otherwise, so unhandled keys keep propagating.\n */\n private onKeyDown(e: KeyboardEvent): Event.ListenerResult {\n // Escape is owned by LayerManager's keydown handler, which closes the\n // topmost non-manual layer (this dialog when it is on top). The dialog\n // keeps only the Tab focus-trap and the Enter-confirms-the-primary\n // shortcut here.\n if (e.key === 'Enter') {\n return this.onEnter(e);\n }\n\n if (e.key === 'Tab') {\n const focusable = this.getFocusable();\n\n if (focusable.length === 0) {\n return { stop: true, prevent: true };\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (DOM.source.getActiveElement() === first) {\n DOM.sink.focus(last);\n\n return { stop: true, prevent: true };\n }\n } else {\n if (DOM.source.getActiveElement() === last) {\n DOM.sink.focus(first);\n\n return { stop: true, prevent: true };\n }\n }\n }\n\n return;\n }\n\n /**\n * Confirms the dialog on Enter by resolving the primary button's result,\n * so a simple form submits like one without the caller wiring Enter itself.\n *\n * @remarks Deliberately inert when focus is on a `<textarea>` (Enter inserts\n * a newline) or on a `<button>` (the button activates itself on Enter, and\n * hijacking it would fire the wrong action). No-op when no button is marked\n * `primary`, so a dialog with no clear default action does not submit blind.\n *\n * @param _e - The keydown event for the Enter press.\n * @returns `{ stop: true, prevent: true }` when Enter confirms the dialog; nothing when there is nothing to confirm.\n */\n private onEnter(_e: KeyboardEvent): Event.ListenerResult {\n const active = DOM.source.getActiveElement();\n const tag = active ? DOM.source.getTagName(active).toLowerCase() : null;\n\n if (tag === 'textarea' || tag === 'button') {\n return;\n }\n\n const result = this.primaryResult();\n\n if (result === null) {\n return;\n }\n\n this.hide(result);\n\n return { stop: true, prevent: true };\n }\n\n /**\n * Returns the result of the button marked `primary` in this dialog's\n * resolved button set — the action Enter confirms — or `null` when none is\n * primary.\n *\n * @returns The primary button's [`DialogResult`](/api/overlay/type-aliases/DialogResult), or `null`.\n */\n private primaryResult(): DialogResult | null {\n const buttons = this._config.buttons ?? DEFAULT_BUTTONS;\n const primary = buttons.find(b => b.primary);\n\n return primary ? (primary.result ?? 'cancel') : null;\n }\n\n /**\n * Re-fits the dialog to the resized viewport: {@link Dialog.resizeToContent}\n * grows it back toward its content when the viewport gained room, or caps it\n * (so the content area scrolls) when the viewport shrank below the content —\n * keeping the dialog within the viewport instead of overflowing it. The\n * backdrop is resized to the new viewport and the panel re-centred (again\n * unconditionally here, since `resizeToContent` skips re-centring when the\n * height is unchanged).\n */\n private onViewportResize(): void {\n this._backdrop.resize();\n this.resizeToContent();\n this.center();\n }\n\n /**\n * Dismisses the dialog with a brief fade-and-scale animation, restores\n * focus, and resolves the promise.\n *\n * @param result - The result to resolve the promise with.\n *\n * @remarks Honours `prefers-reduced-motion: reduce` — the transition is\n * skipped when motion is reduced.\n */\n hide(result: DialogResult): this {\n Event.removeViewportListener(this, 'keydown', this._boundKeyHandler);\n Event.removeViewportListener(this, 'resize', this._boundResizeHandler);\n\n const finalize = (): void => {\n this._finalizing = true;\n\n this._backdrop.destroy();\n this.removeElement();\n this.destructor();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n if (this._previousFocus !== null) {\n DOM.sink.focus(this._previousFocus);\n }\n\n if (this._resolvePromise) {\n this._resolvePromise(result);\n this._resolvePromise = null;\n }\n };\n\n const el = this.getElement();\n const bdEl = this._backdrop.getElement();\n\n if (!el) {\n finalize();\n return this;\n }\n\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = Animation.play(el, {\n to: { opacity: \"0\", transform: \"scale(0.97)\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\", \"transform\"],\n onComplete: finalize,\n });\n\n if (bdEl) {\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = Animation.play(bdEl, {\n to: { opacity: \"0\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n\n return this;\n }\n\n /**\n * Cancels any in-flight panel / backdrop animation, then defers to the base\n * class. Reached from `hide()`'s completion callback as well as from a\n * direct dispose: on that path the dismiss animation is already finished, so\n * cancelling it is a no-op.\n */\n protected destructor(): void {\n this._panelInAnimation?.cancel();\n this._panelInAnimation = null;\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = null;\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = null;\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = null;\n\n // A dispose that lands mid-dismiss cancels the animation whose\n // completion callback owns the rest of teardown, so run that work here.\n // The backdrop is a private field rather than a registered child, so the\n // base class's recursion cannot reach it and it would otherwise stay\n // mounted over the whole app; the promise `show()` handed the caller\n // would never settle. Skipped when `finalize` is already running, which\n // reaches this method partway through and completes the rest itself —\n // including resolving with the caller's real result rather than the\n // `\"close\"` stand-in used here.\n if (!this._finalizing) {\n this._backdrop.destroy();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n if (this._resolvePromise) {\n this._resolvePromise('close');\n this._resolvePromise = null;\n }\n }\n\n super.destructor();\n }\n\n /**\n * Returns the content container component where custom content is rendered.\n *\n * @returns The content container [`Component`](/api/core/classes/Component).\n */\n getContentComponent(): Component {\n return this._contentContainer;\n }\n\n /**\n * The dialog sizes itself explicitly — its height is computed to fit its\n * content at construction and re-fit (capped to the viewport) in\n * {@link Dialog.resizeToContent} — so it must not additionally floor itself\n * to its content's min-size. If it did, a dialog whose content is taller\n * than the viewport could not shrink to the capped height, and its\n * `autoScroll` content container would never get the constrained space it\n * needs to scroll (it would clip instead). The `MIN_DIALOG_HEIGHT` floor is\n * applied explicitly wherever the height is set.\n */\n protected clampsToContentSize(): boolean {\n return false;\n }\n\n /**\n * Returns the dialog's title-bar component.\n *\n * @returns The internal title-bar instance, exposing `getTitleText()` and\n * `setGlyph()` for callers (e.g. the notification detail dialog)\n * that need to tint or decorate the header.\n *\n * @remarks\n * The `DialogTitleBar` class itself is not exported — callers reach it\n * only through this accessor and interact via its few public methods\n * (`getTitleText`, `setGlyph`, `getGlyph`).\n */\n getTitleBar(): DialogTitleBar {\n return this._titleBar;\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the dialog panel's root element for the central layer tree.\n *\n * @returns The dialog'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 dialog\n * is `\"modal\"`: the manager neither dismisses ancestors on an outside\n * interaction nor lets one fall through, and it owns the Escape-to-close\n * shortcut. The dialog keeps its own Tab focus-trap.\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return \"modal\";\n }\n\n /**\n * Advisory close request from the manager — closes the dialog with the\n * `'close'` result, matching the title-bar close affordance.\n *\n * @remarks No-op when `dismissable` is `false`: this is the Escape path\n * (`LayerManager` routes Escape to the topmost non-`\"manual\"` layer's\n * `requestClose()`), so a mandatory modal swallows Escape rather than\n * closing. `getDismissMode()` deliberately stays `\"modal\"` here — a\n * `\"manual\"` layer would be skipped by the Escape loop, letting Escape\n * fall through to close a layer beneath this one.\n */\n requestClose(): void {\n if (this._config.dismissable === false) {\n return;\n }\n\n this.hide('close');\n }\n\n /**\n * Returns the dialog's z-index band so unrelated dialogs stack above\n * every other overlay family.\n *\n * @returns The dialog band base.\n */\n getBand(): number {\n return LayerManager.Band.Dialog;\n }\n\n /**\n * Displays a modal dialog and returns a promise that resolves on dismissal.\n *\n * @param config - Dialog configuration.\n * @returns A promise resolving to the [`DialogResult`](/api/overlay/type-aliases/DialogResult) of the closing action.\n *\n * @example\n * ```typescript\n * const result = await Dialog.show({ title: 'Confirm', message: 'Proceed?' });\n * ```\n */\n static show(config: DialogConfig): Promise<DialogResult> {\n const dialog = new Dialog(config);\n\n return dialog.show();\n }\n\n /**\n * Displays a confirm/cancel dialog and resolves to `true` when the user confirms.\n *\n * Buttons are ordered Cancel (default focus) then Confirm, so pressing Enter or\n * Escape both safely default to cancellation.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n * @returns A promise resolving to `true` if the user clicked Confirm, `false` otherwise.\n *\n * @example\n * ```typescript\n * if (await Dialog.confirm('Delete record', 'This cannot be undone.')) {\n * store.remove(record);\n * }\n * ```\n */\n static async confirm(title: string, message: string): Promise<boolean> {\n const result = await Dialog.show({\n title,\n message,\n buttons: [\n { ...DialogButtons.Cancel, primary: true },\n DialogButtons.Confirm,\n ],\n });\n\n return result === 'confirm';\n }\n\n /**\n * Displays a severity-toned dialog with a single OK button and resolves once\n * the user acknowledges it. Shared by the {@link Dialog.info} /\n * {@link Dialog.success} / {@link Dialog.warning} / {@link Dialog.error}\n * shorthands.\n *\n * @param severity - The title-bar severity tone (see {@link DialogConfig.severity}).\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n */\n private static async alert(severity: DialogSeverity, title: string, message: string): Promise<void> {\n await Dialog.show({\n title,\n message,\n severity,\n buttons: [{ ...DialogButtons.Ok, primary: true }],\n });\n }\n\n /**\n * Displays an info-toned dialog with a single OK button and resolves once the\n * user acknowledges it.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.info('Import complete', 'Loaded 1,204 rows.');\n * ```\n */\n static info(title: string, message: string): Promise<void> {\n return Dialog.alert('info', title, message);\n }\n\n /**\n * Displays a success-toned dialog with a single OK button and resolves once\n * the user acknowledges it.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.success('Saved', 'Your changes have been stored.');\n * ```\n */\n static success(title: string, message: string): Promise<void> {\n return Dialog.alert('success', title, message);\n }\n\n /**\n * Displays a warning-toned dialog with a single OK button and resolves once\n * the user acknowledges it.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.warning('Unsaved changes', 'They will be lost if you continue.');\n * ```\n */\n static warning(title: string, message: string): Promise<void> {\n return Dialog.alert('warning', title, message);\n }\n\n /**\n * Displays an error-toned dialog with a single OK button and resolves once\n * the user acknowledges it. The title bar carries the error severity tint and\n * glyph (see {@link DialogConfig.severity}).\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.error('Connection failed', 'Host not allowed.');\n * ```\n */\n static error(title: string, message: string): Promise<void> {\n return Dialog.alert('error', title, message);\n }\n}\n\nconst DialogCallable = callable(Dialog);\ntype DialogCallable = Dialog;\nexport {\n Dialog as _Dialog,\n DialogCallable as Dialog,\n DialogTitleBar\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager } from \"~/core/LayerManager.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { _Dialog, DialogButtons } from \"~/overlay/Dialog.js\";\nimport { circle_info } from \"~/glyphs/solid/circle_info.js\";\nimport { circle_check } from \"~/glyphs/solid/circle_check.js\";\nimport { triangle_exclamation } from \"~/glyphs/solid/triangle_exclamation.js\";\nimport { circle_exclamation } from \"~/glyphs/solid/circle_exclamation.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\nGlyph.register(circle_info, circle_check, triangle_exclamation, circle_exclamation, xmark);\n\n/**\n * The visual severity of a notification.\n *\n * @category Core\n */\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * A single captured notification, retained in the in-session history returned\n * by {@link Notification.getHistory}.\n *\n * @category Core\n */\nexport interface NotificationRecord {\n /** The full (un-truncated) message text passed to {@link Notification.show}. */\n readonly message: string;\n /** The severity type the toast was shown with. */\n readonly type: NotificationType;\n /** Epoch milliseconds (`Date.now()`) when the toast was shown. */\n readonly timestamp: number;\n}\n\n/**\n * Maps a notification severity to its registry glyph name. Exported (module,\n * not barrel — so it stays out of the public API docs) so the notification\n * history menu reuses the same severity-icon mapping rather than duplicating it.\n */\nexport const BADGE_GLYPH: Record<NotificationType, string> = {\n info: \"circle-info\",\n success: \"circle-check\",\n warning: \"triangle-exclamation\",\n error: \"circle-exclamation\",\n};\n\nconst DETAIL_TITLE: Record<NotificationType, string> = {\n info: \"Information\",\n success: \"Success\",\n warning: \"Warning\",\n error: \"Error\",\n};\n\nconst MIN_RESUMED_MS: number = 8000;\nconst DISMISS_DURATION_MS: number = 200;\nconst ENTRANCE_DURATION_MS: number = 200;\n\n/**\n * A lightweight toast-style notification that appears in the bottom-right corner\n * of the viewport and auto-dismisses after a configurable duration.\n *\n * Multiple notifications stack upward. Each can also be dismissed manually via\n * the × button. Long messages are clipped to two lines with an ellipsis; a\n * double-click on the body opens a modal detail dialog containing the full\n * message.\n *\n * @example\n * ```typescript\n * Notification.show('Record saved.', 'success');\n * Notification.show('Connection lost.', 'error', 0); // persistent\n * ```\n *\n * @category Core\n */\nexport class Notification extends Component {\n\n // In-flight entrance / dismiss animations, cancelled on teardown so their\n // fallback timers cannot fire against this notification's released handle.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _dismissAnimation: Animation.CancelHandle | null = null;\n\n private static readonly WIDTH: number = 320;\n private static readonly HEIGHT: number = 64;\n private static readonly MARGIN: number = 16;\n private static readonly H_PADDING: number = 12;\n private static readonly V_PADDING: number = 10;\n private static readonly CLOSE_SIZE: number = 20;\n private static readonly BADGE_SIZE: number = 20;\n private static readonly BADGE_TEXT_GAP: number = 8;\n // Stacking z-index for toasts. Sits just above the managed dropdown band\n // (`LayerManager.Band.Dropdown` = 10000) so a toast floats over open pickers\n // and menus, yet below the Dialog band (11000) so the modal detail dialog a\n // toast can open covers it. A fixed literal rather than a `Band` allocation\n // because a `Notification` is not a registered layer — it never joins the\n // dismiss / stacking tree, so it has no node for the manager to stamp.\n private static readonly Z_INDEX: number = 10002;\n\n private static activeNotifications: Notification[] = [];\n\n // The most-recent notifications retained by the in-session history. A fixed\n // ring cap keeps memory trivial and the history menu scrollable-but-finite;\n // oldest entries are evicted first.\n private static readonly HISTORY_CAP: number = 50;\n private static history: NotificationRecord[] = [];\n\n // The auto-dismiss timer of every visible notification is paused while\n // either of these counters is positive. `hoverCount` tracks how many\n // notifications the pointer is currently over (so hovering one freezes\n // the whole stack until the pointer leaves the last one); `modalCount`\n // tracks outstanding calls to Notification.pauseAll() (a balanced pair\n // with resumeAll() that callers use to bracket their own modal flows).\n // When the combined paused state transitions back to false, every\n // notification's timer is restarted with at least `MIN_RESUMED_MS` of\n // remaining duration whenever a modal hold was the last released.\n private static hoverCount: number = 0;\n private static modalCount: number = 0;\n\n private readonly _type: NotificationType;\n private readonly _fullMessage: string;\n private readonly _badge: Glyph;\n private readonly _messageText: Text;\n private readonly _closeButton: Button;\n private _dismissTimer: ReturnType<typeof setTimeout> | null = null;\n private _remainingDuration: number = 0;\n private _timerStartedAt: number = 0;\n private _dismissing: boolean = false;\n\n // Named listener refs (removable, grep-able, named in stack traces) for the\n // close button's action, the body double-click, and the hover hold pair.\n private readonly _boundOnCloseAction: () => Event.ListenerResult = () => {\n this.dismiss();\n\n // Prevent the click from contributing to a double-click on the body.\n return true;\n };\n private readonly _boundOnDblClick: () => void = () => this.openDetail();\n private readonly _boundOnMouseOver: (e: MouseEvent) => void = (e) => Notification.acquireHoverHold(e, this.getElement());\n private readonly _boundOnMouseOut: (e: MouseEvent) => void = (e) => Notification.releaseHoverHold(e, this.getElement());\n\n /**\n * Private — use `Notification.show()` to create and display instances.\n *\n * @param message - The text to display inside the notification.\n * @param type - The severity type that controls the colour scheme.\n */\n private constructor(message: string, type: NotificationType) {\n super();\n\n this._type = type;\n this._fullMessage = message;\n\n this.setPosition(Position.FIXED);\n this.setZIndex(Notification.Z_INDEX);\n this.setWidth(Notification.WIDTH);\n this.setHeight(Notification.HEIGHT);\n this.setOverflow(\"hidden\");\n // Fixed size, fixed position, hidden overflow — full strict containment.\n this.setContain(\"strict\");\n\n // Live-region semantics so screen readers announce the toast when it is\n // inserted. Errors and warnings interrupt (`alert`/`assertive`);\n // informational and success toasts wait their turn (`status`/`polite`).\n const assertive = type === 'error' || type === 'warning';\n this.getAria().setRole(assertive ? \"alert\" : \"status\");\n this.getAria().setLive(assertive ? \"assertive\" : \"polite\");\n\n const bgVar = `var(--ts-ui-notification-${type}-bg)`;\n const borderVar = `var(--ts-ui-notification-${type}-border)`;\n const shadowVar = `var(--ts-ui-notification-shadow)`;\n\n this.setBackgroundColor(bgVar);\n this.setBorder({ border: `1px solid ${borderVar}` });\n this.setShadow(shadowVar);\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n this._badge = new Glyph(BADGE_GLYPH[type]);\n this._badge.setForegroundColor(borderVar);\n this._badge.setPreferredSize({ width: Notification.BADGE_SIZE, height: Notification.BADGE_SIZE });\n this._badge.setPointerEvents(\"none\");\n // Decorative severity icon — its meaning is already carried by the\n // message text, so keep it out of the announced live-region content.\n this._badge.getAria().setHidden(true);\n this.addComponent(this._badge);\n\n this._messageText = new Text(message);\n // Webkit-prefixed line clamp is now cross-browser (Chrome, Edge, Safari,\n // Firefox 68+). Clamped to two lines so a long message gets a trailing\n // ellipsis — the full text is reachable via double-click → detail dialog.\n this._messageText.setLineClamp(2);\n this._messageText.setWhiteSpace(\"normal\");\n this._messageText.setWordBreak(\"break-word\");\n this.addComponent(this._messageText);\n\n this._closeButton = new Button({ glyph: \"xmark\" });\n this._closeButton.setInsets(new Insets(0, 0, 0, 0));\n this._closeButton.setBorder(\"none\");\n this._closeButton.clearBackgroundImage();\n this._closeButton.setBackgroundColor(\"transparent\");\n this._closeButton.clearShadow();\n this._closeButton.clearPressedShadow();\n this._closeButton.setForegroundColor(\"var(--ts-ui-text-color, rgb(0, 0, 0))\");\n this._closeButton.setPreferredSize({ width: Notification.CLOSE_SIZE, height: Notification.CLOSE_SIZE });\n this._closeButton.getAria().setLabel(\"Dismiss notification\");\n this.addComponent(this._closeButton);\n\n // Route through the button's own `\"action\"` surface rather than reaching\n // into its DOM `click` via the Event API (a component must not listen to\n // another component's events through Event). The handler's returned `true`\n // stops propagation, so the dblclick-suppressing consume is preserved.\n this._closeButton.on(\"action\", this._boundOnCloseAction);\n\n // addSubtreeListener so double-clicks on the badge / text bubble up.\n Event.addSubtreeListener(this, \"dblclick\", this._boundOnDblClick);\n\n // Subtree mouseover / mouseout on the root. These bubble from every\n // descendant of the toast, so the handlers below filter out\n // intra-element movements via `relatedTarget`. mouseenter / mouseleave\n // look cleaner on paper but proved unreliable here in practice —\n // mouseleave didn't always fire on a root carrying a non-empty\n // `transition` CSS rule left over from the entrance animation.\n // Subtree listeners route through `Event`'s window-level base\n // listener, so `e.currentTarget` resolves to `window` (which has no\n // `.contains` method) — pass the toast root explicitly instead.\n Event.addSubtreeListener(this, \"mouseover\", this._boundOnMouseOver);\n Event.addSubtreeListener(this, \"mouseout\", this._boundOnMouseOut);\n }\n\n /**\n * Displays a notification toast in the bottom-right corner of the viewport.\n *\n * @param message - The text to display.\n * @param type - The severity type; controls background and border colour. Defaults to `'info'`.\n * @param duration - How long in milliseconds before the notification auto-dismisses.\n * Pass `0` for a persistent notification. Defaults to `3000`.\n */\n static show(message: string, type: NotificationType = 'info', duration: number = 3000): void {\n Notification.record(message, type);\n\n const n = new Notification(message, type);\n\n Notification.activeNotifications.push(n);\n\n const el = n.getElement(true)!;\n\n n.scheduleLayout();\n\n LayerManager.mount(el);\n\n Notification.restack();\n n.animateIn();\n\n if (duration > 0) {\n n.startTimer(duration);\n\n // If another toast is currently hovered, or a modal hold is open,\n // freeze the brand-new toast too so it doesn't dismiss out from\n // under the user while they're still reading the stack.\n if (Notification.isPaused()) {\n n.pauseTimer();\n }\n }\n }\n\n /**\n * Appends one entry to the in-session history, evicting the oldest once the\n * {@link HISTORY_CAP} ceiling is exceeded. Called by {@link show} for every\n * toast; browsing history via {@link showDetail} deliberately does not\n * record, so \"history = everything ever shown this session, one entry per\n * `show()`\".\n *\n * @param message - The full message text.\n * @param type - The severity type.\n */\n private static record(message: string, type: NotificationType): void {\n Notification.history.push({ message, type, timestamp: Date.now() });\n\n if (Notification.history.length > Notification.HISTORY_CAP) {\n Notification.history.shift();\n }\n }\n\n /**\n * Returns the in-session notification history, oldest first, capped at the\n * most recent 50 entries. The returned array is a defensive copy — mutating\n * it does not affect the retained history.\n *\n * @returns A copy of the history entries, oldest first.\n */\n static getHistory(): readonly NotificationRecord[] {\n return [...Notification.history];\n }\n\n /**\n * Slides the notification in from the right while fading from `opacity: 0`\n * to `opacity: 1` over 200ms. No-op when `prefers-reduced-motion: reduce`\n * is set — the toast snaps into place immediately.\n */\n private animateIn(): void {\n const el = this.getElement();\n\n if (!el) {\n return;\n }\n\n this._showAnimation?.cancel();\n this._showAnimation = Animation.play(el, {\n from: { transform: \"translateX(100%)\", opacity: \"0\" },\n to: { transform: \"translateX(0)\", opacity: \"1\" },\n durationMs: ENTRANCE_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n });\n }\n\n /**\n * Pauses the auto-dismiss timer of every currently visible notification.\n * Balanced with {@link Notification.resumeAll}; nested pause/resume pairs\n * compose, and the combined paused state is released only when every\n * acquired hold has been released.\n *\n * @remarks Intended for use by code that opens a modal flow during which\n * the user is unable to read or interact with active notifications.\n * Resumed timers are then clamped to a minimum of 8 seconds.\n */\n static pauseAll(): void {\n const wasPaused = Notification.isPaused();\n Notification.modalCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Releases one outstanding {@link Notification.pauseAll} hold. When the\n * last modal hold AND every hover hold is released, the stack's timers\n * are restarted with at least 8 seconds of remaining duration so the\n * user has time to read the toasts after the modal dismissal.\n */\n static resumeAll(): void {\n if (Notification.modalCount === 0) {\n return;\n }\n\n Notification.modalCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(true);\n }\n }\n\n /**\n * Increments the hover refcount when the supplied `mouseover` event\n * represents an actual entry into a notification (not a movement\n * between two of its children). The first hover on any notification\n * pauses the entire stack so the user can read without timers eating\n * notifications mid-glance.\n *\n * @param e - The native `mouseover` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static acquireHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n const wasPaused = Notification.isPaused();\n Notification.hoverCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Decrements the hover refcount when the supplied `mouseout` event\n * represents an actual exit (cursor moving outside the notification,\n * not just between two of its children). When the last hover hold AND\n * every outstanding modal hold has been released, every notification's\n * timer is restarted with whatever remaining duration was captured at\n * pause time (no minimum clamp on the hover-only path).\n *\n * @param e - The native `mouseout` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static releaseHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n if (Notification.hoverCount === 0) {\n return;\n }\n\n Notification.hoverCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(false);\n }\n }\n\n /**\n * Returns `true` when any hover or modal hold is currently active.\n */\n private static isPaused(): boolean {\n return Notification.hoverCount > 0 || Notification.modalCount > 0;\n }\n\n /**\n * Pauses every active notification's auto-dismiss timer.\n */\n private static pauseAllTimers(): void {\n for (const n of Notification.activeNotifications) {\n n.pauseTimer();\n }\n }\n\n /**\n * Restarts every active notification's auto-dismiss timer with its\n * captured remaining duration. When `clampMin` is true, the remaining\n * duration is bumped to at least {@link MIN_RESUMED_MS} so a modal\n * release leaves the user enough time to read the stack.\n */\n private static resumeAllTimers(clampMin: boolean): void {\n for (const n of Notification.activeNotifications) {\n n.restartTimer(clampMin);\n }\n }\n\n /**\n * Starts (or restarts) the auto-dismiss timer with the given duration and\n * records when it began so remaining time can be calculated on hover.\n *\n * @param ms - Milliseconds until the notification is dismissed.\n */\n private startTimer(ms: number): void {\n this._remainingDuration = ms;\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), ms);\n }\n\n /**\n * Pauses the auto-dismiss timer when the pointer enters the notification.\n * Stores how many milliseconds were left so `resumeTimer` can pick up where it left off.\n */\n private pauseTimer(): void {\n if (this._dismissTimer === null) {\n return;\n }\n\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n this._remainingDuration = Math.max(0, this._remainingDuration - (Date.now() - this._timerStartedAt));\n }\n\n /**\n * Restarts this notification's auto-dismiss timer with its captured\n * remaining duration. When `clampMin` is true, the remaining duration\n * is bumped to {@link MIN_RESUMED_MS} so the user gets enough time to\n * read what's left after a modal release.\n *\n * @param clampMin - Apply the modal-resume minimum clamp.\n */\n private restartTimer(clampMin: boolean): void {\n if (this._dismissing) {\n return;\n }\n\n if (this._remainingDuration <= 0 || this._dismissTimer !== null) {\n return;\n }\n\n if (clampMin) {\n this._remainingDuration = Math.max(this._remainingDuration, MIN_RESUMED_MS);\n }\n\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), this._remainingDuration);\n }\n\n /**\n * Opens the modal detail dialog for this toast's full (un-truncated) message.\n * Delegates to {@link showDetail}; a double-click on the toast body routes\n * here.\n */\n private openDetail(): void {\n Notification.showDetail(this._fullMessage, this._type);\n }\n\n /**\n * Opens a modal detail dialog showing the full message text — the same dialog\n * a live toast opens on double-click. Active notification timers are paused\n * while the dialog is open and clamped to a minimum of 8 seconds when the\n * dialog is dismissed. Does not itself record a history entry, so re-opening a\n * past notification from the history menu leaves the history unchanged.\n *\n * @param message - The full message text to display.\n * @param type - The severity type; controls the title and title-bar tint.\n */\n static showDetail(message: string, type: NotificationType): void {\n Notification.pauseAll();\n\n const content = new Text(message);\n content.setAutoMeasure(false);\n content.setWhiteSpace(\"pre-wrap\");\n content.setWordBreak(\"break-word\");\n content.setPadding(new Insets(16, 16, 16, 16));\n\n const dialog = new _Dialog({\n title: DETAIL_TITLE[type],\n contentComponent: content,\n buttons: [{ ...DialogButtons.Close, primary: true }],\n width: 420,\n height: 220,\n });\n\n // Tint the title bar to match the notification's severity colours.\n const titleBar = dialog.getTitleBar();\n titleBar.setBackgroundColor(`var(--ts-ui-notification-${type}-bg)`);\n titleBar.getTitleText().setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n titleBar.setGlyph(BADGE_GLYPH[type]);\n\n const titleGlyph = titleBar.getGlyph();\n\n if (titleGlyph !== null) {\n titleGlyph.setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n }\n\n dialog.show().then(() => Notification.resumeAll());\n }\n\n /**\n * Slides the notification rightward while fading it out, then removes the\n * element from the DOM and restacks the remaining notifications.\n */\n private dismiss(): void {\n if (this._dismissing) {\n return;\n }\n\n this._dismissing = true;\n\n if (this._dismissTimer !== null) {\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n }\n\n const el = this.getElement();\n\n if (!el) {\n this.finishDismiss();\n return;\n }\n\n this._dismissAnimation?.cancel();\n this._dismissAnimation = Animation.play(el, {\n to: { transform: \"translateX(100%)\", opacity: \"0\" },\n durationMs: DISMISS_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n onComplete: () => this.finishDismiss(),\n });\n }\n\n /**\n * Removes this notification from the active stack and from the DOM.\n */\n private finishDismiss(): void {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n\n this.removeElement();\n\n Notification.restack();\n }\n\n /**\n * Recalculates the Y position of every active notification so they\n * stack upward from the bottom-right corner without overlapping.\n */\n private static restack(): void {\n const vp = DOM.source.getViewportSize();\n const x = vp.width - Notification.WIDTH - Notification.MARGIN;\n\n let y = vp.height - Notification.MARGIN;\n\n for (let i = Notification.activeNotifications.length - 1; i >= 0; i--) {\n const n = Notification.activeNotifications[i];\n\n y -= Notification.HEIGHT;\n n.setX(x);\n n.setY(y);\n y -= Notification.MARGIN;\n }\n }\n\n /**\n * Positions the badge glyph, message label, and close button within the\n * notification body.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const closeX = Notification.WIDTH - Notification.CLOSE_SIZE - 4;\n const badgeX = Notification.H_PADDING;\n const badgeY = Notification.V_PADDING + 2;\n const msgX = badgeX + Notification.BADGE_SIZE + Notification.BADGE_TEXT_GAP;\n const msgWidth = closeX - msgX - 4;\n const msgHeight = Notification.HEIGHT - Notification.V_PADDING * 2;\n\n this._badge.setX(badgeX);\n this._badge.setY(badgeY);\n this._badge.setWidth(Notification.BADGE_SIZE);\n this._badge.setHeight(Notification.BADGE_SIZE);\n\n this._messageText.setX(msgX);\n this._messageText.setY(Notification.V_PADDING);\n this._messageText.setWidth(msgWidth);\n this._messageText.setHeight(msgHeight);\n\n this._closeButton.setX(closeX);\n this._closeButton.setY(4);\n this._closeButton.setWidth(Notification.CLOSE_SIZE);\n this._closeButton.setHeight(Notification.CLOSE_SIZE);\n // Cascade the size change down to the times-glyph through the\n // close button's internal Fit/HBox layout.\n this._closeButton.doLayout();\n\n return this;\n }\n\n /**\n * Cancels any in-flight entrance / dismiss animation, then defers to the\n * base class. Cancelling first keeps their fallback timers from firing\n * after `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._dismissAnimation?.cancel();\n this._dismissAnimation = null;\n\n // `finishDismiss` is the only place a notification leaves the static\n // active list, and cancelling above suppressed it. That list outlives\n // every teardown, and `restack` writes setX/setY to each entry — so a\n // disposed notification left in it is positioned through the element\n // handle released below. Re-stack afterwards so the survivors close the\n // gap, exactly as a completed dismiss would have left them.\n if (Notification.activeNotifications.includes(this)) {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n Notification.restack();\n }\n\n super.destructor();\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { MenuButton, MenuButtonOptions } from \"~/component/button/MenuButton.js\";\nimport { MenuItemConfig } from \"~/component/container/MenuItem.js\";\nimport { Notification, BADGE_GLYPH } from \"~/overlay/Notification.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { clock_rotate_left } from \"~/glyphs/solid/clock_rotate_left.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(clock_rotate_left);\n\nconst MINUTE_MS = 60_000;\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\n\n/**\n * Formats the age of a notification as a compact relative-time string\n * (`\"just now\"`, `\"5m ago\"`, `\"2h ago\"`, `\"3d ago\"`). A future timestamp\n * (clock skew) clamps to `\"just now\"`. Module-internal (not barrel-exported, so\n * it stays out of the public API) — exported only so the unit tests can pin the\n * bucket boundaries directly.\n *\n * @param timestampMs - When the notification was shown (epoch ms).\n * @param nowMs - The current time (epoch ms).\n * @returns The relative-time label.\n */\nexport function formatRelativeTime(timestampMs: number, nowMs: number): string {\n const delta = Math.max(0, nowMs - timestampMs);\n\n if (delta < MINUTE_MS) {\n return \"just now\";\n }\n\n if (delta < HOUR_MS) {\n return `${Math.floor(delta / MINUTE_MS)}m ago`;\n }\n\n if (delta < DAY_MS) {\n return `${Math.floor(delta / HOUR_MS)}h ago`;\n }\n\n return `${Math.floor(delta / DAY_MS)}d ago`;\n}\n\n/**\n * Builds the menu item configs from the current notification history in\n * chronological order (oldest first, latest at the bottom). Returns a single\n * disabled placeholder when the history is empty. Module-level (uses only\n * statics), passed through {@link NotificationHistoryButton}'s subclass\n * defaults bag so it is re-invoked on every open. Module-internal (not\n * barrel-exported, so it stays out of the public API) — exported only so the\n * unit tests can pin its pure mapping directly.\n *\n * @returns The menu item descriptors for the current history.\n */\nexport function buildHistoryItems(): MenuItemConfig[] {\n const history = Notification.getHistory();\n\n if (history.length === 0) {\n return [{ text: \"No notifications yet\", enabled: false }];\n }\n\n const now = Date.now();\n\n // History is stored oldest-first; keep that order so the latest entries\n // sit at the bottom (the menu opens scrolled there).\n return history.map(record => ({\n glyph: BADGE_GLYPH[record.type],\n // Tint the badge with the severity's border token — the same colour\n // the live toast's badge uses.\n glyphColor: `var(--ts-ui-notification-${record.type}-border)`,\n text: record.message,\n shortcut: formatRelativeTime(record.timestamp, now),\n action: () => Notification.showDetail(record.message, record.type),\n }));\n}\n\n/**\n * Construction-time options for {@link NotificationHistoryButton}. Inherits\n * every {@link MenuButtonOptions} field; a consumer-supplied `glyph` overrides\n * the default clock icon.\n *\n * @category Components\n */\nexport interface NotificationHistoryButtonOptions extends MenuButtonOptions {}\n\n/**\n * A trigger button that opens a menu of recent notifications. The menu lists the\n * in-session {@link Notification} history in chronological order (latest at the\n * bottom) and opens scrolled to the bottom, so the most recent entries are\n * visible first — each row showing the notification's severity badge, its\n * message, and how long ago it was shown. Activating a row re-opens that\n * notification's full message in the same modal detail dialog a live toast opens\n * on double-click; it does not re-show a toast, so browsing history has no effect\n * on the history itself.\n *\n * Place one wherever a persistent affordance for reviewing past notifications is\n * wanted, e.g. in a toolbar.\n *\n * @example\n * ```typescript\n * toolbar.addComponent(new NotificationHistoryButton());\n * ```\n *\n * @category Components\n */\nclass NotificationHistoryButton extends MenuButton<NotificationHistoryButtonOptions> {\n\n /**\n * Creates a NotificationHistoryButton seeded with the `clock-rotate-left`\n * glyph, the history provider, and scroll-to-bottom-on-show. A\n * consumer-supplied option in `options` still wins over these seeds.\n *\n * @param options - Optional button configuration.\n */\n constructor(options?: NotificationHistoryButtonOptions) {\n // Seeds live in the defaults bag so a caller's options still win.\n // History is chronological (latest at the bottom), so open scrolled to\n // the bottom; the provider re-runs per open so relative times stay current.\n super(undefined, options, {\n glyph: \"clock-rotate-left\",\n menuItems: buildHistoryItems,\n scrollToBottomOnShow: true,\n });\n\n this.getAria().setLabel(\"Notification history\");\n\n // MenuButton wires the bag only for a plain MenuButton; as a subclass we\n // wire our own so a consumer `listeners` option is not silently dropped.\n this.applyListeners(options?.listeners);\n }\n}\n\nconst NotificationHistoryButtonCallable = callable(NotificationHistoryButton);\ntype NotificationHistoryButtonCallable = NotificationHistoryButton;\nexport {\n NotificationHistoryButton as _NotificationHistoryButton,\n NotificationHistoryButtonCallable as NotificationHistoryButton,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { DialogBackdrop } from \"~/component/container/DialogBackdrop.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Viewport edge a {@link Drawer} anchors to and slides in from. Reuses the\n * framework's compass primitive\n * [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`, which is\n * meaningless for an edge-anchored panel — excluding it makes the illegal state\n * unrepresentable at compile time.\n *\n * @category Core\n */\nexport type DrawerEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Events emitted by a {@link Drawer}. `\"open\"` and `\"close\"` fire after the\n * corresponding transition is committed; `\"beforeclose\"` fires before a close\n * begins and can be vetoed via its {@link DrawerCloseController}.\n *\n * @category Core\n */\nexport type DrawerEvent = \"open\" | \"close\" | \"beforeclose\";\n\n/**\n * Controller handed to a `\"beforeclose\"` listener. Calling `preventDefault()`\n * aborts the in-progress close — letting a host veto dismissal (e.g. an\n * unsaved-changes guard).\n *\n * @category Core\n */\nexport interface DrawerCloseController {\n /** Aborts the close that is about to run. */\n preventDefault(): void;\n}\n\n/**\n * Construction-time options for {@link Drawer}.\n *\n * @category Core\n */\nexport interface DrawerOptions extends ComponentOptions {\n /**\n * Viewport edge the drawer rests against and slides in from.\n *\n * @defaultValue Placement.WEST\n */\n edge?: DrawerEdge;\n\n /**\n * When true, render a blocking scrim behind the panel and close on\n * scrim-click or Escape. When false, the surrounding UI stays interactive\n * and the drawer closes only via its public API.\n *\n * @defaultValue false\n */\n modal?: boolean;\n\n /**\n * Drawer extent along its slide axis, in pixels: width for left/right\n * edges, height for top/bottom edges.\n *\n * @defaultValue 320\n */\n size?: number;\n\n /**\n * Slide (and scrim-fade) duration in milliseconds.\n *\n * @defaultValue 220\n */\n durationMs?: number;\n\n /** Construction-time event listeners dispatched to {@link Drawer.on}. */\n listeners?: {\n open?: () => void;\n close?: () => void;\n beforeclose?: (controller: DrawerCloseController) => void;\n };\n}\n\n/**\n * Default drawer extent (px) along the slide axis. A component-level constant\n * rather than a theme token because it is a layout-affecting measurement, not a\n * colour — matching how `Dialog` keeps its `MIN_*` sizes and `Notification` its\n * `WIDTH` / `HEIGHT` out of `Theme.ts`. 320 is the conventional side-panel\n * width (navigation rails, filter panels) and stays comfortably under a narrow\n * viewport so the scrim/content behind it remains visible.\n */\nconst DEFAULT_DRAWER_SIZE_PX: number = 320;\n\n/**\n * Default slide / fade duration (ms). Tuned slightly longer than the dialog's\n * 150 ms because a drawer travels a full panel-width rather than a small\n * scale/opacity delta, so the same wall-clock feel needs more time.\n */\nconst DEFAULT_DRAWER_DURATION_MS: number = 220;\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The four\n * behavioural fields seed the options bag so {@link Drawer.getEdge} and friends\n * return a defined value before the caller (or a setter) writes one. `overflow`\n * is `auto` so a drawer scrolls content that exceeds its extent; the panel\n * surface tokens (`background`, `shadow`) are edge-agnostic and applied here,\n * while the directional divider border is applied per-edge in {@link Drawer.open}.\n */\nconst _defaultDrawerOptions: Partial<DrawerOptions> = {\n edge: Placement.WEST,\n modal: false,\n size: DEFAULT_DRAWER_SIZE_PX,\n durationMs: DEFAULT_DRAWER_DURATION_MS,\n overflow: \"auto\",\n backgroundColor: \"var(--ts-ui-drawer-bg)\",\n shadow: \"var(--ts-ui-drawer-shadow)\",\n};\n\n/**\n * An edge-anchored panel that rests off-screen against a viewport edge and\n * slides into view when opened, overlaying the rest of the UI.\n *\n * The drawer is a bare content host: callers add their own children via the\n * inherited `addComponent` and supply any header / dismiss chrome themselves.\n * It mounts on `document.documentElement` and registers with\n * [`LayerManager`](/api/core/classes/LayerManager) as a\n * [`DismissableLayer`](/api/core/interfaces/DismissableLayer), so Escape,\n * outside-click capture, and z-stacking behave like every other portaled\n * surface. A modal drawer additionally draws a\n * [`DialogBackdrop`](/api/component/container/classes/DialogBackdrop) scrim and\n * closes on scrim-click or Escape; a non-modal drawer leaves the surrounding UI\n * interactive and closes only through its public API.\n *\n * @example\n * ```typescript\n * import { Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n * import { VBox } from '@jimka/typescript-ui/layout';\n *\n * const drawer = Drawer({ edge: Placement.EAST, modal: true, layoutManager: VBox() });\n * drawer.addComponent(myFilterForm);\n * drawer.open();\n * ```\n *\n * @category Core\n */\nclass Drawer extends Component<DrawerOptions> implements DismissableLayer {\n\n // In-flight panel / backdrop animations, cancelled on teardown so their\n // fallback timers cannot fire against released element handles.\n private _panelInAnimation : Animation.CancelHandle | null = null;\n private _panelOutAnimation : Animation.CancelHandle | null = null;\n private _backdropInAnimation : Animation.CancelHandle | null = null;\n private _backdropOutAnimation: Animation.CancelHandle | null = null;\n\n /** Whether the drawer is currently open (or mid-entrance). */\n private _open: boolean = false;\n\n /**\n * Whether an exit slide is in flight. Guards `close()` against re-entry\n * while the panel is sliding out — `_open` only flips to false in the exit\n * transition's completion callback, so without this flag a second `close()`\n * (via `toggle()`, a repeated dismiss click, or Esc) would re-emit `\"close\"`\n * and queue a redundant exit animation.\n */\n private _closing: boolean = false;\n\n /** The modal scrim, created lazily on each modal open and torn down on close. */\n private _backdrop: DialogBackdrop | null = null;\n\n /** Typed-event fan-out for `\"open\"` / `\"close\"` / `\"beforeclose\"`. */\n private _listeners: ListenerBag<DrawerEvent> = new ListenerBag<DrawerEvent>();\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.onViewportResize();\n\n /** Stable scrim-click handler reference; closes the drawer. */\n private _boundBackdropClose: () => void = (): void => {\n this.close();\n };\n\n /**\n * Constructs a drawer but does not display it. Call `open()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: DrawerOptions, subclassDefaults?: Partial<DrawerOptions>) {\n super(options, { ..._defaultDrawerOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like every other portaled surface.\n this.setPosition(Position.FIXED);\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies a {@link DrawerOptions} bag, dispatching the drawer-specific\n * fields after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This drawer, for method chaining.\n */\n protected applyOptions(options: DrawerOptions): this {\n super.applyOptions(options);\n\n // All four carry a class default and seed construction-time state, so\n // always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setModal(options.modal ?? this.isModal());\n this.setDrawerSize(options.size ?? this.getDrawerSize());\n this.setDurationMs(options.durationMs ?? this.getDurationMs());\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/CSS applied in open()) -----\n\n /**\n * Sets the viewport edge the drawer anchors to. Cached only — the resting\n * geometry and directional border are derived in `open()`, where the\n * element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This drawer, for method chaining.\n */\n setEdge(edge: DrawerEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the drawer anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): DrawerEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Sets whether the drawer is modal. A modal drawer draws a blocking scrim\n * and closes on scrim-click / Escape; a non-modal one does neither.\n *\n * @param value - True for a modal drawer.\n *\n * @returns This drawer, for method chaining.\n *\n * @remarks Takes effect on the next `open()`; changing it while open does\n * not retroactively add or remove the scrim.\n */\n setModal(value: boolean): this {\n this._options.modal = value;\n\n return this;\n }\n\n /**\n * Returns whether the drawer is modal.\n *\n * @returns True when modal.\n */\n isModal(): boolean {\n return this._options.modal ?? this._defaultOptions.modal!;\n }\n\n /**\n * Sets the drawer's extent along its slide axis (width for left/right,\n * height for top/bottom). Named `setDrawerSize` rather than overriding the\n * inherited `Component.setSize(size: Size)` — the two-axis geometry setter —\n * because a drawer's extent is a single number along one axis.\n *\n * @param value - The extent in pixels.\n *\n * @returns This drawer, for method chaining.\n */\n setDrawerSize(value: number): this {\n this._options.size = value;\n\n return this;\n }\n\n /**\n * Returns the drawer's extent along its slide axis, in pixels.\n *\n * @returns The current extent.\n */\n getDrawerSize(): number {\n return this._options.size ?? this._defaultOptions.size!;\n }\n\n /**\n * Sets the slide / scrim-fade duration in milliseconds.\n *\n * @param ms - The duration in milliseconds.\n *\n * @returns This drawer, for method chaining.\n */\n setDurationMs(ms: number): this {\n this._options.durationMs = ms;\n\n return this;\n }\n\n /**\n * Returns the slide / scrim-fade duration in milliseconds.\n *\n * @returns The current duration.\n */\n getDurationMs(): number {\n return this._options.durationMs ?? this._defaultOptions.durationMs!;\n }\n\n // ----- open / close API -----\n\n /**\n * Slides the drawer into view from its anchored edge. Registers with the\n * layer tree, draws the scrim when modal, mounts on `documentElement`, and\n * animates in. No-op if already open.\n *\n * @returns This drawer, for method chaining.\n */\n open(): this {\n if (this._open) {\n return this;\n }\n\n LayerManager.register(this);\n\n const panelZ = LayerManager.getZIndex(this);\n this.setZIndex(panelZ);\n\n if (this.isModal()) {\n this.openBackdrop(panelZ - 1);\n }\n\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n DOM.sink.appendChild(DOM.source.getDocumentElement(), this.getElement(true)!);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the drawer panel.\n trapWheel(this);\n\n this.scheduleLayout();\n\n this.animateIn();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._open = true;\n this.emit(\"open\");\n\n return this;\n }\n\n /**\n * Slides the drawer back off-screen and tears it down. Fires the cancelable\n * `\"beforeclose\"` event first; if a listener vetoes via `preventDefault()`\n * the close is aborted. No-op if already closed.\n *\n * @returns This drawer, for method chaining.\n */\n close(): this {\n if (!this._open || this._closing) {\n return this;\n }\n\n let prevented = false;\n const controller: DrawerCloseController = {\n preventDefault: (): void => {\n prevented = true;\n },\n };\n\n this.emit(\"beforeclose\", controller);\n\n if (prevented) {\n return this;\n }\n\n this._closing = true;\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this.animateOutAndFinalize();\n\n return this;\n }\n\n /**\n * Opens the drawer if closed, or closes it if open.\n *\n * @returns This drawer, for method chaining.\n */\n toggle(): this {\n return this._open ? this.close() : this.open();\n }\n\n /**\n * Returns whether the drawer is currently open.\n *\n * @returns True when open (or mid-entrance).\n */\n isOpen(): boolean {\n return this._open;\n }\n\n // ----- internal: geometry & animation -----\n\n /**\n * Computes the panel's on-screen resting rect from the current edge, size,\n * and viewport. WEST/EAST span the full viewport height at the chosen\n * width; NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - size, y: 0, width: size, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: size };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - size, width: vp.width, height: size };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: size, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the panel via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the slide's\n * start (entrance) and end (exit) state, translated one full extent past\n * the anchored edge.\n *\n * @returns A `translateX`/`translateY` CSS value.\n */\n private offscreenTransform(): string {\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return `translateX(${size}px)`;\n\n case Placement.NORTH:\n return `translateY(${-size}px)`;\n\n case Placement.SOUTH:\n return `translateY(${size}px)`;\n\n case Placement.WEST:\n default:\n return `translateX(${-size}px)`;\n }\n }\n\n /**\n * Applies the 1px divider border on the panel's inner edge — the side that\n * faces the rest of the UI — leaving the other three sides borderless.\n */\n private applyEdgeBorder(): void {\n const divider = \"1px solid var(--ts-ui-drawer-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: \"none\", borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: \"none\", borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: \"none\", borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: \"none\", borderRight: divider });\n\n break;\n }\n }\n\n /**\n * Slides the panel in from its off-screen transform to its resting\n * position. Honours `prefers-reduced-motion` via {@link Animation.play}.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._panelInAnimation?.cancel();\n this._panelInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n });\n }\n\n /**\n * Slides the panel back off-screen, then finalizes teardown (detach the\n * element, destroy the scrim, leave the layer tree, emit `\"close\"`) in the\n * transition's completion callback. Fades the scrim out concurrently. Under\n * reduced motion {@link Animation.play} runs the completion synchronously.\n */\n private animateOutAndFinalize(): void {\n const element = this.getElement();\n\n const finalize = (): void => {\n this.removeElement();\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n this.emit(\"close\");\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n onComplete: finalize,\n });\n\n this.fadeBackdropOut();\n }\n\n /**\n * Creates the modal scrim, stamps it one z-index below the panel, wires\n * scrim-click-to-close, mounts it, and fades it in.\n *\n * @param zIndex - The z-index for the scrim (panel z minus one).\n */\n private openBackdrop(zIndex: number): void {\n this._backdrop = new DialogBackdrop();\n this._backdrop.setZIndex(zIndex);\n this._backdrop.addClickListener(this._boundBackdropClose);\n\n const backdropEl = this._backdrop.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), backdropEl);\n\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = Animation.play(backdropEl, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Fades the modal scrim out, if one is present. The element is destroyed by\n * {@link teardownBackdrop} once the panel's exit completes.\n */\n private fadeBackdropOut(): void {\n const backdropEl = this._backdrop?.getElement();\n\n if (!backdropEl) {\n return;\n }\n\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = Animation.play(backdropEl, {\n to: { opacity: \"0\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Removes the modal scrim from the DOM and drops the reference, if one is\n * present.\n */\n private teardownBackdrop(): void {\n if (this._backdrop !== null) {\n this._backdrop.destroy();\n this._backdrop = null;\n }\n }\n\n /**\n * Re-derives the panel rect and resizes the scrim when the viewport\n * changes, keeping a full-height/width drawer flush with the new edges.\n */\n private onViewportResize(): void {\n if (this._backdrop !== null) {\n this._backdrop.resize();\n }\n\n this.applyRestingGeometry();\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the drawer's events.\n *\n * @param event - `\"open\"` / `\"close\"` fire after the matching transition;\n * `\"beforeclose\"` fires before a close and can veto it via its controller.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This drawer, for method chaining.\n */\n on(event: \"open\" | \"close\", listener: () => void): this;\n on(event: \"beforeclose\", listener: (controller: DrawerCloseController) => void): this;\n on(event: DrawerEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This drawer, for method chaining.\n */\n off(event: DrawerEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event`, in registration order.\n *\n * @param event - The event to emit.\n * @param payload - Forwarded to each listener.\n */\n protected emit(event: \"open\" | \"close\"): void;\n protected emit(event: \"beforeclose\", controller: DrawerCloseController): void;\n protected emit(event: DrawerEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the drawer panel's root element for the layer tree.\n *\n * @returns The drawer's element, or null when not yet rendered.\n */\n getLayerElement(): Handle | null {\n return this.getElement() ?? null;\n }\n\n /**\n * Returns the dismiss mode the document-level handlers consult: `\"modal\"`\n * for a modal drawer (captures outside interaction, Escape closes it) and\n * `\"manual\"` for a non-modal one (never auto-dismissed — closing is the\n * caller's job).\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return this.isModal() ? \"modal\" : \"manual\";\n }\n\n /**\n * Advisory close request from the layer manager (Escape on a modal\n * drawer). Routes to the public `close()`.\n */\n requestClose(): void {\n this.close();\n }\n\n /**\n * Drawers are independent top-level peers, not layers opened from another,\n * so each registers as a tree root.\n *\n * @returns Always `true`.\n */\n isLayerRoot(): boolean {\n return true;\n }\n\n /**\n * Mirrors a manager-reallocated z-index onto the panel (and the scrim, one\n * below) when the drawer is re-stamped.\n *\n * @param zIndex - The fresh z-index assigned by the manager.\n */\n onZIndexChanged(zIndex: number): void {\n this.setZIndex(zIndex);\n\n if (this._backdrop !== null) {\n this._backdrop.setZIndex(zIndex - 1);\n }\n }\n\n /**\n * Cancels any in-flight panel / backdrop animation, then defers to the base\n * class. Cancelling first keeps their fallback timers from firing after\n * `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._panelInAnimation?.cancel();\n this._panelInAnimation = null;\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = null;\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = null;\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = null;\n\n // The close animation's completion callback is the only place these are\n // released, and cancelling above suppressed it: the backdrop is a\n // private field rather than a registered child, so the base class's\n // recursion cannot reach it and it would stay mounted over the app.\n // Each of these is idempotent, so a drawer disposed while already\n // closed pays nothing. The `\"close\"` emit is deliberately NOT re-homed\n // — disposal is not a close, and emitting into consumer code from a\n // destructor invites re-entrancy.\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n\n super.destructor();\n }\n}\n\nconst DrawerCallable = callable(Drawer);\ntype DrawerCallable = Drawer;\nexport {\n Drawer as _Drawer,\n DrawerCallable as Drawer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { DOM } from \"~/core/DOM.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Button, ButtonOptions } from \"~/component/button/Button.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link RailHandle}.\n *\n * @category Core\n */\nexport interface RailHandleOptions extends ButtonOptions {\n /**\n * Whether the handle renders in its selected (target-open) wash.\n *\n * @defaultValue false\n */\n selected?: boolean;\n}\n\n/**\n * A single launcher button on a [`Rail`](/api/overlay/classes/Rail). A chromeless\n * [`Button`](/api/component/button/classes/Button) subclass that carries a\n * `selected` state — driven by the rail to mirror whether the handle's drawer\n * is open or its window is restored — rendered as a hover wash\n * (`--ts-ui-rail-handle-hover-bg`) and a selected wash\n * (`--ts-ui-rail-handle-selected-bg`).\n *\n * `RailHandle` is internal to the rail subsystem; the rail creates and owns its\n * handles, so consumers rarely construct one directly. It is exported for typing\n * and subclassing.\n *\n * @category Core\n */\nclass RailHandle extends Button<RailHandleOptions> {\n\n // Lazy `.selected` rule — the selected (target-open) wash. The slot is a\n // fast-path cache for the wrapper `createStyleRule` dedupes by suffix; see\n // Button's `_pressedStyleRule` for the full explanation.\n private declare _selectedRule?: StyleRule;\n private get selectedRule(): StyleRule {\n return this._selectedRule ??= this.createStyleRule(\".selected\");\n }\n\n // Lazy hover rule. `:not(.selected)` keeps the brighter selected wash\n // winning while the pointer is over an already-open handle.\n private declare _railHoverRule?: StyleRule;\n private get railHoverRule(): StyleRule {\n return this._railHoverRule ??= this.createStyleRule(\":hover:not(.selected)\");\n }\n\n /**\n * Builds a chromeless launcher handle and queues its hover / selected washes.\n *\n * @param options - Construction-time options (label `text`, leading `glyph`,\n * initial `selected` state).\n */\n constructor(options: RailHandleOptions = {}) {\n super(options.text, options, { chromeless: true });\n\n this.railHoverRule.set(\"backgroundColor\", \"var(--ts-ui-rail-handle-hover-bg)\");\n this.selectedRule.set(\"backgroundColor\", \"var(--ts-ui-rail-handle-selected-bg)\");\n }\n\n /**\n * Applies a {@link RailHandleOptions} bag, dispatching the `selected` state\n * after inherited Button/Component fields.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This handle, for method chaining.\n */\n protected applyOptions(options: RailHandleOptions): this {\n super.applyOptions(options);\n\n if (options.selected !== undefined) {\n this.setSelected(options.selected);\n }\n\n return this;\n }\n\n /**\n * Returns whether the handle is currently selected.\n *\n * @returns True when selected.\n */\n isSelected(): boolean {\n return this._options.selected ?? false;\n }\n\n /**\n * Sets the selected state: toggles the `.selected` class (carrying the\n * selected wash) and mirrors the state onto `aria-pressed`.\n *\n * @param value - True to select the handle, false to deselect it.\n *\n * @returns This handle, for method chaining.\n */\n setSelected(value: boolean): this {\n this._options.selected = value;\n\n this.getAria().setPressed(value);\n\n const element = this.getElement();\n if (element) {\n DOM.sink.apply(element, { toggleClass: { selected: value } });\n }\n\n return this;\n }\n\n /**\n * Renders the handle element, applying the `.selected` class when selected.\n *\n * @returns The created element.\n */\n render() {\n const element = super.render();\n DOM.sink.apply(element, { toggleClass: { selected: this.isSelected() } });\n\n return element;\n }\n}\n\nconst RailHandleCallable = callable(RailHandle);\ntype RailHandleCallable = RailHandle;\nexport {\n RailHandle as _RailHandle,\n RailHandleCallable as RailHandle,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { isUnbounded } from \"~/primitive/Size.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { BoxLayout } from \"~/layout/BoxLayout.js\";\nimport { RailHandle } from \"~/overlay/RailHandle.js\";\nimport { CollapseButton, CollapseDirection } from \"~/component/container/CollapseButton.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport type { Drawer, DrawerEdge } from \"~/overlay/Drawer.js\";\nimport type { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport type { ClickListener } from \"~/component/button/Button.js\";\n\n/**\n * Viewport edge a {@link Rail} anchors to. Structurally identical to\n * [`DrawerEdge`](/api/overlay/type-aliases/DrawerEdge) — the framework's compass\n * primitive [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`,\n * which is meaningless for an edge-anchored strip.\n *\n * @category Core\n */\nexport type RailEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Text orientation for handle labels on the vertical sides (WEST/EAST). Ignored\n * for NORTH/SOUTH, where handle text is always horizontal. Mirrors the\n * [`Tab`](/api/layout/classes/Tab) layout's orientation vocabulary.\n *\n * - `\"horizontal\"` — handles stack vertically but text stays upright.\n * - `\"vertical-cw\"` — text rotated 90° clockwise, reading top-to-bottom\n * (`writing-mode: sideways-rl`).\n * - `\"vertical-ccw\"` — text rotated the other way, reading bottom-to-top\n * (`writing-mode: sideways-lr`).\n *\n * @remarks Implemented with CSS `writing-mode` rather than `transform: rotate`\n * so the browser reports the rotated box through `getBoundingClientRect`,\n * keeping the handle's preferred-size measurement correct.\n *\n * @category Core\n */\nexport type RailOrientation = \"horizontal\" | \"vertical-cw\" | \"vertical-ccw\";\n\n/**\n * Events emitted by a {@link Rail}. `\"register\"` fires when a drawer or window\n * is added to the rail; `\"unregister\"` when it is removed.\n *\n * @category Core\n */\nexport type RailEvent = \"register\" | \"unregister\";\n\n/**\n * Per-drawer registration options for {@link Rail.registerDrawer}.\n *\n * @category Core\n */\nexport interface RailDrawerRegistration {\n /** Handle glyph (forwarded to the handle's leading icon). */\n glyph?: string;\n\n /** Handle label text. */\n text?: string;\n\n /**\n * When true (default), the rail sets the drawer's edge to its own edge so\n * the drawer slides out from the rail. Pass false to leave the drawer's\n * edge untouched.\n *\n * @defaultValue true\n */\n alignEdge?: boolean;\n}\n\n/**\n * Construction-time options for {@link Rail}.\n *\n * @category Core\n */\nexport interface RailOptions extends ComponentOptions {\n /**\n * Viewport edge the rail anchors to.\n *\n * @defaultValue Placement.WEST\n */\n edge?: RailEdge;\n\n /**\n * Explicit rail thickness in pixels — width for WEST/EAST edges, height for\n * NORTH/SOUTH edges (the main axis always spans the full viewport). Omit to\n * size the rail to its handles: the cross-axis fits the widest (or, on a\n * horizontal rail, tallest) handle, re-derived as handles are added/removed\n * and when the orientation changes.\n *\n * @defaultValue content-derived\n */\n thickness?: number;\n\n /**\n * Handle-label text orientation on the vertical (WEST/EAST) sides. Ignored\n * for NORTH/SOUTH.\n *\n * @defaultValue \"horizontal\"\n */\n orientation?: RailOrientation;\n\n /**\n * Whether the rail starts collapsed — minimized to a thin gutter strip along\n * the edge (handles hidden) that a click on its chevron expands.\n *\n * @defaultValue false\n */\n collapsed?: boolean;\n\n /** Construction-time event listeners dispatched to {@link Rail.on}. */\n listeners?: {\n register?: (target: Drawer | AbstractWindow) => void;\n unregister?: (target: Drawer | AbstractWindow) => void;\n };\n}\n\n/**\n * Fallback rail thickness (px) along the cross axis, used only when no explicit\n * `thickness` is set and the content cannot be measured yet (e.g. an empty rail\n * with no handles). Once handles exist the rail sizes to them. A component-level\n * constant rather than a theme token because it is a layout-affecting\n * measurement, not a colour — matching how `Drawer` keeps its\n * `DEFAULT_DRAWER_SIZE_PX` out of `Theme.ts`. 48 px is the conventional icon-rail\n * width (a comfortable square touch target for a single glyph handle).\n */\nconst DEFAULT_RAIL_THICKNESS_PX: number = 48;\n\n/**\n * Fixed z-index for the rail, a plain module constant just below the window\n * band (`Z_BAND_WINDOW = 9000` in `LayerManager`) — mirroring how the layer\n * manager's bands are plain constants because z-index is unthemed. The rail is\n * a persistent strip that windows, popovers, and dialogs still stack above, and\n * it is deliberately not a `DismissableLayer`, so it carries this stamp itself\n * rather than drawing a band from the layer manager.\n */\nconst RAIL_Z_INDEX: number = 8900;\n\n/**\n * Slide duration (ms) for the rail's mount / unmount animation. Matches\n * Drawer's slide feel — long enough to read as motion, short enough not to\n * delay the launcher. Honoured under `prefers-reduced-motion` by\n * {@link Animation.play}, which then snaps to the end state.\n */\nconst RAIL_ANIM_DURATION_MS: number = 200;\n\n/**\n * Cross-axis thickness (px) of the collapsed rail — the thin strip the rail\n * minimizes into, skinned like a `Split` / `Border` collapsed gutter; a click on\n * the chevron expands the rail again.\n */\nconst RAIL_COLLAPSED_THICKNESS_PX: number = 10;\n\n/**\n * Half the collapse chevron's grip width (`CollapseButton`'s 10px grip), used to\n * inset the chevron's centre from the rail's inner edge so the whole grip sits\n * just inside the strip. The chevron is centred on its anchor by the\n * `CollapseButton` transform, so anchoring its centre half a grip-width in from\n * the edge leaves it flush inside — never overhanging the `overflow: hidden`\n * rail (which would clip it and let clicks fall through to whatever is behind).\n */\nconst RAIL_CHEVRON_HALF_PX: number = 5;\n\n/**\n * Maps a rail edge to the chevron's collapse heading — the direction the rail\n * travels (and the chevron points) when collapsing: toward the outer screen\n * edge it anchors to. The restore heading is the opposite, handled by the\n * chevron itself.\n *\n * @param edge - The rail's edge.\n *\n * @returns The collapse-heading {@link CollapseDirection}.\n */\nfunction collapseHeadingFor(edge: RailEdge): CollapseDirection {\n switch (edge) {\n case Placement.EAST: return \"east\";\n case Placement.NORTH: return \"north\";\n case Placement.SOUTH: return \"south\";\n case Placement.WEST:\n default: return \"west\";\n }\n}\n\n/** The chevron's restore heading is the opposite of its collapse heading. */\nconst OPPOSITE_HEADING: Record<CollapseDirection, CollapseDirection> = {\n west: \"east\",\n east: \"west\",\n north: \"south\",\n south: \"north\",\n};\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The two\n * behavioural fields seed the options bag so {@link Rail.getEdge} /\n * {@link Rail.getThickness} return a defined value before a setter writes one;\n * the surface tokens skin the strip.\n */\nconst _defaultRailOptions: Partial<RailOptions> = {\n edge: Placement.WEST,\n orientation: \"horizontal\",\n backgroundColor: \"var(--ts-ui-rail-bg)\",\n shadow: \"var(--ts-ui-rail-shadow)\",\n};\n\n/** Per-drawer bookkeeping: the handle and the exact listener references to remove. */\ninterface DrawerRegistration {\n handle: RailHandle;\n onOpen: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/** Per-window bookkeeping: the handle (null until minimized) and listener references. */\ninterface WindowRegistration {\n handle: RailHandle | null;\n onMinimize: () => void;\n onRestore: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/**\n * An edge-anchored launcher strip that floats over the app content along one\n * viewport edge, holding a column (WEST/EAST) or row (NORTH/SOUTH) of handle\n * buttons. Unlike a [`Drawer`](/api/overlay/classes/Drawer) it never slides\n * off-screen and is never auto-dismissed — it is the persistent counterpart to\n * the drawer.\n *\n * A rail hosts caller-created drawers (`registerDrawer`): each gets a handle\n * that toggles it, and the handle reflects the drawer's open/closed state by\n * subscribing through the drawer's public typed `on`. A window can also be told\n * to minimize *into* the rail (`AbstractWindow.setRail`): while minimized it is\n * represented by a rail handle that restores it on click.\n *\n * The rail mounts on `document.documentElement` as a `Position.FIXED` overlay\n * (the documented fixed carve-out) and carries a fixed z-index just below the\n * window band; it is deliberately *not* a layer-tree member.\n *\n * @example\n * ```typescript\n * import { Rail, Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n *\n * const rail = Rail({ edge: Placement.WEST }).mount();\n * rail.registerDrawer(Drawer(), { glyph: 'filter', text: 'Filters' });\n * ```\n *\n * @category Core\n */\nclass Rail extends Component<RailOptions> {\n\n // In-flight animations, cancelled on teardown so their fallback timers\n // cannot fire against this rail's released element handle.\n private _collapseAnimation: Animation.CancelHandle | null = null;\n private _slideOutAnimation: Animation.CancelHandle | null = null;\n private _slideInAnimation: Animation.CancelHandle | null = null;\n\n /** Typed-event fan-out for `\"register\"` / `\"unregister\"`. */\n private _listeners: ListenerBag<RailEvent> = new ListenerBag<RailEvent>();\n\n /** Registered drawers, keyed by drawer, holding the handle + listener refs. */\n private _drawers: Map<Drawer, DrawerRegistration> = new Map();\n\n /** Registered windows, keyed by window, holding the handle + listener refs. */\n private _windows: Map<AbstractWindow, WindowRegistration> = new Map();\n\n /** Whether the rail is currently mounted (attached to the document). */\n private _mounted: boolean = false;\n\n /** The collapse/restore chevron pinned to the rail's inner edge. */\n private _collapseButton: CollapseButton;\n\n /**\n * The content-fit thickness captured at collapse time. Hidden handles are\n * excluded from the preferred-size measurement, so the expand animation\n * tweens back to this remembered extent rather than a mis-measured one.\n */\n private _expandedThickness: number = DEFAULT_RAIL_THICKNESS_PX;\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.applyRestingGeometry();\n\n /**\n * Constructs a rail but does not display it. Call `mount()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: RailOptions, subclassDefaults?: Partial<RailOptions>) {\n super(options, { ..._defaultRailOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like Drawer and the other portaled\n // surfaces. The rail is not a DismissableLayer, so it stamps its own\n // fixed z-index rather than drawing one from the layer manager.\n this.setPosition(Position.FIXED);\n this.setZIndex(RAIL_Z_INDEX);\n\n // The collapse chevron sits at the rail's inner edge: double-clicking it\n // (matching the Split / Border gutter chevrons) collapses the rail to a\n // gutter, or restores it. Its heading points the way the rail travels on\n // collapse — toward the outer screen edge.\n this._collapseButton = new CollapseButton({\n direction: collapseHeadingFor(this.getEdge()),\n listeners: { collapse: (): void => { this.toggleCollapsed(); } },\n });\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n if (options?.listeners !== undefined) {\n this.applyListeners(options.listeners);\n }\n }\n\n /**\n * Applies a {@link RailOptions} bag, dispatching the rail-specific fields\n * after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This rail, for method chaining.\n */\n protected applyOptions(options: RailOptions): this {\n super.applyOptions(options);\n\n // edge and orientation carry a class default and seed construction-time\n // state, so always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setOrientation(options.orientation ?? this.getOrientation());\n\n if (options.thickness !== undefined) {\n this.setThickness(options.thickness);\n }\n\n if (options.collapsed !== undefined) {\n // Cache only during the cascade — the visual transition needs the\n // element + chevron + handles, which mount() applies once they exist.\n this._options.collapsed = options.collapsed;\n }\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/layout applied in mount()) -----\n\n /**\n * Sets the viewport edge the rail anchors to. Cached only — the resting\n * geometry, divider border, and handle-axis layout manager are derived in\n * `mount()`, where the element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This rail, for method chaining.\n */\n setEdge(edge: RailEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the rail anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): RailEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Pins the rail's cross-axis thickness (width for WEST/EAST, height for\n * NORTH/SOUTH) to an explicit pixel value, overriding the content-fit\n * default. Re-applies the resting geometry when mounted.\n *\n * @param px - The thickness in pixels.\n *\n * @returns This rail, for method chaining.\n */\n setThickness(px: number): this {\n this._options.thickness = px;\n this.adaptThickness();\n\n return this;\n }\n\n /**\n * Returns the rail's effective cross-axis thickness in pixels — the explicit\n * `thickness` when one was set, otherwise the content-fit measurement.\n *\n * @returns The current thickness.\n */\n getThickness(): number {\n if (this.isCollapsed()) {\n return RAIL_COLLAPSED_THICKNESS_PX;\n }\n\n if (this._options.thickness !== undefined) {\n return this._options.thickness;\n }\n\n return this.measureContentThickness();\n }\n\n /**\n * Measures the cross-axis extent the rail's handles need — the widest handle\n * for a vertical (WEST/EAST) rail, the tallest for a horizontal one — from\n * the layout manager's preferred size. Falls back to\n * {@link DEFAULT_RAIL_THICKNESS_PX} before the rail has a layout manager or\n * any handles (when the preferred cross-axis is unbounded or zero).\n *\n * @returns The content-fit thickness in pixels.\n */\n private measureContentThickness(): number {\n const preferred = this.getPreferredSize();\n if (!preferred) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n const cross = this.isVertical() ? preferred.width : preferred.height;\n if (cross <= 0 || isUnbounded(cross)) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n return Math.ceil(cross);\n }\n\n /**\n * Re-derives the content-fit thickness by re-applying the resting geometry.\n * No-op until mounted (mount applies the geometry itself); called whenever\n * the handle set or orientation changes so the rail tracks its content.\n */\n private adaptThickness(): void {\n if (this._mounted) {\n this.applyRestingGeometry();\n }\n }\n\n // ----- collapse / expand -----\n\n /**\n * Returns whether the rail is collapsed to its gutter strip.\n *\n * @returns True when collapsed.\n */\n isCollapsed(): boolean {\n return this._options.collapsed ?? false;\n }\n\n /**\n * Collapses the rail to a thin gutter strip (handles hidden) or restores it\n * to full size, animating the cross-axis between the two when mounted. No-op\n * if already in the requested state.\n *\n * @param value - True to collapse, false to expand.\n *\n * @returns This rail, for method chaining.\n */\n setCollapsed(value: boolean): this {\n if (value === this.isCollapsed()) {\n return this;\n }\n\n if (value) {\n // Capture the expanded extent first: hidden handles drop out of the\n // preferred-size measurement, so the expand tween reads this back\n // rather than re-measuring an empty rail.\n this._expandedThickness = this.getThickness();\n }\n\n this._options.collapsed = value;\n\n if (this._mounted) {\n this.animateCollapseTransition(value);\n }\n\n return this;\n }\n\n /**\n * Toggles the collapsed state.\n *\n * @returns This rail, for method chaining.\n */\n toggleCollapsed(): this {\n return this.setCollapsed(!this.isCollapsed());\n }\n\n /**\n * Flips the chevron heading, applies the collapsed/expanded skin, positions\n * the chevron, and (immediately, no animation) shows or hides the handles\n * for the current collapsed state. Used by `mount` to seed a rail\n * constructed `collapsed`.\n */\n private applyCollapseAppearance(): void {\n const collapsed = this.isCollapsed();\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n this.setAllHandlesDisplayed(!collapsed);\n }\n\n /**\n * Skins the strip for the collapsed or expanded state, mirroring how a\n * [`Split`](/api/layout/classes/Split) / [`Border`](/api/layout/classes/Border)\n * gutter paints its collapsed strip (`SplitGutter.setOpaque`): collapsed, the\n * rail reads as a themed button surface (the same fill, gradient, and border\n * the framework's buttons use) that invites a click to restore; expanded, it\n * returns to the rail background and its single inner-edge divider.\n *\n * @param collapsed - True for the collapsed strip skin, false for the\n * expanded rail skin.\n */\n private applyCollapseStyling(collapsed: boolean): void {\n if (collapsed) {\n this.setBackgroundColor(\"var(--ts-ui-button-bg, #e8e8e8)\");\n this.setBackgroundImage(\"var(--ts-ui-button-bg, linear-gradient(rgb(241, 241, 241), rgb(200, 200, 200)))\");\n this.setBorder(\"1px solid var(--ts-ui-button-border, #c8c8c8)\");\n } else {\n this.clearBackgroundImage();\n this.setBackgroundColor(\"var(--ts-ui-rail-bg)\");\n this.applyEdgeBorder();\n }\n }\n\n /**\n * Places and sizes the collapse chevron for the current state, writing\n * `left` / `top` / `width` onto the chevron's own `#id` style rule (shared by\n * selector with the rule {@link CollapseButton} uses for its rotation, so the\n * two coexist) and overriding the shared `.CollapseButton` class rule.\n *\n * - **Collapsed:** centred in the strip and sized to fill its full thickness,\n * so the restore handle reads as the Split collapsed-gutter handle.\n * - **Expanded:** pinned just inside the rail's inner (content-facing) edge —\n * the side opposite the viewport edge it anchors to (right for WEST, left\n * for EAST, bottom for NORTH, top for SOUTH) — at the narrow grip width,\n * its centre inset by {@link RAIL_CHEVRON_HALF_PX} so the whole grip sits\n * flush inside the strip rather than overhanging it.\n *\n * In both states the chevron is raised above the handles (`z-index`) so a\n * click always lands on it, never on a handle laid out beneath it; and it is\n * kept fully inside the `overflow: hidden` rail so a click never falls through\n * to whatever sits behind an overhang. Percentages (not pixels) for the\n * placement so the chevron tracks the rail's size and position through the\n * collapse tween and viewport resizes. `width` maps to the across-gutter axis\n * for every edge because {@link CollapseButton}'s rotation orients its box.\n *\n * @param collapsed - True for the collapsed (centred, strip-filling) chevron,\n * false for the expanded (inner-edge, grip) chevron.\n */\n private positionChevron(collapsed: boolean): void {\n let left: string;\n let top: string;\n\n if (collapsed) {\n // Centred in the strip — the CollapseButton class-rule default.\n left = \"50%\";\n top = \"50%\";\n } else {\n // Flush just inside the inner edge: anchor the chevron's centre half\n // a grip-width in from the edge (see RAIL_CHEVRON_HALF_PX).\n const inset = `calc(100% - ${RAIL_CHEVRON_HALF_PX}px)`;\n const near = `${RAIL_CHEVRON_HALF_PX}px`;\n\n switch (this.getEdge()) {\n case Placement.EAST:\n left = near;\n top = \"50%\";\n\n break;\n\n case Placement.NORTH:\n left = \"50%\";\n top = inset;\n\n break;\n\n case Placement.SOUTH:\n left = \"50%\";\n top = near;\n\n break;\n\n case Placement.WEST:\n default:\n left = inset;\n top = \"50%\";\n\n break;\n }\n }\n\n // Fill the strip thickness when collapsed; clear to the CollapseButton\n // grip width (its class-rule default) when expanded.\n const width = collapsed ? RAIL_COLLAPSED_THICKNESS_PX + \"px\" : null;\n\n new StyleRule({\n scope: \"component\",\n name: this._collapseButton.getId(),\n styles: { left, top, width, zIndex: \"1\" },\n });\n }\n\n /**\n * Animates the cross-axis between the full and collapsed extents. The chevron\n * heading, skin, and placement flip up front.\n *\n * The handles stay shown and laid out at the full extent across the whole\n * tween in both directions; because the rail clips its overflow, the moving\n * edge reveals them as the strip widens (expand) and clips them away as it\n * narrows (collapse), so they follow the animation symmetrically rather than\n * popping in or out. On collapse they are hidden only once the strip has\n * closed.\n *\n * @param collapsed - True when collapsing, false when expanding.\n */\n private animateCollapseTransition(collapsed: boolean): void {\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n\n const element = this.getElement();\n\n // The visible extent the tween starts from — the live cross-axis size\n // (full when collapsing, the collapsed strip when expanding). Captured\n // before the expand path lays out the full geometry below.\n const fromThickness = this.isVertical() ? this.getWidth() : this.getHeight();\n const toThickness = collapsed ? RAIL_COLLAPSED_THICKNESS_PX : this._expandedThickness;\n\n if (!collapsed) {\n // Reveal and lay the handles out at the full extent up front; the\n // tween's `from` immediately shrinks the visible strip back to the\n // collapsed width, so they wipe into view as it grows. (Collapse\n // keeps the already-laid-out handles in place so the narrowing strip\n // wipes them out; they are hidden in `finalize`.)\n this.setAllHandlesDisplayed(true);\n this.applyRestingGeometry();\n this.scheduleLayout();\n }\n\n const finalize = (): void => {\n // Now that the strip has closed, drop the handles out of the layout.\n if (collapsed) {\n this.setAllHandlesDisplayed(false);\n }\n\n this.applyRestingGeometry();\n\n if (!collapsed) {\n this.scheduleLayout();\n }\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n const tween = this.collapseTween(fromThickness, toThickness);\n\n this._collapseAnimation?.cancel();\n this._collapseAnimation = Animation.play(element, {\n from: tween.from,\n to: tween.to,\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: tween.properties,\n onComplete: finalize,\n });\n }\n\n /**\n * Builds the from/to inline styles for the collapse/expand tween: the\n * cross-axis dimension (and, for EAST/SOUTH rails whose anchored corner\n * moves, the matching `left`/`top`) between two explicit thicknesses.\n *\n * @param fromThickness - The cross-axis extent the tween starts from.\n * @param toThickness - The cross-axis extent the tween ends at.\n *\n * @returns The `from` / `to` style partials and the animated property names.\n */\n private collapseTween(fromThickness: number, toThickness: number): {\n from: Partial<CSSStyleDeclaration>;\n to: Partial<CSSStyleDeclaration>;\n properties: string[];\n } {\n const vp = DOM.source.getViewportSize();\n const from: Partial<CSSStyleDeclaration> = {};\n const to: Partial<CSSStyleDeclaration> = {};\n\n if (this.isVertical()) {\n from.width = fromThickness + \"px\";\n to.width = toThickness + \"px\";\n\n if (this.getEdge() === Placement.EAST) {\n from.left = (vp.width - fromThickness) + \"px\";\n to.left = (vp.width - toThickness) + \"px\";\n }\n } else {\n from.height = fromThickness + \"px\";\n to.height = toThickness + \"px\";\n\n if (this.getEdge() === Placement.SOUTH) {\n from.top = (vp.height - fromThickness) + \"px\";\n to.top = (vp.height - toThickness) + \"px\";\n }\n }\n\n return { from, to, properties: Object.keys(to) };\n }\n\n /**\n * Shows or hides every handle (drawer and window).\n *\n * @param displayed - True to show the handles, false to hide them.\n */\n private setAllHandlesDisplayed(displayed: boolean): void {\n for (const reg of this._drawers.values()) {\n reg.handle.setDisplayed(displayed);\n }\n\n for (const reg of this._windows.values()) {\n reg.handle?.setDisplayed(displayed);\n }\n }\n\n /**\n * Sets the handle-label text orientation for the vertical (WEST/EAST) sides\n * and re-applies the writing mode to every existing handle. Ignored visually\n * on NORTH/SOUTH, where handle text is always horizontal.\n *\n * @param orientation - The {@link RailOrientation} to apply.\n *\n * @returns This rail, for method chaining.\n */\n setOrientation(orientation: RailOrientation): this {\n this._options.orientation = orientation;\n\n // `_drawers` / `_windows` are class-field Maps initialised only after\n // super() returns, but `applyOptions` dispatches this setter during the\n // super() cascade. Skip the re-apply then — no handles exist yet, and\n // each one picks up the orientation as it is created (see\n // registerDrawer / showWindowHandle).\n if (this._drawers !== undefined) {\n this.applyOrientation();\n }\n\n return this;\n }\n\n /**\n * Returns the current handle-label text orientation.\n *\n * @returns The current orientation.\n */\n getOrientation(): RailOrientation {\n return this._options.orientation ?? this._defaultOptions.orientation!;\n }\n\n // ----- mount / unmount -----\n\n /**\n * Mounts the rail on `document.documentElement`: installs the handle-axis\n * layout manager, applies the divider border and resting geometry, attaches\n * the element, and tracks viewport resizes. No-op if already mounted.\n *\n * @returns This rail, for method chaining.\n */\n mount(): this {\n if (this._mounted) {\n return this;\n }\n\n this.setLayoutManager(this.isVertical() ? new VBox() : new HBox());\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n const element = this.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), element);\n\n // The collapse chevron is a raw child (self-centred via its own class\n // rule), outside the handle layout, so it doesn't count toward the\n // content-fit thickness. Append once; a remount reuses the element.\n const chevron = this._collapseButton.getElement(true);\n if (chevron && DOM.source.getParentElement(chevron) !== element) {\n DOM.sink.appendChild(element, chevron);\n }\n\n // Seed the chevron heading, skin, placement, and handle visibility for\n // the initial (possibly collapsed) state.\n this.applyCollapseAppearance();\n\n this.scheduleLayout();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = true;\n this.animateIn();\n\n return this;\n }\n\n /**\n * Unmounts the rail: stops tracking viewport resizes and detaches the\n * element. Registered drawers and windows keep their subscriptions, so a\n * later `mount()` restores a working strip.\n *\n * @returns This rail, for method chaining.\n */\n unmount(): this {\n if (!this._mounted) {\n return this;\n }\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = false;\n\n // Slide the strip back off its edge, then detach. Under reduced motion\n // Animation.play runs the completion synchronously.\n const element = this.getElement();\n const detach = (): void => { this.removeElement(); };\n\n if (!element) {\n detach();\n\n return this;\n }\n\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n onComplete: detach,\n });\n\n return this;\n }\n\n /**\n * Slides the strip in from off its anchored edge to its resting position.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._slideInAnimation?.cancel();\n this._slideInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n });\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the strip\n * translated one full thickness past the edge it anchors to, the start\n * (mount) and end (unmount) state of the slide.\n *\n * @returns A `translateX` / `translateY` CSS value.\n */\n private offscreenTransform(): string {\n switch (this.getEdge()) {\n case Placement.EAST:\n return \"translateX(100%)\";\n\n case Placement.NORTH:\n return \"translateY(-100%)\";\n\n case Placement.SOUTH:\n return \"translateY(100%)\";\n\n case Placement.WEST:\n default:\n return \"translateX(-100%)\";\n }\n }\n\n // ----- drawer composition -----\n\n /**\n * Registers a drawer: adds a handle that toggles it, mirrors the drawer's\n * open/closed state onto the handle via the drawer's public `on`, and (by\n * default) aligns the drawer's edge to the rail's. No-op if already\n * registered.\n *\n * @param drawer - The drawer to host. The caller retains ownership of its\n * lifecycle.\n * @param reg - Per-registration options (handle glyph / text, edge\n * alignment).\n *\n * @returns This rail, for method chaining.\n */\n registerDrawer(drawer: Drawer, reg: RailDrawerRegistration = {}): this {\n if (this._drawers.has(drawer)) {\n return this;\n }\n\n const handle = new RailHandle({ text: reg.text, glyph: reg.glyph, selected: drawer.isOpen() });\n\n const onOpen: () => void = (): void => { handle.setSelected(true); };\n const onClose: () => void = (): void => { handle.setSelected(false); };\n const onAction: ClickListener = (): void => { drawer.toggle(); };\n\n drawer.on(\"open\", onOpen);\n drawer.on(\"close\", onClose);\n handle.on(\"action\", onAction);\n\n if (reg.alignEdge !== false) {\n drawer.setEdge(this.getEdge() as DrawerEdge);\n }\n\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n this._drawers.set(drawer, { handle, onOpen, onClose, onAction });\n\n this.adaptThickness();\n this.scheduleLayout();\n this.emit(\"register\", drawer);\n\n return this;\n }\n\n /**\n * Unregisters a drawer: removes its handle and detaches every subscription\n * (the exact listener references are removed so nothing leaks). Does not\n * close or destroy the drawer. No-op if not registered.\n *\n * @param drawer - The drawer to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterDrawer(drawer: Drawer): this {\n const reg = this._drawers.get(drawer);\n if (!reg) {\n return this;\n }\n\n drawer.off(\"open\", reg.onOpen);\n drawer.off(\"close\", reg.onClose);\n reg.handle.off(\"action\", reg.onAction);\n\n this.removeComponent(reg.handle);\n this._drawers.delete(drawer);\n\n this.adaptThickness();\n this.emit(\"unregister\", drawer);\n\n return this;\n }\n\n // ----- window-minimize composition -----\n\n /**\n * Registers a window so it can minimize into the rail. Subscribes to the\n * window's minimize / restore / close events; while the window is minimized\n * it is represented by a rail handle that restores it on click. Called by\n * {@link AbstractWindow.setRail}. No-op if already registered.\n *\n * @param window - The window to host.\n *\n * @returns This rail, for method chaining.\n */\n registerWindow(window: AbstractWindow): this {\n if (this._windows.has(window)) {\n return this;\n }\n\n const onMinimize: () => void = (): void => { this.showWindowHandle(window); };\n const onRestore: () => void = (): void => { this.removeWindowHandle(window); };\n const onClose: () => void = (): void => { this.unregisterWindow(window); };\n const onAction: ClickListener = (): void => { window.restore(); };\n\n window.on(\"minimize\", onMinimize);\n window.on(\"restore\", onRestore);\n window.on(\"close\", onClose);\n\n this._windows.set(window, { handle: null, onMinimize, onRestore, onClose, onAction });\n\n // A window registered while already minimized gets its handle now.\n if (window.isMinimized()) {\n this.showWindowHandle(window);\n }\n\n this.emit(\"register\", window);\n\n return this;\n }\n\n /**\n * Unregisters a window: removes any handle and detaches every subscription.\n * Does not close the window. No-op if not registered.\n *\n * @param window - The window to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterWindow(window: AbstractWindow): this {\n const reg = this._windows.get(window);\n if (!reg) {\n return this;\n }\n\n window.off(\"minimize\", reg.onMinimize);\n window.off(\"restore\", reg.onRestore);\n window.off(\"close\", reg.onClose);\n\n this.removeWindowHandle(window);\n this._windows.delete(window);\n\n this.emit(\"unregister\", window);\n\n return this;\n }\n\n /**\n * Creates and adds a handle representing a minimized window, bearing its\n * title and glyph, wired to restore it on click. No-op if a handle already\n * shows.\n *\n * @param window - The minimized window.\n */\n private showWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle !== null) {\n return;\n }\n\n const handle = new RailHandle({ text: window.getTitle(), glyph: window.getGlyph(), selected: true });\n handle.on(\"action\", reg.onAction);\n\n reg.handle = handle;\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Removes the handle representing a window, if one shows. No-op otherwise.\n *\n * @param window - The window whose handle to remove.\n */\n private removeWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle === null) {\n return;\n }\n\n reg.handle.off(\"action\", reg.onAction);\n this.removeComponent(reg.handle);\n reg.handle = null;\n\n this.adaptThickness();\n }\n\n // ----- internal: handle orientation -----\n\n /**\n * Re-applies the current orientation's writing mode to every handle (drawer\n * and window). Called when the orientation changes.\n */\n private applyOrientation(): void {\n for (const reg of this._drawers.values()) {\n this.applyHandleOrientation(reg.handle);\n }\n\n for (const reg of this._windows.values()) {\n if (reg.handle !== null) {\n this.applyHandleOrientation(reg.handle);\n }\n }\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Applies the orientation's writing mode to a single handle: a rotated\n * `writing-mode` on the vertical (WEST/EAST) sides, cleared otherwise (and\n * always on NORTH/SOUTH, where handle text stays horizontal). Mirrors the\n * `Tab` layout's `sideways-rl` / `sideways-lr` mapping.\n *\n * @param handle - The handle to orient.\n */\n private applyHandleOrientation(handle: RailHandle): void {\n const orientation = this.getOrientation();\n\n // `sideways-rl` reads top-to-bottom (clockwise); `sideways-lr` reads\n // bottom-to-top (counter-clockwise). Only meaningful on a vertical rail.\n const writingMode = orientation === \"vertical-cw\" ? \"sideways-rl\"\n : orientation === \"vertical-ccw\" ? \"sideways-lr\"\n : null;\n\n if (this.isVertical() && writingMode !== null) {\n handle.setWritingMode(writingMode);\n } else {\n handle.clearWritingMode();\n }\n }\n\n // ----- internal: geometry -----\n\n /**\n * Opts the rail out of content-derived size clamping. Like\n * [`Container`](/api/core/classes/Container) / [`Panel`](/api/core/classes/Panel),\n * the rail sizes itself explicitly — its main axis spans the viewport and\n * its cross axis is the thickness it computes — so {@link Component.setWidth} /\n * {@link Component.setHeight} must not be clamped back to the layout\n * manager's content size. Without this, collapsing (which hides every\n * handle) would empty the handle layout, drive its content max toward zero,\n * and clamp the rail's viewport-spanning main axis to nothing — the strip\n * would vanish instead of resting at {@link RAIL_COLLAPSED_THICKNESS_PX}.\n *\n * @returns Always `false`.\n */\n protected clampsToContentSize(): boolean {\n return false;\n }\n\n /**\n * Returns whether the rail lays its handles out vertically — true for the\n * WEST and EAST edges (a column at a fixed width).\n *\n * @returns True for a vertical (WEST/EAST) rail.\n */\n private isVertical(): boolean {\n const edge = this.getEdge();\n\n return edge === Placement.WEST || edge === Placement.EAST;\n }\n\n /**\n * Returns the main-axis viewport offset of a registered window's rail handle —\n * where the genie animation should aim. When the handle exists (restore, or an\n * already-minimized window) this is its laid-out position; when it does not yet\n * exist (the collapse genie runs before the handle is created) this is the\n * predicted append slot: after the last existing handle plus one gap, or 0 for\n * an empty rail. Main axis is Y for a vertical (WEST/EAST) rail, X for a\n * horizontal (NORTH/SOUTH) one; the rail's main-axis viewport origin is 0.\n *\n * @param window - The window to locate, registered or not.\n *\n * @returns The main-axis viewport offset in pixels.\n */\n handleMainAxisOffset(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainPos = (c: Component): number => vertical ? c.getY() : c.getX();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n // Restore path (and an already-minimized window): the handle exists — its\n // laid-out main-axis position is the target directly.\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainPos(handle);\n }\n\n // Collapse path: the handle is created after this runs, appended last.\n // Predict its slot from the current last handle's trailing edge + gap.\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n const last = handles[handles.length - 1];\n\n return mainPos(last) + mainExtent(last) + this.handleSpacing();\n }\n\n /**\n * Returns the main-axis length of a registered window's rail handle — the\n * span the genie centres the shrinking window along. When the handle exists\n * (restore, or an already-minimized window) this is its laid-out extent; when\n * it does not yet exist (the collapse genie runs before the handle is\n * created) it is predicted from the current last handle, since sibling\n * handles run close in size. An empty rail has no handle to sample, so this\n * returns 0 and the caller keeps the window at the slot's leading edge. Main\n * axis is the height for a vertical (WEST/EAST) rail, the width for a\n * horizontal (NORTH/SOUTH) one.\n *\n * @param window - The window whose handle to measure, registered or not.\n *\n * @returns The main-axis handle length in pixels, or 0 when unpredictable.\n */\n handleMainAxisExtent(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainExtent(handle);\n }\n\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n return mainExtent(handles[handles.length - 1]);\n }\n\n /**\n * Returns the inter-handle gap the rail's `VBox` / `HBox` layout manager\n * applies between handles, or `0` before the layout manager exists.\n *\n * @returns The component spacing in pixels.\n */\n private handleSpacing(): number {\n const lm = this.getLayoutManager();\n\n return lm instanceof BoxLayout ? lm.getComponentSpacing() : 0;\n }\n\n /**\n * Computes the rail's on-screen rect from the current edge, thickness, and\n * viewport. WEST/EAST span the full viewport height at the chosen width;\n * NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const thickness = this.getThickness();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - thickness, y: 0, width: thickness, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: thickness };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - thickness, width: vp.width, height: thickness };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: thickness, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the rail via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Applies the expanded-state border: a 1px divider (`--ts-ui-rail-border`) on\n * the rail's inner edge — the side facing the rest of the UI — with the other\n * three sides a 1px *transparent* border rather than no border.\n *\n * Reserving the same 1px box on every side that the collapsed strip's button\n * border occupies keeps the rail's border-box geometry identical across the\n * collapse/expand transition, so the handles and chevron don't jump by a\n * pixel when the visible border appears or disappears.\n */\n private applyEdgeBorder(): void {\n const transparent = \"1px solid transparent\";\n const divider = \"1px solid var(--ts-ui-rail-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: transparent, borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: transparent, borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: transparent, borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: transparent, borderRight: divider });\n\n break;\n }\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the rail's events.\n *\n * @param event - `\"register\"` fires when a drawer/window is added,\n * `\"unregister\"` when one is removed.\n * @param listener - The callback, receiving the affected drawer or window.\n *\n * @returns This rail, for method chaining.\n */\n on(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This rail, for method chaining.\n */\n off(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event`, in registration order.\n *\n * @param event - The event to emit.\n * @param target - The affected drawer or window, forwarded to each listener.\n */\n protected emit(event: RailEvent, target: Drawer | AbstractWindow): void {\n this._listeners.fire(event, target);\n }\n\n /**\n * Cancels any in-flight collapse / slide animation, then defers to the base\n * class. Cancelling first keeps their fallback timers from firing after\n * `super.destructor()` has released this rail's element handle.\n */\n protected destructor(): void {\n this._collapseAnimation?.cancel();\n this._collapseAnimation = null;\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = null;\n this._slideInAnimation?.cancel();\n this._slideInAnimation = null;\n\n super.destructor();\n }\n}\n\nconst RailCallable = callable(Rail);\ntype RailCallable = Rail;\nexport {\n Rail as _Rail,\n RailCallable as Rail,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport { Component } from \"~/core/Component.js\";\nimport type { ComponentFactory } from \"~/core/Component.js\";\nimport { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport { TabWindow } from \"~/overlay/TabWindow.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Tab } from \"~/layout/Tab.js\";\nimport { Split } from \"~/layout/Split.js\";\nimport { DockRegion } from \"~/layout/DockRegion.js\";\nimport { LayoutConstraints } from \"~/layout/LayoutConstraints.js\";\nimport { serializeLayout, restoreLayout, LayoutState } from \"~/layout/LayoutSerialization.js\";\nimport { DragManager, DragEventDetail, tabDragRegistry } from \"~/overlay/DragManager.js\";\nimport { DropZoneOverlay } from \"~/overlay/DropZoneOverlay.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { AxisOrientation } from \"~/primitive/Axis.js\";\n\n/**\n * Declarative description of one dockable content panel.\n *\n * @category Core\n */\nexport interface DockPanelSpec {\n /**\n * Stable identity. `Dock` builds an identity frame whose `getId()` is this\n * value (set at the frame's construction), and serialization keys on it — so\n * restored layouts round-trip against it. Keep it stable across sessions.\n */\n id: string;\n /**\n * Visible tab label and tear-off window title. Set as the identity frame's\n * `getName()`; it rides on the frame (not a per-container constraint), so it\n * survives the re-home a restore performs.\n */\n title: string;\n /** Optional registry glyph name shown leading the tab label. */\n glyph?: string;\n /** Optional hover-tooltip text shown over the tab button. */\n tooltip?: string;\n /** Whether the tab shows a close button. Defaults to `true`. */\n closeable?: boolean;\n /**\n * The content: a live component, or a factory built on first resolve. It is\n * placed inside the identity frame, never mutated. A factory returning a\n * promise is accepted only by {@link Dock.addLazyPanel}, which shows a\n * spinner for the whole wait; {@link Dock.addPanel} throws on one.\n */\n content: Component | ComponentFactory;\n}\n\n/**\n * A node in the declarative initial arrangement: a leaf panel, a split of\n * regions, or a tab group.\n *\n * @category Core\n */\nexport type DockLayoutSpec =\n | DockPanelSpec\n | { split: AxisOrientation; children: DockLayoutSpec[] }\n | { tabs: DockPanelSpec[] };\n\n/**\n * Construction options for a {@link Dock}.\n *\n * @category Core\n */\nexport interface DockOptions extends ContainerOptions {\n /** Initial arrangement, compiled to the region tree at construction. Omit for an empty dock. */\n layout?: DockLayoutSpec;\n /**\n * Placeholder shown only while the dock holds no live panel (tiled or\n * floated) — a start-page for an empty dock. It is shown as a single\n * non-closeable tab in the empty region and is chrome, not a panel: it is\n * never serialized. See {@link Dock.setEmptyContent}.\n */\n emptyContent?: Component;\n /**\n * Construction-time listener bag — the declarative form of {@link Dock.on},\n * so a consumer can wire dock events (notably `emptychange`, the empty↔\n * populated aggregate) in the options bag instead of a separate post-build\n * `on(...)` call. Each entry is registered via `on` after construction; the\n * born-empty state is adopted without an emit, so read {@link Dock.isEmpty}\n * for the initial value.\n */\n listeners?: {\n attach?: (event: DockPanelEvent) => void;\n detach?: (event: DockPanelEvent) => void;\n move?: (event: DockPanelEvent) => void;\n focus?: (event: DockPanelEvent | null) => void;\n close?: (event: DockPanelEvent) => void;\n emptychange?: (event: DockEmptyEvent) => void;\n exception?: (event: DockExceptionEvent) => void;\n };\n}\n\n/**\n * String-literal union of the events a {@link Dock} emits across a panel's\n * lifecycle.\n *\n * The model is *host-centric*: a live panel always occupies one Dock-managed\n * *host* — the *tiled tree* (the main dock) or a *float window* — and the events\n * name the host transitions. `\"attach\"` fires when a panel **enters** a host (a\n * fresh `addPanel`/restore into the tiled tree, or a tear-off into a fresh\n * float); `\"detach\"` fires when it **leaves** a host while staying alive. The\n * two pair up across a move: a tear-off is `\"detach\"`(tiled) then\n * `\"attach\"`(float), and a re-dock — whether dropped on a region body/edge or\n * merged onto an existing tab bar — is `\"detach\"`(float) then `\"attach\"`(tiled).\n * `\"move\"` fires when a panel **relocates within** its current host — a\n * different region in the same tiled tree, or repositioned in the same float —\n * without changing host; it never accompanies a host change (that is\n * `\"detach\"`+`\"attach\"`) nor a first appearance (that is `\"attach\"` alone), and\n * a pure reorder within one strip is silent. `\"focus\"` fires when the dock-wide\n * active panel changes (across tiled tabs and floats; `null` when nothing is\n * focused), and `\"close\"` when a panel is destroyed. The\n * {@link DockPanelEvent.window} field names *which* host the panel entered,\n * left, occupies, or moved within. See {@link DockPanelEvent} for the payload.\n *\n * A separate `\"emptychange\"` event is a *dock-wide aggregate*, not a per-panel\n * event: it fires once each time the dock transitions between holding no live\n * panel anywhere and holding at least one, carrying a {@link DockEmptyEvent}.\n *\n * `\"exception\"` reports that a lazy panel's content factory rejected, carrying a\n * {@link DockExceptionEvent} rather than a {@link DockPanelEvent} — a panel that\n * never built has no content to name. It follows that panel's own `\"close\"`,\n * because the failure tears the whole docked panel down; the panel stays\n * registered, so re-adding the same id rebuilds it and retries.\n *\n * @category Core\n */\nexport type DockEvent = \"attach\" | \"detach\" | \"move\" | \"focus\" | \"close\" | \"emptychange\" | \"exception\";\n\n/**\n * Payload for a {@link Dock} lifecycle event, identifying the panel by its\n * stable {@link DockPanelSpec.id}, carrying its Dock-owned identity frame, and\n * naming the host the event concerns.\n *\n * @category Core\n */\nexport interface DockPanelEvent {\n /** The stable id of the panel (its {@link DockPanelSpec.id}). */\n id: string;\n /** The panel's Dock-owned identity frame. */\n content: Component;\n /**\n * The host the panel entered (`\"attach\"`), left (`\"detach\"`), moved within\n * (`\"move\"` — same host before and after), or currently occupies\n * (`\"focus\"`): `null` denotes the tiled tree / main dock, otherwise the float\n * window. Always `null` for `\"close\"` — a destroy is not a host transition,\n * but the field is always present so the payload stays flat.\n */\n window: AbstractWindow | null;\n}\n\n/**\n * Payload for a {@link Dock} `\"emptychange\"` event, reporting whether the dock\n * just became empty. Emitted once per real transition, not per panel.\n *\n * @category Core\n */\nexport interface DockEmptyEvent {\n /** `true` when the dock just became empty (no live panels anywhere), `false` when it became populated. */\n empty: boolean;\n}\n\n/**\n * Payload for a {@link Dock} `\"exception\"` event: a lazy panel's content\n * factory rejected. The panel has already been closed and its `\"close\"` event\n * already emitted by the time this fires.\n *\n * @category Core\n */\nexport interface DockExceptionEvent {\n /** The stable id of the panel that failed (its {@link DockPanelSpec.id}). */\n id: string;\n /** The value the content factory's promise rejected with. */\n error: unknown;\n}\n\n/**\n * Per-region drag-and-drop wiring tracked by the re-wire sweep.\n */\ninterface RegionWiring {\n /** The region's edge/centre drop coordinator (torn down on teardown). */\n dockRegion: DockRegion;\n /** Whether `setReorderable(true)` + the prune-on-`\"empty\"` subscription were applied (Tab regions only). */\n tabWired: boolean;\n}\n\n/**\n * A user-configurable, rearrangeable panel layout — the VS Code / GoldenLayout\n * style dock. It hosts a tree of [`Split`](/api/layout/classes/Split) /\n * [`Tab`](/api/layout/classes/Tab) regions whose panels the user can reorder,\n * tear off into floating [`Window`](/api/overlay/classes/Window)s, drop on region\n * edges to split, and save/restore.\n *\n * `Dock` is **glue**, not new drag mechanics: tab reorder + tear-off come from\n * `Tab`'s reorderable wiring, edge-split-on-drop from\n * [`DockRegion`](/api/layout/classes/DockRegion), every re-parent from\n * [`Component.moveComponent`](/api/core/classes/Component#movecomponent), and\n * persistence from\n * [`serializeLayout`](/api/layout/functions/serializeLayout) /\n * [`restoreLayout`](/api/layout/functions/restoreLayout). `Dock` owns the panel\n * registry (the serialization factory), the declarative initial-layout\n * compiler, and the re-wire sweep that keeps **every** region dockable —\n * including the regions a drop creates mid-gesture.\n *\n * @category Core\n */\nclass Dock extends Container<DockOptions> {\n\n // panelId -> spec; the single source the serialization factory resolves from.\n private _panels: Map<string, DockPanelSpec> = new Map<string, DockPanelSpec>();\n // panelId -> the Dock-owned identity frame built for that spec (cached so a\n // lazy factory runs once and every resolve returns the same instance).\n private _frames: Map<string, Component> = new Map<string, Component>();\n // panelId -> deferred content factory for a lazy panel (see addLazyPanel).\n // resolvePanel reads it to give the frame a lazy Tab layout (Tab.addLazyTab),\n // which owns the once-only materialization; the frame caches in _frames after.\n private _lazyFactories: Map<string, ComponentFactory> = new Map<string, ComponentFactory>();\n // region container -> its DnD wiring; the sweep's idempotence + teardown ledger.\n private _wiring: Map<Component, RegionWiring> = new Map<Component, RegionWiring>();\n // rAF coalescing latch so a burst of moves in one gesture yields one sweep.\n private _sweepScheduled: boolean = false;\n\n // Panel-lifecycle event bag. A field initialiser is safe here: no\n // cascade-dispatched setter touches it, and Dock exposes no construction-time\n // `listeners` option (no DockEvent is a build-time gesture — addPanel /\n // compileLayout run after super()), so it is never dispatched from\n // applyOptions. Consumers wire post-construction via on(...).\n private _listeners: ListenerBag<DockEvent> = new ListenerBag<DockEvent>();\n // The dock-wide focused panel id, or null when nothing is focused. The single\n // source of truth gating every \"focus\" emit so a re-activation is silent.\n private _focusedPanelId: string | null = null;\n // panelId -> last-observed host (null = tiled tree, else the float window);\n // the source of the attach/detach diff. A host change across a sweep emits\n // detach(old host) then attach(new host); a first appearance emits attach only.\n private _panelHost: Map<string, AbstractWindow | null> = new Map<string, AbstractWindow | null>();\n // panelId -> the Tab region last observed hosting it; lets a close recompute\n // the surviving sibling's focus after the region re-selects.\n private _frameRegion: Map<string, Component> = new Map<string, Component>();\n // The Tab region the user most recently focused (or that last received a\n // programmatic add). `addPanel` docks new panels here when it is still a live\n // region, so a new tab opens beside whatever tab-bar the user last worked in\n // rather than always the first/primary region. Null until the first add.\n private _lastActiveRegion: Component | null = null;\n // Float windows whose lifecycle events are already subscribed; the tracked-set\n // guard that stops a re-sweep stacking duplicate listeners.\n private _floatSubscribed: Set<AbstractWindow> = new Set<AbstractWindow>();\n\n // Overlay highlighting the dock as a drop target while it is empty (every\n // panel torn off) and a tab is dragged over it.\n private _emptyDropOverlay: DropZoneOverlay = new DropZoneOverlay();\n\n // Empty-state latch: whether the dock currently holds no live panel. Gates\n // the \"emptychange\" emit so it fires only on a real transition. Seeded true\n // because a dock is born empty: the first reconcile on a still-empty dock then\n // finds no transition (silent), while a dock born with a layout — or the first\n // addPanel — correctly flips it to false and emits emptychange(false). A plain\n // initializer is safe — no cascade-dispatched setter writes it.\n private _empty: boolean = true;\n // Whether reconcileEmptyState has run at least once. Gates only the\n // \"emptychange\" emit: the first run adopts the born emptiness without an emit\n // (being born empty or populated is not a transition). The placeholder itself\n // is re-asserted every sweep from the main-region emptiness, independent of\n // this latch.\n private _emptyReconciled: boolean = false;\n\n // Named, bound listener reference for the DockRegion post-drop callback,\n // routing through one removable handler that coalesces via scheduleSweep.\n private requestSweep: () => void = (): void => {\n this.scheduleSweep();\n };\n\n // Named, bound \"dock\" handler for every wired Tab: a foreign tab was merged\n // into a region's strip. The merge bypasses DockRegion, so it lands a sweep\n // here — the host-diff reconcile then emits the attach. The content arg is\n // unused (the reconcile re-derives every frame's host) but matches the\n // listener signature.\n private onPanelDocked: (content: Component) => void = (_content: Component): void => {\n this.requestSweep();\n };\n\n /**\n * Constructs a Dock, compiling `options.layout` into the region tree (or an\n * empty `Tab` region when omitted) and running the initial re-wire sweep.\n *\n * @param options - Construction options; `layout` seeds the arrangement.\n * @param subclassDefaults - Optional defaults a subclass injects before the caller's options.\n */\n constructor(options?: DockOptions, subclassDefaults?: Partial<DockOptions>) {\n super(options, { layoutManager: new Fit(), ...subclassDefaults });\n\n const root = options?.layout ? this.compileLayout(options.layout) : this.newTabRegion();\n\n this.addComponent(root);\n this.scheduleSweep();\n this.wireEmptyDropTarget();\n\n // Wire the declarative listener bag here, not in applyOptions: applyOptions\n // runs inside super() before this._listeners' field initializer, so on()\n // would target an undefined bag. scheduleSweep only reconciles emptiness\n // asynchronously, so no emptychange is missed by wiring after it.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies inherited options first, then routes the `emptyContent` placeholder\n * through its setter. The setter only caches (no DOM work) because it is\n * dispatched from within `super()`; the placeholder attaches from the first\n * post-construction reconcile.\n *\n * @param options - The construction options.\n *\n * @returns This dock.\n */\n protected applyOptions(options: DockOptions): this {\n super.applyOptions(options);\n\n if (options.emptyContent !== undefined) {\n this.setEmptyContent(options.emptyContent);\n }\n\n return this;\n }\n\n /**\n * Sets (or clears, with `null`) the placeholder shown while the dock holds no\n * live panel — a start-page for an empty dock. The placeholder is shown as a\n * single non-closeable tab in the empty root region, and is chrome: it is\n * never serialized (excluded from a saved arrangement) and never enters the\n * panel registry. When no placeholder is set, the empty region hides its tab\n * strip entirely; either way the dock still reports emptiness and fires\n * `\"emptychange\"`.\n *\n * When the dock is already empty, the shown placeholder is swapped immediately;\n * otherwise the value is only cached and shown on the next empty transition.\n *\n * @param component - The placeholder component, or `null` to clear it.\n *\n * @returns This dock, for chaining.\n */\n setEmptyContent(component: Component | null): this {\n // Hot-swap only once the state machine is live (first reconcile has run):\n // during the super()/applyOptions cascade _emptyReconciled is still false,\n // keeping the setter cache-only so no DOM work happens at construction. The\n // placeholder is shown while the main region is empty (all tabs closed or\n // torn off), so gate the swap on that, not on dock-wide emptiness.\n const showing = this._emptyReconciled && this.mainRegionEmpty();\n\n if (showing) {\n this.hideEmptyState(); // remove the outgoing placeholder tab\n }\n\n this._options.emptyContent = component ?? undefined;\n\n if (showing) {\n this.showEmptyState(); // show the incoming placeholder tab\n }\n\n return this;\n }\n\n /**\n * The placeholder shown while the dock is empty, or `null` when none is set.\n *\n * @returns The placeholder component, or `null`.\n */\n getEmptyContent(): Component | null {\n return this._options.emptyContent ?? null;\n }\n\n /**\n * Whether the dock holds no live panel anywhere — tiled or floated. A dock\n * whose only panels are torn off into floats is *not* empty (the floats are\n * still live panels of this dock), so this reports `false` for it, and\n * `\"emptychange\"` fires off this aggregate. The empty-state placeholder is a\n * separate, *visual* concern that tracks the main region alone, so it can show\n * over an all-floated dock while this still reports `false`.\n *\n * @returns `true` when no live panel exists.\n */\n isEmpty(): boolean {\n return this._frames.size === 0;\n }\n\n /**\n * Registers the dock itself as a drop target that is active only while the\n * dock is empty — every panel torn off, so no region remains to carry a\n * `DockRegion`. Dropping a tab onto the bare dock then builds a fresh root\n * region to hold it, making an emptied dock its own re-dock surface rather\n * than leaving a placeholder region behind. While non-empty the predicate\n * declines, so the regions' own `DockRegion`s (nested deeper, hit first by\n * the hit-test) keep handling drops.\n */\n private wireEmptyDropTarget(): void {\n DragManager.makeDropTarget(this, {\n accepts: (detail: DragEventDetail): boolean =>\n detail.dragData[\"tabDrag\"] === true && this.getComponents().length === 0,\n // The full-region blue overlay is the only feedback here; suppress the\n // manager's whole-target tint so it does not stack with it.\n suppressValidityTint: true,\n onDragOver: (): null => {\n this._emptyDropOverlay.attachTo(this);\n this._emptyDropOverlay.highlightFull();\n\n return null;\n },\n onDragLeave: (): void => {\n this._emptyDropOverlay.detach();\n },\n onDrop: (detail: DragEventDetail): void => {\n this._emptyDropOverlay.detach();\n\n const panel = tabDragRegistry.get(detail.dragData[\"componentId\"] as string);\n\n if (!panel) {\n return;\n }\n\n const region = this.newTabRegion();\n\n this.addComponent(region);\n region.moveComponent(panel);\n this.scheduleSweep();\n },\n });\n }\n\n /**\n * Registers a panel and adds it as a tab in the active region, then schedules\n * a re-wire sweep so a newly-created region is made dockable.\n *\n * @param spec - The panel to register and dock.\n *\n * @returns This dock, for chaining.\n */\n addPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(content, undefined, this.leafConstraints(spec));\n\n // Activate the freshly added panel so opening it shows it. The tab\n // cell is created lazily on the region's next doLayout, so this may\n // defer (Tab.setActiveContent) until that pass.\n (region.getLayoutManager() as Tab).setActiveContent(content);\n\n // The panel just entered the tiled tree. The ledger is left without an\n // entry for this id so the next sweep's host diff sees a first\n // appearance and emits attach(tiled) — the same reconcile path a\n // dragged-in dock flows through, so a programmatic add and a drop\n // produce the event from identical code.\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Adds a panel whose content is built lazily — on first activation — instead\n * of up front. The tab appears immediately (the identity frame is created\n * empty, so re-open dedup via {@link focusPanel} and layout serialization keep\n * working); on first activation a centred spinner shows while the spec's\n * `content` factory runs, then the built content fades in. The frame's own\n * (strip-hidden) Tab layout drives this via {@link Tab.addLazyTab}, so the\n * spinner/content cross-fade is hosted by the same materialize path every lazy\n * tab uses. Use it for panels whose content is expensive to build or fetches\n * data, so opening one never blocks the tab from appearing.\n *\n * @param spec - The panel to register and dock; `content` is treated as the\n * lazy factory (a live component is wrapped in one).\n *\n * @returns This dock, for chaining.\n */\n addLazyPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n this._lazyFactories.set(\n spec.id,\n typeof spec.content === \"function\" ? spec.content : () => spec.content as Component\n );\n\n const frame = this.resolvePanel(spec.id);\n\n if (frame) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(frame, undefined, this.leafConstraints(spec));\n\n // Activate it so the tab shows; activation drives realizeLazyContent\n // (via the region's \"activate\" -> onPanelFocused) to materialize the\n // deferred content behind a spinner.\n (region.getLayoutManager() as Tab).setActiveContent(frame);\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Returns the root region container (a `Container` carrying a `Split`/`Tab`\n * manager). Derived live as the sole `Fit` child rather than cached, because\n * an edge drop onto the root swaps that child for a fresh `Split` wrapper.\n *\n * @returns The root region.\n */\n getRootRegion(): Component {\n return this.getComponents()[0];\n }\n\n /**\n * Captures the current arrangement (delegates to\n * [`serializeLayout`](/api/layout/functions/serializeLayout)).\n *\n * @returns The captured layout state.\n */\n getLayoutState(): LayoutState {\n return serializeLayout(this.getRootRegion());\n }\n\n /**\n * Restores a captured arrangement, sourcing leaves from the panel registry\n * (delegates to [`restoreLayout`](/api/layout/functions/restoreLayout)), then\n * schedules a sweep to wire the fresh regions restore created.\n *\n * The region ledger is cleared first: `restoreLayout` tears the region tree\n * down and rebuilds it, so every surviving panel lands in a fresh region\n * object. Without clearing, the post-restore sweep would read each panel's\n * stale (now-destroyed) region and spuriously fire `\"move\"` for every panel.\n * A restore is not a user-visible relocation, so it stays silent for\n * `\"move\"`; the sweep re-seeds the ledger from the rebuilt tree.\n *\n * @param state - A layout state from {@link getLayoutState}.\n *\n * @returns This dock, for chaining.\n */\n setLayoutState(state: LayoutState): this {\n this._frameRegion.clear();\n restoreLayout(this.getRootRegion(), state, (id: string) => this.resolvePanel(id));\n this.scheduleSweep();\n\n return this;\n }\n\n /**\n * The serialization factory: resolves a panel id to its Dock-owned identity\n * frame, building it once (running a lazy content factory at most once) and\n * caching it so every resolve returns the same instance.\n *\n * The frame is a `Container` constructed with the stable `id` (the serialization\n * key, read back via `getId()`) and the `title` (the visible tab label, via\n * `getName()`) set **at construction** — the caller's content is placed\n * inside it and never mutated. The id must be set at construction, not via a\n * later `setId`, so the frame's `#id`-scoped CSS rule (which carries\n * `position: absolute`) binds to the element; a post-construction `setId`\n * would leave that rule on the old id and the frame would collapse to\n * `position: static`.\n *\n * @param id - The panel id to resolve.\n *\n * @returns The identity frame, or `null` when the id is unknown.\n */\n private resolvePanel(id: string): Component | null {\n const spec = this._panels.get(id);\n\n if (!spec) {\n return null;\n }\n\n let frame = this._frames.get(id);\n\n if (!frame) {\n const factory = this._lazyFactories.get(id);\n\n if (factory) {\n // A lazy panel defers its content to first activation, and the\n // frame's own Tab layout owns that deferral: Tab.addLazyTab mounts a\n // spinner during the two-frame yield and fades the built content in,\n // hosting the transient spinner/content overlap the way every lazy\n // tab does — the strip hidden so the single content fills the frame.\n // Delegating to the Tab is why the frame is not a plain Fit here: a\n // single-child Fit faults the instant the spinner and content coexist.\n const tab = new Tab();\n\n tab.setBarVisible(false);\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: tab });\n\n // The frame's Tab is a local and unreachable from outside, so\n // this subscription is the only thing that can turn a failed\n // content build into a Dock-level event. Named reference, not an\n // inline arrow, per the listener rule in ARCHITECTURE.md — the\n // same shape `wireRegion`'s `onEmpty` uses for a per-frame\n // handler.\n const onFailed: (error: unknown) => void = (error: unknown): void => {\n this.failPanel(spec.id, error);\n };\n\n tab.on(\"exception\", onFailed);\n tab.addLazyTab(factory, spec.title ?? spec.id);\n } else {\n // A normal panel builds its content now; the frame exists at once so\n // the tab shows and the id dedups / serializes like any other.\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: new Fit() });\n\n // A live component or a synchronous factory. A promise-returning\n // factory raises Component.addComponent's Error here: a Fit frame\n // has no spinner and nothing to own the wait.\n frame.addComponent(spec.content);\n }\n\n this._frames.set(id, frame);\n }\n\n return frame;\n }\n\n /**\n * Builds the glyph-only layout constraints for a leaf, or `undefined` when\n * the spec has no glyph. The glyph is the one presentation hint `Tab` reads\n * from the constraint; identity and label ride on the component itself.\n *\n * @param spec - The panel spec.\n *\n * @returns The constraints, or `undefined`.\n */\n private leafConstraints(spec: DockPanelSpec): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n // Dock tabs are closeable by default; a spec may opt out.\n constraints.closeable = spec.closeable ?? true;\n\n if (spec.glyph) {\n constraints.glyph = spec.glyph;\n }\n\n if (spec.tooltip) {\n constraints.tooltip = spec.tooltip;\n }\n\n return constraints;\n }\n\n /**\n * Resolves the region `addPanel` docks into: the root if it is a `Tab`, else\n * the first `Tab` region found depth-first, else the root wrapped in a fresh\n * `Tab` (only reachable for an externally-crafted tab-less tree — `Dock`'s\n * own compiler and `DockRegion` always keep leaves in `Tab` stacks).\n *\n * @returns A `Tab` region to add a tab to.\n */\n private activeTabRegion(): Component {\n const root = this.getRootRegion();\n\n // Prefer the region the user last focused, when it is still a live Tab in\n // the current tree, so a new panel opens beside the last-used tab-bar.\n const last = this._lastActiveRegion;\n if (last && this.isTab(last) && this.containsRegion(root, last)) {\n return last;\n }\n\n if (this.isTab(root)) {\n return root;\n }\n\n return this.firstTabRegion(root) ?? this.wrapRootInTab();\n }\n\n /**\n * Depth-first membership test: whether `target` is `region` or nested under\n * it. Used to confirm `_lastActiveRegion` still lives in the dock's tree\n * before docking into it (a collapsed split or a layout restore can retire a\n * region object).\n *\n * @param region - The subtree root to search.\n * @param target - The region to find.\n *\n * @returns `true` when `target` is within `region`.\n */\n private containsRegion(region: Component, target: Component): boolean {\n if (region === target) {\n return true;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child) && this.containsRegion(child, target)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Depth-first search for the first `Tab` region at or under `region`.\n *\n * @param region - The region to search from.\n *\n * @returns The first `Tab` region, or `null` when none exists.\n */\n private firstTabRegion(region: Component): Component | null {\n if (this.isTab(region)) {\n return region;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n const found = this.firstTabRegion(child);\n\n if (found) {\n return found;\n }\n }\n }\n\n return null;\n }\n\n /**\n * Installs a fresh `Tab` region as the dock's single `Fit` child and\n * re-homes the existing root into it, returning the new region. The safety\n * net for a tab-less tree (see {@link activeTabRegion}).\n *\n * @returns The fresh `Tab` region now holding the old root.\n */\n private wrapRootInTab(): Component {\n const region = this.newTabRegion();\n const oldRoot = this.getRootRegion();\n\n this.addComponent(region);\n region.moveComponent(oldRoot);\n\n return region;\n }\n\n /**\n * Builds an empty region: a `Container` carrying a fresh `Tab` manager.\n *\n * @returns The new `Tab` region.\n */\n private newTabRegion(): Component {\n return new Container({ layoutManager: new Tab({ reorderable: true, compact: true }) });\n }\n\n /**\n * Compiles a declarative layout spec into a region `Component`, registering\n * every leaf in the panel registry along the way. The only structural build\n * code unique to `Dock`; runtime restructuring belongs to `DockRegion`.\n *\n * @param spec - The layout spec to compile.\n *\n * @returns The compiled region.\n */\n private compileLayout(spec: DockLayoutSpec): Component {\n if (\"split\" in spec) {\n const region = new Container({ layoutManager: new Split({ orientation: spec.split }) });\n\n for (const child of spec.children) {\n region.addComponent(this.compileRegion(child));\n }\n\n return region;\n }\n\n if (\"tabs\" in spec) {\n return this.compileTabs(spec.tabs);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Compiles a child of a split: a leaf becomes its own single-tab stack so a\n * split pane is always a draggable region, never a bare leaf.\n *\n * @param spec - The child spec.\n *\n * @returns The compiled region.\n */\n private compileRegion(spec: DockLayoutSpec): Component {\n if (\"split\" in spec || \"tabs\" in spec) {\n return this.compileLayout(spec);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Builds a `Tab` region holding the given leaves, registering and stamping\n * each one.\n *\n * @param specs - The leaf panels to stack.\n *\n * @returns The `Tab` region.\n */\n private compileTabs(specs: DockPanelSpec[]): Component {\n const region = this.newTabRegion();\n\n for (const spec of specs) {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n region.addComponent(content, this.leafConstraints(spec));\n\n // A compiled panel starts tiled; seed the host ledger with null\n // so the first sweep's host diff is silent (no transition to\n // attach from). Construction therefore emits nothing.\n this._panelHost.set(spec.id, null);\n this._frameRegion.set(spec.id, region);\n }\n }\n\n return region;\n }\n\n /**\n * Schedules one coalesced re-wire sweep on the next animation frame. Repeated\n * calls within a frame collapse to a single sweep.\n */\n private scheduleSweep(): void {\n if (this._sweepScheduled) {\n return;\n }\n\n this._sweepScheduled = true;\n\n DOM.sink.requestAnimationFrame(() => {\n this._sweepScheduled = false;\n this.runSweep();\n });\n }\n\n /**\n * Runs the idempotent sweep: adopt every owned float window into a wired\n * region tree, wire the in-dock root and each float region, then tear down\n * the coordinators of regions that have vanished from the combined live tree.\n */\n private runSweep(): void {\n const root = this.getRootRegion();\n\n if (!root) {\n return;\n }\n\n const floatRegions = this.ownedFloatWindows()\n .map(win => this.adoptFloat(win))\n .filter((region): region is Component => region !== null);\n\n this.wireRegion(root);\n\n for (const region of floatRegions) {\n this.wireRegion(region);\n }\n\n this.subscribeFloatWindows();\n this.reconcileHosts(root);\n this.teardownVanished(root, floatRegions);\n this.reconcileEmptyState();\n }\n\n /**\n * Reconciles the two independent empty concerns each sweep:\n *\n * - **Placeholder / bar** track the *main region's* emptiness — whether the\n * root region holds no tiled panel. This is true both when every tab was\n * closed and when they were all torn off into floats, so the start page\n * shows in either case. Re-asserted idempotently every sweep (not just on a\n * transition) so it re-shows after a `setLayoutState` restore clears the\n * root region wholesale.\n * - **`\"emptychange\"`** tracks the *dock-wide* emptiness — {@link isEmpty},\n * `true` only when no panel exists anywhere (tiled or floated). Latched on\n * `_empty` and emitted exactly once per real transition; a no-op sweep is\n * silent. Called at the end of every sweep and reached from every close via\n * {@link scheduleSweep}, so the emit is once per settled transition.\n */\n private reconcileEmptyState(): void {\n if (this.mainRegionEmpty()) {\n this.showEmptyState();\n } else {\n this.hideEmptyState();\n }\n\n const empty = this.isEmpty();\n\n // First reconcile: sync the born state without an emit (being born empty\n // or populated is not a transition). A born-with-layout dock differs from\n // the true=seed latch and falls through to emit emptychange(false) once.\n if (!this._emptyReconciled) {\n this._emptyReconciled = true;\n\n if (empty === this._empty) {\n return;\n }\n }\n\n if (empty === this._empty) {\n return;\n }\n\n this._empty = empty;\n this.emit(\"emptychange\", { empty });\n }\n\n /**\n * Whether the main (tiled) region holds no panel — every tab closed, or all\n * torn off into floats. The placeholder is excluded so it never counts as\n * content. This drives the placeholder, distinct from {@link isEmpty} (which\n * counts floated panels as live).\n *\n * @returns `true` when the root region shows no tiled panel.\n */\n private mainRegionEmpty(): boolean {\n const region = this.getRootRegion();\n\n if (!region) {\n return false;\n }\n\n const placeholder = this.getEmptyContent();\n\n return region.getComponents().every(child => child === placeholder);\n }\n\n /**\n * Shows the empty-state chrome in the empty root region. With a placeholder\n * set, it is docked as a single non-closeable, `transient` (never-serialized)\n * tab; without one, the region's tab strip is hidden so an emptied dock is a\n * clean surface rather than a dangling empty strip. Idempotent — safe to call\n * on every sweep — and a no-op when the root region is not a `Tab`.\n */\n private showEmptyState(): void {\n const region = this.getRootRegion();\n const placeholder = this.getEmptyContent();\n\n if (!region) {\n return;\n }\n\n if (placeholder) {\n this.rootTab()?.setBarVisible(true);\n\n if (placeholder.getParentComponent() !== region) {\n region.moveComponent(placeholder, undefined, this.placeholderConstraints());\n }\n } else {\n this.rootTab()?.setBarVisible(false);\n }\n }\n\n /**\n * Removes the empty-state chrome: detaches the placeholder tab when present\n * and restores the root region's tab strip. Idempotent — safe to call on\n * every sweep and when nothing is shown.\n */\n private hideEmptyState(): void {\n const placeholder = this.getEmptyContent();\n const parent = placeholder?.getParentComponent();\n const manager = parent?.getLayoutManager();\n\n // Remove the placeholder from wherever it currently sits. A `Tab` host must\n // go through `closeTab` (not `removeComponent`): `Tab.doLayout` reconciles\n // only *added* children, so a bare `removeComponent` would orphan the strip\n // cell and leave a stale placeholder tab. `closeTab`'s `\"tabclose\"` is\n // ignored by the dock (the placeholder is not a registered frame) and its\n // drain-`\"empty\"` is absorbed by the root-region prune guard. `closeTab`\n // returns false when no cell exists yet (added but not laid out); then a\n // plain `removeComponent` suffices — there is no cell to orphan.\n if (placeholder) {\n const closed = manager instanceof Tab && manager.closeTab(placeholder);\n\n if (!closed) {\n parent?.removeComponent(placeholder);\n }\n }\n\n this.rootTab()?.setBarVisible(true);\n }\n\n /**\n * The root region's `Tab` manager, or `null` when the root region is absent or\n * carries a different manager (e.g. an externally-crafted `Split` root). An\n * empty dock's root is always a `Tab` — the prune path keeps a single empty\n * `Tab` region — so the placeholder always finds one.\n *\n * @returns The root `Tab` manager, or `null`.\n */\n private rootTab(): Tab | null {\n const manager = this.getRootRegion()?.getLayoutManager();\n\n return manager instanceof Tab ? manager : null;\n }\n\n /**\n * The layout constraints the placeholder tab is docked under: non-closeable\n * (the start page cannot be closed) and `transient` (shown as a tab but never\n * captured by serialization). The tab's label is the placeholder component's\n * own name.\n *\n * @returns The placeholder tab constraints.\n */\n private placeholderConstraints(): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n constraints.closeable = false;\n constraints.transient = true;\n\n return constraints;\n }\n\n /**\n * Idempotently subscribes the lifecycle events of every float window that\n * currently hosts one of this dock's frames — both the adopted bare\n * `Window` mini-docks and the self-contained `TabWindow` tear-offs the sweep\n * does not adopt. A `TabWindow`'s internal `Tab` is never wired by\n * `wireRegion`, so its `\"activate\"` / `\"tabclose\"` / `\"detach\"` /\n * `\"dock\"` are subscribed here explicitly; both float kinds get the\n * window's `\"activate\"` / `\"close\"`. The tracked set stops a re-sweep\n * stacking duplicate listeners.\n */\n private subscribeFloatWindows(): void {\n for (const win of this.floatWindowsHoldingFrames()) {\n if (this._floatSubscribed.has(win)) {\n continue;\n }\n\n const onFloatActivate: () => void = (): void => { this.onFloatActivated(win); };\n const onFloatClose: () => void = (): void => { this.onFloatClosed(win); };\n\n win.on(\"activate\", onFloatActivate);\n win.on(\"close\", onFloatClose);\n\n if (win instanceof TabWindow) {\n const tab = win.getLayoutManager() as Tab;\n\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n }\n\n this._floatSubscribed.add(win);\n }\n\n this.pruneClosedFloatSubscriptions();\n }\n\n /**\n * Drops closed windows from the float-subscription tracking set so a future\n * window object never collides with a stale entry. The listeners themselves\n * die with the closed window, so only the set bookkeeping is needed.\n */\n private pruneClosedFloatSubscriptions(): void {\n const open = new Set<AbstractWindow>(AbstractWindow.getOpenWindows());\n\n for (const win of this._floatSubscribed) {\n if (!open.has(win)) {\n this._floatSubscribed.delete(win);\n }\n }\n }\n\n /**\n * Open float windows hosting one of this dock's frames, including the\n * self-contained `TabWindow` tear-offs (which `ownedFloatWindows` excludes\n * because the sweep does not adopt them), and excluding the window the dock\n * itself lives in. The subscription targets for the panel lifecycle.\n *\n * @returns The float windows holding this dock's frames.\n */\n private floatWindowsHoldingFrames(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Recomputes each registered frame's host (`null` when it sits under the\n * in-dock tiled tree, else the float window holding it) and region, and diffs\n * both against their ledgers, emitting the lifecycle events the change\n * implies: a first appearance (no host ledger entry — a fresh `addPanel` or a\n * restore) emits `\"attach\"` alone; a change from one host to another (a\n * tear-off, a re-dock by either drop path, a float-to-float move) emits\n * `\"detach\"`(old host) then `\"attach\"`(new host); a same-host change of\n * region (a relocation to a different region within one host) emits\n * `\"move\"`; an unchanged host and region is silent. This is the single\n * source of every `\"attach\"`/`\"detach\"`/`\"move\"`, so the events are\n * identical regardless of which DnD path landed the sweep.\n *\n * Only frames still registered and still cached are visited, so a panel whose\n * frame a close handler already evicted produces no phantom `\"detach\"`.\n *\n * @param root - The current root region.\n */\n private reconcileHosts(root: Component): void {\n for (const [id, frame] of this._frames) {\n if (!this._panels.has(id)) {\n continue;\n }\n\n const host = this.hostForFrame(frame, root);\n const had = this._panelHost.has(id);\n const prev = this._panelHost.get(id) ?? null;\n const region = this.regionForFrame(frame);\n const prevRegion = this._frameRegion.get(id) ?? null;\n\n if (!had) {\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (host !== prev) {\n this.emit(\"detach\", { id, content: frame, window: prev });\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (region && prevRegion && region !== prevRegion) {\n // Same host, different region: the panel relocated within its\n // host. The `region && prevRegion` guard keeps a frame transiently\n // out of any region (mid-teardown) silent rather than spurious.\n this.emit(\"move\", { id, content: frame, window: host });\n }\n\n this._panelHost.set(id, host);\n\n if (region) {\n this._frameRegion.set(id, region);\n }\n }\n }\n\n /**\n * The host a frame currently occupies: `null` when it sits under the tiled\n * tree (the main dock), otherwise the float window holding it. Reuses the\n * existing tiled test and float lookup so the reconcile and the\n * focus-payload construction derive the host the same way.\n *\n * @param frame - The identity frame to locate.\n * @param root - The current root region.\n *\n * @returns The host window, or `null` for the tiled tree.\n */\n private hostForFrame(frame: Component, root: Component): AbstractWindow | null {\n return this.isUnder(root, frame) ? null : this.floatForFrame(frame);\n }\n\n /**\n * Open windows whose content subtree holds one of this dock's identity frames\n * — the floats torn off from this dock — excluding the window the dock itself\n * lives in. Re-derived each sweep (never cached) so a closed float drops out\n * naturally, mirroring the derived-live root in {@link getRootRegion}.\n *\n * A {@link TabWindow} is excluded: a default tear-off opens one as a\n * self-contained floating tabbed window, re-dockable via its own tab DnD and\n * self-closing when emptied. It is never adopted into the dock's region tree,\n * so the sweep must leave it alone — only the Shift-torn bare {@link Window}\n * floats become adoptable mini-docks.\n *\n * @returns The owned float windows.\n */\n private ownedFloatWindows(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !(win instanceof TabWindow) &&\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Whether `node` lies within `win`'s subtree — walks `node`'s ancestor chain\n * looking for `win`. Used both to detect a float hosting a frame and to\n * exclude the dock's own host window (which contains the dock, hence every\n * still-docked frame).\n *\n * @param win - The candidate ancestor window.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `win` is an ancestor of `node`.\n */\n private windowContains(win: AbstractWindow, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === win) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * A window's first non-chrome child — its content panel — or `null` when the\n * window has none yet.\n *\n * @param win - The window to inspect.\n *\n * @returns The content component, or `null`.\n */\n private windowContent(win: AbstractWindow): Component | null {\n return win.getComponents().find(child => !win.isChromeComponent(child)) ?? null;\n }\n\n /**\n * Ensures a float window's content is a wired-able region tree and returns\n * that region. A freshly torn-off float holds its bare identity frame as the\n * window's content; this wraps it in a single fresh `Tab` region so it is a\n * proper region leaf with a draggable handle, turning the window into a\n * mini-dock. Idempotent: once the content is a region container (already\n * adopted, or restored as a tree) it is returned unchanged, so re-sweeps\n * after edge-splits inside the float do not re-wrap.\n *\n * @param win - The float window to adopt.\n *\n * @returns The float's content region, or `null` when the window has no content.\n */\n private adoptFloat(win: AbstractWindow): Component | null {\n const content = this.windowContent(win);\n\n if (!content) {\n return null;\n }\n\n if (this.isRegionContainer(content)) {\n return content;\n }\n\n const region = this.newTabRegion();\n\n // A fresh region carries no constraint, so the window's Border fills it as\n // an unplaced→CENTER child — the same way the bare frame filled before.\n win.moveComponent(region);\n region.moveComponent(content);\n\n return region;\n }\n\n /**\n * Idempotently wires a region and recurses into its child regions: makes a\n * `Tab` region reorderable and prunes it when its last tab leaves, and gives\n * every region a `DockRegion` so it accepts edge/centre drops and notifies\n * the dock after a drop mutates the tree.\n *\n * @param region - The region to wire.\n */\n private wireRegion(region: Component): void {\n let wiring = this._wiring.get(region);\n\n if (!wiring) {\n wiring = { dockRegion: new DockRegion(region, this.requestSweep), tabWired: false };\n\n this._wiring.set(region, wiring);\n }\n\n const manager = region.getLayoutManager();\n\n if (this.isTab(region) && !wiring.tabWired) {\n const tab: Tab = manager as Tab;\n\n tab.setReorderable(true);\n // Per-region prune; the named const carries the region the shared\n // handler set otherwise could not (ARCHITECTURE: a listener is a named\n // reference, never an inline arrow).\n const onEmpty: () => void = (): void => { this.pruneRegion(region); };\n\n tab.on(\"empty\", onEmpty);\n // The lifecycle handlers are shared bound methods: their payloads (the\n // closed/activated content, the torn-off window) carry the identity\n // they need, so no per-region capture is required.\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n\n wiring.tabWired = true;\n }\n\n for (const child of region.getComponents()) {\n // A lazy panel's identity frame carries a Tab manager, which would\n // otherwise make this sweep wire the panel itself as a drop-taking,\n // prunable region — so its inner strip draining would prune the\n // frame out of its parent and leave a phantom tab behind.\n if (this.isRegionContainer(child) && this._frames.get(child.getId()) !== child) {\n this.wireRegion(child);\n }\n }\n }\n\n /**\n * Removes a `Tab` region once its last tab has left, then collapses a `Split`\n * that the removal leaves with a single pane, and schedules a sweep to tear\n * down the removed region's coordinator. Wired to every `Tab` region's\n * `\"empty\"` event so regions the dock itself built (the `compileLayout` /\n * `restoreLayout` tree) are pruned, not just the stacks `DockRegion` mints.\n * No-op when the region was already detached (a `DockRegion`-created stack\n * pruned itself first) or still holds a tab.\n *\n * @param region - The region whose `Tab` fired `\"empty\"`.\n */\n private pruneRegion(region: Component): void {\n const parent = region.getParentComponent();\n\n if (!parent || region.getComponents().length > 0) {\n return;\n }\n\n // Keep an emptied root region (its parent is the dock) as the dock's\n // add/drop target — removing it would leave the dock with no region and\n // crash the next addPanel.\n if (parent === this) {\n return;\n }\n\n parent.removeComponent(region);\n this.collapseSinglePaneSplit(parent);\n this.closeFloatIfEmpty(parent);\n this.scheduleSweep();\n }\n\n /**\n * Closes a float window whose mini-dock just emptied. When a region was the\n * direct content of a float window and pruning it leaves the window with no\n * content, the float has nothing left to host — close it, matching the\n * auto-close a strip-mode tear-off window performs when its last tab leaves.\n * A no-op when `container` is an in-dock region (its parent is the dock or a\n * `Split`, never a window) or the window still holds content.\n *\n * @param container - The container the pruned region was removed from.\n */\n private closeFloatIfEmpty(container: Component): void {\n if (!AbstractWindow.getOpenWindows().includes(container as AbstractWindow)) {\n return;\n }\n\n if (!this.windowContent(container as AbstractWindow)) {\n (container as AbstractWindow).requestClose();\n }\n }\n\n /**\n * Collapses a `Split` left with a single child: the lone child is hoisted\n * into the grandparent at the `Split`'s slot (via `moveComponent`) and the\n * emptied `Split` removed. A no-op unless `container` is a single-child\n * `Split` with a grandparent (a root single-pane `Split` renders its child\n * full-bleed). The swap keeps the grandparent's child count unchanged, so the\n * collapse does not cascade.\n *\n * @param container - The container that just lost a child.\n */\n private collapseSinglePaneSplit(container: Component): void {\n if (this.regionKind(container) !== \"Split\") {\n return;\n }\n\n const children = container.getComponents();\n\n if (children.length !== 1) {\n return;\n }\n\n const grandparent = container.getParentComponent();\n\n if (!grandparent) {\n return;\n }\n\n const index = grandparent.getComponents().indexOf(container);\n\n // Carry the collapsing Split's stored size onto the hoisted child so the\n // slot keeps the user's dragged ratio (when the grandparent is a Split).\n if (this.regionKind(grandparent) === \"Split\") {\n (grandparent.getLayoutManager() as Split).transferPaneSize(container, children[0]);\n }\n\n grandparent.moveComponent(children[0], index);\n grandparent.removeComponent(container);\n }\n\n /**\n * Destroys the wiring of every tracked region no longer reachable from the\n * combined live tree (the in-dock root plus every owned float's region tree),\n * releasing the drop targets a removed region's coordinator held. Seeding the\n * reachable set from the float regions too is what keeps a float's drop\n * targets alive across sweeps driven by unrelated in-dock moves.\n *\n * @param root - The current root region.\n * @param floatRegions - The adopted content region of each owned float window.\n */\n private teardownVanished(root: Component, floatRegions: Component[]): void {\n const reachable = new Set<Component>();\n\n this.collectRegions(root, reachable);\n\n for (const region of floatRegions) {\n this.collectRegions(region, reachable);\n }\n\n for (const [region, wiring] of this._wiring) {\n if (!reachable.has(region)) {\n wiring.dockRegion.destroy();\n this._wiring.delete(region);\n }\n }\n }\n\n /**\n * Collects every region container at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The set to populate.\n */\n private collectRegions(region: Component, into: Set<Component>): void {\n into.add(region);\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectRegions(child, into);\n }\n }\n }\n\n /**\n * Whether a component is a region container — a `Container` carrying a `Split`\n * or `Tab` manager. Discriminates on the stripped runtime class name (no\n * `instanceof`, avoiding an import cycle), matching how serialization keys\n * its node kinds.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Split`/`Tab` region.\n */\n private isRegionContainer(component: Component): boolean {\n const kind = this.regionKind(component);\n\n return kind === \"Split\" || kind === \"Tab\";\n }\n\n /**\n * Whether a component is a `Tab` region.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Tab` region.\n */\n private isTab(component: Component): boolean {\n return this.regionKind(component) === \"Tab\";\n }\n\n /**\n * The stripped runtime class name of a component's layout manager, or the\n * empty string when it has none (a leaf content component).\n *\n * @param component - The component to inspect.\n *\n * @returns The manager's class name without its `_` export-alias prefix.\n */\n private regionKind(component: Component): string {\n const manager = component.getLayoutManager() as (Tab | Split | undefined);\n\n return manager ? manager.getClassName().replace(/^_/, \"\") : \"\";\n }\n\n // ----- panel lifecycle -----\n\n /**\n * `\"tabclose\"` handler for every wired `Tab` (tiled or float): a registered\n * panel was genuinely closed. Emits `\"close\"`, evicts the cached frame so a\n * re-`addPanel` rebuilds it via the lazy factory (keeping the `_panels`\n * registration), and — when the closed panel was the dock-wide focused one —\n * recomputes focus once the source `Tab` has re-selected a survivor.\n *\n * @param content - The closed tab's content (a Dock identity frame).\n */\n private onPanelClosed = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n const region = this._frameRegion.get(id) ?? null;\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content, window: null });\n\n // Route the close through the one reconcile site so \"emptychange\" fires\n // even when no structural prune scheduled a sweep. The latch-diff makes a\n // second reconcile in the same settled state a no-op, so this cannot\n // double-fire alongside pruneRegion's own scheduleSweep.\n this.scheduleSweep();\n\n if (this._focusedPanelId === id) {\n this.scheduleFocusRecompute(region);\n }\n };\n\n /**\n * Closes a lazy panel whose content factory rejected, then reports the\n * failure as this dock's `\"exception\"`. The panel's own `\"close\"` event\n * fires first, from the shared close path.\n *\n * The panel stays registered, so re-adding the same id rebuilds its frame\n * and runs the factory again — that is the retry path.\n *\n * @param id - The id of the panel whose factory rejected.\n * @param error - The value the factory's promise rejected with.\n */\n private failPanel(id: string, error: unknown): void {\n // The panel was closed while its factory was still in flight: the close\n // path already evicted its frame, so there is nothing left to tear down\n // and nobody left to report to. Closing a docked panel closes the tab in\n // the *outer* region and never touches the frame's own entry, so the\n // inner staleness check cannot see this — the registry is what knows.\n if (!this._frames.has(id)) {\n return;\n }\n\n // A frame that is registered but sits in no Tab region cannot be closed\n // through the shared path; evict it directly so a re-add rebuilds it.\n if (!this.removePanel(id)) {\n this._frames.delete(id);\n }\n\n this.emit(\"exception\", { id, error });\n }\n\n /**\n * `\"activate\"` handler for every wired `Tab`: the active tab changed via a\n * click or `setActiveTabIndex`. Emits `\"focus\"` for the now-active panel,\n * gated on a genuine focused-panel change.\n *\n * @param content - The now-active tab's content (a Dock identity frame).\n */\n private onPanelFocused = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n // Remember the focused panel's region so the next add docks beside the\n // tab-bar the user just worked in (see activeTabRegion).\n const region = this._frameRegion.get(id);\n if (region) {\n this._lastActiveRegion = region;\n }\n\n this.setFocus(id);\n };\n\n /**\n * `\"detach\"` handler for every wired `Tab`: a tab was torn off into a new\n * float window. Schedules a sweep so the new float is wired (and any\n * Shift-torn bare `Window` adopted); the sweep's host-diff reconcile observes\n * the torn-off frame's tiled -> float transition and emits the\n * `\"detach\"`(tiled) + `\"attach\"`(float) pair itself, so this handler emits\n * nothing directly — the reconcile is the single source of those events.\n *\n * @param _window - The float window the tab was torn off into (unused; the\n * reconcile re-derives every frame's host).\n */\n private onPanelDetached = (_window: AbstractWindow): void => {\n this.scheduleSweep();\n };\n\n /**\n * Window `\"activate\"` handler for an owned float: the float became the active\n * layer. Emits `\"focus\"` for the float's active panel, gated on a genuine\n * focused-panel change.\n *\n * @param window - The float window that was activated.\n */\n private onFloatActivated(window: AbstractWindow): void {\n const frame = this.activeFrameInFloat(window);\n\n if (frame) {\n this.setFocus(frame.getId());\n }\n }\n\n /**\n * Window `\"close\"` handler for an owned float: the float's chrome ✕ closed\n * it. Emits one `\"close\"` per registered frame the float held (a bare-`Window`\n * mini-dock can hold several) — read before the window tears down — and\n * recomputes focus when a closed frame was the focused panel.\n *\n * @param window - The float window being closed.\n */\n private onFloatClosed(window: AbstractWindow): void {\n let focusLost = false;\n\n for (const frame of this.framesInWindow(window)) {\n const id = frame.getId();\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content: frame, window: null });\n\n if (this._focusedPanelId === id) {\n focusLost = true;\n }\n }\n\n // Route the close through the reconcile so a last-float close flips the\n // empty latch and fires \"emptychange\" (a float close schedules no\n // structural sweep of its own).\n this.scheduleSweep();\n\n if (focusLost) {\n this.scheduleFocusRecompute(null);\n }\n }\n\n /**\n * Sets the dock-wide focused panel and emits `\"focus\"` only on a genuine\n * change, so re-activating the already-focused panel is silent. A `null` id\n * clears focus and emits `focus(null)`.\n *\n * @param id - The newly-focused panel id, or `null` when none is focused.\n */\n private setFocus(id: string | null): void {\n if (id === this._focusedPanelId) {\n return;\n }\n\n this._focusedPanelId = id;\n\n if (id === null) {\n this.emit(\"focus\", null);\n\n return;\n }\n\n const frame = this._frames.get(id);\n\n if (frame) {\n const host = this.hostForFrame(frame, this.getRootRegion());\n\n this.emit(\"focus\", { id, content: frame, window: host });\n }\n }\n\n /**\n * Schedules a deferred focus recompute after a close. The source `Tab`\n * re-selects a survivor (visually, with no event) *after* its `\"tabclose\"`\n * fires, so the new active tab is only readable on the next frame.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private scheduleFocusRecompute(region: Component | null): void {\n DOM.sink.requestAnimationFrame(() => this.recomputeFocusAfterClose(region));\n }\n\n /**\n * Recomputes the dock-wide focus after the focused panel was closed: when\n * panels remain in `region`, focus the survivor the region re-selected; when\n * no panel remains anywhere, emit `focus(null)`.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private recomputeFocusAfterClose(region: Component | null): void {\n if (this._frames.size === 0) {\n this.setFocus(null);\n\n return;\n }\n\n if (!region || !this.isTab(region) || region.getComponents().length === 0) {\n this.setFocus(null);\n\n return;\n }\n\n const frame = (region.getLayoutManager() as Tab).getActiveContent();\n\n this.setFocus(frame ? frame.getId() : null);\n }\n\n /**\n * Activates the tab hosting `id` and raises its host float when it lives in\n * one, so a buried floated panel surfaces. A successful activation drives the\n * host `Tab`'s active-tab change and the float raise, each of which emits a\n * `\"focus\"`.\n *\n * @param id - The panel id to focus.\n *\n * @returns `true` when the panel was found and activated, `false` for an\n * unknown id or one in no `Tab` region (registered but never docked).\n */\n focusPanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n const index = (region.getLayoutManager() as Tab).indexOfContent(frame);\n\n if (index < 0) {\n return false;\n }\n\n this.floatForFrame(frame)?.bringToFront();\n (region.getLayoutManager() as Tab).setActiveTabIndex(index);\n\n return true;\n }\n\n /**\n * Closes the panel `id` through the same user-close path a tab ✕ takes, so it\n * emits exactly one `\"close\"` through the shared `\"tabclose\"` subscription.\n *\n * @param id - The panel id to close.\n *\n * @returns `true` when the panel was found and closed, `false` for an unknown\n * id or one in no `Tab` region.\n */\n removePanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n return (region.getLayoutManager() as Tab).closeTab(frame);\n }\n\n /**\n * The `Tab` region currently hosting `frame` — searched across the in-dock\n * tiled tree and every float window's region tree — or `null` when no `Tab`\n * region holds it (registered but never docked, or mid-teardown).\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host `Tab` region, or `null`.\n */\n private regionForFrame(frame: Component): Component | null {\n for (const region of this.allTabRegions()) {\n if ((region.getLayoutManager() as Tab).indexOfContent(frame) >= 0) {\n return region;\n }\n }\n\n return null;\n }\n\n /**\n * The float window currently hosting `frame`, or `null` when it lives in the\n * in-dock tiled tree (or nowhere). Used to raise a buried float on focus.\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host float window, or `null`.\n */\n private floatForFrame(frame: Component): AbstractWindow | null {\n return this.floatWindowsHoldingFrames().find(win => this.windowContains(win, frame)) ?? null;\n }\n\n /**\n * Every `Tab` region across the combined live tree: the in-dock root plus\n * each float window (an adopted bare-`Window` mini-dock's region tree, and a\n * `TabWindow` whose own layout manager is the `Tab`).\n *\n * @returns The live `Tab` regions.\n */\n private allTabRegions(): Component[] {\n const regions: Component[] = [];\n const root = this.getRootRegion();\n\n if (root) {\n this.collectTabRegions(root, regions);\n }\n\n for (const win of this.floatWindowsHoldingFrames()) {\n if (win instanceof TabWindow) {\n regions.push(win as unknown as Component);\n\n continue;\n }\n\n const content = this.windowContent(win);\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n }\n\n return regions;\n }\n\n /**\n * Collects every `Tab` region at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The array to populate.\n */\n private collectTabRegions(region: Component, into: Component[]): void {\n if (this.isTab(region)) {\n into.push(region);\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectTabRegions(child, into);\n }\n }\n }\n\n /**\n * The registered frames of this dock that lie within `window`'s subtree — the\n * panels a float holds. Read at float-close time to fan out one `\"close\"` per\n * frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The registered frames inside the window.\n */\n private framesInWindow(window: AbstractWindow): Component[] {\n return [...this._frames.values()].filter(frame => this.windowContains(window, frame));\n }\n\n /**\n * The active panel frame inside a float window: a `TabWindow`'s own active\n * tab, or the active tab of the first `Tab` region inside a bare-`Window`\n * mini-dock. `null` when none resolves to a registered frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The active registered frame, or `null`.\n */\n private activeFrameInFloat(window: AbstractWindow): Component | null {\n let tab: Tab | null = null;\n\n if (window instanceof TabWindow) {\n tab = window.getLayoutManager() as Tab;\n } else {\n const content = this.windowContent(window);\n const regions: Component[] = [];\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n\n tab = regions.length > 0 ? (regions[0].getLayoutManager() as Tab) : null;\n }\n\n const frame = tab ? tab.getActiveContent() : null;\n\n return frame && this._frames.get(frame.getId()) === frame ? frame : null;\n }\n\n /**\n * Whether `node` lies at or under `ancestor`'s component subtree.\n *\n * @param ancestor - The candidate ancestor component.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `ancestor` is `node` or one of its ancestors.\n */\n private isUnder(ancestor: Component, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === ancestor) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Registers a listener for a panel-lifecycle event. The `\"attach\"`,\n * `\"detach\"`, `\"move\"`, and `\"close\"` events always carry a\n * {@link DockPanelEvent}; `\"focus\"` carries a `DockPanelEvent` or `null` when\n * nothing is focused. The payload's `window` field names the host: `null` for\n * the tiled tree, otherwise the float window the panel entered (`\"attach\"`),\n * left (`\"detach\"`), or moved within (`\"move\"`, same host before and after);\n * it is always `null` for `\"close\"`.\n *\n * @param event - `\"attach\"` / `\"detach\"` / `\"move\"` / `\"close\"`.\n * @param listener - Invoked with the affected panel.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Registers a listener for the `\"focus\"` event, which fires when the\n * dock-wide active panel changes, carrying the now-focused panel or `null`\n * when nothing is focused (e.g. the last panel closed).\n *\n * @param event - The `\"focus\"` event.\n * @param listener - Invoked with the now-focused panel, or `null`.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Registers a listener for the `\"emptychange\"` event, which fires once each\n * time the dock transitions between empty (no live panel anywhere) and\n * populated, carrying `{ empty }`.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - Invoked with the new emptiness state.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Registers a listener for the `\"exception\"` event, which fires when a lazy\n * panel's content factory rejected. The panel has already been closed and\n * its `\"close\"` event already emitted, so a listener must not call\n * {@link Dock.removePanel} for that id. The panel stays registered, so\n * re-adding the same id rebuilds its frame and retries the factory.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - Invoked with the failed panel's id and the rejection value.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n on(event: DockEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Removes a previously registered `\"focus\"` listener.\n *\n * @param event - The `\"focus\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Removes a previously registered `\"emptychange\"` listener.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Removes a previously registered `\"exception\"` listener.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - The exact listener reference passed to `on`.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n off(event: DockEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event` with `payload`, in\n * registration order.\n *\n * @param event - The event to emit.\n * @param payload - The lifecycle payload (`null` only for `\"focus\"`).\n */\n protected emit(event: \"attach\" | \"detach\" | \"move\" | \"close\", payload: DockPanelEvent): void;\n protected emit(event: \"focus\", payload: DockPanelEvent | null): void;\n protected emit(event: \"emptychange\", payload: DockEmptyEvent): void;\n protected emit(event: \"exception\", payload: DockExceptionEvent): void;\n protected emit(event: DockEvent, payload: DockPanelEvent | DockEmptyEvent | DockExceptionEvent | null): void {\n this._listeners.fire(event, payload);\n }\n}\n\nconst DockCallable = callable(Dock);\ntype DockCallable = Dock;\nexport {\n Dock as _Dock,\n DockCallable as Dock,\n};\n"],"mappings":"ohDAqBA,IAAM,EAAgC,GAQhC,EAA6B,EAAwB,EAAK,KAAK,MAS/D,EAA6B,KAAK,KAAK,CAAiB,EAAI,EAG5D,EAA8B,EAG9B,EAAiC,EAGjC,EAAmC,IAmDnC,GAAkD,CACpD,OAAW,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,EAChC,UAAW,OACX,UAAW,gBACX,UAAW,EACf,EAuCM,QAAN,cAAsB,CAAsD,CASxE,mBAAuD,SAKvD,eAAuD,KACvD,YAA+C,KAI/C,mBAA4D,KAC5D,mBAA4D,KAC5D,eAA4C,KAC5C,gBAA+C,KAC/C,QAAsC,GACtC,iBAAuC,CAAC,EAExC,gBACA,UAQA,YAAY,EAA0B,CAClC,MAAM,EAA2B,EAAsB,EAEvD,IAAM,EAAO,IAAI,GAEjB,EAAK,cAAc,EAAI,EACvB,KAAK,iBAAiB,CAAI,EAG1B,KAAK,mBAAmB,6CAA6C,EACrE,KAAK,mBAAmB,0CAA0C,EAClE,KAAK,UAAU,CAAE,OAAQ,2DAA4D,CAAC,EACtF,KAAK,gBAAgB,kCAAkC,EACvD,KAAK,UAAU,+DAA+D,EAK9E,KAAK,YAAY,EAAS,KAAK,EAC/B,KAAK,WAAW,EAAK,EAKrB,KAAK,WAAW,QAAQ,EACxB,KAAK,YAAY,SAAS,EAC1B,KAAK,QAAQ,CAAC,CAAC,QAAQ,QAAQ,EAE/B,KAAK,oBAAwB,KAAK,YAAY,EAC9C,KAAK,cAAwB,KAAK,YAAY,EAM9C,KAAK,SAAoB,KACzB,KAAK,kBAAoB,IAC7B,CASA,aAAuB,EAA+B,CAWlD,OAVA,MAAM,aAAa,CAAO,EAK1B,KAAK,aAAa,EAAQ,WAAa,KAAK,aAAa,CAAC,EAC1D,KAAK,aAAa,EAAQ,WAAa,KAAK,aAAa,CAAC,EAC1D,KAAK,aAAa,EAAQ,WAAa,KAAK,YAAY,CAAC,EACrD,EAAQ,QAAU,IAAA,IAAW,KAAK,SAAS,EAAQ,KAAK,EAErD,IACX,CAQA,aAAa,EAA2B,CAGpC,MAFA,MAAK,WAAa,EAEX,IACX,CAOA,cAAiC,CAC7B,OAAO,KAAK,YAAc,KAAK,gBAAgB,SACnD,CAQA,aAAa,EAAgC,CAGzC,MAFA,MAAK,WAAa,EAEX,IACX,CAOA,cAAmC,CAC/B,OAAO,KAAK,YAAc,KAAK,gBAAgB,SACnD,CAQA,aAAa,EAAsB,CAO/B,MANA,MAAK,WAAa,EAEd,KAAK,iBACL,KAAK,gBAAgB,WAAW,CAAK,EAGlC,IACX,CAOA,aAAuB,CACnB,OAAO,KAAK,YAAc,KAAK,gBAAgB,SACnD,CAUA,SAAS,EAA2B,CAgBhC,OAfI,IAAS,KACF,KAAK,WAAW,GAG3B,KAAK,OAAS,EAET,KAAK,gBAMN,KAAK,gBAAgB,QAAQ,CAAI,GALjC,KAAK,gBAAkB,IAAI,EAAK,CAAI,EACpC,KAAK,gBAAgB,cAAc,MAAM,EAEzC,KAAK,gBAAgB,KAAK,gBAAiB,CAAC,GAKzC,KACX,CAOA,UAA0B,CACtB,OAAO,KAAK,MAChB,CAOA,YAAmB,CAQf,MAPA,CAEI,KAAK,mBADL,KAAK,gBAAgB,KAAK,eAAe,EAClB,MAG3B,KAAK,OAAS,KAEP,IACX,CAUA,QAAQ,EAAmC,CACvC,AAEI,KAAK,kBADL,KAAK,gBAAgB,KAAK,cAAc,EAClB,MAG1B,IAAM,EAAkB,OAAO,GAAY,SACrC,IAAI,EAAK,CAAO,EAChB,EAEN,KAAK,eAAiB,EAGtB,IAAM,EAAgB,QAAK,gBAI3B,OAFA,KAAK,gBAAgB,EAAM,CAAW,EAE/B,IACX,CAOA,SAA4B,CACxB,OAAO,KAAK,cAChB,CAUA,UAAU,EAAe,EAA2B,CAC3C,KAAK,cACN,KAAK,YAAc,IAAI,EACvB,KAAK,YAAY,iBAAiB,IAAI,EAAM,EAE5C,KAAK,aAAa,KAAK,WAAW,GAGtC,IAAM,EAAS,IAAI,EAAO,CAAK,EAM/B,OAJA,EAAO,GAAG,SAAU,CAAO,EAE3B,KAAK,YAAY,aAAa,CAAM,EAE7B,IACX,CAOA,cAAqB,CAMjB,MALA,CAEI,KAAK,eADL,KAAK,gBAAgB,KAAK,WAAW,EAClB,MAGhB,IACX,CAQA,iBAAyB,EAAkB,CAGvC,MAFA,MAAK,eAAiB,EAEf,IACX,CASA,kBAAkB,EAAoB,CAClC,OAAO,KAAK,iBAAiB,EAAE,WAAW,EAAI,CAAE,CACpD,CASA,MAAa,CACT,GAAI,CAAC,KAAK,eAEN,OADA,QAAQ,KAAK,mEAAmE,EACzE,KAGX,KAAK,QAAU,GAIf,EAAa,SAAS,IAAI,EAC1B,KAAK,UAAU,EAAa,UAAU,IAAI,CAAC,EAE3C,IAAM,EAAK,KAAK,WAAW,EAAI,EAqB/B,OAnBA,EAAa,MAAM,CAAE,EAIrB,EAAU,IAAI,EAEd,KAAK,YAAY,EACjB,KAAK,WAAW,EAAI,EAIpB,KAAK,SAAS,EACd,KAAK,YAAY,EAEjB,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,GAAS,KAAM,CAAE,WAAY,CAAyB,CAAC,EAEjF,KAAK,0BAA0B,EAExB,IACX,CAQA,MAAa,CAeT,OAdK,KAAK,SAIV,KAAK,QAAU,GAEf,KAAK,0BAA0B,EAE/B,EAAa,WAAW,IAAI,EAC5B,EAAY,IAAI,EAEhB,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,GAAkB,KAAM,CAAE,WAAY,CAAyB,CAAC,EAEnF,MAbI,IAcf,CAOA,QAAkB,CACd,OAAO,KAAK,OAChB,CASA,iBAAiC,CAC7B,OAAO,KAAK,WAAW,GAAK,IAChC,CAWA,gBAAmC,CAC/B,OAAO,KAAK,YAAc,KAAK,gBAAgB,SACnD,CAMA,cAAqB,CACjB,KAAK,KAAK,CACd,CAQA,kBAAkC,CAC9B,OAAO,KAAK,cAChB,CAQA,SAAkB,CACd,OAAO,EAAa,KAAK,OAC7B,CAQA,UAAiB,CAOb,OANA,MAAM,SAAS,EAEX,KAAK,iBAAmB,KAAK,YAAc,KAAK,gBAChD,KAAK,cAAc,EAGhB,IACX,CAOA,YAA6B,CACrB,KAAK,SACL,KAAK,KAAK,EAcd,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,KAC1B,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,KAE1B,KAAK,iBAAiB,QAAQ,EAE9B,KAAK,eAAmB,KACxB,KAAK,gBAAmB,KACxB,KAAK,eAAmB,KACxB,KAAK,YAAmB,KACxB,KAAK,gBAAmB,KAExB,MAAM,WAAW,CACrB,CAOA,aAA4B,CACxB,GAAI,CAAC,KAAK,eACN,OAGJ,IAAM,EAAa,EAAI,OAAO,eAAe,KAAK,cAAc,EAGhE,GAAI,EAAW,QAAU,GAAK,EAAW,SAAW,EAAG,CACnD,KAAK,KAAK,EACV,MACJ,CAEA,IAAM,EAAY,KAAK,iBAAiB,EAClC,EAAY,GAAW,OAAU,KAAK,SAAS,EAC/C,EAAY,GAAW,QAAU,KAAK,UAAU,EAChD,EAAY,EAAI,OAAO,gBAAgB,EAEvC,EAAW,KAAK,iBAAiB,EAAY,EAAO,EAAQ,CAAE,EAEpE,KAAK,mBAAqB,EAE1B,IAAI,EAAY,EACZ,EAAY,EAEZ,IAAa,OACb,EAAI,EAAW,MAAQ,EAAW,MAAS,GAAU,EACrD,EAAI,EAAW,IAAO,EAAS,GACxB,IAAa,UACpB,EAAI,EAAW,MAAQ,EAAW,MAAS,GAAU,EACrD,EAAI,EAAW,OAAS,GACjB,IAAa,QACpB,EAAI,EAAW,KAAO,EAAQ,EAC9B,EAAI,EAAW,KAAQ,EAAW,OAAS,GAAU,IAErD,EAAI,EAAW,MAAQ,EACvB,EAAI,EAAW,KAAS,EAAW,OAAS,GAAU,GAQ1D,IAAI,EAAO,EACP,EAAO,EAAG,MAAQ,EAAQ,EAC1B,EAAO,EACP,EAAO,EAAG,OAAS,EAAS,EAE5B,IAAa,QACb,GAAQ,EACD,IAAa,OACpB,GAAQ,EACD,IAAa,SACpB,GAAQ,EAER,GAAQ,EAGZ,EAAI,EAAK,MAAM,EAAG,EAAM,CAAI,EAC5B,EAAI,EAAK,MAAM,EAAG,EAAM,CAAI,EAE5B,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,CAAC,EACX,KAAK,SAAS,CAAK,EACnB,KAAK,UAAU,CAAM,EAEjB,KAAK,iBAAmB,KAAK,YAC7B,KAAK,cAAc,CAE3B,CAcA,iBACI,EACA,EACA,EACA,EACgB,CAChB,IAAM,EAAc,EAAO,IACrB,EAAc,EAAG,OAAS,EAAO,OACjC,EAAc,EAAO,KACrB,EAAc,EAAG,MAAS,EAAO,MAEvC,GAAI,KAAK,aAAe,OAAQ,CAC5B,IAAM,EAAS,EAAS,EAClB,EAAS,EAAS,EAElB,EAA6E,CAC/E,CAAE,KAAM,SAAU,MAAO,EAAa,KAAM,CAAO,EACnD,CAAE,KAAM,MAAU,MAAO,EAAa,KAAM,CAAO,EACnD,CAAE,KAAM,QAAU,MAAO,EAAa,KAAM,CAAO,EACnD,CAAE,KAAM,OAAU,MAAO,EAAa,KAAM,CAAO,CACvD,EAIM,EAAU,EAAW,OAAO,GAAK,EAAE,OAAS,EAAE,IAAI,EAClD,EAAU,EAAQ,OAAS,EAAI,EAAU,EAI/C,OAFA,EAAK,MAAM,EAAG,IAAM,EAAE,MAAQ,EAAE,KAAK,EAE9B,EAAK,EAAE,CAAC,IACnB,CAGA,IAAM,KAAQ,GACN,IAAS,MAAmB,GAAe,EAAS,EACpD,IAAS,SAAmB,GAAe,EAAS,EACpD,IAAS,OAAmB,GAAe,EAAS,EAEjD,GAAc,EAAQ,EAGjC,GAAI,KAAK,KAAK,UAAU,EACpB,OAAO,KAAK,WAUhB,IAAM,EAAU,CANZ,IAAQ,SACR,OAAQ,MACR,KAAQ,QACR,MAAQ,MAGI,EAAS,KAAK,YAI9B,OAFA,QAAQ,KAAK,gCAAgC,KAAK,WAAW,6CAA6C,EAAQ,GAAG,EAE9G,CACX,CAYA,aAA4B,CACxB,GAAI,KAAK,gBACL,OAGJ,IAAM,EAAQ,IAAI,EACZ,EAAQ,EAEd,EAAM,mBAAmB,6CAA6C,EACtE,EAAM,SAAS,CAAI,EACnB,EAAM,UAAU,CAAI,EACpB,EAAM,aAAa,eAAe,EAClC,EAAM,iBAAiB,MAAM,EAC7B,EAAM,WAAW,KAAK,UAAU,EAEhC,KAAK,gBAAkB,EAEvB,IAAM,EAAY,KAAK,WAAW,EAAI,EACtC,EAAI,KAAK,aAAa,EAAW,EAAM,WAAW,EAAI,EAAI,EAAI,OAAO,cAAc,CAAS,CAAC,CACjG,CASA,eAA8B,CAC1B,GAAI,CAAC,KAAK,gBAAkB,CAAC,KAAK,gBAC9B,OAGJ,IAAM,EAAa,EAAI,OAAO,eAAe,KAAK,cAAc,EAC1D,EAAa,EACb,EAAa,EAAO,EACpB,EAAa,KAAK,KAAK,EACvB,EAAa,KAAK,KAAK,EACvB,EAAa,KAAK,SAAS,EAC3B,EAAa,KAAK,UAAU,EAK5B,OAAS,EAAe,EAAa,IAAwB,KAAK,IAAI,EAAK,KAAK,IAAI,EAAO,CAAG,CAAC,EAO/F,EAAc,kDAChB,EAEJ,AAOI,EAPA,KAAK,qBAAuB,SACd,iBAAiB,EAAY,kBAAkB,IACtD,KAAK,qBAAuB,MACrB,kBAAkB,EAAY,mBAAmB,IACxD,KAAK,qBAAuB,QACrB,iBAAiB,EAAY,mBAAmB,IAEhD,iBAAiB,EAAY,mBAAmB,IAGlE,KAAK,gBAAgB,UAAU,CAAW,EAE1C,IAAM,EAAS,EAAI,OAAO,gBAAgB,EACpC,EAAS,KAAK,cAAc,EAMlC,GAAI,KAAK,qBAAuB,OAAS,KAAK,qBAAuB,SAAU,CAC3E,IAAM,EAAgB,EAAW,KAAO,EAAW,MAAQ,EACrD,EAAgB,KAAK,IACvB,EACA,EAAyB,EAAW,EAAoB,CAC5D,EACM,EAAgB,KAAK,IACvB,EAAW,EAAO,EAClB,EAAG,MAAQ,EAAyB,EAAW,EAAO,CAC1D,EACM,EAAgB,MAAM,EAAgB,EAAW,EAAO,EAAO,KAAM,EAAW,CAAS,EAE/F,KAAK,gBAAgB,KAAK,CAAM,EAI5B,KAAK,qBAAuB,SAC5B,KAAK,gBAAgB,KAAK,GAAQ,EAAO,GAAG,EAE5C,KAAK,gBAAgB,KAAK,EAAW,EAAO,EAAO,GAAG,CAE9D,KAAO,CACH,IAAM,EAAgB,EAAW,IAAM,EAAW,OAAS,EACrD,EAAgB,KAAK,IACvB,EACA,EAAyB,EAAW,EAAoB,CAC5D,EACM,EAAgB,KAAK,IACvB,EAAW,EAAO,EAClB,EAAG,OAAS,EAAyB,EAAW,EAAO,CAC3D,EACM,EAAgB,MAAM,EAAgB,EAAW,EAAO,EAAO,IAAK,EAAW,CAAS,EAE9F,KAAK,gBAAgB,KAAK,CAAM,EAE5B,KAAK,qBAAuB,QAC5B,KAAK,gBAAgB,KAAK,GAAQ,EAAO,IAAI,EAE7C,KAAK,gBAAgB,KAAK,EAAW,EAAO,EAAO,IAAI,CAE/D,CACJ,CAMA,2BAA0C,CAGtC,GAFA,EAAM,oBAAoB,KAAM,SAAU,KAAK,eAAe,EAE1D,KAAK,eAAgB,CACrB,KAAK,iBAAmB,KAAK,uBAAuB,KAAK,cAAc,EAEvE,IAAK,IAAM,KAAY,KAAK,iBACxB,EAAI,KAAK,YAAY,EAAU,SAAU,KAAK,UAAW,CAAE,QAAS,EAAK,CAAC,CAElF,CACJ,CAKA,2BAA0C,CACtC,EAAM,uBAAuB,KAAM,SAAU,KAAK,eAAe,EAEjE,IAAK,IAAM,KAAY,KAAK,iBACxB,EAAI,KAAK,eAAe,EAAU,SAAU,KAAK,SAAS,EAG9D,KAAK,iBAAmB,CAAC,CAC7B,CASA,uBAA+B,EAAwB,CACnD,IAAM,EAAgB,CAAC,EACnB,EAAwB,EAAI,OAAO,iBAAiB,CAAI,EAE5D,KAAO,GAAU,IAAW,EAAI,OAAO,mBAAmB,GAAG,CACzD,IAAM,EAAQ,EAAI,OAAO,oBAAoB,CAAM,EAC7C,EAAW,EAAM,SAAW,EAAM,UAAY,EAAM,UAEtD,wBAAwB,KAAK,CAAQ,GACrC,EAAI,KAAK,CAAM,EAGnB,EAAS,EAAI,OAAO,iBAAiB,CAAM,CAC/C,CAIA,OAFA,EAAI,KAAK,EAAI,OAAO,mBAAmB,CAAC,EAEjC,CACX,CACJ,EAEM,GAAkB,EAAS,OAAO,ECp7BlC,GAAgE,CAAC,EAQjE,eAAN,cAA6B,CAAiC,CAK1D,YAAY,EAAiC,CACzC,MAAM,EAAS,EAA6B,EAE5C,IAAM,EAAK,EAAI,OAAO,gBAAgB,EAEtC,KAAK,YAAY,EAAS,KAAK,EAC/B,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,CAAC,EACX,KAAK,SAAS,EAAG,KAAK,EACtB,KAAK,UAAU,EAAG,MAAM,EACxB,KAAK,UAAU,KAAK,EACpB,KAAK,mBAAmB,iCAAiC,CAC7D,CAOA,iBAAiB,EAAgC,CAG7C,OAFA,EAAM,YAAY,KAAM,QAAS,CAAQ,EAElC,IACX,CAKA,QAAe,CACX,IAAM,EAAK,EAAI,OAAO,gBAAgB,EAEtC,KAAK,SAAS,EAAG,KAAK,EACtB,KAAK,UAAU,EAAG,MAAM,CAC5B,CAKA,SAAgB,CACZ,KAAK,cAAc,EACnB,KAAK,WAAW,CACpB,CACJ,EAEM,EAAyB,EAAS,cAAc,ECnDtD,EAAM,SAAS,EAAO,EAAc,EAAa,GAAsB,EAAkB,EA8FzF,IAAM,EAAyB,GACzB,EAAyB,GACzB,GAAyB,GACzB,GAAyB,EACzB,GAAyB,GACzB,EAAyB,GACzB,EAAyB,GACzB,GAA4B,IAC5B,EAA4B,IAC5B,EAA6B,GAG7B,GAAiC,GAGjC,GAAiD,CACnD,KAAS,cACT,QAAS,eACT,QAAS,uBACT,MAAS,oBACb,EAMM,EAAkC,IAOlC,EAAqB,2EASrB,EAAsB,EAQtB,EAA0B,EAO1B,GAA+B,EAoB/B,eAAN,cAA6B,CAAU,CAEnC,WACA,aAAsC,KACtC,YAAoC,KAQpC,YAAY,EAAe,EAAqB,EAAsB,CAClE,MAAM,EAEN,KAAK,mBAAmB,sBAAsB,EAC9C,KAAK,UAAU,CACX,OAAc,OACd,aAAc,sCAClB,CAAC,EACD,KAAK,iBAAiB,CAAE,MAAO,EAAG,OAAQ,CAAa,CAAC,EAExD,KAAK,WAAa,IAAI,EAAK,CAAK,EAChC,KAAK,WAAW,cAAc,MAAM,EACpC,KAAK,WAAW,YAAY,QAAQ,EACpC,KAAK,WAAW,gBAAgB,UAAU,EAC1C,KAAK,WAAW,cAAc,QAAQ,EAGtC,KAAK,WAAW,eAAe,EAAe,EAAc,CAAC,EAC7D,KAAK,aAAa,KAAK,UAAU,EAE7B,IACA,KAAK,aAAe,IAAI,EAAO,CAAE,MAAO,OAAQ,CAAC,EACjD,KAAK,aAAa,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAClD,KAAK,aAAa,UAAU,MAAM,EAClC,KAAK,aAAa,qBAAqB,EACvC,KAAK,aAAa,mBAAmB,aAAa,EAClD,KAAK,aAAa,YAAY,EAC9B,KAAK,aAAa,mBAAmB,EACrC,KAAK,aAAa,iBAAiB,EAMnC,KAAK,aAAa,wBAAwB,yDAAyD,EACnG,KAAK,aAAa,0BAA0B,0DAA0D,EACtG,KAAK,aAAa,0BAA0B,EAC5C,KAAK,aAAa,4BAA4B,EAC9C,KAAK,aAAa,iBAAiB,CAAE,MAAO,EAAY,OAAQ,CAAW,CAAC,EAC5E,KAAK,aAAa,KAAK,YAAY,EAEnC,KAAK,aAAa,GAAG,SAAU,CAAO,EAE9C,CAQA,gBAAgC,CAC5B,OAAO,KAAK,YAChB,CAQA,cAAqB,CACjB,OAAO,KAAK,UAChB,CAcA,SAAS,EAAoB,CACzB,AAEI,KAAK,eADL,KAAK,gBAAgB,KAAK,WAAW,EAClB,MAGvB,IAAM,EAAQ,IAAI,EAAM,CAAI,EAQ5B,OAPA,EAAM,iBAAiB,MAAM,EAC7B,EAAM,iBAAiB,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EAChD,KAAK,YAAc,EACnB,KAAK,aAAa,CAAK,EAEvB,KAAK,SAAS,EAEP,IACX,CAOA,YAAmB,CAOf,OANI,KAAK,cACL,KAAK,gBAAgB,KAAK,WAAW,EACrC,KAAK,YAAc,KACnB,KAAK,SAAS,GAGX,IACX,CAOA,UAAyB,CACrB,OAAO,KAAK,WAChB,CAQA,UAAiB,CACb,MAAM,SAAS,EAEf,IAAM,EAAU,KAAK,SAAS,EACxB,EAAU,KAAK,UAAU,EACzB,EAAU,EAAI,EAAa,EAC3B,EAAU,KAAK,OAAO,EAAI,GAAc,CAAC,EACzC,EAAa,KAAK,aAClB,EACC,EAAI,EAEP,EAAS,EAEb,GAAI,KAAK,YAAa,CAClB,IAAM,EAAY,KAAK,YAAY,iBAAiB,GAAK,CAAE,MAAO,GAAI,OAAQ,EAAG,EAC3E,EAAY,KAAK,IAAI,EAAG,KAAK,OAAO,EAAI,EAAU,QAAU,CAAC,CAAC,EAEpE,KAAK,YAAY,KAAK,CAAW,EACjC,KAAK,YAAY,KAAK,CAAM,EAC5B,KAAK,YAAY,SAAS,EAAU,KAAK,EACzC,KAAK,YAAY,UAAU,EAAU,MAAM,EAE3C,EAAS,EAAc,EAAU,MAAQ,EAC7C,CAGA,IAAM,EAAa,KAAK,IAAI,EAAG,EAAa,EAAS,CAAe,EAC9D,EAAa,EAAI,EAAc,EAiBrC,OAfA,KAAK,WAAW,KAAK,CAAM,EAC3B,KAAK,WAAW,KAAK,CAAW,EAChC,KAAK,WAAW,SAAS,CAAU,EACnC,KAAK,WAAW,UAAU,CAAM,EAE5B,KAAK,eACL,KAAK,aAAa,KAAK,CAAM,EAC7B,KAAK,aAAa,KAAK,CAAO,EAC9B,KAAK,aAAa,SAAS,CAAU,EACrC,KAAK,aAAa,UAAU,CAAU,EAGtC,KAAK,aAAa,SAAS,GAGxB,IACX,CACJ,EAUM,gBAAN,cAA8B,CAAU,CAEpC,SAAsC,CAAC,EAMvC,YAAY,EAA+B,EAA0C,CACjF,MAAM,EAEN,KAAK,UAAU,CACX,OAAW,OACX,UAAW,sCACf,CAAC,EACD,KAAK,mBAAmB,sBAAsB,EAC9C,KAAK,iBAAiB,CAAE,MAAO,EAAG,OAAQ,CAAc,CAAC,EAEzD,IAAK,IAAM,KAAO,EAAS,CACvB,IAAM,EAAS,IAAI,EAAO,EAAI,KAAM,EAAI,QAAU,IAAA,GAAmC,IAAA,GAAvB,CAAE,MAAO,EAAI,KAAM,CAAa,EACxF,EAAS,EAAI,QAAU,SAM7B,GAJI,EAAI,SACJ,EAAI,mBAAmB,qDAAqD,EAG5E,EAAI,QAAU,IAAA,GAAW,CACzB,IAAM,EAAQ,EAAI,SAAS,EACrB,EAAO,EAAI,KAEb,IAAU,MAAQ,GAClB,EAAM,mBAAmB,CAAI,CAErC,CAEA,EAAI,GAAG,aAAgB,EAAS,CAAM,CAAC,EACvC,KAAK,SAAS,KAAK,CAAG,EACtB,KAAK,aAAa,CAAG,CACzB,CACJ,CAOA,UAAiB,CACb,MAAM,SAAS,EAEf,IAAM,EAAY,KAAK,SAAS,EAE1B,EADY,KAAK,UACL,EAAI,GAAe,EAC/B,EAAY,KAAK,SAAS,OAAS,IAAgB,KAAK,SAAS,OAAS,GAAK,GAC/E,EAAY,KAAK,OAAO,EAAI,GAAU,CAAC,EAE7C,IAAK,IAAM,KAAO,KAAK,SACnB,EAAI,KAAK,CAAC,EACV,EAAI,KAAK,EAAY,EACrB,EAAI,SAAS,EAAY,EACzB,EAAI,UAAU,CAAI,EAElB,EAAI,SAAS,EAEb,GAAK,GAGT,OAAO,IACX,CACJ,EAgCa,EAAgB,CAEzB,QAAS,CAAE,KAAM,UAAW,OAAQ,UAAW,MAAO,eAAgB,KAAM,mCAAoC,EAEhH,GAAS,CAAE,KAAM,KAAW,OAAQ,UAAW,MAAO,eAAgB,KAAM,mCAAoC,EAEhH,OAAS,CAAE,KAAM,SAAW,OAAQ,SAAW,MAAO,QAAgB,KAAM,kCAAoC,EAEhH,MAAS,CAAE,KAAM,QAAW,OAAQ,QAAW,MAAO,QAAgB,KAAM,kCAAoC,CACpH,EAGM,EAAwC,CAC1C,CAAE,GAAG,EAAc,GAAI,QAAS,EAAK,CACzC,EAuBM,GAAN,MAAM,eAAe,CAAsC,CAEvD,UACA,kBACA,WACA,UACA,QAEA,gBAAqE,KACrE,eAA2C,KAC3C,iBACA,oBAKA,kBAA+D,KAC/D,qBAA+D,KAC/D,mBAA+D,KAC/D,sBAA+D,KAO/D,YAA+B,GAO/B,YAAY,EAAsB,CAC9B,MAAM,EAEN,KAAK,QAAa,EAElB,IAAM,EAAe,KAAK,IAAI,GAAkB,EAAO,OAAS,GAAG,EAC7D,EAAe,EAAO,SAAW,EACjC,EAAgB,KAAK,IAAI,EAAoB,KAAK,qBAAqB,CAAM,CAAC,EAC9E,EAAgB,KAAK,IACvB,EACA,EAAO,QAAW,EAAe,EAAgB,CACrD,EAEA,KAAK,YAAY,EAAS,KAAK,EAC/B,KAAK,SAAS,CAAW,EACzB,KAAK,UAAU,CAAY,EAI3B,KAAK,mBAAmB,sBAAsB,EAC9C,KAAK,gBAAgB,iCAAiC,EACtD,KAAK,UAAU,4BAA4B,EAC3C,KAAK,YAAY,QAAQ,EAEzB,KAAK,WAAW,QAAQ,EAExB,IAAM,EAAS,IAAI,GA8BnB,GA7BA,EAAO,oBAAoB,CAAC,EAC5B,KAAK,iBAAiB,CAAM,EAE5B,KAAK,UAAY,IAAI,eAAe,EAAO,UAAa,KAAK,KAAK,OAAO,EAAG,EAAO,cAAgB,EAAK,EACxG,KAAK,aAAa,KAAK,UAAW,CAAE,UAAW,EAAU,KAAM,CAAC,EAI5D,EAAO,SACP,KAAK,oBAAoB,EAAO,QAAQ,EAExC,KAAK,mBAAmB,KAAK,qBAAqB,CAAO,CAAC,EAY9D,KAAK,kBAAoB,IAAI,EAAM,CAC/B,WAAe,IACf,cAAe,IAAI,EACnB,OAAe,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CACxC,CAAC,EAEG,EAAO,iBACP,KAAK,kBAAkB,aAAa,EAAO,gBAAgB,MACxD,CACH,IAAM,EAAc,IAAI,EAAK,EAAO,SAAW,EAAE,EACjD,EAAY,cAAc,QAAQ,EAClC,EAAY,aAAa,YAAY,EACrC,EAAY,WAAW,IAAI,EAAO,GAAI,GAAI,GAAI,EAAE,CAAC,EACjD,KAAK,kBAAkB,aAAa,CAAW,CACnD,CAEA,KAAK,aAAa,KAAK,kBAAmB,CAAE,UAAW,EAAU,MAAO,CAAC,EAEzE,KAAK,WAAa,IAAI,gBAAgB,EAAU,GAAW,KAAK,KAAK,CAAM,CAAC,EAC5E,KAAK,aAAa,KAAK,WAAY,CAAE,UAAW,EAAU,KAAM,CAAC,EAEjE,KAAK,UAAY,IAAI,EAErB,KAAK,iBAAuB,GAAqB,KAAK,UAAU,CAAC,EACjE,KAAK,wBAA4B,KAAK,iBAAiB,CAC3D,CAaA,qBAA6B,EAA4D,CACrF,IAAM,EAAU,IAAI,IAAI,EAAQ,IAAI,GAAK,EAAE,QAAU,QAAQ,CAAC,EAU9D,OARI,EAAQ,OAAS,GAAK,EAAQ,IAAI,SAAS,EACpC,OAGP,EAAQ,IAAI,SAAS,GAAK,EAAQ,IAAI,QAAQ,EACvC,SAGJ,OACX,CAUA,mBAA2B,EAA4C,CACnE,GAAI,IAAY,QACZ,OAGJ,IAAM,EAAQ,IAAY,OAAS,8BAAgC,gCAC7D,EAAQ,IAAY,OAAS,8BAAgC,gCAKnE,GAHA,KAAK,UAAU,mBAAmB,CAAK,EACvC,KAAK,UAAU,aAAa,CAAC,CAAC,mBAAmB,CAAK,EAElD,IAAY,OAAQ,CACpB,KAAK,UAAU,SAAS,aAAa,EAErC,IAAM,EAAc,KAAK,UAAU,SAAS,EAExC,IAAgB,MAChB,EAAY,mBAAmB,CAAK,CAE5C,CACJ,CAUA,oBAA4B,EAAgC,CACxD,IAAM,EAAQ,4BAA4B,EAAS,MAC7C,EAAQ,4BAA4B,EAAS,UAEnD,KAAK,UAAU,mBAAmB,CAAK,EACvC,KAAK,UAAU,aAAa,CAAC,CAAC,mBAAmB,CAAK,EACtD,KAAK,UAAU,SAAS,GAAe,EAAS,EAEhD,IAAM,EAAc,KAAK,UAAU,SAAS,EAExC,IAAgB,MAChB,EAAY,mBAAmB,CAAK,CAE5C,CAeA,qBAA6B,EAA8B,CACvD,GAAI,EAAO,iBAAkB,CACzB,IAAM,EAAK,EAAO,iBAAiB,iBAAiB,EAEpD,GAAI,EACA,OAAO,EAAG,MAElB,CAEA,MAAO,IACX,CAQA,MAA8B,CAC1B,OAAO,IAAI,QAAS,GAAY,CAC5B,KAAK,gBAAkB,EACvB,KAAK,KAAK,CACd,CAAC,CACL,CAiBA,iBAAwB,CACpB,GAAI,CAAC,KAAK,WAAW,EACjB,OAAO,KAGX,IAAM,EAAgB,KAAK,IAAI,EAAoB,KAAK,qBAAqB,KAAK,OAAO,CAAC,EACpF,EAAgB,KAAK,IAAI,EAAmB,EAAe,EAAgB,CAAa,EAExF,EAAS,EAAI,OAAO,gBAAgB,EACpC,EAAS,KAAK,IAAI,EAAQ,KAAK,IAAI,EAAmB,EAAG,OAAS,GAAyB,CAAC,CAAC,EAUnG,OARI,IAAW,KAAK,UAAU,EACnB,MAGX,KAAK,UAAU,CAAM,EACrB,KAAK,eAAe,EACpB,KAAK,OAAO,EAEL,KACX,CAKA,MAAqB,CACjB,KAAK,eAAiB,EAAI,OAAO,iBAAiB,EAE9C,KAAK,QAAQ,iBAAmB,KAAK,QAAQ,cAAgB,IAC7D,KAAK,UAAU,qBAAuB,CAAE,KAAK,KAAK,OAAO,CAAG,CAAC,EAMjE,EAAa,SAAS,IAAI,EAE1B,IAAM,EAAS,EAAa,UAAU,IAAI,EAC1C,KAAK,UAAU,CAAM,EACrB,KAAK,UAAU,UAAU,EAAS,CAAC,EAEnC,IAAM,EAAa,KAAK,UAAU,WAAW,EAAI,EACjD,EAAa,MAAM,CAAU,EAE7B,IAAM,EAAW,KAAK,WAAW,EAAI,EACrC,EAAa,MAAM,CAAQ,EAI3B,EAAU,IAAI,EAEd,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,UAAU,EAOf,EAAU,oBAAsB,KAAK,gBAAgB,CAAC,EAEtD,EAAM,oBAAoB,KAAM,UAAW,KAAK,gBAAgB,EAChE,EAAM,oBAAoB,KAAM,SAAU,KAAK,mBAAmB,EAOlE,EAAU,oBAAsB,KAAK,WAAW,CAAC,CACrD,CAOA,WAA0B,CACtB,IAAM,EAAO,KAAK,WAAW,EACvB,EAAO,KAAK,UAAU,WAAW,EAElC,IAIL,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,EAAU,KAAK,EAAI,CACxC,KAAY,CAAE,QAAS,IAAK,UAAW,aAAc,EACrD,GAAY,CAAE,QAAS,IAAK,UAAW,UAAc,EACrD,WAAY,EACZ,WAAY,CAAC,UAAW,WAAW,CACvC,CAAC,EAEG,IACA,KAAK,sBAAsB,OAAO,EAClC,KAAK,qBAAuB,EAAU,KAAK,EAAM,CAC7C,KAAY,CAAE,QAAS,GAAI,EAC3B,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,EACZ,WAAY,CAAC,SAAS,CAC1B,CAAC,GAET,CAKA,QAAuB,CACnB,IAAM,EAAK,EAAI,OAAO,gBAAgB,EAChC,EAAK,KAAK,IAAI,EAAG,KAAK,OAAO,EAAG,MAAS,KAAK,SAAS,GAAM,CAAC,CAAC,EAC/D,EAAK,KAAK,IAAI,EAAG,KAAK,OAAO,EAAG,OAAS,KAAK,UAAU,GAAK,CAAC,CAAC,EAErE,KAAK,KAAK,CAAC,EACX,KAAK,KAAK,CAAC,CACf,CAYA,YAA2B,CACvB,IAAM,EAAY,KAAK,sBAAsB,EAE7C,GAAI,EAAW,CACX,EAAI,KAAK,MAAM,CAAS,EAExB,MACJ,CAEA,IAAM,EAAY,KAAK,kBAAkB,WAAW,EAC9C,EAAY,EAAY,EAAI,OAAO,iBAAiB,EAAW,CAAkB,EAAI,CAAC,EAE5F,GAAI,EAAU,OAAS,EAAG,CACtB,EAAI,KAAK,MAAM,EAAU,EAAE,EAE3B,MACJ,CAEA,IAAM,EAAU,KAAK,qBAAqB,EAE1C,GAAI,EAAS,CACT,EAAI,KAAK,MAAM,CAAO,EAEtB,MACJ,CAEA,IAAM,EAAY,KAAK,WAAW,EAC5B,EAAY,EAAK,EAAI,OAAO,iBAAiB,EAAI,CAAkB,EAAI,CAAC,EAE1E,EAAU,OAAS,GACnB,EAAI,KAAK,MAAM,EAAU,EAAE,CAEnC,CAYA,uBAA+C,CAE3C,IAAM,EADY,KAAK,QAAQ,cACF,WAAW,EAUxC,OARK,EAID,EAAI,OAAO,QAAQ,EAAS,CAAkB,EACvC,EAGJ,EAAI,OAAO,cAAc,EAAS,CAAkB,EAPhD,IAQf,CAWA,sBAA8C,CAE1C,IAAM,GADU,KAAK,QAAQ,SAAW,EAAA,CAChB,UAAU,GAAK,EAAE,OAAO,EAEhD,GAAI,EAAQ,EACR,OAAO,KAGX,IAAM,EAAQ,KAAK,WAAW,WAAW,EAGzC,OAFkB,EAAQ,EAAI,OAAO,iBAAiB,EAAO,CAAkB,EAAI,CAAC,EAAA,CAEnE,IAAU,IAC/B,CAOA,cAAiC,CAC7B,IAAM,EAAK,KAAK,WAAW,EAM3B,OAJK,EAIE,EAAI,OAAO,iBAAiB,EAAI,CAAkB,CAAC,CACrD,OAAO,GAAM,CAAC,EAAI,OAAO,aAAa,EAAI,UAAU,CAAC,EAJ/C,CAAC,CAKhB,CAQA,UAAkB,EAAwC,CAKtD,GAAI,EAAE,MAAQ,QACV,OAAO,KAAK,QAAQ,CAAC,EAGzB,GAAI,EAAE,MAAQ,MAAO,CACjB,IAAM,EAAY,KAAK,aAAa,EAEpC,GAAI,EAAU,SAAW,EACrB,MAAO,CAAE,KAAM,GAAM,QAAS,EAAK,EAGvC,IAAM,EAAQ,EAAU,GAClB,EAAQ,EAAU,EAAU,OAAS,GAE3C,GAAI,EAAE,aACE,EAAI,OAAO,iBAAiB,IAAM,EAGlC,OAFA,EAAI,KAAK,MAAM,CAAI,EAEZ,CAAE,KAAM,GAAM,QAAS,EAAK,CACvC,MAEA,GAAI,EAAI,OAAO,iBAAiB,IAAM,EAGlC,OAFA,EAAI,KAAK,MAAM,CAAK,EAEb,CAAE,KAAM,GAAM,QAAS,EAAK,CAG/C,CAGJ,CAcA,QAAgB,EAAyC,CACrD,IAAM,EAAS,EAAI,OAAO,iBAAiB,EACrC,EAAS,EAAS,EAAI,OAAO,WAAW,CAAM,CAAC,CAAC,YAAY,EAAI,KAEtE,GAAI,IAAQ,YAAc,IAAQ,SAC9B,OAGJ,IAAM,EAAS,KAAK,cAAc,EAE9B,OAAW,KAMf,OAFA,KAAK,KAAK,CAAM,EAET,CAAE,KAAM,GAAM,QAAS,EAAK,CACvC,CASA,eAA6C,CAEzC,IAAM,GADU,KAAK,QAAQ,SAAW,EAAA,CAChB,KAAK,GAAK,EAAE,OAAO,EAE3C,OAAO,EAAW,EAAQ,QAAU,SAAY,IACpD,CAWA,kBAAiC,CAC7B,KAAK,UAAU,OAAO,EACtB,KAAK,gBAAgB,EACrB,KAAK,OAAO,CAChB,CAWA,KAAK,EAA4B,CAC7B,EAAM,uBAAuB,KAAM,UAAW,KAAK,gBAAgB,EACnE,EAAM,uBAAuB,KAAM,SAAU,KAAK,mBAAmB,EAErE,IAAM,aAAuB,CACzB,KAAK,YAAc,GAEnB,KAAK,UAAU,QAAQ,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,EAAa,WAAW,IAAI,EAC5B,EAAY,IAAI,EAEZ,KAAK,iBAAmB,MACxB,EAAI,KAAK,MAAM,KAAK,cAAc,EAGtC,AAEI,KAAK,mBADL,KAAK,gBAAgB,CAAM,EACJ,KAE/B,EAEM,EAAO,KAAK,WAAW,EACvB,EAAO,KAAK,UAAU,WAAW,EAwBvC,OAtBK,GAKL,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,EAAU,KAAK,EAAI,CACzC,GAAY,CAAE,QAAS,IAAK,UAAW,aAAc,EACrD,WAAY,EACZ,WAAY,CAAC,UAAW,WAAW,EACnC,WAAY,QAChB,CAAC,EAEG,IACA,KAAK,uBAAuB,OAAO,EACnC,KAAK,sBAAwB,EAAU,KAAK,EAAM,CAC9C,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,EACZ,WAAY,CAAC,SAAS,CAC1B,CAAC,GAGE,OArBH,SAAS,EACF,KAqBf,CAQA,YAA6B,CACzB,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,KACzB,KAAK,sBAAsB,OAAO,EAClC,KAAK,qBAAuB,KAC5B,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,KAC1B,KAAK,uBAAuB,OAAO,EACnC,KAAK,sBAAwB,KAWxB,KAAK,cACN,KAAK,UAAU,QAAQ,EAEvB,EAAa,WAAW,IAAI,EAC5B,EAAY,IAAI,EAEhB,AAEI,KAAK,mBADL,KAAK,gBAAgB,OAAO,EACL,OAI/B,MAAM,WAAW,CACrB,CAOA,qBAAiC,CAC7B,OAAO,KAAK,iBAChB,CAYA,qBAAyC,CACrC,MAAO,EACX,CAcA,aAA8B,CAC1B,OAAO,KAAK,SAChB,CASA,iBAAiC,CAC7B,OAAO,KAAK,WAAW,GAAK,IAChC,CAUA,gBAAmC,CAC/B,MAAO,OACX,CAaA,cAAqB,CACb,KAAK,QAAQ,cAAgB,IAIjC,KAAK,KAAK,OAAO,CACrB,CAQA,SAAkB,CACd,OAAO,EAAa,KAAK,MAC7B,CAaA,OAAO,KAAK,EAA6C,CAGrD,OAAO,IAFY,OAAO,CAEnB,CAAA,CAAO,KAAK,CACvB,CAmBA,aAAa,QAAQ,EAAe,EAAmC,CAUnE,OAAO,MATc,OAAO,KAAK,CAC7B,QACA,UACA,QAAS,CACL,CAAE,GAAG,EAAc,OAAQ,QAAS,EAAK,EACzC,EAAc,OAClB,CACJ,CAAC,IAEiB,SACtB,CAYA,aAAqB,MAAM,EAA0B,EAAe,EAAgC,CAChG,MAAM,OAAO,KAAK,CACd,QACA,UACA,WACA,QAAS,CAAC,CAAE,GAAG,EAAc,GAAI,QAAS,EAAK,CAAC,CACpD,CAAC,CACL,CAcA,OAAO,KAAK,EAAe,EAAgC,CACvD,OAAO,OAAO,MAAM,OAAQ,EAAO,CAAO,CAC9C,CAcA,OAAO,QAAQ,EAAe,EAAgC,CAC1D,OAAO,OAAO,MAAM,UAAW,EAAO,CAAO,CACjD,CAcA,OAAO,QAAQ,EAAe,EAAgC,CAC1D,OAAO,OAAO,MAAM,UAAW,EAAO,CAAO,CACjD,CAeA,OAAO,MAAM,EAAe,EAAgC,CACxD,OAAO,OAAO,MAAM,QAAS,EAAO,CAAO,CAC/C,CACJ,EAEM,GAAiB,EAAS,EAAM,ECv3CtC,EAAM,SAAS,EAAa,EAAc,GAAsB,GAAoB,CAAK,EA6BzF,IAAa,EAAgD,CACzD,KAAS,cACT,QAAS,eACT,QAAS,uBACT,MAAS,oBACb,EAEM,GAAiD,CACnD,KAAS,cACT,QAAS,UACT,QAAS,UACT,MAAS,OACb,EAEM,GAAyB,IACzB,GAA8B,IAC9B,GAA+B,IAmBxB,EAAb,MAAa,qBAAqB,CAAU,CAIxC,eAA2D,KAC3D,kBAA2D,KAE3D,OAAwB,MAAyB,IACjD,OAAwB,OAAyB,GACjD,OAAwB,OAAyB,GACjD,OAAwB,UAAyB,GACjD,OAAwB,UAAyB,GACjD,OAAwB,WAAyB,GACjD,OAAwB,WAAyB,GACjD,OAAwB,eAAyB,EAOjD,OAAwB,QAAyB,MAEjD,OAAe,oBAAsC,CAAC,EAKtD,OAAwB,YAAsB,GAC9C,OAAe,QAAgC,CAAC,EAWhD,OAAe,WAAqB,EACpC,OAAe,WAAqB,EAEpC,MACA,aACA,OACA,aACA,aACA,cAA8D,KAC9D,mBAAqC,EACrC,gBAAqC,EACrC,YAAqC,GAIrC,yBACI,KAAK,QAAQ,EAGN,IAEX,qBAAoE,KAAK,WAAW,EACpF,kBAA+D,GAAM,aAAa,iBAAiB,EAAG,KAAK,WAAW,CAAC,EACvH,iBAA+D,GAAM,aAAa,iBAAiB,EAAG,KAAK,WAAW,CAAC,EAQvH,YAAoB,EAAiB,EAAwB,CACzD,MAAM,EAEN,KAAK,MAAe,EACpB,KAAK,aAAe,EAEpB,KAAK,YAAY,EAAS,KAAK,EAC/B,KAAK,UAAU,aAAa,OAAO,EACnC,KAAK,SAAS,aAAa,KAAK,EAChC,KAAK,UAAU,aAAa,MAAM,EAClC,KAAK,YAAY,QAAQ,EAEzB,KAAK,WAAW,QAAQ,EAKxB,IAAM,EAAY,IAAS,SAAW,IAAS,UAC/C,KAAK,QAAQ,CAAC,CAAC,QAAQ,EAAY,QAAU,QAAQ,EACrD,KAAK,QAAQ,CAAC,CAAC,QAAQ,EAAY,YAAc,QAAQ,EAEzD,IAAM,EAAY,4BAA4B,EAAK,MAC7C,EAAY,4BAA4B,EAAK,UAGnD,KAAK,mBAAmB,CAAK,EAC7B,KAAK,UAAU,CAAE,OAAQ,aAAa,GAAY,CAAC,EACnD,KAAK,UAAU,kCAAS,EACxB,KAAK,gBAAgB,iCAAiC,EAEtD,KAAK,OAAS,IAAI,EAAM,EAAY,EAAK,EACzC,KAAK,OAAO,mBAAmB,CAAS,EACxC,KAAK,OAAO,iBAAiB,CAAE,MAAO,aAAa,WAAY,OAAQ,aAAa,UAAW,CAAC,EAChG,KAAK,OAAO,iBAAiB,MAAM,EAGnC,KAAK,OAAO,QAAQ,CAAC,CAAC,UAAU,EAAI,EACpC,KAAK,aAAa,KAAK,MAAM,EAE7B,KAAK,aAAe,IAAI,EAAK,CAAO,EAIpC,KAAK,aAAa,aAAa,CAAC,EAChC,KAAK,aAAa,cAAc,QAAQ,EACxC,KAAK,aAAa,aAAa,YAAY,EAC3C,KAAK,aAAa,KAAK,YAAY,EAEnC,KAAK,aAAe,IAAI,EAAO,CAAE,MAAO,OAAQ,CAAC,EACjD,KAAK,aAAa,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAClD,KAAK,aAAa,UAAU,MAAM,EAClC,KAAK,aAAa,qBAAqB,EACvC,KAAK,aAAa,mBAAmB,aAAa,EAClD,KAAK,aAAa,YAAY,EAC9B,KAAK,aAAa,mBAAmB,EACrC,KAAK,aAAa,mBAAmB,uCAAuC,EAC5E,KAAK,aAAa,iBAAiB,CAAE,MAAO,aAAa,WAAY,OAAQ,aAAa,UAAW,CAAC,EACtG,KAAK,aAAa,QAAQ,CAAC,CAAC,SAAS,sBAAsB,EAC3D,KAAK,aAAa,KAAK,YAAY,EAMnC,KAAK,aAAa,GAAG,SAAU,KAAK,mBAAmB,EAGvD,EAAM,mBAAmB,KAAM,WAAY,KAAK,gBAAgB,EAWhE,EAAM,mBAAmB,KAAM,YAAa,KAAK,iBAAiB,EAClE,EAAM,mBAAmB,KAAM,WAAa,KAAK,gBAAgB,CACrE,CAUA,OAAO,KAAK,EAAiB,EAAyB,OAAQ,EAAmB,IAAY,CACzF,aAAa,OAAO,EAAS,CAAI,EAEjC,IAAM,EAAI,IAAI,aAAa,EAAS,CAAI,EAExC,aAAa,oBAAoB,KAAK,CAAC,EAEvC,IAAM,EAAK,EAAE,WAAW,EAAI,EAE5B,EAAE,eAAe,EAEjB,EAAa,MAAM,CAAE,EAErB,aAAa,QAAQ,EACrB,EAAE,UAAU,EAER,EAAW,IACX,EAAE,WAAW,CAAQ,EAKjB,aAAa,SAAS,GACtB,EAAE,WAAW,EAGzB,CAYA,OAAe,OAAO,EAAiB,EAA8B,CACjE,aAAa,QAAQ,KAAK,CAAE,UAAS,OAAM,UAAW,KAAK,IAAI,CAAE,CAAC,EAE9D,aAAa,QAAQ,OAAS,aAAa,aAC3C,aAAa,QAAQ,MAAM,CAEnC,CASA,OAAO,YAA4C,CAC/C,MAAO,CAAC,GAAG,aAAa,OAAO,CACnC,CAOA,WAA0B,CACtB,IAAM,EAAK,KAAK,WAAW,EAEtB,IAIL,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,EAAU,KAAK,EAAI,CACrC,KAAY,CAAE,UAAW,mBAAoB,QAAS,GAAI,EAC1D,GAAY,CAAE,UAAW,gBAAmB,QAAS,GAAI,EACzD,WAAY,GACZ,WAAY,CAAC,YAAa,SAAS,CACvC,CAAC,EACL,CAYA,OAAO,UAAiB,CACpB,IAAM,EAAY,aAAa,SAAS,EACxC,aAAa,YAAc,EACtB,GACD,aAAa,eAAe,CAEpC,CAQA,OAAO,WAAkB,CACjB,aAAa,aAAe,IAIhC,eAAa,WACR,aAAa,SAAS,GACvB,aAAa,gBAAgB,EAAI,EAEzC,CAaA,OAAe,iBAAiB,EAAe,EAA8B,CACzE,GAAI,GAAM,EAAI,OAAO,OAAO,EAAE,aAAa,GAAK,EAAI,OAAO,SAAS,EAAI,EAAI,OAAO,OAAO,EAAE,aAAa,CAAC,EACtG,OAGJ,IAAM,EAAY,aAAa,SAAS,EACxC,aAAa,YAAc,EACtB,GACD,aAAa,eAAe,CAEpC,CAcA,OAAe,iBAAiB,EAAe,EAA8B,CACrE,GAAM,EAAI,OAAO,OAAO,EAAE,aAAa,GAAK,EAAI,OAAO,SAAS,EAAI,EAAI,OAAO,OAAO,EAAE,aAAa,CAAC,GAItG,aAAa,aAAe,IAIhC,eAAa,WACR,aAAa,SAAS,GACvB,aAAa,gBAAgB,EAAK,EAE1C,CAKA,OAAe,UAAoB,CAC/B,OAAO,aAAa,WAAa,GAAK,aAAa,WAAa,CACpE,CAKA,OAAe,gBAAuB,CAClC,IAAK,IAAM,KAAK,aAAa,oBACzB,EAAE,WAAW,CAErB,CAQA,OAAe,gBAAgB,EAAyB,CACpD,IAAK,IAAM,KAAK,aAAa,oBACzB,EAAE,aAAa,CAAQ,CAE/B,CAQA,WAAmB,EAAkB,CACjC,KAAK,mBAAqB,EAC1B,KAAK,gBAAqB,KAAK,IAAI,EACnC,KAAK,cAAqB,eAAiB,KAAK,QAAQ,EAAG,CAAE,CACjE,CAMA,YAA2B,CACnB,KAAK,gBAAkB,OAI3B,aAAa,KAAK,aAAa,EAC/B,KAAK,cAAqB,KAC1B,KAAK,mBAAqB,KAAK,IAAI,EAAG,KAAK,oBAAsB,KAAK,IAAI,EAAI,KAAK,gBAAgB,EACvG,CAUA,aAAqB,EAAyB,CACtC,KAAK,aAIL,KAAK,oBAAsB,GAAK,KAAK,gBAAkB,OAIvD,IACA,KAAK,mBAAqB,KAAK,IAAI,KAAK,mBAAoB,EAAc,GAG9E,KAAK,gBAAkB,KAAK,IAAI,EAChC,KAAK,cAAkB,eAAiB,KAAK,QAAQ,EAAG,KAAK,kBAAkB,EACnF,CAOA,YAA2B,CACvB,aAAa,WAAW,KAAK,aAAc,KAAK,KAAK,CACzD,CAYA,OAAO,WAAW,EAAiB,EAA8B,CAC7D,aAAa,SAAS,EAEtB,IAAM,EAAU,IAAI,EAAK,CAAO,EAChC,EAAQ,eAAe,EAAK,EAC5B,EAAQ,cAAc,UAAU,EAChC,EAAQ,aAAa,YAAY,EACjC,EAAQ,WAAW,IAAI,EAAO,GAAI,GAAI,GAAI,EAAE,CAAC,EAE7C,IAAM,EAAS,IAAI,GAAQ,CACvB,MAAkB,GAAa,GAC/B,iBAAkB,EAClB,QAAkB,CAAC,CAAE,GAAG,EAAc,MAAO,QAAS,EAAK,CAAC,EAC5D,MAAkB,IAClB,OAAkB,GACtB,CAAC,EAGK,EAAW,EAAO,YAAY,EACpC,EAAS,mBAAmB,4BAA4B,EAAK,KAAK,EAClE,EAAS,aAAa,CAAC,CAAC,mBAAmB,4BAA4B,EAAK,SAAS,EACrF,EAAS,SAAS,EAAY,EAAK,EAEnC,IAAM,EAAa,EAAS,SAAS,EAEjC,IAAe,MACf,EAAW,mBAAmB,4BAA4B,EAAK,SAAS,EAG5E,EAAO,KAAK,CAAC,CAAC,SAAW,aAAa,UAAU,CAAC,CACrD,CAMA,SAAwB,CACpB,GAAI,KAAK,YACL,OAGJ,KAAK,YAAc,GAEf,KAAK,gBAAkB,OACvB,aAAa,KAAK,aAAa,EAC/B,KAAK,cAAgB,MAGzB,IAAM,EAAK,KAAK,WAAW,EAE3B,GAAI,CAAC,EAAI,CACL,KAAK,cAAc,EACnB,MACJ,CAEA,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,EAAU,KAAK,EAAI,CACxC,GAAY,CAAE,UAAW,mBAAoB,QAAS,GAAI,EAC1D,WAAY,GACZ,WAAY,CAAC,YAAa,SAAS,EACnC,eAAkB,KAAK,cAAc,CACzC,CAAC,CACL,CAKA,eAA8B,CAC1B,aAAa,oBAAsB,aAAa,oBAAoB,OAAO,GAAK,IAAM,IAAI,EAE1F,KAAK,cAAc,EAEnB,aAAa,QAAQ,CACzB,CAMA,OAAe,SAAgB,CAC3B,IAAM,EAAK,EAAI,OAAO,gBAAgB,EAChC,EAAK,EAAG,MAAQ,aAAa,MAAQ,aAAa,OAEpD,EAAI,EAAG,OAAS,aAAa,OAEjC,IAAK,IAAI,EAAI,aAAa,oBAAoB,OAAS,EAAG,GAAK,EAAG,IAAK,CACnE,IAAM,EAAI,aAAa,oBAAoB,GAE3C,GAAK,aAAa,OAClB,EAAE,KAAK,CAAC,EACR,EAAE,KAAK,CAAC,EACR,GAAK,aAAa,MACtB,CACJ,CAQA,UAAiB,CACb,MAAM,SAAS,EAEf,IAAM,EAAY,aAAa,MAAQ,aAAa,WAAa,EAC3D,EAAY,aAAa,UACzB,EAAY,aAAa,UAAY,EACrC,EAAY,EAAS,aAAa,WAAa,aAAa,eAC5D,EAAY,EAAS,EAAO,EAC5B,EAAY,aAAa,OAAS,aAAa,UAAY,EAoBjE,OAlBA,KAAK,OAAO,KAAK,CAAM,EACvB,KAAK,OAAO,KAAK,CAAM,EACvB,KAAK,OAAO,SAAS,aAAa,UAAU,EAC5C,KAAK,OAAO,UAAU,aAAa,UAAU,EAE7C,KAAK,aAAa,KAAK,CAAI,EAC3B,KAAK,aAAa,KAAK,aAAa,SAAS,EAC7C,KAAK,aAAa,SAAS,CAAQ,EACnC,KAAK,aAAa,UAAU,CAAS,EAErC,KAAK,aAAa,KAAK,CAAM,EAC7B,KAAK,aAAa,KAAK,CAAC,EACxB,KAAK,aAAa,SAAS,aAAa,UAAU,EAClD,KAAK,aAAa,UAAU,aAAa,UAAU,EAGnD,KAAK,aAAa,SAAS,EAEpB,IACX,CAOA,YAA6B,CACzB,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KACtB,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,KAQrB,aAAa,oBAAoB,SAAS,IAAI,IAC9C,aAAa,oBAAsB,aAAa,oBAAoB,OAAO,GAAK,IAAM,IAAI,EAC1F,aAAa,QAAQ,GAGzB,MAAM,WAAW,CACrB,CACJ,EC7oBA,EAAM,SAAS,EAAiB,EAEhC,IAAM,GAAY,IACZ,GAAY,KACZ,GAAY,MAalB,SAAgB,mBAAmB,EAAqB,EAAuB,CAC3E,IAAM,EAAQ,KAAK,IAAI,EAAG,EAAQ,CAAW,EAc7C,OAZI,EAAQ,GACD,WAGP,EAAQ,GACD,GAAG,KAAK,MAAM,EAAQ,EAAS,EAAE,OAGxC,EAAQ,GACD,GAAG,KAAK,MAAM,EAAQ,EAAO,EAAE,OAGnC,GAAG,KAAK,MAAM,EAAQ,EAAM,EAAE,MACzC,CAaA,SAAgB,mBAAsC,CAClD,IAAM,EAAU,EAAa,WAAW,EAExC,GAAI,EAAQ,SAAW,EACnB,MAAO,CAAC,CAAE,KAAM,uBAAwB,QAAS,EAAM,CAAC,EAG5D,IAAM,EAAM,KAAK,IAAI,EAIrB,OAAO,EAAQ,IAAI,IAAW,CAC1B,MAAY,EAAY,EAAO,MAG/B,WAAY,4BAA4B,EAAO,KAAK,UACpD,KAAY,EAAO,QACnB,SAAY,mBAAmB,EAAO,UAAW,CAAG,EACpD,WAAkB,EAAa,WAAW,EAAO,QAAS,EAAO,IAAI,CACzE,EAAE,CACN,CA+BA,IAAM,0BAAN,cAAwC,EAA6C,CASjF,YAAY,EAA4C,CAIpD,MAAM,IAAA,GAAW,EAAS,CACtB,MAAsB,oBACtB,UAAsB,kBACtB,qBAAsB,EAC1B,CAAC,EAED,KAAK,QAAQ,CAAC,CAAC,SAAS,sBAAsB,EAI9C,KAAK,eAAe,GAAS,SAAS,CAC1C,CACJ,EAEM,GAAoC,EAAS,yBAAyB,EChBtE,GAAgD,CAClD,KAAiB,EAAU,KAC3B,MAAiB,GACjB,KAAiB,IACjB,WAAiB,IACjB,SAAiB,OACjB,gBAAiB,yBACjB,OAAiB,4BACrB,EA8BM,OAAN,cAAqB,CAAqD,CAItE,kBAA+D,KAC/D,mBAA+D,KAC/D,qBAA+D,KAC/D,sBAA+D,KAG/D,MAAyB,GASzB,SAA4B,GAG5B,UAA2C,KAG3C,WAA+C,IAAI,EAGnD,wBAAsD,KAAK,iBAAiB,EAG5E,wBAAsD,CAClD,KAAK,MAAM,CACf,EAQA,YAAY,EAAyB,EAA2C,CAC5E,MAAM,EAAS,CAAE,GAAG,GAAuB,GAAI,GAAoB,CAAC,CAAG,CAAC,EAIxE,KAAK,YAAY,EAAS,KAAK,EAI/B,KAAK,eAAe,GAAS,SAAS,CAC1C,CAWA,aAAuB,EAA8B,CAUjD,OATA,MAAM,aAAa,CAAO,EAI1B,KAAK,QAAQ,EAAQ,MAAQ,KAAK,QAAQ,CAAC,EAC3C,KAAK,SAAS,EAAQ,OAAS,KAAK,QAAQ,CAAC,EAC7C,KAAK,cAAc,EAAQ,MAAQ,KAAK,cAAc,CAAC,EACvD,KAAK,cAAc,EAAQ,YAAc,KAAK,cAAc,CAAC,EAEtD,IACX,CAaA,QAAQ,EAAwB,CAG5B,MAFA,MAAK,SAAS,KAAO,EAEd,IACX,CAOA,SAAsB,CAClB,OAAO,KAAK,SAAS,MAAQ,KAAK,gBAAgB,IACtD,CAaA,SAAS,EAAsB,CAG3B,MAFA,MAAK,SAAS,MAAQ,EAEf,IACX,CAOA,SAAmB,CACf,OAAO,KAAK,SAAS,OAAS,KAAK,gBAAgB,KACvD,CAYA,cAAc,EAAqB,CAG/B,MAFA,MAAK,SAAS,KAAO,EAEd,IACX,CAOA,eAAwB,CACpB,OAAO,KAAK,SAAS,MAAQ,KAAK,gBAAgB,IACtD,CASA,cAAc,EAAkB,CAG5B,MAFA,MAAK,SAAS,WAAa,EAEpB,IACX,CAOA,eAAwB,CACpB,OAAO,KAAK,SAAS,YAAc,KAAK,gBAAgB,UAC5D,CAWA,MAAa,CACT,GAAI,KAAK,MACL,OAAO,KAGX,EAAa,SAAS,IAAI,EAE1B,IAAM,EAAS,EAAa,UAAU,IAAI,EAyB1C,OAxBA,KAAK,UAAU,CAAM,EAEjB,KAAK,QAAQ,GACb,KAAK,aAAa,EAAS,CAAC,EAGhC,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAE1B,EAAI,KAAK,YAAY,EAAI,OAAO,mBAAmB,EAAG,KAAK,WAAW,EAAI,CAAE,EAI5E,EAAU,IAAI,EAEd,KAAK,eAAe,EAEpB,KAAK,UAAU,EAEf,EAAM,oBAAoB,KAAM,SAAU,KAAK,mBAAmB,EAElE,KAAK,MAAQ,GACb,KAAK,KAAK,MAAM,EAET,IACX,CASA,OAAc,CACV,GAAI,CAAC,KAAK,OAAS,KAAK,SACpB,OAAO,KAGX,IAAI,EAAY,GAmBhB,OAZA,KAAK,KAAK,cAAe,CALrB,mBAA4B,CACxB,EAAY,EAChB,CAGqB,CAAU,EAE/B,EACO,MAGX,KAAK,SAAW,GAEhB,EAAM,uBAAuB,KAAM,SAAU,KAAK,mBAAmB,EAErE,KAAK,sBAAsB,EAEpB,KACX,CAOA,QAAe,CACX,OAAO,KAAK,MAAQ,KAAK,MAAM,EAAI,KAAK,KAAK,CACjD,CAOA,QAAkB,CACd,OAAO,KAAK,KAChB,CAWA,aAA+E,CAC3E,IAAM,EAAO,EAAI,OAAO,gBAAgB,EAClC,EAAO,KAAK,cAAc,EAEhC,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,MAAO,CAAE,EAAG,EAAG,MAAQ,EAAM,EAAG,EAAG,MAAO,EAAM,OAAQ,EAAG,MAAO,EAEtE,KAAK,EAAU,MACX,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAG,MAAO,OAAQ,CAAK,EAEvD,KAAK,EAAU,MACX,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,OAAS,EAAM,MAAO,EAAG,MAAO,OAAQ,CAAK,EAEtE,KAAK,EAAU,KACf,QACI,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAM,OAAQ,EAAG,MAAO,CAC5D,CACJ,CAKA,sBAAqC,CACjC,IAAM,EAAO,KAAK,YAAY,EAE9B,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,SAAS,EAAK,KAAK,EACxB,KAAK,UAAU,EAAK,MAAM,CAC9B,CASA,oBAAqC,CACjC,IAAM,EAAO,KAAK,cAAc,EAEhC,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,MAAO,cAAc,EAAK,KAE9B,KAAK,EAAU,MACX,MAAO,cAAc,CAAC,EAAK,KAE/B,KAAK,EAAU,MACX,MAAO,cAAc,EAAK,KAE9B,KAAK,EAAU,KACf,QACI,MAAO,cAAc,CAAC,EAAK,IACnC,CACJ,CAMA,iBAAgC,CAC5B,IAAM,EAAU,uCAEhB,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,KAAK,UAAU,CAAE,OAAQ,OAAQ,WAAY,CAAQ,CAAC,EAEtD,MAEJ,KAAK,EAAU,MACX,KAAK,UAAU,CAAE,OAAQ,OAAQ,aAAc,CAAQ,CAAC,EAExD,MAEJ,KAAK,EAAU,MACX,KAAK,UAAU,CAAE,OAAQ,OAAQ,UAAW,CAAQ,CAAC,EAErD,MAEJ,KAAK,EAAU,KACf,QACI,KAAK,UAAU,CAAE,OAAQ,OAAQ,YAAa,CAAQ,CAAC,EAEvD,KACR,CACJ,CAMA,WAA0B,CACtB,IAAM,EAAU,KAAK,WAAW,EAE3B,IAIL,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,EAAU,KAAK,EAAS,CAC7C,KAAY,CAAE,UAAW,KAAK,mBAAmB,CAAE,EACnD,GAAY,CAAE,UAAW,iBAAkB,EAC3C,WAAY,KAAK,cAAc,EAC/B,WAAY,CAAC,WAAW,CAC5B,CAAC,EACL,CAQA,uBAAsC,CAClC,IAAM,EAAU,KAAK,WAAW,EAE1B,aAAuB,CACzB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,EAAa,WAAW,IAAI,EAC5B,EAAY,IAAI,EAEhB,KAAK,MAAW,GAChB,KAAK,SAAW,GAChB,KAAK,KAAK,OAAO,CACrB,EAEA,GAAI,CAAC,EAAS,CACV,SAAS,EAET,MACJ,CAEA,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,EAAU,KAAK,EAAS,CAC9C,GAAY,CAAE,UAAW,KAAK,mBAAmB,CAAE,EACnD,WAAY,KAAK,cAAc,EAC/B,WAAY,CAAC,WAAW,EACxB,WAAY,QAChB,CAAC,EAED,KAAK,gBAAgB,CACzB,CAQA,aAAqB,EAAsB,CACvC,KAAK,UAAY,IAAI,EACrB,KAAK,UAAU,UAAU,CAAM,EAC/B,KAAK,UAAU,iBAAiB,KAAK,mBAAmB,EAExD,IAAM,EAAa,KAAK,UAAU,WAAW,EAAI,EACjD,EAAI,KAAK,YAAY,EAAI,OAAO,mBAAmB,EAAG,CAAU,EAEhE,KAAK,sBAAsB,OAAO,EAClC,KAAK,qBAAuB,EAAU,KAAK,EAAY,CACnD,KAAY,CAAE,QAAS,GAAI,EAC3B,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,KAAK,cAAc,EAC/B,WAAY,CAAC,SAAS,CAC1B,CAAC,CACL,CAMA,iBAAgC,CAC5B,IAAM,EAAa,KAAK,WAAW,WAAW,EAEzC,IAIL,KAAK,uBAAuB,OAAO,EACnC,KAAK,sBAAwB,EAAU,KAAK,EAAY,CACpD,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,KAAK,cAAc,EAC/B,WAAY,CAAC,SAAS,CAC1B,CAAC,EACL,CAMA,kBAAiC,CACzB,KAAK,YAAc,OACnB,KAAK,UAAU,QAAQ,EACvB,KAAK,UAAY,KAEzB,CAMA,kBAAiC,CACzB,KAAK,YAAc,MACnB,KAAK,UAAU,OAAO,EAG1B,KAAK,qBAAqB,CAC9B,CAeA,GAAG,EAAyB,EAA0B,CAGlD,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAWA,IAAI,EAAoB,EAA0B,CAG9C,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAUA,KAAe,EAAoB,GAAG,EAA0B,CAC5D,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CASA,iBAAiC,CAC7B,OAAO,KAAK,WAAW,GAAK,IAChC,CAUA,gBAAmC,CAC/B,OAAO,KAAK,QAAQ,EAAI,QAAU,QACtC,CAMA,cAAqB,CACjB,KAAK,MAAM,CACf,CAQA,aAAuB,CACnB,MAAO,EACX,CAQA,gBAAgB,EAAsB,CAClC,KAAK,UAAU,CAAM,EAEjB,KAAK,YAAc,MACnB,KAAK,UAAU,UAAU,EAAS,CAAC,CAE3C,CAOA,YAA6B,CACzB,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,KACzB,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,KAC1B,KAAK,sBAAsB,OAAO,EAClC,KAAK,qBAAuB,KAC5B,KAAK,uBAAuB,OAAO,EACnC,KAAK,sBAAwB,KAU7B,KAAK,iBAAiB,EACtB,EAAa,WAAW,IAAI,EAC5B,EAAY,IAAI,EAEhB,KAAK,MAAW,GAChB,KAAK,SAAW,GAEhB,MAAM,WAAW,CACrB,CACJ,EAEM,GAAiB,EAAS,MAAM,ECzuBhC,WAAN,cAAyB,CAA0B,CAM/C,IAAY,cAA0B,CAClC,MAAO,MAAK,gBAAkB,KAAK,gBAAgB,WAAW,CAClE,CAKA,IAAY,eAA2B,CACnC,MAAO,MAAK,iBAAmB,KAAK,gBAAgB,uBAAuB,CAC/E,CAQA,YAAY,EAA6B,CAAC,EAAG,CACzC,MAAM,EAAQ,KAAM,EAAS,CAAE,WAAY,EAAK,CAAC,EAEjD,KAAK,cAAc,IAAI,kBAAmB,mCAAmC,EAC7E,KAAK,aAAa,IAAI,kBAAmB,sCAAsC,CACnF,CAUA,aAAuB,EAAkC,CAOrD,OANA,MAAM,aAAa,CAAO,EAEtB,EAAQ,WAAa,IAAA,IACrB,KAAK,YAAY,EAAQ,QAAQ,EAG9B,IACX,CAOA,YAAsB,CAClB,OAAO,KAAK,SAAS,UAAY,EACrC,CAUA,YAAY,EAAsB,CAC9B,KAAK,SAAS,SAAW,EAEzB,KAAK,QAAQ,CAAC,CAAC,WAAW,CAAK,EAE/B,IAAM,EAAU,KAAK,WAAW,EAKhC,OAJI,GACA,EAAI,KAAK,MAAM,EAAS,CAAE,YAAa,CAAE,SAAU,CAAM,CAAE,CAAC,EAGzD,IACX,CAOA,QAAS,CACL,IAAM,EAAU,MAAM,OAAO,EAG7B,OAFA,EAAI,KAAK,MAAM,EAAS,CAAE,YAAa,CAAE,SAAU,KAAK,WAAW,CAAE,CAAE,CAAC,EAEjE,CACX,CACJ,EAEM,EAAqB,EAAS,UAAU,ECUxC,EAAoC,GAUpC,GAAuB,KAQvB,EAAgC,IAOhC,GAAsC,GAUtC,GAA+B,EAYrC,SAAS,mBAAmB,EAAmC,CAC3D,OAAQ,EAAR,CACI,KAAK,EAAU,KAAO,MAAO,OAC7B,KAAK,EAAU,MAAO,MAAO,QAC7B,KAAK,EAAU,MAAO,MAAO,QAC7B,KAAK,EAAU,KACf,QAAsB,MAAO,MACjC,CACJ,CAGA,IAAM,GAAiE,CACnE,KAAO,OACP,KAAO,OACP,MAAO,QACP,MAAO,OACX,EAQM,GAA4C,CAC9C,KAAiB,EAAU,KAC3B,YAAiB,aACjB,gBAAiB,uBACjB,OAAiB,0BACrB,EA+CM,KAAN,cAAmB,CAAuB,CAItC,mBAA4D,KAC5D,mBAA4D,KAC5D,kBAA4D,KAG5D,WAA6C,IAAI,EAGjD,SAAoD,IAAI,IAGxD,SAA4D,IAAI,IAGhE,SAA4B,GAG5B,gBAOA,mBAAqC,EAGrC,wBAAsD,KAAK,qBAAqB,EAQhF,YAAY,EAAuB,EAAyC,CACxE,MAAM,EAAS,CAAE,GAAG,GAAqB,GAAI,GAAoB,CAAC,CAAG,CAAC,EAMtE,KAAK,YAAY,EAAS,KAAK,EAC/B,KAAK,UAAU,EAAY,EAM3B,KAAK,gBAAkB,IAAI,GAAe,CACtC,UAAW,mBAAmB,KAAK,QAAQ,CAAC,EAC5C,UAAW,CAAE,aAAsB,CAAE,KAAK,gBAAgB,CAAG,CAAE,CACnE,CAAC,EAIG,GAAS,YAAc,IAAA,IACvB,KAAK,eAAe,EAAQ,SAAS,CAE7C,CAWA,aAAuB,EAA4B,CAkB/C,OAjBA,MAAM,aAAa,CAAO,EAI1B,KAAK,QAAQ,EAAQ,MAAQ,KAAK,QAAQ,CAAC,EAC3C,KAAK,eAAe,EAAQ,aAAe,KAAK,eAAe,CAAC,EAE5D,EAAQ,YAAc,IAAA,IACtB,KAAK,aAAa,EAAQ,SAAS,EAGnC,EAAQ,YAAc,IAAA,KAGtB,KAAK,SAAS,UAAY,EAAQ,WAG/B,IACX,CAaA,QAAQ,EAAsB,CAG1B,MAFA,MAAK,SAAS,KAAO,EAEd,IACX,CAOA,SAAoB,CAChB,OAAO,KAAK,SAAS,MAAQ,KAAK,gBAAgB,IACtD,CAWA,aAAa,EAAkB,CAI3B,MAHA,MAAK,SAAS,UAAY,EAC1B,KAAK,eAAe,EAEb,IACX,CAQA,cAAuB,CASnB,OARI,KAAK,YAAY,EACV,GAGP,KAAK,SAAS,YAAc,IAAA,GAIzB,KAAK,wBAAwB,EAHzB,KAAK,SAAS,SAI7B,CAWA,yBAA0C,CACtC,IAAM,EAAY,KAAK,iBAAiB,EACxC,GAAI,CAAC,EACD,OAAO,EAGX,IAAM,EAAQ,KAAK,WAAW,EAAI,EAAU,MAAQ,EAAU,OAK9D,OAJI,GAAS,GAAK,EAAY,CAAK,EACxB,EAGJ,KAAK,KAAK,CAAK,CAC1B,CAOA,gBAA+B,CACvB,KAAK,UACL,KAAK,qBAAqB,CAElC,CASA,aAAuB,CACnB,OAAO,KAAK,SAAS,WAAa,EACtC,CAWA,aAAa,EAAsB,CAkB/B,OAjBI,IAAU,KAAK,YAAY,EACpB,MAGP,IAIA,KAAK,mBAAqB,KAAK,aAAa,GAGhD,KAAK,SAAS,UAAY,EAEtB,KAAK,UACL,KAAK,0BAA0B,CAAK,EAGjC,KACX,CAOA,iBAAwB,CACpB,OAAO,KAAK,aAAa,CAAC,KAAK,YAAY,CAAC,CAChD,CAQA,yBAAwC,CACpC,IAAM,EAAY,KAAK,YAAY,EAC7B,EAAY,mBAAmB,KAAK,QAAQ,CAAC,EAEnD,KAAK,gBAAgB,aAAa,EAAY,GAAiB,GAAW,CAAO,EACjF,KAAK,qBAAqB,CAAS,EACnC,KAAK,gBAAgB,CAAS,EAC9B,KAAK,uBAAuB,CAAC,CAAS,CAC1C,CAaA,qBAA6B,EAA0B,CAC/C,GACA,KAAK,mBAAmB,iCAAiC,EACzD,KAAK,mBAAmB,iFAAiF,EACzG,KAAK,UAAU,+CAA+C,IAE9D,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,sBAAsB,EAC9C,KAAK,gBAAgB,EAE7B,CA2BA,gBAAwB,EAA0B,CAC9C,IAAI,EACA,EAEJ,GAAI,EAEA,EAAO,MACP,EAAO,UACJ,CAGH,IAAM,EAAQ,eAAe,GAAqB,KAC5C,EAAQ,GAAG,GAAqB,IAEtC,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,EAAO,EACP,EAAO,MAEP,MAEJ,KAAK,EAAU,MACX,EAAO,MACP,EAAO,EAEP,MAEJ,KAAK,EAAU,MACX,EAAO,MACP,EAAO,EAEP,MAEJ,KAAK,EAAU,KACf,QACI,EAAO,EACP,EAAO,MAEP,KACR,CACJ,CAIA,IAAM,EAAQ,EAAY,OAAqC,KAE/D,IAAI,EAAU,CACV,MAAQ,YACR,KAAQ,KAAK,gBAAgB,MAAM,EACnC,OAAQ,CAAE,OAAM,MAAK,QAAO,OAAQ,GAAI,CAC5C,CAAC,CACL,CAeA,0BAAkC,EAA0B,CACxD,IAAM,EAAU,mBAAmB,KAAK,QAAQ,CAAC,EAEjD,KAAK,gBAAgB,aAAa,EAAY,GAAiB,GAAW,CAAO,EACjF,KAAK,qBAAqB,CAAS,EACnC,KAAK,gBAAgB,CAAS,EAE9B,IAAM,EAAU,KAAK,WAAW,EAK1B,EAAgB,KAAK,WAAW,EAAI,KAAK,SAAS,EAAI,KAAK,UAAU,EACrE,EAAgB,EAAY,GAA8B,KAAK,mBAEhE,IAMD,KAAK,uBAAuB,EAAI,EAChC,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GAGxB,IAAM,aAAuB,CAErB,GACA,KAAK,uBAAuB,EAAK,EAGrC,KAAK,qBAAqB,EAErB,GACD,KAAK,eAAe,CAE5B,EAEA,GAAI,CAAC,EAAS,CACV,SAAS,EAET,MACJ,CAEA,IAAM,EAAQ,KAAK,cAAc,EAAe,CAAW,EAE3D,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,EAAU,KAAK,EAAS,CAC9C,KAAY,EAAM,KAClB,GAAY,EAAM,GAClB,WAAY,EACZ,WAAY,EAAM,WAClB,WAAY,QAChB,CAAC,CACL,CAYA,cAAsB,EAAuB,EAI3C,CACE,IAAM,EAAO,EAAI,OAAO,gBAAgB,EAClC,EAAqC,CAAC,EACtC,EAAqC,CAAC,EAoB5C,OAlBI,KAAK,WAAW,GAChB,EAAK,MAAQ,EAAgB,KAC7B,EAAG,MAAU,EAAc,KAEvB,KAAK,QAAQ,IAAM,EAAU,OAC7B,EAAK,KAAQ,EAAG,MAAQ,EAAiB,KACzC,EAAG,KAAU,EAAG,MAAQ,EAAe,QAG3C,EAAK,OAAS,EAAgB,KAC9B,EAAG,OAAW,EAAc,KAExB,KAAK,QAAQ,IAAM,EAAU,QAC7B,EAAK,IAAO,EAAG,OAAS,EAAiB,KACzC,EAAG,IAAS,EAAG,OAAS,EAAe,OAIxC,CAAE,OAAM,KAAI,WAAY,OAAO,KAAK,CAAE,CAAE,CACnD,CAOA,uBAA+B,EAA0B,CACrD,IAAK,IAAM,KAAO,KAAK,SAAS,OAAO,EACnC,EAAI,OAAO,aAAa,CAAS,EAGrC,IAAK,IAAM,KAAO,KAAK,SAAS,OAAO,EACnC,EAAI,QAAQ,aAAa,CAAS,CAE1C,CAWA,eAAe,EAAoC,CAY/C,MAXA,MAAK,SAAS,YAAc,EAOxB,KAAK,WAAa,IAAA,IAClB,KAAK,iBAAiB,EAGnB,IACX,CAOA,gBAAkC,CAC9B,OAAO,KAAK,SAAS,aAAe,KAAK,gBAAgB,WAC7D,CAWA,OAAc,CACV,GAAI,KAAK,SACL,OAAO,KAGX,KAAK,iBAAiB,KAAK,WAAW,EAAI,IAAI,GAAS,IAAI,EAAM,EACjE,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAE1B,IAAM,EAAU,KAAK,WAAW,EAAI,EACpC,EAAI,KAAK,YAAY,EAAI,OAAO,mBAAmB,EAAG,CAAO,EAK7D,IAAM,EAAU,KAAK,gBAAgB,WAAW,EAAI,EAgBpD,OAfI,GAAW,EAAI,OAAO,iBAAiB,CAAO,IAAM,GACpD,EAAI,KAAK,YAAY,EAAS,CAAO,EAKzC,KAAK,wBAAwB,EAE7B,KAAK,eAAe,EAEpB,EAAM,oBAAoB,KAAM,SAAU,KAAK,mBAAmB,EAElE,KAAK,SAAW,GAChB,KAAK,UAAU,EAER,IACX,CASA,SAAgB,CACZ,GAAI,CAAC,KAAK,SACN,OAAO,KAGX,EAAM,uBAAuB,KAAM,SAAU,KAAK,mBAAmB,EAErE,KAAK,SAAW,GAIhB,IAAM,EAAU,KAAK,WAAW,EAC1B,WAAqB,CAAE,KAAK,cAAc,CAAG,EAgBnD,OAdK,GAML,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,EAAU,KAAK,EAAS,CAC9C,GAAY,CAAE,UAAW,KAAK,mBAAmB,CAAE,EACnD,WAAY,EACZ,WAAY,CAAC,WAAW,EACxB,WAAY,MAChB,CAAC,EAEM,OAbH,OAAO,EAEA,KAYf,CAKA,WAA0B,CACtB,IAAM,EAAU,KAAK,WAAW,EAE3B,IAIL,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,EAAU,KAAK,EAAS,CAC7C,KAAY,CAAE,UAAW,KAAK,mBAAmB,CAAE,EACnD,GAAY,CAAE,UAAW,iBAAkB,EAC3C,WAAY,EACZ,WAAY,CAAC,WAAW,CAC5B,CAAC,EACL,CASA,oBAAqC,CACjC,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,MAAO,mBAEX,KAAK,EAAU,MACX,MAAO,oBAEX,KAAK,EAAU,MACX,MAAO,mBAEX,KAAK,EAAU,KACf,QACI,MAAO,mBACf,CACJ,CAiBA,eAAe,EAAgB,EAA8B,CAAC,EAAS,CACnE,GAAI,KAAK,SAAS,IAAI,CAAM,EACxB,OAAO,KAGX,IAAM,EAAS,IAAI,EAAW,CAAE,KAAM,EAAI,KAAM,MAAO,EAAI,MAAO,SAAU,EAAO,OAAO,CAAE,CAAC,EAEvF,WAAqC,CAAE,EAAO,YAAY,EAAI,CAAG,EACjE,YAAqC,CAAE,EAAO,YAAY,EAAK,CAAG,EAClE,aAAsC,CAAE,EAAO,OAAO,CAAG,EAmB/D,OAjBA,EAAO,GAAG,OAAQ,MAAM,EACxB,EAAO,GAAG,QAAS,OAAO,EAC1B,EAAO,GAAG,SAAU,QAAQ,EAExB,EAAI,YAAc,IAClB,EAAO,QAAQ,KAAK,QAAQ,CAAe,EAG/C,KAAK,uBAAuB,CAAM,EAClC,EAAO,aAAa,CAAC,KAAK,YAAY,CAAC,EACvC,KAAK,aAAa,CAAM,EACxB,KAAK,SAAS,IAAI,EAAQ,CAAE,SAAQ,OAAQ,QAAS,QAAS,CAAC,EAE/D,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,KAAK,WAAY,CAAM,EAErB,IACX,CAWA,iBAAiB,EAAsB,CACnC,IAAM,EAAM,KAAK,SAAS,IAAI,CAAM,EAepC,OAdK,GAIL,EAAO,IAAI,OAAQ,EAAI,MAAM,EAC7B,EAAO,IAAI,QAAS,EAAI,OAAO,EAC/B,EAAI,OAAO,IAAI,SAAU,EAAI,QAAQ,EAErC,KAAK,gBAAgB,EAAI,MAAM,EAC/B,KAAK,SAAS,OAAO,CAAM,EAE3B,KAAK,eAAe,EACpB,KAAK,KAAK,aAAc,CAAM,EAEvB,MAbI,IAcf,CAcA,eAAe,EAA8B,CACzC,GAAI,KAAK,SAAS,IAAI,CAAM,EACxB,OAAO,KAGX,IAAM,eAAuC,CAAE,KAAK,iBAAiB,CAAM,CAAG,EACxE,cAAuC,CAAE,KAAK,mBAAmB,CAAM,CAAG,EAC1E,YAAuC,CAAE,KAAK,iBAAiB,CAAM,CAAG,EACxE,aAAwC,CAAE,EAAO,QAAQ,CAAG,EAelE,OAbA,EAAO,GAAG,WAAY,UAAU,EAChC,EAAO,GAAG,UAAW,SAAS,EAC9B,EAAO,GAAG,QAAS,OAAO,EAE1B,KAAK,SAAS,IAAI,EAAQ,CAAE,OAAQ,KAAM,WAAY,UAAW,QAAS,QAAS,CAAC,EAGhF,EAAO,YAAY,GACnB,KAAK,iBAAiB,CAAM,EAGhC,KAAK,KAAK,WAAY,CAAM,EAErB,IACX,CAUA,iBAAiB,EAA8B,CAC3C,IAAM,EAAM,KAAK,SAAS,IAAI,CAAM,EAcpC,OAbK,GAIL,EAAO,IAAI,WAAY,EAAI,UAAU,EACrC,EAAO,IAAI,UAAW,EAAI,SAAS,EACnC,EAAO,IAAI,QAAS,EAAI,OAAO,EAE/B,KAAK,mBAAmB,CAAM,EAC9B,KAAK,SAAS,OAAO,CAAM,EAE3B,KAAK,KAAK,aAAc,CAAM,EAEvB,MAZI,IAaf,CASA,iBAAyB,EAA8B,CACnD,IAAM,EAAM,KAAK,SAAS,IAAI,CAAM,EACpC,GAAI,CAAC,GAAO,EAAI,SAAW,KACvB,OAGJ,IAAM,EAAS,IAAI,EAAW,CAAE,KAAM,EAAO,SAAS,EAAG,MAAO,EAAO,SAAS,EAAG,SAAU,EAAK,CAAC,EACnG,EAAO,GAAG,SAAU,EAAI,QAAQ,EAEhC,EAAI,OAAS,EACb,KAAK,uBAAuB,CAAM,EAClC,EAAO,aAAa,CAAC,KAAK,YAAY,CAAC,EACvC,KAAK,aAAa,CAAM,EAExB,KAAK,eAAe,EACpB,KAAK,eAAe,CACxB,CAOA,mBAA2B,EAA8B,CACrD,IAAM,EAAM,KAAK,SAAS,IAAI,CAAM,EAChC,CAAC,GAAO,EAAI,SAAW,OAI3B,EAAI,OAAO,IAAI,SAAU,EAAI,QAAQ,EACrC,KAAK,gBAAgB,EAAI,MAAM,EAC/B,EAAI,OAAS,KAEb,KAAK,eAAe,EACxB,CAQA,kBAAiC,CAC7B,IAAK,IAAM,KAAO,KAAK,SAAS,OAAO,EACnC,KAAK,uBAAuB,EAAI,MAAM,EAG1C,IAAK,IAAM,KAAO,KAAK,SAAS,OAAO,EAC/B,EAAI,SAAW,MACf,KAAK,uBAAuB,EAAI,MAAM,EAI9C,KAAK,eAAe,EACpB,KAAK,eAAe,CACxB,CAUA,uBAA+B,EAA0B,CACrD,IAAM,EAAc,KAAK,eAAe,EAIlC,EAAc,IAAgB,cAAiB,cAChC,IAAgB,eAAiB,cACjC,KAEjB,KAAK,WAAW,GAAK,IAAgB,KACrC,EAAO,eAAe,CAAW,EAEjC,EAAO,iBAAiB,CAEhC,CAiBA,qBAAyC,CACrC,MAAO,EACX,CAQA,YAA8B,CAC1B,IAAM,EAAO,KAAK,QAAQ,EAE1B,OAAO,IAAS,EAAU,MAAQ,IAAS,EAAU,IACzD,CAeA,qBAAqB,EAAgC,CACjD,IAAM,EAAa,KAAK,WAAW,EAC7B,QAAc,GAAyB,EAAW,EAAE,KAAK,EAAI,EAAE,KAAK,EACpE,WAAc,GAAyB,EAAW,EAAE,UAAU,EAAI,EAAE,SAAS,EAI7E,EAAS,KAAK,SAAS,IAAI,CAAM,CAAC,EAAE,QAAU,KACpD,GAAI,IAAW,KACX,OAAO,QAAQ,CAAM,EAKzB,IAAM,EAAU,KAAK,cAAc,EACnC,GAAI,EAAQ,SAAW,EACnB,MAAO,GAGX,IAAM,EAAO,EAAQ,EAAQ,OAAS,GAEtC,OAAO,QAAQ,CAAI,EAAI,WAAW,CAAI,EAAI,KAAK,cAAc,CACjE,CAiBA,qBAAqB,EAAgC,CACjD,IAAM,EAAa,KAAK,WAAW,EAC7B,WAAc,GAAyB,EAAW,EAAE,UAAU,EAAI,EAAE,SAAS,EAE7E,EAAS,KAAK,SAAS,IAAI,CAAM,CAAC,EAAE,QAAU,KACpD,GAAI,IAAW,KACX,OAAO,WAAW,CAAM,EAG5B,IAAM,EAAU,KAAK,cAAc,EAKnC,OAJI,EAAQ,SAAW,EACZ,EAGJ,WAAW,EAAQ,EAAQ,OAAS,EAAE,CACjD,CAQA,eAAgC,CAC5B,IAAM,EAAK,KAAK,iBAAiB,EAEjC,OAAO,aAAc,GAAY,EAAG,oBAAoB,EAAI,CAChE,CASA,aAA+E,CAC3E,IAAM,EAAY,EAAI,OAAO,gBAAgB,EACvC,EAAY,KAAK,aAAa,EAEpC,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,MAAO,CAAE,EAAG,EAAG,MAAQ,EAAW,EAAG,EAAG,MAAO,EAAW,OAAQ,EAAG,MAAO,EAEhF,KAAK,EAAU,MACX,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAG,MAAO,OAAQ,CAAU,EAE5D,KAAK,EAAU,MACX,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,OAAS,EAAW,MAAO,EAAG,MAAO,OAAQ,CAAU,EAEhF,KAAK,EAAU,KACf,QACI,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,MAAO,EAAW,OAAQ,EAAG,MAAO,CACjE,CACJ,CAKA,sBAAqC,CACjC,IAAM,EAAO,KAAK,YAAY,EAE9B,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,KAAK,EAAK,CAAC,EAChB,KAAK,SAAS,EAAK,KAAK,EACxB,KAAK,UAAU,EAAK,MAAM,CAC9B,CAYA,iBAAgC,CAC5B,IAAM,EAAc,wBACd,EAAc,qCAEpB,OAAQ,KAAK,QAAQ,EAArB,CACI,KAAK,EAAU,KACX,KAAK,UAAU,CAAE,OAAQ,EAAa,WAAY,CAAQ,CAAC,EAE3D,MAEJ,KAAK,EAAU,MACX,KAAK,UAAU,CAAE,OAAQ,EAAa,aAAc,CAAQ,CAAC,EAE7D,MAEJ,KAAK,EAAU,MACX,KAAK,UAAU,CAAE,OAAQ,EAAa,UAAW,CAAQ,CAAC,EAE1D,MAEJ,KAAK,EAAU,KACf,QACI,KAAK,UAAU,CAAE,OAAQ,EAAa,YAAa,CAAQ,CAAC,EAE5D,KACR,CACJ,CAaA,GAAG,EAAkB,EAA2D,CAG5E,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAWA,IAAI,EAAkB,EAA2D,CAG7E,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAQA,KAAe,EAAkB,EAAuC,CACpE,KAAK,WAAW,KAAK,EAAO,CAAM,CACtC,CAOA,YAA6B,CACzB,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,KAC1B,KAAK,oBAAoB,OAAO,EAChC,KAAK,mBAAqB,KAC1B,KAAK,mBAAmB,OAAO,EAC/B,KAAK,kBAAoB,KAEzB,MAAM,WAAW,CACrB,CACJ,EAEM,GAAe,EAAS,IAAI,ECxoC5B,KAAN,cAAmB,CAAuB,CAGtC,QAAsD,IAAI,IAG1D,QAAkD,IAAI,IAItD,eAAyD,IAAI,IAE7D,QAAwD,IAAI,IAE5D,gBAAmC,GAOnC,WAAkD,IAAI,EAGtD,gBAAyC,KAIzC,WAA8D,IAAI,IAGlE,aAAkD,IAAI,IAKtD,kBAA8C,KAG9C,iBAAgD,IAAI,IAIpD,kBAA6C,IAAI,EAQjD,OAA0B,GAM1B,iBAAoC,GAIpC,iBAA+C,CAC3C,KAAK,cAAc,CACvB,EAOA,cAAuD,GAA8B,CACjF,KAAK,aAAa,CACtB,EASA,YAAY,EAAuB,EAAyC,CACxE,MAAM,EAAS,CAAE,cAAe,IAAI,EAAO,GAAG,CAAiB,CAAC,EAEhE,IAAM,EAAO,GAAS,OAAS,KAAK,cAAc,EAAQ,MAAM,EAAI,KAAK,aAAa,EAEtF,KAAK,aAAa,CAAI,EACtB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAMzB,KAAK,eAAe,GAAS,SAAS,CAC1C,CAYA,aAAuB,EAA4B,CAO/C,OANA,MAAM,aAAa,CAAO,EAEtB,EAAQ,eAAiB,IAAA,IACzB,KAAK,gBAAgB,EAAQ,YAAY,EAGtC,IACX,CAkBA,gBAAgB,EAAmC,CAM/C,IAAM,EAAU,KAAK,kBAAoB,KAAK,gBAAgB,EAY9D,OAVI,GACA,KAAK,eAAe,EAGxB,KAAK,SAAS,aAAe,GAAa,IAAA,GAEtC,GACA,KAAK,eAAe,EAGjB,IACX,CAOA,iBAAoC,CAChC,OAAO,KAAK,SAAS,cAAgB,IACzC,CAYA,SAAmB,CACf,OAAO,KAAK,QAAQ,OAAS,CACjC,CAWA,qBAAoC,CAChC,EAAY,eAAe,KAAM,CAC7B,QAAU,GACN,EAAO,SAAS,UAAe,IAAQ,KAAK,cAAc,CAAC,CAAC,SAAW,EAG3E,qBAAsB,GACtB,gBACI,KAAK,kBAAkB,SAAS,IAAI,EACpC,KAAK,kBAAkB,cAAc,EAE9B,MAEX,gBAAyB,CACrB,KAAK,kBAAkB,OAAO,CAClC,EACA,OAAS,GAAkC,CACvC,KAAK,kBAAkB,OAAO,EAE9B,IAAM,EAAQ,GAAgB,IAAI,EAAO,SAAS,WAAwB,EAE1E,GAAI,CAAC,EACD,OAGJ,IAAM,EAAS,KAAK,aAAa,EAEjC,KAAK,aAAa,CAAM,EACxB,EAAO,cAAc,CAAK,EAC1B,KAAK,cAAc,CACvB,CACJ,CAAC,CACL,CAUA,SAAS,EAA2B,CAChC,KAAK,QAAQ,IAAI,EAAK,GAAI,CAAI,EAE9B,IAAM,EAAU,KAAK,aAAa,EAAK,EAAE,EAEzC,GAAI,EAAS,CACT,IAAM,EAAS,KAAK,gBAAgB,EAEpC,KAAK,kBAAoB,EACzB,EAAO,cAAc,EAAS,IAAA,GAAW,KAAK,gBAAgB,CAAI,CAAC,EAKnE,EAAQ,iBAAiB,CAAC,CAAS,iBAAiB,CAAO,EAO3D,KAAK,aAAa,IAAI,EAAK,GAAI,CAAM,EAErC,KAAK,cAAc,CACvB,CAEA,OAAO,IACX,CAkBA,aAAa,EAA2B,CACpC,KAAK,QAAQ,IAAI,EAAK,GAAI,CAAI,EAC9B,KAAK,eAAe,IAChB,EAAK,GACL,OAAO,EAAK,SAAY,WAAa,EAAK,YAAgB,EAAK,OACnE,EAEA,IAAM,EAAQ,KAAK,aAAa,EAAK,EAAE,EAEvC,GAAI,EAAO,CACP,IAAM,EAAS,KAAK,gBAAgB,EAEpC,KAAK,kBAAoB,EACzB,EAAO,cAAc,EAAO,IAAA,GAAW,KAAK,gBAAgB,CAAI,CAAC,EAKjE,EAAQ,iBAAiB,CAAC,CAAS,iBAAiB,CAAK,EACzD,KAAK,aAAa,IAAI,EAAK,GAAI,CAAM,EAErC,KAAK,cAAc,CACvB,CAEA,OAAO,IACX,CASA,eAA2B,CACvB,OAAO,KAAK,cAAc,CAAC,CAAC,EAChC,CAQA,gBAA8B,CAC1B,OAAO,GAAgB,KAAK,cAAc,CAAC,CAC/C,CAkBA,eAAe,EAA0B,CAKrC,OAJA,KAAK,aAAa,MAAM,EACxB,GAAc,KAAK,cAAc,EAAG,EAAQ,GAAe,KAAK,aAAa,CAAE,CAAC,EAChF,KAAK,cAAc,EAEZ,IACX,CAoBA,aAAqB,EAA8B,CAC/C,IAAM,EAAO,KAAK,QAAQ,IAAI,CAAE,EAEhC,GAAI,CAAC,EACD,OAAO,KAGX,IAAI,EAAQ,KAAK,QAAQ,IAAI,CAAE,EAE/B,GAAI,CAAC,EAAO,CACR,IAAM,EAAU,KAAK,eAAe,IAAI,CAAE,EAE1C,GAAI,EAAS,CAQT,IAAM,EAAM,IAAI,EAEhB,EAAI,cAAc,EAAK,EACvB,EAAQ,IAAI,EAAU,CAAE,GAAI,EAAK,GAAI,KAAM,EAAK,MAAO,cAAe,CAAI,CAAC,EAQ3E,IAAM,SAAsC,GAAyB,CACjE,KAAK,UAAU,EAAK,GAAI,CAAK,CACjC,EAEA,EAAI,GAAG,YAAa,QAAQ,EAC5B,EAAI,WAAW,EAAS,EAAK,OAAS,EAAK,EAAE,CACjD,KAGI,GAAQ,IAAI,EAAU,CAAE,GAAI,EAAK,GAAI,KAAM,EAAK,MAAO,cAAe,IAAI,CAAM,CAAC,EAKjF,EAAM,aAAa,EAAK,OAAO,EAGnC,KAAK,QAAQ,IAAI,EAAI,CAAK,CAC9B,CAEA,OAAO,CACX,CAWA,gBAAwB,EAAwC,CAC5D,IAAM,EAAc,IAAI,EAaxB,MAVA,GAAY,UAAY,EAAK,WAAa,GAEtC,EAAK,QACL,EAAY,MAAQ,EAAK,OAGzB,EAAK,UACL,EAAY,QAAU,EAAK,SAGxB,CACX,CAUA,iBAAqC,CACjC,IAAM,EAAO,KAAK,cAAc,EAI1B,EAAO,KAAK,kBASlB,OARI,GAAQ,KAAK,MAAM,CAAI,GAAK,KAAK,eAAe,EAAM,CAAI,EACnD,EAGP,KAAK,MAAM,CAAI,EACR,EAGJ,KAAK,eAAe,CAAI,GAAK,KAAK,cAAc,CAC3D,CAaA,eAAuB,EAAmB,EAA4B,CAClE,GAAI,IAAW,EACX,MAAO,GAGX,IAAK,IAAM,KAAS,EAAO,cAAc,EACrC,GAAI,KAAK,kBAAkB,CAAK,GAAK,KAAK,eAAe,EAAO,CAAM,EAClE,MAAO,GAIf,MAAO,EACX,CASA,eAAuB,EAAqC,CACxD,GAAI,KAAK,MAAM,CAAM,EACjB,OAAO,EAGX,IAAK,IAAM,KAAS,EAAO,cAAc,EACrC,GAAI,KAAK,kBAAkB,CAAK,EAAG,CAC/B,IAAM,EAAQ,KAAK,eAAe,CAAK,EAEvC,GAAI,EACA,OAAO,CAEf,CAGJ,OAAO,IACX,CASA,eAAmC,CAC/B,IAAM,EAAU,KAAK,aAAa,EAC5B,EAAU,KAAK,cAAc,EAKnC,OAHA,KAAK,aAAa,CAAM,EACxB,EAAO,cAAc,CAAO,EAErB,CACX,CAOA,cAAkC,CAC9B,OAAO,IAAI,EAAU,CAAE,cAAe,IAAI,EAAI,CAAE,YAAa,GAAM,QAAS,EAAK,CAAC,CAAE,CAAC,CACzF,CAWA,cAAsB,EAAiC,CACnD,GAAI,UAAW,EAAM,CACjB,IAAM,EAAS,IAAI,EAAU,CAAE,cAAe,IAAI,GAAM,CAAE,YAAa,EAAK,KAAM,CAAC,CAAE,CAAC,EAEtF,IAAK,IAAM,KAAS,EAAK,SACrB,EAAO,aAAa,KAAK,cAAc,CAAK,CAAC,EAGjD,OAAO,CACX,CAMA,MAJI,SAAU,EACH,KAAK,YAAY,EAAK,IAAI,EAG9B,KAAK,YAAY,CAAC,CAAI,CAAC,CAClC,CAUA,cAAsB,EAAiC,CAKnD,MAJI,UAAW,GAAQ,SAAU,EACtB,KAAK,cAAc,CAAI,EAG3B,KAAK,YAAY,CAAC,CAAI,CAAC,CAClC,CAUA,YAAoB,EAAmC,CACnD,IAAM,EAAS,KAAK,aAAa,EAEjC,IAAK,IAAM,KAAQ,EAAO,CACtB,KAAK,QAAQ,IAAI,EAAK,GAAI,CAAI,EAE9B,IAAM,EAAU,KAAK,aAAa,EAAK,EAAE,EAErC,IACA,EAAO,aAAa,EAAS,KAAK,gBAAgB,CAAI,CAAC,EAKvD,KAAK,WAAW,IAAI,EAAK,GAAI,IAAI,EACjC,KAAK,aAAa,IAAI,EAAK,GAAI,CAAM,EAE7C,CAEA,OAAO,CACX,CAMA,eAA8B,CACtB,KAAK,kBAIT,KAAK,gBAAkB,GAEvB,EAAI,KAAK,0BAA4B,CACjC,KAAK,gBAAkB,GACvB,KAAK,SAAS,CAClB,CAAC,EACL,CAOA,UAAyB,CACrB,IAAM,EAAO,KAAK,cAAc,EAEhC,GAAI,CAAC,EACD,OAGJ,IAAM,EAAe,KAAK,kBAAkB,CAAC,CACxC,IAAI,GAAO,KAAK,WAAW,CAAG,CAAC,CAAC,CAChC,OAAQ,GAAgC,IAAW,IAAI,EAE5D,KAAK,WAAW,CAAI,EAEpB,IAAK,IAAM,KAAU,EACjB,KAAK,WAAW,CAAM,EAG1B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,CAAI,EACxB,KAAK,iBAAiB,EAAM,CAAY,EACxC,KAAK,oBAAoB,CAC7B,CAiBA,qBAAoC,CAC5B,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAEpB,KAAK,eAAe,EAGxB,IAAM,EAAQ,KAAK,QAAQ,EAKvB,CAAC,KAAK,mBACN,KAAK,iBAAmB,GAEpB,IAAU,KAAK,SAKnB,IAAU,KAAK,SAInB,KAAK,OAAS,EACd,KAAK,KAAK,cAAe,CAAE,OAAM,CAAC,EACtC,CAUA,iBAAmC,CAC/B,IAAM,EAAS,KAAK,cAAc,EAElC,GAAI,CAAC,EACD,MAAO,GAGX,IAAM,EAAc,KAAK,gBAAgB,EAEzC,OAAO,EAAO,cAAc,CAAC,CAAC,MAAM,GAAS,IAAU,CAAW,CACtE,CASA,gBAA+B,CAC3B,IAAM,EAAc,KAAK,cAAc,EACjC,EAAc,KAAK,gBAAgB,EAEpC,IAID,GACA,KAAK,QAAQ,CAAC,EAAE,cAAc,EAAI,EAE9B,EAAY,mBAAmB,IAAM,GACrC,EAAO,cAAc,EAAa,IAAA,GAAW,KAAK,uBAAuB,CAAC,GAG9E,KAAK,QAAQ,CAAC,EAAE,cAAc,EAAK,EAE3C,CAOA,gBAA+B,CAC3B,IAAM,EAAc,KAAK,gBAAgB,EACnC,EAAc,GAAa,mBAAmB,EAC9C,EAAc,GAAQ,iBAAiB,EAUzC,IACe,aAAmB,GAAO,EAAQ,SAAS,CAAW,GAGjE,GAAQ,gBAAgB,CAAW,GAI3C,KAAK,QAAQ,CAAC,EAAE,cAAc,EAAI,CACtC,CAUA,SAA8B,CAC1B,IAAM,EAAU,KAAK,cAAc,CAAC,EAAE,iBAAiB,EAEvD,OAAO,aAAmB,EAAM,EAAU,IAC9C,CAUA,wBAAoD,CAChD,IAAM,EAAc,IAAI,EAKxB,MAHA,GAAY,UAAY,GACxB,EAAY,UAAY,GAEjB,CACX,CAYA,uBAAsC,CAClC,IAAK,IAAM,KAAO,KAAK,0BAA0B,EAAG,CAChD,GAAI,KAAK,iBAAiB,IAAI,CAAG,EAC7B,SAGJ,IAAM,oBAA0C,CAAE,KAAK,iBAAiB,CAAG,CAAG,EACxE,iBAA0C,CAAE,KAAK,cAAc,CAAG,CAAG,EAK3E,GAHA,EAAI,GAAG,WAAY,eAAe,EAClC,EAAI,GAAG,QAAY,YAAY,EAE3B,aAAe,EAAW,CAC1B,IAAM,EAAM,EAAI,iBAAiB,EAEjC,EAAI,GAAG,WAAY,KAAK,cAAc,EACtC,EAAI,GAAG,WAAa,KAAK,aAAa,EACtC,EAAI,GAAG,SAAW,KAAK,eAAe,EACtC,EAAI,GAAG,OAAW,KAAK,aAAa,CACxC,CAEA,KAAK,iBAAiB,IAAI,CAAG,CACjC,CAEA,KAAK,8BAA8B,CACvC,CAOA,+BAA8C,CAC1C,IAAM,EAAO,IAAI,IAAoB,EAAe,eAAe,CAAC,EAEpE,IAAK,IAAM,KAAO,KAAK,iBACd,EAAK,IAAI,CAAG,GACb,KAAK,iBAAiB,OAAO,CAAG,CAG5C,CAUA,2BAAsD,CAClD,IAAM,EAAS,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,EAExC,OAAO,EAAe,eAAe,CAAC,CAAC,OAAO,GAC1C,CAAC,KAAK,eAAe,EAAK,IAAI,GAC9B,EAAO,KAAK,GAAS,KAAK,eAAe,EAAK,CAAK,CAAC,CAAC,CAC7D,CAoBA,eAAuB,EAAuB,CAC1C,IAAK,GAAM,CAAC,EAAI,KAAU,KAAK,QAAS,CACpC,GAAI,CAAC,KAAK,QAAQ,IAAI,CAAE,EACpB,SAGJ,IAAM,EAAa,KAAK,aAAa,EAAO,CAAI,EAC1C,EAAa,KAAK,WAAW,IAAI,CAAE,EACnC,EAAa,KAAK,WAAW,IAAI,CAAE,GAAK,KACxC,EAAa,KAAK,eAAe,CAAK,EACtC,EAAa,KAAK,aAAa,IAAI,CAAE,GAAK,KAE3C,EAEM,IAAS,EAGT,GAAU,GAAc,IAAW,GAI1C,KAAK,KAAK,OAAQ,CAAE,KAAI,QAAS,EAAO,OAAQ,CAAK,CAAC,GANtD,KAAK,KAAK,SAAU,CAAE,KAAI,QAAS,EAAO,OAAQ,CAAK,CAAC,EACxD,KAAK,KAAK,SAAU,CAAE,KAAI,QAAS,EAAO,OAAQ,CAAK,CAAC,GAHxD,KAAK,KAAK,SAAU,CAAE,KAAI,QAAS,EAAO,OAAQ,CAAK,CAAC,EAW5D,KAAK,WAAW,IAAI,EAAI,CAAI,EAExB,GACA,KAAK,aAAa,IAAI,EAAI,CAAM,CAExC,CACJ,CAaA,aAAqB,EAAkB,EAAwC,CAC3E,OAAO,KAAK,QAAQ,EAAM,CAAK,EAAI,KAAO,KAAK,cAAc,CAAK,CACtE,CAgBA,mBAA8C,CAC1C,IAAM,EAAS,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,EAExC,OAAO,EAAe,eAAe,CAAC,CAAC,OAAO,GAC1C,EAAE,aAAe,IACjB,CAAC,KAAK,eAAe,EAAK,IAAI,GAC9B,EAAO,KAAK,GAAS,KAAK,eAAe,EAAK,CAAK,CAAC,CAAC,CAC7D,CAaA,eAAuB,EAAqB,EAA0B,CAClE,IAAK,IAAI,EAA4B,EAAM,EAAS,EAAU,EAAQ,mBAAmB,EACrF,GAAI,IAAY,EACZ,MAAO,GAIf,MAAO,EACX,CAUA,cAAsB,EAAuC,CACzD,OAAO,EAAI,cAAc,CAAC,CAAC,KAAK,GAAS,CAAC,EAAI,kBAAkB,CAAK,CAAC,GAAK,IAC/E,CAeA,WAAmB,EAAuC,CACtD,IAAM,EAAU,KAAK,cAAc,CAAG,EAEtC,GAAI,CAAC,EACD,OAAO,KAGX,GAAI,KAAK,kBAAkB,CAAO,EAC9B,OAAO,EAGX,IAAM,EAAS,KAAK,aAAa,EAOjC,OAHA,EAAI,cAAc,CAAM,EACxB,EAAO,cAAc,CAAO,EAErB,CACX,CAUA,WAAmB,EAAyB,CACxC,IAAI,EAAS,KAAK,QAAQ,IAAI,CAAM,EAE/B,IACD,EAAS,CAAE,WAAY,IAAI,GAAW,EAAQ,KAAK,YAAY,EAAG,SAAU,EAAM,EAElF,KAAK,QAAQ,IAAI,EAAQ,CAAM,GAGnC,IAAM,EAAU,EAAO,iBAAiB,EAExC,GAAI,KAAK,MAAM,CAAM,GAAK,CAAC,EAAO,SAAU,CACxC,IAAM,EAAW,EAEjB,EAAI,eAAe,EAAI,EAIvB,IAAM,YAAkC,CAAE,KAAK,YAAY,CAAM,CAAG,EAEpE,EAAI,GAAG,QAAS,OAAO,EAIvB,EAAI,GAAG,WAAa,KAAK,aAAa,EACtC,EAAI,GAAG,WAAY,KAAK,cAAc,EACtC,EAAI,GAAG,SAAW,KAAK,eAAe,EACtC,EAAI,GAAG,OAAW,KAAK,aAAa,EAEpC,EAAO,SAAW,EACtB,CAEA,IAAK,IAAM,KAAS,EAAO,cAAc,EAKjC,KAAK,kBAAkB,CAAK,GAAK,KAAK,QAAQ,IAAI,EAAM,MAAM,CAAC,IAAM,GACrE,KAAK,WAAW,CAAK,CAGjC,CAaA,YAAoB,EAAyB,CACzC,IAAM,EAAS,EAAO,mBAAmB,EAErC,CAAC,GAAU,EAAO,cAAc,CAAC,CAAC,OAAS,GAO3C,IAAW,OAIf,EAAO,gBAAgB,CAAM,EAC7B,KAAK,wBAAwB,CAAM,EACnC,KAAK,kBAAkB,CAAM,EAC7B,KAAK,cAAc,EACvB,CAYA,kBAA0B,EAA4B,CAC7C,EAAe,eAAe,CAAC,CAAC,SAAS,CAA2B,IAIpE,KAAK,cAAc,CAA2B,GAC/C,EAA8B,aAAa,EAEnD,CAYA,wBAAgC,EAA4B,CACxD,GAAI,KAAK,WAAW,CAAS,IAAM,QAC/B,OAGJ,IAAM,EAAW,EAAU,cAAc,EAEzC,GAAI,EAAS,SAAW,EACpB,OAGJ,IAAM,EAAc,EAAU,mBAAmB,EAEjD,GAAI,CAAC,EACD,OAGJ,IAAM,EAAQ,EAAY,cAAc,CAAC,CAAC,QAAQ,CAAS,EAIvD,KAAK,WAAW,CAAW,IAAM,SACjC,EAAa,iBAAiB,CAAC,CAAW,iBAAiB,EAAW,EAAS,EAAE,EAGrF,EAAY,cAAc,EAAS,GAAI,CAAK,EAC5C,EAAY,gBAAgB,CAAS,CACzC,CAYA,iBAAyB,EAAiB,EAAiC,CACvE,IAAM,EAAY,IAAI,IAEtB,KAAK,eAAe,EAAM,CAAS,EAEnC,IAAK,IAAM,KAAU,EACjB,KAAK,eAAe,EAAQ,CAAS,EAGzC,IAAK,GAAM,CAAC,EAAQ,KAAW,KAAK,QAC3B,EAAU,IAAI,CAAM,IACrB,EAAO,WAAW,QAAQ,EAC1B,KAAK,QAAQ,OAAO,CAAM,EAGtC,CAQA,eAAuB,EAAmB,EAA4B,CAClE,EAAK,IAAI,CAAM,EAEf,IAAK,IAAM,KAAS,EAAO,cAAc,EACjC,KAAK,kBAAkB,CAAK,GAC5B,KAAK,eAAe,EAAO,CAAI,CAG3C,CAYA,kBAA0B,EAA+B,CACrD,IAAM,EAAO,KAAK,WAAW,CAAS,EAEtC,OAAO,IAAS,SAAW,IAAS,KACxC,CASA,MAAc,EAA+B,CACzC,OAAO,KAAK,WAAW,CAAS,IAAM,KAC1C,CAUA,WAAmB,EAA8B,CAC7C,IAAM,EAAU,EAAU,iBAAiB,EAE3C,OAAO,EAAU,EAAQ,aAAa,CAAC,CAAC,QAAQ,KAAM,EAAE,EAAI,EAChE,CAaA,cAAyB,GAA6B,CAClD,IAAM,EAAK,EAAQ,MAAM,EAEzB,GAAI,KAAK,QAAQ,IAAI,CAAE,IAAM,EACzB,OAGJ,IAAM,EAAS,KAAK,aAAa,IAAI,CAAE,GAAK,KAE5C,KAAK,QAAQ,OAAO,CAAE,EACtB,KAAK,WAAW,OAAO,CAAE,EACzB,KAAK,aAAa,OAAO,CAAE,EAE3B,KAAK,KAAK,QAAS,CAAE,KAAI,UAAS,OAAQ,IAAK,CAAC,EAMhD,KAAK,cAAc,EAEf,KAAK,kBAAoB,GACzB,KAAK,uBAAuB,CAAM,CAE1C,EAaA,UAAkB,EAAY,EAAsB,CAM3C,KAAK,QAAQ,IAAI,CAAE,IAMnB,KAAK,YAAY,CAAE,GACpB,KAAK,QAAQ,OAAO,CAAE,EAG1B,KAAK,KAAK,YAAa,CAAE,KAAI,OAAM,CAAC,EACxC,CASA,eAA0B,GAA6B,CACnD,IAAM,EAAK,EAAQ,MAAM,EAEzB,GAAI,KAAK,QAAQ,IAAI,CAAE,IAAM,EACzB,OAKJ,IAAM,EAAS,KAAK,aAAa,IAAI,CAAE,EACnC,IACA,KAAK,kBAAoB,GAG7B,KAAK,SAAS,CAAE,CACpB,EAaA,gBAA2B,GAAkC,CACzD,KAAK,cAAc,CACvB,EASA,iBAAyB,EAA8B,CACnD,IAAM,EAAQ,KAAK,mBAAmB,CAAM,EAExC,GACA,KAAK,SAAS,EAAM,MAAM,CAAC,CAEnC,CAUA,cAAsB,EAA8B,CAChD,IAAI,EAAY,GAEhB,IAAK,IAAM,KAAS,KAAK,eAAe,CAAM,EAAG,CAC7C,IAAM,EAAK,EAAM,MAAM,EAEvB,KAAK,QAAQ,OAAO,CAAE,EACtB,KAAK,WAAW,OAAO,CAAE,EACzB,KAAK,aAAa,OAAO,CAAE,EAE3B,KAAK,KAAK,QAAS,CAAE,KAAI,QAAS,EAAO,OAAQ,IAAK,CAAC,EAEnD,KAAK,kBAAoB,IACzB,EAAY,GAEpB,CAKA,KAAK,cAAc,EAEf,GACA,KAAK,uBAAuB,IAAI,CAExC,CASA,SAAiB,EAAyB,CACtC,GAAI,IAAO,KAAK,gBACZ,OAKJ,GAFA,KAAK,gBAAkB,EAEnB,IAAO,KAAM,CACb,KAAK,KAAK,QAAS,IAAI,EAEvB,MACJ,CAEA,IAAM,EAAQ,KAAK,QAAQ,IAAI,CAAE,EAEjC,GAAI,EAAO,CACP,IAAM,EAAO,KAAK,aAAa,EAAO,KAAK,cAAc,CAAC,EAE1D,KAAK,KAAK,QAAS,CAAE,KAAI,QAAS,EAAO,OAAQ,CAAK,CAAC,CAC3D,CACJ,CASA,uBAA+B,EAAgC,CAC3D,EAAI,KAAK,0BAA4B,KAAK,yBAAyB,CAAM,CAAC,CAC9E,CASA,yBAAiC,EAAgC,CAC7D,GAAI,KAAK,QAAQ,OAAS,EAAG,CACzB,KAAK,SAAS,IAAI,EAElB,MACJ,CAEA,GAAI,CAAC,GAAU,CAAC,KAAK,MAAM,CAAM,GAAK,EAAO,cAAc,CAAC,CAAC,SAAW,EAAG,CACvE,KAAK,SAAS,IAAI,EAElB,MACJ,CAEA,IAAM,EAAS,EAAO,iBAAiB,CAAC,CAAS,iBAAiB,EAElE,KAAK,SAAS,EAAQ,EAAM,MAAM,EAAI,IAAI,CAC9C,CAaA,WAAW,EAAqB,CAC5B,IAAM,EAAQ,KAAK,QAAQ,IAAI,CAAE,EAEjC,GAAI,CAAC,EACD,MAAO,GAGX,IAAM,EAAS,KAAK,eAAe,CAAK,EAExC,GAAI,CAAC,EACD,MAAO,GAGX,IAAM,EAAS,EAAO,iBAAiB,CAAC,CAAS,eAAe,CAAK,EASrE,OAPI,EAAQ,EACD,IAGX,KAAK,cAAc,CAAK,CAAC,EAAE,aAAa,EACxC,EAAQ,iBAAiB,CAAC,CAAS,kBAAkB,CAAK,EAEnD,GACX,CAWA,YAAY,EAAqB,CAC7B,IAAM,EAAQ,KAAK,QAAQ,IAAI,CAAE,EAEjC,GAAI,CAAC,EACD,MAAO,GAGX,IAAM,EAAS,KAAK,eAAe,CAAK,EAMxC,OAJK,EAIG,EAAO,iBAAiB,CAAC,CAAS,SAAS,CAAK,EAH7C,EAIf,CAWA,eAAuB,EAAoC,CACvD,IAAK,IAAM,KAAU,KAAK,cAAc,EACpC,GAAK,EAAO,iBAAiB,CAAC,CAAS,eAAe,CAAK,GAAK,EAC5D,OAAO,EAIf,OAAO,IACX,CAUA,cAAsB,EAAyC,CAC3D,OAAO,KAAK,0BAA0B,CAAC,CAAC,KAAK,GAAO,KAAK,eAAe,EAAK,CAAK,CAAC,GAAK,IAC5F,CASA,eAAqC,CACjC,IAAM,EAAuB,CAAC,EACxB,EAAO,KAAK,cAAc,EAE5B,GACA,KAAK,kBAAkB,EAAM,CAAO,EAGxC,IAAK,IAAM,KAAO,KAAK,0BAA0B,EAAG,CAChD,GAAI,aAAe,EAAW,CAC1B,EAAQ,KAAK,CAA2B,EAExC,QACJ,CAEA,IAAM,EAAU,KAAK,cAAc,CAAG,EAElC,GACA,KAAK,kBAAkB,EAAS,CAAO,CAE/C,CAEA,OAAO,CACX,CAQA,kBAA0B,EAAmB,EAAyB,CAC9D,KAAK,MAAM,CAAM,GACjB,EAAK,KAAK,CAAM,EAGpB,IAAK,IAAM,KAAS,EAAO,cAAc,EACjC,KAAK,kBAAkB,CAAK,GAC5B,KAAK,kBAAkB,EAAO,CAAI,CAG9C,CAWA,eAAuB,EAAqC,CACxD,MAAO,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC,CAAC,CAAC,OAAO,GAAS,KAAK,eAAe,EAAQ,CAAK,CAAC,CACxF,CAWA,mBAA2B,EAA0C,CACjE,IAAI,EAAkB,KAEtB,GAAI,aAAkB,EAClB,EAAM,EAAO,iBAAiB,MAC3B,CACH,IAAM,EAAU,KAAK,cAAc,CAAM,EACnC,EAAuB,CAAC,EAE1B,GACA,KAAK,kBAAkB,EAAS,CAAO,EAG3C,EAAM,EAAQ,OAAS,EAAK,EAAQ,EAAE,CAAC,iBAAiB,EAAY,IACxE,CAEA,IAAM,EAAQ,EAAM,EAAI,iBAAiB,EAAI,KAE7C,OAAO,GAAS,KAAK,QAAQ,IAAI,EAAM,MAAM,CAAC,IAAM,EAAQ,EAAQ,IACxE,CAUA,QAAgB,EAAqB,EAA0B,CAC3D,IAAK,IAAI,EAA4B,EAAM,EAAS,EAAU,EAAQ,mBAAmB,EACrF,GAAI,IAAY,EACZ,MAAO,GAIf,MAAO,EACX,CAoDA,GAAG,EAAkB,EAA0B,CAG3C,OAFA,KAAK,WAAW,IAAI,EAAO,CAAQ,EAE5B,IACX,CAuCA,IAAI,EAAkB,EAA0B,CAG5C,OAFA,KAAK,WAAW,OAAO,EAAO,CAAQ,EAE/B,IACX,CAaA,KAAe,EAAkB,EAA4E,CACzG,KAAK,WAAW,KAAK,EAAO,CAAO,CACvC,CACJ,EAEM,GAAe,EAAS,IAAI"}
1
+ {"version":3,"file":"overlay.es.js","names":[],"sources":["../../src/typescript/lib/overlay/Popover.ts","../../src/typescript/lib/component/container/DialogBackdrop.ts","../../src/typescript/lib/overlay/Dialog.ts","../../src/typescript/lib/overlay/Notification.ts","../../src/typescript/lib/overlay/NotificationHistoryButton.ts","../../src/typescript/lib/overlay/Drawer.ts","../../src/typescript/lib/overlay/RailHandle.ts","../../src/typescript/lib/overlay/Rail.ts","../../src/typescript/lib/overlay/Dock.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { fadeShow, fadeHideAndDetach } from \"~/core/AnimatedDropdown.js\";\nimport type { Animation } from \"~/core/Animation.js\";\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport type { Edge } from \"~/primitive/Edge.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM, type Rect, type Handle } from \"~/core/DOM.js\";\n\n/** Fallback arrow side length used until the theme token is read. */\nconst DEFAULT_ARROW_SIZE_PX: number = 14;\n\n/**\n * Half of the visual extent of the rotated arrow along either axis. The\n * arrow is a square of side `DEFAULT_ARROW_SIZE_PX` rotated 45°, so its\n * bounding rectangle is `DEFAULT_ARROW_SIZE_PX * sqrt(2)` on each side and\n * each corner sits half that far from the centre.\n */\nconst ARROW_VISUAL_HALF: number = (DEFAULT_ARROW_SIZE_PX / 2) * Math.SQRT2;\n\n/** Pixel gap between the arrow tip and the anchor element. */\nconst ARROW_ANCHOR_GAP_PX: number = 2;\n\n/**\n * Pixel gap between the popover edge and the anchor element. Sized so the\n * arrow tip lands {@link ARROW_ANCHOR_GAP_PX} away from the anchor.\n */\nconst POPOVER_ANCHOR_GAP: number = Math.ceil(ARROW_VISUAL_HALF) + ARROW_ANCHOR_GAP_PX;\n\n/** Pixel inset preserved between the arrow and the popover corner. */\nconst ARROW_EDGE_INSET_PX: number = 6;\n\n/** Pixel inset preserved between the arrow / popover and any viewport edge. */\nconst VIEWPORT_EDGE_INSET_PX: number = 5;\n\n/** Fade duration matched to the rest of the floating-overlay family. */\nconst POPOVER_FADE_DURATION_MS: number = 120;\n\n/**\n * Placement of a {@link Popover} relative to its anchor element. `\"auto\"`\n * picks the side with the most viewport space at `show()` time.\n *\n * @category Core\n */\nexport type PopoverPlacement = Edge | \"auto\";\n\n/**\n * Strategy used to dismiss a {@link Popover}:\n *\n * - `\"click-outside\"`: closes on a `pointerdown` outside both the popover and\n * its anchor element (the anchor is excluded so the trigger click can\n * re-open without immediate re-close).\n * - `\"blur\"`: closes when focus or a pointer leaves the popover subtree —\n * including a portaled descendant layer such as a dropdown opened inside\n * the popover, which now keeps the popover open.\n * - `\"manual\"`: caller drives `hide()` explicitly.\n *\n * @remarks Dismissal is executed by {@link LayerManager}: the popover reports\n * this mode from its {@link DismissableLayer.getDismissMode} and the manager's\n * document-level handlers decide when to call `requestClose`. This is the\n * public option name; it maps 1:1 onto {@link LayerDismissMode}.\n *\n * @category Core\n */\nexport type PopoverDismissMode = \"click-outside\" | \"blur\" | \"manual\";\n\n/**\n * Construction-time options for {@link Popover}.\n *\n * @category Core\n */\nexport interface PopoverOptions extends ContainerOptions {\n /** Resolved placement relative to the anchor. Default `\"auto\"`. */\n placement?: PopoverPlacement;\n /** Dismiss strategy. Default `\"click-outside\"`. */\n dismissOn?: PopoverDismissMode;\n /** When `false`, the arrow tail is not rendered. Default `true`. */\n showArrow?: boolean;\n /** Optional title row rendered above the body. */\n title?: string;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each present setter once\n * with the final value, so any field the caller supplied wins.\n */\nconst _defaultPopoverOptions: Partial<PopoverOptions> = {\n insets: new Insets(5, 5, 5, 5),\n placement: \"auto\",\n dismissOn: \"click-outside\",\n showArrow: true,\n};\n\n/**\n * An anchored, non-modal floating bubble with a directional arrow tail. Use\n * `Popover` for click-triggered, interactive content (title, body, action\n * buttons, or any arbitrary subtree) — for ephemeral hover hints reach for\n * [`Tooltip`](/api/overlay/classes/Tooltip), and for modal containment reach\n * for [`Dialog`](/api/overlay/classes/Dialog).\n *\n * `Popover` extends {@link Container} so authors can compose freely via\n * `addComponent`; the `setTitle` / `setBody` / `addAction` conveniences are\n * sugar over the same container surface.\n *\n * Positioning is anchor-relative. `attachToComponent(Component)` records the\n * anchor. While the popover is open, `window` `resize` and each scrollable\n * ancestor's `scroll` event trigger a reposition so the bubble follows the\n * anchor.\n *\n * Fade-in / fade-out reuse the shared [`fadeShow`](/api/core/functions/fadeShow)\n * and [`fadeHideAndDetach`](/api/core/functions/fadeHideAndDetach) helpers,\n * inheriting the standard 120 ms `opacity + translateY` transition and the\n * re-entrancy guard against a fresh `show()` mid-fade.\n *\n * @example\n * ```typescript\n * const popover = new Popover({ placement: \"auto\" });\n * popover.setTitle(\"Confirm delete\");\n * popover.setBody(\"This action cannot be undone.\");\n * popover.addAction(\"Delete\", () => doDelete());\n * popover.addAction(\"Cancel\", () => popover.hide());\n *\n * Event.addListener(triggerButton, \"click\", () => {\n * popover.attachToComponent(triggerButton);\n * popover.show();\n * });\n * ```\n *\n * @category Core\n */\nclass Popover extends Container<PopoverOptions> implements DismissableLayer {\n\n // Option-backed fields use `declare` rather than initializers to dodge the\n // class-field super-cascade trap: an initializer runs *after* super()\n // returns, overwriting whatever the cascade-dispatched setter wrote\n // during the super-time `applyOptions` call. The applyOptions override\n // below always dispatches each setter with a fallback so the field is\n // seeded even when no caller option was supplied.\n declare private _placement: PopoverPlacement;\n private _resolvedPlacement: PopoverPlacement = \"bottom\";\n declare private _dismissOn: PopoverDismissMode;\n declare private _showArrow: boolean;\n declare private _title: string | null;\n declare private _titleComponent: Text | null;\n private _bodyComponent: Component | null = null;\n private _actionsRow: Component | null = null;\n\n // In-flight fades, cancelled on teardown so their fallback timers cannot\n // fire against this popover's released element handle.\n private _fadeShowAnimation: Animation.CancelHandle | null = null;\n private _fadeHideAnimation: Animation.CancelHandle | null = null;\n private _anchorElement: Handle | null = null;\n private _arrowComponent: Component | null = null;\n private _isOpen: boolean = false;\n private _scrollAncestors: Handle[] = [];\n\n private readonly _onWindowResize: () => void;\n private readonly _onScroll: () => void;\n\n /**\n * Creates a popover with a 5px layout-side inset and the default chrome\n * wired to the theme tokens.\n *\n * @param options - Optional construction-time options.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults; subclasses forward their `_defaultXxxOptions`\n * constant here.\n */\n constructor(options?: PopoverOptions, subclassDefaults?: Partial<PopoverOptions>) {\n super(options as PopoverOptions, { ..._defaultPopoverOptions, ...(subclassDefaults ?? {}) });\n\n const vbox = new VBox();\n\n vbox.setStretching(true);\n this.setLayoutManager(vbox);\n\n // Theme-driven chrome.\n this.setBackgroundColor(\"var(--ts-ui-popover-bg, rgb(255, 255, 255))\");\n this.setForegroundColor(\"var(--ts-ui-popover-color, rgb(0, 0, 0))\");\n this.setBorder({ border: \"1px solid var(--ts-ui-popover-border, rgb(200, 200, 200))\" });\n this.setBorderRadius(\"var(--ts-ui-popover-radius, 6px)\");\n this.setShadow(\"var(--ts-ui-popover-shadow, 2px 4px 12px rgba(0, 0, 0, 0.18))\");\n\n // Overlay placement: top-level, viewport-fixed. The z-index is\n // stamped from LayerManager's Popover band at show() time, so no\n // static value is set here.\n this.setPosition(Position.FIXED);\n this.setVisible(false);\n\n // `paint` containment would clip the arrow tail (which straddles the\n // popover edge), so use `layout` containment only. The framework\n // default `overflow: hidden` would also clip the arrow, so opt out.\n this.setContain(\"layout\");\n this.setOverflow(\"visible\");\n this.getAria().setRole(\"dialog\");\n\n this._onWindowResize = () => this._reposition();\n this._onScroll = () => this._reposition();\n\n // Seed the `declare`-d title fields after the cascade has had its\n // chance to write them via setTitle. Without a `title` option the\n // cascade dispatch above skips setTitle entirely, so the fields\n // would otherwise remain undefined (declare allocates no default).\n this._title ??= null;\n this._titleComponent ??= null;\n }\n\n /**\n * Applies a {@link PopoverOptions} bag, dispatching popover-specific\n * fields after the inherited Container cascade.\n *\n * @param options - The options bag carrying the values to apply.\n * @returns This popover, for method chaining.\n */\n protected applyOptions(options: PopoverOptions): this {\n super.applyOptions(options);\n\n // placement/dismissOn/showArrow carry a class default and seed\n // construction-time state, so always dispatch the caller value or the\n // class default; title has no default.\n this.setPlacement(options.placement ?? this.getPlacement());\n this.setDismissOn(options.dismissOn ?? this.getDismissOn());\n this.setShowArrow(options.showArrow ?? this.isShowArrow());\n if (options.title !== undefined) this.setTitle(options.title);\n\n return this;\n }\n\n /**\n * Sets the requested placement relative to the anchor.\n *\n * @param p - One of `\"top\"`, `\"bottom\"`, `\"left\"`, `\"right\"`, or `\"auto\"`.\n * @returns This popover, for method chaining.\n */\n setPlacement(p: PopoverPlacement): this {\n this._placement = p;\n\n return this;\n }\n\n /**\n * Returns the configured placement.\n *\n * @returns The placement set via {@link setPlacement}, or `\"auto\"` by default.\n */\n getPlacement(): PopoverPlacement {\n return this._placement ?? this._defaultOptions.placement!;\n }\n\n /**\n * Sets the dismiss strategy.\n *\n * @param mode - One of `\"click-outside\"`, `\"blur\"`, or `\"manual\"`.\n * @returns This popover, for method chaining.\n */\n setDismissOn(mode: PopoverDismissMode): this {\n this._dismissOn = mode;\n\n return this;\n }\n\n /**\n * Returns the active dismiss strategy.\n *\n * @returns The dismiss strategy set via {@link setDismissOn}.\n */\n getDismissOn(): PopoverDismissMode {\n return this._dismissOn ?? this._defaultOptions.dismissOn!;\n }\n\n /**\n * Enables or disables the arrow tail.\n *\n * @param value - `true` to render the arrow, `false` to hide it.\n * @returns This popover, for method chaining.\n */\n setShowArrow(value: boolean): this {\n this._showArrow = value;\n\n if (this._arrowComponent) {\n this._arrowComponent.setVisible(value);\n }\n\n return this;\n }\n\n /**\n * Returns whether the arrow tail is currently enabled.\n *\n * @returns `true` if the arrow is rendered.\n */\n isShowArrow(): boolean {\n return this._showArrow ?? this._defaultOptions.showArrow!;\n }\n\n /**\n * Sets the title row text. Lazily creates the underlying\n * [`Text`](/api/component/input/classes/Text) child on first call. Passing\n * `null` clears the title.\n *\n * @param text - The title string, or `null` to clear.\n * @returns This popover, for method chaining.\n */\n setTitle(text: string | null): this {\n if (text === null) {\n return this.clearTitle();\n }\n\n this._title = text;\n\n if (!this._titleComponent) {\n this._titleComponent = new Text(text);\n this._titleComponent.setFontWeight(\"bold\");\n\n this.insertComponent(this._titleComponent, 0);\n } else {\n this._titleComponent.setText(text);\n }\n\n return this;\n }\n\n /**\n * Returns the current title string, or `null` if no title is set.\n *\n * @returns The title set via {@link setTitle}, or `null`.\n */\n getTitle(): string | null {\n return this._title;\n }\n\n /**\n * Removes the title row.\n *\n * @returns This popover, for method chaining.\n */\n clearTitle(): this {\n if (this._titleComponent) {\n this.removeComponent(this._titleComponent);\n this._titleComponent = null;\n }\n\n this._title = null;\n\n return this;\n }\n\n /**\n * Sets the body content. A `string` is wrapped in a\n * [`Text`](/api/component/input/classes/Text) child; a `Component` is used\n * directly. Replaces any previous body.\n *\n * @param content - The body string or component.\n * @returns This popover, for method chaining.\n */\n setBody(content: Component | string): this {\n if (this._bodyComponent) {\n this.removeComponent(this._bodyComponent);\n this._bodyComponent = null;\n }\n\n const next: Component = typeof content === \"string\"\n ? new Text(content)\n : content;\n\n this._bodyComponent = next;\n\n // Insert after the title (if any) and before the actions row (if any).\n const titleOffset = this._titleComponent ? 1 : 0;\n\n this.insertComponent(next, titleOffset);\n\n return this;\n }\n\n /**\n * Returns the current body component, or `null` when none has been set.\n *\n * @returns The body component, or `null`.\n */\n getBody(): Component | null {\n return this._bodyComponent;\n }\n\n /**\n * Appends an action button to the actions row. Lazily creates the row\n * (an [`HBox`](/api/layout/classes/HBox)-laid `Panel`) on first call.\n *\n * @param label - The button label.\n * @param onClick - Handler invoked when the button is activated.\n * @returns This popover, for method chaining.\n */\n addAction(label: string, onClick: () => void): this {\n if (!this._actionsRow) {\n this._actionsRow = new Component();\n this._actionsRow.setLayoutManager(new HBox());\n\n this.addComponent(this._actionsRow);\n }\n\n const button = new Button(label);\n\n button.on(\"action\", onClick);\n\n this._actionsRow.addComponent(button);\n\n return this;\n }\n\n /**\n * Removes every action button previously registered via {@link addAction}.\n *\n * @returns This popover, for method chaining.\n */\n clearActions(): this {\n if (this._actionsRow) {\n this.removeComponent(this._actionsRow);\n this._actionsRow = null;\n }\n\n return this;\n }\n\n /**\n * Records the raw DOM element used as the positioning anchor.\n *\n * @param el - The anchor element.\n * @returns This popover, for method chaining.\n */\n private _attachToElement(el: Handle): this {\n this._anchorElement = el;\n\n return this;\n }\n\n /**\n * Records the anchor by resolving the given component's element. Must be\n * called before {@link Popover.show}.\n *\n * @param c - The component whose element should be used as the anchor.\n * @returns This popover, for method chaining.\n */\n attachToComponent(c: Component): this {\n return this._attachToElement(c.getElement(true)!);\n }\n\n /**\n * Mounts the popover on `document.documentElement` (if not already\n * mounted), resolves the final placement, positions the bubble + arrow,\n * fades it in, and wires the dismiss listeners.\n *\n * @returns This popover, for method chaining.\n */\n show(): this {\n if (!this._anchorElement) {\n console.warn(\"Popover.show(): no anchor attached; call attachToComponent first.\");\n return this;\n }\n\n this._isOpen = true;\n\n // Join the central layer tree and mirror its band-based z-stamp so a\n // popover opened from inside a window or dropdown stacks correctly.\n LayerManager.register(this);\n this.setZIndex(LayerManager.getZIndex(this));\n\n const el = this.getElement(true)!;\n\n LayerManager.mount(el);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the popover.\n trapWheel(this);\n\n this.ensureArrow();\n this.setVisible(true);\n\n // First measurement pass: compute preferred size so we can resolve\n // placement against the real bubble dimensions.\n this.doLayout();\n this._reposition();\n\n this._fadeShowAnimation?.cancel();\n this._fadeShowAnimation = fadeShow(this, { durationMs: POPOVER_FADE_DURATION_MS });\n\n this.attachRepositionListeners();\n\n return this;\n }\n\n /**\n * Plays the exit fade, detaches the popover from the DOM, and removes\n * every dismiss / reposition listener.\n *\n * @returns This popover, for method chaining.\n */\n hide(): this {\n if (!this._isOpen) {\n return this;\n }\n\n this._isOpen = false;\n\n this.detachRepositionListeners();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._fadeHideAnimation?.cancel();\n this._fadeHideAnimation = fadeHideAndDetach(this, { durationMs: POPOVER_FADE_DURATION_MS });\n\n return this;\n }\n\n /**\n * Returns whether the popover is currently open (showing or fading in).\n *\n * @returns `true` when the popover is open.\n */\n isOpen(): boolean {\n return this._isOpen;\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the popover's root element for the central layer tree.\n *\n * @returns The popover'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, mapping\n * the public {@link PopoverDismissMode} directly onto the manager's\n * vocabulary (the two share the `\"click-outside\"` / `\"blur\"` / `\"manual\"`\n * names). The manager now executes dismissal; the `\"blur\"` mode works for\n * a nested dropdown because the dropdown registers as the popover's child.\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return this._dismissOn ?? this._defaultOptions.dismissOn!;\n }\n\n /**\n * Advisory close request from the manager — runs the standard\n * {@link Popover.hide} teardown, which unregisters the layer.\n */\n requestClose(): void {\n this.hide();\n }\n\n /**\n * Returns the anchor element excluded from outside-interaction tests so a\n * click on the trigger does not immediately re-close the popover.\n *\n * @returns The anchor element, or null when none is attached.\n */\n getAnchorElement(): Handle | null {\n return this._anchorElement;\n }\n\n /**\n * Returns the popover's z-index band so an unrelated top-level popover\n * stacks above windows but below dropdowns and dialogs.\n *\n * @returns The popover band base.\n */\n getBand(): number {\n return LayerManager.Band.Popover;\n }\n\n /**\n * Lays out children (delegating to the VBox) and positions the arrow\n * tail along the resolved edge.\n *\n * @returns This popover, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n if (this._arrowComponent && this._showArrow && this._anchorElement) {\n this.positionArrow();\n }\n\n return this;\n }\n\n /**\n * Releases per-instance resources. Closes the popover if it is still\n * open, disposes the raw-appended arrow, then defers to the base class\n * for the rest of teardown.\n */\n protected destructor(): void {\n if (this._isOpen) {\n this.hide();\n }\n\n // `_titleComponent` / `_bodyComponent` / `_actionsRow` are all\n // registered via `insertComponent` / `addComponent`, so\n // `super.destructor()`'s child recursion below already disposes\n // them — an explicit call here would run each one's destructor()\n // a second time. `_arrowComponent`, by contrast, is inserted\n // straight into the DOM via `ensureArrow()` (not registered in\n // `_components`), so recursion can never reach it and it must be\n // disposed explicitly.\n // After `hide()` above, since that starts a fresh fade-out whose\n // fallback timer would otherwise outlive the element handle\n // `super.destructor()` releases below.\n this._fadeShowAnimation?.cancel();\n this._fadeShowAnimation = null;\n this._fadeHideAnimation?.cancel();\n this._fadeHideAnimation = null;\n\n this._arrowComponent?.dispose();\n\n this._anchorElement = null;\n this._titleComponent = null;\n this._bodyComponent = null;\n this._actionsRow = null;\n this._arrowComponent = null;\n\n super.destructor();\n }\n\n /**\n * Resolves the requested placement against the viewport, anchor rect, and\n * own preferred size; updates the popover's `setX` / `setY` and the\n * resolved-placement field.\n */\n private _reposition(): void {\n if (!this._anchorElement) {\n return;\n }\n\n const anchorRect = DOM.source.getElementRect(this._anchorElement);\n\n // Anchor removed from the DOM or rendered with zero size — close.\n if (anchorRect.width === 0 && anchorRect.height === 0) {\n this.hide();\n return;\n }\n\n const preferred = this.getPreferredSize();\n const width = preferred?.width ?? this.getWidth();\n const height = preferred?.height ?? this.getHeight();\n const vp = DOM.source.getViewportSize();\n\n const resolved = this.resolvePlacement(anchorRect, width, height, vp);\n\n this._resolvedPlacement = resolved;\n\n let x: number = 0;\n let y: number = 0;\n\n if (resolved === \"top\") {\n x = anchorRect.left + (anchorRect.width - width) / 2;\n y = anchorRect.top - height - POPOVER_ANCHOR_GAP;\n } else if (resolved === \"bottom\") {\n x = anchorRect.left + (anchorRect.width - width) / 2;\n y = anchorRect.bottom + POPOVER_ANCHOR_GAP;\n } else if (resolved === \"left\") {\n x = anchorRect.left - width - POPOVER_ANCHOR_GAP;\n y = anchorRect.top + (anchorRect.height - height) / 2;\n } else { // \"right\"\n x = anchorRect.right + POPOVER_ANCHOR_GAP;\n y = anchorRect.top + (anchorRect.height - height) / 2;\n }\n\n // Clamp into viewport so the bubble stays on-screen. The arrow's\n // independent positioning keeps it pointing at the anchor centre\n // even when the bubble is shifted laterally. On the tip-direction\n // side, leave room for the arrow's outward visual extent so the tip\n // also stays at least VIEWPORT_EDGE_INSET_PX inside the viewport.\n let minX = VIEWPORT_EDGE_INSET_PX;\n let maxX = vp.width - width - VIEWPORT_EDGE_INSET_PX;\n let minY = VIEWPORT_EDGE_INSET_PX;\n let maxY = vp.height - height - VIEWPORT_EDGE_INSET_PX;\n\n if (resolved === \"right\") {\n minX += ARROW_VISUAL_HALF;\n } else if (resolved === \"left\") {\n maxX -= ARROW_VISUAL_HALF;\n } else if (resolved === \"bottom\") {\n minY += ARROW_VISUAL_HALF;\n } else {\n maxY -= ARROW_VISUAL_HALF;\n }\n\n x = Util.clamp(x, minX, maxX);\n y = Util.clamp(y, minY, maxY);\n\n this.setX(x);\n this.setY(y);\n this.setWidth(width);\n this.setHeight(height);\n\n if (this._arrowComponent && this._showArrow) {\n this.positionArrow();\n }\n }\n\n /**\n * Picks the placement to use given the configured value and the available\n * viewport space. `\"auto\"` selects the side with the most room; an\n * explicit side is honoured unless it would overflow, in which case the\n * opposite side is used and a console warning is logged.\n *\n * @param anchor - The anchor's `DOMRect`.\n * @param width - The popover's preferred width in pixels.\n * @param height - The popover's preferred height in pixels.\n * @param vp - The viewport size returned by `DOM.source.getViewportSize`.\n * @returns The resolved placement.\n */\n private resolvePlacement(\n anchor: Rect,\n width: number,\n height: number,\n vp: { width: number; height: number },\n ): PopoverPlacement {\n const spaceTop = anchor.top;\n const spaceBottom = vp.height - anchor.bottom;\n const spaceLeft = anchor.left;\n const spaceRight = vp.width - anchor.right;\n\n if (this._placement === \"auto\") {\n const needsH = height + POPOVER_ANCHOR_GAP;\n const needsV = width + POPOVER_ANCHOR_GAP;\n\n const candidates: Array<{ side: PopoverPlacement; space: number; need: number }> = [\n { side: \"bottom\", space: spaceBottom, need: needsH },\n { side: \"top\", space: spaceTop, need: needsH },\n { side: \"right\", space: spaceRight, need: needsV },\n { side: \"left\", space: spaceLeft, need: needsV },\n ];\n\n // Prefer a side that physically fits; otherwise pick the one\n // with the most absolute space.\n const fitting = candidates.filter(c => c.space >= c.need);\n const pool = fitting.length > 0 ? fitting : candidates;\n\n pool.sort((a, b) => b.space - a.space);\n\n return pool[0].side;\n }\n\n // Explicit placement — honour unless the requested side cannot fit.\n const fits = (side: PopoverPlacement): boolean => {\n if (side === \"top\") { return spaceTop >= height + POPOVER_ANCHOR_GAP; }\n if (side === \"bottom\") { return spaceBottom >= height + POPOVER_ANCHOR_GAP; }\n if (side === \"left\") { return spaceLeft >= width + POPOVER_ANCHOR_GAP; }\n\n return spaceRight >= width + POPOVER_ANCHOR_GAP;\n };\n\n if (fits(this._placement)) {\n return this._placement;\n }\n\n const opposite: Record<Exclude<PopoverPlacement, \"auto\">, Exclude<PopoverPlacement, \"auto\">> = {\n top: \"bottom\",\n bottom: \"top\",\n left: \"right\",\n right: \"left\",\n };\n\n const flipped = opposite[this._placement as Exclude<PopoverPlacement, \"auto\">];\n\n console.warn(`Popover: explicit placement \"${this._placement}\" overflows the viewport; falling back to \"${flipped}\".`);\n\n return flipped;\n }\n\n /**\n * Lazily creates the arrow tail component on first show.\n *\n * @remarks The arrow's element is inserted as the *first* DOM child of\n * the popover (not via `addComponent`) so the VBox layout manager does\n * not size it AND so it paints beneath subsequent sibling content. The\n * outward half of the diamond is still visible because it lies outside\n * the popover's bounds; the inward half is overlaid by the bubble's\n * children, preventing the arrow from obscuring the title/body text.\n */\n private ensureArrow(): void {\n if (this._arrowComponent) {\n return;\n }\n\n const arrow = new Component();\n const size = DEFAULT_ARROW_SIZE_PX;\n\n arrow.setBackgroundColor(\"var(--ts-ui-popover-bg, rgb(255, 255, 255))\");\n arrow.setWidth(size);\n arrow.setHeight(size);\n arrow.setTransform(\"rotate(45deg)\");\n arrow.setPointerEvents(\"none\");\n arrow.setVisible(this._showArrow);\n\n this._arrowComponent = arrow;\n\n const popoverEl = this.getElement(true)!;\n DOM.sink.insertBefore(popoverEl, arrow.getElement(true)!, DOM.source.getFirstChild(popoverEl));\n }\n\n /**\n * Positions the arrow tail along the resolved popover edge so it points\n * at the anchor centre, clamped to keep it within the popover's lateral\n * extent. Also re-applies the directional inset box-shadow that draws the\n * two outward-facing edges of the rotated diamond so the popover outline\n * appears continuous through the arrow.\n */\n private positionArrow(): void {\n if (!this._anchorElement || !this._arrowComponent) {\n return;\n }\n\n const anchorRect = DOM.source.getElementRect(this._anchorElement);\n const size = DEFAULT_ARROW_SIZE_PX;\n const half = size / 2;\n const popoverX = this.getX();\n const popoverY = this.getY();\n const popoverW = this.getWidth();\n const popoverH = this.getHeight();\n\n // High-first clamp (not Util.clamp): when the popover is larger than the\n // viewport, maxLocal < minLocal, and the arrow must pin to the leading\n // inset (minLocal), which the low-first Util.clamp would not do.\n const clamp = (value: number, min: number, max: number): number => Math.max(min, Math.min(value, max));\n\n // Inset box-shadow draws a 1px line on the two outward-facing edges\n // of the rotated diamond. The mapping below is given in\n // pre-rotation coordinates (the element's own CSS axes); after the\n // 45° clockwise rotation, original TOP/RIGHT/BOTTOM/LEFT edges map to\n // the rotated NE/SE/SW/NW edges.\n const borderColor = \"var(--ts-ui-popover-border, rgb(200, 200, 200))\";\n let arrowShadow: string;\n\n if (this._resolvedPlacement === \"bottom\") {\n arrowShadow = `inset 1px 0 0 ${borderColor}, inset 0 1px 0 ${borderColor}`;\n } else if (this._resolvedPlacement === \"top\") {\n arrowShadow = `inset -1px 0 0 ${borderColor}, inset 0 -1px 0 ${borderColor}`;\n } else if (this._resolvedPlacement === \"right\") {\n arrowShadow = `inset 1px 0 0 ${borderColor}, inset 0 -1px 0 ${borderColor}`;\n } else {\n arrowShadow = `inset 0 1px 0 ${borderColor}, inset -1px 0 0 ${borderColor}`;\n }\n\n this._arrowComponent.setShadow(arrowShadow);\n\n const vp = DOM.source.getViewportSize();\n const border = this.getBorderSize();\n\n // `position: absolute` measures from the popover's padding box (i.e.\n // inside the border). Subtracting the border width centres the\n // rotated diamond on the popover's *outer* edge, keeping the tip-to-\n // anchor gap predictable regardless of border thickness.\n if (this._resolvedPlacement === \"top\" || this._resolvedPlacement === \"bottom\") {\n const anchorCentreX = anchorRect.left + anchorRect.width / 2;\n const minLocalX = Math.max(\n ARROW_EDGE_INSET_PX,\n VIEWPORT_EDGE_INSET_PX - popoverX + ARROW_VISUAL_HALF - half,\n );\n const maxLocalX = Math.min(\n popoverW - size - ARROW_EDGE_INSET_PX,\n vp.width - VIEWPORT_EDGE_INSET_PX - popoverX - half - ARROW_VISUAL_HALF,\n );\n const localX = clamp(anchorCentreX - popoverX - half - border.left, minLocalX, maxLocalX);\n\n this._arrowComponent.setX(localX);\n\n // Pull the arrow half its own size outside the body so the\n // rotated diamond reads as a triangle straddling the edge.\n if (this._resolvedPlacement === \"bottom\") {\n this._arrowComponent.setY(-half - border.top);\n } else {\n this._arrowComponent.setY(popoverH - half - border.top);\n }\n } else {\n const anchorCentreY = anchorRect.top + anchorRect.height / 2;\n const minLocalY = Math.max(\n ARROW_EDGE_INSET_PX,\n VIEWPORT_EDGE_INSET_PX - popoverY + ARROW_VISUAL_HALF - half,\n );\n const maxLocalY = Math.min(\n popoverH - size - ARROW_EDGE_INSET_PX,\n vp.height - VIEWPORT_EDGE_INSET_PX - popoverY - half - ARROW_VISUAL_HALF,\n );\n const localY = clamp(anchorCentreY - popoverY - half - border.top, minLocalY, maxLocalY);\n\n this._arrowComponent.setY(localY);\n\n if (this._resolvedPlacement === \"right\") {\n this._arrowComponent.setX(-half - border.left);\n } else {\n this._arrowComponent.setX(popoverW - half - border.left);\n }\n }\n }\n\n /**\n * Registers `window` resize and `scroll` listeners on every scrollable\n * ancestor of the anchor so the popover follows the anchor while open.\n */\n private attachRepositionListeners(): void {\n Event.addViewportListener(this, \"resize\", this._onWindowResize);\n\n if (this._anchorElement) {\n this._scrollAncestors = this.collectScrollAncestors(this._anchorElement);\n\n for (const ancestor of this._scrollAncestors) {\n DOM.sink.addListener(ancestor, \"scroll\", this._onScroll, { passive: true });\n }\n }\n }\n\n /**\n * Detaches every reposition listener registered by {@link attachRepositionListeners}.\n */\n private detachRepositionListeners(): void {\n Event.removeViewportListener(this, \"resize\", this._onWindowResize);\n\n for (const ancestor of this._scrollAncestors) {\n DOM.sink.removeListener(ancestor, \"scroll\", this._onScroll);\n }\n\n this._scrollAncestors = [];\n }\n\n /**\n * Walks the anchor's ancestor chain up to `document.documentElement` and\n * collects every element whose computed `overflow` makes it scrollable.\n *\n * @param node - The starting element.\n * @returns The list of scrollable ancestors plus `document.documentElement`.\n */\n private collectScrollAncestors(node: Handle): Handle[] {\n const out: Handle[] = [];\n let cursor: Handle | null = DOM.source.getParentElement(node);\n\n while (cursor && cursor !== DOM.source.getDocumentElement()) {\n const style = DOM.source.getComputedOverflow(cursor);\n const overflow = style.overflow + style.overflowX + style.overflowY;\n\n if (/(auto|scroll|overlay)/.test(overflow)) {\n out.push(cursor);\n }\n\n cursor = DOM.source.getParentElement(cursor);\n }\n\n out.push(DOM.source.getDocumentElement());\n\n return out;\n }\n}\n\nconst PopoverCallable = callable(Popover);\ntype PopoverCallable = Popover;\nexport {\n Popover as _Popover,\n PopoverCallable as Popover\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link DialogBackdrop}.\n *\n * @category Components\n */\ninterface DialogBackdropOptions extends ComponentOptions {\n}\n\n/**\n * Empty subclass-default const so the super call follows the framework's\n * `(options, defaults)` shape uniformly.\n */\nconst _defaultDialogBackdropOptions: Partial<DialogBackdropOptions> = {};\n\n/**\n * A full-viewport fixed overlay that visually blocks content behind a modal dialog.\n *\n * Appended to `document.documentElement` as a sibling of the dialog panel at a lower\n * z-index, mirroring the pattern used by [`Notification`](/api/overlay/classes/Notification) and [`Menu`](/api/overlay/classes/Menu).\n */\nclass DialogBackdrop extends Component<DialogBackdropOptions> {\n\n /**\n * Creates the backdrop and applies viewport-filling fixed positioning.\n *\n * @param options - Optional construction options.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults; subclasses forward their `_defaultXxxOptions`\n * constant here.\n */\n constructor(options?: DialogBackdropOptions, subclassDefaults?: Partial<DialogBackdropOptions>) {\n super(options, { ..._defaultDialogBackdropOptions, ...(subclassDefaults ?? {}) });\n\n const vp = DOM.source.getViewportSize();\n\n this.setPosition(Position.FIXED);\n this.setX(0);\n this.setY(0);\n this.setWidth(vp.width);\n this.setHeight(vp.height);\n this.setZIndex(10100);\n this.setBackgroundColor(\"var(--ts-ui-dialog-backdrop-bg)\");\n }\n\n /**\n * Registers a click listener on this backdrop element.\n *\n * @param listener - Called when the user clicks the backdrop area.\n */\n addClickListener(listener: Event.Listener): this {\n Event.addListener(this, \"click\", listener);\n\n return this;\n }\n\n /**\n * Updates backdrop dimensions to match the current viewport size.\n */\n resize(): void {\n const vp = DOM.source.getViewportSize();\n\n this.setWidth(vp.width);\n this.setHeight(vp.height);\n }\n\n /**\n * Removes the backdrop from the DOM and runs component cleanup.\n */\n destroy(): void {\n this.removeElement();\n this.destructor();\n }\n}\n\nconst DialogBackdropCallable = callable(DialogBackdrop);\ntype DialogBackdropCallable = DialogBackdrop;\nexport {\n DialogBackdrop as _DialogBackdrop,\n DialogBackdropCallable as DialogBackdrop\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Panel } from \"~/core/Panel.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { DialogBackdrop } from \"~/component/container/DialogBackdrop.js\";\nimport { Border as BorderLayout } from \"~/layout/Border.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { circle_check } from \"~/glyphs/solid/circle_check.js\";\nimport { circle_info } from \"~/glyphs/solid/circle_info.js\";\nimport { triangle_exclamation } from \"~/glyphs/solid/triangle_exclamation.js\";\nimport { circle_exclamation } from \"~/glyphs/solid/circle_exclamation.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\nGlyph.register(xmark, circle_check, circle_info, triangle_exclamation, circle_exclamation);\n\n/**\n * The result produced when a dialog is dismissed.\n *\n * @category Core\n */\nexport type DialogResult = 'confirm' | 'cancel' | 'close';\n\n/**\n * Configuration for a single button in a dialog's button row.\n *\n * @category Core\n */\nexport interface DialogButtonConfig {\n /** The label text displayed on the button. */\n text : string;\n /** The result value emitted when this button is clicked. Defaults to `'cancel'`. */\n result? : DialogResult;\n /** When true, renders the button with primary (confirm) styling. */\n primary?: boolean;\n /** Optional registry glyph name shown to the left of the button label. */\n glyph? : string;\n /**\n * Optional foreground colour applied to the button's leading glyph. Accepts\n * any CSS colour string — typically a theme variable reference such as\n * `'var(--ts-ui-dialog-confirm-color)'`. When omitted the glyph inherits\n * the button's `currentColor`. The {@link DialogButtons} presets supply\n * the appropriate tint by default; reach for this field at the call site\n * only when overriding a preset or building a one-off button.\n */\n tint? : string;\n}\n\n/**\n * Severity tone for a dialog's title bar, mirroring\n * [`NotificationType`](/api/core/type-aliases/NotificationType). When set it\n * tints the header and shows a matching leading glyph, taking precedence over\n * the tone otherwise derived from the buttons.\n *\n * @category Core\n */\nexport type DialogSeverity = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * Configuration object passed to `new Dialog(config)` or `Dialog.show(config)`.\n *\n * @category Core\n */\nexport interface DialogConfig {\n /** Text displayed in the dialog title bar. */\n title : string;\n /** Plain-text body message. Ignored when `contentComponent` is provided. */\n message? : string;\n /** A custom component rendered in the content area instead of a message label. */\n contentComponent?: Component;\n /**\n * Button definitions for the footer row.\n * Defaults to a single OK button that resolves with `'confirm'`.\n */\n buttons? : DialogButtonConfig[];\n /** Dialog panel width in pixels. Defaults to 480. */\n width? : number;\n /** Dialog panel height in pixels. When omitted the height is computed from content. */\n height? : number;\n /** When true, clicking the backdrop closes the dialog with result `'close'`. Defaults to false. */\n closeOnBackdrop? : boolean;\n /**\n * When `false`, the dialog is a mandatory modal: no title-bar close button,\n * Escape does not close it, and a backdrop click does not close it (regardless\n * of `closeOnBackdrop`). Defaults to `true`.\n */\n dismissable? : boolean;\n /**\n * Optional severity tone for the title bar (`'info'`, `'success'`,\n * `'warning'`, `'error'`). When set it tints the header and shows a matching\n * leading glyph, overriding the tone derived from the buttons. Omit for the\n * default button-derived chrome.\n */\n severity? : DialogSeverity;\n /**\n * Component to receive focus when the dialog opens, overriding the default\n * (the first focusable element in the content region). Use it when the field\n * that should take focus is not the first one — a form whose first control is\n * a read-only summary, say. Ignored when the component has no focusable\n * element on open, which falls back to the default order.\n */\n initialFocus? : Component;\n}\n\n// ---------------------------------------------------------------------------\n// Private constants\n// ---------------------------------------------------------------------------\n\nconst TITLE_HEIGHT : number = 36;\nconst BUTTON_HEIGHT : number = 52;\nconst BUTTON_WIDTH : number = 90;\nconst BUTTON_GAP : number = 8;\nconst BUTTON_V_PAD : number = 11;\nconst CLOSE_SIZE : number = 20;\nconst TITLE_H_PAD : number = 12;\nconst MIN_DIALOG_WIDTH : number = 320;\nconst MIN_DIALOG_HEIGHT: number = 160;\nconst MIN_CONTENT_HEIGHT: number = 80;\n// Minimum gap kept between a content-resized dialog and each viewport edge, so a\n// dialog grown to tall content never runs flush to the top/bottom of the screen.\nconst DIALOG_VIEWPORT_MARGIN: number = 24;\n\n// Leading title-bar glyph per severity tone, mirroring Notification's badges.\nconst SEVERITY_GLYPH: Record<DialogSeverity, string> = {\n info: \"circle-info\",\n success: \"circle-check\",\n warning: \"triangle-exclamation\",\n error: \"circle-exclamation\",\n};\n\n/**\n * Shared duration (ms) for the dialog entrance/exit transition. The backdrop\n * fade and the panel's opacity + scale all run for this many milliseconds.\n */\nconst DIALOG_ANIM_DURATION_MS: number = 150;\n\n/**\n * CSS selector matching the focusable elements inside a dialog — the Tab\n * focus-trap boundary set, the initial-focus candidates, and the primary-button\n * lookup all share it so the notion of \"focusable\" stays single-sourced.\n */\nconst FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Vertical breathing room reserved above and below the title text inside the\n * `TITLE_HEIGHT` row. 4 px on each side: keeps the bold label off the divider\n * border and gives a balanced visual cap height. Used both for the text's\n * line-height (via {@link Text.centerInHeight}) and for its laid-out height\n * inside the bar.\n */\nconst TITLE_V_PAD: number = 4;\n\n/**\n * Horizontal gap between the close button and the right edge of the title\n * bar, plus the gap between the title text and the close button. Two\n * separate offsets share the same value so the close-button column reads as\n * a balanced `[gap][icon][gap]` strip.\n */\nconst TITLE_RIGHT_GAP: number = 4;\n\n/**\n * Horizontal gap between the optional leading glyph and the title text.\n * Matches the 8 px gutter the WindowHeader uses between its title icon and\n * label so the two surfaces feel consistent.\n */\nconst TITLE_GLYPH_TEXT_GAP: number = 8;\n\n// ---------------------------------------------------------------------------\n// Private: DialogTitleBar\n// ---------------------------------------------------------------------------\n\n/**\n * Title bar that occupies the NORTH slot of a Dialog's border layout.\n * Contains a text label on the left, an optional leading glyph, and a glyph\n * close button on the right.\n *\n * @remarks\n * Reach this instance via [`Dialog.getTitleBar()`](/api/overlay/classes/Dialog#gettitlebar) — there is no public\n * constructor. The supported surface is `getTitleText()` (for tinting the\n * title text colour), `setGlyph()` / `getGlyph()` (for the optional leading\n * icon), and any inherited [`Component`](/api/core/classes/Component) setter\n * (e.g. `setBackgroundColor`).\n *\n * @category Core\n */\nclass DialogTitleBar extends Component {\n\n private readonly _titleText : Text;\n private _closeButton: Button | null = null;\n private _titleGlyph: Glyph | null = null;\n\n /**\n * @param title - Text to display in the title bar.\n * @param onClose - Called when the user clicks the close button.\n * @param dismissable - When `false`, the close button is not built at all\n * (mandatory-modal title bar).\n */\n constructor(title: string, onClose: () => void, dismissable: boolean) {\n super();\n\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.setBorder({\n border: \"none\",\n borderBottom: \"1px solid var(--ts-ui-dialog-border)\",\n });\n this.setPreferredSize({ width: 0, height: TITLE_HEIGHT });\n\n this._titleText = new Text(title);\n this._titleText.setFontWeight(\"bold\");\n this._titleText.setOverflow(\"hidden\");\n this._titleText.setTextOverflow(\"ellipsis\");\n this._titleText.setWhiteSpace(\"nowrap\");\n // Centre the label within the inner area of the row (TITLE_HEIGHT\n // minus the top + bottom TITLE_V_PAD breathing room).\n this._titleText.centerInHeight(TITLE_HEIGHT - TITLE_V_PAD * 2);\n this.addComponent(this._titleText);\n\n if (dismissable) {\n this._closeButton = new Button({ glyph: \"xmark\" });\n this._closeButton.setInsets(new Insets(0, 0, 0, 0));\n this._closeButton.setBorder(\"none\");\n this._closeButton.clearBackgroundImage();\n this._closeButton.setBackgroundColor(\"transparent\");\n this._closeButton.clearShadow();\n this._closeButton.clearPressedShadow();\n this._closeButton.clearHoverShadow();\n // Hover and pressed background swap out the framework's gray\n // var(--ts-ui-button-hover-bg, …) for a translucent overlay that\n // darkens whatever tinted header sits underneath without\n // imposing its own colour. Also drop the hover gradient so it\n // doesn't double up over the overlay.\n this._closeButton.setHoverBackgroundColor(\"var(--ts-ui-titlebar-btn-hover-bg, rgba(0, 0, 0, 0.08))\");\n this._closeButton.setPressedBackgroundColor(\"var(--ts-ui-titlebar-btn-active-bg, rgba(0, 0, 0, 0.16))\");\n this._closeButton.clearHoverBackgroundImage();\n this._closeButton.clearPressedBackgroundImage();\n this._closeButton.setPreferredSize({ width: CLOSE_SIZE, height: CLOSE_SIZE });\n this.addComponent(this._closeButton);\n\n this._closeButton.on(\"action\", onClose);\n }\n }\n\n /**\n * Returns the title bar's close button, or `null` when the title bar was\n * built non-dismissable (no close affordance).\n *\n * @returns The close [`Button`](/api/component/button/classes/Button), or `null`.\n */\n getCloseButton(): Button | null {\n return this._closeButton;\n }\n\n /**\n * Returns the title-text component, for callers that need to tint or\n * otherwise restyle it from outside the title bar.\n *\n * @returns The internal title [`Text`](/api/component/input/classes/Text) instance.\n */\n getTitleText(): Text {\n return this._titleText;\n }\n\n /**\n * Sets or clears an optional leading glyph shown to the left of the title text.\n *\n * @param name - Registry glyph name to display, or `null` to clear an existing glyph.\n *\n * @returns This component, for method chaining.\n *\n * @remarks\n * The current implementation positions the glyph in `doLayout`; only the\n * notification-detail path uses this slot, and the glyph never coexists with\n * other left-side decoration on a Dialog title bar.\n */\n setGlyph(name: string): this {\n if (this._titleGlyph) {\n this.removeComponent(this._titleGlyph);\n this._titleGlyph = null;\n }\n\n const glyph = new Glyph(name);\n glyph.setPointerEvents(\"none\");\n glyph.setPreferredSize({ width: 16, height: 16 });\n this._titleGlyph = glyph;\n this.addComponent(glyph);\n\n this.doLayout();\n\n return this;\n }\n\n /**\n * Removes the leading title-bar glyph from the dialog, if one is present.\n *\n * @returns This component, for method chaining.\n */\n clearGlyph(): this {\n if (this._titleGlyph) {\n this.removeComponent(this._titleGlyph);\n this._titleGlyph = null;\n this.doLayout();\n }\n\n return this;\n }\n\n /**\n * Returns the optional leading title-glyph component, or null if none is set.\n *\n * @returns The leading [`Glyph`](/api/component/display/classes/Glyph) instance, or null.\n */\n getGlyph(): Glyph | null {\n return this._titleGlyph;\n }\n\n /**\n * Positions the title label, optional leading glyph, and close button within\n * the title bar's content box.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const box = this.getContentBounds();\n\n if (!box) {\n return this;\n }\n\n const w = box.width;\n const h = box.height;\n const closeX = w - CLOSE_SIZE - TITLE_RIGHT_GAP;\n const centerY = Math.floor((h - CLOSE_SIZE) / 2);\n const rightBound = this._closeButton\n ? closeX // reserve the close-button slot\n : (w - TITLE_H_PAD); // no button: label runs to the right pad\n\n let labelX = TITLE_H_PAD;\n\n if (this._titleGlyph) {\n const glyphSize = this._titleGlyph.getPreferredSize() ?? { width: 16, height: 16 };\n const glyphY = Math.max(0, Math.floor((h - glyphSize.height) / 2));\n\n this._titleGlyph.setX(box.x + TITLE_H_PAD);\n this._titleGlyph.setY(box.y + glyphY);\n this._titleGlyph.setWidth(glyphSize.width);\n this._titleGlyph.setHeight(glyphSize.height);\n\n labelX = TITLE_H_PAD + glyphSize.width + TITLE_GLYPH_TEXT_GAP;\n }\n\n // Reserve TITLE_RIGHT_GAP of space between the label and the close button.\n const labelWidth = Math.max(0, rightBound - labelX - TITLE_RIGHT_GAP);\n const labelH = h - TITLE_V_PAD * 2;\n\n this._titleText.setX(box.x + labelX);\n this._titleText.setY(box.y + TITLE_V_PAD);\n this._titleText.setWidth(labelWidth);\n this._titleText.setHeight(labelH);\n\n if (this._closeButton) {\n this._closeButton.setX(box.x + closeX);\n this._closeButton.setY(box.y + centerY);\n this._closeButton.setWidth(CLOSE_SIZE);\n this._closeButton.setHeight(CLOSE_SIZE);\n // setX/setY/setWidth/setHeight don't cascade — explicitly relayout the\n // close button so its internal Fit layout sizes the times glyph.\n this._closeButton.doLayout();\n }\n\n return this;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private: DialogButtonRow\n// ---------------------------------------------------------------------------\n\n/**\n * Footer row that occupies the SOUTH slot of a Dialog's border layout.\n * Lays out one or more buttons, right-aligned.\n */\nclass DialogButtonRow extends Component {\n\n private readonly _buttons: Button[] = [];\n\n /**\n * @param configs - Button definitions to render.\n * @param onButton - Called with the resolved [`DialogResult`](/api/overlay/type-aliases/DialogResult) when any button is clicked.\n */\n constructor(configs: DialogButtonConfig[], onButton: (result: DialogResult) => void) {\n super();\n\n this.setBorder({\n border: \"none\",\n borderTop: \"1px solid var(--ts-ui-dialog-border)\",\n });\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.setPreferredSize({ width: 0, height: BUTTON_HEIGHT });\n\n for (const cfg of configs) {\n const btn = new Button(cfg.text, cfg.glyph !== undefined ? { glyph: cfg.glyph } : undefined);\n const result = cfg.result ?? 'cancel';\n\n if (cfg.primary) {\n btn.setBackgroundImage(\"var(--ts-ui-toggle-selected-bg, rgb(200, 200, 200))\");\n }\n\n if (cfg.glyph !== undefined) {\n const glyph = btn.getGlyph();\n const tint = cfg.tint;\n\n if (glyph !== null && tint) {\n glyph.setForegroundColor(tint);\n }\n }\n\n btn.on(\"action\", () => onButton(result));\n this._buttons.push(btn);\n this.addComponent(btn);\n }\n }\n\n /**\n * Positions buttons right-aligned within the footer row's content box.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const box = this.getContentBounds();\n\n if (!box) {\n return this;\n }\n\n const btnH = box.height - BUTTON_V_PAD * 2;\n const totalW = this._buttons.length * BUTTON_WIDTH + (this._buttons.length - 1) * BUTTON_GAP;\n let x = Math.round((box.width - totalW) / 2);\n\n for (const btn of this._buttons) {\n btn.setX(box.x + x);\n btn.setY(box.y + BUTTON_V_PAD);\n btn.setWidth(BUTTON_WIDTH);\n btn.setHeight(btnH);\n\n btn.doLayout();\n\n x += BUTTON_WIDTH + BUTTON_GAP;\n }\n\n return this;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: Dialog\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical dialog button presets. Spread into the `buttons` array of a\n * {@link DialogConfig} to inherit the standard text / result / glyph mapping\n * for the three universal dismiss-row affordances; override `primary` per\n * call site since which button is default-focused is contextual (Cancel is\n * primary when paired with Confirm; Confirm is primary when it stands alone).\n *\n * Centralising the `glyph` here is the rule that prevents drift — the\n * checkmark/xmark mapping is bound to the button's identity, never re-typed\n * at the call site. {@link Dialog} also inspects the resolved result set to\n * pick the title-bar variant (only `confirm` → info header with leading\n * `circle-info`; `confirm` + `cancel` → affirm header).\n *\n * @category Core\n * @example\n * ```typescript\n * Dialog.show({\n * title: 'Delete record',\n * message: 'This cannot be undone.',\n * buttons: [\n * { ...DialogButtons.Cancel, primary: true },\n * DialogButtons.Confirm,\n * ],\n * });\n * ```\n */\nexport const DialogButtons = {\n /** Affirm an action — emits `'confirm'`, carries the green-tinted `circle-check` glyph. Paired with {@link Cancel} for action prompts. */\n Confirm: { text: 'Confirm', result: 'confirm', glyph: 'circle-check', tint: 'var(--ts-ui-dialog-confirm-color)' },\n /** Acknowledge information — emits `'confirm'`, carries the green-tinted `circle-check` glyph. Stands alone on informational dialogs. */\n Ok: { text: 'Ok', result: 'confirm', glyph: 'circle-check', tint: 'var(--ts-ui-dialog-confirm-color)' },\n /** Reject an action — emits `'cancel'`, carries the red-tinted `xmark` glyph. Paired with {@link Confirm}. */\n Cancel: { text: 'Cancel', result: 'cancel', glyph: 'xmark', tint: 'var(--ts-ui-dialog-cancel-color)' },\n /** Dismiss without choosing — emits `'close'`, carries the red-tinted `xmark` glyph (same icon family as cancel; the result value disambiguates). */\n Close: { text: 'Close', result: 'close', glyph: 'xmark', tint: 'var(--ts-ui-dialog-cancel-color)' },\n} as const satisfies Record<string, DialogButtonConfig>;\n\n/** Default button set when no buttons are supplied in config. */\nconst DEFAULT_BUTTONS: DialogButtonConfig[] = [\n { ...DialogButtons.Ok, primary: true },\n];\n\n/**\n * A modal dialog component with a title bar, scrollable content area, and button row.\n *\n * Use the static `Dialog.show(config)` convenience method for one-shot confirm/cancel\n * prompts, or construct an instance and call `show()` for fine-grained control.\n *\n * @example\n * ```typescript\n * const result = await Dialog.show({\n * title : 'Confirm deletion',\n * message: 'Are you sure you want to delete this record?',\n * buttons: [\n * { text: 'Delete', result: 'confirm', primary: true },\n * { text: 'Cancel', result: 'cancel' },\n * ],\n * });\n * if (result === 'confirm') { ... }\n * ```\n *\n * @category Core\n */\nclass Dialog extends Component implements DismissableLayer {\n\n private readonly _titleBar : DialogTitleBar;\n private readonly _contentContainer: Panel;\n private readonly _buttonRow : DialogButtonRow;\n private readonly _backdrop : DialogBackdrop;\n private readonly _config : DialogConfig;\n\n private _resolvePromise : ((result: DialogResult) => void) | null = null;\n private _previousFocus : Handle | null = null;\n private _boundKeyHandler : (e: KeyboardEvent) => Event.ListenerResult;\n private _boundResizeHandler: () => void;\n\n // In-flight entrance / dismiss animations for the panel and its backdrop,\n // cancelled on teardown so their fallback timers cannot fire against\n // released element handles.\n private _panelInAnimation : Animation.CancelHandle | null = null;\n private _backdropInAnimation : Animation.CancelHandle | null = null;\n private _panelOutAnimation : Animation.CancelHandle | null = null;\n private _backdropOutAnimation: Animation.CancelHandle | null = null;\n\n // True once `hide()`'s finalize has begun. `finalize` calls `destructor()`\n // partway through and finishes the rest afterwards, so the destructor uses\n // this to tell \"reached from a completing hide\" (leave the remainder to\n // finalize) from \"reached from a bare dispose\" (run it here, because the\n // cancelled dismiss animation will never call finalize at all).\n private _finalizing: boolean = false;\n\n /**\n * Constructs a Dialog but does not display it. Call `show()` to open.\n *\n * @param config - Dialog configuration.\n */\n constructor(config: DialogConfig) {\n super();\n\n this._config = config;\n\n const dialogWidth = Math.max(MIN_DIALOG_WIDTH, config.width ?? 480);\n const buttons = config.buttons ?? DEFAULT_BUTTONS;\n const contentHeight = Math.max(MIN_CONTENT_HEIGHT, this.computeContentHeight(config));\n const dialogHeight = Math.max(\n MIN_DIALOG_HEIGHT,\n config.height ?? (TITLE_HEIGHT + contentHeight + BUTTON_HEIGHT)\n );\n\n this.setPosition(Position.FIXED);\n this.setWidth(dialogWidth);\n this.setHeight(dialogHeight);\n // z-index is stamped from LayerManager's Dialog band at open() time\n // so stacked dialogs ascend monotonically; the backdrop is set one\n // below the panel there.\n this.setBackgroundColor(\"var(--ts-ui-body-bg)\");\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n this.setShadow(\"var(--ts-ui-dialog-shadow)\");\n this.setOverflow(\"hidden\");\n // Fixed dimensions, hidden overflow, no escaping descendants — full strict containment.\n this.setContain(\"strict\");\n\n const layout = new BorderLayout();\n layout.setComponentSpacing(0);\n this.setLayoutManager(layout);\n\n this._titleBar = new DialogTitleBar(config.title, () => this.hide('close'), config.dismissable !== false);\n this.addComponent(this._titleBar, { placement: Placement.NORTH });\n\n // An explicit severity tone wins over the tone derived from the buttons,\n // so an error/warning dialog reads as such regardless of its footer.\n if (config.severity) {\n this.applySeverityHeader(config.severity);\n } else {\n this.applyHeaderVariant(this.computeHeaderVariant(buttons));\n }\n\n // A Panel (not a bare Component) so the content area is a real scroll\n // region: `autoScroll: \"y\"` opts it into the framework's overflow\n // handling, and — because Panel does not clamp itself to its content size\n // (`clampsToContentSize` is false) — a dialog capped shorter than its\n // content (see resizeToContent) shrinks this container to the available\n // space and scrolls, instead of a bare Component flooring to its content\n // height and clipping. Vertical only: a horizontal scrollbar would cover\n // the bottom rows of body text and is rarely useful for dialog content.\n // Zero insets so content sits flush, matching the former bare container.\n this._contentContainer = new Panel({\n autoScroll: \"y\",\n layoutManager: new Fit(),\n insets: new Insets(0, 0, 0, 0),\n });\n\n if (config.contentComponent) {\n this._contentContainer.addComponent(config.contentComponent);\n } else {\n const messageText = new Text(config.message ?? '');\n messageText.setWhiteSpace(\"normal\");\n messageText.setWordBreak(\"break-word\");\n messageText.setPadding(new Insets(16, 16, 16, 16));\n this._contentContainer.addComponent(messageText);\n }\n\n this.addComponent(this._contentContainer, { placement: Placement.CENTER });\n\n this._buttonRow = new DialogButtonRow(buttons, (result) => this.hide(result));\n this.addComponent(this._buttonRow, { placement: Placement.SOUTH });\n\n this._backdrop = new DialogBackdrop();\n\n this._boundKeyHandler = (e: KeyboardEvent) => this.onKeyDown(e);\n this._boundResizeHandler = () => this.onViewportResize();\n }\n\n /**\n * Picks the title-bar tint variant for this dialog by inspecting the set\n * of button results. The variant is derived from the buttons rather than\n * a config flag so callers cannot signal one intent via buttons and a\n * conflicting intent via a separate option.\n *\n * @param buttons - The resolved button configuration array.\n * @returns `'info'` for a single confirm-result button (informational\n * dialog), `'affirm'` when both confirm and cancel are present\n * (affirmative-action dialog), `'plain'` otherwise.\n */\n private computeHeaderVariant(buttons: DialogButtonConfig[]): 'info' | 'affirm' | 'plain' {\n const results = new Set(buttons.map(b => b.result ?? 'cancel'));\n\n if (results.size === 1 && results.has('confirm')) {\n return 'info';\n }\n\n if (results.has('confirm') && results.has('cancel')) {\n return 'affirm';\n }\n\n return 'plain';\n }\n\n /**\n * Applies the chosen header variant's background, title-text foreground,\n * and (for the info variant only) a leading `circle-info` glyph. The\n * close button inside the title bar is intentionally untouched — dismiss\n * affordances stay on `currentColor` per the modal-glyph-theming rule.\n *\n * @param variant - One of `'info'`, `'affirm'`, or `'plain'`.\n */\n private applyHeaderVariant(variant: 'info' | 'affirm' | 'plain'): void {\n if (variant === 'plain') {\n return;\n }\n\n const bgVar = variant === 'info' ? 'var(--ts-ui-dialog-info-bg)' : 'var(--ts-ui-dialog-affirm-bg)';\n const fgVar = variant === 'info' ? 'var(--ts-ui-dialog-info-fg)' : 'var(--ts-ui-dialog-affirm-fg)';\n\n this._titleBar.setBackgroundColor(bgVar);\n this._titleBar.getTitleText().setForegroundColor(fgVar);\n\n if (variant === 'info') {\n this._titleBar.setGlyph('circle-info');\n\n const headerGlyph = this._titleBar.getGlyph();\n\n if (headerGlyph !== null) {\n headerGlyph.setForegroundColor(fgVar);\n }\n }\n }\n\n /**\n * Tints the title bar for an explicit {@link DialogSeverity}, reusing the\n * notification severity palette and leading glyph (matching the\n * notification-detail dialog). The border colour doubles as the title-text\n * and glyph foreground, as it does for a notification.\n *\n * @param severity - The severity tone to apply.\n */\n private applySeverityHeader(severity: DialogSeverity): void {\n const bgVar = `var(--ts-ui-notification-${severity}-bg)`;\n const fgVar = `var(--ts-ui-notification-${severity}-border)`;\n\n this._titleBar.setBackgroundColor(bgVar);\n this._titleBar.getTitleText().setForegroundColor(fgVar);\n this._titleBar.setGlyph(SEVERITY_GLYPH[severity]);\n\n const headerGlyph = this._titleBar.getGlyph();\n\n if (headerGlyph !== null) {\n headerGlyph.setForegroundColor(fgVar);\n }\n }\n\n /**\n * Computes the default content area height based on config.\n *\n * @param config - Dialog configuration.\n * @returns Height in pixels for the content region.\n *\n * @remarks\n * Content whose height depends on its width — a wrapping {@link Text} only\n * knows its line count once laid out — reports a single-line height here,\n * before it has been sized. {@link resizeToContent}, scheduled after the\n * first layout in {@link open}, re-fits the dialog once the content has\n * settled at the dialog width.\n */\n private computeContentHeight(config: DialogConfig): number {\n if (config.contentComponent) {\n const ps = config.contentComponent.getPreferredSize();\n\n if (ps) {\n return ps.height;\n }\n }\n\n return 100;\n }\n\n /**\n * Displays the dialog, attaches event listeners, and returns a promise that\n * resolves when the dialog is dismissed.\n *\n * @returns A promise resolving to the [`DialogResult`](/api/overlay/type-aliases/DialogResult) of the closing action.\n */\n show(): Promise<DialogResult> {\n return new Promise((resolve) => {\n this._resolvePromise = resolve;\n this.open();\n });\n }\n\n /**\n * Re-fits the dialog's height to its content's current preferred size and\n * re-centres it. A dialog's height is otherwise fixed at construction, so a\n * form whose content grows or shrinks after `show()` (e.g. add/remove rows)\n * is stretched or clipped to the original box; call this after mutating the\n * content so the dialog tracks it.\n *\n * The new height is `TITLE_HEIGHT + content + BUTTON_HEIGHT`, floored at the\n * dialog minimum and capped so the panel keeps a margin from the top and\n * bottom viewport edges — past that cap the content area scrolls (its\n * container is already `overflow-y: auto`). No-op before `show()` (nothing to\n * re-centre) and when the height is unchanged. Width is untouched.\n *\n * @returns This dialog, for method chaining.\n */\n resizeToContent(): this {\n if (!this.getElement()) {\n return this;\n }\n\n const contentHeight = Math.max(MIN_CONTENT_HEIGHT, this.computeContentHeight(this._config));\n const target = Math.max(MIN_DIALOG_HEIGHT, TITLE_HEIGHT + contentHeight + BUTTON_HEIGHT);\n\n const vp = DOM.source.getViewportSize();\n const capped = Math.min(target, Math.max(MIN_DIALOG_HEIGHT, vp.height - DIALOG_VIEWPORT_MARGIN * 2));\n\n if (capped === this.getHeight()) {\n return this;\n }\n\n this.setHeight(capped);\n this.scheduleLayout();\n this.center();\n\n return this;\n }\n\n /**\n * Appends backdrop and dialog to the DOM, centers the panel, and captures focus.\n */\n private open(): void {\n this._previousFocus = DOM.source.getActiveElement();\n\n if (this._config.closeOnBackdrop && this._config.dismissable !== false) {\n this._backdrop.addClickListener(() => { this.hide('close'); });\n }\n\n // Join the central layer tree and stamp the panel from the Dialog\n // band; the backdrop sits one below the panel so stacked dialogs keep\n // each panel above its own backdrop.\n LayerManager.register(this);\n\n const panelZ = LayerManager.getZIndex(this);\n this.setZIndex(panelZ);\n this._backdrop.setZIndex(panelZ - 1);\n\n const backdropEl = this._backdrop.getElement(true)!;\n LayerManager.mount(backdropEl);\n\n const dialogEl = this.getElement(true)!;\n LayerManager.mount(dialogEl);\n\n // Trap wheels no inner scroller claimed so the content behind a modal\n // dialog stays inert, matching modality.\n trapWheel(this);\n\n this.scheduleLayout();\n this.center();\n this.animateIn();\n\n // The construction-time height assumed single-line content; once the\n // first layout has sized the content at the dialog width, content whose\n // height depends on width (wrapping Text) has settled, so re-fit to it.\n // A no-op for content whose height did not change (resizeToContent bails\n // when the height is unchanged), so only width-dependent content reflows.\n Component.afterNextLayout(() => this.resizeToContent());\n\n Event.addViewportListener(this, 'keydown', this._boundKeyHandler);\n Event.addViewportListener(this, 'resize', this._boundResizeHandler);\n\n // Deferred past the scheduled layout: focusing synchronously here does\n // land on the right element, but the first layout then wraps the content\n // in its frame and re-parents the subtree into it. Moving a focused\n // element out of the document blurs it — silently, with no blur event —\n // so the focus is undone a frame later and lands nowhere.\n Component.afterNextLayout(() => this.focusFirst());\n }\n\n /**\n * Fades the backdrop in and the dialog panel in from `opacity: 0` +\n * `scale(0.97)` to `opacity: 1` + `scale(1)` over 150ms. No-op when\n * `prefers-reduced-motion: reduce` is set.\n */\n private animateIn(): void {\n const el = this.getElement();\n const bdEl = this._backdrop.getElement();\n\n if (!el) {\n return;\n }\n\n this._panelInAnimation?.cancel();\n this._panelInAnimation = Animation.play(el, {\n from: { opacity: \"0\", transform: \"scale(0.97)\" },\n to: { opacity: \"1\", transform: \"scale(1)\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\", \"transform\"],\n });\n\n if (bdEl) {\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = Animation.play(bdEl, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n }\n\n /**\n * Centers the dialog panel within the viewport.\n */\n private center(): void {\n const vp = DOM.source.getViewportSize();\n const x = Math.max(0, Math.round((vp.width - this.getWidth()) / 2));\n const y = Math.max(0, Math.round((vp.height - this.getHeight()) / 2));\n\n this.setX(x);\n this.setY(y);\n }\n\n /**\n * Moves initial focus into the dialog. Prefers the configured\n * `initialFocus` component, then the first focusable element in the content\n * region, so a form field — not the title-bar close button, which is first in\n * DOM order — receives focus on open. Falls back to the primary action\n * button, then to the first focusable element anywhere in the dialog.\n *\n * @remarks Must run after the dialog's first layout, which re-parents the\n * content into its frame and would blur anything focused before it.\n */\n private focusFirst(): void {\n const requested = this.requestedFocusElement();\n\n if (requested) {\n DOM.sink.focus(requested);\n\n return;\n }\n\n const contentEl = this._contentContainer.getElement();\n const inContent = contentEl ? DOM.source.querySelectorAll(contentEl, FOCUSABLE_SELECTOR) : [];\n\n if (inContent.length > 0) {\n DOM.sink.focus(inContent[0]);\n\n return;\n }\n\n const primary = this.primaryButtonElement();\n\n if (primary) {\n DOM.sink.focus(primary);\n\n return;\n }\n\n const el = this.getElement();\n const focusable = el ? DOM.source.querySelectorAll(el, FOCUSABLE_SELECTOR) : [];\n\n if (focusable.length > 0) {\n DOM.sink.focus(focusable[0]);\n }\n }\n\n /**\n * Resolves the configured `initialFocus` component to the element that\n * should take focus: the component's own root element when it is itself\n * focusable (a `TextField` renders as the `<input>`), otherwise its first\n * focusable descendant (a `Panel` wrapping a field).\n *\n * @returns The element to focus, or `null` when no `initialFocus` is\n * configured or it offers nothing focusable — both of which fall through\n * to the default order.\n */\n private requestedFocusElement(): Handle | null {\n const component = this._config.initialFocus;\n const element = component?.getElement();\n\n if (!element) {\n return null;\n }\n\n if (DOM.source.matches(element, FOCUSABLE_SELECTOR)) {\n return element;\n }\n\n return DOM.source.querySelector(element, FOCUSABLE_SELECTOR);\n }\n\n /**\n * Returns the DOM element of the primary action button, used as the initial\n * focus target when the content region has nothing focusable. Resolves by\n * position: the index of the `primary` entry in the resolved button set maps\n * to the same-indexed focusable in the button row.\n *\n * @returns The primary button element, or `null` when none is primary or the\n * row is not yet rendered.\n */\n private primaryButtonElement(): Handle | null {\n const buttons = this._config.buttons ?? DEFAULT_BUTTONS;\n const index = buttons.findIndex(b => b.primary);\n\n if (index < 0) {\n return null;\n }\n\n const rowEl = this._buttonRow.getElement();\n const focusable = rowEl ? DOM.source.querySelectorAll(rowEl, FOCUSABLE_SELECTOR) : [];\n\n return focusable[index] ?? null;\n }\n\n /**\n * Collects all currently focusable elements inside the dialog.\n *\n * @returns An array of focusable elements in DOM order.\n */\n private getFocusable(): Handle[] {\n const el = this.getElement();\n\n if (!el) {\n return [];\n }\n\n return DOM.source.querySelectorAll(el, FOCUSABLE_SELECTOR)\n .filter(el => !DOM.source.hasAttribute(el, 'disabled'));\n }\n\n /**\n * Handles document-level keydown events for Escape and Tab focus trapping.\n *\n * @param e - The keyboard event.\n * @returns A stop-and-prevent disposition when the dialog handles the key (the Tab trap, or Enter); nothing otherwise, so unhandled keys keep propagating.\n */\n private onKeyDown(e: KeyboardEvent): Event.ListenerResult {\n // Escape is owned by LayerManager's keydown handler, which closes the\n // topmost non-manual layer (this dialog when it is on top). The dialog\n // keeps only the Tab focus-trap and the Enter-confirms-the-primary\n // shortcut here.\n if (e.key === 'Enter') {\n return this.onEnter(e);\n }\n\n if (e.key === 'Tab') {\n const focusable = this.getFocusable();\n\n if (focusable.length === 0) {\n return { stop: true, prevent: true };\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (DOM.source.getActiveElement() === first) {\n DOM.sink.focus(last);\n\n return { stop: true, prevent: true };\n }\n } else {\n if (DOM.source.getActiveElement() === last) {\n DOM.sink.focus(first);\n\n return { stop: true, prevent: true };\n }\n }\n }\n\n return;\n }\n\n /**\n * Confirms the dialog on Enter by resolving the primary button's result,\n * so a simple form submits like one without the caller wiring Enter itself.\n *\n * @remarks Deliberately inert when focus is on a `<textarea>` (Enter inserts\n * a newline) or on a `<button>` (the button activates itself on Enter, and\n * hijacking it would fire the wrong action). No-op when no button is marked\n * `primary`, so a dialog with no clear default action does not submit blind.\n *\n * @param _e - The keydown event for the Enter press.\n * @returns `{ stop: true, prevent: true }` when Enter confirms the dialog; nothing when there is nothing to confirm.\n */\n private onEnter(_e: KeyboardEvent): Event.ListenerResult {\n const active = DOM.source.getActiveElement();\n const tag = active ? DOM.source.getTagName(active).toLowerCase() : null;\n\n if (tag === 'textarea' || tag === 'button') {\n return;\n }\n\n const result = this.primaryResult();\n\n if (result === null) {\n return;\n }\n\n this.hide(result);\n\n return { stop: true, prevent: true };\n }\n\n /**\n * Returns the result of the button marked `primary` in this dialog's\n * resolved button set — the action Enter confirms — or `null` when none is\n * primary.\n *\n * @returns The primary button's [`DialogResult`](/api/overlay/type-aliases/DialogResult), or `null`.\n */\n private primaryResult(): DialogResult | null {\n const buttons = this._config.buttons ?? DEFAULT_BUTTONS;\n const primary = buttons.find(b => b.primary);\n\n return primary ? (primary.result ?? 'cancel') : null;\n }\n\n /**\n * Re-fits the dialog to the resized viewport: {@link Dialog.resizeToContent}\n * grows it back toward its content when the viewport gained room, or caps it\n * (so the content area scrolls) when the viewport shrank below the content —\n * keeping the dialog within the viewport instead of overflowing it. The\n * backdrop is resized to the new viewport and the panel re-centred (again\n * unconditionally here, since `resizeToContent` skips re-centring when the\n * height is unchanged).\n */\n private onViewportResize(): void {\n this._backdrop.resize();\n this.resizeToContent();\n this.center();\n }\n\n /**\n * Dismisses the dialog with a brief fade-and-scale animation, restores\n * focus, and resolves the promise.\n *\n * @param result - The result to resolve the promise with.\n *\n * @remarks Honours `prefers-reduced-motion: reduce` — the transition is\n * skipped when motion is reduced.\n */\n hide(result: DialogResult): this {\n Event.removeViewportListener(this, 'keydown', this._boundKeyHandler);\n Event.removeViewportListener(this, 'resize', this._boundResizeHandler);\n\n const finalize = (): void => {\n this._finalizing = true;\n\n this._backdrop.destroy();\n this.removeElement();\n this.destructor();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n if (this._previousFocus !== null) {\n DOM.sink.focus(this._previousFocus);\n }\n\n if (this._resolvePromise) {\n this._resolvePromise(result);\n this._resolvePromise = null;\n }\n };\n\n const el = this.getElement();\n const bdEl = this._backdrop.getElement();\n\n if (!el) {\n finalize();\n return this;\n }\n\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = Animation.play(el, {\n to: { opacity: \"0\", transform: \"scale(0.97)\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\", \"transform\"],\n onComplete: finalize,\n });\n\n if (bdEl) {\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = Animation.play(bdEl, {\n to: { opacity: \"0\" },\n durationMs: DIALOG_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n\n return this;\n }\n\n /**\n * Cancels any in-flight panel / backdrop animation, then defers to the base\n * class. Reached from `hide()`'s completion callback as well as from a\n * direct dispose: on that path the dismiss animation is already finished, so\n * cancelling it is a no-op.\n */\n protected destructor(): void {\n this._panelInAnimation?.cancel();\n this._panelInAnimation = null;\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = null;\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = null;\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = null;\n\n // A dispose that lands mid-dismiss cancels the animation whose\n // completion callback owns the rest of teardown, so run that work here.\n // The backdrop is a private field rather than a registered child, so the\n // base class's recursion cannot reach it and it would otherwise stay\n // mounted over the whole app; the promise `show()` handed the caller\n // would never settle. Skipped when `finalize` is already running, which\n // reaches this method partway through and completes the rest itself —\n // including resolving with the caller's real result rather than the\n // `\"close\"` stand-in used here.\n if (!this._finalizing) {\n this._backdrop.destroy();\n\n LayerManager.unregister(this);\n untrapWheel(this);\n\n if (this._resolvePromise) {\n this._resolvePromise('close');\n this._resolvePromise = null;\n }\n }\n\n super.destructor();\n }\n\n /**\n * Returns the content container component where custom content is rendered.\n *\n * @returns The content container [`Component`](/api/core/classes/Component).\n */\n getContentComponent(): Component {\n return this._contentContainer;\n }\n\n /**\n * The dialog sizes itself explicitly — its height is computed to fit its\n * content at construction and re-fit (capped to the viewport) in\n * {@link Dialog.resizeToContent} — so it must not additionally floor itself\n * to its content's min-size. If it did, a dialog whose content is taller\n * than the viewport could not shrink to the capped height, and its\n * `autoScroll` content container would never get the constrained space it\n * needs to scroll (it would clip instead). The `MIN_DIALOG_HEIGHT` floor is\n * applied explicitly wherever the height is set.\n */\n protected clampsToContentSize(): boolean {\n return false;\n }\n\n /**\n * Returns the dialog's title-bar component.\n *\n * @returns The internal title-bar instance, exposing `getTitleText()` and\n * `setGlyph()` for callers (e.g. the notification detail dialog)\n * that need to tint or decorate the header.\n *\n * @remarks\n * The `DialogTitleBar` class itself is not exported — callers reach it\n * only through this accessor and interact via its few public methods\n * (`getTitleText`, `setGlyph`, `getGlyph`).\n */\n getTitleBar(): DialogTitleBar {\n return this._titleBar;\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the dialog panel's root element for the central layer tree.\n *\n * @returns The dialog'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 dialog\n * is `\"modal\"`: the manager neither dismisses ancestors on an outside\n * interaction nor lets one fall through, and it owns the Escape-to-close\n * shortcut. The dialog keeps its own Tab focus-trap.\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return \"modal\";\n }\n\n /**\n * Advisory close request from the manager — closes the dialog with the\n * `'close'` result, matching the title-bar close affordance.\n *\n * @remarks No-op when `dismissable` is `false`: this is the Escape path\n * (`LayerManager` routes Escape to the topmost non-`\"manual\"` layer's\n * `requestClose()`), so a mandatory modal swallows Escape rather than\n * closing. `getDismissMode()` deliberately stays `\"modal\"` here — a\n * `\"manual\"` layer would be skipped by the Escape loop, letting Escape\n * fall through to close a layer beneath this one.\n */\n requestClose(): void {\n if (this._config.dismissable === false) {\n return;\n }\n\n this.hide('close');\n }\n\n /**\n * Returns the dialog's z-index band so unrelated dialogs stack above\n * every other overlay family.\n *\n * @returns The dialog band base.\n */\n getBand(): number {\n return LayerManager.Band.Dialog;\n }\n\n /**\n * Displays a modal dialog and returns a promise that resolves on dismissal.\n *\n * @param config - Dialog configuration.\n * @returns A promise resolving to the [`DialogResult`](/api/overlay/type-aliases/DialogResult) of the closing action.\n *\n * @example\n * ```typescript\n * const result = await Dialog.show({ title: 'Confirm', message: 'Proceed?' });\n * ```\n */\n static show(config: DialogConfig): Promise<DialogResult> {\n const dialog = new Dialog(config);\n\n return dialog.show();\n }\n\n /**\n * Displays a confirm/cancel dialog and resolves to `true` when the user confirms.\n *\n * Buttons are ordered Cancel (default focus) then Confirm, so pressing Enter or\n * Escape both safely default to cancellation.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n * @returns A promise resolving to `true` if the user clicked Confirm, `false` otherwise.\n *\n * @example\n * ```typescript\n * if (await Dialog.confirm('Delete record', 'This cannot be undone.')) {\n * store.remove(record);\n * }\n * ```\n */\n static async confirm(title: string, message: string): Promise<boolean> {\n const result = await Dialog.show({\n title,\n message,\n buttons: [\n { ...DialogButtons.Cancel, primary: true },\n DialogButtons.Confirm,\n ],\n });\n\n return result === 'confirm';\n }\n\n /**\n * Displays a severity-toned dialog with a single OK button and resolves once\n * the user acknowledges it. Shared by the {@link Dialog.info} /\n * {@link Dialog.success} / {@link Dialog.warning} / {@link Dialog.error}\n * shorthands.\n *\n * @param severity - The title-bar severity tone (see {@link DialogConfig.severity}).\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n */\n private static async alert(severity: DialogSeverity, title: string, message: string): Promise<void> {\n await Dialog.show({\n title,\n message,\n severity,\n buttons: [{ ...DialogButtons.Ok, primary: true }],\n });\n }\n\n /**\n * Displays an info-toned dialog with a single OK button and resolves once the\n * user acknowledges it.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.info('Import complete', 'Loaded 1,204 rows.');\n * ```\n */\n static info(title: string, message: string): Promise<void> {\n return Dialog.alert('info', title, message);\n }\n\n /**\n * Displays a success-toned dialog with a single OK button and resolves once\n * the user acknowledges it.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.success('Saved', 'Your changes have been stored.');\n * ```\n */\n static success(title: string, message: string): Promise<void> {\n return Dialog.alert('success', title, message);\n }\n\n /**\n * Displays a warning-toned dialog with a single OK button and resolves once\n * the user acknowledges it.\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.warning('Unsaved changes', 'They will be lost if you continue.');\n * ```\n */\n static warning(title: string, message: string): Promise<void> {\n return Dialog.alert('warning', title, message);\n }\n\n /**\n * Displays an error-toned dialog with a single OK button and resolves once\n * the user acknowledges it. The title bar carries the error severity tint and\n * glyph (see {@link DialogConfig.severity}).\n *\n * @param title - Text displayed in the title bar.\n * @param message - Body message shown in the content area.\n *\n * @example\n * ```typescript\n * await Dialog.error('Connection failed', 'Host not allowed.');\n * ```\n */\n static error(title: string, message: string): Promise<void> {\n return Dialog.alert('error', title, message);\n }\n}\n\nconst DialogCallable = callable(Dialog);\ntype DialogCallable = Dialog;\nexport {\n Dialog as _Dialog,\n DialogCallable as Dialog,\n DialogTitleBar\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager } from \"~/core/LayerManager.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { _Dialog, DialogButtons } from \"~/overlay/Dialog.js\";\nimport { circle_info } from \"~/glyphs/solid/circle_info.js\";\nimport { circle_check } from \"~/glyphs/solid/circle_check.js\";\nimport { triangle_exclamation } from \"~/glyphs/solid/triangle_exclamation.js\";\nimport { circle_exclamation } from \"~/glyphs/solid/circle_exclamation.js\";\nimport { xmark } from \"~/glyphs/solid/xmark.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\nGlyph.register(circle_info, circle_check, triangle_exclamation, circle_exclamation, xmark);\n\n/**\n * The visual severity of a notification.\n *\n * @category Core\n */\nexport type NotificationType = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * A single captured notification, retained in the in-session history returned\n * by {@link Notification.getHistory}.\n *\n * @category Core\n */\nexport interface NotificationRecord {\n /** The full (un-truncated) message text passed to {@link Notification.show}. */\n readonly message: string;\n /** The severity type the toast was shown with. */\n readonly type: NotificationType;\n /** Epoch milliseconds (`Date.now()`) when the toast was shown. */\n readonly timestamp: number;\n}\n\n/**\n * Maps a notification severity to its registry glyph name. Exported (module,\n * not barrel — so it stays out of the public API docs) so the notification\n * history menu reuses the same severity-icon mapping rather than duplicating it.\n */\nexport const BADGE_GLYPH: Record<NotificationType, string> = {\n info: \"circle-info\",\n success: \"circle-check\",\n warning: \"triangle-exclamation\",\n error: \"circle-exclamation\",\n};\n\nconst DETAIL_TITLE: Record<NotificationType, string> = {\n info: \"Information\",\n success: \"Success\",\n warning: \"Warning\",\n error: \"Error\",\n};\n\nconst MIN_RESUMED_MS: number = 8000;\nconst DISMISS_DURATION_MS: number = 200;\nconst ENTRANCE_DURATION_MS: number = 200;\n\n/**\n * A lightweight toast-style notification that appears in the bottom-right corner\n * of the viewport and auto-dismisses after a configurable duration.\n *\n * Multiple notifications stack upward. Each can also be dismissed manually via\n * the × button. Long messages are clipped to two lines with an ellipsis; a\n * double-click on the body opens a modal detail dialog containing the full\n * message.\n *\n * @example\n * ```typescript\n * Notification.show('Record saved.', 'success');\n * Notification.show('Connection lost.', 'error', 0); // persistent\n * ```\n *\n * @category Core\n */\nexport class Notification extends Component {\n\n // In-flight entrance / dismiss animations, cancelled on teardown so their\n // fallback timers cannot fire against this notification's released handle.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _dismissAnimation: Animation.CancelHandle | null = null;\n\n private static readonly WIDTH: number = 320;\n private static readonly HEIGHT: number = 64;\n private static readonly MARGIN: number = 16;\n private static readonly H_PADDING: number = 12;\n private static readonly V_PADDING: number = 10;\n private static readonly CLOSE_SIZE: number = 20;\n private static readonly BADGE_SIZE: number = 20;\n private static readonly BADGE_TEXT_GAP: number = 8;\n // Stacking z-index for toasts. Sits just above the managed dropdown band\n // (`LayerManager.Band.Dropdown` = 10000) so a toast floats over open pickers\n // and menus, yet below the Dialog band (11000) so the modal detail dialog a\n // toast can open covers it. A fixed literal rather than a `Band` allocation\n // because a `Notification` is not a registered layer — it never joins the\n // dismiss / stacking tree, so it has no node for the manager to stamp.\n private static readonly Z_INDEX: number = 10002;\n\n private static activeNotifications: Notification[] = [];\n\n // The most-recent notifications retained by the in-session history. A fixed\n // ring cap keeps memory trivial and the history menu scrollable-but-finite;\n // oldest entries are evicted first.\n private static readonly HISTORY_CAP: number = 50;\n private static history: NotificationRecord[] = [];\n\n // The auto-dismiss timer of every visible notification is paused while\n // either of these counters is positive. `hoverCount` tracks how many\n // notifications the pointer is currently over (so hovering one freezes\n // the whole stack until the pointer leaves the last one); `modalCount`\n // tracks outstanding calls to Notification.pauseAll() (a balanced pair\n // with resumeAll() that callers use to bracket their own modal flows).\n // When the combined paused state transitions back to false, every\n // notification's timer is restarted with at least `MIN_RESUMED_MS` of\n // remaining duration whenever a modal hold was the last released.\n private static hoverCount: number = 0;\n private static modalCount: number = 0;\n\n private readonly _type: NotificationType;\n private readonly _fullMessage: string;\n private readonly _badge: Glyph;\n private readonly _messageText: Text;\n private readonly _closeButton: Button;\n private _dismissTimer: ReturnType<typeof setTimeout> | null = null;\n private _remainingDuration: number = 0;\n private _timerStartedAt: number = 0;\n private _dismissing: boolean = false;\n\n // Named listener refs (removable, grep-able, named in stack traces) for the\n // close button's action, the body double-click, and the hover hold pair.\n private readonly _boundOnCloseAction: () => Event.ListenerResult = () => {\n this.dismiss();\n\n // Prevent the click from contributing to a double-click on the body.\n return true;\n };\n private readonly _boundOnDblClick: () => void = () => this.openDetail();\n private readonly _boundOnMouseOver: (e: MouseEvent) => void = (e) => Notification.acquireHoverHold(e, this.getElement());\n private readonly _boundOnMouseOut: (e: MouseEvent) => void = (e) => Notification.releaseHoverHold(e, this.getElement());\n\n /**\n * Private — use `Notification.show()` to create and display instances.\n *\n * @param message - The text to display inside the notification.\n * @param type - The severity type that controls the colour scheme.\n */\n private constructor(message: string, type: NotificationType) {\n super();\n\n this._type = type;\n this._fullMessage = message;\n\n this.setPosition(Position.FIXED);\n this.setZIndex(Notification.Z_INDEX);\n this.setWidth(Notification.WIDTH);\n this.setHeight(Notification.HEIGHT);\n this.setOverflow(\"hidden\");\n // Fixed size, fixed position, hidden overflow — full strict containment.\n this.setContain(\"strict\");\n\n // Live-region semantics so screen readers announce the toast when it is\n // inserted. Errors and warnings interrupt (`alert`/`assertive`);\n // informational and success toasts wait their turn (`status`/`polite`).\n const assertive = type === 'error' || type === 'warning';\n this.getAria().setRole(assertive ? \"alert\" : \"status\");\n this.getAria().setLive(assertive ? \"assertive\" : \"polite\");\n\n const bgVar = `var(--ts-ui-notification-${type}-bg)`;\n const borderVar = `var(--ts-ui-notification-${type}-border)`;\n const shadowVar = `var(--ts-ui-notification-shadow)`;\n\n this.setBackgroundColor(bgVar);\n this.setBorder({ border: `1px solid ${borderVar}` });\n this.setShadow(shadowVar);\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n this._badge = new Glyph(BADGE_GLYPH[type]);\n this._badge.setForegroundColor(borderVar);\n this._badge.setPreferredSize({ width: Notification.BADGE_SIZE, height: Notification.BADGE_SIZE });\n this._badge.setPointerEvents(\"none\");\n // Decorative severity icon — its meaning is already carried by the\n // message text, so keep it out of the announced live-region content.\n this._badge.getAria().setHidden(true);\n this.addComponent(this._badge);\n\n this._messageText = new Text(message);\n // Webkit-prefixed line clamp is now cross-browser (Chrome, Edge, Safari,\n // Firefox 68+). Clamped to two lines so a long message gets a trailing\n // ellipsis — the full text is reachable via double-click → detail dialog.\n this._messageText.setLineClamp(2);\n this._messageText.setWhiteSpace(\"normal\");\n this._messageText.setWordBreak(\"break-word\");\n this.addComponent(this._messageText);\n\n this._closeButton = new Button({ glyph: \"xmark\" });\n this._closeButton.setInsets(new Insets(0, 0, 0, 0));\n this._closeButton.setBorder(\"none\");\n this._closeButton.clearBackgroundImage();\n this._closeButton.setBackgroundColor(\"transparent\");\n this._closeButton.clearShadow();\n this._closeButton.clearPressedShadow();\n this._closeButton.setForegroundColor(\"var(--ts-ui-text-color, rgb(0, 0, 0))\");\n this._closeButton.setPreferredSize({ width: Notification.CLOSE_SIZE, height: Notification.CLOSE_SIZE });\n this._closeButton.getAria().setLabel(\"Dismiss notification\");\n this.addComponent(this._closeButton);\n\n // Route through the button's own `\"action\"` surface rather than reaching\n // into its DOM `click` via the Event API (a component must not listen to\n // another component's events through Event). The handler's returned `true`\n // stops propagation, so the dblclick-suppressing consume is preserved.\n this._closeButton.on(\"action\", this._boundOnCloseAction);\n\n // addSubtreeListener so double-clicks on the badge / text bubble up.\n Event.addSubtreeListener(this, \"dblclick\", this._boundOnDblClick);\n\n // Subtree mouseover / mouseout on the root. These bubble from every\n // descendant of the toast, so the handlers below filter out\n // intra-element movements via `relatedTarget`. mouseenter / mouseleave\n // look cleaner on paper but proved unreliable here in practice —\n // mouseleave didn't always fire on a root carrying a non-empty\n // `transition` CSS rule left over from the entrance animation.\n // Subtree listeners route through `Event`'s window-level base\n // listener, so `e.currentTarget` resolves to `window` (which has no\n // `.contains` method) — pass the toast root explicitly instead.\n Event.addSubtreeListener(this, \"mouseover\", this._boundOnMouseOver);\n Event.addSubtreeListener(this, \"mouseout\", this._boundOnMouseOut);\n }\n\n /**\n * Displays a notification toast in the bottom-right corner of the viewport.\n *\n * @param message - The text to display.\n * @param type - The severity type; controls background and border colour. Defaults to `'info'`.\n * @param duration - How long in milliseconds before the notification auto-dismisses.\n * Pass `0` for a persistent notification. Defaults to `3000`.\n */\n static show(message: string, type: NotificationType = 'info', duration: number = 3000): void {\n Notification.record(message, type);\n\n const n = new Notification(message, type);\n\n Notification.activeNotifications.push(n);\n\n const el = n.getElement(true)!;\n\n n.scheduleLayout();\n\n LayerManager.mount(el);\n\n Notification.restack();\n n.animateIn();\n\n if (duration > 0) {\n n.startTimer(duration);\n\n // If another toast is currently hovered, or a modal hold is open,\n // freeze the brand-new toast too so it doesn't dismiss out from\n // under the user while they're still reading the stack.\n if (Notification.isPaused()) {\n n.pauseTimer();\n }\n }\n }\n\n /**\n * Appends one entry to the in-session history, evicting the oldest once the\n * {@link HISTORY_CAP} ceiling is exceeded. Called by {@link show} for every\n * toast; browsing history via {@link showDetail} deliberately does not\n * record, so \"history = everything ever shown this session, one entry per\n * `show()`\".\n *\n * @param message - The full message text.\n * @param type - The severity type.\n */\n private static record(message: string, type: NotificationType): void {\n Notification.history.push({ message, type, timestamp: Date.now() });\n\n if (Notification.history.length > Notification.HISTORY_CAP) {\n Notification.history.shift();\n }\n }\n\n /**\n * Returns the in-session notification history, oldest first, capped at the\n * most recent 50 entries. The returned array is a defensive copy — mutating\n * it does not affect the retained history.\n *\n * @returns A copy of the history entries, oldest first.\n */\n static getHistory(): readonly NotificationRecord[] {\n return [...Notification.history];\n }\n\n /**\n * Slides the notification in from the right while fading from `opacity: 0`\n * to `opacity: 1` over 200ms. No-op when `prefers-reduced-motion: reduce`\n * is set — the toast snaps into place immediately.\n */\n private animateIn(): void {\n const el = this.getElement();\n\n if (!el) {\n return;\n }\n\n this._showAnimation?.cancel();\n this._showAnimation = Animation.play(el, {\n from: { transform: \"translateX(100%)\", opacity: \"0\" },\n to: { transform: \"translateX(0)\", opacity: \"1\" },\n durationMs: ENTRANCE_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n });\n }\n\n /**\n * Pauses the auto-dismiss timer of every currently visible notification.\n * Balanced with {@link Notification.resumeAll}; nested pause/resume pairs\n * compose, and the combined paused state is released only when every\n * acquired hold has been released.\n *\n * @remarks Intended for use by code that opens a modal flow during which\n * the user is unable to read or interact with active notifications.\n * Resumed timers are then clamped to a minimum of 8 seconds.\n */\n static pauseAll(): void {\n const wasPaused = Notification.isPaused();\n Notification.modalCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Releases one outstanding {@link Notification.pauseAll} hold. When the\n * last modal hold AND every hover hold is released, the stack's timers\n * are restarted with at least 8 seconds of remaining duration so the\n * user has time to read the toasts after the modal dismissal.\n */\n static resumeAll(): void {\n if (Notification.modalCount === 0) {\n return;\n }\n\n Notification.modalCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(true);\n }\n }\n\n /**\n * Increments the hover refcount when the supplied `mouseover` event\n * represents an actual entry into a notification (not a movement\n * between two of its children). The first hover on any notification\n * pauses the entire stack so the user can read without timers eating\n * notifications mid-glance.\n *\n * @param e - The native `mouseover` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static acquireHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n const wasPaused = Notification.isPaused();\n Notification.hoverCount += 1;\n if (!wasPaused) {\n Notification.pauseAllTimers();\n }\n }\n\n /**\n * Decrements the hover refcount when the supplied `mouseout` event\n * represents an actual exit (cursor moving outside the notification,\n * not just between two of its children). When the last hover hold AND\n * every outstanding modal hold has been released, every notification's\n * timer is restarted with whatever remaining duration was captured at\n * pause time (no minimum clamp on the hover-only path).\n *\n * @param e - The native `mouseout` event from the toast's root element.\n * @param el - The toast root element (the `Event`-routed listener can't\n * rely on `e.currentTarget` — that's `window` here).\n */\n private static releaseHoverHold(e: MouseEvent, el: Handle | undefined): void {\n if (el && DOM.source.isNode(e.relatedTarget) && DOM.source.contains(el, DOM.source.intern(e.relatedTarget))) {\n return;\n }\n\n if (Notification.hoverCount === 0) {\n return;\n }\n\n Notification.hoverCount -= 1;\n if (!Notification.isPaused()) {\n Notification.resumeAllTimers(false);\n }\n }\n\n /**\n * Returns `true` when any hover or modal hold is currently active.\n */\n private static isPaused(): boolean {\n return Notification.hoverCount > 0 || Notification.modalCount > 0;\n }\n\n /**\n * Pauses every active notification's auto-dismiss timer.\n */\n private static pauseAllTimers(): void {\n for (const n of Notification.activeNotifications) {\n n.pauseTimer();\n }\n }\n\n /**\n * Restarts every active notification's auto-dismiss timer with its\n * captured remaining duration. When `clampMin` is true, the remaining\n * duration is bumped to at least {@link MIN_RESUMED_MS} so a modal\n * release leaves the user enough time to read the stack.\n */\n private static resumeAllTimers(clampMin: boolean): void {\n for (const n of Notification.activeNotifications) {\n n.restartTimer(clampMin);\n }\n }\n\n /**\n * Starts (or restarts) the auto-dismiss timer with the given duration and\n * records when it began so remaining time can be calculated on hover.\n *\n * @param ms - Milliseconds until the notification is dismissed.\n */\n private startTimer(ms: number): void {\n this._remainingDuration = ms;\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), ms);\n }\n\n /**\n * Pauses the auto-dismiss timer when the pointer enters the notification.\n * Stores how many milliseconds were left so `resumeTimer` can pick up where it left off.\n */\n private pauseTimer(): void {\n if (this._dismissTimer === null) {\n return;\n }\n\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n this._remainingDuration = Math.max(0, this._remainingDuration - (Date.now() - this._timerStartedAt));\n }\n\n /**\n * Restarts this notification's auto-dismiss timer with its captured\n * remaining duration. When `clampMin` is true, the remaining duration\n * is bumped to {@link MIN_RESUMED_MS} so the user gets enough time to\n * read what's left after a modal release.\n *\n * @param clampMin - Apply the modal-resume minimum clamp.\n */\n private restartTimer(clampMin: boolean): void {\n if (this._dismissing) {\n return;\n }\n\n if (this._remainingDuration <= 0 || this._dismissTimer !== null) {\n return;\n }\n\n if (clampMin) {\n this._remainingDuration = Math.max(this._remainingDuration, MIN_RESUMED_MS);\n }\n\n this._timerStartedAt = Date.now();\n this._dismissTimer = setTimeout(() => this.dismiss(), this._remainingDuration);\n }\n\n /**\n * Opens the modal detail dialog for this toast's full (un-truncated) message.\n * Delegates to {@link showDetail}; a double-click on the toast body routes\n * here.\n */\n private openDetail(): void {\n Notification.showDetail(this._fullMessage, this._type);\n }\n\n /**\n * Opens a modal detail dialog showing the full message text — the same dialog\n * a live toast opens on double-click. Active notification timers are paused\n * while the dialog is open and clamped to a minimum of 8 seconds when the\n * dialog is dismissed. Does not itself record a history entry, so re-opening a\n * past notification from the history menu leaves the history unchanged.\n *\n * @param message - The full message text to display.\n * @param type - The severity type; controls the title and title-bar tint.\n */\n static showDetail(message: string, type: NotificationType): void {\n Notification.pauseAll();\n\n const content = new Text(message);\n content.setAutoMeasure(false);\n content.setWhiteSpace(\"pre-wrap\");\n content.setWordBreak(\"break-word\");\n content.setPadding(new Insets(16, 16, 16, 16));\n\n const dialog = new _Dialog({\n title: DETAIL_TITLE[type],\n contentComponent: content,\n buttons: [{ ...DialogButtons.Close, primary: true }],\n width: 420,\n height: 220,\n });\n\n // Tint the title bar to match the notification's severity colours.\n const titleBar = dialog.getTitleBar();\n titleBar.setBackgroundColor(`var(--ts-ui-notification-${type}-bg)`);\n titleBar.getTitleText().setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n titleBar.setGlyph(BADGE_GLYPH[type]);\n\n const titleGlyph = titleBar.getGlyph();\n\n if (titleGlyph !== null) {\n titleGlyph.setForegroundColor(`var(--ts-ui-notification-${type}-border)`);\n }\n\n dialog.show().then(() => Notification.resumeAll());\n }\n\n /**\n * Slides the notification rightward while fading it out, then removes the\n * element from the DOM and restacks the remaining notifications.\n */\n private dismiss(): void {\n if (this._dismissing) {\n return;\n }\n\n this._dismissing = true;\n\n if (this._dismissTimer !== null) {\n clearTimeout(this._dismissTimer);\n this._dismissTimer = null;\n }\n\n const el = this.getElement();\n\n if (!el) {\n this.finishDismiss();\n return;\n }\n\n this._dismissAnimation?.cancel();\n this._dismissAnimation = Animation.play(el, {\n to: { transform: \"translateX(100%)\", opacity: \"0\" },\n durationMs: DISMISS_DURATION_MS,\n properties: [\"transform\", \"opacity\"],\n onComplete: () => this.finishDismiss(),\n });\n }\n\n /**\n * Removes this notification from the active stack and from the DOM.\n */\n private finishDismiss(): void {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n\n this.removeElement();\n\n Notification.restack();\n }\n\n /**\n * Recalculates the Y position of every active notification so they\n * stack upward from the bottom-right corner without overlapping.\n */\n private static restack(): void {\n const vp = DOM.source.getViewportSize();\n const x = vp.width - Notification.WIDTH - Notification.MARGIN;\n\n let y = vp.height - Notification.MARGIN;\n\n for (let i = Notification.activeNotifications.length - 1; i >= 0; i--) {\n const n = Notification.activeNotifications[i];\n\n y -= Notification.HEIGHT;\n n.setX(x);\n n.setY(y);\n y -= Notification.MARGIN;\n }\n }\n\n /**\n * Positions the badge glyph, message label, and close button within the\n * notification body.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const closeX = Notification.WIDTH - Notification.CLOSE_SIZE - 4;\n const badgeX = Notification.H_PADDING;\n const badgeY = Notification.V_PADDING + 2;\n const msgX = badgeX + Notification.BADGE_SIZE + Notification.BADGE_TEXT_GAP;\n const msgWidth = closeX - msgX - 4;\n const msgHeight = Notification.HEIGHT - Notification.V_PADDING * 2;\n\n this._badge.setX(badgeX);\n this._badge.setY(badgeY);\n this._badge.setWidth(Notification.BADGE_SIZE);\n this._badge.setHeight(Notification.BADGE_SIZE);\n\n this._messageText.setX(msgX);\n this._messageText.setY(Notification.V_PADDING);\n this._messageText.setWidth(msgWidth);\n this._messageText.setHeight(msgHeight);\n\n this._closeButton.setX(closeX);\n this._closeButton.setY(4);\n this._closeButton.setWidth(Notification.CLOSE_SIZE);\n this._closeButton.setHeight(Notification.CLOSE_SIZE);\n // Cascade the size change down to the times-glyph through the\n // close button's internal Fit/HBox layout.\n this._closeButton.doLayout();\n\n return this;\n }\n\n /**\n * Cancels any in-flight entrance / dismiss animation, then defers to the\n * base class. Cancelling first keeps their fallback timers from firing\n * after `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._dismissAnimation?.cancel();\n this._dismissAnimation = null;\n\n // `finishDismiss` is the only place a notification leaves the static\n // active list, and cancelling above suppressed it. That list outlives\n // every teardown, and `restack` writes setX/setY to each entry — so a\n // disposed notification left in it is positioned through the element\n // handle released below. Re-stack afterwards so the survivors close the\n // gap, exactly as a completed dismiss would have left them.\n if (Notification.activeNotifications.includes(this)) {\n Notification.activeNotifications = Notification.activeNotifications.filter(n => n !== this);\n Notification.restack();\n }\n\n super.destructor();\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { MenuButton, MenuButtonOptions } from \"~/component/button/MenuButton.js\";\nimport { MenuItemConfig } from \"~/component/container/MenuItem.js\";\nimport { Notification, BADGE_GLYPH } from \"~/overlay/Notification.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { clock_rotate_left } from \"~/glyphs/solid/clock_rotate_left.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(clock_rotate_left);\n\nconst MINUTE_MS = 60_000;\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\n\n/**\n * Formats the age of a notification as a compact relative-time string\n * (`\"just now\"`, `\"5m ago\"`, `\"2h ago\"`, `\"3d ago\"`). A future timestamp\n * (clock skew) clamps to `\"just now\"`. Module-internal (not barrel-exported, so\n * it stays out of the public API) — exported only so the unit tests can pin the\n * bucket boundaries directly.\n *\n * @param timestampMs - When the notification was shown (epoch ms).\n * @param nowMs - The current time (epoch ms).\n * @returns The relative-time label.\n */\nexport function formatRelativeTime(timestampMs: number, nowMs: number): string {\n const delta = Math.max(0, nowMs - timestampMs);\n\n if (delta < MINUTE_MS) {\n return \"just now\";\n }\n\n if (delta < HOUR_MS) {\n return `${Math.floor(delta / MINUTE_MS)}m ago`;\n }\n\n if (delta < DAY_MS) {\n return `${Math.floor(delta / HOUR_MS)}h ago`;\n }\n\n return `${Math.floor(delta / DAY_MS)}d ago`;\n}\n\n/**\n * Builds the menu item configs from the current notification history in\n * chronological order (oldest first, latest at the bottom). Returns a single\n * disabled placeholder when the history is empty. Module-level (uses only\n * statics), passed through {@link NotificationHistoryButton}'s subclass\n * defaults bag so it is re-invoked on every open. Module-internal (not\n * barrel-exported, so it stays out of the public API) — exported only so the\n * unit tests can pin its pure mapping directly.\n *\n * @returns The menu item descriptors for the current history.\n */\nexport function buildHistoryItems(): MenuItemConfig[] {\n const history = Notification.getHistory();\n\n if (history.length === 0) {\n return [{ text: \"No notifications yet\", enabled: false }];\n }\n\n const now = Date.now();\n\n // History is stored oldest-first; keep that order so the latest entries\n // sit at the bottom (the menu opens scrolled there).\n return history.map(record => ({\n glyph: BADGE_GLYPH[record.type],\n // Tint the badge with the severity's border token — the same colour\n // the live toast's badge uses.\n glyphColor: `var(--ts-ui-notification-${record.type}-border)`,\n text: record.message,\n shortcut: formatRelativeTime(record.timestamp, now),\n action: () => Notification.showDetail(record.message, record.type),\n }));\n}\n\n/**\n * Construction-time options for {@link NotificationHistoryButton}. Inherits\n * every {@link MenuButtonOptions} field; a consumer-supplied `glyph` overrides\n * the default clock icon.\n *\n * @category Components\n */\nexport interface NotificationHistoryButtonOptions extends MenuButtonOptions {}\n\n/**\n * A trigger button that opens a menu of recent notifications. The menu lists the\n * in-session {@link Notification} history in chronological order (latest at the\n * bottom) and opens scrolled to the bottom, so the most recent entries are\n * visible first — each row showing the notification's severity badge, its\n * message, and how long ago it was shown. Activating a row re-opens that\n * notification's full message in the same modal detail dialog a live toast opens\n * on double-click; it does not re-show a toast, so browsing history has no effect\n * on the history itself.\n *\n * Place one wherever a persistent affordance for reviewing past notifications is\n * wanted, e.g. in a toolbar.\n *\n * @example\n * ```typescript\n * toolbar.addComponent(new NotificationHistoryButton());\n * ```\n *\n * @category Components\n */\nclass NotificationHistoryButton extends MenuButton<NotificationHistoryButtonOptions> {\n\n /**\n * Creates a NotificationHistoryButton seeded with the `clock-rotate-left`\n * glyph, the history provider, and scroll-to-bottom-on-show. A\n * consumer-supplied option in `options` still wins over these seeds.\n *\n * @param options - Optional button configuration.\n */\n constructor(options?: NotificationHistoryButtonOptions) {\n // Seeds live in the defaults bag so a caller's options still win.\n // History is chronological (latest at the bottom), so open scrolled to\n // the bottom; the provider re-runs per open so relative times stay current.\n super(undefined, options, {\n glyph: \"clock-rotate-left\",\n menuItems: buildHistoryItems,\n scrollToBottomOnShow: true,\n });\n\n this.getAria().setLabel(\"Notification history\");\n\n // MenuButton wires the bag only for a plain MenuButton; as a subclass we\n // wire our own so a consumer `listeners` option is not silently dropped.\n this.applyListeners(options?.listeners);\n }\n}\n\nconst NotificationHistoryButtonCallable = callable(NotificationHistoryButton);\ntype NotificationHistoryButtonCallable = NotificationHistoryButton;\nexport {\n NotificationHistoryButton as _NotificationHistoryButton,\n NotificationHistoryButtonCallable as NotificationHistoryButton,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { trapWheel, untrapWheel } from \"~/core/WheelTrap.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { DialogBackdrop } from \"~/component/container/DialogBackdrop.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Viewport edge a {@link Drawer} anchors to and slides in from. Reuses the\n * framework's compass primitive\n * [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`, which is\n * meaningless for an edge-anchored panel — excluding it makes the illegal state\n * unrepresentable at compile time.\n *\n * @category Core\n */\nexport type DrawerEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Events emitted by a {@link Drawer}. `\"open\"` and `\"close\"` fire after the\n * corresponding transition is committed; `\"beforeclose\"` fires before a close\n * begins and can be vetoed via its {@link DrawerCloseController}.\n *\n * @category Core\n */\nexport type DrawerEvent = \"open\" | \"close\" | \"beforeclose\";\n\n/**\n * Controller handed to a `\"beforeclose\"` listener. Calling `preventDefault()`\n * aborts the in-progress close — letting a host veto dismissal (e.g. an\n * unsaved-changes guard).\n *\n * @category Core\n */\nexport interface DrawerCloseController {\n /** Aborts the close that is about to run. */\n preventDefault(): void;\n}\n\n/**\n * Construction-time options for {@link Drawer}.\n *\n * @category Core\n */\nexport interface DrawerOptions extends ComponentOptions {\n /**\n * Viewport edge the drawer rests against and slides in from.\n *\n * @defaultValue Placement.WEST\n */\n edge?: DrawerEdge;\n\n /**\n * When true, render a blocking scrim behind the panel and close on\n * scrim-click or Escape. When false, the surrounding UI stays interactive\n * and the drawer closes only via its public API.\n *\n * @defaultValue false\n */\n modal?: boolean;\n\n /**\n * Drawer extent along its slide axis, in pixels: width for left/right\n * edges, height for top/bottom edges.\n *\n * @defaultValue 320\n */\n size?: number;\n\n /**\n * Slide (and scrim-fade) duration in milliseconds.\n *\n * @defaultValue 220\n */\n durationMs?: number;\n\n /** Construction-time event listeners dispatched to {@link Drawer.on}. */\n listeners?: {\n open?: () => void;\n close?: () => void;\n beforeclose?: (controller: DrawerCloseController) => void;\n };\n}\n\n/**\n * Default drawer extent (px) along the slide axis. A component-level constant\n * rather than a theme token because it is a layout-affecting measurement, not a\n * colour — matching how `Dialog` keeps its `MIN_*` sizes and `Notification` its\n * `WIDTH` / `HEIGHT` out of `Theme.ts`. 320 is the conventional side-panel\n * width (navigation rails, filter panels) and stays comfortably under a narrow\n * viewport so the scrim/content behind it remains visible.\n */\nconst DEFAULT_DRAWER_SIZE_PX: number = 320;\n\n/**\n * Default slide / fade duration (ms). Tuned slightly longer than the dialog's\n * 150 ms because a drawer travels a full panel-width rather than a small\n * scale/opacity delta, so the same wall-clock feel needs more time.\n */\nconst DEFAULT_DRAWER_DURATION_MS: number = 220;\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The four\n * behavioural fields seed the options bag so {@link Drawer.getEdge} and friends\n * return a defined value before the caller (or a setter) writes one. `overflow`\n * is `auto` so a drawer scrolls content that exceeds its extent; the panel\n * surface tokens (`background`, `shadow`) are edge-agnostic and applied here,\n * while the directional divider border is applied per-edge in {@link Drawer.open}.\n */\nconst _defaultDrawerOptions: Partial<DrawerOptions> = {\n edge: Placement.WEST,\n modal: false,\n size: DEFAULT_DRAWER_SIZE_PX,\n durationMs: DEFAULT_DRAWER_DURATION_MS,\n overflow: \"auto\",\n backgroundColor: \"var(--ts-ui-drawer-bg)\",\n shadow: \"var(--ts-ui-drawer-shadow)\",\n};\n\n/**\n * An edge-anchored panel that rests off-screen against a viewport edge and\n * slides into view when opened, overlaying the rest of the UI.\n *\n * The drawer is a bare content host: callers add their own children via the\n * inherited `addComponent` and supply any header / dismiss chrome themselves.\n * It mounts on `document.documentElement` and registers with\n * [`LayerManager`](/api/core/classes/LayerManager) as a\n * [`DismissableLayer`](/api/core/interfaces/DismissableLayer), so Escape,\n * outside-click capture, and z-stacking behave like every other portaled\n * surface. A modal drawer additionally draws a\n * [`DialogBackdrop`](/api/component/container/classes/DialogBackdrop) scrim and\n * closes on scrim-click or Escape; a non-modal drawer leaves the surrounding UI\n * interactive and closes only through its public API.\n *\n * @example\n * ```typescript\n * import { Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n * import { VBox } from '@jimka/typescript-ui/layout';\n *\n * const drawer = Drawer({ edge: Placement.EAST, modal: true, layoutManager: VBox() });\n * drawer.addComponent(myFilterForm);\n * drawer.open();\n * ```\n *\n * @category Core\n */\nclass Drawer extends Component<DrawerOptions> implements DismissableLayer {\n\n // In-flight panel / backdrop animations, cancelled on teardown so their\n // fallback timers cannot fire against released element handles.\n private _panelInAnimation : Animation.CancelHandle | null = null;\n private _panelOutAnimation : Animation.CancelHandle | null = null;\n private _backdropInAnimation : Animation.CancelHandle | null = null;\n private _backdropOutAnimation: Animation.CancelHandle | null = null;\n\n /** Whether the drawer is currently open (or mid-entrance). */\n private _open: boolean = false;\n\n /**\n * Whether an exit slide is in flight. Guards `close()` against re-entry\n * while the panel is sliding out — `_open` only flips to false in the exit\n * transition's completion callback, so without this flag a second `close()`\n * (via `toggle()`, a repeated dismiss click, or Esc) would re-emit `\"close\"`\n * and queue a redundant exit animation.\n */\n private _closing: boolean = false;\n\n /** The modal scrim, created lazily on each modal open and torn down on close. */\n private _backdrop: DialogBackdrop | null = null;\n\n /** Typed-event fan-out for `\"open\"` / `\"close\"` / `\"beforeclose\"`. */\n private _listeners: ListenerBag<DrawerEvent> = new ListenerBag<DrawerEvent>();\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.onViewportResize();\n\n /** Stable scrim-click handler reference; closes the drawer. */\n private _boundBackdropClose: () => void = (): void => {\n this.close();\n };\n\n /**\n * Constructs a drawer but does not display it. Call `open()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: DrawerOptions, subclassDefaults?: Partial<DrawerOptions>) {\n super(options, { ..._defaultDrawerOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like every other portaled surface.\n this.setPosition(Position.FIXED);\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies a {@link DrawerOptions} bag, dispatching the drawer-specific\n * fields after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This drawer, for method chaining.\n */\n protected applyOptions(options: DrawerOptions): this {\n super.applyOptions(options);\n\n // All four carry a class default and seed construction-time state, so\n // always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setModal(options.modal ?? this.isModal());\n this.setDrawerSize(options.size ?? this.getDrawerSize());\n this.setDurationMs(options.durationMs ?? this.getDurationMs());\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/CSS applied in open()) -----\n\n /**\n * Sets the viewport edge the drawer anchors to. Cached only — the resting\n * geometry and directional border are derived in `open()`, where the\n * element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This drawer, for method chaining.\n */\n setEdge(edge: DrawerEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the drawer anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): DrawerEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Sets whether the drawer is modal. A modal drawer draws a blocking scrim\n * and closes on scrim-click / Escape; a non-modal one does neither.\n *\n * @param value - True for a modal drawer.\n *\n * @returns This drawer, for method chaining.\n *\n * @remarks Takes effect on the next `open()`; changing it while open does\n * not retroactively add or remove the scrim.\n */\n setModal(value: boolean): this {\n this._options.modal = value;\n\n return this;\n }\n\n /**\n * Returns whether the drawer is modal.\n *\n * @returns True when modal.\n */\n isModal(): boolean {\n return this._options.modal ?? this._defaultOptions.modal!;\n }\n\n /**\n * Sets the drawer's extent along its slide axis (width for left/right,\n * height for top/bottom). Named `setDrawerSize` rather than overriding the\n * inherited `Component.setSize(size: Size)` — the two-axis geometry setter —\n * because a drawer's extent is a single number along one axis.\n *\n * @param value - The extent in pixels.\n *\n * @returns This drawer, for method chaining.\n */\n setDrawerSize(value: number): this {\n this._options.size = value;\n\n return this;\n }\n\n /**\n * Returns the drawer's extent along its slide axis, in pixels.\n *\n * @returns The current extent.\n */\n getDrawerSize(): number {\n return this._options.size ?? this._defaultOptions.size!;\n }\n\n /**\n * Sets the slide / scrim-fade duration in milliseconds.\n *\n * @param ms - The duration in milliseconds.\n *\n * @returns This drawer, for method chaining.\n */\n setDurationMs(ms: number): this {\n this._options.durationMs = ms;\n\n return this;\n }\n\n /**\n * Returns the slide / scrim-fade duration in milliseconds.\n *\n * @returns The current duration.\n */\n getDurationMs(): number {\n return this._options.durationMs ?? this._defaultOptions.durationMs!;\n }\n\n // ----- open / close API -----\n\n /**\n * Slides the drawer into view from its anchored edge. Registers with the\n * layer tree, draws the scrim when modal, mounts on `documentElement`, and\n * animates in. No-op if already open.\n *\n * @returns This drawer, for method chaining.\n */\n open(): this {\n if (this._open) {\n return this;\n }\n\n LayerManager.register(this);\n\n const panelZ = LayerManager.getZIndex(this);\n this.setZIndex(panelZ);\n\n if (this.isModal()) {\n this.openBackdrop(panelZ - 1);\n }\n\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n DOM.sink.appendChild(DOM.source.getDocumentElement(), this.getElement(true)!);\n\n // Trap wheels no inner scroller claimed so they cannot fall through to\n // scrollable content behind the drawer panel.\n trapWheel(this);\n\n this.scheduleLayout();\n\n this.animateIn();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._open = true;\n this.emit(\"open\");\n\n return this;\n }\n\n /**\n * Slides the drawer back off-screen and tears it down. Fires the cancelable\n * `\"beforeclose\"` event first; if a listener vetoes via `preventDefault()`\n * the close is aborted. No-op if already closed.\n *\n * @returns This drawer, for method chaining.\n */\n close(): this {\n if (!this._open || this._closing) {\n return this;\n }\n\n let prevented = false;\n const controller: DrawerCloseController = {\n preventDefault: (): void => {\n prevented = true;\n },\n };\n\n this.emit(\"beforeclose\", controller);\n\n if (prevented) {\n return this;\n }\n\n this._closing = true;\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this.animateOutAndFinalize();\n\n return this;\n }\n\n /**\n * Opens the drawer if closed, or closes it if open.\n *\n * @returns This drawer, for method chaining.\n */\n toggle(): this {\n return this._open ? this.close() : this.open();\n }\n\n /**\n * Returns whether the drawer is currently open.\n *\n * @returns True when open (or mid-entrance).\n */\n isOpen(): boolean {\n return this._open;\n }\n\n // ----- internal: geometry & animation -----\n\n /**\n * Computes the panel's on-screen resting rect from the current edge, size,\n * and viewport. WEST/EAST span the full viewport height at the chosen\n * width; NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - size, y: 0, width: size, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: size };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - size, width: vp.width, height: size };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: size, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the panel via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the slide's\n * start (entrance) and end (exit) state, translated one full extent past\n * the anchored edge.\n *\n * @returns A `translateX`/`translateY` CSS value.\n */\n private offscreenTransform(): string {\n const size = this.getDrawerSize();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return `translateX(${size}px)`;\n\n case Placement.NORTH:\n return `translateY(${-size}px)`;\n\n case Placement.SOUTH:\n return `translateY(${size}px)`;\n\n case Placement.WEST:\n default:\n return `translateX(${-size}px)`;\n }\n }\n\n /**\n * Applies the 1px divider border on the panel's inner edge — the side that\n * faces the rest of the UI — leaving the other three sides borderless.\n */\n private applyEdgeBorder(): void {\n const divider = \"1px solid var(--ts-ui-drawer-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: \"none\", borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: \"none\", borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: \"none\", borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: \"none\", borderRight: divider });\n\n break;\n }\n }\n\n /**\n * Slides the panel in from its off-screen transform to its resting\n * position. Honours `prefers-reduced-motion` via {@link Animation.play}.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._panelInAnimation?.cancel();\n this._panelInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n });\n }\n\n /**\n * Slides the panel back off-screen, then finalizes teardown (detach the\n * element, destroy the scrim, leave the layer tree, emit `\"close\"`) in the\n * transition's completion callback. Fades the scrim out concurrently. Under\n * reduced motion {@link Animation.play} runs the completion synchronously.\n */\n private animateOutAndFinalize(): void {\n const element = this.getElement();\n\n const finalize = (): void => {\n this.removeElement();\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n this.emit(\"close\");\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: this.getDurationMs(),\n properties: [\"transform\"],\n onComplete: finalize,\n });\n\n this.fadeBackdropOut();\n }\n\n /**\n * Creates the modal scrim, stamps it one z-index below the panel, wires\n * scrim-click-to-close, mounts it, and fades it in.\n *\n * @param zIndex - The z-index for the scrim (panel z minus one).\n */\n private openBackdrop(zIndex: number): void {\n this._backdrop = new DialogBackdrop();\n this._backdrop.setZIndex(zIndex);\n this._backdrop.addClickListener(this._boundBackdropClose);\n\n const backdropEl = this._backdrop.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), backdropEl);\n\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = Animation.play(backdropEl, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Fades the modal scrim out, if one is present. The element is destroyed by\n * {@link teardownBackdrop} once the panel's exit completes.\n */\n private fadeBackdropOut(): void {\n const backdropEl = this._backdrop?.getElement();\n\n if (!backdropEl) {\n return;\n }\n\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = Animation.play(backdropEl, {\n to: { opacity: \"0\" },\n durationMs: this.getDurationMs(),\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Removes the modal scrim from the DOM and drops the reference, if one is\n * present.\n */\n private teardownBackdrop(): void {\n if (this._backdrop !== null) {\n this._backdrop.destroy();\n this._backdrop = null;\n }\n }\n\n /**\n * Re-derives the panel rect and resizes the scrim when the viewport\n * changes, keeping a full-height/width drawer flush with the new edges.\n */\n private onViewportResize(): void {\n if (this._backdrop !== null) {\n this._backdrop.resize();\n }\n\n this.applyRestingGeometry();\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the drawer's events.\n *\n * @param event - `\"open\"` / `\"close\"` fire after the matching transition;\n * `\"beforeclose\"` fires before a close and can veto it via its controller.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This drawer, for method chaining.\n */\n on(event: \"open\" | \"close\", listener: () => void): this;\n on(event: \"beforeclose\", listener: (controller: DrawerCloseController) => void): this;\n on(event: DrawerEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This drawer, for method chaining.\n */\n off(event: DrawerEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event`, in registration order.\n *\n * @param event - The event to emit.\n * @param payload - Forwarded to each listener.\n */\n protected emit(event: \"open\" | \"close\"): void;\n protected emit(event: \"beforeclose\", controller: DrawerCloseController): void;\n protected emit(event: DrawerEvent, ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the drawer panel's root element for the layer tree.\n *\n * @returns The drawer's element, or null when not yet rendered.\n */\n getLayerElement(): Handle | null {\n return this.getElement() ?? null;\n }\n\n /**\n * Returns the dismiss mode the document-level handlers consult: `\"modal\"`\n * for a modal drawer (captures outside interaction, Escape closes it) and\n * `\"manual\"` for a non-modal one (never auto-dismissed — closing is the\n * caller's job).\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return this.isModal() ? \"modal\" : \"manual\";\n }\n\n /**\n * Advisory close request from the layer manager (Escape on a modal\n * drawer). Routes to the public `close()`.\n */\n requestClose(): void {\n this.close();\n }\n\n /**\n * Drawers are independent top-level peers, not layers opened from another,\n * so each registers as a tree root.\n *\n * @returns Always `true`.\n */\n isLayerRoot(): boolean {\n return true;\n }\n\n /**\n * Mirrors a manager-reallocated z-index onto the panel (and the scrim, one\n * below) when the drawer is re-stamped.\n *\n * @param zIndex - The fresh z-index assigned by the manager.\n */\n onZIndexChanged(zIndex: number): void {\n this.setZIndex(zIndex);\n\n if (this._backdrop !== null) {\n this._backdrop.setZIndex(zIndex - 1);\n }\n }\n\n /**\n * Cancels any in-flight panel / backdrop animation, then defers to the base\n * class. Cancelling first keeps their fallback timers from firing after\n * `super.destructor()` has released the animated element handles.\n */\n protected destructor(): void {\n this._panelInAnimation?.cancel();\n this._panelInAnimation = null;\n this._panelOutAnimation?.cancel();\n this._panelOutAnimation = null;\n this._backdropInAnimation?.cancel();\n this._backdropInAnimation = null;\n this._backdropOutAnimation?.cancel();\n this._backdropOutAnimation = null;\n\n // The close animation's completion callback is the only place these are\n // released, and cancelling above suppressed it: the backdrop is a\n // private field rather than a registered child, so the base class's\n // recursion cannot reach it and it would stay mounted over the app.\n // Each of these is idempotent, so a drawer disposed while already\n // closed pays nothing. The `\"close\"` emit is deliberately NOT re-homed\n // — disposal is not a close, and emitting into consumer code from a\n // destructor invites re-entrancy.\n this.teardownBackdrop();\n LayerManager.unregister(this);\n untrapWheel(this);\n\n this._open = false;\n this._closing = false;\n\n super.destructor();\n }\n}\n\nconst DrawerCallable = callable(Drawer);\ntype DrawerCallable = Drawer;\nexport {\n Drawer as _Drawer,\n DrawerCallable as Drawer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { DOM } from \"~/core/DOM.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Button, ButtonOptions } from \"~/component/button/Button.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link RailHandle}.\n *\n * @category Core\n */\nexport interface RailHandleOptions extends ButtonOptions {\n /**\n * Whether the handle renders in its selected (target-open) wash.\n *\n * @defaultValue false\n */\n selected?: boolean;\n}\n\n/**\n * A single launcher button on a [`Rail`](/api/overlay/classes/Rail). A chromeless\n * [`Button`](/api/component/button/classes/Button) subclass that carries a\n * `selected` state — driven by the rail to mirror whether the handle's drawer\n * is open or its window is restored — rendered as a hover wash\n * (`--ts-ui-rail-handle-hover-bg`) and a selected wash\n * (`--ts-ui-rail-handle-selected-bg`).\n *\n * `RailHandle` is internal to the rail subsystem; the rail creates and owns its\n * handles, so consumers rarely construct one directly. It is exported for typing\n * and subclassing.\n *\n * @category Core\n */\nclass RailHandle extends Button<RailHandleOptions> {\n\n // Lazy `.selected` rule — the selected (target-open) wash. The slot is a\n // fast-path cache for the wrapper `createStyleRule` dedupes by suffix; see\n // Button's `_pressedStyleRule` for the full explanation.\n private declare _selectedRule?: StyleRule;\n private get selectedRule(): StyleRule {\n return this._selectedRule ??= this.createStyleRule(\".selected\");\n }\n\n // Lazy hover rule. `:not(.selected)` keeps the brighter selected wash\n // winning while the pointer is over an already-open handle.\n private declare _railHoverRule?: StyleRule;\n private get railHoverRule(): StyleRule {\n return this._railHoverRule ??= this.createStyleRule(\":hover:not(.selected)\");\n }\n\n /**\n * Builds a chromeless launcher handle and queues its hover / selected washes.\n *\n * @param options - Construction-time options (label `text`, leading `glyph`,\n * initial `selected` state).\n */\n constructor(options: RailHandleOptions = {}) {\n super(options.text, options, { chromeless: true });\n\n this.railHoverRule.set(\"backgroundColor\", \"var(--ts-ui-rail-handle-hover-bg)\");\n this.selectedRule.set(\"backgroundColor\", \"var(--ts-ui-rail-handle-selected-bg)\");\n }\n\n /**\n * Applies a {@link RailHandleOptions} bag, dispatching the `selected` state\n * after inherited Button/Component fields.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This handle, for method chaining.\n */\n protected applyOptions(options: RailHandleOptions): this {\n super.applyOptions(options);\n\n if (options.selected !== undefined) {\n this.setSelected(options.selected);\n }\n\n return this;\n }\n\n /**\n * Returns whether the handle is currently selected.\n *\n * @returns True when selected.\n */\n isSelected(): boolean {\n return this._options.selected ?? false;\n }\n\n /**\n * Sets the selected state: toggles the `.selected` class (carrying the\n * selected wash) and mirrors the state onto `aria-pressed`.\n *\n * @param value - True to select the handle, false to deselect it.\n *\n * @returns This handle, for method chaining.\n */\n setSelected(value: boolean): this {\n this._options.selected = value;\n\n this.getAria().setPressed(value);\n\n const element = this.getElement();\n if (element) {\n DOM.sink.apply(element, { toggleClass: { selected: value } });\n }\n\n return this;\n }\n\n /**\n * Renders the handle element, applying the `.selected` class when selected.\n *\n * @returns The created element.\n */\n render() {\n const element = super.render();\n DOM.sink.apply(element, { toggleClass: { selected: this.isSelected() } });\n\n return element;\n }\n}\n\nconst RailHandleCallable = callable(RailHandle);\ntype RailHandleCallable = RailHandle;\nexport {\n RailHandle as _RailHandle,\n RailHandleCallable as RailHandle,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Position } from \"~/primitive/Position.js\";\nimport { Placement } from \"~/primitive/Placement.js\";\nimport { isUnbounded } from \"~/primitive/Size.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { BoxLayout } from \"~/layout/BoxLayout.js\";\nimport { RailHandle } from \"~/overlay/RailHandle.js\";\nimport { CollapseButton, CollapseDirection } from \"~/component/container/CollapseButton.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport type { Drawer, DrawerEdge } from \"~/overlay/Drawer.js\";\nimport type { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport type { ClickListener } from \"~/component/button/Button.js\";\n\n/**\n * Viewport edge a {@link Rail} anchors to. Structurally identical to\n * [`DrawerEdge`](/api/overlay/type-aliases/DrawerEdge) — the framework's compass\n * primitive [`Placement`](/api/primitive/enumerations/Placement) minus `CENTER`,\n * which is meaningless for an edge-anchored strip.\n *\n * @category Core\n */\nexport type RailEdge = Exclude<Placement, Placement.CENTER>;\n\n/**\n * Text orientation for handle labels on the vertical sides (WEST/EAST). Ignored\n * for NORTH/SOUTH, where handle text is always horizontal. Mirrors the\n * [`Tab`](/api/layout/classes/Tab) layout's orientation vocabulary.\n *\n * - `\"horizontal\"` — handles stack vertically but text stays upright.\n * - `\"vertical-cw\"` — text rotated 90° clockwise, reading top-to-bottom\n * (`writing-mode: sideways-rl`).\n * - `\"vertical-ccw\"` — text rotated the other way, reading bottom-to-top\n * (`writing-mode: sideways-lr`).\n *\n * @remarks Implemented with CSS `writing-mode` rather than `transform: rotate`\n * so the browser reports the rotated box through `getBoundingClientRect`,\n * keeping the handle's preferred-size measurement correct.\n *\n * @category Core\n */\nexport type RailOrientation = \"horizontal\" | \"vertical-cw\" | \"vertical-ccw\";\n\n/**\n * Events emitted by a {@link Rail}. `\"register\"` fires when a drawer or window\n * is added to the rail; `\"unregister\"` when it is removed.\n *\n * @category Core\n */\nexport type RailEvent = \"register\" | \"unregister\";\n\n/**\n * Per-drawer registration options for {@link Rail.registerDrawer}.\n *\n * @category Core\n */\nexport interface RailDrawerRegistration {\n /** Handle glyph (forwarded to the handle's leading icon). */\n glyph?: string;\n\n /** Handle label text. */\n text?: string;\n\n /**\n * When true (default), the rail sets the drawer's edge to its own edge so\n * the drawer slides out from the rail. Pass false to leave the drawer's\n * edge untouched.\n *\n * @defaultValue true\n */\n alignEdge?: boolean;\n}\n\n/**\n * Construction-time options for {@link Rail}.\n *\n * @category Core\n */\nexport interface RailOptions extends ComponentOptions {\n /**\n * Viewport edge the rail anchors to.\n *\n * @defaultValue Placement.WEST\n */\n edge?: RailEdge;\n\n /**\n * Explicit rail thickness in pixels — width for WEST/EAST edges, height for\n * NORTH/SOUTH edges (the main axis always spans the full viewport). Omit to\n * size the rail to its handles: the cross-axis fits the widest (or, on a\n * horizontal rail, tallest) handle, re-derived as handles are added/removed\n * and when the orientation changes.\n *\n * @defaultValue content-derived\n */\n thickness?: number;\n\n /**\n * Handle-label text orientation on the vertical (WEST/EAST) sides. Ignored\n * for NORTH/SOUTH.\n *\n * @defaultValue \"horizontal\"\n */\n orientation?: RailOrientation;\n\n /**\n * Whether the rail starts collapsed — minimized to a thin gutter strip along\n * the edge (handles hidden) that a click on its chevron expands.\n *\n * @defaultValue false\n */\n collapsed?: boolean;\n\n /** Construction-time event listeners dispatched to {@link Rail.on}. */\n listeners?: {\n register?: (target: Drawer | AbstractWindow) => void;\n unregister?: (target: Drawer | AbstractWindow) => void;\n };\n}\n\n/**\n * Fallback rail thickness (px) along the cross axis, used only when no explicit\n * `thickness` is set and the content cannot be measured yet (e.g. an empty rail\n * with no handles). Once handles exist the rail sizes to them. A component-level\n * constant rather than a theme token because it is a layout-affecting\n * measurement, not a colour — matching how `Drawer` keeps its\n * `DEFAULT_DRAWER_SIZE_PX` out of `Theme.ts`. 48 px is the conventional icon-rail\n * width (a comfortable square touch target for a single glyph handle).\n */\nconst DEFAULT_RAIL_THICKNESS_PX: number = 48;\n\n/**\n * Fixed z-index for the rail, a plain module constant just below the window\n * band (`Z_BAND_WINDOW = 9000` in `LayerManager`) — mirroring how the layer\n * manager's bands are plain constants because z-index is unthemed. The rail is\n * a persistent strip that windows, popovers, and dialogs still stack above, and\n * it is deliberately not a `DismissableLayer`, so it carries this stamp itself\n * rather than drawing a band from the layer manager.\n */\nconst RAIL_Z_INDEX: number = 8900;\n\n/**\n * Slide duration (ms) for the rail's mount / unmount animation. Matches\n * Drawer's slide feel — long enough to read as motion, short enough not to\n * delay the launcher. Honoured under `prefers-reduced-motion` by\n * {@link Animation.play}, which then snaps to the end state.\n */\nconst RAIL_ANIM_DURATION_MS: number = 200;\n\n/**\n * Cross-axis thickness (px) of the collapsed rail — the thin strip the rail\n * minimizes into, skinned like a `Split` / `Border` collapsed gutter; a click on\n * the chevron expands the rail again.\n */\nconst RAIL_COLLAPSED_THICKNESS_PX: number = 10;\n\n/**\n * Half the collapse chevron's grip width (`CollapseButton`'s 10px grip), used to\n * inset the chevron's centre from the rail's inner edge so the whole grip sits\n * just inside the strip. The chevron is centred on its anchor by the\n * `CollapseButton` transform, so anchoring its centre half a grip-width in from\n * the edge leaves it flush inside — never overhanging the `overflow: hidden`\n * rail (which would clip it and let clicks fall through to whatever is behind).\n */\nconst RAIL_CHEVRON_HALF_PX: number = 5;\n\n/**\n * Maps a rail edge to the chevron's collapse heading — the direction the rail\n * travels (and the chevron points) when collapsing: toward the outer screen\n * edge it anchors to. The restore heading is the opposite, handled by the\n * chevron itself.\n *\n * @param edge - The rail's edge.\n *\n * @returns The collapse-heading {@link CollapseDirection}.\n */\nfunction collapseHeadingFor(edge: RailEdge): CollapseDirection {\n switch (edge) {\n case Placement.EAST: return \"east\";\n case Placement.NORTH: return \"north\";\n case Placement.SOUTH: return \"south\";\n case Placement.WEST:\n default: return \"west\";\n }\n}\n\n/** The chevron's restore heading is the opposite of its collapse heading. */\nconst OPPOSITE_HEADING: Record<CollapseDirection, CollapseDirection> = {\n west: \"east\",\n east: \"west\",\n north: \"south\",\n south: \"north\",\n};\n\n/**\n * Subclass defaults layered into `Component._defaultOptions`. The two\n * behavioural fields seed the options bag so {@link Rail.getEdge} /\n * {@link Rail.getThickness} return a defined value before a setter writes one;\n * the surface tokens skin the strip.\n */\nconst _defaultRailOptions: Partial<RailOptions> = {\n edge: Placement.WEST,\n orientation: \"horizontal\",\n backgroundColor: \"var(--ts-ui-rail-bg)\",\n shadow: \"var(--ts-ui-rail-shadow)\",\n};\n\n/** Per-drawer bookkeeping: the handle and the exact listener references to remove. */\ninterface DrawerRegistration {\n handle: RailHandle;\n onOpen: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/** Per-window bookkeeping: the handle (null until minimized) and listener references. */\ninterface WindowRegistration {\n handle: RailHandle | null;\n onMinimize: () => void;\n onRestore: () => void;\n onClose: () => void;\n onAction: ClickListener;\n}\n\n/**\n * An edge-anchored launcher strip that floats over the app content along one\n * viewport edge, holding a column (WEST/EAST) or row (NORTH/SOUTH) of handle\n * buttons. Unlike a [`Drawer`](/api/overlay/classes/Drawer) it never slides\n * off-screen and is never auto-dismissed — it is the persistent counterpart to\n * the drawer.\n *\n * A rail hosts caller-created drawers (`registerDrawer`): each gets a handle\n * that toggles it, and the handle reflects the drawer's open/closed state by\n * subscribing through the drawer's public typed `on`. A window can also be told\n * to minimize *into* the rail (`AbstractWindow.setRail`): while minimized it is\n * represented by a rail handle that restores it on click.\n *\n * The rail mounts on `document.documentElement` as a `Position.FIXED` overlay\n * (the documented fixed carve-out) and carries a fixed z-index just below the\n * window band; it is deliberately *not* a layer-tree member.\n *\n * @example\n * ```typescript\n * import { Rail, Drawer } from '@jimka/typescript-ui/overlay';\n * import { Placement } from '@jimka/typescript-ui/primitive';\n *\n * const rail = Rail({ edge: Placement.WEST }).mount();\n * rail.registerDrawer(Drawer(), { glyph: 'filter', text: 'Filters' });\n * ```\n *\n * @category Core\n */\nclass Rail extends Component<RailOptions> {\n\n // In-flight animations, cancelled on teardown so their fallback timers\n // cannot fire against this rail's released element handle.\n private _collapseAnimation: Animation.CancelHandle | null = null;\n private _slideOutAnimation: Animation.CancelHandle | null = null;\n private _slideInAnimation: Animation.CancelHandle | null = null;\n\n /** Typed-event fan-out for `\"register\"` / `\"unregister\"`. */\n private _listeners: ListenerBag<RailEvent> = new ListenerBag<RailEvent>();\n\n /** Registered drawers, keyed by drawer, holding the handle + listener refs. */\n private _drawers: Map<Drawer, DrawerRegistration> = new Map();\n\n /** Registered windows, keyed by window, holding the handle + listener refs. */\n private _windows: Map<AbstractWindow, WindowRegistration> = new Map();\n\n /** Whether the rail is currently mounted (attached to the document). */\n private _mounted: boolean = false;\n\n /** The collapse/restore chevron pinned to the rail's inner edge. */\n private _collapseButton: CollapseButton;\n\n /**\n * The content-fit thickness captured at collapse time. Hidden handles are\n * excluded from the preferred-size measurement, so the expand animation\n * tweens back to this remembered extent rather than a mis-measured one.\n */\n private _expandedThickness: number = DEFAULT_RAIL_THICKNESS_PX;\n\n /** Stable viewport-resize handler reference, for add/remove symmetry. */\n private _boundResizeHandler: () => void = (): void => this.applyRestingGeometry();\n\n /**\n * Constructs a rail but does not display it. Call `mount()` to show.\n *\n * @param options - Construction-time options.\n * @param subclassDefaults - Defaults layered under `options` by a subclass.\n */\n constructor(options?: RailOptions, subclassDefaults?: Partial<RailOptions>) {\n super(options, { ..._defaultRailOptions, ...(subclassDefaults ?? {}) });\n\n // Floating overlay anchored to the viewport — the documented FIXED\n // carve-out, applied after super() like Drawer and the other portaled\n // surfaces. The rail is not a DismissableLayer, so it stamps its own\n // fixed z-index rather than drawing one from the layer manager.\n this.setPosition(Position.FIXED);\n this.setZIndex(RAIL_Z_INDEX);\n\n // The collapse chevron sits at the rail's inner edge: double-clicking it\n // (matching the Split / Border gutter chevrons) collapses the rail to a\n // gutter, or restores it. Its heading points the way the rail travels on\n // collapse — toward the outer screen edge.\n this._collapseButton = new CollapseButton({\n direction: collapseHeadingFor(this.getEdge()),\n listeners: { collapse: (): void => { this.toggleCollapsed(); } },\n });\n\n // Listener dispatch lives in the constructor body, not applyOptions:\n // the ListenerBag field is undefined during the super() cascade.\n if (options?.listeners !== undefined) {\n this.applyListeners(options.listeners);\n }\n }\n\n /**\n * Applies a {@link RailOptions} bag, dispatching the rail-specific fields\n * after inherited Component fields. `listeners` is handled in the\n * constructor instead — it cannot run during the super() cascade.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This rail, for method chaining.\n */\n protected applyOptions(options: RailOptions): this {\n super.applyOptions(options);\n\n // edge and orientation carry a class default and seed construction-time\n // state, so always dispatch the caller value or the class default.\n this.setEdge(options.edge ?? this.getEdge());\n this.setOrientation(options.orientation ?? this.getOrientation());\n\n if (options.thickness !== undefined) {\n this.setThickness(options.thickness);\n }\n\n if (options.collapsed !== undefined) {\n // Cache only during the cascade — the visual transition needs the\n // element + chevron + handles, which mount() applies once they exist.\n this._options.collapsed = options.collapsed;\n }\n\n return this;\n }\n\n // ----- typed setters (cache only; geometry/layout applied in mount()) -----\n\n /**\n * Sets the viewport edge the rail anchors to. Cached only — the resting\n * geometry, divider border, and handle-axis layout manager are derived in\n * `mount()`, where the element provably exists.\n *\n * @param edge - The edge to anchor against.\n *\n * @returns This rail, for method chaining.\n */\n setEdge(edge: RailEdge): this {\n this._options.edge = edge;\n\n return this;\n }\n\n /**\n * Returns the edge the rail anchors to.\n *\n * @returns The current edge.\n */\n getEdge(): RailEdge {\n return this._options.edge ?? this._defaultOptions.edge!;\n }\n\n /**\n * Pins the rail's cross-axis thickness (width for WEST/EAST, height for\n * NORTH/SOUTH) to an explicit pixel value, overriding the content-fit\n * default. Re-applies the resting geometry when mounted.\n *\n * @param px - The thickness in pixels.\n *\n * @returns This rail, for method chaining.\n */\n setThickness(px: number): this {\n this._options.thickness = px;\n this.adaptThickness();\n\n return this;\n }\n\n /**\n * Returns the rail's effective cross-axis thickness in pixels — the explicit\n * `thickness` when one was set, otherwise the content-fit measurement.\n *\n * @returns The current thickness.\n */\n getThickness(): number {\n if (this.isCollapsed()) {\n return RAIL_COLLAPSED_THICKNESS_PX;\n }\n\n if (this._options.thickness !== undefined) {\n return this._options.thickness;\n }\n\n return this.measureContentThickness();\n }\n\n /**\n * Measures the cross-axis extent the rail's handles need — the widest handle\n * for a vertical (WEST/EAST) rail, the tallest for a horizontal one — from\n * the layout manager's preferred size. Falls back to\n * {@link DEFAULT_RAIL_THICKNESS_PX} before the rail has a layout manager or\n * any handles (when the preferred cross-axis is unbounded or zero).\n *\n * @returns The content-fit thickness in pixels.\n */\n private measureContentThickness(): number {\n const preferred = this.getPreferredSize();\n if (!preferred) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n const cross = this.isVertical() ? preferred.width : preferred.height;\n if (cross <= 0 || isUnbounded(cross)) {\n return DEFAULT_RAIL_THICKNESS_PX;\n }\n\n return Math.ceil(cross);\n }\n\n /**\n * Re-derives the content-fit thickness by re-applying the resting geometry.\n * No-op until mounted (mount applies the geometry itself); called whenever\n * the handle set or orientation changes so the rail tracks its content.\n */\n private adaptThickness(): void {\n if (this._mounted) {\n this.applyRestingGeometry();\n }\n }\n\n // ----- collapse / expand -----\n\n /**\n * Returns whether the rail is collapsed to its gutter strip.\n *\n * @returns True when collapsed.\n */\n isCollapsed(): boolean {\n return this._options.collapsed ?? false;\n }\n\n /**\n * Collapses the rail to a thin gutter strip (handles hidden) or restores it\n * to full size, animating the cross-axis between the two when mounted. No-op\n * if already in the requested state.\n *\n * @param value - True to collapse, false to expand.\n *\n * @returns This rail, for method chaining.\n */\n setCollapsed(value: boolean): this {\n if (value === this.isCollapsed()) {\n return this;\n }\n\n if (value) {\n // Capture the expanded extent first: hidden handles drop out of the\n // preferred-size measurement, so the expand tween reads this back\n // rather than re-measuring an empty rail.\n this._expandedThickness = this.getThickness();\n }\n\n this._options.collapsed = value;\n\n if (this._mounted) {\n this.animateCollapseTransition(value);\n }\n\n return this;\n }\n\n /**\n * Toggles the collapsed state.\n *\n * @returns This rail, for method chaining.\n */\n toggleCollapsed(): this {\n return this.setCollapsed(!this.isCollapsed());\n }\n\n /**\n * Flips the chevron heading, applies the collapsed/expanded skin, positions\n * the chevron, and (immediately, no animation) shows or hides the handles\n * for the current collapsed state. Used by `mount` to seed a rail\n * constructed `collapsed`.\n */\n private applyCollapseAppearance(): void {\n const collapsed = this.isCollapsed();\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n this.setAllHandlesDisplayed(!collapsed);\n }\n\n /**\n * Skins the strip for the collapsed or expanded state, mirroring how a\n * [`Split`](/api/layout/classes/Split) / [`Border`](/api/layout/classes/Border)\n * gutter paints its collapsed strip (`SplitGutter.setOpaque`): collapsed, the\n * rail reads as a themed button surface (the same fill, gradient, and border\n * the framework's buttons use) that invites a click to restore; expanded, it\n * returns to the rail background and its single inner-edge divider.\n *\n * @param collapsed - True for the collapsed strip skin, false for the\n * expanded rail skin.\n */\n private applyCollapseStyling(collapsed: boolean): void {\n if (collapsed) {\n this.setBackgroundColor(\"var(--ts-ui-button-bg, #e8e8e8)\");\n this.setBackgroundImage(\"var(--ts-ui-button-bg, linear-gradient(rgb(241, 241, 241), rgb(200, 200, 200)))\");\n this.setBorder(\"1px solid var(--ts-ui-button-border, #c8c8c8)\");\n } else {\n this.clearBackgroundImage();\n this.setBackgroundColor(\"var(--ts-ui-rail-bg)\");\n this.applyEdgeBorder();\n }\n }\n\n /**\n * Places and sizes the collapse chevron for the current state, writing\n * `left` / `top` / `width` onto the chevron's own `#id` style rule (shared by\n * selector with the rule {@link CollapseButton} uses for its rotation, so the\n * two coexist) and overriding the shared `.CollapseButton` class rule.\n *\n * - **Collapsed:** centred in the strip and sized to fill its full thickness,\n * so the restore handle reads as the Split collapsed-gutter handle.\n * - **Expanded:** pinned just inside the rail's inner (content-facing) edge —\n * the side opposite the viewport edge it anchors to (right for WEST, left\n * for EAST, bottom for NORTH, top for SOUTH) — at the narrow grip width,\n * its centre inset by {@link RAIL_CHEVRON_HALF_PX} so the whole grip sits\n * flush inside the strip rather than overhanging it.\n *\n * In both states the chevron is raised above the handles (`z-index`) so a\n * click always lands on it, never on a handle laid out beneath it; and it is\n * kept fully inside the `overflow: hidden` rail so a click never falls through\n * to whatever sits behind an overhang. Percentages (not pixels) for the\n * placement so the chevron tracks the rail's size and position through the\n * collapse tween and viewport resizes. `width` maps to the across-gutter axis\n * for every edge because {@link CollapseButton}'s rotation orients its box.\n *\n * @param collapsed - True for the collapsed (centred, strip-filling) chevron,\n * false for the expanded (inner-edge, grip) chevron.\n */\n private positionChevron(collapsed: boolean): void {\n let left: string;\n let top: string;\n\n if (collapsed) {\n // Centred in the strip — the CollapseButton class-rule default.\n left = \"50%\";\n top = \"50%\";\n } else {\n // Flush just inside the inner edge: anchor the chevron's centre half\n // a grip-width in from the edge (see RAIL_CHEVRON_HALF_PX).\n const inset = `calc(100% - ${RAIL_CHEVRON_HALF_PX}px)`;\n const near = `${RAIL_CHEVRON_HALF_PX}px`;\n\n switch (this.getEdge()) {\n case Placement.EAST:\n left = near;\n top = \"50%\";\n\n break;\n\n case Placement.NORTH:\n left = \"50%\";\n top = inset;\n\n break;\n\n case Placement.SOUTH:\n left = \"50%\";\n top = near;\n\n break;\n\n case Placement.WEST:\n default:\n left = inset;\n top = \"50%\";\n\n break;\n }\n }\n\n // Fill the strip thickness when collapsed; clear to the CollapseButton\n // grip width (its class-rule default) when expanded.\n const width = collapsed ? RAIL_COLLAPSED_THICKNESS_PX + \"px\" : null;\n\n new StyleRule({\n scope: \"component\",\n name: this._collapseButton.getId(),\n styles: { left, top, width, zIndex: \"1\" },\n });\n }\n\n /**\n * Animates the cross-axis between the full and collapsed extents. The chevron\n * heading, skin, and placement flip up front.\n *\n * The handles stay shown and laid out at the full extent across the whole\n * tween in both directions; because the rail clips its overflow, the moving\n * edge reveals them as the strip widens (expand) and clips them away as it\n * narrows (collapse), so they follow the animation symmetrically rather than\n * popping in or out. On collapse they are hidden only once the strip has\n * closed.\n *\n * @param collapsed - True when collapsing, false when expanding.\n */\n private animateCollapseTransition(collapsed: boolean): void {\n const heading = collapseHeadingFor(this.getEdge());\n\n this._collapseButton.setDirection(collapsed ? OPPOSITE_HEADING[heading] : heading);\n this.applyCollapseStyling(collapsed);\n this.positionChevron(collapsed);\n\n const element = this.getElement();\n\n // The visible extent the tween starts from — the live cross-axis size\n // (full when collapsing, the collapsed strip when expanding). Captured\n // before the expand path lays out the full geometry below.\n const fromThickness = this.isVertical() ? this.getWidth() : this.getHeight();\n const toThickness = collapsed ? RAIL_COLLAPSED_THICKNESS_PX : this._expandedThickness;\n\n if (!collapsed) {\n // Reveal and lay the handles out at the full extent up front; the\n // tween's `from` immediately shrinks the visible strip back to the\n // collapsed width, so they wipe into view as it grows. (Collapse\n // keeps the already-laid-out handles in place so the narrowing strip\n // wipes them out; they are hidden in `finalize`.)\n this.setAllHandlesDisplayed(true);\n this.applyRestingGeometry();\n this.scheduleLayout();\n }\n\n const finalize = (): void => {\n // Now that the strip has closed, drop the handles out of the layout.\n if (collapsed) {\n this.setAllHandlesDisplayed(false);\n }\n\n this.applyRestingGeometry();\n\n if (!collapsed) {\n this.scheduleLayout();\n }\n };\n\n if (!element) {\n finalize();\n\n return;\n }\n\n const tween = this.collapseTween(fromThickness, toThickness);\n\n this._collapseAnimation?.cancel();\n this._collapseAnimation = Animation.play(element, {\n from: tween.from,\n to: tween.to,\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: tween.properties,\n onComplete: finalize,\n });\n }\n\n /**\n * Builds the from/to inline styles for the collapse/expand tween: the\n * cross-axis dimension (and, for EAST/SOUTH rails whose anchored corner\n * moves, the matching `left`/`top`) between two explicit thicknesses.\n *\n * @param fromThickness - The cross-axis extent the tween starts from.\n * @param toThickness - The cross-axis extent the tween ends at.\n *\n * @returns The `from` / `to` style partials and the animated property names.\n */\n private collapseTween(fromThickness: number, toThickness: number): {\n from: Partial<CSSStyleDeclaration>;\n to: Partial<CSSStyleDeclaration>;\n properties: string[];\n } {\n const vp = DOM.source.getViewportSize();\n const from: Partial<CSSStyleDeclaration> = {};\n const to: Partial<CSSStyleDeclaration> = {};\n\n if (this.isVertical()) {\n from.width = fromThickness + \"px\";\n to.width = toThickness + \"px\";\n\n if (this.getEdge() === Placement.EAST) {\n from.left = (vp.width - fromThickness) + \"px\";\n to.left = (vp.width - toThickness) + \"px\";\n }\n } else {\n from.height = fromThickness + \"px\";\n to.height = toThickness + \"px\";\n\n if (this.getEdge() === Placement.SOUTH) {\n from.top = (vp.height - fromThickness) + \"px\";\n to.top = (vp.height - toThickness) + \"px\";\n }\n }\n\n return { from, to, properties: Object.keys(to) };\n }\n\n /**\n * Shows or hides every handle (drawer and window).\n *\n * @param displayed - True to show the handles, false to hide them.\n */\n private setAllHandlesDisplayed(displayed: boolean): void {\n for (const reg of this._drawers.values()) {\n reg.handle.setDisplayed(displayed);\n }\n\n for (const reg of this._windows.values()) {\n reg.handle?.setDisplayed(displayed);\n }\n }\n\n /**\n * Sets the handle-label text orientation for the vertical (WEST/EAST) sides\n * and re-applies the writing mode to every existing handle. Ignored visually\n * on NORTH/SOUTH, where handle text is always horizontal.\n *\n * @param orientation - The {@link RailOrientation} to apply.\n *\n * @returns This rail, for method chaining.\n */\n setOrientation(orientation: RailOrientation): this {\n this._options.orientation = orientation;\n\n // `_drawers` / `_windows` are class-field Maps initialised only after\n // super() returns, but `applyOptions` dispatches this setter during the\n // super() cascade. Skip the re-apply then — no handles exist yet, and\n // each one picks up the orientation as it is created (see\n // registerDrawer / showWindowHandle).\n if (this._drawers !== undefined) {\n this.applyOrientation();\n }\n\n return this;\n }\n\n /**\n * Returns the current handle-label text orientation.\n *\n * @returns The current orientation.\n */\n getOrientation(): RailOrientation {\n return this._options.orientation ?? this._defaultOptions.orientation!;\n }\n\n // ----- mount / unmount -----\n\n /**\n * Mounts the rail on `document.documentElement`: installs the handle-axis\n * layout manager, applies the divider border and resting geometry, attaches\n * the element, and tracks viewport resizes. No-op if already mounted.\n *\n * @returns This rail, for method chaining.\n */\n mount(): this {\n if (this._mounted) {\n return this;\n }\n\n this.setLayoutManager(this.isVertical() ? new VBox() : new HBox());\n this.applyEdgeBorder();\n this.applyRestingGeometry();\n\n const element = this.getElement(true)!;\n DOM.sink.appendChild(DOM.source.getDocumentElement(), element);\n\n // The collapse chevron is a raw child (self-centred via its own class\n // rule), outside the handle layout, so it doesn't count toward the\n // content-fit thickness. Append once; a remount reuses the element.\n const chevron = this._collapseButton.getElement(true);\n if (chevron && DOM.source.getParentElement(chevron) !== element) {\n DOM.sink.appendChild(element, chevron);\n }\n\n // Seed the chevron heading, skin, placement, and handle visibility for\n // the initial (possibly collapsed) state.\n this.applyCollapseAppearance();\n\n this.scheduleLayout();\n\n Event.addViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = true;\n this.animateIn();\n\n return this;\n }\n\n /**\n * Unmounts the rail: stops tracking viewport resizes and detaches the\n * element. Registered drawers and windows keep their subscriptions, so a\n * later `mount()` restores a working strip.\n *\n * @returns This rail, for method chaining.\n */\n unmount(): this {\n if (!this._mounted) {\n return this;\n }\n\n Event.removeViewportListener(this, \"resize\", this._boundResizeHandler);\n\n this._mounted = false;\n\n // Slide the strip back off its edge, then detach. Under reduced motion\n // Animation.play runs the completion synchronously.\n const element = this.getElement();\n const detach = (): void => { this.removeElement(); };\n\n if (!element) {\n detach();\n\n return this;\n }\n\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = Animation.play(element, {\n to: { transform: this.offscreenTransform() },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n onComplete: detach,\n });\n\n return this;\n }\n\n /**\n * Slides the strip in from off its anchored edge to its resting position.\n */\n private animateIn(): void {\n const element = this.getElement();\n\n if (!element) {\n return;\n }\n\n this._slideInAnimation?.cancel();\n this._slideInAnimation = Animation.play(element, {\n from: { transform: this.offscreenTransform() },\n to: { transform: \"translate(0, 0)\" },\n durationMs: RAIL_ANIM_DURATION_MS,\n properties: [\"transform\"],\n });\n }\n\n /**\n * Returns the off-screen `transform` for the current edge — the strip\n * translated one full thickness past the edge it anchors to, the start\n * (mount) and end (unmount) state of the slide.\n *\n * @returns A `translateX` / `translateY` CSS value.\n */\n private offscreenTransform(): string {\n switch (this.getEdge()) {\n case Placement.EAST:\n return \"translateX(100%)\";\n\n case Placement.NORTH:\n return \"translateY(-100%)\";\n\n case Placement.SOUTH:\n return \"translateY(100%)\";\n\n case Placement.WEST:\n default:\n return \"translateX(-100%)\";\n }\n }\n\n // ----- drawer composition -----\n\n /**\n * Registers a drawer: adds a handle that toggles it, mirrors the drawer's\n * open/closed state onto the handle via the drawer's public `on`, and (by\n * default) aligns the drawer's edge to the rail's. No-op if already\n * registered.\n *\n * @param drawer - The drawer to host. The caller retains ownership of its\n * lifecycle.\n * @param reg - Per-registration options (handle glyph / text, edge\n * alignment).\n *\n * @returns This rail, for method chaining.\n */\n registerDrawer(drawer: Drawer, reg: RailDrawerRegistration = {}): this {\n if (this._drawers.has(drawer)) {\n return this;\n }\n\n const handle = new RailHandle({ text: reg.text, glyph: reg.glyph, selected: drawer.isOpen() });\n\n const onOpen: () => void = (): void => { handle.setSelected(true); };\n const onClose: () => void = (): void => { handle.setSelected(false); };\n const onAction: ClickListener = (): void => { drawer.toggle(); };\n\n drawer.on(\"open\", onOpen);\n drawer.on(\"close\", onClose);\n handle.on(\"action\", onAction);\n\n if (reg.alignEdge !== false) {\n drawer.setEdge(this.getEdge() as DrawerEdge);\n }\n\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n this._drawers.set(drawer, { handle, onOpen, onClose, onAction });\n\n this.adaptThickness();\n this.scheduleLayout();\n this.emit(\"register\", drawer);\n\n return this;\n }\n\n /**\n * Unregisters a drawer: removes its handle and detaches every subscription\n * (the exact listener references are removed so nothing leaks). Does not\n * close or destroy the drawer. No-op if not registered.\n *\n * @param drawer - The drawer to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterDrawer(drawer: Drawer): this {\n const reg = this._drawers.get(drawer);\n if (!reg) {\n return this;\n }\n\n drawer.off(\"open\", reg.onOpen);\n drawer.off(\"close\", reg.onClose);\n reg.handle.off(\"action\", reg.onAction);\n\n this.removeComponent(reg.handle);\n this._drawers.delete(drawer);\n\n this.adaptThickness();\n this.emit(\"unregister\", drawer);\n\n return this;\n }\n\n // ----- window-minimize composition -----\n\n /**\n * Registers a window so it can minimize into the rail. Subscribes to the\n * window's minimize / restore / close events; while the window is minimized\n * it is represented by a rail handle that restores it on click. Called by\n * {@link AbstractWindow.setRail}. No-op if already registered.\n *\n * @param window - The window to host.\n *\n * @returns This rail, for method chaining.\n */\n registerWindow(window: AbstractWindow): this {\n if (this._windows.has(window)) {\n return this;\n }\n\n const onMinimize: () => void = (): void => { this.showWindowHandle(window); };\n const onRestore: () => void = (): void => { this.removeWindowHandle(window); };\n const onClose: () => void = (): void => { this.unregisterWindow(window); };\n const onAction: ClickListener = (): void => { window.restore(); };\n\n window.on(\"minimize\", onMinimize);\n window.on(\"restore\", onRestore);\n window.on(\"close\", onClose);\n\n this._windows.set(window, { handle: null, onMinimize, onRestore, onClose, onAction });\n\n // A window registered while already minimized gets its handle now.\n if (window.isMinimized()) {\n this.showWindowHandle(window);\n }\n\n this.emit(\"register\", window);\n\n return this;\n }\n\n /**\n * Unregisters a window: removes any handle and detaches every subscription.\n * Does not close the window. No-op if not registered.\n *\n * @param window - The window to remove.\n *\n * @returns This rail, for method chaining.\n */\n unregisterWindow(window: AbstractWindow): this {\n const reg = this._windows.get(window);\n if (!reg) {\n return this;\n }\n\n window.off(\"minimize\", reg.onMinimize);\n window.off(\"restore\", reg.onRestore);\n window.off(\"close\", reg.onClose);\n\n this.removeWindowHandle(window);\n this._windows.delete(window);\n\n this.emit(\"unregister\", window);\n\n return this;\n }\n\n /**\n * Creates and adds a handle representing a minimized window, bearing its\n * title and glyph, wired to restore it on click. No-op if a handle already\n * shows.\n *\n * @param window - The minimized window.\n */\n private showWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle !== null) {\n return;\n }\n\n const handle = new RailHandle({ text: window.getTitle(), glyph: window.getGlyph(), selected: true });\n handle.on(\"action\", reg.onAction);\n\n reg.handle = handle;\n this.applyHandleOrientation(handle);\n handle.setDisplayed(!this.isCollapsed());\n this.addComponent(handle);\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Removes the handle representing a window, if one shows. No-op otherwise.\n *\n * @param window - The window whose handle to remove.\n */\n private removeWindowHandle(window: AbstractWindow): void {\n const reg = this._windows.get(window);\n if (!reg || reg.handle === null) {\n return;\n }\n\n reg.handle.off(\"action\", reg.onAction);\n this.removeComponent(reg.handle);\n reg.handle = null;\n\n this.adaptThickness();\n }\n\n // ----- internal: handle orientation -----\n\n /**\n * Re-applies the current orientation's writing mode to every handle (drawer\n * and window). Called when the orientation changes.\n */\n private applyOrientation(): void {\n for (const reg of this._drawers.values()) {\n this.applyHandleOrientation(reg.handle);\n }\n\n for (const reg of this._windows.values()) {\n if (reg.handle !== null) {\n this.applyHandleOrientation(reg.handle);\n }\n }\n\n this.adaptThickness();\n this.scheduleLayout();\n }\n\n /**\n * Applies the orientation's writing mode to a single handle: a rotated\n * `writing-mode` on the vertical (WEST/EAST) sides, cleared otherwise (and\n * always on NORTH/SOUTH, where handle text stays horizontal). Mirrors the\n * `Tab` layout's `sideways-rl` / `sideways-lr` mapping.\n *\n * @param handle - The handle to orient.\n */\n private applyHandleOrientation(handle: RailHandle): void {\n const orientation = this.getOrientation();\n\n // `sideways-rl` reads top-to-bottom (clockwise); `sideways-lr` reads\n // bottom-to-top (counter-clockwise). Only meaningful on a vertical rail.\n const writingMode = orientation === \"vertical-cw\" ? \"sideways-rl\"\n : orientation === \"vertical-ccw\" ? \"sideways-lr\"\n : null;\n\n if (this.isVertical() && writingMode !== null) {\n handle.setWritingMode(writingMode);\n } else {\n handle.clearWritingMode();\n }\n }\n\n // ----- internal: geometry -----\n\n /**\n * Opts the rail out of content-derived size clamping. Like\n * [`Container`](/api/core/classes/Container) / [`Panel`](/api/core/classes/Panel),\n * the rail sizes itself explicitly — its main axis spans the viewport and\n * its cross axis is the thickness it computes — so {@link Component.setWidth} /\n * {@link Component.setHeight} must not be clamped back to the layout\n * manager's content size. Without this, collapsing (which hides every\n * handle) would empty the handle layout, drive its content max toward zero,\n * and clamp the rail's viewport-spanning main axis to nothing — the strip\n * would vanish instead of resting at {@link RAIL_COLLAPSED_THICKNESS_PX}.\n *\n * @returns Always `false`.\n */\n protected clampsToContentSize(): boolean {\n return false;\n }\n\n /**\n * Returns whether the rail lays its handles out vertically — true for the\n * WEST and EAST edges (a column at a fixed width).\n *\n * @returns True for a vertical (WEST/EAST) rail.\n */\n private isVertical(): boolean {\n const edge = this.getEdge();\n\n return edge === Placement.WEST || edge === Placement.EAST;\n }\n\n /**\n * Returns the main-axis viewport offset of a registered window's rail handle —\n * where the genie animation should aim. When the handle exists (restore, or an\n * already-minimized window) this is its laid-out position; when it does not yet\n * exist (the collapse genie runs before the handle is created) this is the\n * predicted append slot: after the last existing handle plus one gap, or 0 for\n * an empty rail. Main axis is Y for a vertical (WEST/EAST) rail, X for a\n * horizontal (NORTH/SOUTH) one; the rail's main-axis viewport origin is 0.\n *\n * @param window - The window to locate, registered or not.\n *\n * @returns The main-axis viewport offset in pixels.\n */\n handleMainAxisOffset(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainPos = (c: Component): number => vertical ? c.getY() : c.getX();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n // Restore path (and an already-minimized window): the handle exists — its\n // laid-out main-axis position is the target directly.\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainPos(handle);\n }\n\n // Collapse path: the handle is created after this runs, appended last.\n // Predict its slot from the current last handle's trailing edge + gap.\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n const last = handles[handles.length - 1];\n\n return mainPos(last) + mainExtent(last) + this.handleSpacing();\n }\n\n /**\n * Returns the main-axis length of a registered window's rail handle — the\n * span the genie centres the shrinking window along. When the handle exists\n * (restore, or an already-minimized window) this is its laid-out extent; when\n * it does not yet exist (the collapse genie runs before the handle is\n * created) it is predicted from the current last handle, since sibling\n * handles run close in size. An empty rail has no handle to sample, so this\n * returns 0 and the caller keeps the window at the slot's leading edge. Main\n * axis is the height for a vertical (WEST/EAST) rail, the width for a\n * horizontal (NORTH/SOUTH) one.\n *\n * @param window - The window whose handle to measure, registered or not.\n *\n * @returns The main-axis handle length in pixels, or 0 when unpredictable.\n */\n handleMainAxisExtent(window: AbstractWindow): number {\n const vertical = this.isVertical();\n const mainExtent = (c: Component): number => vertical ? c.getHeight() : c.getWidth();\n\n const handle = this._windows.get(window)?.handle ?? null;\n if (handle !== null) {\n return mainExtent(handle);\n }\n\n const handles = this.getComponents();\n if (handles.length === 0) {\n return 0;\n }\n\n return mainExtent(handles[handles.length - 1]);\n }\n\n /**\n * Returns the inter-handle gap the rail's `VBox` / `HBox` layout manager\n * applies between handles, or `0` before the layout manager exists.\n *\n * @returns The component spacing in pixels.\n */\n private handleSpacing(): number {\n const lm = this.getLayoutManager();\n\n return lm instanceof BoxLayout ? lm.getComponentSpacing() : 0;\n }\n\n /**\n * Computes the rail's on-screen rect from the current edge, thickness, and\n * viewport. WEST/EAST span the full viewport height at the chosen width;\n * NORTH/SOUTH span the full width at the chosen height.\n *\n * @returns The resting `{ x, y, width, height }` in pixels.\n */\n private restingRect(): { x: number; y: number; width: number; height: number } {\n const vp = DOM.source.getViewportSize();\n const thickness = this.getThickness();\n\n switch (this.getEdge()) {\n case Placement.EAST:\n return { x: vp.width - thickness, y: 0, width: thickness, height: vp.height };\n\n case Placement.NORTH:\n return { x: 0, y: 0, width: vp.width, height: thickness };\n\n case Placement.SOUTH:\n return { x: 0, y: vp.height - thickness, width: vp.width, height: thickness };\n\n case Placement.WEST:\n default:\n return { x: 0, y: 0, width: thickness, height: vp.height };\n }\n }\n\n /**\n * Applies the resting rect to the rail via the typed geometry setters.\n */\n private applyRestingGeometry(): void {\n const rect = this.restingRect();\n\n this.setX(rect.x);\n this.setY(rect.y);\n this.setWidth(rect.width);\n this.setHeight(rect.height);\n }\n\n /**\n * Applies the expanded-state border: a 1px divider (`--ts-ui-rail-border`) on\n * the rail's inner edge — the side facing the rest of the UI — with the other\n * three sides a 1px *transparent* border rather than no border.\n *\n * Reserving the same 1px box on every side that the collapsed strip's button\n * border occupies keeps the rail's border-box geometry identical across the\n * collapse/expand transition, so the handles and chevron don't jump by a\n * pixel when the visible border appears or disappears.\n */\n private applyEdgeBorder(): void {\n const transparent = \"1px solid transparent\";\n const divider = \"1px solid var(--ts-ui-rail-border)\";\n\n switch (this.getEdge()) {\n case Placement.EAST:\n this.setBorder({ border: transparent, borderLeft: divider });\n\n break;\n\n case Placement.NORTH:\n this.setBorder({ border: transparent, borderBottom: divider });\n\n break;\n\n case Placement.SOUTH:\n this.setBorder({ border: transparent, borderTop: divider });\n\n break;\n\n case Placement.WEST:\n default:\n this.setBorder({ border: transparent, borderRight: divider });\n\n break;\n }\n }\n\n // ----- typed events -----\n\n /**\n * Registers a listener for one of the rail's events.\n *\n * @param event - `\"register\"` fires when a drawer/window is added,\n * `\"unregister\"` when one is removed.\n * @param listener - The callback, receiving the affected drawer or window.\n *\n * @returns This rail, for method chaining.\n */\n on(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This rail, for method chaining.\n */\n off(event: RailEvent, listener: (target: Drawer | AbstractWindow) => void): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event`, in registration order.\n *\n * @param event - The event to emit.\n * @param target - The affected drawer or window, forwarded to each listener.\n */\n protected emit(event: RailEvent, target: Drawer | AbstractWindow): void {\n this._listeners.fire(event, target);\n }\n\n /**\n * Cancels any in-flight collapse / slide animation and disposes the\n * collapse chevron, then defers to the base class. Cancelling the\n * animations first keeps their fallback timers from firing after\n * `super.destructor()` has released this rail's element handle. The\n * chevron needs its own disposal because `mount()` appends it straight to\n * this rail's element rather than registering it as a child, so\n * `super.destructor()`'s recursion cannot reach it.\n */\n protected destructor(): void {\n this._collapseAnimation?.cancel();\n this._collapseAnimation = null;\n this._slideOutAnimation?.cancel();\n this._slideOutAnimation = null;\n this._slideInAnimation?.cancel();\n this._slideInAnimation = null;\n\n this._collapseButton.dispose();\n\n super.destructor();\n }\n}\n\nconst RailCallable = callable(Rail);\ntype RailCallable = Rail;\nexport {\n Rail as _Rail,\n RailCallable as Rail,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Container, ContainerOptions } from \"~/core/Container.js\";\nimport { Component } from \"~/core/Component.js\";\nimport type { ComponentFactory } from \"~/core/Component.js\";\nimport { AbstractWindow } from \"~/overlay/AbstractWindow.js\";\nimport { TabWindow } from \"~/overlay/TabWindow.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { Tab } from \"~/layout/Tab.js\";\nimport { Split } from \"~/layout/Split.js\";\nimport { DockRegion } from \"~/layout/DockRegion.js\";\nimport { LayoutConstraints } from \"~/layout/LayoutConstraints.js\";\nimport { serializeLayout, restoreLayout, LayoutState } from \"~/layout/LayoutSerialization.js\";\nimport { DragManager, DragEventDetail, tabDragRegistry } from \"~/overlay/DragManager.js\";\nimport { DropZoneOverlay } from \"~/overlay/DropZoneOverlay.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { AxisOrientation } from \"~/primitive/Axis.js\";\n\n/**\n * Declarative description of one dockable content panel.\n *\n * @category Core\n */\nexport interface DockPanelSpec {\n /**\n * Stable identity. `Dock` builds an identity frame whose `getId()` is this\n * value (set at the frame's construction), and serialization keys on it — so\n * restored layouts round-trip against it. Keep it stable across sessions.\n */\n id: string;\n /**\n * Visible tab label and tear-off window title. Set as the identity frame's\n * `getName()`; it rides on the frame (not a per-container constraint), so it\n * survives the re-home a restore performs.\n */\n title: string;\n /** Optional registry glyph name shown leading the tab label. */\n glyph?: string;\n /** Optional hover-tooltip text shown over the tab button. */\n tooltip?: string;\n /** Whether the tab shows a close button. Defaults to `true`. */\n closeable?: boolean;\n /**\n * The content: a live component, or a factory built on first resolve. It is\n * placed inside the identity frame, never mutated. A factory returning a\n * promise is accepted only by {@link Dock.addLazyPanel}, which shows a\n * spinner for the whole wait; {@link Dock.addPanel} throws on one.\n */\n content: Component | ComponentFactory;\n}\n\n/**\n * A node in the declarative initial arrangement: a leaf panel, a split of\n * regions, or a tab group.\n *\n * @category Core\n */\nexport type DockLayoutSpec =\n | DockPanelSpec\n | { split: AxisOrientation; children: DockLayoutSpec[] }\n | { tabs: DockPanelSpec[] };\n\n/**\n * Construction options for a {@link Dock}.\n *\n * @category Core\n */\nexport interface DockOptions extends ContainerOptions {\n /** Initial arrangement, compiled to the region tree at construction. Omit for an empty dock. */\n layout?: DockLayoutSpec;\n /**\n * Placeholder shown only while the dock holds no live panel (tiled or\n * floated) — a start-page for an empty dock. It is shown as a single\n * non-closeable tab in the empty region and is chrome, not a panel: it is\n * never serialized. See {@link Dock.setEmptyContent}.\n */\n emptyContent?: Component;\n /**\n * Construction-time listener bag — the declarative form of {@link Dock.on},\n * so a consumer can wire dock events (notably `emptychange`, the empty↔\n * populated aggregate) in the options bag instead of a separate post-build\n * `on(...)` call. Each entry is registered via `on` after construction; the\n * born-empty state is adopted without an emit, so read {@link Dock.isEmpty}\n * for the initial value.\n */\n listeners?: {\n attach?: (event: DockPanelEvent) => void;\n detach?: (event: DockPanelEvent) => void;\n move?: (event: DockPanelEvent) => void;\n focus?: (event: DockPanelEvent | null) => void;\n close?: (event: DockPanelEvent) => void;\n emptychange?: (event: DockEmptyEvent) => void;\n exception?: (event: DockExceptionEvent) => void;\n };\n}\n\n/**\n * String-literal union of the events a {@link Dock} emits across a panel's\n * lifecycle.\n *\n * The model is *host-centric*: a live panel always occupies one Dock-managed\n * *host* — the *tiled tree* (the main dock) or a *float window* — and the events\n * name the host transitions. `\"attach\"` fires when a panel **enters** a host (a\n * fresh `addPanel`/restore into the tiled tree, or a tear-off into a fresh\n * float); `\"detach\"` fires when it **leaves** a host while staying alive. The\n * two pair up across a move: a tear-off is `\"detach\"`(tiled) then\n * `\"attach\"`(float), and a re-dock — whether dropped on a region body/edge or\n * merged onto an existing tab bar — is `\"detach\"`(float) then `\"attach\"`(tiled).\n * `\"move\"` fires when a panel **relocates within** its current host — a\n * different region in the same tiled tree, or repositioned in the same float —\n * without changing host; it never accompanies a host change (that is\n * `\"detach\"`+`\"attach\"`) nor a first appearance (that is `\"attach\"` alone), and\n * a pure reorder within one strip is silent. `\"focus\"` fires when the dock-wide\n * active panel changes (across tiled tabs and floats; `null` when nothing is\n * focused), and `\"close\"` when a panel is destroyed. The\n * {@link DockPanelEvent.window} field names *which* host the panel entered,\n * left, occupies, or moved within. See {@link DockPanelEvent} for the payload.\n *\n * A separate `\"emptychange\"` event is a *dock-wide aggregate*, not a per-panel\n * event: it fires once each time the dock transitions between holding no live\n * panel anywhere and holding at least one, carrying a {@link DockEmptyEvent}.\n *\n * `\"exception\"` reports that a lazy panel's content factory rejected, carrying a\n * {@link DockExceptionEvent} rather than a {@link DockPanelEvent} — a panel that\n * never built has no content to name. It follows that panel's own `\"close\"`,\n * because the failure tears the whole docked panel down; the panel stays\n * registered, so re-adding the same id rebuilds it and retries.\n *\n * @category Core\n */\nexport type DockEvent = \"attach\" | \"detach\" | \"move\" | \"focus\" | \"close\" | \"emptychange\" | \"exception\";\n\n/**\n * Payload for a {@link Dock} lifecycle event, identifying the panel by its\n * stable {@link DockPanelSpec.id}, carrying its Dock-owned identity frame, and\n * naming the host the event concerns.\n *\n * @category Core\n */\nexport interface DockPanelEvent {\n /** The stable id of the panel (its {@link DockPanelSpec.id}). */\n id: string;\n /** The panel's Dock-owned identity frame. */\n content: Component;\n /**\n * The host the panel entered (`\"attach\"`), left (`\"detach\"`), moved within\n * (`\"move\"` — same host before and after), or currently occupies\n * (`\"focus\"`): `null` denotes the tiled tree / main dock, otherwise the float\n * window. Always `null` for `\"close\"` — a destroy is not a host transition,\n * but the field is always present so the payload stays flat.\n */\n window: AbstractWindow | null;\n}\n\n/**\n * Payload for a {@link Dock} `\"emptychange\"` event, reporting whether the dock\n * just became empty. Emitted once per real transition, not per panel.\n *\n * @category Core\n */\nexport interface DockEmptyEvent {\n /** `true` when the dock just became empty (no live panels anywhere), `false` when it became populated. */\n empty: boolean;\n}\n\n/**\n * Payload for a {@link Dock} `\"exception\"` event: a lazy panel's content\n * factory rejected. The panel has already been closed and its `\"close\"` event\n * already emitted by the time this fires.\n *\n * @category Core\n */\nexport interface DockExceptionEvent {\n /** The stable id of the panel that failed (its {@link DockPanelSpec.id}). */\n id: string;\n /** The value the content factory's promise rejected with. */\n error: unknown;\n}\n\n/**\n * Per-region drag-and-drop wiring tracked by the re-wire sweep.\n */\ninterface RegionWiring {\n /** The region's edge/centre drop coordinator (torn down on teardown). */\n dockRegion: DockRegion;\n /** Whether `setReorderable(true)` + the prune-on-`\"empty\"` subscription were applied (Tab regions only). */\n tabWired: boolean;\n}\n\n/**\n * A user-configurable, rearrangeable panel layout — the VS Code / GoldenLayout\n * style dock. It hosts a tree of [`Split`](/api/layout/classes/Split) /\n * [`Tab`](/api/layout/classes/Tab) regions whose panels the user can reorder,\n * tear off into floating [`Window`](/api/overlay/classes/Window)s, drop on region\n * edges to split, and save/restore.\n *\n * `Dock` is **glue**, not new drag mechanics: tab reorder + tear-off come from\n * `Tab`'s reorderable wiring, edge-split-on-drop from\n * [`DockRegion`](/api/layout/classes/DockRegion), every re-parent from\n * [`Component.moveComponent`](/api/core/classes/Component#movecomponent), and\n * persistence from\n * [`serializeLayout`](/api/layout/functions/serializeLayout) /\n * [`restoreLayout`](/api/layout/functions/restoreLayout). `Dock` owns the panel\n * registry (the serialization factory), the declarative initial-layout\n * compiler, and the re-wire sweep that keeps **every** region dockable —\n * including the regions a drop creates mid-gesture.\n *\n * @category Core\n */\nclass Dock extends Container<DockOptions> {\n\n // panelId -> spec; the single source the serialization factory resolves from.\n private _panels: Map<string, DockPanelSpec> = new Map<string, DockPanelSpec>();\n // panelId -> the Dock-owned identity frame built for that spec (cached so a\n // lazy factory runs once and every resolve returns the same instance).\n private _frames: Map<string, Component> = new Map<string, Component>();\n // panelId -> deferred content factory for a lazy panel (see addLazyPanel).\n // resolvePanel reads it to give the frame a lazy Tab layout (Tab.addLazyTab),\n // which owns the once-only materialization; the frame caches in _frames after.\n private _lazyFactories: Map<string, ComponentFactory> = new Map<string, ComponentFactory>();\n // region container -> its DnD wiring; the sweep's idempotence + teardown ledger.\n private _wiring: Map<Component, RegionWiring> = new Map<Component, RegionWiring>();\n // rAF coalescing latch so a burst of moves in one gesture yields one sweep.\n private _sweepScheduled: boolean = false;\n\n // Panel-lifecycle event bag. A field initialiser is safe here: no\n // cascade-dispatched setter touches it, and Dock exposes no construction-time\n // `listeners` option (no DockEvent is a build-time gesture — addPanel /\n // compileLayout run after super()), so it is never dispatched from\n // applyOptions. Consumers wire post-construction via on(...).\n private _listeners: ListenerBag<DockEvent> = new ListenerBag<DockEvent>();\n // The dock-wide focused panel id, or null when nothing is focused. The single\n // source of truth gating every \"focus\" emit so a re-activation is silent.\n private _focusedPanelId: string | null = null;\n // panelId -> last-observed host (null = tiled tree, else the float window);\n // the source of the attach/detach diff. A host change across a sweep emits\n // detach(old host) then attach(new host); a first appearance emits attach only.\n private _panelHost: Map<string, AbstractWindow | null> = new Map<string, AbstractWindow | null>();\n // panelId -> the Tab region last observed hosting it; lets a close recompute\n // the surviving sibling's focus after the region re-selects.\n private _frameRegion: Map<string, Component> = new Map<string, Component>();\n // The Tab region the user most recently focused (or that last received a\n // programmatic add). `addPanel` docks new panels here when it is still a live\n // region, so a new tab opens beside whatever tab-bar the user last worked in\n // rather than always the first/primary region. Null until the first add.\n private _lastActiveRegion: Component | null = null;\n // Float windows whose lifecycle events are already subscribed; the tracked-set\n // guard that stops a re-sweep stacking duplicate listeners.\n private _floatSubscribed: Set<AbstractWindow> = new Set<AbstractWindow>();\n\n // Overlay highlighting the dock as a drop target while it is empty (every\n // panel torn off) and a tab is dragged over it.\n private _emptyDropOverlay: DropZoneOverlay = new DropZoneOverlay();\n\n // Empty-state latch: whether the dock currently holds no live panel. Gates\n // the \"emptychange\" emit so it fires only on a real transition. Seeded true\n // because a dock is born empty: the first reconcile on a still-empty dock then\n // finds no transition (silent), while a dock born with a layout — or the first\n // addPanel — correctly flips it to false and emits emptychange(false). A plain\n // initializer is safe — no cascade-dispatched setter writes it.\n private _empty: boolean = true;\n // Whether reconcileEmptyState has run at least once. Gates only the\n // \"emptychange\" emit: the first run adopts the born emptiness without an emit\n // (being born empty or populated is not a transition). The placeholder itself\n // is re-asserted every sweep from the main-region emptiness, independent of\n // this latch.\n private _emptyReconciled: boolean = false;\n\n // Named, bound listener reference for the DockRegion post-drop callback,\n // routing through one removable handler that coalesces via scheduleSweep.\n private requestSweep: () => void = (): void => {\n this.scheduleSweep();\n };\n\n // Named, bound \"dock\" handler for every wired Tab: a foreign tab was merged\n // into a region's strip. The merge bypasses DockRegion, so it lands a sweep\n // here — the host-diff reconcile then emits the attach. The content arg is\n // unused (the reconcile re-derives every frame's host) but matches the\n // listener signature.\n private onPanelDocked: (content: Component) => void = (_content: Component): void => {\n this.requestSweep();\n };\n\n /**\n * Constructs a Dock, compiling `options.layout` into the region tree (or an\n * empty `Tab` region when omitted) and running the initial re-wire sweep.\n *\n * @param options - Construction options; `layout` seeds the arrangement.\n * @param subclassDefaults - Optional defaults a subclass injects before the caller's options.\n */\n constructor(options?: DockOptions, subclassDefaults?: Partial<DockOptions>) {\n super(options, { layoutManager: new Fit(), ...subclassDefaults });\n\n const root = options?.layout ? this.compileLayout(options.layout) : this.newTabRegion();\n\n this.addComponent(root);\n this.scheduleSweep();\n this.wireEmptyDropTarget();\n\n // Wire the declarative listener bag here, not in applyOptions: applyOptions\n // runs inside super() before this._listeners' field initializer, so on()\n // would target an undefined bag. scheduleSweep only reconciles emptiness\n // asynchronously, so no emptychange is missed by wiring after it.\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Applies inherited options first, then routes the `emptyContent` placeholder\n * through its setter. The setter only caches (no DOM work) because it is\n * dispatched from within `super()`; the placeholder attaches from the first\n * post-construction reconcile.\n *\n * @param options - The construction options.\n *\n * @returns This dock.\n */\n protected applyOptions(options: DockOptions): this {\n super.applyOptions(options);\n\n if (options.emptyContent !== undefined) {\n this.setEmptyContent(options.emptyContent);\n }\n\n return this;\n }\n\n /**\n * Sets (or clears, with `null`) the placeholder shown while the dock holds no\n * live panel — a start-page for an empty dock. The placeholder is shown as a\n * single non-closeable tab in the empty root region, and is chrome: it is\n * never serialized (excluded from a saved arrangement) and never enters the\n * panel registry. When no placeholder is set, the empty region hides its tab\n * strip entirely; either way the dock still reports emptiness and fires\n * `\"emptychange\"`.\n *\n * When the dock is already empty, the shown placeholder is swapped immediately;\n * otherwise the value is only cached and shown on the next empty transition.\n *\n * @param component - The placeholder component, or `null` to clear it.\n *\n * @returns This dock, for chaining.\n */\n setEmptyContent(component: Component | null): this {\n // Hot-swap only once the state machine is live (first reconcile has run):\n // during the super()/applyOptions cascade _emptyReconciled is still false,\n // keeping the setter cache-only so no DOM work happens at construction. The\n // placeholder is shown while the main region is empty (all tabs closed or\n // torn off), so gate the swap on that, not on dock-wide emptiness.\n const showing = this._emptyReconciled && this.mainRegionEmpty();\n\n if (showing) {\n this.hideEmptyState(); // remove the outgoing placeholder tab\n }\n\n this._options.emptyContent = component ?? undefined;\n\n if (showing) {\n this.showEmptyState(); // show the incoming placeholder tab\n }\n\n return this;\n }\n\n /**\n * The placeholder shown while the dock is empty, or `null` when none is set.\n *\n * @returns The placeholder component, or `null`.\n */\n getEmptyContent(): Component | null {\n return this._options.emptyContent ?? null;\n }\n\n /**\n * Whether the dock holds no live panel anywhere — tiled or floated. A dock\n * whose only panels are torn off into floats is *not* empty (the floats are\n * still live panels of this dock), so this reports `false` for it, and\n * `\"emptychange\"` fires off this aggregate. The empty-state placeholder is a\n * separate, *visual* concern that tracks the main region alone, so it can show\n * over an all-floated dock while this still reports `false`.\n *\n * @returns `true` when no live panel exists.\n */\n isEmpty(): boolean {\n return this._frames.size === 0;\n }\n\n /**\n * Registers the dock itself as a drop target that is active only while the\n * dock is empty — every panel torn off, so no region remains to carry a\n * `DockRegion`. Dropping a tab onto the bare dock then builds a fresh root\n * region to hold it, making an emptied dock its own re-dock surface rather\n * than leaving a placeholder region behind. While non-empty the predicate\n * declines, so the regions' own `DockRegion`s (nested deeper, hit first by\n * the hit-test) keep handling drops.\n */\n private wireEmptyDropTarget(): void {\n DragManager.makeDropTarget(this, {\n accepts: (detail: DragEventDetail): boolean =>\n detail.dragData[\"tabDrag\"] === true && this.getComponents().length === 0,\n // The full-region blue overlay is the only feedback here; suppress the\n // manager's whole-target tint so it does not stack with it.\n suppressValidityTint: true,\n onDragOver: (): null => {\n this._emptyDropOverlay.attachTo(this);\n this._emptyDropOverlay.highlightFull();\n\n return null;\n },\n onDragLeave: (): void => {\n this._emptyDropOverlay.detach();\n },\n onDrop: (detail: DragEventDetail): void => {\n this._emptyDropOverlay.detach();\n\n const panel = tabDragRegistry.get(detail.dragData[\"componentId\"] as string);\n\n if (!panel) {\n return;\n }\n\n const region = this.newTabRegion();\n\n this.addComponent(region);\n region.moveComponent(panel);\n this.scheduleSweep();\n },\n });\n }\n\n /**\n * Registers a panel and adds it as a tab in the active region, then schedules\n * a re-wire sweep so a newly-created region is made dockable.\n *\n * @param spec - The panel to register and dock.\n *\n * @returns This dock, for chaining.\n */\n addPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(content, undefined, this.leafConstraints(spec));\n\n // Activate the freshly added panel so opening it shows it. The tab\n // cell is created lazily on the region's next doLayout, so this may\n // defer (Tab.setActiveContent) until that pass.\n (region.getLayoutManager() as Tab).setActiveContent(content);\n\n // The panel just entered the tiled tree. The ledger is left without an\n // entry for this id so the next sweep's host diff sees a first\n // appearance and emits attach(tiled) — the same reconcile path a\n // dragged-in dock flows through, so a programmatic add and a drop\n // produce the event from identical code.\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Adds a panel whose content is built lazily — on first activation — instead\n * of up front. The tab appears immediately (the identity frame is created\n * empty, so re-open dedup via {@link focusPanel} and layout serialization keep\n * working); on first activation a centred spinner shows while the spec's\n * `content` factory runs, then the built content fades in. The frame's own\n * (strip-hidden) Tab layout drives this via {@link Tab.addLazyTab}, so the\n * spinner/content cross-fade is hosted by the same materialize path every lazy\n * tab uses. Use it for panels whose content is expensive to build or fetches\n * data, so opening one never blocks the tab from appearing.\n *\n * @param spec - The panel to register and dock; `content` is treated as the\n * lazy factory (a live component is wrapped in one).\n *\n * @returns This dock, for chaining.\n */\n addLazyPanel(spec: DockPanelSpec): this {\n this._panels.set(spec.id, spec);\n this._lazyFactories.set(\n spec.id,\n typeof spec.content === \"function\" ? spec.content : () => spec.content as Component\n );\n\n const frame = this.resolvePanel(spec.id);\n\n if (frame) {\n const region = this.activeTabRegion();\n\n this._lastActiveRegion = region;\n region.moveComponent(frame, undefined, this.leafConstraints(spec));\n\n // Activate it so the tab shows; activation drives realizeLazyContent\n // (via the region's \"activate\" -> onPanelFocused) to materialize the\n // deferred content behind a spinner.\n (region.getLayoutManager() as Tab).setActiveContent(frame);\n this._frameRegion.set(spec.id, region);\n\n this.scheduleSweep();\n }\n\n return this;\n }\n\n /**\n * Returns the root region container (a `Container` carrying a `Split`/`Tab`\n * manager). Derived live as the sole `Fit` child rather than cached, because\n * an edge drop onto the root swaps that child for a fresh `Split` wrapper.\n *\n * @returns The root region.\n */\n getRootRegion(): Component {\n return this.getComponents()[0];\n }\n\n /**\n * Captures the current arrangement (delegates to\n * [`serializeLayout`](/api/layout/functions/serializeLayout)).\n *\n * @returns The captured layout state.\n */\n getLayoutState(): LayoutState {\n return serializeLayout(this.getRootRegion());\n }\n\n /**\n * Restores a captured arrangement, sourcing leaves from the panel registry\n * (delegates to [`restoreLayout`](/api/layout/functions/restoreLayout)), then\n * schedules a sweep to wire the fresh regions restore created.\n *\n * The region ledger is cleared first: `restoreLayout` tears the region tree\n * down and rebuilds it, so every surviving panel lands in a fresh region\n * object. Without clearing, the post-restore sweep would read each panel's\n * stale (now-destroyed) region and spuriously fire `\"move\"` for every panel.\n * A restore is not a user-visible relocation, so it stays silent for\n * `\"move\"`; the sweep re-seeds the ledger from the rebuilt tree.\n *\n * @param state - A layout state from {@link getLayoutState}.\n *\n * @returns This dock, for chaining.\n */\n setLayoutState(state: LayoutState): this {\n this._frameRegion.clear();\n restoreLayout(this.getRootRegion(), state, (id: string) => this.resolvePanel(id));\n this.scheduleSweep();\n\n return this;\n }\n\n /**\n * The serialization factory: resolves a panel id to its Dock-owned identity\n * frame, building it once (running a lazy content factory at most once) and\n * caching it so every resolve returns the same instance.\n *\n * The frame is a `Container` constructed with the stable `id` (the serialization\n * key, read back via `getId()`) and the `title` (the visible tab label, via\n * `getName()`) set **at construction** — the caller's content is placed\n * inside it and never mutated. The id must be set at construction, not via a\n * later `setId`, so the frame's `#id`-scoped CSS rule (which carries\n * `position: absolute`) binds to the element; a post-construction `setId`\n * would leave that rule on the old id and the frame would collapse to\n * `position: static`.\n *\n * @param id - The panel id to resolve.\n *\n * @returns The identity frame, or `null` when the id is unknown.\n */\n private resolvePanel(id: string): Component | null {\n const spec = this._panels.get(id);\n\n if (!spec) {\n return null;\n }\n\n let frame = this._frames.get(id);\n\n if (!frame) {\n const factory = this._lazyFactories.get(id);\n\n if (factory) {\n // A lazy panel defers its content to first activation, and the\n // frame's own Tab layout owns that deferral: Tab.addLazyTab mounts a\n // spinner during the two-frame yield and fades the built content in,\n // hosting the transient spinner/content overlap the way every lazy\n // tab does — the strip hidden so the single content fills the frame.\n // Delegating to the Tab is why the frame is not a plain Fit here: a\n // single-child Fit faults the instant the spinner and content coexist.\n const tab = new Tab();\n\n tab.setBarVisible(false);\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: tab });\n\n // The frame's Tab is a local and unreachable from outside, so\n // this subscription is the only thing that can turn a failed\n // content build into a Dock-level event. Named reference, not an\n // inline arrow, per the listener rule in ARCHITECTURE.md — the\n // same shape `wireRegion`'s `onEmpty` uses for a per-frame\n // handler.\n const onFailed: (error: unknown) => void = (error: unknown): void => {\n this.failPanel(spec.id, error);\n };\n\n tab.on(\"exception\", onFailed);\n tab.addLazyTab(factory, spec.title ?? spec.id);\n } else {\n // A normal panel builds its content now; the frame exists at once so\n // the tab shows and the id dedups / serializes like any other.\n frame = new Container({ id: spec.id, name: spec.title, layoutManager: new Fit() });\n\n // A live component or a synchronous factory. A promise-returning\n // factory raises Component.addComponent's Error here: a Fit frame\n // has no spinner and nothing to own the wait.\n frame.addComponent(spec.content);\n }\n\n this._frames.set(id, frame);\n }\n\n return frame;\n }\n\n /**\n * Builds the glyph-only layout constraints for a leaf, or `undefined` when\n * the spec has no glyph. The glyph is the one presentation hint `Tab` reads\n * from the constraint; identity and label ride on the component itself.\n *\n * @param spec - The panel spec.\n *\n * @returns The constraints, or `undefined`.\n */\n private leafConstraints(spec: DockPanelSpec): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n // Dock tabs are closeable by default; a spec may opt out.\n constraints.closeable = spec.closeable ?? true;\n\n if (spec.glyph) {\n constraints.glyph = spec.glyph;\n }\n\n if (spec.tooltip) {\n constraints.tooltip = spec.tooltip;\n }\n\n return constraints;\n }\n\n /**\n * Resolves the region `addPanel` docks into: the root if it is a `Tab`, else\n * the first `Tab` region found depth-first, else the root wrapped in a fresh\n * `Tab` (only reachable for an externally-crafted tab-less tree — `Dock`'s\n * own compiler and `DockRegion` always keep leaves in `Tab` stacks).\n *\n * @returns A `Tab` region to add a tab to.\n */\n private activeTabRegion(): Component {\n const root = this.getRootRegion();\n\n // Prefer the region the user last focused, when it is still a live Tab in\n // the current tree, so a new panel opens beside the last-used tab-bar.\n const last = this._lastActiveRegion;\n if (last && this.isTab(last) && this.containsRegion(root, last)) {\n return last;\n }\n\n if (this.isTab(root)) {\n return root;\n }\n\n return this.firstTabRegion(root) ?? this.wrapRootInTab();\n }\n\n /**\n * Depth-first membership test: whether `target` is `region` or nested under\n * it. Used to confirm `_lastActiveRegion` still lives in the dock's tree\n * before docking into it (a collapsed split or a layout restore can retire a\n * region object).\n *\n * @param region - The subtree root to search.\n * @param target - The region to find.\n *\n * @returns `true` when `target` is within `region`.\n */\n private containsRegion(region: Component, target: Component): boolean {\n if (region === target) {\n return true;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child) && this.containsRegion(child, target)) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Depth-first search for the first `Tab` region at or under `region`.\n *\n * @param region - The region to search from.\n *\n * @returns The first `Tab` region, or `null` when none exists.\n */\n private firstTabRegion(region: Component): Component | null {\n if (this.isTab(region)) {\n return region;\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n const found = this.firstTabRegion(child);\n\n if (found) {\n return found;\n }\n }\n }\n\n return null;\n }\n\n /**\n * Installs a fresh `Tab` region as the dock's single `Fit` child and\n * re-homes the existing root into it, returning the new region. The safety\n * net for a tab-less tree (see {@link activeTabRegion}).\n *\n * @returns The fresh `Tab` region now holding the old root.\n */\n private wrapRootInTab(): Component {\n const region = this.newTabRegion();\n const oldRoot = this.getRootRegion();\n\n this.addComponent(region);\n region.moveComponent(oldRoot);\n\n return region;\n }\n\n /**\n * Builds an empty region: a `Container` carrying a fresh `Tab` manager.\n *\n * @returns The new `Tab` region.\n */\n private newTabRegion(): Component {\n return new Container({ layoutManager: new Tab({ reorderable: true, compact: true }) });\n }\n\n /**\n * Compiles a declarative layout spec into a region `Component`, registering\n * every leaf in the panel registry along the way. The only structural build\n * code unique to `Dock`; runtime restructuring belongs to `DockRegion`.\n *\n * @param spec - The layout spec to compile.\n *\n * @returns The compiled region.\n */\n private compileLayout(spec: DockLayoutSpec): Component {\n if (\"split\" in spec) {\n const region = new Container({ layoutManager: new Split({ orientation: spec.split }) });\n\n for (const child of spec.children) {\n region.addComponent(this.compileRegion(child));\n }\n\n return region;\n }\n\n if (\"tabs\" in spec) {\n return this.compileTabs(spec.tabs);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Compiles a child of a split: a leaf becomes its own single-tab stack so a\n * split pane is always a draggable region, never a bare leaf.\n *\n * @param spec - The child spec.\n *\n * @returns The compiled region.\n */\n private compileRegion(spec: DockLayoutSpec): Component {\n if (\"split\" in spec || \"tabs\" in spec) {\n return this.compileLayout(spec);\n }\n\n return this.compileTabs([spec]);\n }\n\n /**\n * Builds a `Tab` region holding the given leaves, registering and stamping\n * each one.\n *\n * @param specs - The leaf panels to stack.\n *\n * @returns The `Tab` region.\n */\n private compileTabs(specs: DockPanelSpec[]): Component {\n const region = this.newTabRegion();\n\n for (const spec of specs) {\n this._panels.set(spec.id, spec);\n\n const content = this.resolvePanel(spec.id);\n\n if (content) {\n region.addComponent(content, this.leafConstraints(spec));\n\n // A compiled panel starts tiled; seed the host ledger with null\n // so the first sweep's host diff is silent (no transition to\n // attach from). Construction therefore emits nothing.\n this._panelHost.set(spec.id, null);\n this._frameRegion.set(spec.id, region);\n }\n }\n\n return region;\n }\n\n /**\n * Schedules one coalesced re-wire sweep on the next animation frame. Repeated\n * calls within a frame collapse to a single sweep.\n */\n private scheduleSweep(): void {\n if (this._sweepScheduled) {\n return;\n }\n\n this._sweepScheduled = true;\n\n DOM.sink.requestAnimationFrame(() => {\n this._sweepScheduled = false;\n this.runSweep();\n });\n }\n\n /**\n * Runs the idempotent sweep: adopt every owned float window into a wired\n * region tree, wire the in-dock root and each float region, then tear down\n * the coordinators of regions that have vanished from the combined live tree.\n */\n private runSweep(): void {\n const root = this.getRootRegion();\n\n if (!root) {\n return;\n }\n\n const floatRegions = this.ownedFloatWindows()\n .map(win => this.adoptFloat(win))\n .filter((region): region is Component => region !== null);\n\n this.wireRegion(root);\n\n for (const region of floatRegions) {\n this.wireRegion(region);\n }\n\n this.subscribeFloatWindows();\n this.reconcileHosts(root);\n this.teardownVanished(root, floatRegions);\n this.reconcileEmptyState();\n }\n\n /**\n * Reconciles the two independent empty concerns each sweep:\n *\n * - **Placeholder / bar** track the *main region's* emptiness — whether the\n * root region holds no tiled panel. This is true both when every tab was\n * closed and when they were all torn off into floats, so the start page\n * shows in either case. Re-asserted idempotently every sweep (not just on a\n * transition) so it re-shows after a `setLayoutState` restore clears the\n * root region wholesale.\n * - **`\"emptychange\"`** tracks the *dock-wide* emptiness — {@link isEmpty},\n * `true` only when no panel exists anywhere (tiled or floated). Latched on\n * `_empty` and emitted exactly once per real transition; a no-op sweep is\n * silent. Called at the end of every sweep and reached from every close via\n * {@link scheduleSweep}, so the emit is once per settled transition.\n */\n private reconcileEmptyState(): void {\n if (this.mainRegionEmpty()) {\n this.showEmptyState();\n } else {\n this.hideEmptyState();\n }\n\n const empty = this.isEmpty();\n\n // First reconcile: sync the born state without an emit (being born empty\n // or populated is not a transition). A born-with-layout dock differs from\n // the true=seed latch and falls through to emit emptychange(false) once.\n if (!this._emptyReconciled) {\n this._emptyReconciled = true;\n\n if (empty === this._empty) {\n return;\n }\n }\n\n if (empty === this._empty) {\n return;\n }\n\n this._empty = empty;\n this.emit(\"emptychange\", { empty });\n }\n\n /**\n * Whether the main (tiled) region holds no panel — every tab closed, or all\n * torn off into floats. The placeholder is excluded so it never counts as\n * content. This drives the placeholder, distinct from {@link isEmpty} (which\n * counts floated panels as live).\n *\n * @returns `true` when the root region shows no tiled panel.\n */\n private mainRegionEmpty(): boolean {\n const region = this.getRootRegion();\n\n if (!region) {\n return false;\n }\n\n const placeholder = this.getEmptyContent();\n\n return region.getComponents().every(child => child === placeholder);\n }\n\n /**\n * Shows the empty-state chrome in the empty root region. With a placeholder\n * set, it is docked as a single non-closeable, `transient` (never-serialized)\n * tab; without one, the region's tab strip is hidden so an emptied dock is a\n * clean surface rather than a dangling empty strip. Idempotent — safe to call\n * on every sweep — and a no-op when the root region is not a `Tab`.\n */\n private showEmptyState(): void {\n const region = this.getRootRegion();\n const placeholder = this.getEmptyContent();\n\n if (!region) {\n return;\n }\n\n if (placeholder) {\n this.rootTab()?.setBarVisible(true);\n\n if (placeholder.getParentComponent() !== region) {\n region.moveComponent(placeholder, undefined, this.placeholderConstraints());\n }\n } else {\n this.rootTab()?.setBarVisible(false);\n }\n }\n\n /**\n * Removes the empty-state chrome: detaches the placeholder tab when present\n * and restores the root region's tab strip. Idempotent — safe to call on\n * every sweep and when nothing is shown.\n */\n private hideEmptyState(): void {\n const placeholder = this.getEmptyContent();\n const parent = placeholder?.getParentComponent();\n const manager = parent?.getLayoutManager();\n\n // Remove the placeholder from wherever it currently sits. A `Tab` host must\n // go through `closeTab` (not `removeComponent`): `Tab.doLayout` reconciles\n // only *added* children, so a bare `removeComponent` would orphan the strip\n // cell and leave a stale placeholder tab. `closeTab`'s `\"tabclose\"` is\n // ignored by the dock (the placeholder is not a registered frame) and its\n // drain-`\"empty\"` is absorbed by the root-region prune guard. `closeTab`\n // returns false when no cell exists yet (added but not laid out); then a\n // plain `removeComponent` suffices — there is no cell to orphan.\n if (placeholder) {\n const closed = manager instanceof Tab && manager.closeTab(placeholder);\n\n if (!closed) {\n parent?.removeComponent(placeholder);\n }\n }\n\n this.rootTab()?.setBarVisible(true);\n }\n\n /**\n * The root region's `Tab` manager, or `null` when the root region is absent or\n * carries a different manager (e.g. an externally-crafted `Split` root). An\n * empty dock's root is always a `Tab` — the prune path keeps a single empty\n * `Tab` region — so the placeholder always finds one.\n *\n * @returns The root `Tab` manager, or `null`.\n */\n private rootTab(): Tab | null {\n const manager = this.getRootRegion()?.getLayoutManager();\n\n return manager instanceof Tab ? manager : null;\n }\n\n /**\n * The layout constraints the placeholder tab is docked under: non-closeable\n * (the start page cannot be closed) and `transient` (shown as a tab but never\n * captured by serialization). The tab's label is the placeholder component's\n * own name.\n *\n * @returns The placeholder tab constraints.\n */\n private placeholderConstraints(): LayoutConstraints {\n const constraints = new LayoutConstraints();\n\n constraints.closeable = false;\n constraints.transient = true;\n\n return constraints;\n }\n\n /**\n * Idempotently subscribes the lifecycle events of every float window that\n * currently hosts one of this dock's frames — both the adopted bare\n * `Window` mini-docks and the self-contained `TabWindow` tear-offs the sweep\n * does not adopt. A `TabWindow`'s internal `Tab` is never wired by\n * `wireRegion`, so its `\"activate\"` / `\"tabclose\"` / `\"detach\"` /\n * `\"dock\"` are subscribed here explicitly; both float kinds get the\n * window's `\"activate\"` / `\"close\"`. The tracked set stops a re-sweep\n * stacking duplicate listeners.\n */\n private subscribeFloatWindows(): void {\n for (const win of this.floatWindowsHoldingFrames()) {\n if (this._floatSubscribed.has(win)) {\n continue;\n }\n\n const onFloatActivate: () => void = (): void => { this.onFloatActivated(win); };\n const onFloatClose: () => void = (): void => { this.onFloatClosed(win); };\n\n win.on(\"activate\", onFloatActivate);\n win.on(\"close\", onFloatClose);\n\n if (win instanceof TabWindow) {\n const tab = win.getLayoutManager() as Tab;\n\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n }\n\n this._floatSubscribed.add(win);\n }\n\n this.pruneClosedFloatSubscriptions();\n }\n\n /**\n * Drops closed windows from the float-subscription tracking set so a future\n * window object never collides with a stale entry. The listeners themselves\n * die with the closed window, so only the set bookkeeping is needed.\n */\n private pruneClosedFloatSubscriptions(): void {\n const open = new Set<AbstractWindow>(AbstractWindow.getOpenWindows());\n\n for (const win of this._floatSubscribed) {\n if (!open.has(win)) {\n this._floatSubscribed.delete(win);\n }\n }\n }\n\n /**\n * Open float windows hosting one of this dock's frames, including the\n * self-contained `TabWindow` tear-offs (which `ownedFloatWindows` excludes\n * because the sweep does not adopt them), and excluding the window the dock\n * itself lives in. The subscription targets for the panel lifecycle.\n *\n * @returns The float windows holding this dock's frames.\n */\n private floatWindowsHoldingFrames(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Recomputes each registered frame's host (`null` when it sits under the\n * in-dock tiled tree, else the float window holding it) and region, and diffs\n * both against their ledgers, emitting the lifecycle events the change\n * implies: a first appearance (no host ledger entry — a fresh `addPanel` or a\n * restore) emits `\"attach\"` alone; a change from one host to another (a\n * tear-off, a re-dock by either drop path, a float-to-float move) emits\n * `\"detach\"`(old host) then `\"attach\"`(new host); a same-host change of\n * region (a relocation to a different region within one host) emits\n * `\"move\"`; an unchanged host and region is silent. This is the single\n * source of every `\"attach\"`/`\"detach\"`/`\"move\"`, so the events are\n * identical regardless of which DnD path landed the sweep.\n *\n * Only frames still registered and still cached are visited, so a panel whose\n * frame a close handler already evicted produces no phantom `\"detach\"`.\n *\n * @param root - The current root region.\n */\n private reconcileHosts(root: Component): void {\n for (const [id, frame] of this._frames) {\n if (!this._panels.has(id)) {\n continue;\n }\n\n const host = this.hostForFrame(frame, root);\n const had = this._panelHost.has(id);\n const prev = this._panelHost.get(id) ?? null;\n const region = this.regionForFrame(frame);\n const prevRegion = this._frameRegion.get(id) ?? null;\n\n if (!had) {\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (host !== prev) {\n this.emit(\"detach\", { id, content: frame, window: prev });\n this.emit(\"attach\", { id, content: frame, window: host });\n } else if (region && prevRegion && region !== prevRegion) {\n // Same host, different region: the panel relocated within its\n // host. The `region && prevRegion` guard keeps a frame transiently\n // out of any region (mid-teardown) silent rather than spurious.\n this.emit(\"move\", { id, content: frame, window: host });\n }\n\n this._panelHost.set(id, host);\n\n if (region) {\n this._frameRegion.set(id, region);\n }\n }\n }\n\n /**\n * The host a frame currently occupies: `null` when it sits under the tiled\n * tree (the main dock), otherwise the float window holding it. Reuses the\n * existing tiled test and float lookup so the reconcile and the\n * focus-payload construction derive the host the same way.\n *\n * @param frame - The identity frame to locate.\n * @param root - The current root region.\n *\n * @returns The host window, or `null` for the tiled tree.\n */\n private hostForFrame(frame: Component, root: Component): AbstractWindow | null {\n return this.isUnder(root, frame) ? null : this.floatForFrame(frame);\n }\n\n /**\n * Open windows whose content subtree holds one of this dock's identity frames\n * — the floats torn off from this dock — excluding the window the dock itself\n * lives in. Re-derived each sweep (never cached) so a closed float drops out\n * naturally, mirroring the derived-live root in {@link getRootRegion}.\n *\n * A {@link TabWindow} is excluded: a default tear-off opens one as a\n * self-contained floating tabbed window, re-dockable via its own tab DnD and\n * self-closing when emptied. It is never adopted into the dock's region tree,\n * so the sweep must leave it alone — only the Shift-torn bare {@link Window}\n * floats become adoptable mini-docks.\n *\n * @returns The owned float windows.\n */\n private ownedFloatWindows(): AbstractWindow[] {\n const frames = [...this._frames.values()];\n\n return AbstractWindow.getOpenWindows().filter(win =>\n !(win instanceof TabWindow) &&\n !this.windowContains(win, this) &&\n frames.some(frame => this.windowContains(win, frame)));\n }\n\n /**\n * Whether `node` lies within `win`'s subtree — walks `node`'s ancestor chain\n * looking for `win`. Used both to detect a float hosting a frame and to\n * exclude the dock's own host window (which contains the dock, hence every\n * still-docked frame).\n *\n * @param win - The candidate ancestor window.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `win` is an ancestor of `node`.\n */\n private windowContains(win: AbstractWindow, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === win) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * A window's first non-chrome child — its content panel — or `null` when the\n * window has none yet.\n *\n * @param win - The window to inspect.\n *\n * @returns The content component, or `null`.\n */\n private windowContent(win: AbstractWindow): Component | null {\n return win.getComponents().find(child => !win.isChromeComponent(child)) ?? null;\n }\n\n /**\n * Ensures a float window's content is a wired-able region tree and returns\n * that region. A freshly torn-off float holds its bare identity frame as the\n * window's content; this wraps it in a single fresh `Tab` region so it is a\n * proper region leaf with a draggable handle, turning the window into a\n * mini-dock. Idempotent: once the content is a region container (already\n * adopted, or restored as a tree) it is returned unchanged, so re-sweeps\n * after edge-splits inside the float do not re-wrap.\n *\n * @param win - The float window to adopt.\n *\n * @returns The float's content region, or `null` when the window has no content.\n */\n private adoptFloat(win: AbstractWindow): Component | null {\n const content = this.windowContent(win);\n\n if (!content) {\n return null;\n }\n\n if (this.isRegionContainer(content)) {\n return content;\n }\n\n const region = this.newTabRegion();\n\n // A fresh region carries no constraint, so the window's Border fills it as\n // an unplaced→CENTER child — the same way the bare frame filled before.\n win.moveComponent(region);\n region.moveComponent(content);\n\n return region;\n }\n\n /**\n * Idempotently wires a region and recurses into its child regions: makes a\n * `Tab` region reorderable and prunes it when its last tab leaves, and gives\n * every region a `DockRegion` so it accepts edge/centre drops and notifies\n * the dock after a drop mutates the tree.\n *\n * @param region - The region to wire.\n */\n private wireRegion(region: Component): void {\n let wiring = this._wiring.get(region);\n\n if (!wiring) {\n wiring = { dockRegion: new DockRegion(region, this.requestSweep), tabWired: false };\n\n this._wiring.set(region, wiring);\n }\n\n const manager = region.getLayoutManager();\n\n if (this.isTab(region) && !wiring.tabWired) {\n const tab: Tab = manager as Tab;\n\n tab.setReorderable(true);\n // Per-region prune; the named const carries the region the shared\n // handler set otherwise could not (ARCHITECTURE: a listener is a named\n // reference, never an inline arrow).\n const onEmpty: () => void = (): void => { this.pruneRegion(region); };\n\n tab.on(\"empty\", onEmpty);\n // The lifecycle handlers are shared bound methods: their payloads (the\n // closed/activated content, the torn-off window) carry the identity\n // they need, so no per-region capture is required.\n tab.on(\"tabclose\", this.onPanelClosed);\n tab.on(\"activate\", this.onPanelFocused);\n tab.on(\"detach\", this.onPanelDetached);\n tab.on(\"dock\", this.onPanelDocked);\n\n wiring.tabWired = true;\n }\n\n for (const child of region.getComponents()) {\n // A lazy panel's identity frame carries a Tab manager, which would\n // otherwise make this sweep wire the panel itself as a drop-taking,\n // prunable region — so its inner strip draining would prune the\n // frame out of its parent and leave a phantom tab behind.\n if (this.isRegionContainer(child) && this._frames.get(child.getId()) !== child) {\n this.wireRegion(child);\n }\n }\n }\n\n /**\n * Removes a `Tab` region once its last tab has left, then collapses a `Split`\n * that the removal leaves with a single pane, and schedules a sweep to tear\n * down the removed region's coordinator. Wired to every `Tab` region's\n * `\"empty\"` event so regions the dock itself built (the `compileLayout` /\n * `restoreLayout` tree) are pruned, not just the stacks `DockRegion` mints.\n * No-op when the region was already detached (a `DockRegion`-created stack\n * pruned itself first) or still holds a tab.\n *\n * @param region - The region whose `Tab` fired `\"empty\"`.\n */\n private pruneRegion(region: Component): void {\n const parent = region.getParentComponent();\n\n if (!parent || region.getComponents().length > 0) {\n return;\n }\n\n // Keep an emptied root region (its parent is the dock) as the dock's\n // add/drop target — removing it would leave the dock with no region and\n // crash the next addPanel.\n if (parent === this) {\n return;\n }\n\n parent.removeComponent(region);\n this.collapseSinglePaneSplit(parent);\n this.closeFloatIfEmpty(parent);\n this.scheduleSweep();\n }\n\n /**\n * Closes a float window whose mini-dock just emptied. When a region was the\n * direct content of a float window and pruning it leaves the window with no\n * content, the float has nothing left to host — close it, matching the\n * auto-close a strip-mode tear-off window performs when its last tab leaves.\n * A no-op when `container` is an in-dock region (its parent is the dock or a\n * `Split`, never a window) or the window still holds content.\n *\n * @param container - The container the pruned region was removed from.\n */\n private closeFloatIfEmpty(container: Component): void {\n if (!AbstractWindow.getOpenWindows().includes(container as AbstractWindow)) {\n return;\n }\n\n if (!this.windowContent(container as AbstractWindow)) {\n (container as AbstractWindow).requestClose();\n }\n }\n\n /**\n * Collapses a `Split` left with a single child: the lone child is hoisted\n * into the grandparent at the `Split`'s slot (via `moveComponent`) and the\n * emptied `Split` removed. A no-op unless `container` is a single-child\n * `Split` with a grandparent (a root single-pane `Split` renders its child\n * full-bleed). The swap keeps the grandparent's child count unchanged, so the\n * collapse does not cascade.\n *\n * @param container - The container that just lost a child.\n */\n private collapseSinglePaneSplit(container: Component): void {\n if (this.regionKind(container) !== \"Split\") {\n return;\n }\n\n const children = container.getComponents();\n\n if (children.length !== 1) {\n return;\n }\n\n const grandparent = container.getParentComponent();\n\n if (!grandparent) {\n return;\n }\n\n const index = grandparent.getComponents().indexOf(container);\n\n // Carry the collapsing Split's stored size onto the hoisted child so the\n // slot keeps the user's dragged ratio (when the grandparent is a Split).\n if (this.regionKind(grandparent) === \"Split\") {\n (grandparent.getLayoutManager() as Split).transferPaneSize(container, children[0]);\n }\n\n grandparent.moveComponent(children[0], index);\n grandparent.removeComponent(container);\n }\n\n /**\n * Destroys the wiring of every tracked region no longer reachable from the\n * combined live tree (the in-dock root plus every owned float's region tree),\n * releasing the drop targets a removed region's coordinator held. Seeding the\n * reachable set from the float regions too is what keeps a float's drop\n * targets alive across sweeps driven by unrelated in-dock moves.\n *\n * @param root - The current root region.\n * @param floatRegions - The adopted content region of each owned float window.\n */\n private teardownVanished(root: Component, floatRegions: Component[]): void {\n const reachable = new Set<Component>();\n\n this.collectRegions(root, reachable);\n\n for (const region of floatRegions) {\n this.collectRegions(region, reachable);\n }\n\n for (const [region, wiring] of this._wiring) {\n if (!reachable.has(region)) {\n wiring.dockRegion.destroy();\n this._wiring.delete(region);\n }\n }\n }\n\n /**\n * Collects every region container at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The set to populate.\n */\n private collectRegions(region: Component, into: Set<Component>): void {\n into.add(region);\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectRegions(child, into);\n }\n }\n }\n\n /**\n * Whether a component is a region container — a `Container` carrying a `Split`\n * or `Tab` manager. Discriminates on the stripped runtime class name (no\n * `instanceof`, avoiding an import cycle), matching how serialization keys\n * its node kinds.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Split`/`Tab` region.\n */\n private isRegionContainer(component: Component): boolean {\n const kind = this.regionKind(component);\n\n return kind === \"Split\" || kind === \"Tab\";\n }\n\n /**\n * Whether a component is a `Tab` region.\n *\n * @param component - The component to test.\n *\n * @returns `true` for a `Tab` region.\n */\n private isTab(component: Component): boolean {\n return this.regionKind(component) === \"Tab\";\n }\n\n /**\n * The stripped runtime class name of a component's layout manager, or the\n * empty string when it has none (a leaf content component).\n *\n * @param component - The component to inspect.\n *\n * @returns The manager's class name without its `_` export-alias prefix.\n */\n private regionKind(component: Component): string {\n const manager = component.getLayoutManager() as (Tab | Split | undefined);\n\n return manager ? manager.getClassName().replace(/^_/, \"\") : \"\";\n }\n\n // ----- panel lifecycle -----\n\n /**\n * `\"tabclose\"` handler for every wired `Tab` (tiled or float): a registered\n * panel was genuinely closed. Emits `\"close\"`, evicts the cached frame so a\n * re-`addPanel` rebuilds it via the lazy factory (keeping the `_panels`\n * registration), and — when the closed panel was the dock-wide focused one —\n * recomputes focus once the source `Tab` has re-selected a survivor.\n *\n * @param content - The closed tab's content (a Dock identity frame).\n */\n private onPanelClosed = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n const region = this._frameRegion.get(id) ?? null;\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content, window: null });\n\n // Route the close through the one reconcile site so \"emptychange\" fires\n // even when no structural prune scheduled a sweep. The latch-diff makes a\n // second reconcile in the same settled state a no-op, so this cannot\n // double-fire alongside pruneRegion's own scheduleSweep.\n this.scheduleSweep();\n\n if (this._focusedPanelId === id) {\n this.scheduleFocusRecompute(region);\n }\n };\n\n /**\n * Closes a lazy panel whose content factory rejected, then reports the\n * failure as this dock's `\"exception\"`. The panel's own `\"close\"` event\n * fires first, from the shared close path.\n *\n * The panel stays registered, so re-adding the same id rebuilds its frame\n * and runs the factory again — that is the retry path.\n *\n * @param id - The id of the panel whose factory rejected.\n * @param error - The value the factory's promise rejected with.\n */\n private failPanel(id: string, error: unknown): void {\n // The panel was closed while its factory was still in flight: the close\n // path already evicted its frame, so there is nothing left to tear down\n // and nobody left to report to. Closing a docked panel closes the tab in\n // the *outer* region and never touches the frame's own entry, so the\n // inner staleness check cannot see this — the registry is what knows.\n if (!this._frames.has(id)) {\n return;\n }\n\n // A frame that is registered but sits in no Tab region cannot be closed\n // through the shared path; evict it directly so a re-add rebuilds it.\n if (!this.removePanel(id)) {\n this._frames.delete(id);\n }\n\n this.emit(\"exception\", { id, error });\n }\n\n /**\n * `\"activate\"` handler for every wired `Tab`: the active tab changed via a\n * click or `setActiveTabIndex`. Emits `\"focus\"` for the now-active panel,\n * gated on a genuine focused-panel change.\n *\n * @param content - The now-active tab's content (a Dock identity frame).\n */\n private onPanelFocused = (content: Component): void => {\n const id = content.getId();\n\n if (this._frames.get(id) !== content) {\n return;\n }\n\n // Remember the focused panel's region so the next add docks beside the\n // tab-bar the user just worked in (see activeTabRegion).\n const region = this._frameRegion.get(id);\n if (region) {\n this._lastActiveRegion = region;\n }\n\n this.setFocus(id);\n };\n\n /**\n * `\"detach\"` handler for every wired `Tab`: a tab was torn off into a new\n * float window. Schedules a sweep so the new float is wired (and any\n * Shift-torn bare `Window` adopted); the sweep's host-diff reconcile observes\n * the torn-off frame's tiled -> float transition and emits the\n * `\"detach\"`(tiled) + `\"attach\"`(float) pair itself, so this handler emits\n * nothing directly — the reconcile is the single source of those events.\n *\n * @param _window - The float window the tab was torn off into (unused; the\n * reconcile re-derives every frame's host).\n */\n private onPanelDetached = (_window: AbstractWindow): void => {\n this.scheduleSweep();\n };\n\n /**\n * Window `\"activate\"` handler for an owned float: the float became the active\n * layer. Emits `\"focus\"` for the float's active panel, gated on a genuine\n * focused-panel change.\n *\n * @param window - The float window that was activated.\n */\n private onFloatActivated(window: AbstractWindow): void {\n const frame = this.activeFrameInFloat(window);\n\n if (frame) {\n this.setFocus(frame.getId());\n }\n }\n\n /**\n * Window `\"close\"` handler for an owned float: the float's chrome ✕ closed\n * it. Emits one `\"close\"` per registered frame the float held (a bare-`Window`\n * mini-dock can hold several) — read before the window tears down — and\n * recomputes focus when a closed frame was the focused panel.\n *\n * @param window - The float window being closed.\n */\n private onFloatClosed(window: AbstractWindow): void {\n let focusLost = false;\n\n for (const frame of this.framesInWindow(window)) {\n const id = frame.getId();\n\n this._frames.delete(id);\n this._panelHost.delete(id);\n this._frameRegion.delete(id);\n\n this.emit(\"close\", { id, content: frame, window: null });\n\n if (this._focusedPanelId === id) {\n focusLost = true;\n }\n }\n\n // Route the close through the reconcile so a last-float close flips the\n // empty latch and fires \"emptychange\" (a float close schedules no\n // structural sweep of its own).\n this.scheduleSweep();\n\n if (focusLost) {\n this.scheduleFocusRecompute(null);\n }\n }\n\n /**\n * Sets the dock-wide focused panel and emits `\"focus\"` only on a genuine\n * change, so re-activating the already-focused panel is silent. A `null` id\n * clears focus and emits `focus(null)`.\n *\n * @param id - The newly-focused panel id, or `null` when none is focused.\n */\n private setFocus(id: string | null): void {\n if (id === this._focusedPanelId) {\n return;\n }\n\n this._focusedPanelId = id;\n\n if (id === null) {\n this.emit(\"focus\", null);\n\n return;\n }\n\n const frame = this._frames.get(id);\n\n if (frame) {\n const host = this.hostForFrame(frame, this.getRootRegion());\n\n this.emit(\"focus\", { id, content: frame, window: host });\n }\n }\n\n /**\n * Schedules a deferred focus recompute after a close. The source `Tab`\n * re-selects a survivor (visually, with no event) *after* its `\"tabclose\"`\n * fires, so the new active tab is only readable on the next frame.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private scheduleFocusRecompute(region: Component | null): void {\n DOM.sink.requestAnimationFrame(() => this.recomputeFocusAfterClose(region));\n }\n\n /**\n * Recomputes the dock-wide focus after the focused panel was closed: when\n * panels remain in `region`, focus the survivor the region re-selected; when\n * no panel remains anywhere, emit `focus(null)`.\n *\n * @param region - The region the closed frame was hosted in, or `null`.\n */\n private recomputeFocusAfterClose(region: Component | null): void {\n if (this._frames.size === 0) {\n this.setFocus(null);\n\n return;\n }\n\n if (!region || !this.isTab(region) || region.getComponents().length === 0) {\n this.setFocus(null);\n\n return;\n }\n\n const frame = (region.getLayoutManager() as Tab).getActiveContent();\n\n this.setFocus(frame ? frame.getId() : null);\n }\n\n /**\n * Activates the tab hosting `id` and raises its host float when it lives in\n * one, so a buried floated panel surfaces. A successful activation drives the\n * host `Tab`'s active-tab change and the float raise, each of which emits a\n * `\"focus\"`.\n *\n * @param id - The panel id to focus.\n *\n * @returns `true` when the panel was found and activated, `false` for an\n * unknown id or one in no `Tab` region (registered but never docked).\n */\n focusPanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n const index = (region.getLayoutManager() as Tab).indexOfContent(frame);\n\n if (index < 0) {\n return false;\n }\n\n this.floatForFrame(frame)?.bringToFront();\n (region.getLayoutManager() as Tab).setActiveTabIndex(index);\n\n return true;\n }\n\n /**\n * Closes the panel `id` through the same user-close path a tab ✕ takes, so it\n * emits exactly one `\"close\"` through the shared `\"tabclose\"` subscription.\n *\n * @param id - The panel id to close.\n *\n * @returns `true` when the panel was found and closed, `false` for an unknown\n * id or one in no `Tab` region.\n */\n removePanel(id: string): boolean {\n const frame = this._frames.get(id);\n\n if (!frame) {\n return false;\n }\n\n const region = this.regionForFrame(frame);\n\n if (!region) {\n return false;\n }\n\n return (region.getLayoutManager() as Tab).closeTab(frame);\n }\n\n /**\n * The `Tab` region currently hosting `frame` — searched across the in-dock\n * tiled tree and every float window's region tree — or `null` when no `Tab`\n * region holds it (registered but never docked, or mid-teardown).\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host `Tab` region, or `null`.\n */\n private regionForFrame(frame: Component): Component | null {\n for (const region of this.allTabRegions()) {\n if ((region.getLayoutManager() as Tab).indexOfContent(frame) >= 0) {\n return region;\n }\n }\n\n return null;\n }\n\n /**\n * The float window currently hosting `frame`, or `null` when it lives in the\n * in-dock tiled tree (or nowhere). Used to raise a buried float on focus.\n *\n * @param frame - The identity frame to locate.\n *\n * @returns The host float window, or `null`.\n */\n private floatForFrame(frame: Component): AbstractWindow | null {\n return this.floatWindowsHoldingFrames().find(win => this.windowContains(win, frame)) ?? null;\n }\n\n /**\n * Every `Tab` region across the combined live tree: the in-dock root plus\n * each float window (an adopted bare-`Window` mini-dock's region tree, and a\n * `TabWindow` whose own layout manager is the `Tab`).\n *\n * @returns The live `Tab` regions.\n */\n private allTabRegions(): Component[] {\n const regions: Component[] = [];\n const root = this.getRootRegion();\n\n if (root) {\n this.collectTabRegions(root, regions);\n }\n\n for (const win of this.floatWindowsHoldingFrames()) {\n if (win instanceof TabWindow) {\n regions.push(win as unknown as Component);\n\n continue;\n }\n\n const content = this.windowContent(win);\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n }\n\n return regions;\n }\n\n /**\n * Collects every `Tab` region at or under `region` into `into`.\n *\n * @param region - The region to collect from.\n * @param into - The array to populate.\n */\n private collectTabRegions(region: Component, into: Component[]): void {\n if (this.isTab(region)) {\n into.push(region);\n }\n\n for (const child of region.getComponents()) {\n if (this.isRegionContainer(child)) {\n this.collectTabRegions(child, into);\n }\n }\n }\n\n /**\n * The registered frames of this dock that lie within `window`'s subtree — the\n * panels a float holds. Read at float-close time to fan out one `\"close\"` per\n * frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The registered frames inside the window.\n */\n private framesInWindow(window: AbstractWindow): Component[] {\n return [...this._frames.values()].filter(frame => this.windowContains(window, frame));\n }\n\n /**\n * The active panel frame inside a float window: a `TabWindow`'s own active\n * tab, or the active tab of the first `Tab` region inside a bare-`Window`\n * mini-dock. `null` when none resolves to a registered frame.\n *\n * @param window - The float window to inspect.\n *\n * @returns The active registered frame, or `null`.\n */\n private activeFrameInFloat(window: AbstractWindow): Component | null {\n let tab: Tab | null = null;\n\n if (window instanceof TabWindow) {\n tab = window.getLayoutManager() as Tab;\n } else {\n const content = this.windowContent(window);\n const regions: Component[] = [];\n\n if (content) {\n this.collectTabRegions(content, regions);\n }\n\n tab = regions.length > 0 ? (regions[0].getLayoutManager() as Tab) : null;\n }\n\n const frame = tab ? tab.getActiveContent() : null;\n\n return frame && this._frames.get(frame.getId()) === frame ? frame : null;\n }\n\n /**\n * Whether `node` lies at or under `ancestor`'s component subtree.\n *\n * @param ancestor - The candidate ancestor component.\n * @param node - The component whose ancestor chain to walk.\n *\n * @returns `true` when `ancestor` is `node` or one of its ancestors.\n */\n private isUnder(ancestor: Component, node: Component): boolean {\n for (let current: Component | null = node; current; current = current.getParentComponent()) {\n if (current === ancestor) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Registers a listener for a panel-lifecycle event. The `\"attach\"`,\n * `\"detach\"`, `\"move\"`, and `\"close\"` events always carry a\n * {@link DockPanelEvent}; `\"focus\"` carries a `DockPanelEvent` or `null` when\n * nothing is focused. The payload's `window` field names the host: `null` for\n * the tiled tree, otherwise the float window the panel entered (`\"attach\"`),\n * left (`\"detach\"`), or moved within (`\"move\"`, same host before and after);\n * it is always `null` for `\"close\"`.\n *\n * @param event - `\"attach\"` / `\"detach\"` / `\"move\"` / `\"close\"`.\n * @param listener - Invoked with the affected panel.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Registers a listener for the `\"focus\"` event, which fires when the\n * dock-wide active panel changes, carrying the now-focused panel or `null`\n * when nothing is focused (e.g. the last panel closed).\n *\n * @param event - The `\"focus\"` event.\n * @param listener - Invoked with the now-focused panel, or `null`.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Registers a listener for the `\"emptychange\"` event, which fires once each\n * time the dock transitions between empty (no live panel anywhere) and\n * populated, carrying `{ empty }`.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - Invoked with the new emptiness state.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Registers a listener for the `\"exception\"` event, which fires when a lazy\n * panel's content factory rejected. The panel has already been closed and\n * its `\"close\"` event already emitted, so a listener must not call\n * {@link Dock.removePanel} for that id. The panel stays registered, so\n * re-adding the same id rebuilds its frame and retries the factory.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - Invoked with the failed panel's id and the rejection value.\n *\n * @returns This dock, for method chaining.\n */\n on(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n on(event: DockEvent, listener: Function): this {\n this._listeners.add(event, listener);\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"attach\" | \"detach\" | \"move\" | \"close\", listener: (event: DockPanelEvent) => void): this;\n /**\n * Removes a previously registered `\"focus\"` listener.\n *\n * @param event - The `\"focus\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"focus\", listener: (event: DockPanelEvent | null) => void): this;\n /**\n * Removes a previously registered `\"emptychange\"` listener.\n *\n * @param event - The `\"emptychange\"` event.\n * @param listener - The callback to remove.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"emptychange\", listener: (event: DockEmptyEvent) => void): this;\n /**\n * Removes a previously registered `\"exception\"` listener.\n *\n * @param event - The `\"exception\"` event.\n * @param listener - The exact listener reference passed to `on`.\n *\n * @returns This dock, for method chaining.\n */\n off(event: \"exception\", listener: (event: DockExceptionEvent) => void): this;\n off(event: DockEvent, listener: Function): this {\n this._listeners.remove(event, listener);\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event` with `payload`, in\n * registration order.\n *\n * @param event - The event to emit.\n * @param payload - The lifecycle payload (`null` only for `\"focus\"`).\n */\n protected emit(event: \"attach\" | \"detach\" | \"move\" | \"close\", payload: DockPanelEvent): void;\n protected emit(event: \"focus\", payload: DockPanelEvent | null): void;\n protected emit(event: \"emptychange\", payload: DockEmptyEvent): void;\n protected emit(event: \"exception\", payload: DockExceptionEvent): void;\n protected emit(event: DockEvent, payload: DockPanelEvent | DockEmptyEvent | DockExceptionEvent | null): void {\n this._listeners.fire(event, payload);\n }\n\n /**\n * Disposes the empty-state drop overlay before the base destructor runs.\n * `attachTo` raw-appends the overlay onto this dock's element rather than\n * registering it as a child, so the inherited child recursion never\n * reaches it; the `detach()` calls in the drop-target callbacks are\n * mid-drag hides of a still-live overlay, not teardown.\n */\n protected destructor(): void {\n this._emptyDropOverlay.dispose();\n\n // Each wired region owns a DockRegion holding its own drop-zone\n // overlay. `destroy()` is otherwise reached only by the\n // unreachable-region sweep, so without this every region a drag ever\n // hovered would keep its overlay's rules when the dock goes away.\n for (const wiring of this._wiring.values()) {\n wiring.dockRegion.destroy();\n }\n\n this._wiring.clear();\n\n super.destructor();\n }\n}\n\nconst DockCallable = callable(Dock);\ntype DockCallable = Dock;\nexport {\n Dock as _Dock,\n DockCallable as Dock,\n};\n"],"mappings":"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"}