@ds-mo/ui 14.18.0 → 14.18.1

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 (273) hide show
  1. package/README.md +7 -0
  2. package/dist/.package-ready.json +1 -1
  3. package/dist/agent-patterns.json +4 -3
  4. package/dist/agent.json +44 -14
  5. package/dist/angular/ds-filter-menu.d.ts +1 -1
  6. package/dist/angular/ds-filter-menu.js +3 -3
  7. package/dist/angular/ds-input.d.ts +1 -1
  8. package/dist/angular/ds-input.js +3 -3
  9. package/dist/angular/ds-menu.d.ts +1 -1
  10. package/dist/angular/ds-menu.js +3 -3
  11. package/dist/angular/ds-panel-tool-header.d.ts +3 -8
  12. package/dist/angular/ds-panel-tools.d.ts +3 -8
  13. package/dist/angular/ds-select.d.ts +1 -1
  14. package/dist/angular/ds-select.js +3 -3
  15. package/dist/angular/ds-tooltip.d.ts +1 -1
  16. package/dist/angular/ds-tooltip.js +3 -3
  17. package/dist/components/ds-agent-activity.js +1 -1
  18. package/dist/components/ds-agent-questionnaire.js +1 -1
  19. package/dist/components/ds-agent-response.js +1 -1
  20. package/dist/components/ds-agent-source-list.js +1 -1
  21. package/dist/components/ds-agent-tool-call.js +1 -1
  22. package/dist/components/ds-attachment-list.js +1 -1
  23. package/dist/components/ds-badge.js +1 -1
  24. package/dist/components/ds-banner.js +1 -1
  25. package/dist/components/ds-banner.js.map +1 -1
  26. package/dist/components/ds-bar-action.js +1 -1
  27. package/dist/components/ds-bar-action.js.map +1 -1
  28. package/dist/components/ds-bar-nav.js +1 -1
  29. package/dist/components/ds-bar-title.js +1 -1
  30. package/dist/components/ds-bar-workflow.js +1 -1
  31. package/dist/components/ds-bar-workflow.js.map +1 -1
  32. package/dist/components/ds-breadcrumb.js +1 -1
  33. package/dist/components/ds-button-filled.js +1 -1
  34. package/dist/components/ds-button-unfilled.js +1 -1
  35. package/dist/components/ds-card-chart.js +1 -1
  36. package/dist/components/ds-card-overview.js +1 -1
  37. package/dist/components/ds-card-setting.js +1 -1
  38. package/dist/components/ds-chart-legend.js +1 -1
  39. package/dist/components/ds-chart.js +1 -1
  40. package/dist/components/ds-checkbox.js +1 -1
  41. package/dist/components/ds-chip.js +1 -1
  42. package/dist/components/ds-code-block.js +1 -1
  43. package/dist/components/ds-conversation-list-item.js +1 -1
  44. package/dist/components/ds-conversation-list-section.js +1 -1
  45. package/dist/components/ds-conversation-list.js +1 -1
  46. package/dist/components/ds-conversation-list.js.map +1 -1
  47. package/dist/components/ds-divider.js +1 -1
  48. package/dist/components/ds-empty-state.js +1 -1
  49. package/dist/components/ds-field.js +1 -1
  50. package/dist/components/ds-filter-menu.js +1 -1
  51. package/dist/components/ds-input.js +1 -1
  52. package/dist/components/ds-markdown.js +1 -1
  53. package/dist/components/ds-menu.js +1 -1
  54. package/dist/components/ds-message-actions.js +1 -1
  55. package/dist/components/ds-message-bubble.js +1 -1
  56. package/dist/components/ds-message-composer.js +1 -1
  57. package/dist/components/ds-message-composer.js.map +1 -1
  58. package/dist/components/ds-message-scroller.js +1 -1
  59. package/dist/components/ds-message.js +1 -1
  60. package/dist/components/ds-mobile-bar-nav.js +1 -1
  61. package/dist/components/ds-mobile-header.js +1 -1
  62. package/dist/components/ds-mobile-section-switcher.js +1 -1
  63. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  64. package/dist/components/ds-modal.js +1 -1
  65. package/dist/components/ds-pagination.js +1 -1
  66. package/dist/components/ds-panel-nav.js +1 -1
  67. package/dist/components/ds-panel-sub-nav.js +1 -1
  68. package/dist/components/ds-panel-tool-header.js +1 -1
  69. package/dist/components/ds-panel-tool-search.js +1 -1
  70. package/dist/components/ds-panel-tools.js +1 -1
  71. package/dist/components/ds-paper-texture.js +1 -1
  72. package/dist/components/ds-radio.js +1 -1
  73. package/dist/components/ds-scroll-overlay.js +1 -1
  74. package/dist/components/ds-select.js +1 -1
  75. package/dist/components/ds-shell-app.js +1 -1
  76. package/dist/components/ds-shell-app.js.map +1 -1
  77. package/dist/components/ds-shell-page.js +1 -1
  78. package/dist/components/ds-shell-tools.js +1 -1
  79. package/dist/components/ds-skeleton.js +1 -1
  80. package/dist/components/ds-slider.js +1 -1
  81. package/dist/components/ds-swatch-picker.js +1 -1
  82. package/dist/components/ds-switch.js +1 -1
  83. package/dist/components/ds-tab-group.js +1 -1
  84. package/dist/components/ds-table-filter.js +1 -1
  85. package/dist/components/ds-table-saved-views.js +1 -1
  86. package/dist/components/ds-table-sort.js +1 -1
  87. package/dist/components/ds-table-toolbar.js +1 -1
  88. package/dist/components/ds-table.js +1 -1
  89. package/dist/components/ds-table.js.map +1 -1
  90. package/dist/components/ds-tag.js +1 -1
  91. package/dist/components/ds-text.js +1 -1
  92. package/dist/components/ds-toast.js +1 -1
  93. package/dist/components/ds-toast.js.map +1 -1
  94. package/dist/components/ds-tooltip-chart.js +1 -1
  95. package/dist/components/ds-tooltip.js +1 -1
  96. package/dist/components/ds-typing-indicator.js +1 -1
  97. package/dist/components/ds-typing-indicator.js.map +1 -1
  98. package/dist/components/{p-Dginv8PT.js → p-1Hu9jlju.js} +2 -2
  99. package/dist/components/{p-BiBXCjjo.js → p-B00PEE6t.js} +2 -2
  100. package/dist/components/{p-C6yLERx6.js → p-B1JRCDHS.js} +2 -2
  101. package/dist/components/p-BBG6Sswb.js +2 -0
  102. package/dist/components/{p-Drcl4zS9.js.map → p-BBG6Sswb.js.map} +1 -1
  103. package/dist/components/{p-Dd8VKdAO.js → p-BCOl1x0_.js} +2 -2
  104. package/dist/components/{p-C9StMCWg.js → p-BHVOiWz5.js} +2 -2
  105. package/dist/components/{p-DJ0GCCFa.js → p-BNkU9dcc.js} +2 -2
  106. package/dist/components/p-BPG0r_mC.js +2 -0
  107. package/dist/components/p-BPG0r_mC.js.map +1 -0
  108. package/dist/components/p-BUDAwJVV.js +2 -0
  109. package/dist/components/p-BUDAwJVV.js.map +1 -0
  110. package/dist/components/{p-hp_2W2Qy.js → p-BXN-e7vF.js} +2 -2
  111. package/dist/components/{p-CihNoe1M.js → p-B_jZIC9R.js} +2 -2
  112. package/dist/components/{p-CAJIARNW.js → p-Bbb9A3NV.js} +2 -2
  113. package/dist/components/{p-jk2-y5dH.js → p-Bece5KH3.js} +2 -2
  114. package/dist/components/{p-D6P_1YrL.js → p-BhYS_Ump.js} +2 -2
  115. package/dist/components/p-Bkh7scMQ.js +2 -0
  116. package/dist/components/{p-DMaQkEYP.js.map → p-Bkh7scMQ.js.map} +1 -1
  117. package/dist/components/p-BknXYAEc.js +2 -0
  118. package/dist/components/{p-9GuNIXuz.js.map → p-BknXYAEc.js.map} +1 -1
  119. package/dist/components/{p-BKx2ihnG.js → p-Bu3dOjpW.js} +2 -2
  120. package/dist/components/{p-CBa_6lqj.js → p-C-Y7gYgz.js} +2 -2
  121. package/dist/components/{p-9EQ3xLI1.js → p-C6R5Lft0.js} +2 -2
  122. package/dist/components/{p-DPR0TGGJ.js → p-C9gC4haA.js} +2 -2
  123. package/dist/components/{p-LHzBS95u.js → p-CAnnQL1F.js} +2 -2
  124. package/dist/components/p-CE2IKOsy.js +2 -0
  125. package/dist/components/{p-DaxKVtZG.js.map → p-CE2IKOsy.js.map} +1 -1
  126. package/dist/components/{p-DHzn7INS.js → p-CKVHrOEp.js} +2 -2
  127. package/dist/components/{p-CbZ0RU2c.js → p-CLVxgTme.js} +2 -2
  128. package/dist/components/p-CY4YFAT3.js +2 -0
  129. package/dist/components/p-CY4YFAT3.js.map +1 -0
  130. package/dist/components/{p-3WlVHGUe.js → p-Cg0kimXZ.js} +2 -2
  131. package/dist/components/p-CqQFowZd.js +2 -0
  132. package/dist/components/p-CqQFowZd.js.map +1 -0
  133. package/dist/components/{p-HZz5aJKz.js → p-CsBICYmt.js} +2 -2
  134. package/dist/components/p-CwjVgUqy.js +2 -0
  135. package/dist/components/p-CwjVgUqy.js.map +1 -0
  136. package/dist/components/{p-Dl3gciej.js → p-D123Jvp-.js} +2 -2
  137. package/dist/components/p-D1eEgwcP.js +2 -0
  138. package/dist/components/p-D1eEgwcP.js.map +1 -0
  139. package/dist/components/{p-oCc3mz_c.js → p-D3fMaUJ3.js} +2 -2
  140. package/dist/components/p-D7R4yE2C.js +2 -0
  141. package/dist/components/{p-Ba7DaFkb.js.map → p-D7R4yE2C.js.map} +1 -1
  142. package/dist/components/{p-B9ZfoprK.js → p-D7haeL5y.js} +2 -2
  143. package/dist/components/p-D7haeL5y.js.map +1 -0
  144. package/dist/components/{p-CHEt4mW8.js → p-DAvh38E4.js} +2 -2
  145. package/dist/components/{p-DPpHSIzS.js → p-DBue3ISk.js} +2 -2
  146. package/dist/components/{p-BdgoB2ef.js → p-DDgeXGbB.js} +2 -2
  147. package/dist/components/{p-Be-vZpWx.js → p-DUdOmSFR.js} +2 -2
  148. package/dist/components/p-DtndC9f1.js +2 -0
  149. package/dist/components/p-DtndC9f1.js.map +1 -0
  150. package/dist/components/{p-BTTxl3ro.js → p-H5T258T-.js} +2 -2
  151. package/dist/components/p-H5T258T-.js.map +1 -0
  152. package/dist/components/p-HB_cf_aR.js +2 -0
  153. package/dist/components/{p-BBmRD_S0.js.map → p-HB_cf_aR.js.map} +1 -1
  154. package/dist/components/p-LK-jpxwq.js +2 -0
  155. package/dist/components/p-LK-jpxwq.js.map +1 -0
  156. package/dist/components/p-ag6Io-kL.js +2 -0
  157. package/dist/components/p-ag6Io-kL.js.map +1 -0
  158. package/dist/components/p-iMuFgSoM.js +2 -0
  159. package/dist/components/p-iMuFgSoM.js.map +1 -0
  160. package/dist/components/{p-B145kX-i.js → p-uI6z53BE.js} +2 -2
  161. package/dist/docs/components.json +223 -37
  162. package/dist/lib/components/PanelTools/panel-tools-types.d.ts +12 -0
  163. package/dist/lib/shell/index.d.ts +1 -1
  164. package/dist/lib/shell/index.js.map +1 -1
  165. package/dist/lib/utils/anchored-position.d.ts +6 -2
  166. package/dist/mcp-data/registry/badge.json +7 -9
  167. package/dist/mcp-data/registry/banner.json +2 -2
  168. package/dist/mcp-data/registry/bar-action.json +2 -2
  169. package/dist/mcp-data/registry/button-unfilled.json +1 -1
  170. package/dist/mcp-data/registry/conversation-list.json +2 -2
  171. package/dist/mcp-data/registry/filter-menu.json +21 -6
  172. package/dist/mcp-data/registry/input.json +23 -3
  173. package/dist/mcp-data/registry/menu.json +22 -4
  174. package/dist/mcp-data/registry/message-composer.json +2 -2
  175. package/dist/mcp-data/registry/panel-nav.json +1 -1
  176. package/dist/mcp-data/registry/panel-tool-header.json +3 -3
  177. package/dist/mcp-data/registry/panel-tools.json +5 -4
  178. package/dist/mcp-data/registry/registry.json +43 -13
  179. package/dist/mcp-data/registry/select.json +17 -3
  180. package/dist/mcp-data/registry/shell-app.json +3 -2
  181. package/dist/mcp-data/registry/table.json +21 -1
  182. package/dist/mcp-data/registry/toast.json +2 -2
  183. package/dist/mcp-data/registry/tooltip.json +20 -3
  184. package/dist/mcp-data/registry/typing-indicator.json +4 -4
  185. package/dist/react/ds-panel-tool-header.d.ts +2 -5
  186. package/dist/react/ds-panel-tools.d.ts +2 -5
  187. package/dist/types/components/Badge/Badge.d.ts +2 -16
  188. package/dist/types/components/Banner/Banner.d.ts +0 -1
  189. package/dist/types/components/BarAction/BarAction.d.ts +0 -7
  190. package/dist/types/components/ConversationList/ConversationList.d.ts +1 -0
  191. package/dist/types/components/FilterMenu/FilterMenu.d.ts +5 -5
  192. package/dist/types/components/Input/Input.d.ts +7 -0
  193. package/dist/types/components/Menu/Menu.d.ts +3 -4
  194. package/dist/types/components/MessageComposer/MessageComposer.d.ts +0 -3
  195. package/dist/types/components/PanelToolHeader/PanelToolHeader.d.ts +2 -5
  196. package/dist/types/components/PanelTools/PanelTools.d.ts +2 -5
  197. package/dist/types/components/PanelTools/panel-tools-types.d.ts +12 -0
  198. package/dist/types/components/Select/Select.d.ts +3 -0
  199. package/dist/types/components/Table/Table.d.ts +1 -13
  200. package/dist/types/components/Table/table-body-renderer.d.ts +36 -0
  201. package/dist/types/components/Table/table-load-view.d.ts +17 -0
  202. package/dist/types/components/Table/table-row-view.d.ts +26 -0
  203. package/dist/types/components/Table/table-skeleton-view.d.ts +12 -0
  204. package/dist/types/components/Tooltip/Tooltip.d.ts +3 -0
  205. package/dist/types/components.d.ts +61 -23
  206. package/dist/types/shell/index.d.ts +1 -1
  207. package/dist/types/utils/anchored-overlay-boundary.d.ts +14 -0
  208. package/dist/types/utils/anchored-overlay-interaction-controller.d.ts +26 -0
  209. package/dist/types/utils/anchored-position-controller.d.ts +14 -0
  210. package/dist/types/utils/anchored-position.d.ts +6 -2
  211. package/dist/types/utils/select-controller.d.ts +1 -0
  212. package/dist/vue/ds-filter-menu.js +1 -0
  213. package/dist/vue/ds-input.js +1 -0
  214. package/dist/vue/ds-menu.js +1 -0
  215. package/dist/vue/ds-select.js +1 -0
  216. package/dist/vue/ds-tooltip.js +1 -0
  217. package/package.json +4 -1
  218. package/dist/components/p-9GuNIXuz.js +0 -2
  219. package/dist/components/p-B9ZfoprK.js.map +0 -1
  220. package/dist/components/p-BBmRD_S0.js +0 -2
  221. package/dist/components/p-BTTxl3ro.js.map +0 -1
  222. package/dist/components/p-Ba7DaFkb.js +0 -2
  223. package/dist/components/p-BcNYvLMi.js +0 -2
  224. package/dist/components/p-BcNYvLMi.js.map +0 -1
  225. package/dist/components/p-CI5Ce8VF.js +0 -2
  226. package/dist/components/p-CI5Ce8VF.js.map +0 -1
  227. package/dist/components/p-Cgm7YMLw.js +0 -2
  228. package/dist/components/p-Cgm7YMLw.js.map +0 -1
  229. package/dist/components/p-Cj5Hkt6Y.js +0 -2
  230. package/dist/components/p-Cj5Hkt6Y.js.map +0 -1
  231. package/dist/components/p-DC_8dK55.js +0 -2
  232. package/dist/components/p-DC_8dK55.js.map +0 -1
  233. package/dist/components/p-DKY3IJqt.js +0 -2
  234. package/dist/components/p-DKY3IJqt.js.map +0 -1
  235. package/dist/components/p-DMaQkEYP.js +0 -2
  236. package/dist/components/p-DQyvAN44.js +0 -2
  237. package/dist/components/p-DQyvAN44.js.map +0 -1
  238. package/dist/components/p-DaxKVtZG.js +0 -2
  239. package/dist/components/p-DbJahF86.js +0 -2
  240. package/dist/components/p-DbJahF86.js.map +0 -1
  241. package/dist/components/p-DhKvqh5G.js +0 -2
  242. package/dist/components/p-DhKvqh5G.js.map +0 -1
  243. package/dist/components/p-Drcl4zS9.js +0 -2
  244. package/dist/components/p-DzPw6aDK.js +0 -2
  245. package/dist/components/p-DzPw6aDK.js.map +0 -1
  246. package/dist/types/shell/badge-gradient-ring.d.ts +0 -28
  247. /package/dist/components/{p-Dginv8PT.js.map → p-1Hu9jlju.js.map} +0 -0
  248. /package/dist/components/{p-BiBXCjjo.js.map → p-B00PEE6t.js.map} +0 -0
  249. /package/dist/components/{p-C6yLERx6.js.map → p-B1JRCDHS.js.map} +0 -0
  250. /package/dist/components/{p-Dd8VKdAO.js.map → p-BCOl1x0_.js.map} +0 -0
  251. /package/dist/components/{p-C9StMCWg.js.map → p-BHVOiWz5.js.map} +0 -0
  252. /package/dist/components/{p-DJ0GCCFa.js.map → p-BNkU9dcc.js.map} +0 -0
  253. /package/dist/components/{p-hp_2W2Qy.js.map → p-BXN-e7vF.js.map} +0 -0
  254. /package/dist/components/{p-CihNoe1M.js.map → p-B_jZIC9R.js.map} +0 -0
  255. /package/dist/components/{p-CAJIARNW.js.map → p-Bbb9A3NV.js.map} +0 -0
  256. /package/dist/components/{p-jk2-y5dH.js.map → p-Bece5KH3.js.map} +0 -0
  257. /package/dist/components/{p-D6P_1YrL.js.map → p-BhYS_Ump.js.map} +0 -0
  258. /package/dist/components/{p-BKx2ihnG.js.map → p-Bu3dOjpW.js.map} +0 -0
  259. /package/dist/components/{p-CBa_6lqj.js.map → p-C-Y7gYgz.js.map} +0 -0
  260. /package/dist/components/{p-9EQ3xLI1.js.map → p-C6R5Lft0.js.map} +0 -0
  261. /package/dist/components/{p-DPR0TGGJ.js.map → p-C9gC4haA.js.map} +0 -0
  262. /package/dist/components/{p-LHzBS95u.js.map → p-CAnnQL1F.js.map} +0 -0
  263. /package/dist/components/{p-DHzn7INS.js.map → p-CKVHrOEp.js.map} +0 -0
  264. /package/dist/components/{p-CbZ0RU2c.js.map → p-CLVxgTme.js.map} +0 -0
  265. /package/dist/components/{p-3WlVHGUe.js.map → p-Cg0kimXZ.js.map} +0 -0
  266. /package/dist/components/{p-HZz5aJKz.js.map → p-CsBICYmt.js.map} +0 -0
  267. /package/dist/components/{p-Dl3gciej.js.map → p-D123Jvp-.js.map} +0 -0
  268. /package/dist/components/{p-oCc3mz_c.js.map → p-D3fMaUJ3.js.map} +0 -0
  269. /package/dist/components/{p-CHEt4mW8.js.map → p-DAvh38E4.js.map} +0 -0
  270. /package/dist/components/{p-DPpHSIzS.js.map → p-DBue3ISk.js.map} +0 -0
  271. /package/dist/components/{p-BdgoB2ef.js.map → p-DDgeXGbB.js.map} +0 -0
  272. /package/dist/components/{p-Be-vZpWx.js.map → p-DUdOmSFR.js.map} +0 -0
  273. /package/dist/components/{p-B145kX-i.js.map → p-uI6z53BE.js.map} +0 -0
