@jimka/typescript-ui 0.4.1 → 0.5.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 (194) hide show
  1. package/dist/lib/{AbstractBooleanInput-DE5NNJT0.js → AbstractBooleanInput-CbW7ZrDT.js} +2 -2
  2. package/dist/lib/{AbstractBooleanInput-DE5NNJT0.js.map → AbstractBooleanInput-CbW7ZrDT.js.map} +1 -1
  3. package/dist/lib/{AbstractInput-BxfrmD2W.js → AbstractInput-B10DfLOf.js} +2 -2
  4. package/dist/lib/{AbstractInput-BxfrmD2W.js.map → AbstractInput-B10DfLOf.js.map} +1 -1
  5. package/dist/lib/Anchor-BsblS5it.js +2 -0
  6. package/dist/lib/Anchor-BsblS5it.js.map +1 -0
  7. package/dist/lib/AnchorConstraints-Bfkt8019.js +2 -0
  8. package/dist/lib/AnchorConstraints-Bfkt8019.js.map +1 -0
  9. package/dist/lib/{AnimatedDropdown-QLNasu_Y.js → AnimatedDropdown-kL47HPGP.js} +2 -2
  10. package/dist/lib/{AnimatedDropdown-QLNasu_Y.js.map → AnimatedDropdown-kL47HPGP.js.map} +1 -1
  11. package/dist/lib/Border-CNS6aEXB.js +2 -0
  12. package/dist/lib/{Border-CKKUk5G9.js.map → Border-CNS6aEXB.js.map} +1 -1
  13. package/dist/lib/Button-T16zSZip.js +2 -0
  14. package/dist/lib/Button-T16zSZip.js.map +1 -0
  15. package/dist/lib/{Card-a4dzYrbX.js → Card-CD3jxshU.js} +2 -2
  16. package/dist/lib/{Card-a4dzYrbX.js.map → Card-CD3jxshU.js.map} +1 -1
  17. package/dist/lib/CodeEditor-BGlB232h.js +2 -0
  18. package/dist/lib/CodeEditor-BGlB232h.js.map +1 -0
  19. package/dist/lib/ComboBox-C9FYMuLv.js +2 -0
  20. package/dist/lib/ComboBox-C9FYMuLv.js.map +1 -0
  21. package/dist/lib/Component-w65rPK75.js +2 -0
  22. package/dist/lib/Component-w65rPK75.js.map +1 -0
  23. package/dist/lib/Container-C7RPLQEc.js +2 -0
  24. package/dist/lib/{Container-0bsG2Q8Z.js.map → Container-C7RPLQEc.js.map} +1 -1
  25. package/dist/lib/DOM-Ba8YRVel.js +2 -0
  26. package/dist/lib/{DOM-67StIm5G.js.map → DOM-Ba8YRVel.js.map} +1 -1
  27. package/dist/lib/DragManager-UvPHSoRm.js +2 -0
  28. package/dist/lib/{DragManager-DRor_-1x.js.map → DragManager-UvPHSoRm.js.map} +1 -1
  29. package/dist/lib/{FieldDecorator-Bpx8J8n9.js → FieldDecorator-HG65hEtk.js} +2 -2
  30. package/dist/lib/{FieldDecorator-Bpx8J8n9.js.map → FieldDecorator-HG65hEtk.js.map} +1 -1
  31. package/dist/lib/{Fit-CL1m1nIZ.js → Fit-Cg3dPyZg.js} +2 -2
  32. package/dist/lib/{Fit-CL1m1nIZ.js.map → Fit-Cg3dPyZg.js.map} +1 -1
  33. package/dist/lib/FloatingPanel-D5buuVrQ.js +2 -0
  34. package/dist/lib/FloatingPanel-D5buuVrQ.js.map +1 -0
  35. package/dist/lib/{Glyph-wN7bk7mg.js → Glyph-DeJF_zsR.js} +2 -2
  36. package/dist/lib/{Glyph-wN7bk7mg.js.map → Glyph-DeJF_zsR.js.map} +1 -1
  37. package/dist/lib/{Grid-DHHmaB-Y.js → Grid-BYBohJIA.js} +2 -2
  38. package/dist/lib/{Grid-DHHmaB-Y.js.map → Grid-BYBohJIA.js.map} +1 -1
  39. package/dist/lib/{GridConstraints-DVc1BmlP.js → GridConstraints-Bts284FI.js} +2 -2
  40. package/dist/lib/{GridConstraints-DVc1BmlP.js.map → GridConstraints-Bts284FI.js.map} +1 -1
  41. package/dist/lib/{Header-2hKwQo_u.js → Header-BIFJK9nR.js} +2 -2
  42. package/dist/lib/{Header-2hKwQo_u.js.map → Header-BIFJK9nR.js.map} +1 -1
  43. package/dist/lib/{IconText-DC7Xe34b.js → IconText-Dsb1xS-E.js} +2 -2
  44. package/dist/lib/{IconText-DC7Xe34b.js.map → IconText-Dsb1xS-E.js.map} +1 -1
  45. package/dist/lib/LanguageRegistry-CcVnbKuh.js +2 -0
  46. package/dist/lib/LanguageRegistry-CcVnbKuh.js.map +1 -0
  47. package/dist/lib/LayoutSerialization-ySzY1h2l.js +2 -0
  48. package/dist/lib/{LayoutSerialization-D5bBas4W.js.map → LayoutSerialization-ySzY1h2l.js.map} +1 -1
  49. package/dist/lib/LayoutSizes-DKDK1Cle.js +2 -0
  50. package/dist/lib/LayoutSizes-DKDK1Cle.js.map +1 -0
  51. package/dist/lib/List-BPd-IXZR.js +2 -0
  52. package/dist/lib/{List-CG4NvwuV.js.map → List-BPd-IXZR.js.map} +1 -1
  53. package/dist/lib/MemoryStore-BgP7-Uzn.js +2 -0
  54. package/dist/lib/{MemoryStore-DT6iWWco.js.map → MemoryStore-BgP7-Uzn.js.map} +1 -1
  55. package/dist/lib/Menu-C0roOCY7.js +2 -0
  56. package/dist/lib/Menu-C0roOCY7.js.map +1 -0
  57. package/dist/lib/{MenuButton-wy_0RskC.js → MenuButton-BR4KOFld.js} +2 -2
  58. package/dist/lib/{MenuButton-wy_0RskC.js.map → MenuButton-BR4KOFld.js.map} +1 -1
  59. package/dist/lib/Panel-CpNQTLNF.js +2 -0
  60. package/dist/lib/{Panel-Rd9zkNXx.js.map → Panel-CpNQTLNF.js.map} +1 -1
  61. package/dist/lib/{Position-Ck_KD4cR.js → Position-PBVBW1JS.js} +2 -2
  62. package/dist/lib/{Position-Ck_KD4cR.js.map → Position-PBVBW1JS.js.map} +1 -1
  63. package/dist/lib/{ProgressSpinner-yt-VXyCb.js → ProgressSpinner-lAsK26C-.js} +2 -2
  64. package/dist/lib/{ProgressSpinner-yt-VXyCb.js.map → ProgressSpinner-lAsK26C-.js.map} +1 -1
  65. package/dist/lib/RadioButton-CCcIaL54.js +2 -0
  66. package/dist/lib/RadioButton-CCcIaL54.js.map +1 -0
  67. package/dist/lib/{RovingTabIndex-DFJyoveq.js → RovingTabIndex-C2Vzq0IX.js} +2 -2
  68. package/dist/lib/{RovingTabIndex-DFJyoveq.js.map → RovingTabIndex-C2Vzq0IX.js.map} +1 -1
  69. package/dist/lib/Scrollbar-C3ZZyZ1e.js +2 -0
  70. package/dist/lib/Scrollbar-C3ZZyZ1e.js.map +1 -0
  71. package/dist/lib/Slider-DZ9drUxn.js +2 -0
  72. package/dist/lib/Slider-DZ9drUxn.js.map +1 -0
  73. package/dist/lib/{Spacer-qalSEJ5i.js → Spacer-SDhOMp2g.js} +2 -2
  74. package/dist/lib/{Spacer-qalSEJ5i.js.map → Spacer-SDhOMp2g.js.map} +1 -1
  75. package/dist/lib/TabButton-DNo4ZgV8.js +2 -0
  76. package/dist/lib/TabButton-DNo4ZgV8.js.map +1 -0
  77. package/dist/lib/Text-CdHf3B-1.js +2 -0
  78. package/dist/lib/Text-CdHf3B-1.js.map +1 -0
  79. package/dist/lib/{Tooltip-Dro4DOZs.js → Tooltip-B_UF5bXD.js} +2 -2
  80. package/dist/lib/{Tooltip-Dro4DOZs.js.map → Tooltip-B_UF5bXD.js.map} +1 -1
  81. package/dist/lib/Tree-C7qLbIYH.js +2 -0
  82. package/dist/lib/Tree-C7qLbIYH.js.map +1 -0
  83. package/dist/lib/VBox-32fxpkDl.js +2 -0
  84. package/dist/lib/VBox-32fxpkDl.js.map +1 -0
  85. package/dist/lib/{Validator-Du8PqS5i.js → Validator-Cjs1EU5Q.js} +2 -2
  86. package/dist/lib/{Validator-Du8PqS5i.js.map → Validator-Cjs1EU5Q.js.map} +1 -1
  87. package/dist/lib/{VirtualScroller-Bv4KXGKW.js → VirtualScroller-cvFNMS7U.js} +2 -2
  88. package/dist/lib/{VirtualScroller-Bv4KXGKW.js.map → VirtualScroller-cvFNMS7U.js.map} +1 -1
  89. package/dist/lib/component/button.es.js +1 -1
  90. package/dist/lib/component/chart.es.js +1 -1
  91. package/dist/lib/component/chart.es.js.map +1 -1
  92. package/dist/lib/component/container.es.js +1 -1
  93. package/dist/lib/component/container.es.js.map +1 -1
  94. package/dist/lib/component/diagram.es.js +1 -1
  95. package/dist/lib/component/diagram.es.js.map +1 -1
  96. package/dist/lib/component/display.es.js +1 -1
  97. package/dist/lib/component/display.es.js.map +1 -1
  98. package/dist/lib/component/editor.es.js +3 -3
  99. package/dist/lib/component/editor.es.js.map +1 -1
  100. package/dist/lib/component/input.es.js +1 -1
  101. package/dist/lib/component/input.es.js.map +1 -1
  102. package/dist/lib/component/list.es.js +1 -1
  103. package/dist/lib/component/list.es.js.map +1 -1
  104. package/dist/lib/component/menubar.es.js +1 -1
  105. package/dist/lib/component/menubar.es.js.map +1 -1
  106. package/dist/lib/component/table.es.js +2 -2
  107. package/dist/lib/component/table.es.js.map +1 -1
  108. package/dist/lib/component/tree.es.js +1 -1
  109. package/dist/lib/component/tree.es.js.map +1 -1
  110. package/dist/lib/core.es.js +1 -1
  111. package/dist/lib/core.es.js.map +1 -1
  112. package/dist/lib/data.es.js +1 -1
  113. package/dist/lib/glyphs/solid/index.es.js +1 -1
  114. package/dist/lib/languages-Dl8qdL8a.js +2 -0
  115. package/dist/lib/languages-Dl8qdL8a.js.map +1 -0
  116. package/dist/lib/layout.es.js +1 -1
  117. package/dist/lib/layout.es.js.map +1 -1
  118. package/dist/lib/overlay.es.js +1 -1
  119. package/dist/lib/overlay.es.js.map +1 -1
  120. package/dist/lib/primitive.es.js +1 -1
  121. package/dist/lib/router.es.js +1 -1
  122. package/dist/lib/router.es.js.map +1 -1
  123. package/dist/lib/selectionsEqual-BrTxPMmK.js +2 -0
  124. package/dist/lib/{selectionsEqual-Ci3_uflt.js.map → selectionsEqual-BrTxPMmK.js.map} +1 -1
  125. package/dist/lib/types/component/button/TabButton.d.ts +1 -1
  126. package/dist/lib/types/component/button/ToggleButton.d.ts +1 -1
  127. package/dist/lib/types/component/chart/ChartLegend.d.ts +1 -1
  128. package/dist/lib/types/component/container/FloatingPanel.d.ts +23 -0
  129. package/dist/lib/types/component/container/ScrollStrip.d.ts +1 -1
  130. package/dist/lib/types/component/container/Scrollbar.d.ts +1 -3
  131. package/dist/lib/types/component/container/TabBar.d.ts +1 -1
  132. package/dist/lib/types/component/container/index.d.ts +2 -0
  133. package/dist/lib/types/component/diagram/DiagramGroupNode.d.ts +1 -1
  134. package/dist/lib/types/component/display/Markdown.d.ts +42 -1
  135. package/dist/lib/types/component/display/MarkdownMinimap.d.ts +38 -0
  136. package/dist/lib/types/component/display/MarkdownViewer.d.ts +60 -0
  137. package/dist/lib/types/component/display/index.d.ts +6 -0
  138. package/dist/lib/types/component/editor/CodeEditor.d.ts +13 -0
  139. package/dist/lib/types/component/editor/index.d.ts +1 -1
  140. package/dist/lib/types/component/input/Checkbox.d.ts +1 -1
  141. package/dist/lib/types/component/input/RadioButton.d.ts +1 -1
  142. package/dist/lib/types/component/input/Slider.d.ts +1 -1
  143. package/dist/lib/types/component/input/Text.d.ts +0 -2
  144. package/dist/lib/types/component/input/Toggle.d.ts +1 -1
  145. package/dist/lib/types/component/menubar/MenuBar.d.ts +1 -1
  146. package/dist/lib/types/component/menubar/ToolBar.d.ts +1 -0
  147. package/dist/lib/types/component/table/Body.d.ts +1 -0
  148. package/dist/lib/types/component/table/cell/SortPriorityBadge.d.ts +1 -1
  149. package/dist/lib/types/component/table/cell/editor/CellEditorPool.d.ts +1 -0
  150. package/dist/lib/types/component/table/cell/editor/Date.d.ts +1 -0
  151. package/dist/lib/types/component/table/cell/editor/DateTime.d.ts +1 -0
  152. package/dist/lib/types/component/table/cell/editor/Time.d.ts +1 -0
  153. package/dist/lib/types/component/tree/Tree.d.ts +9 -1
  154. package/dist/lib/types/component/tree/index.d.ts +1 -1
  155. package/dist/lib/types/component/tree/renderer/Label.d.ts +1 -1
  156. package/dist/lib/types/core/Component.d.ts +5 -0
  157. package/dist/lib/types/core/Theme.d.ts +4 -2
  158. package/dist/lib/types/layout/LayoutManager.d.ts +8 -0
  159. package/dist/lib/validation.es.js +1 -1
  160. package/llms.txt +3 -0
  161. package/package.json +15 -15
  162. package/dist/lib/AnchorConstraints-DdDYSDkf.js +0 -2
  163. package/dist/lib/AnchorConstraints-DdDYSDkf.js.map +0 -1
  164. package/dist/lib/Border-CKKUk5G9.js +0 -2
  165. package/dist/lib/Button-MZDYJOMC.js +0 -2
  166. package/dist/lib/Button-MZDYJOMC.js.map +0 -1
  167. package/dist/lib/ComboBox-C04SfXAR.js +0 -2
  168. package/dist/lib/ComboBox-C04SfXAR.js.map +0 -1
  169. package/dist/lib/Component-fRQvgaEY.js +0 -2
  170. package/dist/lib/Component-fRQvgaEY.js.map +0 -1
  171. package/dist/lib/Container-0bsG2Q8Z.js +0 -2
  172. package/dist/lib/DOM-67StIm5G.js +0 -2
  173. package/dist/lib/DragManager-DRor_-1x.js +0 -2
  174. package/dist/lib/LayoutSerialization-D5bBas4W.js +0 -2
  175. package/dist/lib/LayoutSizes-qNaURyiw.js +0 -2
  176. package/dist/lib/LayoutSizes-qNaURyiw.js.map +0 -1
  177. package/dist/lib/List-CG4NvwuV.js +0 -2
  178. package/dist/lib/MemoryStore-DT6iWWco.js +0 -2
  179. package/dist/lib/Menu-CrZy2u0g.js +0 -2
  180. package/dist/lib/Menu-CrZy2u0g.js.map +0 -1
  181. package/dist/lib/Panel-Rd9zkNXx.js +0 -2
  182. package/dist/lib/RadioButton-D23rId-V.js +0 -2
  183. package/dist/lib/RadioButton-D23rId-V.js.map +0 -1
  184. package/dist/lib/Scrollbar-SOj9psiP.js +0 -2
  185. package/dist/lib/Scrollbar-SOj9psiP.js.map +0 -1
  186. package/dist/lib/Slider-DBnAZ-F7.js +0 -2
  187. package/dist/lib/Slider-DBnAZ-F7.js.map +0 -1
  188. package/dist/lib/TabButton-DKECTTk1.js +0 -2
  189. package/dist/lib/TabButton-DKECTTk1.js.map +0 -1
  190. package/dist/lib/Text-KB5LksJl.js +0 -2
  191. package/dist/lib/Text-KB5LksJl.js.map +0 -1
  192. package/dist/lib/VBox-P3WFGkPc.js +0 -2
  193. package/dist/lib/VBox-P3WFGkPc.js.map +0 -1
  194. package/dist/lib/selectionsEqual-Ci3_uflt.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip-Dro4DOZs.js","names":[],"sources":["../../src/typescript/lib/core/LayerManager.ts","../../src/typescript/lib/core/OverlayPosition.ts","../../src/typescript/lib/overlay/Tooltip.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\n/**\n * How a layer responds to an outside interaction.\n *\n * - `\"click-outside\"`: dismissed when a `pointerdown` lands outside the\n * layer's subtree (and its anchor); ignores focus moves.\n * - `\"blur\"`: dismissed when either a `pointerdown` or a focus move lands\n * outside the layer's subtree (and its anchor).\n * - `\"manual\"`: never dismissed by the document-level handlers; the host\n * drives its own teardown.\n * - `\"modal\"`: never dismissed by an outside `pointerdown` or focus move\n * (the layer captures the interaction); only Escape, routed through the\n * keydown handler, asks it to close.\n *\n * @category Core\n */\nexport type LayerDismissMode = \"click-outside\" | \"blur\" | \"manual\" | \"modal\";\n\n/**\n * A portaled overlay surface that participates in the runtime layer tree.\n * Implemented by [`AnimatedDropdown`](/api/core/classes/AnimatedDropdown),\n * [`Popover`](/api/overlay/classes/Popover),\n * [`Dialog`](/api/overlay/classes/Dialog), and\n * [`Window`](/api/overlay/classes/Window).\n *\n * A surface registers itself with {@link LayerManager.register} from its\n * show path and unregisters with {@link LayerManager.unregister} from its\n * hide path. The manager then owns the z-index stamp, the activation\n * marking, and the dismissal decision — calling {@link DismissableLayer.requestClose}\n * (advisory) when an outside interaction meets the layer's\n * {@link LayerDismissMode}.\n *\n * @category Core\n */\nexport interface DismissableLayer {\n /** The layer's root element (already mounted on `documentElement`). */\n getLayerElement(): Handle | null;\n\n /** Dismiss policy consulted by the document-level interaction handlers. */\n getDismissMode(): LayerDismissMode;\n\n /** Advisory request to close; the surface runs its own teardown + unregister. */\n requestClose(): void;\n\n /**\n * Optional anchor element excluded from \"outside\" tests so the trigger\n * click that opened the layer does not immediately re-close it.\n */\n getAnchorElement?(): Handle | null;\n\n /**\n * Optional activation hook. The manager calls it with `true` when this\n * layer becomes the active (topmost containing) layer for a pointer /\n * focus interaction and `false` when it is superseded. Surfaces that\n * carry an active-state affordance (e.g. a window title bar) map it to\n * their own toggle; surfaces without one omit the method.\n */\n onActivate?(active: boolean): void;\n\n /**\n * Optional re-stamp hook. The manager calls it with a fresh z-index when\n * {@link LayerManager.bringToFront} re-allocates the layer (or one of its\n * ancestors) while it is already shown, so the surface can mirror the new\n * value onto its element through its own typed `setZIndex`. Surfaces that\n * only read z once in their show path (and never get raised afterwards)\n * may omit it.\n */\n onZIndexChanged?(zIndex: number): void;\n\n /**\n * Optional z-index band hint. Returns one of {@link LayerManager.Band}'s\n * values so an unrelated peer layer stacks in its surface family's band\n * (Window < Popover < dropdown < Dialog). Omitted surfaces default to the\n * dropdown band. A nested layer ignores its own band and inherits its\n * opener's, so this only matters for top-level (unparented) registrations.\n */\n getBand?(): number;\n\n /**\n * Optional top-level hint. Returns `true` for a surface that is an\n * independent peer rather than a layer opened from another — a window is\n * not a child of whatever happened to be topmost when it appeared. Such a\n * layer registers as a tree root, so raising it does not drag an unrelated\n * peer up with it (see {@link LayerManager.bringToFront}). Omitted surfaces\n * register under the current topmost layer (the opened-from edge).\n */\n isLayerRoot?(): boolean;\n}\n\n/**\n * Node in the runtime layer tree. Generalizes the per-host `LayerEntry`\n * that `AnimatedDropdown` previously kept module-private: `children` are the\n * layers opened while this one was topmost, `band` is the z-index band base\n * the layer was assigned (inherited from its opener), and `zIndex` is the\n * band + counter stamp the manager assigned at register time.\n */\ninterface LayerNode {\n layer: DismissableLayer;\n parent: LayerNode | null;\n children: LayerNode[];\n band: number;\n zIndex: number;\n}\n\n// Z-index bands. Plain module constants because z-index is not themed\n// anywhere today (Theme.ts carries no z-index tokens; the values were inline\n// per class). The bands reconcile the four historical bases into one\n// ascending allocator and preserve relative order between unrelated peers:\n// Window 9000 < Popover 9800 < dropdowns 10000 < Dialog 11000.\n// A nested child inherits its opener's band but always lands above it because\n// it registers later and so draws a higher counter. The 200-1000 gap between\n// bands leaves headroom for the monotonic `_zCounter`; it is not reset, on the\n// assumption a single session opens far fewer than 200 unrelated layers in the\n// same band before a reload — the historical inline values made the same bet.\nconst Z_BAND_WINDOW: number = 9000;\nconst Z_BAND_POPOVER: number = 9800;\nconst Z_BAND_DROPDOWN: number = 10000;\nconst Z_BAND_DIALOG: number = 11000;\n// Above every managed layer: a tooltip is a transient, non-interactive\n// affordance that must float over even a modal Dialog and its backdrop. Not a\n// registered layer band — exposed so the Tooltip singleton stamps itself here\n// rather than carrying a magic number that could fall below the Dialog band.\nconst Z_BAND_TOOLTIP: number = 12000;\n\n/**\n * Sentinel `Component` used as the registration key for the manager's three\n * document-level listeners. `Event.addViewportListener` binds a listener to a\n * `Component`, but the manager's handlers are module-global (one per type, not\n * per layer), so a single stable sentinel owns them — mirroring `Window`'s\n * `_viewportListenerOwner`. The install/teardown lifecycle keys off the tree\n * being non-empty rather than any individual layer.\n */\nconst _listenerOwner: Component = new Component();\n\n/**\n * Central registry and document-level interaction broker for portaled overlay\n * surfaces. Owns one runtime layer tree (push-on-register /\n * link-under-current-topmost / pop-on-unregister), one z-index allocator with\n * reserved bands, and a single set of document-level `pointerdown` / `focusin`\n * / `keydown` listeners installed on the first register and removed on the\n * last unregister.\n *\n * The relationship the tree captures — the runtime \"opened-from\" edge (which\n * layer was topmost when this one opened) — is distinct from the static\n * component hierarchy and changes per activation, so it lives here rather than\n * in the component tree. Containment queries hop across portaled descendant\n * layers via {@link LayerManager.containsAcrossLayers}, which is the single\n * place cross-portal containment is reasoned about.\n *\n * @category Core\n */\nexport namespace LayerManager {\n\n // LIFO stack of open layers in register order; the last entry is topmost.\n const _stack: LayerNode[] = [];\n\n // Membership + node lookup keyed by the layer instance, generalizing the\n // per-host `_entryByLayer` WeakMap.\n const _nodeByLayer: WeakMap<DismissableLayer, LayerNode> = new WeakMap();\n\n // Ascending per-register counter; combined with a band base to stamp z.\n let _zCounter: number = 0;\n\n // The layer currently marked active (received the last `onActivate(true)`).\n let _activeLayer: DismissableLayer | null = null;\n\n let _listenersInstalled: boolean = false;\n\n /**\n * The four z-index bands a surface returns from\n * {@link DismissableLayer.getBand}. Exposed so each surface can tag itself\n * without re-declaring the constants. Reconciles the historical inline\n * bases (Window 9000, Popover 9998, dropdowns 10050, Dialog 10101) into\n * one ascending allocator.\n */\n export const Band = {\n Window: Z_BAND_WINDOW,\n Popover: Z_BAND_POPOVER,\n Dropdown: Z_BAND_DROPDOWN,\n Dialog: Z_BAND_DIALOG,\n Tooltip: Z_BAND_TOOLTIP,\n } as const;\n\n /**\n * Picks the z-index band for a layer. A nested layer inherits its\n * opener's band so it stays in the same stacking neighbourhood (and rises\n * above the opener because it registers later); an unrelated peer uses its\n * own surface-type band from {@link DismissableLayer.getBand}.\n */\n function bandFor(parent: LayerNode | null, ownBand: number): number {\n return parent ? parent.band : ownBand;\n }\n\n /**\n * Pushes `layer` as a child of the current topmost layer — unless it\n * declares itself a top-level peer via\n * {@link DismissableLayer.isLayerRoot}, in which case it registers as a\n * tree root — assigns its band-based z-index, and installs the\n * document-level listeners on the first call. A duplicate register (e.g. a\n * `showAnimated` that cancels an in-flight fade-out) is a no-op so the tree\n * never double-pushes.\n *\n * @param layer - The surface entering the layer tree.\n */\n export function register(layer: DismissableLayer): void {\n if (_nodeByLayer.has(layer)) {\n return;\n }\n\n const topmost = _stack.length > 0 ? _stack[_stack.length - 1] : null;\n const parent = layer.isLayerRoot?.() ? null : topmost;\n const band = bandFor(parent, layer.getBand?.() ?? Z_BAND_DROPDOWN);\n const zIndex = band + (++_zCounter);\n\n const node: LayerNode = { layer, parent, children: [], band, zIndex };\n\n _nodeByLayer.set(layer, node);\n _stack.push(node);\n\n if (parent) {\n parent.children.push(node);\n }\n\n if (!_listenersInstalled) {\n installListeners();\n }\n }\n\n /**\n * Appends a portaled overlay element to `document.documentElement` if it is\n * not already contained there. The single home for the \"portal mount\"\n * idiom every floating surface used to inline. Idempotent — a surface that\n * re-shows after detach re-appends, and one already mounted is left alone.\n * Does **not** register the layer; surfaces still call {@link register}\n * explicitly.\n *\n * @param el - The overlay element to portal onto the document root.\n */\n export function mount(el: Handle): void {\n if (!DOM.source.contains(DOM.source.getDocumentElement(), el)) {\n DOM.sink.appendChild(DOM.source.getDocumentElement(), el);\n }\n }\n\n /**\n * Pops `layer` from the tree (and unlinks it from its parent's child\n * list), clears it as active if it was, and removes the document-level\n * listeners on the last call.\n *\n * @param layer - The surface leaving the layer tree.\n */\n export function unregister(layer: DismissableLayer): void {\n const node = _nodeByLayer.get(layer);\n\n if (!node) {\n return;\n }\n\n _nodeByLayer.delete(layer);\n\n const idx = _stack.indexOf(node);\n\n if (idx >= 0) {\n _stack.splice(idx, 1);\n }\n\n if (node.parent) {\n const ci = node.parent.children.indexOf(node);\n\n if (ci >= 0) {\n node.parent.children.splice(ci, 1);\n }\n }\n\n if (_activeLayer === layer) {\n _activeLayer = null;\n }\n\n if (_stack.length === 0 && _listenersInstalled) {\n uninstallListeners();\n }\n }\n\n /**\n * Returns true when `node` is inside `layer`'s own element OR inside any\n * descendant layer's element. This is the only cross-portal containment\n * hop in the framework: a `pointerdown` or focus move that lands in a\n * child layer (e.g. a dropdown opened from inside a popover) counts as\n * inside the ancestor so the ancestor stays open.\n *\n * @param layer - The layer whose subtree is tested.\n * @param node - The DOM node receiving the interaction.\n * @returns True when `node` is inside `layer` or any descendant layer.\n */\n export function containsAcrossLayers(layer: DismissableLayer, node: Handle | null): boolean {\n if (!node) {\n return false;\n }\n\n const layerNode = _nodeByLayer.get(layer);\n\n if (!layerNode) {\n const el = layer.getLayerElement();\n\n return el ? DOM.source.contains(el, node) : false;\n }\n\n return nodeContains(layerNode, node);\n }\n\n /**\n * Returns the topmost currently-registered layer, or null when none is\n * open.\n *\n * @returns The topmost open layer, or null.\n */\n export function getTopLayer(): DismissableLayer | null {\n return _stack.length > 0 ? _stack[_stack.length - 1].layer : null;\n }\n\n /**\n * Re-stamps `layer` (and its descendant layers) with fresh top-of-band\n * z-indices and marks it active. Used by surfaces that raise on click\n * (e.g. a window brought to front), so the raised layer — and anything it\n * opened — jumps above its band peers without disturbing other bands.\n *\n * @param layer - The layer to raise and activate.\n */\n export function bringToFront(layer: DismissableLayer): void {\n const node = _nodeByLayer.get(layer);\n\n if (!node) {\n return;\n }\n\n restampSubtree(node);\n markActive(layer);\n }\n\n /**\n * Returns the z-index currently assigned to `layer`, for surfaces that\n * mirror it elsewhere. Falls back to the dropdown band when the layer is\n * not registered.\n *\n * @param layer - The layer whose stamp to read.\n * @returns The assigned z-index.\n */\n export function getZIndex(layer: DismissableLayer): number {\n const node = _nodeByLayer.get(layer);\n\n return node ? node.zIndex : Z_BAND_DROPDOWN;\n }\n\n /**\n * Re-stamps `node`'s subtree from a fresh top-of-band counter run so the\n * raised layer and its descendants ascend together, preserving their\n * relative order. Each re-stamped layer is notified via\n * {@link DismissableLayer.onZIndexChanged} so it can mirror the new value\n * through its own typed `setZIndex` (the manager never writes the DOM).\n */\n function restampSubtree(node: LayerNode): void {\n const walk = (n: LayerNode): void => {\n n.zIndex = n.band + (++_zCounter);\n n.layer.onZIndexChanged?.(n.zIndex);\n\n for (const child of n.children) {\n walk(child);\n }\n };\n\n walk(node);\n }\n\n /** True when `node` (or a descendant layer) contains `target`. */\n function nodeContains(node: LayerNode, target: Handle): boolean {\n const el = node.layer.getLayerElement();\n\n if (el && DOM.source.contains(el, target)) {\n return true;\n }\n\n for (const child of node.children) {\n if (nodeContains(child, target)) {\n return true;\n }\n }\n\n return false;\n }\n\n /** Marks `layer` active and deactivates the previously-active layer. */\n function markActive(layer: DismissableLayer): void {\n if (_activeLayer === layer) {\n return;\n }\n\n deactivateActive();\n\n _activeLayer = layer;\n\n if (layer.onActivate) {\n layer.onActivate(true);\n }\n }\n\n /** Deactivates the currently-active layer, if any. */\n function deactivateActive(): void {\n if (_activeLayer && _activeLayer.onActivate) {\n _activeLayer.onActivate(false);\n }\n\n _activeLayer = null;\n }\n\n /**\n * Walks up the \"opened-from\" chain from `node` and returns the nearest\n * ancestor-or-self layer that carries an activation affordance\n * ({@link DismissableLayer.onActivate}), or `null` if none does. A layer\n * opened inside a window resolves to that window, so the window stays\n * active while its descendant holds focus — interacting with a child layer\n * must not darken the title bar of the window that owns it.\n */\n function activatableAncestor(node: LayerNode): DismissableLayer | null {\n let cur: LayerNode | null = node;\n\n while (cur) {\n if (cur.layer.onActivate) {\n return cur.layer;\n }\n\n cur = cur.parent;\n }\n\n return null;\n }\n\n /**\n * Walks the stack top-down for a pointer / focus interaction at `target`,\n * dismissing each topmost layer whose dismiss mode and containment test\n * say the interaction landed outside it, and marking the topmost\n * containing layer active. `focusOnly` is true for the `focusin` pass, so\n * only `\"blur\"` layers act on it (a `\"click-outside\"` layer ignores focus\n * moves).\n */\n function handleOutside(target: Handle | null, focusOnly: boolean): void {\n // Snapshot top-down so requestClose-driven unregisters don't disturb\n // the walk; re-read containment against the live tree each step.\n const snapshot = _stack.slice().reverse();\n let activated = false;\n let landedInside = false;\n\n for (const node of snapshot) {\n const mode = node.layer.getDismissMode();\n\n if (containsAcrossLayers(node.layer, target)) {\n // Activation belongs to the nearest activatable ancestor (the\n // window a child layer was opened from), not the innermost\n // containing layer; fall back to the layer itself when nothing\n // up the chain carries an activation affordance, preserving the\n // prior behaviour for standalone layers.\n markActive(activatableAncestor(node) ?? node.layer);\n activated = true;\n landedInside = true;\n\n break;\n }\n\n const anchor = node.layer.getAnchorElement?.();\n\n if (anchor && target && DOM.source.contains(anchor, target)) {\n landedInside = true;\n\n break;\n }\n\n const dismissable = mode === \"click-outside\" || mode === \"blur\";\n const acts = focusOnly ? mode === \"blur\" : dismissable;\n\n if (mode === \"manual\" || mode === \"modal\" || !acts) {\n // Modal captures the interaction: an outside pointer/focus on a\n // modal must not fall through to dismiss layers beneath it.\n if (mode === \"modal\") {\n landedInside = true;\n\n break;\n }\n\n continue;\n }\n\n node.layer.requestClose();\n }\n\n // An interaction that landed outside every layer's subtree (and any\n // anchor / modal capture) deactivates the active layer — e.g. an\n // empty-viewport click that should drop a window's active title-bar\n // highlight. A focus move to a non-layer element (focusOnly) does not\n // deactivate, so tabbing through page chrome doesn't flicker the\n // active window.\n if (!activated && !landedInside && !focusOnly) {\n deactivateActive();\n }\n }\n\n /** Document `pointerdown` handler — see {@link handleOutside}. */\n function onPointerDown(e: PointerEvent): void {\n handleOutside(e.target === null ? null : DOM.source.intern(e.target), false);\n }\n\n /**\n * Window `blur` handler. When the whole browser window loses focus (the user\n * clicks another application or alt-tabs away) no in-page `pointerdown` or\n * `focusin` fires, so an open layer would otherwise stay up. Treat it as an\n * interaction landing outside every layer, dismissing each `\"click-outside\"`\n * / `\"blur\"` layer while a `\"modal\"` shields those beneath it and `\"manual\"`\n * layers (windows) are left open.\n *\n * @remarks Viewport listeners are capture-phase, so element blurs from within\n * the page surface here too; only a genuine window blur (`target` is the\n * window itself) should dismiss layers.\n */\n function onWindowBlur(e: FocusEvent): void {\n if (!DOM.source.isWindow(e.target === null ? null : DOM.source.intern(e.target))) {\n return;\n }\n\n handleOutside(null, false);\n }\n\n /** Document `focusin` handler — only `\"blur\"` layers act on it. */\n function onFocusIn(e: FocusEvent): void {\n handleOutside(e.target === null ? null : DOM.source.intern(e.target), true);\n }\n\n /**\n * Document `keydown` handler — Escape asks the topmost non-manual layer to close.\n *\n * @param e - The keydown event.\n * @returns `true` when Escape actually closed a layer; nothing otherwise, so an\n * Escape with no dismissible layer open keeps propagating.\n */\n function onKeyDown(e: KeyboardEvent): Event.ListenerResult {\n if (e.key !== \"Escape\") {\n return;\n }\n\n for (let i = _stack.length - 1; i >= 0; i--) {\n const mode = _stack[i].layer.getDismissMode();\n\n if (mode === \"manual\") {\n continue;\n }\n\n _stack[i].layer.requestClose();\n\n return true;\n }\n\n return;\n }\n\n /** Installs the three document-level listeners against the sentinel owner. */\n function installListeners(): void {\n Event.addViewportListener(_listenerOwner, \"pointerdown\", onPointerDown);\n Event.addViewportListener(_listenerOwner, \"focusin\", onFocusIn);\n Event.addViewportListener(_listenerOwner, \"keydown\", onKeyDown);\n Event.addViewportListener(_listenerOwner, \"blur\", onWindowBlur);\n\n _listenersInstalled = true;\n }\n\n /** Removes the three document-level listeners. */\n function uninstallListeners(): void {\n Event.removeViewportListener(_listenerOwner, \"pointerdown\", onPointerDown);\n Event.removeViewportListener(_listenerOwner, \"focusin\", onFocusIn);\n Event.removeViewportListener(_listenerOwner, \"keydown\", onKeyDown);\n Event.removeViewportListener(_listenerOwner, \"blur\", onWindowBlur);\n\n _listenersInstalled = false;\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Util } from \"~/core/Util.js\";\nimport type { Rect } from \"~/core/DOM.js\";\nimport type { Size } from \"~/primitive/Size.js\";\n\n/**\n * The primary growth axis for an anchored overlay.\n *\n * @category Core\n */\nexport type AnchorAxis = \"vertical\" | \"horizontal\";\n\n/**\n * Options controlling how {@link positionAnchored} places an element against an\n * anchor rect.\n *\n * @category Core\n */\nexport interface AnchorOptions {\n /** Primary growth axis: `\"vertical\"` grows below/above, `\"horizontal\"` right/left. */\n axis: AnchorAxis;\n /** Gap in px between the anchor edge and the element on the primary axis. Default 0. */\n gap?: number;\n /** Viewport-edge margin in px kept on the cross axis. Default 0. */\n margin?: number;\n}\n\n/**\n * Chooses a top-left coordinate on the primary axis. Prefers growing past the\n * anchor's far edge (`farEdge + gap`); flips to the near edge\n * (`nearEdge - extent - gap`) only when the far side lacks room AND the near\n * side has it. When neither side fits, saturates on-screen toward the side with\n * more room — pinned to the viewport's far end when the far side is roomier,\n * otherwise to `0` — so the element never overflows off-screen.\n *\n * @param nearEdge - The anchor's near edge (top / left) on this axis.\n * @param farEdge - The anchor's far edge (bottom / right) on this axis.\n * @param extent - The element's size on this axis.\n * @param viewportExtent - The viewport's size on this axis.\n * @param gap - Gap in px kept between the anchor edge and the element.\n * @returns The chosen top-left coordinate on this axis.\n *\n * @category Core\n */\nexport function positionAdjacent(nearEdge: number, farEdge: number, extent: number, viewportExtent: number, gap: number): number {\n const farStart = farEdge + gap;\n const spaceFar = viewportExtent - farStart;\n const spaceNear = nearEdge - gap;\n\n if (extent <= spaceFar) {\n return farStart;\n }\n\n if (extent <= spaceNear) {\n return nearEdge - extent - gap;\n }\n\n // Neither side fits: pin to whichever side has more room, saturated so the\n // element stays on-screen (mirrors placeAnchored's spaceBelow/spaceAbove\n // fallback, axis-agnostic).\n if (spaceFar >= spaceNear) {\n return Math.max(0, viewportExtent - extent);\n }\n\n return 0;\n}\n\n/**\n * Chooses a top-left coordinate on the cross axis for a **fixed-size** element\n * that ALIGNS WITH (and overlaps) its anchor rather than sitting beside it.\n * Prefers the element's near edge flush with the anchor's near edge\n * (`nearEdge`); flips to the element's FAR edge flush with the anchor's far\n * edge (`farEdge - extent`) only when the near alignment overflows the\n * viewport. This is alignment, not adjacency — the element overlaps the\n * anchor either way; only which pair of edges is made flush changes. It is\n * fixed-size and returns no `available` (contrast {@link\n * positionFlexibleAnchored}); `margin` binds on **this** axis. When neither\n * alignment fits on-screen (an anchor whose own far edge is off-viewport, or\n * an element wider than the viewport), falls back to {@link clampAxis} — the\n * right answer when no placement fits, not a flip substitute.\n *\n * @param nearEdge - The anchor's near edge (left / top) on this axis.\n * @param farEdge - The anchor's far edge (right / bottom) on this axis.\n * @param extent - The element's fixed size on this axis.\n * @param viewportExtent - The viewport's size on this axis.\n * @param margin - Viewport-edge margin in px kept on this axis.\n * @returns The chosen top-left coordinate on this axis.\n *\n * @category Core\n */\nexport function positionAligned(\n nearEdge: number, farEdge: number, extent: number, viewportExtent: number, margin: number,\n): number {\n // Preferred: the element's near edge aligns with the anchor's near edge.\n if (nearEdge >= margin && nearEdge + extent <= viewportExtent - margin) {\n return nearEdge;\n }\n\n // Flipped: the element's FAR edge aligns with the anchor's far edge. Note this\n // is alignment, not adjacency — the element overlaps the anchor either way; only\n // which pair of edges is made flush changes.\n const farStart = farEdge - extent;\n\n if (farStart >= margin && farStart + extent <= viewportExtent - margin) {\n return farStart;\n }\n\n // Neither alignment fits on-screen (an anchor whose own far edge is off-viewport,\n // or an element wider than the viewport): fall back to the clamp. Clamping is the\n // right answer when NO placement fits — it is only wrong as a substitute for a flip.\n return clampAxis(nearEdge, extent, viewportExtent, margin);\n}\n\n/**\n * Clamps a top-left coordinate into `[margin, extent - size - margin]`, pinning\n * to `margin` (top-aligned) when the element is larger than the available span\n * so the coordinate never goes below `margin` and off-screen. The upper bound is\n * floored to `margin` before clamping, because `Util.clamp` resolves a `min >\n * max` range to the max — the opposite of the `Math.max(margin, Math.min(...))`\n * this replaces; flooring keeps `min <= max`, so an over-large element pins to\n * `margin` and the caller's height-cap / scroll carries the overflow.\n *\n * @param value - The proposed coordinate.\n * @param size - The element's extent on this axis.\n * @param extent - The viewport's extent on this axis.\n * @param margin - Viewport-edge margin kept on this axis.\n * @returns The clamped coordinate.\n */\nfunction clampAxis(value: number, size: number, extent: number, margin: number): number {\n return Util.clamp(value, margin, Math.max(margin, extent - size - margin));\n}\n\n/**\n * The chosen coordinate for a size-flexible anchored element, plus the room\n * available on the side it landed on.\n *\n * @category Core\n */\nexport interface FlexiblePlacement {\n /** Top-left coordinate on the primary axis. */\n start: number;\n /** Room (px) on the side actually chosen — the caller's height/width cap. */\n available: number;\n}\n\n/**\n * Chooses a top-left coordinate on the primary axis for a **size-flexible**\n * element — one whose extent may be capped and the overflow scrolled, rather\n * than being placed at a fixed size. Grows from `farEdge`; flips to end at\n * `nearEdge` only when the content overflows the far room **and** the near\n * side is roomier. Unlike {@link positionAdjacent}, the far-fits check is against the\n * *room*, not the raw `extent` — so a flip that still doesn't fully fit on\n * the near side still flips (and clamps) rather than falling back to filling\n * the viewport over the anchor. The returned `available` is the room on the\n * chosen side, so a caller that caps the extent measures the correct side —\n * re-deriving it from `start` would measure the wrong side for a flipped\n * element. `viewportMargin` binds on **this** (primary) axis, unlike\n * {@link positionAnchored}'s cross-axis-only `margin`. Always flush against\n * the anchor edge — no `gap` parameter, because every current caller sits\n * flush.\n *\n * @param nearEdge - The anchor's near edge (top / left) on this axis — the\n * edge a flipped element's far edge meets.\n * @param farEdge - The anchor's far edge (bottom / right) on this axis — the\n * edge an unflipped element grows from.\n * @param extent - The element's unclamped preferred size on this axis.\n * @param viewportExtent - The viewport's size on this axis.\n * @param viewportMargin - Viewport-edge margin in px kept on this axis.\n * @returns The chosen coordinate and the room available at it.\n *\n * @category Core\n */\nexport function positionFlexibleAnchored(\n nearEdge: number,\n farEdge: number,\n extent: number,\n viewportExtent: number,\n viewportMargin: number,\n): FlexiblePlacement {\n const roomFar = viewportExtent - farEdge - viewportMargin;\n const roomNear = nearEdge - viewportMargin;\n\n if (extent <= roomFar || roomFar >= roomNear) {\n return { start: farEdge, available: roomFar };\n }\n\n return { start: nearEdge - Math.min(extent, roomNear), available: roomNear };\n}\n\n/**\n * Places an element of `size` against `anchorRect` inside `viewport`. On the\n * primary axis it grows past the anchor's far edge (below / right), flipping to\n * the near edge (above / left) only when the far side lacks room AND the near\n * side has more; on the cross axis it aligns to the anchor's near edge,\n * flipping to align with the anchor's far edge when the near alignment\n * overflows, and clamping only when neither alignment fits. Pure — all\n * viewport reads are supplied by the caller, so it is directly unit-testable\n * with no DOM.\n *\n * @param anchorRect - The anchor element's bounding rect.\n * @param size - The element's width/height to place.\n * @param viewport - The viewport size to clamp/flip within.\n * @param opts - Axis, gap, and cross-axis margin.\n * @returns The resolved top-left `{ x, y }` for the element.\n *\n * @category Core\n */\nexport function positionAnchored(anchorRect: Rect, size: Size, viewport: Size, opts: AnchorOptions): { x: number; y: number } {\n const gap = opts.gap ?? 0;\n const margin = opts.margin ?? 0;\n\n if (opts.axis === \"vertical\") {\n const y = positionAdjacent(anchorRect.top, anchorRect.bottom, size.height, viewport.height, gap);\n const x = positionAligned(anchorRect.left, anchorRect.right, size.width, viewport.width, margin);\n\n return { x, y };\n }\n\n const x = positionAdjacent(anchorRect.left, anchorRect.right, size.width, viewport.width, gap);\n const y = positionAligned(anchorRect.top, anchorRect.bottom, size.height, viewport.height, margin);\n\n return { x, y };\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { DOM, type Handle } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager } from \"~/core/LayerManager.js\";\nimport { positionAdjacent } from \"~/core/OverlayPosition.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Text } from \"~/component/input/Text.js\";\n\nconst TOOLTIP_ANIM_DURATION_MS: number = 100;\n\n/**\n * Optional color overrides for a tooltip attachment.\n *\n * When provided to {@link Tooltip.attach}, these replace the default theme variables\n * for the duration that the tooltip is shown for that component.\n *\n * @category Core\n */\nexport interface TooltipColors {\n /** CSS color for the tooltip background. */\n background?: string;\n /** CSS color for the tooltip text. */\n color?: string;\n /** CSS color for the tooltip border. */\n border?: string;\n}\n\n/** Internal record of a component's tooltip attachment. */\ninterface TooltipAttachment {\n text : string;\n colors : TooltipColors | undefined;\n mouseoverFn : (e: MouseEvent) => void;\n mousemoveFn : (e: MouseEvent) => void;\n mouseoutFn : () => void;\n mousedownFn : () => void;\n}\n\n/** Internal record of a raw-element tooltip attachment. */\ninterface ElementTooltipAttachment {\n text : string;\n mouseoverFn : (e: MouseEvent) => void;\n mousemoveFn : (e: MouseEvent) => void;\n mouseoutFn : () => void;\n mousedownFn : () => void;\n showTimer : ReturnType<typeof setTimeout> | null;\n lastX : number;\n lastY : number;\n}\n\n/**\n * A singleton floating tooltip that appears near the cursor after a short delay.\n *\n * Use `Tooltip.attach(component, text)` to wire hover listeners onto any component.\n * For manual control use `Tooltip.show(text, x, y)` and `Tooltip.hide()` directly.\n *\n * @example\n * ```typescript\n * Tooltip.attach(myButton, 'Save the document');\n * ```\n *\n * @category Core\n */\n// Intentional exception to the `callable()`-wrap rule: `Tooltip` has a private\n// constructor and is used only through its static methods (`show` / `attach` /\n// …), so there is no public `new Tooltip()` / `Tooltip(...)` surface to wrap.\n// The singleton is exported directly (see `overlay/index.ts`).\nexport class Tooltip extends Component {\n\n // In-flight fades, cancelled on teardown so their fallback timers cannot\n // fire against this tooltip's released element handle. Instance fields\n // written through the singleton `inst` in the static show / hide below.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _hideAnimation: Animation.CancelHandle | null = null;\n\n private static instance: Tooltip | null = null;\n private static showTimer: ReturnType<typeof setTimeout> | null = null;\n private static attachments: Map<string, TooltipAttachment> = new Map();\n private static elementAttachments: Map<Handle, ElementTooltipAttachment> = new Map();\n private static activeElement: Handle | null = null;\n\n // True while the anchor-watch viewport listener is installed (only while a\n // tooltip is shown). See _onAnchorWatch.\n private static watching: boolean = false;\n\n /**\n * Hides the tooltip when its anchor element has left the DOM. The browser\n * fires no `mouseout` when an element is removed under a stationary pointer,\n * so a tooltip whose anchor vanishes would otherwise linger; this runs on\n * pointer movement while a tooltip is shown and dismisses it once the anchor\n * is no longer connected.\n */\n private static readonly _onAnchorWatch = (): void => {\n if (Tooltip.activeElement !== null && !DOM.source.isConnected(Tooltip.activeElement)) {\n Tooltip.hide();\n }\n };\n\n // Set true while a fade-out is in flight; reset to false when the fade-out\n // completes (so the deferred removeElement fires) or when a fresh `show()`\n // re-displays the tooltip mid-fade (the deferred removeElement is then\n // skipped because the tooltip is back on screen).\n private static dismissing: boolean = false;\n\n private static readonly H_PADDING: number = 16;\n private static readonly V_PADDING: number = 8;\n private static readonly MAX_WIDTH: number = 300;\n private static readonly ITEM_HEIGHT: number = 20;\n\n // Clearance kept free around the pointer *hotspot* (the arrow's tip, the\n // hand's fingertip) — the coordinate `show` is handed. The cursor glyph\n // hangs down and to the right of its hotspot, so this box is deliberately\n // NOT centred on it: nothing sits above the tip, and only a few px reach\n // left of it. A box symmetric about the hotspot would push a flipped\n // tooltip visibly further from the cursor than an unflipped one, since the\n // same gap spans glyph on one side and empty space on the other.\n //\n // These are clearance, not glyph metrics: DOWN is shorter than the ~24px\n // glyph on purpose, so an unflipped tooltip keeps sitting alongside the\n // pointer's lower half (where it tapers and hides nothing) rather than\n // being pushed clear of it. No API reports the live cursor's size or\n // hotspot, so the numbers approximate the platform defaults.\n private static readonly CURSOR_LEFT: number = 4;\n private static readonly CURSOR_RIGHT: number = 12;\n private static readonly CURSOR_UP: number = 0;\n private static readonly CURSOR_DOWN: number = 12;\n private static readonly CURSOR_GAP: number = 2;\n\n private _text: Text;\n\n // Number of `\\n`-separated lines in the text currently shown. Cached by\n // `show()` (which knows the raw string) and read by `doLayout()` (which\n // only sees the committed size) so the label's height tracks the line\n // count. 1 for a single-line tooltip — the unchanged legacy case.\n private _lineCount: number = 1;\n\n // Resolved per-line pixel height of the tooltip text, set by `show()` from\n // the live additive line box and read by `doLayout()`. Replaces the\n // fixed `ITEM_HEIGHT` multiplier, which over-allocated ~3px/line and left\n // a trailing empty line on multi-line tooltips.\n private _perLine: number = Tooltip.ITEM_HEIGHT;\n\n /** Private — use the static methods; only one instance is ever created. */\n private constructor() {\n super();\n\n this.setVisible(false);\n // Above every managed layer (incl. a modal Dialog and its backdrop) so a\n // hover tooltip is never occluded.\n this.setZIndex(LayerManager.Band.Tooltip);\n this.setBackgroundColor(\"var(--ts-ui-tooltip-bg, rgb(255, 255, 240))\");\n this.setForegroundColor(\"var(--ts-ui-tooltip-color, rgb(0, 0, 0))\");\n this.setBorder({ border: \"1px solid var(--ts-ui-tooltip-border, rgb(180, 180, 100))\" });\n this.setShadow(\"var(--ts-ui-tooltip-shadow, 1px 2px 4px rgba(0, 0, 0, 0.2))\");\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n this.setPointerEvents(\"none\");\n // Top-level overlay, dynamic size — layout+paint containment scopes reflow without\n // committing to a fixed size.\n this.setContain(\"layout paint\");\n\n this._text = new Text();\n this._text.setPointerEvents(\"none\");\n // `pre-wrap` preserves explicit `\\n` breaks as real lines *and* wraps\n // a line that overflows the fixed width — `nowrap` would collapse the\n // breaks onto one line. A single-line tooltip narrower than MAX_WIDTH\n // renders identically to the old `nowrap` behaviour.\n this._text.setWhiteSpace(\"pre-wrap\");\n this.addComponent(this._text);\n }\n\n /**\n * Returns the single shared `Tooltip` instance, creating it on first call.\n *\n * @returns The singleton `Tooltip` component.\n */\n private static getInstance(): Tooltip {\n if (!Tooltip.instance) {\n Tooltip.instance = new Tooltip();\n }\n\n return Tooltip.instance;\n }\n\n /**\n * Immediately shows the tooltip with the given text near the specified coordinates.\n *\n * The tooltip sits just past the cursor on each axis — down and to the right —\n * flipping to sit just before it when there is no room past, so it never covers\n * the cursor it's hinting at. The clearance is measured from a small box around\n * the pointer hotspot rather than from the hotspot itself, so a flipped tooltip\n * hugs the cursor as closely as an unflipped one. A tooltip taller than roughly\n * half the viewport (an unusually long multi-line label) can still saturate\n * on-screen rather than flipping cleanly, since its height is uncapped and never\n * scrolls; this is a known limitation, not a bug to fix here.\n *\n * @param text - The string to display inside the tooltip.\n * @param x - Horizontal viewport coordinate for the tooltip origin.\n * @param y - Vertical viewport coordinate for the tooltip origin.\n */\n static show(text: string, x: number, y: number): void {\n if (Tooltip.showTimer !== null) {\n clearTimeout(Tooltip.showTimer);\n Tooltip.showTimer = null;\n }\n\n const inst = Tooltip.getInstance();\n\n // Whether the tooltip is already on screen (and not mid-dismiss). A\n // caller that shows on every mousemove — a chart hover — reaches `show`\n // dozens of times a second; replaying the entrance fade each time would\n // reset opacity to 0 before the 100 ms fade completes, flickering the\n // tooltip. When already shown we skip the fade and just reposition/retext.\n const alreadyShown = inst.isVisible() && !Tooltip.dismissing;\n\n inst._text.setText(text);\n\n // Size width to the widest line and height to the line count so a\n // `\\n`-containing string renders as a multi-line block. A string with\n // no `\\n` yields a single line and the original single-line sizing.\n const lines = text.split(\"\\n\");\n const widestLine = lines.reduce((max, line) => Math.max(max, Util.measureTextWidth(line)), 0);\n\n inst._perLine = inst._perLineHeight();\n\n // The label is laid out inside the content box, so the tooltip's own\n // border has to be added on top of the text box it needs — otherwise\n // the border eats into the measured width and the last word wraps.\n const perimeter = inst.getPerimeterSize();\n const chromeW = perimeter.left + perimeter.right;\n const chromeH = perimeter.top + perimeter.bottom;\n\n // No minimum width — the tooltip hugs its content (widest line plus\n // horizontal padding), capped at MAX_WIDTH. A min-width floor would\n // pad short labels out to a fixed box wider than their text. MAX_WIDTH\n // stays an outer cap.\n const tooltipWidth = Math.min(Tooltip.MAX_WIDTH, widestLine + Tooltip.H_PADDING + chromeW);\n\n // When the widest line is wider than the cap, the label soft-wraps at the\n // available text width, so the visual line count exceeds the `\\n`-split\n // count. Measure the wrapped height at that width and derive the real line\n // count; an uncapped tooltip never wraps, so the split count stands.\n if (widestLine + Tooltip.H_PADDING + chromeW > Tooltip.MAX_WIDTH) {\n const availTextWidth = tooltipWidth - Tooltip.H_PADDING - chromeW;\n const wrappedHeight = DOM.source.measureText(text, { maxWidth: availTextWidth }).height;\n inst._lineCount = Math.max(lines.length, Math.round(wrappedHeight / inst._perLine));\n } else {\n inst._lineCount = lines.length;\n }\n\n // Height hugs the text: one resolved line height per line plus padding.\n // Floor the single-line case to the legacy ITEM_HEIGHT box so plain\n // one-line tooltips stay pixel-stable; only multi-line tooltips switch\n // to the tighter per-line height that removes the trailing gap.\n let tooltipHeight = inst._lineCount * inst._perLine + Tooltip.V_PADDING;\n if (inst._lineCount === 1) {\n tooltipHeight = Math.max(tooltipHeight, Tooltip.ITEM_HEIGHT + Tooltip.V_PADDING);\n }\n\n // Added after the single-line floor so the floor keeps meaning \"one\n // line of text\" rather than \"one line of text minus the border\".\n tooltipHeight += chromeH;\n\n inst.setWidth(tooltipWidth);\n inst.setHeight(tooltipHeight);\n\n const vp = DOM.source.getViewportSize();\n\n // Clamp the cursor into the viewport first: the flip primitive only guarantees\n // an on-screen result for an in-viewport anchor, and show() is public.\n const cx = Util.clamp(x, 0, vp.width);\n const cy = Util.clamp(y, 0, vp.height);\n\n // Sit past the cursor's clearance box, flipping to sit *before* it when there\n // is no room past — so the tooltip can never land under the cursor. The box\n // (not the bare hotspot) is the anchor rect, which is what lets the two sides\n // differ: the primitive applies one `gap` to whichever edge it picks, and the\n // asymmetry the cursor actually has lives in the edges. No viewport margin,\n // since the tooltip may sit flush against an edge.\n inst.setX(positionAdjacent(\n cx - Tooltip.CURSOR_LEFT, cx + Tooltip.CURSOR_RIGHT, tooltipWidth, vp.width, Tooltip.CURSOR_GAP,\n ));\n inst.setY(positionAdjacent(\n cy - Tooltip.CURSOR_UP, cy + Tooltip.CURSOR_DOWN, tooltipHeight, vp.height, Tooltip.CURSOR_GAP,\n ));\n\n const el = inst.getElement(true)!;\n\n inst.scheduleLayout();\n\n LayerManager.mount(el);\n\n inst.setVisible(true);\n\n // Watch for the anchor leaving the DOM while the tooltip is up (a removed\n // element fires no mouseout), so an orphaned tooltip dismisses on the next\n // pointer move instead of lingering.\n if (!Tooltip.watching) {\n Event.addViewportListener(inst, \"mousemove\", Tooltip._onAnchorWatch);\n Tooltip.watching = true;\n }\n\n // Cancel a pending fade-out's removeElement so a fresh show during the\n // outgoing transition keeps the element in the DOM.\n Tooltip.dismissing = false;\n\n if (alreadyShown) {\n // Already visible — hold at full opacity; the position/size/text\n // updates above are all this repeat show needs. No entrance fade.\n DOM.sink.apply(el, { style: { opacity: \"1\" } });\n\n return;\n }\n\n inst._showAnimation?.cancel();\n inst._showAnimation = Animation.play(el, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: TOOLTIP_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Hides and detaches the tooltip.\n *\n * Any pending show timer is also cancelled.\n */\n static hide(): void {\n if (Tooltip.showTimer !== null) {\n clearTimeout(Tooltip.showTimer);\n Tooltip.showTimer = null;\n }\n\n const inst = Tooltip.getInstance();\n const el = inst.getElement();\n\n // The tooltip is going away; stop watching the (now-irrelevant) anchor\n // and forget it.\n if (Tooltip.watching) {\n Event.removeViewportListener(inst, \"mousemove\", Tooltip._onAnchorWatch);\n Tooltip.watching = false;\n }\n Tooltip.activeElement = null;\n\n if (!el) {\n return;\n }\n\n Tooltip.dismissing = true;\n\n inst._hideAnimation?.cancel();\n inst._hideAnimation = Animation.play(el, {\n to: { opacity: \"0\" },\n durationMs: TOOLTIP_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n onComplete: () => {\n if (!Tooltip.dismissing) {\n return;\n }\n Tooltip.dismissing = false;\n inst.setVisible(false);\n inst.removeElement();\n },\n });\n }\n\n /**\n * Wires `mouseover` and `mouseout` listeners onto `component` so the tooltip\n * shows automatically after a 500 ms hover delay.\n *\n * @remarks Uses `mouseover`/`mouseout` rather than `mouseenter`/`mouseleave`\n * because the Event system routes through a window-level capture handler, and\n * the non-bubbling enter/leave events are not reliably seen there in Chrome.\n *\n * If `colors` is provided the tooltip uses those colors instead of the default\n * theme variables while it is showing for this component.\n *\n * Calling `attach` on a component that already has an attachment replaces it.\n *\n * @param component - The component to attach hover behaviour to.\n * @param text - The tooltip text to display.\n * @param colors - Optional color overrides applied while this tooltip is visible.\n */\n static attach(component: Component, text: string, colors?: TooltipColors): void {\n Tooltip.detach(component);\n\n let cursorX = 0;\n let cursorY = 0;\n\n const mouseoverFn = (e: MouseEvent) => {\n if (Tooltip.showTimer !== null) {\n return;\n }\n\n cursorX = e.clientX;\n cursorY = e.clientY;\n\n Tooltip.showTimer = setTimeout(() => {\n Tooltip._applyColors(colors);\n // Record the anchor so the anchor-watch dismisses the tooltip if\n // this component is later removed from the DOM.\n Tooltip.activeElement = component.getElement() ?? null;\n Tooltip.show(text, cursorX, cursorY);\n Tooltip.showTimer = null;\n }, 500);\n };\n\n const mousemoveFn = (e: MouseEvent) => {\n cursorX = e.clientX;\n cursorY = e.clientY;\n };\n\n const mouseoutFn = () => {\n Tooltip.hide();\n };\n\n // Acting on the component (a click, or the start of a drag) dismisses\n // its tooltip: once the pointer is pressed the hover hint has served its\n // purpose and would only obscure the result of the action. `hide` also\n // cancels a still-pending show timer, so a press during the hover delay\n // suppresses the tooltip entirely. It stays hidden until the pointer\n // leaves and re-enters, since no fresh `mouseover` fires under a\n // stationary cursor.\n const mousedownFn = () => {\n Tooltip.hide();\n };\n\n Event.addListener(component, \"mouseover\", mouseoverFn);\n Event.addListener(component, \"mousemove\", mousemoveFn);\n Event.addListener(component, \"mouseout\", mouseoutFn);\n Event.addListener(component, \"mousedown\", mousedownFn);\n\n Tooltip.attachments.set(component.getId(), {\n text, colors, mouseoverFn, mousemoveFn, mouseoutFn, mousedownFn,\n });\n }\n\n /**\n * Removes the tooltip attachment from a component, cancelling any pending show\n * and hiding the tooltip if it is currently visible for this component.\n *\n * @param component - The component whose attachment should be removed.\n */\n static detach(component: Component): void {\n const id = component.getId();\n const att = Tooltip.attachments.get(id);\n\n if (!att) {\n return;\n }\n\n Event.removeListener(component, \"mouseover\", att.mouseoverFn);\n Event.removeListener(component, \"mousemove\", att.mousemoveFn);\n Event.removeListener(component, \"mouseout\", att.mouseoutFn);\n Event.removeListener(component, \"mousedown\", att.mousedownFn);\n\n Tooltip.attachments.delete(id);\n Tooltip.hide();\n }\n\n /**\n * Applies optional color overrides to the singleton tooltip instance before it is shown.\n * Resets to the default theme variables when no overrides are provided.\n *\n * @param colors - The color overrides to apply, or `undefined` to use defaults.\n */\n private static _applyColors(colors?: TooltipColors): void {\n const inst = Tooltip.getInstance();\n\n inst.setBackgroundColor(\n colors?.background ?? 'var(--ts-ui-tooltip-bg, rgb(255, 255, 240))'\n );\n inst.setForegroundColor(\n colors?.color ?? 'var(--ts-ui-tooltip-color, rgb(0, 0, 0))'\n );\n inst.setBorder({ border: `1px solid ${colors?.border ?? 'var(--ts-ui-tooltip-border, rgb(180, 180, 100))'}` });\n }\n\n /**\n * Wires native `mouseover`, `mousemove`, and `mouseout` listeners directly onto\n * a raw DOM element so the tooltip shows after a 500 ms hover delay.\n *\n * @remarks Use this instead of {@link attach} when the element contains child nodes\n * that would receive the event as `evnt.target` — the Event system's component\n * listener only matches the exact target id, so `attach` would miss those cases.\n *\n * Calling `attachToElement` on an element that already has an attachment\n * replaces it — the previous listeners are removed and a fresh set is\n * installed against the new text. If the tooltip is currently visible for\n * this element when the call happens, it re-renders immediately at the\n * last known cursor position so the visible text updates without a\n * re-hover.\n *\n * @param element - The element handle to attach hover behaviour to.\n * @param text - The tooltip text to display.\n */\n static attachToElement(element: Handle, text: string): void {\n const previous = Tooltip.elementAttachments.get(element);\n const wasActive = Tooltip.activeElement === element;\n\n // Carry the previous binding's last cursor coords so the mid-hover\n // repaint below has real values instead of (0, 0) — detachElement\n // deletes the WeakMap entry, so the read has to happen first.\n const carryX = previous ? previous.lastX : 0;\n const carryY = previous ? previous.lastY : 0;\n\n Tooltip.detachElement(element);\n\n const att: ElementTooltipAttachment = {\n text,\n showTimer : null,\n lastX : carryX,\n lastY : carryY,\n mouseoverFn: function onTooltipMouseOver(e: MouseEvent): void {\n if (att.showTimer !== null) {\n return;\n }\n\n att.lastX = e.clientX;\n att.lastY = e.clientY;\n\n att.showTimer = setTimeout(function onTooltipShowTimer(): void {\n Tooltip.activeElement = element;\n Tooltip.show(att.text, att.lastX, att.lastY);\n att.showTimer = null;\n }, 500);\n },\n mousemoveFn: function onTooltipMouseMove(e: MouseEvent): void {\n att.lastX = e.clientX;\n att.lastY = e.clientY;\n },\n mouseoutFn: function onTooltipMouseOut(): void {\n if (att.showTimer !== null) {\n clearTimeout(att.showTimer);\n att.showTimer = null;\n }\n\n if (Tooltip.activeElement === element) {\n Tooltip.activeElement = null;\n }\n\n Tooltip.hide();\n },\n // Pressing the element (click or drag start) dismisses its tooltip\n // and cancels any pending show — see the `attach` counterpart.\n mousedownFn: function onTooltipMouseDown(): void {\n if (att.showTimer !== null) {\n clearTimeout(att.showTimer);\n att.showTimer = null;\n }\n\n Tooltip.hide();\n },\n };\n\n DOM.sink.addListener(element, \"mouseover\", att.mouseoverFn);\n DOM.sink.addListener(element, \"mousemove\", att.mousemoveFn);\n DOM.sink.addListener(element, \"mouseout\", att.mouseoutFn);\n DOM.sink.addListener(element, \"mousedown\", att.mousedownFn);\n\n Tooltip.elementAttachments.set(element, att);\n\n // Mid-hover update: if the previous attachment was the active tooltip\n // target, repaint the visible tooltip with the new text at the last\n // known cursor coords so the swap is seen without a re-hover.\n if (wasActive) {\n Tooltip.activeElement = element;\n Tooltip.show(text, att.lastX, att.lastY);\n }\n }\n\n /**\n * Removes an `attachToElement` binding installed on an element handle.\n *\n * Detaches the three hover listeners, cancels any pending show timer, and\n * clears the active-element tracker if it pointed at this element.\n * Idempotent — calling on an unattached element is a no-op.\n *\n * @param element - The element handle whose attachment should be removed.\n */\n static detachElement(element: Handle): void {\n const att = Tooltip.elementAttachments.get(element);\n\n if (!att) {\n return;\n }\n\n DOM.sink.removeListener(element, \"mouseover\", att.mouseoverFn);\n DOM.sink.removeListener(element, \"mousemove\", att.mousemoveFn);\n DOM.sink.removeListener(element, \"mouseout\", att.mouseoutFn);\n DOM.sink.removeListener(element, \"mousedown\", att.mousedownFn);\n\n if (att.showTimer !== null) {\n clearTimeout(att.showTimer);\n att.showTimer = null;\n }\n\n Tooltip.elementAttachments.delete(element);\n\n if (Tooltip.activeElement === element) {\n Tooltip.activeElement = null;\n }\n }\n\n /**\n * Resolves the tooltip text's rendered per-line height in pixels from the\n * live additive line box, measured at hover time. Matches the `_text` label\n * (a default `Text`: 14px font, theme leading) so the box hugs the text\n * vertically with no trailing gap, and tracks theme/font changes on the next\n * hover.\n *\n * @returns The per-line height in pixels, ceiled to a whole pixel.\n *\n * @remarks `measureTextMetrics` already defaults `lineHeight` to\n * `calc(1em + var(--ts-ui-line-padding, 2px))`, so the line box is left to\n * that default rather than passed explicitly.\n */\n private _perLineHeight(): number {\n const { height } = DOM.source.measureText(\"X\");\n\n return Math.ceil(height);\n }\n\n /**\n * Positions the label to fill the tooltip's content box with uniform\n * padding, so the label does not overhang the tooltip's border.\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 this._text.setX(box.x + Tooltip.H_PADDING / 2);\n this._text.setY(box.y + Tooltip.V_PADDING / 2);\n this._text.setWidth(Math.max(0, box.width - Tooltip.H_PADDING));\n this._text.setHeight(this._lineCount * this._perLine);\n\n return this;\n }\n\n /**\n * Cancels any in-flight fade, then defers to the base class. Cancelling\n * first keeps their fallback timers from firing after `super.destructor()`\n * has released this tooltip's element handle.\n */\n protected destructor(): void {\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._hideAnimation?.cancel();\n this._hideAnimation = null;\n\n super.destructor();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Tooltip-B_UF5bXD.js","names":[],"sources":["../../src/typescript/lib/core/LayerManager.ts","../../src/typescript/lib/core/OverlayPosition.ts","../../src/typescript/lib/overlay/Tooltip.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\n\n/**\n * How a layer responds to an outside interaction.\n *\n * - `\"click-outside\"`: dismissed when a `pointerdown` lands outside the\n * layer's subtree (and its anchor); ignores focus moves.\n * - `\"blur\"`: dismissed when either a `pointerdown` or a focus move lands\n * outside the layer's subtree (and its anchor).\n * - `\"manual\"`: never dismissed by the document-level handlers; the host\n * drives its own teardown.\n * - `\"modal\"`: never dismissed by an outside `pointerdown` or focus move\n * (the layer captures the interaction); only Escape, routed through the\n * keydown handler, asks it to close.\n *\n * @category Core\n */\nexport type LayerDismissMode = \"click-outside\" | \"blur\" | \"manual\" | \"modal\";\n\n/**\n * A portaled overlay surface that participates in the runtime layer tree.\n * Implemented by [`AnimatedDropdown`](/api/core/classes/AnimatedDropdown),\n * [`Popover`](/api/overlay/classes/Popover),\n * [`Dialog`](/api/overlay/classes/Dialog), and\n * [`Window`](/api/overlay/classes/Window).\n *\n * A surface registers itself with {@link LayerManager.register} from its\n * show path and unregisters with {@link LayerManager.unregister} from its\n * hide path. The manager then owns the z-index stamp, the activation\n * marking, and the dismissal decision — calling {@link DismissableLayer.requestClose}\n * (advisory) when an outside interaction meets the layer's\n * {@link LayerDismissMode}.\n *\n * @category Core\n */\nexport interface DismissableLayer {\n /** The layer's root element (already mounted on `documentElement`). */\n getLayerElement(): Handle | null;\n\n /** Dismiss policy consulted by the document-level interaction handlers. */\n getDismissMode(): LayerDismissMode;\n\n /** Advisory request to close; the surface runs its own teardown + unregister. */\n requestClose(): void;\n\n /**\n * Optional anchor element excluded from \"outside\" tests so the trigger\n * click that opened the layer does not immediately re-close it.\n */\n getAnchorElement?(): Handle | null;\n\n /**\n * Optional activation hook. The manager calls it with `true` when this\n * layer becomes the active (topmost containing) layer for a pointer /\n * focus interaction and `false` when it is superseded. Surfaces that\n * carry an active-state affordance (e.g. a window title bar) map it to\n * their own toggle; surfaces without one omit the method.\n */\n onActivate?(active: boolean): void;\n\n /**\n * Optional re-stamp hook. The manager calls it with a fresh z-index when\n * {@link LayerManager.bringToFront} re-allocates the layer (or one of its\n * ancestors) while it is already shown, so the surface can mirror the new\n * value onto its element through its own typed `setZIndex`. Surfaces that\n * only read z once in their show path (and never get raised afterwards)\n * may omit it.\n */\n onZIndexChanged?(zIndex: number): void;\n\n /**\n * Optional z-index band hint. Returns one of {@link LayerManager.Band}'s\n * values so an unrelated peer layer stacks in its surface family's band\n * (Window < Popover < dropdown < Dialog). Omitted surfaces default to the\n * dropdown band. A nested layer ignores its own band and inherits its\n * opener's, so this only matters for top-level (unparented) registrations.\n */\n getBand?(): number;\n\n /**\n * Optional top-level hint. Returns `true` for a surface that is an\n * independent peer rather than a layer opened from another — a window is\n * not a child of whatever happened to be topmost when it appeared. Such a\n * layer registers as a tree root, so raising it does not drag an unrelated\n * peer up with it (see {@link LayerManager.bringToFront}). Omitted surfaces\n * register under the current topmost layer (the opened-from edge).\n */\n isLayerRoot?(): boolean;\n}\n\n/**\n * Node in the runtime layer tree. Generalizes the per-host `LayerEntry`\n * that `AnimatedDropdown` previously kept module-private: `children` are the\n * layers opened while this one was topmost, `band` is the z-index band base\n * the layer was assigned (inherited from its opener), and `zIndex` is the\n * band + counter stamp the manager assigned at register time.\n */\ninterface LayerNode {\n layer: DismissableLayer;\n parent: LayerNode | null;\n children: LayerNode[];\n band: number;\n zIndex: number;\n}\n\n// Z-index bands. Plain module constants because z-index is not themed\n// anywhere today (Theme.ts carries no z-index tokens; the values were inline\n// per class). The bands reconcile the four historical bases into one\n// ascending allocator and preserve relative order between unrelated peers:\n// Window 9000 < Popover 9800 < dropdowns 10000 < Dialog 11000.\n// A nested child inherits its opener's band but always lands above it because\n// it registers later and so draws a higher counter. The 200-1000 gap between\n// bands leaves headroom for the monotonic `_zCounter`; it is not reset, on the\n// assumption a single session opens far fewer than 200 unrelated layers in the\n// same band before a reload — the historical inline values made the same bet.\nconst Z_BAND_WINDOW: number = 9000;\nconst Z_BAND_POPOVER: number = 9800;\nconst Z_BAND_DROPDOWN: number = 10000;\nconst Z_BAND_DIALOG: number = 11000;\n// Above every managed layer: a tooltip is a transient, non-interactive\n// affordance that must float over even a modal Dialog and its backdrop. Not a\n// registered layer band — exposed so the Tooltip singleton stamps itself here\n// rather than carrying a magic number that could fall below the Dialog band.\nconst Z_BAND_TOOLTIP: number = 12000;\n\n/**\n * Sentinel `Component` used as the registration key for the manager's three\n * document-level listeners. `Event.addViewportListener` binds a listener to a\n * `Component`, but the manager's handlers are module-global (one per type, not\n * per layer), so a single stable sentinel owns them — mirroring `Window`'s\n * `_viewportListenerOwner`. The install/teardown lifecycle keys off the tree\n * being non-empty rather than any individual layer.\n */\nconst _listenerOwner: Component = new Component();\n\n/**\n * Central registry and document-level interaction broker for portaled overlay\n * surfaces. Owns one runtime layer tree (push-on-register /\n * link-under-current-topmost / pop-on-unregister), one z-index allocator with\n * reserved bands, and a single set of document-level `pointerdown` / `focusin`\n * / `keydown` listeners installed on the first register and removed on the\n * last unregister.\n *\n * The relationship the tree captures — the runtime \"opened-from\" edge (which\n * layer was topmost when this one opened) — is distinct from the static\n * component hierarchy and changes per activation, so it lives here rather than\n * in the component tree. Containment queries hop across portaled descendant\n * layers via {@link LayerManager.containsAcrossLayers}, which is the single\n * place cross-portal containment is reasoned about.\n *\n * @category Core\n */\nexport namespace LayerManager {\n\n // LIFO stack of open layers in register order; the last entry is topmost.\n const _stack: LayerNode[] = [];\n\n // Membership + node lookup keyed by the layer instance, generalizing the\n // per-host `_entryByLayer` WeakMap.\n const _nodeByLayer: WeakMap<DismissableLayer, LayerNode> = new WeakMap();\n\n // Ascending per-register counter; combined with a band base to stamp z.\n let _zCounter: number = 0;\n\n // The layer currently marked active (received the last `onActivate(true)`).\n let _activeLayer: DismissableLayer | null = null;\n\n let _listenersInstalled: boolean = false;\n\n /**\n * The four z-index bands a surface returns from\n * {@link DismissableLayer.getBand}. Exposed so each surface can tag itself\n * without re-declaring the constants. Reconciles the historical inline\n * bases (Window 9000, Popover 9998, dropdowns 10050, Dialog 10101) into\n * one ascending allocator.\n */\n export const Band = {\n Window: Z_BAND_WINDOW,\n Popover: Z_BAND_POPOVER,\n Dropdown: Z_BAND_DROPDOWN,\n Dialog: Z_BAND_DIALOG,\n Tooltip: Z_BAND_TOOLTIP,\n } as const;\n\n /**\n * Picks the z-index band for a layer. A nested layer inherits its\n * opener's band so it stays in the same stacking neighbourhood (and rises\n * above the opener because it registers later); an unrelated peer uses its\n * own surface-type band from {@link DismissableLayer.getBand}.\n */\n function bandFor(parent: LayerNode | null, ownBand: number): number {\n return parent ? parent.band : ownBand;\n }\n\n /**\n * Pushes `layer` as a child of the current topmost layer — unless it\n * declares itself a top-level peer via\n * {@link DismissableLayer.isLayerRoot}, in which case it registers as a\n * tree root — assigns its band-based z-index, and installs the\n * document-level listeners on the first call. A duplicate register (e.g. a\n * `showAnimated` that cancels an in-flight fade-out) is a no-op so the tree\n * never double-pushes.\n *\n * @param layer - The surface entering the layer tree.\n */\n export function register(layer: DismissableLayer): void {\n if (_nodeByLayer.has(layer)) {\n return;\n }\n\n const topmost = _stack.length > 0 ? _stack[_stack.length - 1] : null;\n const parent = layer.isLayerRoot?.() ? null : topmost;\n const band = bandFor(parent, layer.getBand?.() ?? Z_BAND_DROPDOWN);\n const zIndex = band + (++_zCounter);\n\n const node: LayerNode = { layer, parent, children: [], band, zIndex };\n\n _nodeByLayer.set(layer, node);\n _stack.push(node);\n\n if (parent) {\n parent.children.push(node);\n }\n\n if (!_listenersInstalled) {\n installListeners();\n }\n }\n\n /**\n * Appends a portaled overlay element to `document.documentElement` if it is\n * not already contained there. The single home for the \"portal mount\"\n * idiom every floating surface used to inline. Idempotent — a surface that\n * re-shows after detach re-appends, and one already mounted is left alone.\n * Does **not** register the layer; surfaces still call {@link register}\n * explicitly.\n *\n * @param el - The overlay element to portal onto the document root.\n */\n export function mount(el: Handle): void {\n if (!DOM.source.contains(DOM.source.getDocumentElement(), el)) {\n DOM.sink.appendChild(DOM.source.getDocumentElement(), el);\n }\n }\n\n /**\n * Pops `layer` from the tree (and unlinks it from its parent's child\n * list), clears it as active if it was, and removes the document-level\n * listeners on the last call.\n *\n * @param layer - The surface leaving the layer tree.\n */\n export function unregister(layer: DismissableLayer): void {\n const node = _nodeByLayer.get(layer);\n\n if (!node) {\n return;\n }\n\n _nodeByLayer.delete(layer);\n\n const idx = _stack.indexOf(node);\n\n if (idx >= 0) {\n _stack.splice(idx, 1);\n }\n\n if (node.parent) {\n const ci = node.parent.children.indexOf(node);\n\n if (ci >= 0) {\n node.parent.children.splice(ci, 1);\n }\n }\n\n if (_activeLayer === layer) {\n _activeLayer = null;\n }\n\n if (_stack.length === 0 && _listenersInstalled) {\n uninstallListeners();\n }\n }\n\n /**\n * Returns true when `node` is inside `layer`'s own element OR inside any\n * descendant layer's element. This is the only cross-portal containment\n * hop in the framework: a `pointerdown` or focus move that lands in a\n * child layer (e.g. a dropdown opened from inside a popover) counts as\n * inside the ancestor so the ancestor stays open.\n *\n * @param layer - The layer whose subtree is tested.\n * @param node - The DOM node receiving the interaction.\n * @returns True when `node` is inside `layer` or any descendant layer.\n */\n export function containsAcrossLayers(layer: DismissableLayer, node: Handle | null): boolean {\n if (!node) {\n return false;\n }\n\n const layerNode = _nodeByLayer.get(layer);\n\n if (!layerNode) {\n const el = layer.getLayerElement();\n\n return el ? DOM.source.contains(el, node) : false;\n }\n\n return nodeContains(layerNode, node);\n }\n\n /**\n * Returns the topmost currently-registered layer, or null when none is\n * open.\n *\n * @returns The topmost open layer, or null.\n */\n export function getTopLayer(): DismissableLayer | null {\n return _stack.length > 0 ? _stack[_stack.length - 1].layer : null;\n }\n\n /**\n * Re-stamps `layer` (and its descendant layers) with fresh top-of-band\n * z-indices and marks it active. Used by surfaces that raise on click\n * (e.g. a window brought to front), so the raised layer — and anything it\n * opened — jumps above its band peers without disturbing other bands.\n *\n * @param layer - The layer to raise and activate.\n */\n export function bringToFront(layer: DismissableLayer): void {\n const node = _nodeByLayer.get(layer);\n\n if (!node) {\n return;\n }\n\n restampSubtree(node);\n markActive(layer);\n }\n\n /**\n * Returns the z-index currently assigned to `layer`, for surfaces that\n * mirror it elsewhere. Falls back to the dropdown band when the layer is\n * not registered.\n *\n * @param layer - The layer whose stamp to read.\n * @returns The assigned z-index.\n */\n export function getZIndex(layer: DismissableLayer): number {\n const node = _nodeByLayer.get(layer);\n\n return node ? node.zIndex : Z_BAND_DROPDOWN;\n }\n\n /**\n * Re-stamps `node`'s subtree from a fresh top-of-band counter run so the\n * raised layer and its descendants ascend together, preserving their\n * relative order. Each re-stamped layer is notified via\n * {@link DismissableLayer.onZIndexChanged} so it can mirror the new value\n * through its own typed `setZIndex` (the manager never writes the DOM).\n */\n function restampSubtree(node: LayerNode): void {\n const walk = (n: LayerNode): void => {\n n.zIndex = n.band + (++_zCounter);\n n.layer.onZIndexChanged?.(n.zIndex);\n\n for (const child of n.children) {\n walk(child);\n }\n };\n\n walk(node);\n }\n\n /** True when `node` (or a descendant layer) contains `target`. */\n function nodeContains(node: LayerNode, target: Handle): boolean {\n const el = node.layer.getLayerElement();\n\n if (el && DOM.source.contains(el, target)) {\n return true;\n }\n\n for (const child of node.children) {\n if (nodeContains(child, target)) {\n return true;\n }\n }\n\n return false;\n }\n\n /** Marks `layer` active and deactivates the previously-active layer. */\n function markActive(layer: DismissableLayer): void {\n if (_activeLayer === layer) {\n return;\n }\n\n deactivateActive();\n\n _activeLayer = layer;\n\n if (layer.onActivate) {\n layer.onActivate(true);\n }\n }\n\n /** Deactivates the currently-active layer, if any. */\n function deactivateActive(): void {\n if (_activeLayer && _activeLayer.onActivate) {\n _activeLayer.onActivate(false);\n }\n\n _activeLayer = null;\n }\n\n /**\n * Walks up the \"opened-from\" chain from `node` and returns the nearest\n * ancestor-or-self layer that carries an activation affordance\n * ({@link DismissableLayer.onActivate}), or `null` if none does. A layer\n * opened inside a window resolves to that window, so the window stays\n * active while its descendant holds focus — interacting with a child layer\n * must not darken the title bar of the window that owns it.\n */\n function activatableAncestor(node: LayerNode): DismissableLayer | null {\n let cur: LayerNode | null = node;\n\n while (cur) {\n if (cur.layer.onActivate) {\n return cur.layer;\n }\n\n cur = cur.parent;\n }\n\n return null;\n }\n\n /**\n * Walks the stack top-down for a pointer / focus interaction at `target`,\n * dismissing each topmost layer whose dismiss mode and containment test\n * say the interaction landed outside it, and marking the topmost\n * containing layer active. `focusOnly` is true for the `focusin` pass, so\n * only `\"blur\"` layers act on it (a `\"click-outside\"` layer ignores focus\n * moves).\n */\n function handleOutside(target: Handle | null, focusOnly: boolean): void {\n // Snapshot top-down so requestClose-driven unregisters don't disturb\n // the walk; re-read containment against the live tree each step.\n const snapshot = _stack.slice().reverse();\n let activated = false;\n let landedInside = false;\n\n for (const node of snapshot) {\n const mode = node.layer.getDismissMode();\n\n if (containsAcrossLayers(node.layer, target)) {\n // Activation belongs to the nearest activatable ancestor (the\n // window a child layer was opened from), not the innermost\n // containing layer; fall back to the layer itself when nothing\n // up the chain carries an activation affordance, preserving the\n // prior behaviour for standalone layers.\n markActive(activatableAncestor(node) ?? node.layer);\n activated = true;\n landedInside = true;\n\n break;\n }\n\n const anchor = node.layer.getAnchorElement?.();\n\n if (anchor && target && DOM.source.contains(anchor, target)) {\n landedInside = true;\n\n break;\n }\n\n const dismissable = mode === \"click-outside\" || mode === \"blur\";\n const acts = focusOnly ? mode === \"blur\" : dismissable;\n\n if (mode === \"manual\" || mode === \"modal\" || !acts) {\n // Modal captures the interaction: an outside pointer/focus on a\n // modal must not fall through to dismiss layers beneath it.\n if (mode === \"modal\") {\n landedInside = true;\n\n break;\n }\n\n continue;\n }\n\n node.layer.requestClose();\n }\n\n // An interaction that landed outside every layer's subtree (and any\n // anchor / modal capture) deactivates the active layer — e.g. an\n // empty-viewport click that should drop a window's active title-bar\n // highlight. A focus move to a non-layer element (focusOnly) does not\n // deactivate, so tabbing through page chrome doesn't flicker the\n // active window.\n if (!activated && !landedInside && !focusOnly) {\n deactivateActive();\n }\n }\n\n /** Document `pointerdown` handler — see {@link handleOutside}. */\n function onPointerDown(e: PointerEvent): void {\n handleOutside(e.target === null ? null : DOM.source.intern(e.target), false);\n }\n\n /**\n * Window `blur` handler. When the whole browser window loses focus (the user\n * clicks another application or alt-tabs away) no in-page `pointerdown` or\n * `focusin` fires, so an open layer would otherwise stay up. Treat it as an\n * interaction landing outside every layer, dismissing each `\"click-outside\"`\n * / `\"blur\"` layer while a `\"modal\"` shields those beneath it and `\"manual\"`\n * layers (windows) are left open.\n *\n * @remarks Viewport listeners are capture-phase, so element blurs from within\n * the page surface here too; only a genuine window blur (`target` is the\n * window itself) should dismiss layers.\n */\n function onWindowBlur(e: FocusEvent): void {\n if (!DOM.source.isWindow(e.target === null ? null : DOM.source.intern(e.target))) {\n return;\n }\n\n handleOutside(null, false);\n }\n\n /** Document `focusin` handler — only `\"blur\"` layers act on it. */\n function onFocusIn(e: FocusEvent): void {\n handleOutside(e.target === null ? null : DOM.source.intern(e.target), true);\n }\n\n /**\n * Document `keydown` handler — Escape asks the topmost non-manual layer to close.\n *\n * @param e - The keydown event.\n * @returns `true` when Escape actually closed a layer; nothing otherwise, so an\n * Escape with no dismissible layer open keeps propagating.\n */\n function onKeyDown(e: KeyboardEvent): Event.ListenerResult {\n if (e.key !== \"Escape\") {\n return;\n }\n\n for (let i = _stack.length - 1; i >= 0; i--) {\n const mode = _stack[i].layer.getDismissMode();\n\n if (mode === \"manual\") {\n continue;\n }\n\n _stack[i].layer.requestClose();\n\n return true;\n }\n\n return;\n }\n\n /** Installs the three document-level listeners against the sentinel owner. */\n function installListeners(): void {\n Event.addViewportListener(_listenerOwner, \"pointerdown\", onPointerDown);\n Event.addViewportListener(_listenerOwner, \"focusin\", onFocusIn);\n Event.addViewportListener(_listenerOwner, \"keydown\", onKeyDown);\n Event.addViewportListener(_listenerOwner, \"blur\", onWindowBlur);\n\n _listenersInstalled = true;\n }\n\n /** Removes the three document-level listeners. */\n function uninstallListeners(): void {\n Event.removeViewportListener(_listenerOwner, \"pointerdown\", onPointerDown);\n Event.removeViewportListener(_listenerOwner, \"focusin\", onFocusIn);\n Event.removeViewportListener(_listenerOwner, \"keydown\", onKeyDown);\n Event.removeViewportListener(_listenerOwner, \"blur\", onWindowBlur);\n\n _listenersInstalled = false;\n }\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Util } from \"~/core/Util.js\";\nimport type { Rect } from \"~/core/DOM.js\";\nimport type { Size } from \"~/primitive/Size.js\";\n\n/**\n * The primary growth axis for an anchored overlay.\n *\n * @category Core\n */\nexport type AnchorAxis = \"vertical\" | \"horizontal\";\n\n/**\n * Options controlling how {@link positionAnchored} places an element against an\n * anchor rect.\n *\n * @category Core\n */\nexport interface AnchorOptions {\n /** Primary growth axis: `\"vertical\"` grows below/above, `\"horizontal\"` right/left. */\n axis: AnchorAxis;\n /** Gap in px between the anchor edge and the element on the primary axis. Default 0. */\n gap?: number;\n /** Viewport-edge margin in px kept on the cross axis. Default 0. */\n margin?: number;\n}\n\n/**\n * Chooses a top-left coordinate on the primary axis. Prefers growing past the\n * anchor's far edge (`farEdge + gap`); flips to the near edge\n * (`nearEdge - extent - gap`) only when the far side lacks room AND the near\n * side has it. When neither side fits, saturates on-screen toward the side with\n * more room — pinned to the viewport's far end when the far side is roomier,\n * otherwise to `0` — so the element never overflows off-screen.\n *\n * @param nearEdge - The anchor's near edge (top / left) on this axis.\n * @param farEdge - The anchor's far edge (bottom / right) on this axis.\n * @param extent - The element's size on this axis.\n * @param viewportExtent - The viewport's size on this axis.\n * @param gap - Gap in px kept between the anchor edge and the element.\n * @returns The chosen top-left coordinate on this axis.\n *\n * @category Core\n */\nexport function positionAdjacent(nearEdge: number, farEdge: number, extent: number, viewportExtent: number, gap: number): number {\n const farStart = farEdge + gap;\n const spaceFar = viewportExtent - farStart;\n const spaceNear = nearEdge - gap;\n\n if (extent <= spaceFar) {\n return farStart;\n }\n\n if (extent <= spaceNear) {\n return nearEdge - extent - gap;\n }\n\n // Neither side fits: pin to whichever side has more room, saturated so the\n // element stays on-screen (mirrors placeAnchored's spaceBelow/spaceAbove\n // fallback, axis-agnostic).\n if (spaceFar >= spaceNear) {\n return Math.max(0, viewportExtent - extent);\n }\n\n return 0;\n}\n\n/**\n * Chooses a top-left coordinate on the cross axis for a **fixed-size** element\n * that ALIGNS WITH (and overlaps) its anchor rather than sitting beside it.\n * Prefers the element's near edge flush with the anchor's near edge\n * (`nearEdge`); flips to the element's FAR edge flush with the anchor's far\n * edge (`farEdge - extent`) only when the near alignment overflows the\n * viewport. This is alignment, not adjacency — the element overlaps the\n * anchor either way; only which pair of edges is made flush changes. It is\n * fixed-size and returns no `available` (contrast {@link\n * positionFlexibleAnchored}); `margin` binds on **this** axis. When neither\n * alignment fits on-screen (an anchor whose own far edge is off-viewport, or\n * an element wider than the viewport), falls back to {@link clampAxis} — the\n * right answer when no placement fits, not a flip substitute.\n *\n * @param nearEdge - The anchor's near edge (left / top) on this axis.\n * @param farEdge - The anchor's far edge (right / bottom) on this axis.\n * @param extent - The element's fixed size on this axis.\n * @param viewportExtent - The viewport's size on this axis.\n * @param margin - Viewport-edge margin in px kept on this axis.\n * @returns The chosen top-left coordinate on this axis.\n *\n * @category Core\n */\nexport function positionAligned(\n nearEdge: number, farEdge: number, extent: number, viewportExtent: number, margin: number,\n): number {\n // Preferred: the element's near edge aligns with the anchor's near edge.\n if (nearEdge >= margin && nearEdge + extent <= viewportExtent - margin) {\n return nearEdge;\n }\n\n // Flipped: the element's FAR edge aligns with the anchor's far edge. Note this\n // is alignment, not adjacency — the element overlaps the anchor either way; only\n // which pair of edges is made flush changes.\n const farStart = farEdge - extent;\n\n if (farStart >= margin && farStart + extent <= viewportExtent - margin) {\n return farStart;\n }\n\n // Neither alignment fits on-screen (an anchor whose own far edge is off-viewport,\n // or an element wider than the viewport): fall back to the clamp. Clamping is the\n // right answer when NO placement fits — it is only wrong as a substitute for a flip.\n return clampAxis(nearEdge, extent, viewportExtent, margin);\n}\n\n/**\n * Clamps a top-left coordinate into `[margin, extent - size - margin]`, pinning\n * to `margin` (top-aligned) when the element is larger than the available span\n * so the coordinate never goes below `margin` and off-screen. The upper bound is\n * floored to `margin` before clamping, because `Util.clamp` resolves a `min >\n * max` range to the max — the opposite of the `Math.max(margin, Math.min(...))`\n * this replaces; flooring keeps `min <= max`, so an over-large element pins to\n * `margin` and the caller's height-cap / scroll carries the overflow.\n *\n * @param value - The proposed coordinate.\n * @param size - The element's extent on this axis.\n * @param extent - The viewport's extent on this axis.\n * @param margin - Viewport-edge margin kept on this axis.\n * @returns The clamped coordinate.\n */\nfunction clampAxis(value: number, size: number, extent: number, margin: number): number {\n return Util.clamp(value, margin, Math.max(margin, extent - size - margin));\n}\n\n/**\n * The chosen coordinate for a size-flexible anchored element, plus the room\n * available on the side it landed on.\n *\n * @category Core\n */\nexport interface FlexiblePlacement {\n /** Top-left coordinate on the primary axis. */\n start: number;\n /** Room (px) on the side actually chosen — the caller's height/width cap. */\n available: number;\n}\n\n/**\n * Chooses a top-left coordinate on the primary axis for a **size-flexible**\n * element — one whose extent may be capped and the overflow scrolled, rather\n * than being placed at a fixed size. Grows from `farEdge`; flips to end at\n * `nearEdge` only when the content overflows the far room **and** the near\n * side is roomier. Unlike {@link positionAdjacent}, the far-fits check is against the\n * *room*, not the raw `extent` — so a flip that still doesn't fully fit on\n * the near side still flips (and clamps) rather than falling back to filling\n * the viewport over the anchor. The returned `available` is the room on the\n * chosen side, so a caller that caps the extent measures the correct side —\n * re-deriving it from `start` would measure the wrong side for a flipped\n * element. `viewportMargin` binds on **this** (primary) axis, unlike\n * {@link positionAnchored}'s cross-axis-only `margin`. Always flush against\n * the anchor edge — no `gap` parameter, because every current caller sits\n * flush.\n *\n * @param nearEdge - The anchor's near edge (top / left) on this axis — the\n * edge a flipped element's far edge meets.\n * @param farEdge - The anchor's far edge (bottom / right) on this axis — the\n * edge an unflipped element grows from.\n * @param extent - The element's unclamped preferred size on this axis.\n * @param viewportExtent - The viewport's size on this axis.\n * @param viewportMargin - Viewport-edge margin in px kept on this axis.\n * @returns The chosen coordinate and the room available at it.\n *\n * @category Core\n */\nexport function positionFlexibleAnchored(\n nearEdge: number,\n farEdge: number,\n extent: number,\n viewportExtent: number,\n viewportMargin: number,\n): FlexiblePlacement {\n const roomFar = viewportExtent - farEdge - viewportMargin;\n const roomNear = nearEdge - viewportMargin;\n\n if (extent <= roomFar || roomFar >= roomNear) {\n return { start: farEdge, available: roomFar };\n }\n\n return { start: nearEdge - Math.min(extent, roomNear), available: roomNear };\n}\n\n/**\n * Places an element of `size` against `anchorRect` inside `viewport`. On the\n * primary axis it grows past the anchor's far edge (below / right), flipping to\n * the near edge (above / left) only when the far side lacks room AND the near\n * side has more; on the cross axis it aligns to the anchor's near edge,\n * flipping to align with the anchor's far edge when the near alignment\n * overflows, and clamping only when neither alignment fits. Pure — all\n * viewport reads are supplied by the caller, so it is directly unit-testable\n * with no DOM.\n *\n * @param anchorRect - The anchor element's bounding rect.\n * @param size - The element's width/height to place.\n * @param viewport - The viewport size to clamp/flip within.\n * @param opts - Axis, gap, and cross-axis margin.\n * @returns The resolved top-left `{ x, y }` for the element.\n *\n * @category Core\n */\nexport function positionAnchored(anchorRect: Rect, size: Size, viewport: Size, opts: AnchorOptions): { x: number; y: number } {\n const gap = opts.gap ?? 0;\n const margin = opts.margin ?? 0;\n\n if (opts.axis === \"vertical\") {\n const y = positionAdjacent(anchorRect.top, anchorRect.bottom, size.height, viewport.height, gap);\n const x = positionAligned(anchorRect.left, anchorRect.right, size.width, viewport.width, margin);\n\n return { x, y };\n }\n\n const x = positionAdjacent(anchorRect.left, anchorRect.right, size.width, viewport.width, gap);\n const y = positionAligned(anchorRect.top, anchorRect.bottom, size.height, viewport.height, margin);\n\n return { x, y };\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { DOM, type Handle } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { LayerManager } from \"~/core/LayerManager.js\";\nimport { positionAdjacent } from \"~/core/OverlayPosition.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Animation } from \"~/core/Animation.js\";\nimport { Text } from \"~/component/input/Text.js\";\n\nconst TOOLTIP_ANIM_DURATION_MS: number = 100;\n\n/**\n * Optional color overrides for a tooltip attachment.\n *\n * When provided to {@link Tooltip.attach}, these replace the default theme variables\n * for the duration that the tooltip is shown for that component.\n *\n * @category Core\n */\nexport interface TooltipColors {\n /** CSS color for the tooltip background. */\n background?: string;\n /** CSS color for the tooltip text. */\n color?: string;\n /** CSS color for the tooltip border. */\n border?: string;\n}\n\n/** Internal record of a component's tooltip attachment. */\ninterface TooltipAttachment {\n text : string;\n colors : TooltipColors | undefined;\n mouseoverFn : (e: MouseEvent) => void;\n mousemoveFn : (e: MouseEvent) => void;\n mouseoutFn : () => void;\n mousedownFn : () => void;\n}\n\n/** Internal record of a raw-element tooltip attachment. */\ninterface ElementTooltipAttachment {\n text : string;\n mouseoverFn : (e: MouseEvent) => void;\n mousemoveFn : (e: MouseEvent) => void;\n mouseoutFn : () => void;\n mousedownFn : () => void;\n showTimer : ReturnType<typeof setTimeout> | null;\n lastX : number;\n lastY : number;\n}\n\n/**\n * A singleton floating tooltip that appears near the cursor after a short delay.\n *\n * Use `Tooltip.attach(component, text)` to wire hover listeners onto any component.\n * For manual control use `Tooltip.show(text, x, y)` and `Tooltip.hide()` directly.\n *\n * @example\n * ```typescript\n * Tooltip.attach(myButton, 'Save the document');\n * ```\n *\n * @category Core\n */\n// Intentional exception to the `callable()`-wrap rule: `Tooltip` has a private\n// constructor and is used only through its static methods (`show` / `attach` /\n// …), so there is no public `new Tooltip()` / `Tooltip(...)` surface to wrap.\n// The singleton is exported directly (see `overlay/index.ts`).\nexport class Tooltip extends Component {\n\n // In-flight fades, cancelled on teardown so their fallback timers cannot\n // fire against this tooltip's released element handle. Instance fields\n // written through the singleton `inst` in the static show / hide below.\n private _showAnimation: Animation.CancelHandle | null = null;\n private _hideAnimation: Animation.CancelHandle | null = null;\n\n private static instance: Tooltip | null = null;\n private static showTimer: ReturnType<typeof setTimeout> | null = null;\n private static attachments: Map<string, TooltipAttachment> = new Map();\n private static elementAttachments: Map<Handle, ElementTooltipAttachment> = new Map();\n private static activeElement: Handle | null = null;\n\n // True while the anchor-watch viewport listener is installed (only while a\n // tooltip is shown). See _onAnchorWatch.\n private static watching: boolean = false;\n\n /**\n * Hides the tooltip when its anchor element has left the DOM. The browser\n * fires no `mouseout` when an element is removed under a stationary pointer,\n * so a tooltip whose anchor vanishes would otherwise linger; this runs on\n * pointer movement while a tooltip is shown and dismisses it once the anchor\n * is no longer connected.\n */\n private static readonly _onAnchorWatch = (): void => {\n if (Tooltip.activeElement !== null && !DOM.source.isConnected(Tooltip.activeElement)) {\n Tooltip.hide();\n }\n };\n\n // Set true while a fade-out is in flight; reset to false when the fade-out\n // completes (so the deferred removeElement fires) or when a fresh `show()`\n // re-displays the tooltip mid-fade (the deferred removeElement is then\n // skipped because the tooltip is back on screen).\n private static dismissing: boolean = false;\n\n private static readonly H_PADDING: number = 16;\n private static readonly V_PADDING: number = 8;\n private static readonly MAX_WIDTH: number = 300;\n private static readonly ITEM_HEIGHT: number = 20;\n\n // Clearance kept free around the pointer *hotspot* (the arrow's tip, the\n // hand's fingertip) — the coordinate `show` is handed. The cursor glyph\n // hangs down and to the right of its hotspot, so this box is deliberately\n // NOT centred on it: nothing sits above the tip, and only a few px reach\n // left of it. A box symmetric about the hotspot would push a flipped\n // tooltip visibly further from the cursor than an unflipped one, since the\n // same gap spans glyph on one side and empty space on the other.\n //\n // These are clearance, not glyph metrics: DOWN is shorter than the ~24px\n // glyph on purpose, so an unflipped tooltip keeps sitting alongside the\n // pointer's lower half (where it tapers and hides nothing) rather than\n // being pushed clear of it. No API reports the live cursor's size or\n // hotspot, so the numbers approximate the platform defaults.\n private static readonly CURSOR_LEFT: number = 4;\n private static readonly CURSOR_RIGHT: number = 12;\n private static readonly CURSOR_UP: number = 0;\n private static readonly CURSOR_DOWN: number = 12;\n private static readonly CURSOR_GAP: number = 2;\n\n private _text: Text;\n\n // Number of `\\n`-separated lines in the text currently shown. Cached by\n // `show()` (which knows the raw string) and read by `doLayout()` (which\n // only sees the committed size) so the label's height tracks the line\n // count. 1 for a single-line tooltip — the unchanged legacy case.\n private _lineCount: number = 1;\n\n // Resolved per-line pixel height of the tooltip text, set by `show()` from\n // the live additive line box and read by `doLayout()`. Replaces the\n // fixed `ITEM_HEIGHT` multiplier, which over-allocated ~3px/line and left\n // a trailing empty line on multi-line tooltips.\n private _perLine: number = Tooltip.ITEM_HEIGHT;\n\n /** Private — use the static methods; only one instance is ever created. */\n private constructor() {\n super();\n\n this.setVisible(false);\n // Above every managed layer (incl. a modal Dialog and its backdrop) so a\n // hover tooltip is never occluded.\n this.setZIndex(LayerManager.Band.Tooltip);\n this.setBackgroundColor(\"var(--ts-ui-tooltip-bg, rgb(255, 255, 240))\");\n this.setForegroundColor(\"var(--ts-ui-tooltip-color, rgb(0, 0, 0))\");\n this.setBorder({ border: \"1px solid var(--ts-ui-tooltip-border, rgb(180, 180, 100))\" });\n this.setShadow(\"var(--ts-ui-tooltip-shadow, 1px 2px 4px rgba(0, 0, 0, 0.2))\");\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n this.setPointerEvents(\"none\");\n // Top-level overlay, dynamic size — layout+paint containment scopes reflow without\n // committing to a fixed size.\n this.setContain(\"layout paint\");\n\n this._text = new Text();\n this._text.setPointerEvents(\"none\");\n // `pre-wrap` preserves explicit `\\n` breaks as real lines *and* wraps\n // a line that overflows the fixed width — `nowrap` would collapse the\n // breaks onto one line. A single-line tooltip narrower than MAX_WIDTH\n // renders identically to the old `nowrap` behaviour.\n this._text.setWhiteSpace(\"pre-wrap\");\n this.addComponent(this._text);\n }\n\n /**\n * Returns the single shared `Tooltip` instance, creating it on first call.\n *\n * @returns The singleton `Tooltip` component.\n */\n private static getInstance(): Tooltip {\n if (!Tooltip.instance) {\n Tooltip.instance = new Tooltip();\n }\n\n return Tooltip.instance;\n }\n\n /**\n * Immediately shows the tooltip with the given text near the specified coordinates.\n *\n * The tooltip sits just past the cursor on each axis — down and to the right —\n * flipping to sit just before it when there is no room past, so it never covers\n * the cursor it's hinting at. The clearance is measured from a small box around\n * the pointer hotspot rather than from the hotspot itself, so a flipped tooltip\n * hugs the cursor as closely as an unflipped one. A tooltip taller than roughly\n * half the viewport (an unusually long multi-line label) can still saturate\n * on-screen rather than flipping cleanly, since its height is uncapped and never\n * scrolls; this is a known limitation, not a bug to fix here.\n *\n * @param text - The string to display inside the tooltip.\n * @param x - Horizontal viewport coordinate for the tooltip origin.\n * @param y - Vertical viewport coordinate for the tooltip origin.\n */\n static show(text: string, x: number, y: number): void {\n if (Tooltip.showTimer !== null) {\n clearTimeout(Tooltip.showTimer);\n Tooltip.showTimer = null;\n }\n\n const inst = Tooltip.getInstance();\n\n // Whether the tooltip is already on screen (and not mid-dismiss). A\n // caller that shows on every mousemove — a chart hover — reaches `show`\n // dozens of times a second; replaying the entrance fade each time would\n // reset opacity to 0 before the 100 ms fade completes, flickering the\n // tooltip. When already shown we skip the fade and just reposition/retext.\n const alreadyShown = inst.isVisible() && !Tooltip.dismissing;\n\n inst._text.setText(text);\n\n // Size width to the widest line and height to the line count so a\n // `\\n`-containing string renders as a multi-line block. A string with\n // no `\\n` yields a single line and the original single-line sizing.\n const lines = text.split(\"\\n\");\n const widestLine = lines.reduce((max, line) => Math.max(max, Util.measureTextWidth(line)), 0);\n\n inst._perLine = inst._perLineHeight();\n\n // The label is laid out inside the content box, so the tooltip's own\n // border has to be added on top of the text box it needs — otherwise\n // the border eats into the measured width and the last word wraps.\n const perimeter = inst.getPerimeterSize();\n const chromeW = perimeter.left + perimeter.right;\n const chromeH = perimeter.top + perimeter.bottom;\n\n // No minimum width — the tooltip hugs its content (widest line plus\n // horizontal padding), capped at MAX_WIDTH. A min-width floor would\n // pad short labels out to a fixed box wider than their text. MAX_WIDTH\n // stays an outer cap.\n const tooltipWidth = Math.min(Tooltip.MAX_WIDTH, widestLine + Tooltip.H_PADDING + chromeW);\n\n // When the widest line is wider than the cap, the label soft-wraps at the\n // available text width, so the visual line count exceeds the `\\n`-split\n // count. Measure the wrapped height at that width and derive the real line\n // count; an uncapped tooltip never wraps, so the split count stands.\n if (widestLine + Tooltip.H_PADDING + chromeW > Tooltip.MAX_WIDTH) {\n const availTextWidth = tooltipWidth - Tooltip.H_PADDING - chromeW;\n const wrappedHeight = DOM.source.measureText(text, { maxWidth: availTextWidth }).height;\n inst._lineCount = Math.max(lines.length, Math.round(wrappedHeight / inst._perLine));\n } else {\n inst._lineCount = lines.length;\n }\n\n // Height hugs the text: one resolved line height per line plus padding.\n // Floor the single-line case to the legacy ITEM_HEIGHT box so plain\n // one-line tooltips stay pixel-stable; only multi-line tooltips switch\n // to the tighter per-line height that removes the trailing gap.\n let tooltipHeight = inst._lineCount * inst._perLine + Tooltip.V_PADDING;\n if (inst._lineCount === 1) {\n tooltipHeight = Math.max(tooltipHeight, Tooltip.ITEM_HEIGHT + Tooltip.V_PADDING);\n }\n\n // Added after the single-line floor so the floor keeps meaning \"one\n // line of text\" rather than \"one line of text minus the border\".\n tooltipHeight += chromeH;\n\n inst.setWidth(tooltipWidth);\n inst.setHeight(tooltipHeight);\n\n const vp = DOM.source.getViewportSize();\n\n // Clamp the cursor into the viewport first: the flip primitive only guarantees\n // an on-screen result for an in-viewport anchor, and show() is public.\n const cx = Util.clamp(x, 0, vp.width);\n const cy = Util.clamp(y, 0, vp.height);\n\n // Sit past the cursor's clearance box, flipping to sit *before* it when there\n // is no room past — so the tooltip can never land under the cursor. The box\n // (not the bare hotspot) is the anchor rect, which is what lets the two sides\n // differ: the primitive applies one `gap` to whichever edge it picks, and the\n // asymmetry the cursor actually has lives in the edges. No viewport margin,\n // since the tooltip may sit flush against an edge.\n inst.setX(positionAdjacent(\n cx - Tooltip.CURSOR_LEFT, cx + Tooltip.CURSOR_RIGHT, tooltipWidth, vp.width, Tooltip.CURSOR_GAP,\n ));\n inst.setY(positionAdjacent(\n cy - Tooltip.CURSOR_UP, cy + Tooltip.CURSOR_DOWN, tooltipHeight, vp.height, Tooltip.CURSOR_GAP,\n ));\n\n const el = inst.getElement(true)!;\n\n inst.scheduleLayout();\n\n LayerManager.mount(el);\n\n inst.setVisible(true);\n\n // Watch for the anchor leaving the DOM while the tooltip is up (a removed\n // element fires no mouseout), so an orphaned tooltip dismisses on the next\n // pointer move instead of lingering.\n if (!Tooltip.watching) {\n Event.addViewportListener(inst, \"mousemove\", Tooltip._onAnchorWatch);\n Tooltip.watching = true;\n }\n\n // Cancel a pending fade-out's removeElement so a fresh show during the\n // outgoing transition keeps the element in the DOM.\n Tooltip.dismissing = false;\n\n if (alreadyShown) {\n // Already visible — hold at full opacity; the position/size/text\n // updates above are all this repeat show needs. No entrance fade.\n DOM.sink.apply(el, { style: { opacity: \"1\" } });\n\n return;\n }\n\n inst._showAnimation?.cancel();\n inst._showAnimation = Animation.play(el, {\n from: { opacity: \"0\" },\n to: { opacity: \"1\" },\n durationMs: TOOLTIP_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n });\n }\n\n /**\n * Hides and detaches the tooltip.\n *\n * Any pending show timer is also cancelled.\n */\n static hide(): void {\n if (Tooltip.showTimer !== null) {\n clearTimeout(Tooltip.showTimer);\n Tooltip.showTimer = null;\n }\n\n const inst = Tooltip.getInstance();\n const el = inst.getElement();\n\n // The tooltip is going away; stop watching the (now-irrelevant) anchor\n // and forget it.\n if (Tooltip.watching) {\n Event.removeViewportListener(inst, \"mousemove\", Tooltip._onAnchorWatch);\n Tooltip.watching = false;\n }\n Tooltip.activeElement = null;\n\n if (!el) {\n return;\n }\n\n Tooltip.dismissing = true;\n\n inst._hideAnimation?.cancel();\n inst._hideAnimation = Animation.play(el, {\n to: { opacity: \"0\" },\n durationMs: TOOLTIP_ANIM_DURATION_MS,\n properties: [\"opacity\"],\n onComplete: () => {\n if (!Tooltip.dismissing) {\n return;\n }\n Tooltip.dismissing = false;\n inst.setVisible(false);\n inst.removeElement();\n },\n });\n }\n\n /**\n * Wires `mouseover` and `mouseout` listeners onto `component` so the tooltip\n * shows automatically after a 500 ms hover delay.\n *\n * @remarks Uses `mouseover`/`mouseout` rather than `mouseenter`/`mouseleave`\n * because the Event system routes through a window-level capture handler, and\n * the non-bubbling enter/leave events are not reliably seen there in Chrome.\n *\n * If `colors` is provided the tooltip uses those colors instead of the default\n * theme variables while it is showing for this component.\n *\n * Calling `attach` on a component that already has an attachment replaces it.\n *\n * @param component - The component to attach hover behaviour to.\n * @param text - The tooltip text to display.\n * @param colors - Optional color overrides applied while this tooltip is visible.\n */\n static attach(component: Component, text: string, colors?: TooltipColors): void {\n Tooltip.detach(component);\n\n let cursorX = 0;\n let cursorY = 0;\n\n const mouseoverFn = (e: MouseEvent) => {\n if (Tooltip.showTimer !== null) {\n return;\n }\n\n cursorX = e.clientX;\n cursorY = e.clientY;\n\n Tooltip.showTimer = setTimeout(() => {\n Tooltip._applyColors(colors);\n // Record the anchor so the anchor-watch dismisses the tooltip if\n // this component is later removed from the DOM.\n Tooltip.activeElement = component.getElement() ?? null;\n Tooltip.show(text, cursorX, cursorY);\n Tooltip.showTimer = null;\n }, 500);\n };\n\n const mousemoveFn = (e: MouseEvent) => {\n cursorX = e.clientX;\n cursorY = e.clientY;\n };\n\n const mouseoutFn = () => {\n Tooltip.hide();\n };\n\n // Acting on the component (a click, or the start of a drag) dismisses\n // its tooltip: once the pointer is pressed the hover hint has served its\n // purpose and would only obscure the result of the action. `hide` also\n // cancels a still-pending show timer, so a press during the hover delay\n // suppresses the tooltip entirely. It stays hidden until the pointer\n // leaves and re-enters, since no fresh `mouseover` fires under a\n // stationary cursor.\n const mousedownFn = () => {\n Tooltip.hide();\n };\n\n Event.addListener(component, \"mouseover\", mouseoverFn);\n Event.addListener(component, \"mousemove\", mousemoveFn);\n Event.addListener(component, \"mouseout\", mouseoutFn);\n Event.addListener(component, \"mousedown\", mousedownFn);\n\n Tooltip.attachments.set(component.getId(), {\n text, colors, mouseoverFn, mousemoveFn, mouseoutFn, mousedownFn,\n });\n }\n\n /**\n * Removes the tooltip attachment from a component, cancelling any pending show\n * and hiding the tooltip if it is currently visible for this component.\n *\n * @param component - The component whose attachment should be removed.\n */\n static detach(component: Component): void {\n const id = component.getId();\n const att = Tooltip.attachments.get(id);\n\n if (!att) {\n return;\n }\n\n Event.removeListener(component, \"mouseover\", att.mouseoverFn);\n Event.removeListener(component, \"mousemove\", att.mousemoveFn);\n Event.removeListener(component, \"mouseout\", att.mouseoutFn);\n Event.removeListener(component, \"mousedown\", att.mousedownFn);\n\n Tooltip.attachments.delete(id);\n Tooltip.hide();\n }\n\n /**\n * Applies optional color overrides to the singleton tooltip instance before it is shown.\n * Resets to the default theme variables when no overrides are provided.\n *\n * @param colors - The color overrides to apply, or `undefined` to use defaults.\n */\n private static _applyColors(colors?: TooltipColors): void {\n const inst = Tooltip.getInstance();\n\n inst.setBackgroundColor(\n colors?.background ?? 'var(--ts-ui-tooltip-bg, rgb(255, 255, 240))'\n );\n inst.setForegroundColor(\n colors?.color ?? 'var(--ts-ui-tooltip-color, rgb(0, 0, 0))'\n );\n inst.setBorder({ border: `1px solid ${colors?.border ?? 'var(--ts-ui-tooltip-border, rgb(180, 180, 100))'}` });\n }\n\n /**\n * Wires native `mouseover`, `mousemove`, and `mouseout` listeners directly onto\n * a raw DOM element so the tooltip shows after a 500 ms hover delay.\n *\n * @remarks Use this instead of {@link attach} when the element contains child nodes\n * that would receive the event as `evnt.target` — the Event system's component\n * listener only matches the exact target id, so `attach` would miss those cases.\n *\n * Calling `attachToElement` on an element that already has an attachment\n * replaces it — the previous listeners are removed and a fresh set is\n * installed against the new text. If the tooltip is currently visible for\n * this element when the call happens, it re-renders immediately at the\n * last known cursor position so the visible text updates without a\n * re-hover.\n *\n * @param element - The element handle to attach hover behaviour to.\n * @param text - The tooltip text to display.\n */\n static attachToElement(element: Handle, text: string): void {\n const previous = Tooltip.elementAttachments.get(element);\n const wasActive = Tooltip.activeElement === element;\n\n // Carry the previous binding's last cursor coords so the mid-hover\n // repaint below has real values instead of (0, 0) — detachElement\n // deletes the WeakMap entry, so the read has to happen first.\n const carryX = previous ? previous.lastX : 0;\n const carryY = previous ? previous.lastY : 0;\n\n Tooltip.detachElement(element);\n\n const att: ElementTooltipAttachment = {\n text,\n showTimer : null,\n lastX : carryX,\n lastY : carryY,\n mouseoverFn: function onTooltipMouseOver(e: MouseEvent): void {\n if (att.showTimer !== null) {\n return;\n }\n\n att.lastX = e.clientX;\n att.lastY = e.clientY;\n\n att.showTimer = setTimeout(function onTooltipShowTimer(): void {\n Tooltip.activeElement = element;\n Tooltip.show(att.text, att.lastX, att.lastY);\n att.showTimer = null;\n }, 500);\n },\n mousemoveFn: function onTooltipMouseMove(e: MouseEvent): void {\n att.lastX = e.clientX;\n att.lastY = e.clientY;\n },\n mouseoutFn: function onTooltipMouseOut(): void {\n if (att.showTimer !== null) {\n clearTimeout(att.showTimer);\n att.showTimer = null;\n }\n\n if (Tooltip.activeElement === element) {\n Tooltip.activeElement = null;\n }\n\n Tooltip.hide();\n },\n // Pressing the element (click or drag start) dismisses its tooltip\n // and cancels any pending show — see the `attach` counterpart.\n mousedownFn: function onTooltipMouseDown(): void {\n if (att.showTimer !== null) {\n clearTimeout(att.showTimer);\n att.showTimer = null;\n }\n\n Tooltip.hide();\n },\n };\n\n DOM.sink.addListener(element, \"mouseover\", att.mouseoverFn);\n DOM.sink.addListener(element, \"mousemove\", att.mousemoveFn);\n DOM.sink.addListener(element, \"mouseout\", att.mouseoutFn);\n DOM.sink.addListener(element, \"mousedown\", att.mousedownFn);\n\n Tooltip.elementAttachments.set(element, att);\n\n // Mid-hover update: if the previous attachment was the active tooltip\n // target, repaint the visible tooltip with the new text at the last\n // known cursor coords so the swap is seen without a re-hover.\n if (wasActive) {\n Tooltip.activeElement = element;\n Tooltip.show(text, att.lastX, att.lastY);\n }\n }\n\n /**\n * Removes an `attachToElement` binding installed on an element handle.\n *\n * Detaches the three hover listeners, cancels any pending show timer, and\n * clears the active-element tracker if it pointed at this element.\n * Idempotent — calling on an unattached element is a no-op.\n *\n * @param element - The element handle whose attachment should be removed.\n */\n static detachElement(element: Handle): void {\n const att = Tooltip.elementAttachments.get(element);\n\n if (!att) {\n return;\n }\n\n DOM.sink.removeListener(element, \"mouseover\", att.mouseoverFn);\n DOM.sink.removeListener(element, \"mousemove\", att.mousemoveFn);\n DOM.sink.removeListener(element, \"mouseout\", att.mouseoutFn);\n DOM.sink.removeListener(element, \"mousedown\", att.mousedownFn);\n\n if (att.showTimer !== null) {\n clearTimeout(att.showTimer);\n att.showTimer = null;\n }\n\n Tooltip.elementAttachments.delete(element);\n\n if (Tooltip.activeElement === element) {\n Tooltip.activeElement = null;\n }\n }\n\n /**\n * Resolves the tooltip text's rendered per-line height in pixels from the\n * live additive line box, measured at hover time. Matches the `_text` label\n * (a default `Text`: 14px font, theme leading) so the box hugs the text\n * vertically with no trailing gap, and tracks theme/font changes on the next\n * hover.\n *\n * @returns The per-line height in pixels, ceiled to a whole pixel.\n *\n * @remarks `measureTextMetrics` already defaults `lineHeight` to\n * `calc(1em + var(--ts-ui-line-padding, 2px))`, so the line box is left to\n * that default rather than passed explicitly.\n */\n private _perLineHeight(): number {\n const { height } = DOM.source.measureText(\"X\");\n\n return Math.ceil(height);\n }\n\n /**\n * Positions the label to fill the tooltip's content box with uniform\n * padding, so the label does not overhang the tooltip's border.\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 this._text.setX(box.x + Tooltip.H_PADDING / 2);\n this._text.setY(box.y + Tooltip.V_PADDING / 2);\n this._text.setWidth(Math.max(0, box.width - Tooltip.H_PADDING));\n this._text.setHeight(this._lineCount * this._perLine);\n\n return this;\n }\n\n /**\n * Cancels any in-flight fade, then defers to the base class. Cancelling\n * first keeps their fallback timers from firing after `super.destructor()`\n * has released this tooltip's element handle.\n */\n protected destructor(): void {\n this._showAnimation?.cancel();\n this._showAnimation = null;\n this._hideAnimation?.cancel();\n this._hideAnimation = null;\n\n super.destructor();\n }\n}\n"],"mappings":"kLAwHA,IAAM,EAA0B,IAC1B,EAA0B,KAC1B,EAA0B,IAC1B,EAA0B,KAK1B,EAA0B,KAU1B,EAA4B,IAAI,EAmBrB,GAAV,SAAA,EAAA,CAGH,IAAM,EAAsB,CAAC,EAIvB,EAAqD,IAAI,QAG3D,EAAoB,EAGpB,EAAwC,KAExC,EAA+B,GAStB,EAAA,KAAO,CAChB,OAAU,EACV,QAAU,EACV,SAAU,EACV,OAAU,EACV,QAAU,CACd,EAQA,SAAS,QAAQ,EAA0B,EAAyB,CAChE,OAAO,EAAS,EAAO,KAAO,CAClC,CAaO,SAAS,SAAS,EAA+B,CACpD,GAAI,EAAa,IAAI,CAAK,EACtB,OAGJ,IAAM,EAAU,EAAO,OAAS,EAAI,EAAO,EAAO,OAAS,GAAK,KAC1D,EAAU,EAAM,cAAc,EAAI,KAAO,EACzC,EAAU,QAAQ,EAAQ,EAAM,UAAU,GAAK,CAAe,EAG9D,EAAkB,CAAE,QAAO,SAAQ,SAAU,CAAC,EAAG,OAAM,OAF9C,GAAQ,EAAE,CAE2C,EAEpE,EAAa,IAAI,EAAO,CAAI,EAC5B,EAAO,KAAK,CAAI,EAEZ,GACA,EAAO,SAAS,KAAK,CAAI,EAGxB,GACD,iBAAiB,CAEzB,qBAYO,SAAS,MAAM,EAAkB,CAC/B,EAAI,OAAO,SAAS,EAAI,OAAO,mBAAmB,EAAG,CAAE,GACxD,EAAI,KAAK,YAAY,EAAI,OAAO,mBAAmB,EAAG,CAAE,CAEhE,eASO,SAAS,WAAW,EAA+B,CACtD,IAAM,EAAO,EAAa,IAAI,CAAK,EAEnC,GAAI,CAAC,EACD,OAGJ,EAAa,OAAO,CAAK,EAEzB,IAAM,EAAM,EAAO,QAAQ,CAAI,EAM/B,GAJI,GAAO,GACP,EAAO,OAAO,EAAK,CAAC,EAGpB,EAAK,OAAQ,CACb,IAAM,EAAK,EAAK,OAAO,SAAS,QAAQ,CAAI,EAExC,GAAM,GACN,EAAK,OAAO,SAAS,OAAO,EAAI,CAAC,CAEzC,CAEI,IAAiB,IACjB,EAAe,MAGf,EAAO,SAAW,GAAK,GACvB,mBAAmB,CAE3B,yBAaO,SAAS,qBAAqB,EAAyB,EAA8B,CACxF,GAAI,CAAC,EACD,MAAO,GAGX,IAAM,EAAY,EAAa,IAAI,CAAK,EAExC,GAAI,CAAC,EAAW,CACZ,IAAM,EAAK,EAAM,gBAAgB,EAEjC,OAAO,EAAK,EAAI,OAAO,SAAS,EAAI,CAAI,EAAI,EAChD,CAEA,OAAO,aAAa,EAAW,CAAI,CACvC,6CAQO,SAAS,aAAuC,CACnD,OAAO,EAAO,OAAS,EAAI,EAAO,EAAO,OAAS,EAAE,CAAC,MAAQ,IACjE,2BAUO,SAAS,aAAa,EAA+B,CACxD,IAAM,EAAO,EAAa,IAAI,CAAK,EAE9B,IAIL,eAAe,CAAI,EACnB,WAAW,CAAK,EACpB,6BAUO,SAAS,UAAU,EAAiC,CACvD,IAAM,EAAO,EAAa,IAAI,CAAK,EAEnC,OAAO,EAAO,EAAK,OAAS,CAChC,uBASA,SAAS,eAAe,EAAuB,CAC3C,IAAM,KAAQ,GAAuB,CACjC,EAAE,OAAS,EAAE,MAAQ,EAAE,EACvB,EAAE,MAAM,kBAAkB,EAAE,MAAM,EAElC,IAAK,IAAM,KAAS,EAAE,SAClB,KAAK,CAAK,CAElB,EAEA,KAAK,CAAI,CACb,CAGA,SAAS,aAAa,EAAiB,EAAyB,CAC5D,IAAM,EAAK,EAAK,MAAM,gBAAgB,EAEtC,GAAI,GAAM,EAAI,OAAO,SAAS,EAAI,CAAM,EACpC,MAAO,GAGX,IAAK,IAAM,KAAS,EAAK,SACrB,GAAI,aAAa,EAAO,CAAM,EAC1B,MAAO,GAIf,MAAO,EACX,CAGA,SAAS,WAAW,EAA+B,CAC3C,IAAiB,IAIrB,iBAAiB,EAEjB,EAAe,EAEX,EAAM,YACN,EAAM,WAAW,EAAI,EAE7B,CAGA,SAAS,kBAAyB,CAC1B,GAAgB,EAAa,YAC7B,EAAa,WAAW,EAAK,EAGjC,EAAe,IACnB,CAUA,SAAS,oBAAoB,EAA0C,CACnE,IAAI,EAAwB,EAE5B,KAAO,GAAK,CACR,GAAI,EAAI,MAAM,WACV,OAAO,EAAI,MAGf,EAAM,EAAI,MACd,CAEA,OAAO,IACX,CAUA,SAAS,cAAc,EAAuB,EAA0B,CAGpE,IAAM,EAAY,EAAO,MAAM,CAAC,CAAC,QAAQ,EACrC,EAAc,GACd,EAAe,GAEnB,IAAK,IAAM,KAAQ,EAAU,CACzB,IAAM,EAAO,EAAK,MAAM,eAAe,EAEvC,GAAI,qBAAqB,EAAK,MAAO,CAAM,EAAG,CAM1C,WAAW,oBAAoB,CAAI,GAAK,EAAK,KAAK,EAClD,EAAe,GACf,EAAe,GAEf,KACJ,CAEA,IAAM,EAAS,EAAK,MAAM,mBAAmB,EAE7C,GAAI,GAAU,GAAU,EAAI,OAAO,SAAS,EAAQ,CAAM,EAAG,CACzD,EAAe,GAEf,KACJ,CAKA,GAAI,IAAS,UAAY,IAAS,SAAW,EAFzB,EAAY,IAAS,OADrB,IAAS,iBAAmB,IAAS,QAGL,CAGhD,GAAI,IAAS,QAAS,CAClB,EAAe,GAEf,KACJ,CAEA,QACJ,CAEA,EAAK,MAAM,aAAa,CAC5B,CAQI,CAAC,GAAa,CAAC,GAAgB,CAAC,GAChC,iBAAiB,CAEzB,CAGA,SAAS,cAAc,EAAuB,CAC1C,cAAc,EAAE,SAAW,KAAO,KAAO,EAAI,OAAO,OAAO,EAAE,MAAM,EAAG,EAAK,CAC/E,CAcA,SAAS,aAAa,EAAqB,CAClC,EAAI,OAAO,SAAS,EAAE,SAAW,KAAO,KAAO,EAAI,OAAO,OAAO,EAAE,MAAM,CAAC,GAI/E,cAAc,KAAM,EAAK,CAC7B,CAGA,SAAS,UAAU,EAAqB,CACpC,cAAc,EAAE,SAAW,KAAO,KAAO,EAAI,OAAO,OAAO,EAAE,MAAM,EAAG,EAAI,CAC9E,CASA,SAAS,UAAU,EAAwC,CACnD,KAAE,MAAQ,SAId,KAAK,IAAI,EAAI,EAAO,OAAS,EAAG,GAAK,EAAG,IACvB,KAAO,EAAE,CAAC,MAAM,eAEzB,IAAS,SAMb,OAFA,EAAO,EAAE,CAAC,MAAM,aAAa,EAEtB,EACX,CAGJ,CAGA,SAAS,kBAAyB,CAC9B,EAAM,oBAAoB,EAAgB,cAAe,aAAa,EACtE,EAAM,oBAAoB,EAAgB,UAAe,SAAS,EAClE,EAAM,oBAAoB,EAAgB,UAAe,SAAS,EAClE,EAAM,oBAAoB,EAAgB,OAAe,YAAY,EAErE,EAAsB,EAC1B,CAGA,SAAS,oBAA2B,CAChC,EAAM,uBAAuB,EAAgB,cAAe,aAAa,EACzE,EAAM,uBAAuB,EAAgB,UAAe,SAAS,EACrE,EAAM,uBAAuB,EAAgB,UAAe,SAAS,EACrE,EAAM,uBAAuB,EAAgB,OAAe,YAAY,EAExE,EAAsB,EAC1B,CACH,EAAA,CAAA,AAAA,IAAA,CAAA,CAAD,EC7hBA,SAAgB,iBAAiB,EAAkB,EAAiB,EAAgB,EAAwB,EAAqB,CAC7H,IAAM,EAAY,EAAU,EACtB,EAAY,EAAiB,EAC7B,EAAY,EAAW,EAiB7B,OAfI,GAAU,EACH,EAGP,GAAU,EACH,EAAW,EAAS,EAM3B,GAAY,EACL,KAAK,IAAI,EAAG,EAAiB,CAAM,EAGvC,CACX,CAyBA,SAAgB,gBACZ,EAAkB,EAAiB,EAAgB,EAAwB,EACrE,CAEN,GAAI,GAAY,GAAU,EAAW,GAAU,EAAiB,EAC5D,OAAO,EAMX,IAAM,EAAW,EAAU,EAS3B,OAPI,GAAY,GAAU,EAAW,GAAU,EAAiB,EACrD,EAMJ,UAAU,EAAU,EAAQ,EAAgB,CAAM,CAC7D,CAiBA,SAAS,UAAU,EAAe,EAAc,EAAgB,EAAwB,CACpF,OAAO,EAAK,MAAM,EAAO,EAAQ,KAAK,IAAI,EAAQ,EAAS,EAAO,CAAM,CAAC,CAC7E,CA0CA,SAAgB,yBACZ,EACA,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAAW,EAAiB,EAAU,EACtC,EAAW,EAAW,EAM5B,OAJI,GAAU,GAAW,GAAW,EACzB,CAAE,MAAO,EAAS,UAAW,CAAQ,EAGzC,CAAE,MAAO,EAAW,KAAK,IAAI,EAAQ,CAAQ,EAAG,UAAW,CAAS,CAC/E,CAoBA,SAAgB,iBAAiB,EAAkB,EAAY,EAAgB,EAA+C,CAC1H,IAAM,EAAS,EAAK,KAAU,EACxB,EAAS,EAAK,QAAU,EAE9B,GAAI,EAAK,OAAS,WAAY,CAC1B,IAAM,EAAI,iBAAiB,EAAW,IAAK,EAAW,OAAQ,EAAK,OAAQ,EAAS,OAAQ,CAAG,EAG/F,MAAO,CAAE,EAFC,gBAAgB,EAAW,KAAM,EAAW,MAAO,EAAK,MAAO,EAAS,MAAO,CAEhF,EAAG,GAAE,CAClB,CAKA,MAAO,CAAE,EAHC,iBAAiB,EAAW,KAAM,EAAW,MAAO,EAAK,MAAO,EAAS,MAAO,CAGjF,EAAG,EAFF,gBAAgB,EAAW,IAAK,EAAW,OAAQ,EAAK,OAAQ,EAAS,OAAQ,CAE/E,CAAE,CAClB,CCpNA,IAAM,EAAmC,IA0D5B,EAAb,MAAa,gBAAgB,CAAU,CAKnC,eAAwD,KACxD,eAAwD,KAExD,OAAe,SAA2B,KAC1C,OAAe,UAAkD,KACjE,OAAe,YAA8C,IAAI,IACjE,OAAe,mBAA4D,IAAI,IAC/E,OAAe,cAA+B,KAI9C,OAAe,SAAoB,GASnC,OAAwB,mBAA6B,CAC7C,QAAQ,gBAAkB,MAAQ,CAAC,EAAI,OAAO,YAAY,QAAQ,aAAa,GAC/E,QAAQ,KAAK,CAErB,EAMA,OAAe,WAAsB,GAErC,OAAwB,UAAoB,GAC5C,OAAwB,UAAoB,EAC5C,OAAwB,UAAoB,IAC5C,OAAwB,YAAsB,GAe9C,OAAwB,YAAsB,EAC9C,OAAwB,aAAuB,GAC/C,OAAwB,UAAoB,EAC5C,OAAwB,YAAsB,GAC9C,OAAwB,WAAqB,EAE7C,MAMA,WAA6B,EAM7B,SAA2B,QAAQ,YAGnC,aAAsB,CAClB,MAAM,EAEN,KAAK,WAAW,EAAK,EAGrB,KAAK,UAAU,EAAa,KAAK,OAAO,EACxC,KAAK,mBAAmB,6CAA6C,EACrE,KAAK,mBAAmB,0CAA0C,EAClE,KAAK,UAAU,CAAE,OAAQ,2DAA4D,CAAC,EACtF,KAAK,UAAU,6DAA6D,EAC5E,KAAK,gBAAgB,iCAAiC,EACtD,KAAK,iBAAiB,MAAM,EAG5B,KAAK,WAAW,cAAc,EAE9B,KAAK,MAAQ,IAAI,EACjB,KAAK,MAAM,iBAAiB,MAAM,EAKlC,KAAK,MAAM,cAAc,UAAU,EACnC,KAAK,aAAa,KAAK,KAAK,CAChC,CAOA,OAAe,aAAuB,CAKlC,MAJA,CACI,QAAQ,WAAW,IAAI,QAGpB,QAAQ,QACnB,CAkBA,OAAO,KAAK,EAAc,EAAW,EAAiB,CAC9C,QAAQ,YAAc,OACtB,aAAa,QAAQ,SAAS,EAC9B,QAAQ,UAAY,MAGxB,IAAM,EAAO,QAAQ,YAAY,EAO3B,EAAe,EAAK,UAAU,GAAK,CAAC,QAAQ,WAElD,EAAK,MAAM,QAAQ,CAAI,EAKvB,IAAM,EAAa,EAAK,MAAM;CAAI,EAC5B,EAAa,EAAM,QAAQ,EAAK,IAAS,KAAK,IAAI,EAAK,EAAK,iBAAiB,CAAI,CAAC,EAAG,CAAC,EAE5F,EAAK,SAAW,EAAK,eAAe,EAKpC,IAAM,EAAY,EAAK,iBAAiB,EAClC,EAAY,EAAU,KAAO,EAAU,MACvC,EAAY,EAAU,IAAO,EAAU,OAMvC,EAAgB,KAAK,IAAI,QAAQ,UAAW,EAAa,QAAQ,UAAY,CAAO,EAM1F,GAAI,EAAa,QAAQ,UAAY,EAAU,QAAQ,UAAW,CAC9D,IAAM,EAAiB,EAAe,QAAQ,UAAY,EACpD,EAAiB,EAAI,OAAO,YAAY,EAAM,CAAE,SAAU,CAAe,CAAC,CAAC,CAAC,OAClF,EAAK,WAAkB,KAAK,IAAI,EAAM,OAAQ,KAAK,MAAM,EAAgB,EAAK,QAAQ,CAAC,CAC3F,KACI,GAAK,WAAa,EAAM,OAO5B,IAAI,EAAgB,EAAK,WAAa,EAAK,SAAW,QAAQ,UAC1D,EAAK,aAAe,IACpB,EAAgB,KAAK,IAAI,EAAe,QAAQ,YAAc,QAAQ,SAAS,GAKnF,GAAiB,EAEjB,EAAK,SAAS,CAAY,EAC1B,EAAK,UAAU,CAAa,EAE5B,IAAM,EAAK,EAAI,OAAO,gBAAgB,EAIhC,EAAK,EAAK,MAAM,EAAG,EAAG,EAAG,KAAK,EAC9B,EAAK,EAAK,MAAM,EAAG,EAAG,EAAG,MAAM,EAQrC,EAAK,KAAK,iBACN,EAAK,QAAQ,YAAa,EAAK,QAAQ,aAAc,EAAe,EAAG,MAAQ,QAAQ,UAC3F,CAAC,EACD,EAAK,KAAK,iBACN,EAAK,QAAQ,UAAa,EAAK,QAAQ,YAAc,EAAe,EAAG,OAAQ,QAAQ,UAC3F,CAAC,EAED,IAAM,EAAK,EAAK,WAAW,EAAI,EAoB/B,GAlBA,EAAK,eAAe,EAEpB,EAAa,MAAM,CAAE,EAErB,EAAK,WAAW,EAAI,EAKpB,AAEI,QAAQ,YADR,EAAM,oBAAoB,EAAM,YAAa,QAAQ,cAAc,EAChD,IAKvB,QAAQ,WAAa,GAEjB,EAAc,CAGd,EAAI,KAAK,MAAM,EAAI,CAAE,MAAO,CAAE,QAAS,GAAI,CAAE,CAAC,EAE9C,MACJ,CAEA,EAAK,gBAAgB,OAAO,EAC5B,EAAK,eAAiB,EAAU,KAAK,EAAI,CACrC,KAAY,CAAE,QAAS,GAAI,EAC3B,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,EACZ,WAAY,CAAC,SAAS,CAC1B,CAAC,CACL,CAOA,OAAO,MAAa,CACZ,QAAQ,YAAc,OACtB,aAAa,QAAQ,SAAS,EAC9B,QAAQ,UAAY,MAGxB,IAAM,EAAO,QAAQ,YAAY,EAC3B,EAAO,EAAK,WAAW,EAI7B,AAEI,QAAQ,YADR,EAAM,uBAAuB,EAAM,YAAa,QAAQ,cAAc,EACnD,IAEvB,QAAQ,cAAgB,KAEnB,IAIL,QAAQ,WAAa,GAErB,EAAK,gBAAgB,OAAO,EAC5B,EAAK,eAAiB,EAAU,KAAK,EAAI,CACrC,GAAY,CAAE,QAAS,GAAI,EAC3B,WAAY,EACZ,WAAY,CAAC,SAAS,EACtB,eAAkB,CACT,QAAQ,aAGb,QAAQ,WAAa,GACrB,EAAK,WAAW,EAAK,EACrB,EAAK,cAAc,EACvB,CACJ,CAAC,EACL,CAmBA,OAAO,OAAO,EAAsB,EAAc,EAA8B,CAC5E,QAAQ,OAAO,CAAS,EAExB,IAAI,EAAU,EACV,EAAU,EAER,YAAe,GAAkB,CAC/B,QAAQ,YAAc,OAI1B,EAAU,EAAE,QACZ,EAAU,EAAE,QAEZ,QAAQ,UAAY,eAAiB,CACjC,QAAQ,aAAa,CAAM,EAG3B,QAAQ,cAAgB,EAAU,WAAW,GAAK,KAClD,QAAQ,KAAK,EAAM,EAAS,CAAO,EACnC,QAAQ,UAAY,IACxB,EAAG,GAAG,EACV,EAEM,YAAe,GAAkB,CACnC,EAAU,EAAE,QACZ,EAAU,EAAE,OAChB,EAEM,eAAmB,CACrB,QAAQ,KAAK,CACjB,EASM,gBAAoB,CACtB,QAAQ,KAAK,CACjB,EAEA,EAAM,YAAY,EAAW,YAAa,WAAW,EACrD,EAAM,YAAY,EAAW,YAAa,WAAW,EACrD,EAAM,YAAY,EAAW,WAAY,UAAU,EACnD,EAAM,YAAY,EAAW,YAAa,WAAW,EAErD,QAAQ,YAAY,IAAI,EAAU,MAAM,EAAG,CACvC,OAAM,SAAQ,YAAa,YAAa,WAAY,WACxD,CAAC,CACL,CAQA,OAAO,OAAO,EAA4B,CACtC,IAAM,EAAM,EAAU,MAAM,EACtB,EAAM,QAAQ,YAAY,IAAI,CAAE,EAEjC,IAIL,EAAM,eAAe,EAAW,YAAa,EAAI,WAAW,EAC5D,EAAM,eAAe,EAAW,YAAa,EAAI,WAAW,EAC5D,EAAM,eAAe,EAAW,WAAa,EAAI,UAAU,EAC3D,EAAM,eAAe,EAAW,YAAa,EAAI,WAAW,EAE5D,QAAQ,YAAY,OAAO,CAAE,EAC7B,QAAQ,KAAK,EACjB,CAQA,OAAe,aAAa,EAA8B,CACtD,IAAM,EAAO,QAAQ,YAAY,EAEjC,EAAK,mBACD,GAAQ,YAAc,6CAC1B,EACA,EAAK,mBACD,GAAQ,OAAS,0CACrB,EACA,EAAK,UAAU,CAAE,OAAQ,aAAa,GAAQ,QAAU,mDAAoD,CAAC,CACjH,CAoBA,OAAO,gBAAgB,EAAiB,EAAoB,CACxD,IAAM,EAAY,QAAQ,mBAAmB,IAAI,CAAO,EAClD,EAAY,QAAQ,gBAAkB,EAKtC,EAAS,EAAW,EAAS,MAAQ,EACrC,EAAS,EAAW,EAAS,MAAQ,EAE3C,QAAQ,cAAc,CAAO,EAE7B,IAAM,EAAgC,CAClC,OACA,UAAa,KACb,MAAa,EACb,MAAa,EACb,YAAa,SAAS,mBAAmB,EAAqB,CACtD,EAAI,YAAc,OAItB,EAAI,MAAQ,EAAE,QACd,EAAI,MAAQ,EAAE,QAEd,EAAI,UAAY,WAAW,SAAS,oBAA2B,CAC3D,QAAQ,cAAgB,EACxB,QAAQ,KAAK,EAAI,KAAM,EAAI,MAAO,EAAI,KAAK,EAC3C,EAAI,UAAY,IACpB,EAAG,GAAG,EACV,EACA,YAAa,SAAS,mBAAmB,EAAqB,CAC1D,EAAI,MAAQ,EAAE,QACd,EAAI,MAAQ,EAAE,OAClB,EACA,WAAY,SAAS,mBAA0B,CACvC,EAAI,YAAc,OAClB,aAAa,EAAI,SAAS,EAC1B,EAAI,UAAY,MAGhB,QAAQ,gBAAkB,IAC1B,QAAQ,cAAgB,MAG5B,QAAQ,KAAK,CACjB,EAGA,YAAa,SAAS,oBAA2B,CACzC,EAAI,YAAc,OAClB,aAAa,EAAI,SAAS,EAC1B,EAAI,UAAY,MAGpB,QAAQ,KAAK,CACjB,CACJ,EAEA,EAAI,KAAK,YAAY,EAAS,YAAa,EAAI,WAAW,EAC1D,EAAI,KAAK,YAAY,EAAS,YAAa,EAAI,WAAW,EAC1D,EAAI,KAAK,YAAY,EAAS,WAAa,EAAI,UAAU,EACzD,EAAI,KAAK,YAAY,EAAS,YAAa,EAAI,WAAW,EAE1D,QAAQ,mBAAmB,IAAI,EAAS,CAAG,EAKvC,IACA,QAAQ,cAAgB,EACxB,QAAQ,KAAK,EAAM,EAAI,MAAO,EAAI,KAAK,EAE/C,CAWA,OAAO,cAAc,EAAuB,CACxC,IAAM,EAAM,QAAQ,mBAAmB,IAAI,CAAO,EAE7C,IAIL,EAAI,KAAK,eAAe,EAAS,YAAa,EAAI,WAAW,EAC7D,EAAI,KAAK,eAAe,EAAS,YAAa,EAAI,WAAW,EAC7D,EAAI,KAAK,eAAe,EAAS,WAAa,EAAI,UAAU,EAC5D,EAAI,KAAK,eAAe,EAAS,YAAa,EAAI,WAAW,EAEzD,EAAI,YAAc,OAClB,aAAa,EAAI,SAAS,EAC1B,EAAI,UAAY,MAGpB,QAAQ,mBAAmB,OAAO,CAAO,EAErC,QAAQ,gBAAkB,IAC1B,QAAQ,cAAgB,MAEhC,CAeA,gBAAiC,CAC7B,GAAM,CAAE,UAAW,EAAI,OAAO,YAAY,GAAG,EAE7C,OAAO,KAAK,KAAK,CAAM,CAC3B,CAQA,UAAiB,CACb,MAAM,SAAS,EAEf,IAAM,EAAM,KAAK,iBAAiB,EAWlC,OATK,GAIL,KAAK,MAAM,KAAK,EAAI,EAAI,QAAQ,UAAY,CAAC,EAC7C,KAAK,MAAM,KAAK,EAAI,EAAI,QAAQ,UAAY,CAAC,EAC7C,KAAK,MAAM,SAAS,KAAK,IAAI,EAAG,EAAI,MAAQ,QAAQ,SAAS,CAAC,EAC9D,KAAK,MAAM,UAAU,KAAK,WAAa,KAAK,QAAQ,EAE7C,MARI,IASf,CAOA,YAA6B,CACzB,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KACtB,KAAK,gBAAgB,OAAO,EAC5B,KAAK,eAAiB,KAEtB,MAAM,WAAW,CACrB,CACJ"}
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./DOM-Ba8YRVel.js";import{S as t,h as n,t as r}from"./Component-w65rPK75.js";import{t as i}from"./ListenerBag-WPdKuuds.js";import{t as a}from"./Glyph-DeJF_zsR.js";import{t as o}from"./Text-CdHf3B-1.js";import{t as s}from"./ProgressSpinner-lAsK26C-.js";import{caret_down as c}from"./glyphs/solid/caret_down.es.js";import{n as l,t as u}from"./selectionsEqual-BrTxPMmK.js";import{caret_right as d}from"./glyphs/solid/caret_right.es.js";var TreeNodeRenderer=class extends r{constructor(){super()}},LabelTreeNodeRenderer=class extends TreeNodeRenderer{_label;constructor(){super(),this.clearInsets(),this._label=new o(void 0,{truncate:!0}),this._label.clearInsets(),this._label.setAutoMeasure(!1)}getLabel(){return this._label}update(e){this._label.setText(e.node.label),this._label.measure()}getContentWidth(){return this._label.getPreferredSize()?.width??0}layoutChildren(e,t){let n=this.getContentBounds()??{x:0,y:0,height:t},r=Math.min(this.getContentWidth(),e);this._label.setAutoCommitStyle(!1),this._label.setX(n.x),this._label.setY(n.y),this._label.setWidth(r),this._label.setHeight(n.height),this._label.setLineHeight(n.height),this._label.setAutoCommitStyle(!0)}init(t){super.init(t);let n=t||this.getElement();return n&&e.sink.appendChild(n,this._label.getElement(!0)),this}};a.register(c,d);var TreeRow=class extends r{_toggle=null;_spinner=null;_renderer;_node=null;_depth=0;constructor(e=()=>new LabelTreeNodeRenderer){super(),this.getAria().setRole(`treeitem`),this._renderer=e()}getToggle(){return this._toggle}getRenderer(){return this._renderer}setRenderer(t){let n=this.getElement();if(n){let r=this._renderer.getElement();r&&e.source.getParentNode(r)===n&&e.sink.removeChild(n,r),e.sink.appendChild(n,t.getElement(!0))}return this._renderer=t,this}getNode(){return this._node}getDepth(){return this._depth}setRowData(t,n,r,i,o,c,l,u){if(this._node=t,this._depth=n,this._toggle){let t=this.getElement();t&&e.sink.removeChild(t,this._toggle.getElement(!0)),this._toggle=null}if(this._spinner){let t=this.getElement();t&&e.sink.removeChild(t,this._spinner.getElement(!0)),this._spinner=null}if(u){let t=new s;this._spinner=t;let n=this.getElement();n&&e.sink.appendChild(n,t.getElement(!0))}else if(r){let t=new a(i?`caret-down`:`caret-right`);t.setCursor(`pointer`),t.clearInsets(),t.getAria().setHidden(!0),this._toggle=t;let n=this.getElement();n&&e.sink.appendChild(n,t.getElement(!0))}return this._renderer.update({node:t,depth:n,expanded:i,selected:l,hasChildren:r}),this.getAria().setLevel(n+1),this.getAria().setExpanded(r?i:null),this.getAria().setSetSize(o),this.getAria().setPosInSet(c),this}layoutChildren(e,t){let n=this.getContentBounds()??{x:0,y:0,width:this.getWidth()||0,height:e},r=n.x+this._depth*t;if(this._toggle){let e=this._toggle.getPreferredSize()?.height??n.height;this._toggle.setAutoCommitStyle(!1),this._toggle.setX(r),this._toggle.setY(n.y+Math.max(0,(n.height-e)/2)),this._toggle.setWidth(20),this._toggle.setHeight(e),this._toggle.setAutoCommitStyle(!0)}this._spinner&&(this._spinner.setAutoCommitStyle(!1),this._spinner.setX(r),this._spinner.setY(n.y),this._spinner.setWidth(20),this._spinner.setHeight(n.height),this._spinner.setAutoCommitStyle(!0),this._spinner.doLayout());let i=r+20,a=Math.max(0,n.x+n.width-i);this._renderer.setAutoCommitStyle(!1),this._renderer.setX(i),this._renderer.setY(n.y),this._renderer.setWidth(a),this._renderer.setHeight(n.height),this._renderer.setAutoCommitStyle(!0),this._renderer.layoutChildren(a,n.height)}getContentWidth(e){let t=this._depth*e,n=this._renderer.getContentWidth();return t+20+n}init(t){super.init(t);let n=t||this.getElement();return n&&e.sink.appendChild(n,this._renderer.getElement(!0)),this}},f=n(TreeRow),p=16,m=24,h=200,g=`var(--ts-ui-table-row-selected, rgba(30, 100, 200, 0.15))`,_={backgroundColor:`var(--ts-ui-input-bg, rgb(255, 255, 255))`,overflow:`hidden`,maxSize:{width:2**53-1,height:2**53-1},rowOverflow:`scroll`},Tree=class extends l{_nodes=[];_expandedNodes=new Set;_flatRows=[];_lastRowWidth=0;_maxContentWidth=0;_selectedNodes=new Set;_loadingNodes=new Set;_loadedNodes=new Set;_anchorNode=null;_focusNode=null;_listeners=new i;_rendererFactory=()=>new LabelTreeNodeRenderer;constructor(e,t){super(e,{..._,...t??{}}),this.getAria().setRole(`tree`),this.getAria().setTabIndex(0),this.getAria().setMultiselectable(!0),this.subscribeTheme(()=>this.onThemeReflow()),this.applyListeners(e?.listeners)}applyOptions(e){return super.applyOptions(e),e.rowOverflow!==void 0&&this.setRowOverflow(e.rowOverflow),this}getRowOverflow(){return this._options.rowOverflow??this._defaultOptions.rowOverflow??`scroll`}setRowOverflow(e){return this._options.rowOverflow=e,this}getPreferredSize(){return this.getPreferredSizeConstraint()===null?{width:h,height:this._flatRows.length*this.getRowHeight()}:super.getPreferredSize()}setNodes(e){return this._nodes=e,this._expandedNodes.clear(),this._selectedNodes.clear(),this._loadingNodes.clear(),this._loadedNodes.clear(),this._anchorNode=null,this._focusNode=null,this._flatten(),this.getElement()&&(this._boundIndices.fill(-1),this.invalidateGeom(),this.renderWindow()),this}expandAll(){let addExpandable=e=>{for(let t of e)t.children&&t.children.length>0&&(this._expandedNodes.add(t),addExpandable(t.children))};return addExpandable(this._nodes),this._reflattenAndRender(),this}getRowHeight(){return m}createPoolRow(){return new f(this._rendererFactory)}getNodes(){return this._nodes}getSelectedNode(){return this._anchorNode}getSelectedNodes(){return Array.from(this._selectedNodes)}selectNode(e){let t=this._flatRows.findIndex(t=>t.node===e);return t<0?this:(this._selectedNodes.clear(),this._selectedNodes.add(e),this._anchorNode=e,this._focusNode=e,this._updateSelectionStyle(),this._scrollIntoView(t),this.renderWindow(),this._updateActiveDescendant(),this)}async revealByPredicate(e){let t=await this._findPath(this._nodes,e,[]);if(t===null)return null;for(let e=0;e<t.length-1;e++)this._expandedNodes.add(t[e]);this._reflattenAndRender();let n=t[t.length-1],r=this._flatRows.findIndex(e=>e.node===n);return r>=0&&this._scrollIntoView(r),n}async _findPath(e,t,n){for(let r of e){let e=[...n,r];if(t(r.data,r))return e;let i=await this._ensureChildrenLoaded(r);if(i.length>0){let n=await this._findPath(i,t,e);if(n!==null)return n}}return null}async _ensureChildrenLoaded(e){if(e.children&&e.children.length>0)return e.children;if(e.loadChildren!==void 0&&!this._loadedNodes.has(e))try{let t=await e.loadChildren();return e.children=t,this._loadedNodes.add(e),t}catch{return[]}return e.children??[]}on(e,t){return this._listeners.add(e,t),this}off(e,t){return this._listeners.remove(e,t),this}emit(e,...t){this._listeners.fire(e,...t)}setRendererFactory(e){this._rendererFactory=e;for(let t of this._rowPool)t.setRenderer(e());return this._boundIndices.fill(-1),this.invalidateGeom(),this.getElement()&&this.renderWindow(),this}getRendererFactory(){return this._rendererFactory}_isExpandable(e){return!!(e.children&&e.children.length>0)||e.hasChildren===!0}_flatten(){this._flatRows=[],this._maxContentWidth=0;let recurse=(e,t)=>{let n=e.length;for(let r=0;r<e.length;r++){let i=e[r];this._flatRows.push({node:i,depth:t,siblingCount:n,posInSet:r+1}),this._isExpandable(i)&&this._expandedNodes.has(i)&&i.children&&recurse(i.children,t+1)}};recurse(this._nodes,0)}_reflattenAndRender(){this._flatten(),this._boundIndices.fill(-1),this.invalidateGeom(),this.renderWindow()}expandNode(e){return this._expandedNodes.has(e)||this._onToggle(e),this}_onToggle(e){if(this._expandedNodes.has(e)){this._expandedNodes.delete(e),this._reflattenAndRender();return}if(!(e.loadChildren!==void 0&&!this._loadedNodes.has(e)&&!(e.children&&e.children.length))){this._expandedNodes.add(e),this._reflattenAndRender();return}this._loadingNodes.has(e)||this._loadAndExpand(e)}async _loadAndExpand(e){this._loadingNodes.add(e),this._reflattenAndRender();try{let t=await e.loadChildren();if(!this._loadingNodes.has(e))return;e.children=t,this._loadedNodes.add(e),this._expandedNodes.add(e)}catch(t){if(!this._loadingNodes.has(e))return;this.emit(`loaderror`,e,t)}finally{this._loadingNodes.delete(e),this._reflattenAndRender()}}_notifySelectionChange(e){u(e,this._selectedNodes)||this.emit(`selection`,this.getSelectedNodes())}_rangeSelect(e,t){let n=Math.min(e,t),r=Math.max(e,t);this._selectedNodes.clear();for(let e=n;e<=r;e++)this._selectedNodes.add(this._flatRows[e].node)}_selectAtIndex(e){if(e<0||e>=this._flatRows.length)return;let t=this._flatRows[e].node,n=new Set(this._selectedNodes);this._selectedNodes.clear(),this._selectedNodes.add(t),this._anchorNode=t,this._focusNode=t,this._updateSelectionStyle(),this._scrollIntoView(e),this.renderWindow(),this._updateActiveDescendant(),this._notifySelectionChange(n)}_extendSelectionTo(e){if(e<0||e>=this._flatRows.length)return;let t=new Set(this._selectedNodes),n=this._anchorNode?this._flatRows.findIndex(e=>e.node===this._anchorNode):e;this._rangeSelect(n<0?e:n,e),this._focusNode=this._flatRows[e].node,this._updateSelectionStyle(),this._scrollIntoView(e),this.renderWindow(),this._updateActiveDescendant(),this._notifySelectionChange(t)}_scrollIntoView(e){this.scrollRowIntoView(e)}_onKeyDown(e){if(!new Set([`ArrowDown`,`ArrowUp`,`ArrowLeft`,`ArrowRight`,`Home`,`End`]).has(e.key))return;let t=this._flatRows;if(t.length===0)return;e.preventDefault();let n=this._focusNode?t.findIndex(e=>e.node===this._focusNode):-1;if(e.key===`ArrowDown`){let r=n<0?0:Math.min(n+1,t.length-1);e.shiftKey?this._extendSelectionTo(r):this._selectAtIndex(r)}else if(e.key===`ArrowUp`){let t=n<0?0:Math.max(n-1,0);e.shiftKey?this._extendSelectionTo(t):this._selectAtIndex(t)}else if(e.key===`Home`)e.shiftKey?this._extendSelectionTo(0):this._selectAtIndex(0);else if(e.key===`End`){let n=t.length-1;e.shiftKey?this._extendSelectionTo(n):this._selectAtIndex(n)}else if(e.key===`ArrowRight`){if(n<0){this._selectAtIndex(0);return}let{node:e}=t[n];if(!this._isExpandable(e))return;this._expandedNodes.has(e)?this._selectAtIndex(n+1):this._onToggle(e)}else if(e.key===`ArrowLeft`){if(n<0)return;let{node:e,depth:r}=t[n];if(this._isExpandable(e)&&this._expandedNodes.has(e)){this._onToggle(e);let t=this._flatRows.findIndex(t=>t.node===e);t>=0&&this._scrollIntoView(t)}else if(r>0){for(let e=n-1;e>=0;e--)if(t[e].depth===r-1){this._selectAtIndex(e);break}}}}_handleClick(t){let n=t.target===null?null:e.source.intern(t.target);for(let r of this._rowPool){let i=r.getNode();if(!i)continue;let a=r.getToggle();if(a){let t=a.getElement();if(t&&(n===t||e.source.contains(t,n))){this._onToggle(i);return}}let o=r.getElement();if(!o||!e.source.contains(o,n)&&n!==o)continue;let s=this._flatRows.findIndex(e=>e.node===i);if(t.shiftKey&&this._anchorNode)this._extendSelectionTo(s);else if(t.ctrlKey||t.metaKey){let e=new Set(this._selectedNodes);this._selectedNodes.has(i)?this._selectedNodes.delete(i):this._selectedNodes.add(i),this._anchorNode=i,this._focusNode=i,this._updateSelectionStyle(),this._notifySelectionChange(e)}else this._selectAtIndex(s);return}}_handleContextMenu(t){let n=t.target===null?null:e.source.intern(t.target);for(let r of this._rowPool){let i=r.getNode();if(!i)continue;let a=r.getElement();if(!(!a||!e.source.contains(a,n)&&n!==a)){t.preventDefault(),this.emit(`contextmenu`,i,t);return}}}_handleDblClick(t){let n=t.target===null?null:e.source.intern(t.target);for(let r of this._rowPool){let i=r.getNode();if(!i)continue;let a=r.getElement();if(!a||!e.source.contains(a,n)&&n!==a)continue;t.preventDefault(),this.emit(`dblclick`,i);let o=r.getToggle(),s=o?o.getElement():void 0;!(s&&(n===s||e.source.contains(s,n)))&&this._isExpandable(i)&&this._onToggle(i);return}}_updateSelectionStyle(){for(let t of this._rowPool){let n=t.getElement();if(!n)continue;let r=t.getNode(),i=r!==null&&this._selectedNodes.has(r),a=r!==null&&r===this._focusNode;i?e.sink.apply(n,{style:{"background-color":g}}):e.sink.apply(n,{style:{"background-color":null}}),a?e.sink.apply(n,{style:{outline:`2px solid var(--ts-ui-focus-ring, rgba(30, 100, 200, 0.6))`,"outline-offset":`-2px`}}):e.sink.apply(n,{style:{outline:null,"outline-offset":null}}),t.getAria().setSelected(i)}}_updateActiveDescendant(){if(!this._focusNode){this.getAria().setActiveDescendant(``);return}for(let e of this._rowPool)if(e.getNode()===this._focusNode){this.getAria().setActiveDescendant(e.getId());return}this.getAria().setActiveDescendant(``)}onThemeReflow(){this._maxContentWidth=0,super.onThemeReflow()}renderWindow(){if(!this.getElement()||!this._scroller||this.deferRenderWhileFirstLayoutHeld())return;let e=this._scroller,t=this._flatRows.length,n=t*m;e.clampToContent(this._lastRowWidth,n);let r=this.getHeight()||0,i=this.computeVisibleWindow(e.getScrollY(),r,t),a=this.computePoolTarget(i.windowSize,r,t);this.growRowPool(a);let{reboundFlags:o,maxContentWidth:s}=this._bindAndMeasure(i.firstRow,i.windowSize);s>this._maxContentWidth&&(this._maxContentWidth=s);let c=this.getRowOverflow()===`clip`?e.getViewportWidth():Math.max(e.getViewportWidth(),this._maxContentWidth);c!==this._lastRowWidth&&(this._lastRowWidth=c,this.invalidateGeom()),this._positionRows(i.firstRow,i.windowSize,c,o),this.hideExcessPoolRows(i.windowSize),this._updateSelectionStyle(),e.layoutScrollbars(c,n),this.finishResumedRender()&&this._updateActiveDescendant()}_bindAndMeasure(e,t){let n=Array(t),r=0;for(let i=0;i<t;i++){let t=this._rowPool[i],a=e+i,o=this._flatRows[a],s=this._isExpandable(o.node),c=this._expandedNodes.has(o.node),l=this._loadingNodes.has(o.node),u=this._boundIndices[i]!==a;if(u){let e=this._selectedNodes.has(o.node);t.setRowData(o.node,o.depth,s,c,o.siblingCount,o.posInSet,e,l),this._boundIndices[i]=a}n[i]=u;let d=t.getContentWidth(p);d>r&&(r=d)}return{reboundFlags:n,maxContentWidth:r}}_positionRows(e,t,n,r){for(let i=0;i<t;i++){let t=this._rowPool[i],a=e+i,o=r[i],s=this.positionRow(i,a*m,n);(o||s)&&t.layoutChildren(m,p)}}init(e){super.init(e);let n=e||this.getElement();return n?(this.initScroller(n),t.addSubtreeListener(this,`click`,this._handleClick),t.addSubtreeListener(this,`contextmenu`,this._handleContextMenu),t.addSubtreeListener(this,`dblclick`,this._handleDblClick),t.addListener(this,`keydown`,this._onKeyDown),this.renderWindow(),this):this}doLayout(){return this.isLayoutPaused()?this:(super.doLayout(),this.renderWindowIfDeferred()||this.renderWindow(),this)}},v=n(Tree);export{LabelTreeNodeRenderer as n,TreeNodeRenderer as r,v as t};
2
+ //# sourceMappingURL=Tree-C7qLbIYH.js.map