@@ -78,6 +78,7 @@
78
78
  "If an active optional tool becomes unavailable, PanelTools closes the drawer, clears activeTool, and emits its deselection.",
79
79
  "Use isInactive only for a temporarily unavailable visible tool; omit tools the user is not authorized to access.",
80
80
  "Rail actions disable physical press scaling so notification-dot halos remain aligned to the shell gradient; hover and pressed washes continue to paint above the dot and halo.",
81
+ "Rail tooltips are shell-owned and position against the viewport so the narrow tools-lane collision boundary cannot clamp them beyond the viewport edge.",
81
82
  "The application owns slotted tool products and data while PanelTools owns rail ordering, drawer selection, open state, motion, and shell events.",
82
83
  "Opening and closing clip an already-rendered fixed-width surface. Header and tool content remain fully opaque and retained during width motion; transition completion is accepted only at the measured terminal clip width, after which the closed surface becomes inert and paint-skipped.",
83
84
  "Entering and exiting fullscreen snaps without borrowing the drawer open or close animation.",
@@ -220,7 +221,7 @@
220
221
  },
221
222
  {
222
223
  "name": "dsHeaderAction",
223
- "detail": "{\n tool: PanelToolsToolId;\n id: string;\n }",
224
+ "detail": "PanelToolsHeaderActionDetail",
224
225
  "bubbles": true,
225
226
  "cancelable": true,
226
227
  "composed": true,
@@ -367,7 +368,7 @@
367
368
  },
368
369
  {
369
370
  "name": "dsHeaderAction",
370
- "detail": "{\n tool: PanelToolsToolId;\n id: string;\n }",
371
+ "detail": "PanelToolsHeaderActionDetail",
371
372
  "bubbles": true,
372
373
  "cancelable": true,
373
374
  "composed": true,
@@ -468,12 +469,12 @@
468
469
  },
469
470
  {
470
471
  "path": "src/wc/components/PanelTools/PanelTools.tsx",
471
- "content": "import {\n Component,\n Prop,\n Event,\n EventEmitter,\n Element,\n State,\n Watch,\n Method,\n Listen,\n h,\n Host,\n} from '@stencil/core';\nimport type { ChromeTransitionDetail } from '../../shell/chrome-transition';\nimport {\n PANEL_TOOLS_LABELS,\n PANEL_TOOLS_SHORTCUTS,\n type PanelToolsHeaderAction,\n type PanelToolsHeaderConfig,\n type PanelToolsHeaders,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from './panel-tools-types';\nimport {\n isPanelToolsToolId,\n orderPanelToolsItems,\n panelToolsDrawerAtTerminal,\n panelToolsDrawerResting,\n panelToolsDrawerTransitionMs,\n panelToolsRailPlacement,\n reconcilePanelToolsAvailability,\n resolvePanelToolActivation,\n} from './panel-tools-utils';\n\n@Component({\n tag: 'ds-panel-tools',\n styleUrl: 'PanelTools.css',\n shadow: true,\n})\nexport class PanelTools {\n @Element() el!: HTMLElement;\n\n /** When false, only the icon rail is shown. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n\n /** Drawer presentation or viewport-covering presentation for the active tool. */\n @Prop({ mutable: true, reflect: true }) presentation: 'drawer' | 'fullscreen' = 'drawer';\n\n /** Let a fullscreen product supply independent master/detail headers inside its view. */\n @Prop({ attribute: 'fullscreen-header-mode', reflect: true })\n fullscreenHeaderMode: 'shared' | 'split' = 'shared';\n\n /** Active tool view — `search`, `agents`, `messages`, `stacks`, `activity`, or `help`. */\n @Prop({ mutable: true, attribute: 'active-tool', reflect: true })\n activeTool: PanelToolsToolId | '' = '';\n\n /**\n * Rail items rendered in the right column.\n * Set via JS property: `el.items = [...]`. Replace the array reference to update.\n */\n @Prop() items: PanelToolsItem[] = [];\n\n @Prop() toolsLabel: string = 'Tools';\n @Prop() toolShortcutsLabel: string = 'Tool shortcuts';\n\n /** Active header state per tool. Replace the object when title, depth, or actions change. */\n @Prop() headers: PanelToolsHeaders = {};\n\n /**\n * Optional localStorage key for the last active tool. The drawer always starts\n * closed; only the tool identity is restored for continuity within this browser.\n */\n @Prop({ attribute: 'storage-key' }) storageKey: string = '';\n\n /** Emitted when a rail button is toggled. Detail = { id, selected }. */\n @Event({ bubbles: true, composed: true }) dsToolChange!: EventEmitter<{\n id: PanelToolsToolId;\n selected: boolean;\n }>;\n\n /** Emitted when fullscreen changes, including Escape-initiated exits. */\n @Event({ bubbles: true, composed: true }) dsPresentationChange!: EventEmitter<{\n presentation: 'drawer' | 'fullscreen';\n }>;\n\n /** Requests navigation to the active tool's parent view. */\n @Event({ bubbles: true, composed: true }) dsHeaderBack!: EventEmitter<{\n tool: PanelToolsToolId;\n }>;\n\n /** Requests one application-owned action from the active tool header. */\n @Event({ bubbles: true, composed: true }) dsHeaderAction!: EventEmitter<{\n tool: PanelToolsToolId;\n id: string;\n }>;\n\n /** Bubbling lifecycle — `ds-bar-nav` defers overflow checks during drawer motion. */\n @Event({ bubbles: true, composed: true })\n dsChromeTransitionStart!: EventEmitter<ChromeTransitionDetail>;\n\n @Event({ bubbles: true, composed: true })\n dsChromeTransitionEnd!: EventEmitter<ChromeTransitionDetail>;\n\n /** Arms open vs close easing for the in-flight width transition. */\n @State() private motion: 'opening' | 'closing' | 'idle' = 'idle';\n\n /** Keeps header and slotted content painted until the closing clip transition completes. */\n @State() private drawerSurfaceRetained = false;\n\n /** Suppresses width transition until the host has painted its initial open state. */\n @State() private readyForMotion = false;\n\n /** Fullscreen presentation changes snap without borrowing drawer width motion. */\n @State() private presentationMotionSuppressed = false;\n\n @State() private rovingIndex = 0;\n @State() private fullViewToolIds: PanelToolsToolId[] = [];\n @State() private railBodyScrollable = false;\n\n private motionEnableGeneration = 0;\n private presentationMotionGeneration = 0;\n private transitionGeneration = 0;\n private transitionFallbackTimer: number | null = null;\n /** Ignores the old transition's terminal event when reversing direction. */\n private ignoreReplacementTerminal = false;\n private focusBeforeFullscreen: HTMLElement | null = null;\n private fullViewObserver: MutationObserver | null = null;\n private railBodyEl?: HTMLElement;\n private railBodyObserver?: ResizeObserver;\n\n private get railItems(): PanelToolsItem[] {\n return this.items ?? [];\n }\n\n private get orderedRailItems(): PanelToolsItem[] {\n return orderPanelToolsItems(this.railItems);\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('transitionend', this.handleTransitionEnd);\n this.el.removeEventListener('transitioncancel', this.handleTransitionEnd);\n this.el.removeEventListener('dsAction', this.handleComposedHeaderAction);\n this.clearTransitionCompletion();\n this.motionEnableGeneration += 1;\n this.fullViewObserver?.disconnect();\n this.fullViewObserver = null;\n this.disconnectRailBodyObserver();\n }\n\n @Watch('presentation')\n presentationChanged(next: 'drawer' | 'fullscreen', previous?: 'drawer' | 'fullscreen') {\n if (next === previous) return;\n // Framework property bindings can update slotted master/detail content in\n // the same render that requests fullscreen. Reflect the host selector\n // immediately so that content cannot paint once in the drawer geometry\n // while Stencil waits to commit its next render.\n if (this.el.getAttribute('presentation') !== next) {\n this.el.setAttribute('presentation', next);\n }\n const presentationGeneration = ++this.presentationMotionGeneration;\n this.presentationMotionSuppressed = true;\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (presentationGeneration !== this.presentationMotionGeneration) return;\n this.presentationMotionSuppressed = false;\n });\n });\n if (next === 'fullscreen') {\n this.focusBeforeFullscreen =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.open = true;\n } else if (previous === 'fullscreen') {\n const focusBeforeFullscreen = this.focusBeforeFullscreen;\n const fallbackActionId = this.activeHeader().actions?.[0]?.id;\n requestAnimationFrame(async () => {\n if (focusBeforeFullscreen?.isConnected) {\n focusBeforeFullscreen.focus({ preventScroll: true });\n }\n const focusRestored = focusBeforeFullscreen\n ? document.activeElement === focusBeforeFullscreen ||\n focusBeforeFullscreen.contains(document.activeElement)\n : false;\n if (!focusRestored && fallbackActionId) await this.focusHeaderAction(fallbackActionId);\n this.focusBeforeFullscreen = null;\n });\n }\n this.dsPresentationChange.emit({ presentation: next });\n }\n\n @Listen('keydown', { target: 'window', capture: true })\n handleFullscreenEscape(event: KeyboardEvent) {\n if (this.presentation !== 'fullscreen' || event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n this.presentation = 'drawer';\n }\n\n private deferMotionEnable() {\n if (this.readyForMotion) return;\n const generation = ++this.motionEnableGeneration;\n requestAnimationFrame(() => {\n if (generation !== this.motionEnableGeneration) return;\n requestAnimationFrame(() => {\n if (generation !== this.motionEnableGeneration) return;\n this.readyForMotion = true;\n });\n });\n }\n\n connectedCallback() {\n this.el.addEventListener('transitionend', this.handleTransitionEnd);\n this.el.addEventListener('transitioncancel', this.handleTransitionEnd);\n this.el.addEventListener('dsAction', this.handleComposedHeaderAction);\n }\n\n private handleComposedHeaderAction = (event: Event) => {\n const detail = (event as CustomEvent<{ id?: string }>).detail;\n if (!this.activeTool || !detail?.id) return;\n event.stopPropagation();\n this.dsHeaderAction.emit({ tool: this.activeTool, id: detail.id });\n };\n\n componentWillLoad() {\n this.restoreLastActiveTool();\n this.drawerSurfaceRetained = this.open;\n }\n\n @Watch('open')\n openChanged(isOpen: boolean, wasOpen?: boolean) {\n if (isOpen) this.drawerSurfaceRetained = true;\n if (this.readyForMotion && wasOpen !== undefined && wasOpen !== isOpen) {\n this.startDrawerTransition(isOpen ? 'opening' : 'closing');\n } else if (!isOpen) {\n this.drawerSurfaceRetained = false;\n }\n this.deferMotionEnable();\n }\n\n @Watch('activeTool')\n activeToolChanged() {\n this.persistLastActiveTool();\n this.deferMotionEnable();\n }\n\n @Watch('storageKey')\n storageKeyChanged() {\n this.restoreLastActiveTool();\n }\n\n componentDidLoad() {\n this.deferMotionEnable();\n this.connectRailBodyObserver();\n this.updateRailBodyScrollable();\n this.syncFullViewTools();\n this.fullViewObserver = new MutationObserver(() => this.syncFullViewTools());\n this.fullViewObserver.observe(this.el, {\n childList: true,\n attributes: true,\n attributeFilter: ['slot'],\n });\n }\n\n componentDidRender() {\n this.updateRailBodyScrollable();\n }\n\n private connectRailBodyObserver() {\n this.disconnectRailBodyObserver();\n if (!this.railBodyEl || typeof ResizeObserver === 'undefined') return;\n this.railBodyObserver = new ResizeObserver(() => this.updateRailBodyScrollable());\n this.railBodyObserver.observe(this.railBodyEl);\n }\n\n private disconnectRailBodyObserver() {\n this.railBodyObserver?.disconnect();\n this.railBodyObserver = undefined;\n }\n\n private updateRailBodyScrollable() {\n const next = Boolean(\n this.railBodyEl && this.railBodyEl.scrollHeight > this.railBodyEl.clientHeight + 1\n );\n if (next !== this.railBodyScrollable) this.railBodyScrollable = next;\n }\n\n @Watch('items')\n itemsChanged() {\n this.rovingIndex = 0;\n this.reconcileActiveTool();\n this.deferMotionEnable();\n }\n\n private restoreLastActiveTool() {\n if (!this.storageKey || this.activeTool) return;\n try {\n const stored = localStorage.getItem(this.storageKey);\n if (isPanelToolsToolId(stored)) this.activeTool = stored;\n } catch {\n /* localStorage unavailable */\n }\n }\n\n private persistLastActiveTool() {\n if (!this.storageKey || !this.activeTool) return;\n try {\n localStorage.setItem(this.storageKey, this.activeTool);\n } catch {\n /* unavailable */\n }\n }\n\n private clearPersistedTool() {\n if (!this.storageKey) return;\n try {\n localStorage.removeItem(this.storageKey);\n } catch {\n /* unavailable */\n }\n }\n\n private reconcileActiveTool() {\n const next = reconcilePanelToolsAvailability(this.railItems, this.open, this.activeTool);\n if (!next.removedTool) return;\n this.clearPersistedTool();\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id: next.removedTool, selected: false });\n }\n\n private startDrawerTransition(phase: 'opening' | 'closing') {\n const drawer = this.renderRoot.querySelector('.panel-tools__drawer') as HTMLElement | null;\n if (this.motion !== 'idle') {\n this.finishDrawerTransition();\n this.ignoreReplacementTerminal = true;\n }\n this.motion = phase;\n this.dsChromeTransitionStart.emit({ source: 'panel-tools', phase });\n const generation = ++this.transitionGeneration;\n\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (generation !== this.transitionGeneration || this.motion !== phase) return;\n if (!drawer) {\n this.finishDrawerTransition();\n return;\n }\n\n if (this.drawerTransitionAtTerminal(drawer, phase)) {\n this.finishDrawerTransition();\n return;\n }\n\n const fallbackMs = panelToolsDrawerTransitionMs(getComputedStyle(drawer));\n if (fallbackMs <= 0) {\n this.finishDrawerTransition();\n return;\n }\n this.transitionFallbackTimer = window.setTimeout(() => {\n this.finishDrawerTransition();\n }, fallbackMs);\n });\n });\n }\n\n private handleTransitionEnd = (event: TransitionEvent) => {\n const drawer = this.renderRoot.querySelector('.panel-tools__drawer') as HTMLElement | null;\n if (event.target !== drawer) return;\n if (event.propertyName !== 'max-width') return;\n if (this.ignoreReplacementTerminal) {\n this.ignoreReplacementTerminal = false;\n return;\n }\n // Changing from the neutral transition recipe to the direction-specific\n // easing can cancel the transition that the reflected `open` class began.\n // The replacement transition and its watchdog still own completion; ending\n // motion here would hide the fixed-width tool surface while it is visible.\n if (event.type === 'transitioncancel') return;\n if (drawer && !this.drawerTransitionAtTerminal(drawer, this.motion)) return;\n this.finishDrawerTransition();\n };\n\n private drawerTransitionAtTerminal(\n drawer: HTMLElement,\n phase: 'opening' | 'closing' | 'idle'\n ): boolean {\n const width = drawer.getBoundingClientRect().width;\n const surface = drawer.querySelector('.panel-tools__drawer-surface') as HTMLElement | null;\n const targetWidth = surface?.getBoundingClientRect().width ?? 0;\n return panelToolsDrawerAtTerminal(width, targetWidth, phase);\n }\n\n private finishDrawerTransition() {\n const completedPhase = this.motion;\n const wasTransitioning = completedPhase !== 'idle';\n this.clearTransitionCompletion();\n if (!wasTransitioning) return;\n this.ignoreReplacementTerminal = false;\n this.motion = 'idle';\n if (completedPhase === 'closing' && !this.open) {\n this.drawerSurfaceRetained = false;\n }\n this.dsChromeTransitionEnd.emit({ source: 'panel-tools' });\n }\n\n private clearTransitionCompletion() {\n this.transitionGeneration += 1;\n if (this.transitionFallbackTimer !== null) {\n window.clearTimeout(this.transitionFallbackTimer);\n this.transitionFallbackTimer = null;\n }\n }\n\n /** Rail selection follows `open` immediately — independent of the slide animation. */\n private isRailSelected(id: PanelToolsToolId): boolean {\n return this.open && this.activeTool === id;\n }\n\n /** Drawer body stays mounted while the clip frame animates closed. */\n private isDrawerPresent(): boolean {\n return this.open || this.drawerSurfaceRetained;\n }\n\n private isViewActive(id: PanelToolsToolId): boolean {\n return this.isDrawerPresent() && this.activeTool === id;\n }\n\n private hasFullView(id: PanelToolsToolId): boolean {\n return this.fullViewToolIds.includes(id);\n }\n\n private syncFullViewTools() {\n const ids = this.orderedRailItems.map(item => item.id);\n const next = ids.filter(id => Boolean(this.el.querySelector(`[slot=\"${id}-view\"]`)));\n if (next.join('|') !== this.fullViewToolIds.join('|')) this.fullViewToolIds = next;\n }\n\n private get renderRoot(): ParentNode {\n return this.el.shadowRoot ?? this.el;\n }\n\n private headerLabel(): string {\n if (!this.isDrawerPresent() || !this.activeTool) return '';\n const item = this.railItems.find(candidate => candidate.id === this.activeTool);\n return (\n item?.label ??\n PANEL_TOOLS_LABELS[this.activeTool as keyof typeof PANEL_TOOLS_LABELS] ??\n this.activeTool\n );\n }\n\n private resolvedHeaders(): PanelToolsHeaders {\n return this.headers ?? {};\n }\n\n private activeHeader(): PanelToolsHeaderConfig {\n if (!this.activeTool) return {};\n return this.resolvedHeaders()[this.activeTool] ?? {};\n }\n\n private handleToolChange = (id: PanelToolsToolId) => {\n const next = resolvePanelToolActivation(this.open, this.activeTool, id);\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id, selected: next.selected });\n };\n\n /** Toggle any rail tool open/closed — shell shortcuts K/A/S/M/N call this. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const item = this.railItems.find(entry => entry.id === id);\n if (!item || item.isInactive) return;\n this.handleToolChange(id);\n }\n\n /** Close the tools drawer when open — used by shell keyboard shortcuts. */\n @Method()\n async closeDrawer() {\n if (!this.open) return;\n const id = this.activeTool;\n this.open = false;\n if (id) {\n this.dsToolChange.emit({ id, selected: false });\n }\n }\n\n /** Focus an active tool-header action by its application-owned id. */\n @Method()\n async focusHeaderAction(id: string) {\n const action = this.renderRoot.querySelector(`[data-header-action-id=\"${CSS.escape(id)}\"]`) as\n | (HTMLElement & { setFocus?: () => Promise<void> })\n | null;\n await action?.setFocus?.();\n }\n\n private focusRailAt(index: number) {\n const items = this.orderedRailItems;\n if (!items.length) return;\n const bounded = Math.max(0, Math.min(index, items.length - 1));\n if (bounded === this.rovingIndex) return;\n this.rovingIndex = bounded;\n const actions = Array.from(\n this.renderRoot.querySelectorAll<HTMLElement>('.panel-tools__rail-action .button-icon')\n );\n actions[bounded]?.focus({ preventScroll: true });\n }\n\n private handleRailKeyDown = (e: KeyboardEvent, index: number) => {\n const items = this.orderedRailItems;\n if (!items.length) return;\n\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.handleToolChange(items[index].id);\n return;\n }\n\n if (e.key === 'ArrowDown') {\n if (index >= items.length - 1) return;\n e.preventDefault();\n this.focusRailAt(index + 1);\n return;\n }\n\n if (e.key === 'ArrowUp') {\n if (index <= 0) return;\n e.preventDefault();\n this.focusRailAt(index - 1);\n }\n };\n\n private renderRailAction(item: PanelToolsItem, index: number) {\n const label =\n item.ariaLabel ??\n item.label ??\n PANEL_TOOLS_LABELS[item.id as keyof typeof PANEL_TOOLS_LABELS] ??\n item.id;\n return (\n <ds-tooltip\n key={item.id}\n label={label}\n shortcutKey={\n item.shortcutKey ??\n PANEL_TOOLS_SHORTCUTS[item.id as keyof typeof PANEL_TOOLS_SHORTCUTS]\n }\n side=\"left\"\n size=\"sm\"\n >\n <ds-button-unfilled\n variant=\"icon\"\n class=\"panel-tools__rail-action\"\n icon={item.icon}\n isActive={this.isRailSelected(item.id)}\n activeFill={false}\n hasBorder={false}\n pressScale={false}\n dot={item.dot ?? false}\n isInactive={item.isInactive}\n focusTabIndex={index === this.rovingIndex ? 0 : -1}\n aria-label={label}\n pressed={this.isRailSelected(item.id)}\n onFocusin={() => {\n this.rovingIndex = index;\n }}\n onKeyDown={(e: KeyboardEvent) => this.handleRailKeyDown(e, index)}\n onDsClick={() => this.handleToolChange(item.id)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const headerLabel = this.headerLabel();\n const header = this.activeHeader();\n const headerTitle = header.title?.trim() || headerLabel;\n const headerActions = header.actions ?? [];\n const orderedRailItems = this.orderedRailItems;\n const headerItems = orderedRailItems.filter(\n item => panelToolsRailPlacement(item) === 'header'\n );\n const bodyItems = orderedRailItems.filter(\n item => panelToolsRailPlacement(item) === 'body'\n );\n const footerItems = orderedRailItems.filter(\n item => panelToolsRailPlacement(item) === 'footer'\n );\n const headerCount = headerItems.length;\n const footerStart = headerCount + bodyItems.length;\n const showDrawerChrome = this.isDrawerPresent();\n const drawerResting = panelToolsDrawerResting(this.open, this.motion);\n const activeFullView = Boolean(\n showDrawerChrome && this.activeTool && this.hasFullView(this.activeTool)\n );\n const toolIds = orderedRailItems.map(item => item.id);\n\n return (\n <Host\n class={{\n 'panel-tools': true,\n 'panel-tools--open': this.open,\n 'panel-tools--ready': this.readyForMotion,\n 'panel-tools--motion-opening': this.motion === 'opening',\n 'panel-tools--motion-closing': this.motion === 'closing',\n 'panel-tools--drawer-resting': drawerResting,\n 'panel-tools--fullscreen': this.presentation === 'fullscreen',\n 'panel-tools--presentation-snap': this.presentationMotionSuppressed,\n }}\n role={this.presentation === 'fullscreen' ? 'dialog' : 'complementary'}\n aria-modal={this.presentation === 'fullscreen' ? 'true' : undefined}\n aria-label={this.toolsLabel}\n >\n <div class=\"panel-tools__layout\">\n <nav class=\"panel-tools__rail\" aria-label={this.toolShortcutsLabel}>\n {headerItems.length ? (\n <div class=\"panel-tools__rail-header ds-chrome-row ds-chrome-space--md\">\n {headerItems.map((item, index) => this.renderRailAction(item, index))}\n </div>\n ) : null}\n <div\n class={{\n 'panel-tools__rail-body': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--md': true,\n 'ds-scrollbar-hidden': true,\n 'ds-focus-ring': this.railBodyScrollable,\n }}\n ref={element => {\n const next = (element as HTMLElement) ?? undefined;\n if (next === this.railBodyEl) return;\n this.railBodyEl = next;\n this.connectRailBodyObserver();\n }}\n role={this.railBodyScrollable ? 'region' : undefined}\n aria-label={this.railBodyScrollable ? this.toolShortcutsLabel : undefined}\n tabIndex={this.railBodyScrollable ? 0 : undefined}\n >\n <div class=\"panel-tools__rail-actions\">\n {bodyItems.map((item, bodyIdx) =>\n this.renderRailAction(item, headerCount + bodyIdx)\n )}\n </div>\n </div>\n {footerItems.length ? (\n <div class=\"panel-tools__rail-footer ds-chrome-row ds-chrome-space--md\">\n {footerItems.map((item, footerIdx) =>\n this.renderRailAction(item, footerStart + footerIdx)\n )}\n </div>\n ) : null}\n </nav>\n\n <div\n class={{\n 'panel-tools__drawer': true,\n 'panel-tools__drawer--visible': showDrawerChrome,\n }}\n aria-hidden={showDrawerChrome ? null : 'true'}\n inert={showDrawerChrome ? undefined : true}\n >\n <div class=\"panel-tools__drawer-surface\">\n {this.presentation !== 'fullscreen' || this.fullscreenHeaderMode === 'shared' ? (\n <ds-panel-tool-header\n class=\"panel-tools__header\"\n heading={headerTitle}\n showBack={header.showBack ?? false}\n backIcon={header.backIcon || 'ChevronLeft'}\n backAriaLabel={header.backAriaLabel || 'Back'}\n showMenu={false}\n ref={element => {\n const toolHeader = element as\n | (HTMLDsPanelToolHeaderElement & {\n actions: PanelToolsHeaderAction[];\n })\n | undefined;\n if (toolHeader) toolHeader.actions = headerActions;\n }}\n onDsBack={() => {\n if (this.activeTool) this.dsHeaderBack.emit({ tool: this.activeTool });\n }}\n />\n ) : null}\n <div class=\"panel-tools__body\" hidden={activeFullView}>\n {toolIds.map(id => {\n const active = this.isViewActive(id);\n return (\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': active,\n }}\n hidden={!active}\n >\n <slot name={id} />\n </div>\n );\n })}\n </div>\n <div class=\"panel-tools__full-views\" hidden={!activeFullView}>\n {toolIds.map(id => (\n <div\n class={{\n 'panel-tools__full-view': true,\n 'panel-tools__full-view--active':\n this.isViewActive(id) && this.hasFullView(id),\n }}\n hidden={!(this.isViewActive(id) && this.hasFullView(id))}\n >\n <slot name={`${id}-view`} />\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
472
+ "content": "import {\n Component,\n Prop,\n Event,\n EventEmitter,\n Element,\n State,\n Watch,\n Method,\n Listen,\n h,\n Host,\n} from '@stencil/core';\nimport type { ChromeTransitionDetail } from '../../shell/chrome-transition';\nimport {\n PANEL_TOOLS_LABELS,\n PANEL_TOOLS_SHORTCUTS,\n type PanelToolsHeaderAction,\n type PanelToolsHeaderActionDetail,\n type PanelToolsHeaderConfig,\n type PanelToolsHeaders,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from './panel-tools-types';\nimport {\n isPanelToolsToolId,\n orderPanelToolsItems,\n panelToolsDrawerAtTerminal,\n panelToolsDrawerResting,\n panelToolsDrawerTransitionMs,\n panelToolsRailPlacement,\n reconcilePanelToolsAvailability,\n resolvePanelToolActivation,\n} from './panel-tools-utils';\n\n@Component({\n tag: 'ds-panel-tools',\n styleUrl: 'PanelTools.css',\n shadow: true,\n})\nexport class PanelTools {\n @Element() el!: HTMLElement;\n\n /** When false, only the icon rail is shown. */\n @Prop({ mutable: true, reflect: true }) open: boolean = false;\n\n /** Drawer presentation or viewport-covering presentation for the active tool. */\n @Prop({ mutable: true, reflect: true }) presentation: 'drawer' | 'fullscreen' = 'drawer';\n\n /** Let a fullscreen product supply independent master/detail headers inside its view. */\n @Prop({ attribute: 'fullscreen-header-mode', reflect: true })\n fullscreenHeaderMode: 'shared' | 'split' = 'shared';\n\n /** Active tool view — `search`, `agents`, `messages`, `stacks`, `activity`, or `help`. */\n @Prop({ mutable: true, attribute: 'active-tool', reflect: true })\n activeTool: PanelToolsToolId | '' = '';\n\n /**\n * Rail items rendered in the right column.\n * Set via JS property: `el.items = [...]`. Replace the array reference to update.\n */\n @Prop() items: PanelToolsItem[] = [];\n\n @Prop() toolsLabel: string = 'Tools';\n @Prop() toolShortcutsLabel: string = 'Tool shortcuts';\n\n /** Active header state per tool. Replace the object when title, depth, or actions change. */\n @Prop() headers: PanelToolsHeaders = {};\n\n /**\n * Optional localStorage key for the last active tool. The drawer always starts\n * closed; only the tool identity is restored for continuity within this browser.\n */\n @Prop({ attribute: 'storage-key' }) storageKey: string = '';\n\n /** Emitted when a rail button is toggled. Detail = { id, selected }. */\n @Event({ bubbles: true, composed: true }) dsToolChange!: EventEmitter<{\n id: PanelToolsToolId;\n selected: boolean;\n }>;\n\n /** Emitted when fullscreen changes, including Escape-initiated exits. */\n @Event({ bubbles: true, composed: true }) dsPresentationChange!: EventEmitter<{\n presentation: 'drawer' | 'fullscreen';\n }>;\n\n /** Requests navigation to the active tool's parent view. */\n @Event({ bubbles: true, composed: true }) dsHeaderBack!: EventEmitter<{\n tool: PanelToolsToolId;\n }>;\n\n /** Requests one application-owned action from the active tool header. */\n @Event({ bubbles: true, composed: true })\n dsHeaderAction!: EventEmitter<PanelToolsHeaderActionDetail>;\n\n /** Bubbling lifecycle — `ds-bar-nav` defers overflow checks during drawer motion. */\n @Event({ bubbles: true, composed: true })\n dsChromeTransitionStart!: EventEmitter<ChromeTransitionDetail>;\n\n @Event({ bubbles: true, composed: true })\n dsChromeTransitionEnd!: EventEmitter<ChromeTransitionDetail>;\n\n /** Arms open vs close easing for the in-flight width transition. */\n @State() private motion: 'opening' | 'closing' | 'idle' = 'idle';\n\n /** Keeps header and slotted content painted until the closing clip transition completes. */\n @State() private drawerSurfaceRetained = false;\n\n /** Suppresses width transition until the host has painted its initial open state. */\n @State() private readyForMotion = false;\n\n /** Fullscreen presentation changes snap without borrowing drawer width motion. */\n @State() private presentationMotionSuppressed = false;\n\n @State() private rovingIndex = 0;\n @State() private fullViewToolIds: PanelToolsToolId[] = [];\n @State() private railBodyScrollable = false;\n\n private motionEnableGeneration = 0;\n private presentationMotionGeneration = 0;\n private transitionGeneration = 0;\n private transitionFallbackTimer: number | null = null;\n /** Ignores the old transition's terminal event when reversing direction. */\n private ignoreReplacementTerminal = false;\n private focusBeforeFullscreen: HTMLElement | null = null;\n private fullViewObserver: MutationObserver | null = null;\n private railBodyEl?: HTMLElement;\n private railBodyObserver?: ResizeObserver;\n\n private get railItems(): PanelToolsItem[] {\n return this.items ?? [];\n }\n\n private get orderedRailItems(): PanelToolsItem[] {\n return orderPanelToolsItems(this.railItems);\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('transitionend', this.handleTransitionEnd);\n this.el.removeEventListener('transitioncancel', this.handleTransitionEnd);\n this.el.removeEventListener('dsAction', this.handleComposedHeaderAction);\n this.clearTransitionCompletion();\n this.motionEnableGeneration += 1;\n this.fullViewObserver?.disconnect();\n this.fullViewObserver = null;\n this.disconnectRailBodyObserver();\n }\n\n @Watch('presentation')\n presentationChanged(next: 'drawer' | 'fullscreen', previous?: 'drawer' | 'fullscreen') {\n if (next === previous) return;\n // Framework property bindings can update slotted master/detail content in\n // the same render that requests fullscreen. Reflect the host selector\n // immediately so that content cannot paint once in the drawer geometry\n // while Stencil waits to commit its next render.\n if (this.el.getAttribute('presentation') !== next) {\n this.el.setAttribute('presentation', next);\n }\n const presentationGeneration = ++this.presentationMotionGeneration;\n this.presentationMotionSuppressed = true;\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (presentationGeneration !== this.presentationMotionGeneration) return;\n this.presentationMotionSuppressed = false;\n });\n });\n if (next === 'fullscreen') {\n this.focusBeforeFullscreen =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.open = true;\n } else if (previous === 'fullscreen') {\n const focusBeforeFullscreen = this.focusBeforeFullscreen;\n const fallbackActionId = this.activeHeader().actions?.[0]?.id;\n requestAnimationFrame(async () => {\n if (focusBeforeFullscreen?.isConnected) {\n focusBeforeFullscreen.focus({ preventScroll: true });\n }\n const focusRestored = focusBeforeFullscreen\n ? document.activeElement === focusBeforeFullscreen ||\n focusBeforeFullscreen.contains(document.activeElement)\n : false;\n if (!focusRestored && fallbackActionId) await this.focusHeaderAction(fallbackActionId);\n this.focusBeforeFullscreen = null;\n });\n }\n this.dsPresentationChange.emit({ presentation: next });\n }\n\n @Listen('keydown', { target: 'window', capture: true })\n handleFullscreenEscape(event: KeyboardEvent) {\n if (this.presentation !== 'fullscreen' || event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n this.presentation = 'drawer';\n }\n\n private deferMotionEnable() {\n if (this.readyForMotion) return;\n const generation = ++this.motionEnableGeneration;\n requestAnimationFrame(() => {\n if (generation !== this.motionEnableGeneration) return;\n requestAnimationFrame(() => {\n if (generation !== this.motionEnableGeneration) return;\n this.readyForMotion = true;\n });\n });\n }\n\n connectedCallback() {\n this.el.addEventListener('transitionend', this.handleTransitionEnd);\n this.el.addEventListener('transitioncancel', this.handleTransitionEnd);\n this.el.addEventListener('dsAction', this.handleComposedHeaderAction);\n }\n\n private handleComposedHeaderAction = (event: Event) => {\n const detail = (event as CustomEvent<{ id?: string; anchor?: HTMLElement }>).detail;\n if (!this.activeTool || !detail?.id) return;\n event.stopPropagation();\n this.dsHeaderAction.emit({\n tool: this.activeTool,\n id: detail.id,\n anchor: detail.anchor,\n });\n };\n\n componentWillLoad() {\n this.restoreLastActiveTool();\n this.drawerSurfaceRetained = this.open;\n }\n\n @Watch('open')\n openChanged(isOpen: boolean, wasOpen?: boolean) {\n if (isOpen) this.drawerSurfaceRetained = true;\n if (this.readyForMotion && wasOpen !== undefined && wasOpen !== isOpen) {\n this.startDrawerTransition(isOpen ? 'opening' : 'closing');\n } else if (!isOpen) {\n this.drawerSurfaceRetained = false;\n }\n this.deferMotionEnable();\n }\n\n @Watch('activeTool')\n activeToolChanged() {\n this.persistLastActiveTool();\n this.deferMotionEnable();\n }\n\n @Watch('storageKey')\n storageKeyChanged() {\n this.restoreLastActiveTool();\n }\n\n componentDidLoad() {\n this.deferMotionEnable();\n this.connectRailBodyObserver();\n this.updateRailBodyScrollable();\n this.syncFullViewTools();\n this.fullViewObserver = new MutationObserver(() => this.syncFullViewTools());\n this.fullViewObserver.observe(this.el, {\n childList: true,\n attributes: true,\n attributeFilter: ['slot'],\n });\n }\n\n componentDidRender() {\n this.updateRailBodyScrollable();\n }\n\n private connectRailBodyObserver() {\n this.disconnectRailBodyObserver();\n if (!this.railBodyEl || typeof ResizeObserver === 'undefined') return;\n this.railBodyObserver = new ResizeObserver(() => this.updateRailBodyScrollable());\n this.railBodyObserver.observe(this.railBodyEl);\n }\n\n private disconnectRailBodyObserver() {\n this.railBodyObserver?.disconnect();\n this.railBodyObserver = undefined;\n }\n\n private updateRailBodyScrollable() {\n const next = Boolean(\n this.railBodyEl && this.railBodyEl.scrollHeight > this.railBodyEl.clientHeight + 1\n );\n if (next !== this.railBodyScrollable) this.railBodyScrollable = next;\n }\n\n @Watch('items')\n itemsChanged() {\n this.rovingIndex = 0;\n this.reconcileActiveTool();\n this.deferMotionEnable();\n }\n\n private restoreLastActiveTool() {\n if (!this.storageKey || this.activeTool) return;\n try {\n const stored = localStorage.getItem(this.storageKey);\n if (isPanelToolsToolId(stored)) this.activeTool = stored;\n } catch {\n /* localStorage unavailable */\n }\n }\n\n private persistLastActiveTool() {\n if (!this.storageKey || !this.activeTool) return;\n try {\n localStorage.setItem(this.storageKey, this.activeTool);\n } catch {\n /* unavailable */\n }\n }\n\n private clearPersistedTool() {\n if (!this.storageKey) return;\n try {\n localStorage.removeItem(this.storageKey);\n } catch {\n /* unavailable */\n }\n }\n\n private reconcileActiveTool() {\n const next = reconcilePanelToolsAvailability(this.railItems, this.open, this.activeTool);\n if (!next.removedTool) return;\n this.clearPersistedTool();\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id: next.removedTool, selected: false });\n }\n\n private startDrawerTransition(phase: 'opening' | 'closing') {\n const drawer = this.renderRoot.querySelector('.panel-tools__drawer') as HTMLElement | null;\n if (this.motion !== 'idle') {\n this.finishDrawerTransition();\n this.ignoreReplacementTerminal = true;\n }\n this.motion = phase;\n this.dsChromeTransitionStart.emit({ source: 'panel-tools', phase });\n const generation = ++this.transitionGeneration;\n\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (generation !== this.transitionGeneration || this.motion !== phase) return;\n if (!drawer) {\n this.finishDrawerTransition();\n return;\n }\n\n if (this.drawerTransitionAtTerminal(drawer, phase)) {\n this.finishDrawerTransition();\n return;\n }\n\n const fallbackMs = panelToolsDrawerTransitionMs(getComputedStyle(drawer));\n if (fallbackMs <= 0) {\n this.finishDrawerTransition();\n return;\n }\n this.transitionFallbackTimer = window.setTimeout(() => {\n this.finishDrawerTransition();\n }, fallbackMs);\n });\n });\n }\n\n private handleTransitionEnd = (event: TransitionEvent) => {\n const drawer = this.renderRoot.querySelector('.panel-tools__drawer') as HTMLElement | null;\n if (event.target !== drawer) return;\n if (event.propertyName !== 'max-width') return;\n if (this.ignoreReplacementTerminal) {\n this.ignoreReplacementTerminal = false;\n return;\n }\n // Changing from the neutral transition recipe to the direction-specific\n // easing can cancel the transition that the reflected `open` class began.\n // The replacement transition and its watchdog still own completion; ending\n // motion here would hide the fixed-width tool surface while it is visible.\n if (event.type === 'transitioncancel') return;\n if (drawer && !this.drawerTransitionAtTerminal(drawer, this.motion)) return;\n this.finishDrawerTransition();\n };\n\n private drawerTransitionAtTerminal(\n drawer: HTMLElement,\n phase: 'opening' | 'closing' | 'idle'\n ): boolean {\n const width = drawer.getBoundingClientRect().width;\n const surface = drawer.querySelector('.panel-tools__drawer-surface') as HTMLElement | null;\n const targetWidth = surface?.getBoundingClientRect().width ?? 0;\n return panelToolsDrawerAtTerminal(width, targetWidth, phase);\n }\n\n private finishDrawerTransition() {\n const completedPhase = this.motion;\n const wasTransitioning = completedPhase !== 'idle';\n this.clearTransitionCompletion();\n if (!wasTransitioning) return;\n this.ignoreReplacementTerminal = false;\n this.motion = 'idle';\n if (completedPhase === 'closing' && !this.open) {\n this.drawerSurfaceRetained = false;\n }\n this.dsChromeTransitionEnd.emit({ source: 'panel-tools' });\n }\n\n private clearTransitionCompletion() {\n this.transitionGeneration += 1;\n if (this.transitionFallbackTimer !== null) {\n window.clearTimeout(this.transitionFallbackTimer);\n this.transitionFallbackTimer = null;\n }\n }\n\n /** Rail selection follows `open` immediately — independent of the slide animation. */\n private isRailSelected(id: PanelToolsToolId): boolean {\n return this.open && this.activeTool === id;\n }\n\n /** Drawer body stays mounted while the clip frame animates closed. */\n private isDrawerPresent(): boolean {\n return this.open || this.drawerSurfaceRetained;\n }\n\n private isViewActive(id: PanelToolsToolId): boolean {\n return this.isDrawerPresent() && this.activeTool === id;\n }\n\n private hasFullView(id: PanelToolsToolId): boolean {\n return this.fullViewToolIds.includes(id);\n }\n\n private syncFullViewTools() {\n const ids = this.orderedRailItems.map(item => item.id);\n const next = ids.filter(id => Boolean(this.el.querySelector(`[slot=\"${id}-view\"]`)));\n if (next.join('|') !== this.fullViewToolIds.join('|')) this.fullViewToolIds = next;\n }\n\n private get renderRoot(): ParentNode {\n return this.el.shadowRoot ?? this.el;\n }\n\n private headerLabel(): string {\n if (!this.isDrawerPresent() || !this.activeTool) return '';\n const item = this.railItems.find(candidate => candidate.id === this.activeTool);\n return (\n item?.label ??\n PANEL_TOOLS_LABELS[this.activeTool as keyof typeof PANEL_TOOLS_LABELS] ??\n this.activeTool\n );\n }\n\n private resolvedHeaders(): PanelToolsHeaders {\n return this.headers ?? {};\n }\n\n private activeHeader(): PanelToolsHeaderConfig {\n if (!this.activeTool) return {};\n return this.resolvedHeaders()[this.activeTool] ?? {};\n }\n\n private handleToolChange = (id: PanelToolsToolId) => {\n const next = resolvePanelToolActivation(this.open, this.activeTool, id);\n this.open = next.open;\n this.activeTool = next.activeTool;\n this.dsToolChange.emit({ id, selected: next.selected });\n };\n\n /** Toggle any rail tool open/closed — shell shortcuts K/A/S/M/N call this. */\n @Method()\n async activateTool(id: PanelToolsToolId) {\n const item = this.railItems.find(entry => entry.id === id);\n if (!item || item.isInactive) return;\n this.handleToolChange(id);\n }\n\n /** Close the tools drawer when open — used by shell keyboard shortcuts. */\n @Method()\n async closeDrawer() {\n if (!this.open) return;\n const id = this.activeTool;\n this.open = false;\n if (id) {\n this.dsToolChange.emit({ id, selected: false });\n }\n }\n\n /** Focus an active tool-header action by its application-owned id. */\n @Method()\n async focusHeaderAction(id: string) {\n const action = this.renderRoot.querySelector(`[data-header-action-id=\"${CSS.escape(id)}\"]`) as\n | (HTMLElement & { setFocus?: () => Promise<void> })\n | null;\n await action?.setFocus?.();\n }\n\n private focusRailAt(index: number) {\n const items = this.orderedRailItems;\n if (!items.length) return;\n const bounded = Math.max(0, Math.min(index, items.length - 1));\n if (bounded === this.rovingIndex) return;\n this.rovingIndex = bounded;\n const actions = Array.from(\n this.renderRoot.querySelectorAll<HTMLElement>('.panel-tools__rail-action .button-icon')\n );\n actions[bounded]?.focus({ preventScroll: true });\n }\n\n private handleRailKeyDown = (e: KeyboardEvent, index: number) => {\n const items = this.orderedRailItems;\n if (!items.length) return;\n\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.handleToolChange(items[index].id);\n return;\n }\n\n if (e.key === 'ArrowDown') {\n if (index >= items.length - 1) return;\n e.preventDefault();\n this.focusRailAt(index + 1);\n return;\n }\n\n if (e.key === 'ArrowUp') {\n if (index <= 0) return;\n e.preventDefault();\n this.focusRailAt(index - 1);\n }\n };\n\n private renderRailAction(item: PanelToolsItem, index: number) {\n const label =\n item.ariaLabel ??\n item.label ??\n PANEL_TOOLS_LABELS[item.id as keyof typeof PANEL_TOOLS_LABELS] ??\n item.id;\n return (\n <ds-tooltip\n key={item.id}\n label={label}\n shortcutKey={\n item.shortcutKey ??\n PANEL_TOOLS_SHORTCUTS[item.id as keyof typeof PANEL_TOOLS_SHORTCUTS]\n }\n side=\"left\"\n size=\"sm\"\n boundary=\"viewport\"\n >\n <ds-button-unfilled\n variant=\"icon\"\n class=\"panel-tools__rail-action\"\n icon={item.icon}\n isActive={this.isRailSelected(item.id)}\n activeFill={false}\n hasBorder={false}\n pressScale={false}\n dot={item.dot ?? false}\n isInactive={item.isInactive}\n focusTabIndex={index === this.rovingIndex ? 0 : -1}\n aria-label={label}\n pressed={this.isRailSelected(item.id)}\n onFocusin={() => {\n this.rovingIndex = index;\n }}\n onKeyDown={(e: KeyboardEvent) => this.handleRailKeyDown(e, index)}\n onDsClick={() => this.handleToolChange(item.id)}\n />\n </ds-tooltip>\n );\n }\n\n render() {\n const headerLabel = this.headerLabel();\n const header = this.activeHeader();\n const headerTitle = header.title?.trim() || headerLabel;\n const headerActions = header.actions ?? [];\n const orderedRailItems = this.orderedRailItems;\n const headerItems = orderedRailItems.filter(\n item => panelToolsRailPlacement(item) === 'header'\n );\n const bodyItems = orderedRailItems.filter(\n item => panelToolsRailPlacement(item) === 'body'\n );\n const footerItems = orderedRailItems.filter(\n item => panelToolsRailPlacement(item) === 'footer'\n );\n const headerCount = headerItems.length;\n const footerStart = headerCount + bodyItems.length;\n const showDrawerChrome = this.isDrawerPresent();\n const drawerResting = panelToolsDrawerResting(this.open, this.motion);\n const activeFullView = Boolean(\n showDrawerChrome && this.activeTool && this.hasFullView(this.activeTool)\n );\n const toolIds = orderedRailItems.map(item => item.id);\n\n return (\n <Host\n class={{\n 'panel-tools': true,\n 'panel-tools--open': this.open,\n 'panel-tools--ready': this.readyForMotion,\n 'panel-tools--motion-opening': this.motion === 'opening',\n 'panel-tools--motion-closing': this.motion === 'closing',\n 'panel-tools--drawer-resting': drawerResting,\n 'panel-tools--fullscreen': this.presentation === 'fullscreen',\n 'panel-tools--presentation-snap': this.presentationMotionSuppressed,\n }}\n role={this.presentation === 'fullscreen' ? 'dialog' : 'complementary'}\n aria-modal={this.presentation === 'fullscreen' ? 'true' : undefined}\n aria-label={this.toolsLabel}\n >\n <div class=\"panel-tools__layout\">\n <nav class=\"panel-tools__rail\" aria-label={this.toolShortcutsLabel}>\n {headerItems.length ? (\n <div class=\"panel-tools__rail-header ds-chrome-row ds-chrome-space--md\">\n {headerItems.map((item, index) => this.renderRailAction(item, index))}\n </div>\n ) : null}\n <div\n class={{\n 'panel-tools__rail-body': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--md': true,\n 'ds-scrollbar-hidden': true,\n 'ds-focus-ring': this.railBodyScrollable,\n }}\n ref={element => {\n const next = (element as HTMLElement) ?? undefined;\n if (next === this.railBodyEl) return;\n this.railBodyEl = next;\n this.connectRailBodyObserver();\n }}\n role={this.railBodyScrollable ? 'region' : undefined}\n aria-label={this.railBodyScrollable ? this.toolShortcutsLabel : undefined}\n tabIndex={this.railBodyScrollable ? 0 : undefined}\n >\n <div class=\"panel-tools__rail-actions\">\n {bodyItems.map((item, bodyIdx) =>\n this.renderRailAction(item, headerCount + bodyIdx)\n )}\n </div>\n </div>\n {footerItems.length ? (\n <div class=\"panel-tools__rail-footer ds-chrome-row ds-chrome-space--md\">\n {footerItems.map((item, footerIdx) =>\n this.renderRailAction(item, footerStart + footerIdx)\n )}\n </div>\n ) : null}\n </nav>\n\n <div\n class={{\n 'panel-tools__drawer': true,\n 'panel-tools__drawer--visible': showDrawerChrome,\n }}\n aria-hidden={showDrawerChrome ? null : 'true'}\n inert={showDrawerChrome ? undefined : true}\n >\n <div class=\"panel-tools__drawer-surface\">\n {this.presentation !== 'fullscreen' || this.fullscreenHeaderMode === 'shared' ? (\n <ds-panel-tool-header\n class=\"panel-tools__header\"\n heading={headerTitle}\n showBack={header.showBack ?? false}\n backIcon={header.backIcon || 'ChevronLeft'}\n backAriaLabel={header.backAriaLabel || 'Back'}\n showMenu={false}\n ref={element => {\n const toolHeader = element as\n | (HTMLDsPanelToolHeaderElement & {\n actions: PanelToolsHeaderAction[];\n })\n | undefined;\n if (toolHeader) toolHeader.actions = headerActions;\n }}\n onDsBack={() => {\n if (this.activeTool) this.dsHeaderBack.emit({ tool: this.activeTool });\n }}\n />\n ) : null}\n <div class=\"panel-tools__body\" hidden={activeFullView}>\n {toolIds.map(id => {\n const active = this.isViewActive(id);\n return (\n <div\n class={{\n 'panel-tools__view': true,\n 'panel-tools__view--active': active,\n }}\n hidden={!active}\n >\n <slot name={id} />\n </div>\n );\n })}\n </div>\n <div class=\"panel-tools__full-views\" hidden={!activeFullView}>\n {toolIds.map(id => (\n <div\n class={{\n 'panel-tools__full-view': true,\n 'panel-tools__full-view--active':\n this.isViewActive(id) && this.hasFullView(id),\n }}\n hidden={!(this.isViewActive(id) && this.hasFullView(id))}\n >\n <slot name={`${id}-view`} />\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
472
473
  "type": "registry:ui"
473
474
  },
474
475
  {
475
476
  "path": "src/wc/components/PanelTools/panel-tools-types.ts",
476
- "content": "/** Application-owned tool id for the shell rail and responsive tool views. */\nexport type PanelToolsToolId = string;\n\n/** Canonical Motive shell recipe used when item metadata is omitted. */\nexport type CanonicalPanelToolsToolId =\n | 'search'\n | 'messages'\n | 'agents'\n | 'stacks'\n | 'activity'\n | 'help';\n\nexport const PANEL_TOOLS_TOOL_IDS: CanonicalPanelToolsToolId[] = [\n 'agents',\n 'messages',\n 'activity',\n 'search',\n 'stacks',\n 'help',\n];\n\n/** Rail header slot — mirrors panel-nav M logo row. */\nexport const PANEL_TOOLS_PRIMARY_TOOL_ID: CanonicalPanelToolsToolId = 'agents';\n\n/** Rail footer slot — flush to the bottom of the tools column. */\nexport const PANEL_TOOLS_FOOTER_TOOL_ID: CanonicalPanelToolsToolId = 'help';\n\nexport const PANEL_TOOLS_LABELS: Record<CanonicalPanelToolsToolId, string> = {\n search: 'Search',\n messages: 'Messages',\n stacks: 'Stacks',\n activity: 'Activity',\n agents: 'Agents',\n help: 'Help & Support',\n};\n\nexport const PANEL_TOOLS_SHORTCUTS: Partial<Record<CanonicalPanelToolsToolId, string>> = {\n search: 'K',\n agents: 'A',\n messages: 'M',\n stacks: 'S',\n activity: 'N',\n help: '/',\n};\n\nexport interface PanelToolsItem {\n id: PanelToolsToolId;\n /** Icon name for <ds-icon>. */\n icon: string;\n /** Visible and accessible fallback label for custom tool ids. */\n label?: string;\n /** Explicit rail region; canonical Agents/Help placement is the fallback. */\n railPlacement?: 'header' | 'body' | 'footer';\n /** Stable ordering within the selected rail region. */\n order?: number;\n /** Optional shortcut label displayed by the tooltip. */\n shortcutKey?: string;\n /** Mobile destination recipe; canonical ids retain their explicit defaults. */\n mobileDestination?: 'search' | 'inbox' | 'activity' | 'messages' | 'agents' | 'help';\n /** Whether this rail button is currently pressed/active. */\n selected?: boolean;\n /** Show a notification dot. */\n dot?: boolean;\n isInactive?: boolean;\n ariaLabel?: string;\n}\n\n/** Canonical shell recipe used by Lab and by managed ShellApp examples. */\nexport const PANEL_TOOLS_DEFAULT_ITEMS: PanelToolsItem[] = [\n {\n id: 'agents',\n icon: 'AI',\n label: PANEL_TOOLS_LABELS.agents,\n railPlacement: 'header',\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.agents,\n mobileDestination: 'agents',\n },\n {\n id: 'messages',\n icon: 'MessageBubbleStack',\n label: PANEL_TOOLS_LABELS.messages,\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.messages,\n mobileDestination: 'messages',\n },\n {\n id: 'activity',\n icon: 'Bell',\n label: PANEL_TOOLS_LABELS.activity,\n order: 1,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.activity,\n mobileDestination: 'activity',\n },\n {\n id: 'search',\n icon: 'MagnifyingGlass',\n label: PANEL_TOOLS_LABELS.search,\n order: 2,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.search,\n mobileDestination: 'search',\n },\n {\n id: 'help',\n icon: 'CircleQuestion',\n label: PANEL_TOOLS_LABELS.help,\n railPlacement: 'footer',\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.help,\n mobileDestination: 'help',\n },\n];\n\nexport interface PanelToolsHeaderAction {\n id: string;\n icon: string;\n ariaLabel: string;\n triggerId?: string;\n controls?: string;\n expanded?: boolean;\n haspopup?: 'menu' | 'dialog' | 'listbox' | 'tree' | 'grid';\n pressed?: boolean;\n isInactive?: boolean;\n}\n\nexport interface PanelToolsHeaderConfig {\n title?: string;\n showBack?: boolean;\n backIcon?: string;\n backAriaLabel?: string;\n actions?: PanelToolsHeaderAction[];\n}\n\n/** Per-tool header state. The application replaces the object when a tool changes depth or actions. */\nexport type PanelToolsHeaders = Partial<Record<PanelToolsToolId, PanelToolsHeaderConfig>>;\n",
477
+ "content": "/** Application-owned tool id for the shell rail and responsive tool views. */\nexport type PanelToolsToolId = string;\n\n/** Canonical Motive shell recipe used when item metadata is omitted. */\nexport type CanonicalPanelToolsToolId =\n | 'search'\n | 'messages'\n | 'agents'\n | 'stacks'\n | 'activity'\n | 'help';\n\nexport const PANEL_TOOLS_TOOL_IDS: CanonicalPanelToolsToolId[] = [\n 'agents',\n 'messages',\n 'activity',\n 'search',\n 'stacks',\n 'help',\n];\n\n/** Rail header slot — mirrors panel-nav M logo row. */\nexport const PANEL_TOOLS_PRIMARY_TOOL_ID: CanonicalPanelToolsToolId = 'agents';\n\n/** Rail footer slot — flush to the bottom of the tools column. */\nexport const PANEL_TOOLS_FOOTER_TOOL_ID: CanonicalPanelToolsToolId = 'help';\n\nexport const PANEL_TOOLS_LABELS: Record<CanonicalPanelToolsToolId, string> = {\n search: 'Search',\n messages: 'Messages',\n stacks: 'Stacks',\n activity: 'Activity',\n agents: 'Agents',\n help: 'Help & Support',\n};\n\nexport const PANEL_TOOLS_SHORTCUTS: Partial<Record<CanonicalPanelToolsToolId, string>> = {\n search: 'K',\n agents: 'A',\n messages: 'M',\n stacks: 'S',\n activity: 'N',\n help: '/',\n};\n\nexport interface PanelToolsItem {\n id: PanelToolsToolId;\n /** Icon name for <ds-icon>. */\n icon: string;\n /** Visible and accessible fallback label for custom tool ids. */\n label?: string;\n /** Explicit rail region; canonical Agents/Help placement is the fallback. */\n railPlacement?: 'header' | 'body' | 'footer';\n /** Stable ordering within the selected rail region. */\n order?: number;\n /** Optional shortcut label displayed by the tooltip. */\n shortcutKey?: string;\n /** Mobile destination recipe; canonical ids retain their explicit defaults. */\n mobileDestination?: 'search' | 'inbox' | 'activity' | 'messages' | 'agents' | 'help';\n /** Whether this rail button is currently pressed/active. */\n selected?: boolean;\n /** Show a notification dot. */\n dot?: boolean;\n isInactive?: boolean;\n ariaLabel?: string;\n}\n\n/** Canonical shell recipe used by Lab and by managed ShellApp examples. */\nexport const PANEL_TOOLS_DEFAULT_ITEMS: PanelToolsItem[] = [\n {\n id: 'agents',\n icon: 'AI',\n label: PANEL_TOOLS_LABELS.agents,\n railPlacement: 'header',\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.agents,\n mobileDestination: 'agents',\n },\n {\n id: 'messages',\n icon: 'MessageBubbleStack',\n label: PANEL_TOOLS_LABELS.messages,\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.messages,\n mobileDestination: 'messages',\n },\n {\n id: 'activity',\n icon: 'Bell',\n label: PANEL_TOOLS_LABELS.activity,\n order: 1,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.activity,\n mobileDestination: 'activity',\n },\n {\n id: 'search',\n icon: 'MagnifyingGlass',\n label: PANEL_TOOLS_LABELS.search,\n order: 2,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.search,\n mobileDestination: 'search',\n },\n {\n id: 'help',\n icon: 'CircleQuestion',\n label: PANEL_TOOLS_LABELS.help,\n railPlacement: 'footer',\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.help,\n mobileDestination: 'help',\n },\n];\n\nexport interface PanelToolsHeaderAction {\n id: string;\n icon: string;\n ariaLabel: string;\n triggerId?: string;\n controls?: string;\n expanded?: boolean;\n haspopup?: 'menu' | 'dialog' | 'listbox' | 'tree' | 'grid';\n pressed?: boolean;\n isInactive?: boolean;\n}\n\nexport interface PanelToolsHeaderActionDetail {\n tool: PanelToolsToolId;\n id: string;\n /** Rendered header action host. Pass this to an external overlay's element anchor prop. */\n anchor?: HTMLElement;\n}\n\nexport interface PanelToolHeaderActionDetail {\n id: string;\n originalEvent: MouseEvent;\n /** Rendered action host, suitable for anchoring an external overlay across a shadow boundary. */\n anchor: HTMLElement;\n}\n\nexport interface PanelToolsHeaderConfig {\n title?: string;\n showBack?: boolean;\n backIcon?: string;\n backAriaLabel?: string;\n actions?: PanelToolsHeaderAction[];\n}\n\n/** Per-tool header state. The application replaces the object when a tool changes depth or actions. */\nexport type PanelToolsHeaders = Partial<Record<PanelToolsToolId, PanelToolsHeaderConfig>>;\n",
477
478
  "type": "registry:ui"
478
479
  },
479
480
  {
@@ -748,7 +748,7 @@
748
748
  "Badge keeps one brand treatment; semantic severity belongs to another component or adjacent content.",
749
749
  "Use the ring only when Badge overlaps an icon or other content. Disable it in a reserved safe-area slot.",
750
750
  "When enabled, the ring matches the immediate backing surface for both variants. Use a direct ring override only for a component-local fill that no surface preset represents.",
751
- "Enabled shell gradient rings align automatically inside active ShellApp gradient chrome and may be explicitly enabled or disabled when composition requires it."
751
+ "Shell gradient rings follow the inherited ShellApp chrome context automatically; an explicit composition override may force either the gradient or flat ring treatment."
752
752
  ],
753
753
  "responsiveBehavior": [
754
754
  "The owning control or layout positions Badge and decides whether notification indicators remain visible, condense, or move at responsive breakpoints.",
@@ -3946,6 +3946,7 @@
3946
3946
  "The built-in select trigger exposes combobox semantics with a dialog popup, synchronizes expanded state, holds the pressed interaction wash without changing resting foreground while open, and restores focus after dismissal.",
3947
3947
  "The category pane is a vertical tab list. Arrow Up and Arrow Down wrap, Home and End jump, Arrow Right enters the option pane, and Arrow Left returns to the active category.",
3948
3948
  "Escape and outside activation close the popup and restore trigger focus. Tab moves through option controls and closes only when it leaves the popup boundary.",
3949
+ "When an external trigger is inside an open shadow tree, pass its HTMLElement through anchor so outside activation, focus return, and Tab continuation use the composed tree.",
3949
3950
  "The trigger count reports active filter categories. Category counts and the footer report individual selected criteria; none replace visible selected-state indication."
3950
3951
  ],
3951
3952
  "states": [
@@ -3956,10 +3957,10 @@
3956
3957
  "The trigger reports how many filter categories have selections, while the footer appears when values are selected and reports the total selected criteria."
3957
3958
  ],
3958
3959
  "responsiveBehavior": [
3959
- "The popup defaults to the large menu-width token and clamps to the viewport.",
3960
+ "The popup defaults to the large menu-width token and clamps to the nearest data-ds-overlay-boundary rectangle or the viewport fallback.",
3960
3961
  "When collapseLabel is set inside a named ds-table container narrower than 900px, the trigger omits its label and chevron, becomes icon-only, and exposes its label through a hover and keyboard-focus tooltip. Active criteria promote the prefix icon to primary.",
3961
3962
  "Both panes remain visible at narrow widths, with a smaller category minimum and reduced option-pane padding.",
3962
- "Treat side as preferred placement; the popup flips and clamps when viewport collision requires it."
3963
+ "Treat side as preferred placement; the popup flips and clamps when boundary collision requires it. Assign boundary as an HTMLElement only for an explicit override."
3963
3964
  ],
3964
3965
  "frameworkCaveats": {
3965
3966
  "customElements": [
@@ -4008,6 +4009,10 @@
4008
4009
  "type": "string | null",
4009
4010
  "required": false
4010
4011
  },
4012
+ "boundary": {
4013
+ "type": "HTMLElement | undefined",
4014
+ "required": false
4015
+ },
4011
4016
  "categoriesLabel": {
4012
4017
  "type": "string",
4013
4018
  "required": false
@@ -4302,6 +4307,7 @@
4302
4307
  "Choose the native input type that matches the value so browsers and assistive technology can provide appropriate semantics and keyboards.",
4303
4308
  "Error text is associated with the native input and announced as an alert when the invalid state is rendered.",
4304
4309
  "Read-only fields remain focusable and submittable while preventing edits; inactive or disabled fields leave the tab order and are omitted from form submission.",
4310
+ "Number stepper actions are pointer affordances hidden from the accessibility tree; the native spinbutton remains the single keyboard and assistive-technology control.",
4305
4311
  "Suffix content must not carry information that is otherwise absent from the field's visible label, value, or description."
4306
4312
  ],
4307
4313
  "states": [
@@ -4312,7 +4318,8 @@
4312
4318
  "Search fields show a standard borderless CrossCircle icon action only while they contain a value. Clearing emits the empty value and restores focus to the native field.",
4313
4319
  "The suffix slot is for compact units or decorations. The optional leading icon is decorative and does not replace the field label.",
4314
4320
  "Use autoComplete, inputMode, and enterKeyHint to provide browser and virtual-keyboard hints without changing the value's semantic type.",
4315
- "Number fields preserve native spinbutton semantics, stepping, and min/max constraint behavior while dsChange continues to report the native string value.",
4321
+ "Number fields preserve native spinbutton semantics, keyboard stepping, and min/max constraint behavior while dsChange continues to report the native string value. Two borderless inset chevron actions split the available inner height, disable independently at their numeric bound, and restore focus to the spinbutton after pointer activation.",
4322
+ "textAlign positions the editable value while the numeric stepper stays on the opposite inline edge, keeping the actions clear of the number: start-aligned values use an inline-end stepper and end-aligned values use an inline-start stepper.",
4316
4323
  "The host exposes focused, filled, dirty, touched, invalid, disabled, read-only, and required data attributes for Field integration and diagnostics.",
4317
4324
  "Use Input only on primary or secondary application backgrounds. It intentionally does not expose alternate surface contexts."
4318
4325
  ],
@@ -4448,6 +4455,10 @@
4448
4455
  "type": "number | undefined",
4449
4456
  "required": false
4450
4457
  },
4458
+ "textAlign": {
4459
+ "type": "InputTextAlign",
4460
+ "required": false
4461
+ },
4451
4462
  "type": {
4452
4463
  "type": "InputType",
4453
4464
  "required": false
@@ -4736,6 +4747,7 @@
4736
4747
  "Switch rows use menuitemcheckbox semantics on the row; the nested ds-switch is an aria-hidden presentation indicator at the same density as the menu.",
4737
4748
  "Single-selection menus use menuitemradio semantics and aria-checked on each row, but render no nested radio control; selected interaction fill and primary foreground provide the visual state.",
4738
4749
  "A swatch-picker section changes the popup to a non-modal dialog so its radio group and ordinary preference buttons keep valid nested semantics; Tab moves between its controls, then leaves and closes at the popup boundary.",
4750
+ "When the anchor is inside an open shadow tree, pass its HTMLElement through anchor. Outside activation, focus return, and forward Tab continuation follow the composed tree across that boundary.",
4739
4751
  "Use notification dots only as supplemental status and not as the sole indication of important information.",
4740
4752
  "Trailing tags are non-interactive supplemental content; their visible label remains part of the menu item's accessible name.",
4741
4753
  "Reorderable rows keep a presentation Drag handle. Pointer drag on the handle and Alt+Arrow Up or Down emit dsReorder without closing. Pointer cancellation abandons the pending move. Hide-locked reorderable rows expose aria-disabled for the unavailable toggle but stay focusable so they can move; fully locked rows stay out of the contiguous reorderable run.",
@@ -4748,6 +4760,7 @@
4748
4760
  "After dsReorder, replace items or the affected section with the emitted items array. Menu never mutates order and does not close.",
4749
4761
  "For selectionMode single, keep exactly one item isSelected and replace the items or sections array after dsSelect; Menu never mutates consumer-owned selection data.",
4750
4762
  "Arrays, objects, and element anchors must be assigned as JavaScript properties.",
4763
+ "anchorId resolves only in the document tree; use the anchor property for a trigger rendered inside a shadow root.",
4751
4764
  "Use a swatch-picker section for a small curated visual preference and apply dsSwatchSelect immediately without closing the popup.",
4752
4765
  "Ordinary rows, section headings, and supporting text consume the Menu size. At xs, both the primary label and supporting text use caption metrics.",
4753
4766
  "A trailing tag resolves one density smaller than its menu row and uses the inset recipe so it nests without changing row height. Do not combine tag, dot, and switch suffixes on one item.",
@@ -4755,9 +4768,11 @@
4755
4768
  ],
4756
4769
  "responsiveBehavior": [
4757
4770
  "Keep the default choice-cell anchor alignment so the first or last interactive row edge aligns with its trigger, matching Select; use popup-frame only for deliberate custom frame geometry.",
4758
- "Treat side as the preferred placement. When the popup cannot fit there and the opposite side offers more room, Menu flips on that main axis before clamping the final position to the viewport.",
4771
+ "Treat side as the preferred placement. When the popup cannot fit there and the opposite side offers more room, Menu flips on that main axis before clamping to the nearest data-ds-overlay-boundary rectangle or the viewport fallback.",
4772
+ "Keep the popup attached while its external anchor moves or resizes, including during CSS transitions in responsive shell chrome.",
4759
4773
  "Cap the popup to the vertical room available on its resolved side. When content is taller, the choice list scrolls internally and keyboard focus scrolls the active row into view.",
4760
- "Avoid fixed widths that exceed narrow viewports."
4774
+ "Assign boundary as an HTMLElement only when the nearest composed-tree boundary is not the intended collision owner.",
4775
+ "Avoid fixed widths that exceed narrow collision boundaries."
4761
4776
  ],
4762
4777
  "frameworkCaveats": {
4763
4778
  "customElements": [
@@ -4803,6 +4818,10 @@
4803
4818
  "type": "string | undefined",
4804
4819
  "required": false
4805
4820
  },
4821
+ "boundary": {
4822
+ "type": "HTMLElement | undefined",
4823
+ "required": false
4824
+ },
4806
4825
  "initialFocusVisible": {
4807
4826
  "type": "boolean",
4808
4827
  "required": false
@@ -6656,7 +6675,7 @@
6656
6675
  "events": [
6657
6676
  {
6658
6677
  "name": "dsAction",
6659
- "detail": "{ id: string; originalEvent: MouseEvent }"
6678
+ "detail": "PanelToolHeaderActionDetail"
6660
6679
  },
6661
6680
  {
6662
6681
  "name": "dsBack",
@@ -6910,6 +6929,7 @@
6910
6929
  "If an active optional tool becomes unavailable, PanelTools closes the drawer, clears activeTool, and emits its deselection.",
6911
6930
  "Use isInactive only for a temporarily unavailable visible tool; omit tools the user is not authorized to access.",
6912
6931
  "Rail actions disable physical press scaling so notification-dot halos remain aligned to the shell gradient; hover and pressed washes continue to paint above the dot and halo.",
6932
+ "Rail tooltips are shell-owned and position against the viewport so the narrow tools-lane collision boundary cannot clamp them beyond the viewport edge.",
6913
6933
  "The application owns slotted tool products and data while PanelTools owns rail ordering, drawer selection, open state, motion, and shell events.",
6914
6934
  "Opening and closing clip an already-rendered fixed-width surface. Header and tool content remain fully opaque and retained during width motion; transition completion is accepted only at the measured terminal clip width, after which the closed surface becomes inert and paint-skipped.",
6915
6935
  "Entering and exiting fullscreen snaps without borrowing the drawer open or close animation.",
@@ -7003,7 +7023,7 @@
7003
7023
  },
7004
7024
  {
7005
7025
  "name": "dsHeaderAction",
7006
- "detail": "{\n tool: PanelToolsToolId;\n id: string;\n }"
7026
+ "detail": "PanelToolsHeaderActionDetail"
7007
7027
  },
7008
7028
  {
7009
7029
  "name": "dsHeaderBack",
@@ -7449,7 +7469,7 @@
7449
7469
  "responsiveBehavior": [
7450
7470
  "Use width fill for form fields and width hug for compact filter bars. Override those defaults only when the owning layout has a clear constraint.",
7451
7471
  "When collapseLabel is set inside a named ds-table container narrower than 900px, the trigger omits its label and chevron, becomes icon-only, and exposes its label through a hover and keyboard-focus tooltip. A selected value promotes the prefix icon to primary.",
7452
- "The popup matches the trigger minimum width, may grow for content, and clamps to the viewport.",
7472
+ "The popup matches the trigger minimum width, may grow for content, and clamps to the nearest data-ds-overlay-boundary rectangle or the viewport fallback. Assign boundary as an HTMLElement only for an explicit override.",
7453
7473
  "Keep popupAlign at start for ordinary fields and filters. Use end when a control sits against a right layout edge and the popup should grow inward.",
7454
7474
  "Long option collections scroll inside the popup; virtualization and remote search are not part of this component."
7455
7475
  ],
@@ -7503,6 +7523,10 @@
7503
7523
  "type": "SelectBackground | undefined",
7504
7524
  "required": false
7505
7525
  },
7526
+ "boundary": {
7527
+ "type": "HTMLElement | undefined",
7528
+ "required": false
7529
+ },
7506
7530
  "clearLabel": {
7507
7531
  "type": "string",
7508
7532
  "required": false
@@ -7765,6 +7789,7 @@
7765
7789
  "A slotted Banner remains below the top safe-area inset and above routed, navigation, fullscreen-tool, and mobile global-surface presentations.",
7766
7790
  "Mobile global surfaces conceal and inert the routed workspace without unmounting it, preserving nested routes and scroll state.",
7767
7791
  "Keep PanelNav width, BarNav height, PanelTools chrome, and main-content origin stable except during intentional responsive or collapse transitions.",
7792
+ "The routed content cell and tools cell expose separate anchored-overlay collision boundaries, so top-layer menus and tooltips remain above local clipping without crossing into neighboring shell lanes. Full-screen modal overlays remain viewport-owned.",
7768
7793
  "Paper texture chrome is desktop/tablet-only and follows the fixed viewport phase of the shell chrome; mobile remains a solid primary stage.",
7769
7794
  "Managed page-header capacity follows only the fixed shell breakpoint: roomy on desktop and compact on tablet. Opening PanelTools does not compact or constrain BarTitle.",
7770
7795
  "The bottom bar remains visible at all route and tool depths in the foundation release."
@@ -10402,7 +10427,7 @@
10402
10427
  "responsiveBehavior": [
10403
10428
  "The global stack stays at the viewport bottom-end with token-based inset and never exceeds the available viewport width.",
10404
10429
  "Set avoid-mobile-shell-bar when the application uses the persistent shell bottom bar. Below 768px the global stack clears the complete safe-area-aware bar container by 16px; tablet and desktop positioning remain unchanged.",
10405
- "Anchored toasts flip on their placement axis and clamp to viewport padding when the preferred side does not fit.",
10430
+ "Anchored toasts flip on their placement axis and clamp to their anchor's nearest data-ds-overlay-boundary rectangle, falling back to viewport padding when no owner is marked.",
10406
10431
  "The owner does not create separate mobile and desktop managers; one queue preserves ordering across responsive layouts.",
10407
10432
  "Pointer swipe remains optional input. Close and action controls retain keyboard access through the notification region."
10408
10433
  ],
@@ -10557,7 +10582,7 @@
10557
10582
  "Only one tooltip remains active globally; warm handoff removes the previous popup before showing the next.",
10558
10583
  "Disconnecting and reinserting the tooltip host rebinds the current slotted trigger.",
10559
10584
  "A host may present the popup against an external anchor it already owns, without wrapping that trigger or binding hover on it.",
10560
- "The preferred side flips when it cannot fit, then alignment is clamped to viewport padding.",
10585
+ "The preferred side flips when it cannot fit, then alignment is clamped to the nearest data-ds-overlay-boundary rectangle or viewport padding. Assign boundary as an HTMLElement for an explicit owner, or viewport when shell-owned chrome must escape a narrow page or lane boundary.",
10561
10586
  "Reduced motion removes enter and exit fades while retaining the final visible or hidden state."
10562
10587
  ],
10563
10588
  "responsiveBehavior": [
@@ -10570,7 +10595,8 @@
10570
10595
  "Place exactly one trigger element as the component's light-DOM child.",
10571
10596
  "Replacing the slotted trigger rebinds pointer and focus behavior automatically.",
10572
10597
  "A table or similar host can present the popup against an overflowing text node instead of wrapping that node.",
10573
- "sideOffset, alignOffset, and delay accept numbers or TokoMo CSS values."
10598
+ "sideOffset, alignOffset, and delay accept numbers or TokoMo CSS values.",
10599
+ "Use boundary=viewport only for shell-owned chrome whose tooltip intentionally crosses an inner page or lane boundary."
10574
10600
  ],
10575
10601
  "react": [
10576
10602
  "Wrap one focusable trigger with DsTooltip; keep the trigger's aria-label independent from the tooltip label."
@@ -10603,6 +10629,10 @@
10603
10629
  "type": "number | string",
10604
10630
  "required": false
10605
10631
  },
10632
+ "boundary": {
10633
+ "type": "AnchoredOverlayBoundary | undefined",
10634
+ "required": false
10635
+ },
10606
10636
  "delay": {
10607
10637
  "type": "number | string",
10608
10638
  "required": false
@@ -10812,7 +10842,7 @@
10812
10842
  "pattern:conversation"
10813
10843
  ],
10814
10844
  "accessibility": [
10815
- "Provide concise localized status text; the standard label is Typing followed by a three-dot ellipsis.",
10845
+ "Provide concise localized status text; the standard label is Typing followed by a single Unicode ellipsis.",
10816
10846
  "The polite atomic status announces typing without representing it as a message bubble."
10817
10847
  ],
10818
10848
  "states": [