@ds-mo/ui 12.8.0 → 13.0.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 (439) hide show
  1. package/README.md +1 -0
  2. package/dist/.package-ready.json +1 -0
  3. package/dist/agent-patterns.json +124 -135
  4. package/dist/agent.json +260 -1104
  5. package/dist/angular/ds-bar-nav.d.ts +1 -1
  6. package/dist/angular/ds-bar-nav.js +3 -3
  7. package/dist/angular/ds-button-filled.d.ts +1 -1
  8. package/dist/angular/ds-button-filled.js +3 -3
  9. package/dist/angular/ds-card-data-viz.d.ts +18 -0
  10. package/dist/angular/{ds-card-data-viz-donut.js → ds-card-data-viz.js} +12 -12
  11. package/dist/angular/ds-card-overview.d.ts +1 -1
  12. package/dist/angular/ds-card-overview.js +3 -3
  13. package/dist/angular/ds-chart-bar.d.ts +1 -1
  14. package/dist/angular/ds-chart-bar.js +3 -3
  15. package/dist/angular/ds-menu.d.ts +1 -7
  16. package/dist/angular/ds-menu.js +2 -5
  17. package/dist/angular/ds-mobile-header.d.ts +1 -1
  18. package/dist/angular/ds-mobile-header.js +3 -3
  19. package/dist/angular/ds-mobile-section-switcher.d.ts +1 -1
  20. package/dist/angular/ds-mobile-section-switcher.js +3 -3
  21. package/dist/angular/ds-panel-tools.d.ts +1 -1
  22. package/dist/angular/ds-panel-tools.js +3 -3
  23. package/dist/angular/ds-select.d.ts +5 -4
  24. package/dist/angular/ds-select.js +3 -3
  25. package/dist/angular/ds-shell-app.d.ts +90 -1
  26. package/dist/angular/ds-shell-app.js +44 -4
  27. package/dist/angular/ds-shell-tools.d.ts +1 -1
  28. package/dist/angular/ds-shell-tools.js +3 -3
  29. package/dist/angular/index.d.ts +1 -1
  30. package/dist/angular/index.js +1 -8
  31. package/dist/angular/proxies.d.ts +1 -8
  32. package/dist/angular/proxies.js +1 -8
  33. package/dist/components/ds-agent-activity.js +1 -1
  34. package/dist/components/ds-agent-response.js +1 -1
  35. package/dist/components/ds-agent-source-list.js +1 -1
  36. package/dist/components/ds-agent-tool-call.js +1 -1
  37. package/dist/components/ds-attachment-list.js +1 -1
  38. package/dist/components/ds-badge.js +1 -1
  39. package/dist/components/ds-bar-nav.js +1 -1
  40. package/dist/components/ds-bar-nav.js.map +1 -1
  41. package/dist/components/ds-bar-title.js +1 -1
  42. package/dist/components/ds-bar-title.js.map +1 -1
  43. package/dist/components/ds-bar-workflow.js +1 -1
  44. package/dist/components/ds-breadcrumb.js +1 -1
  45. package/dist/components/ds-button-filled.js +1 -1
  46. package/dist/components/ds-button-unfilled.js +1 -1
  47. package/dist/components/{ds-select-multi.d.ts → ds-card-data-viz.d.ts} +4 -4
  48. package/dist/components/ds-card-data-viz.js +2 -0
  49. package/dist/components/ds-card-data-viz.js.map +1 -0
  50. package/dist/components/ds-card-overview.js +1 -1
  51. package/dist/components/ds-card-overview.js.map +1 -1
  52. package/dist/components/ds-card-setting.js +1 -1
  53. package/dist/components/ds-chart-bar.js +1 -1
  54. package/dist/components/ds-chart-bar.js.map +1 -1
  55. package/dist/components/ds-chart-donut.js +1 -1
  56. package/dist/components/ds-chart-legend.js +1 -1
  57. package/dist/components/ds-chart-legend.js.map +1 -1
  58. package/dist/components/ds-chart-line.js +1 -1
  59. package/dist/components/ds-chart-line.js.map +1 -1
  60. package/dist/components/ds-checkbox.js +1 -1
  61. package/dist/components/ds-chip.js +1 -1
  62. package/dist/components/ds-code-block.js +1 -1
  63. package/dist/components/ds-conversation-list-item.js +1 -1
  64. package/dist/components/ds-conversation-list-item.js.map +1 -1
  65. package/dist/components/ds-conversation-list-section.js +1 -1
  66. package/dist/components/ds-conversation-list.js +1 -1
  67. package/dist/components/ds-divider.js +1 -1
  68. package/dist/components/ds-empty-state.js +1 -1
  69. package/dist/components/ds-field.js +1 -1
  70. package/dist/components/ds-input.js +1 -1
  71. package/dist/components/ds-input.js.map +1 -1
  72. package/dist/components/ds-markdown.js +1 -1
  73. package/dist/components/ds-menu.js +1 -1
  74. package/dist/components/ds-message-bubble.js +1 -1
  75. package/dist/components/ds-message-composer.js +1 -1
  76. package/dist/components/ds-message-composer.js.map +1 -1
  77. package/dist/components/ds-message-scroller.js +1 -1
  78. package/dist/components/ds-message.js +1 -1
  79. package/dist/components/ds-mobile-bar-nav.js +1 -1
  80. package/dist/components/ds-mobile-bar-nav.js.map +1 -1
  81. package/dist/components/ds-mobile-header.js +1 -1
  82. package/dist/components/ds-mobile-section-switcher.js +1 -1
  83. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  84. package/dist/components/ds-mobile-sheet-nav.js.map +1 -1
  85. package/dist/components/ds-modal.js +1 -1
  86. package/dist/components/ds-panel-nav.js +1 -1
  87. package/dist/components/ds-panel-nav.js.map +1 -1
  88. package/dist/components/ds-panel-sub-nav.js +1 -1
  89. package/dist/components/ds-panel-tool-header.js +1 -1
  90. package/dist/components/ds-panel-tool-search.js +1 -1
  91. package/dist/components/ds-panel-tools.js +1 -1
  92. package/dist/components/ds-radio.js +1 -1
  93. package/dist/components/ds-radio.js.map +1 -1
  94. package/dist/components/ds-scroll-overlay.js +1 -1
  95. package/dist/components/ds-select.js +1 -1
  96. package/dist/components/ds-select.js.map +1 -1
  97. package/dist/components/ds-shell-app.js +1 -1
  98. package/dist/components/ds-shell-app.js.map +1 -1
  99. package/dist/components/ds-shell-page.js +1 -1
  100. package/dist/components/ds-shell-page.js.map +1 -1
  101. package/dist/components/ds-shell-tools.js +1 -1
  102. package/dist/components/ds-shell-tools.js.map +1 -1
  103. package/dist/components/ds-skeleton.js +1 -1
  104. package/dist/components/ds-slider.js +1 -1
  105. package/dist/components/ds-slider.js.map +1 -1
  106. package/dist/components/ds-swatch-picker.js +1 -1
  107. package/dist/components/ds-switch.js +1 -1
  108. package/dist/components/ds-tab-group.js +1 -1
  109. package/dist/components/ds-tag.js +1 -1
  110. package/dist/components/ds-text.js +1 -1
  111. package/dist/components/ds-toast.js +1 -1
  112. package/dist/components/ds-tooltip-data-viz.js +1 -1
  113. package/dist/components/ds-tooltip.js +1 -1
  114. package/dist/components/ds-typing-indicator.js +1 -1
  115. package/dist/components/{p-DB9XJm2c.js → p-6qXDqCj_.js} +2 -2
  116. package/dist/components/{p-CPwpgUWT.js → p-Ay1AX7Ql.js} +2 -2
  117. package/dist/components/p-Ay1AX7Ql.js.map +1 -0
  118. package/dist/components/p-B-qDb_Zo.js +2 -0
  119. package/dist/components/p-B-qDb_Zo.js.map +1 -0
  120. package/dist/components/{p-DhLmoCe_.js → p-B1cbQ0SG.js} +2 -2
  121. package/dist/components/p-BBxcMVqk.js +2 -0
  122. package/dist/components/p-BBxcMVqk.js.map +1 -0
  123. package/dist/components/{p-BKOlvCGS.js → p-BUZXuod2.js} +2 -2
  124. package/dist/components/{p-Utr23NC-.js → p-BaUsq_VK.js} +2 -2
  125. package/dist/components/p-BbMJTilO.js +2 -0
  126. package/dist/components/p-BbMJTilO.js.map +1 -0
  127. package/dist/components/{p-BO5aqZcf.js → p-BcTYhmku.js} +2 -2
  128. package/dist/components/p-Bkv3UjZz.js +2 -0
  129. package/dist/components/p-Bkv3UjZz.js.map +1 -0
  130. package/dist/components/p-BnQXAbif.js +2 -0
  131. package/dist/components/p-BnQXAbif.js.map +1 -0
  132. package/dist/components/p-BnbbnuVC.js +2 -0
  133. package/dist/components/p-BnbbnuVC.js.map +1 -0
  134. package/dist/components/{p-BlHzmszE.js → p-BroVnDzE.js} +2 -2
  135. package/dist/components/{p-BFRMwl93.js → p-BsoqsbBh.js} +2 -2
  136. package/dist/components/p-BwT6B5Fi.js +2 -0
  137. package/dist/components/p-BwT6B5Fi.js.map +1 -0
  138. package/dist/components/p-BybqPD8K.js +2 -0
  139. package/dist/components/p-BybqPD8K.js.map +1 -0
  140. package/dist/components/p-C-vRkDyn.js +2 -0
  141. package/dist/components/p-C-vRkDyn.js.map +1 -0
  142. package/dist/components/p-C3ArcO50.js +2 -0
  143. package/dist/components/p-C3ArcO50.js.map +1 -0
  144. package/dist/components/p-C4uYvyq6.js +2 -0
  145. package/dist/components/p-C4uYvyq6.js.map +1 -0
  146. package/dist/components/{p-ukB-dPOK.js → p-CBazaRlE.js} +2 -2
  147. package/dist/components/{p-CNgPwd11.js → p-CHuh71-U.js} +2 -2
  148. package/dist/components/{p-D9u1_OEZ.js → p-CMWata9g.js} +2 -2
  149. package/dist/components/p-CS6CHlHK.js +2 -0
  150. package/dist/components/p-CS6CHlHK.js.map +1 -0
  151. package/dist/components/p-CTTnM7cE.js +2 -0
  152. package/dist/components/p-CTTnM7cE.js.map +1 -0
  153. package/dist/components/{p-CsX3ICz7.js → p-CWoXKe_h.js} +2 -2
  154. package/dist/components/p-CWoXKe_h.js.map +1 -0
  155. package/dist/components/p-CwwuSGER.js +2 -0
  156. package/dist/components/p-CwwuSGER.js.map +1 -0
  157. package/dist/components/p-D09HINr_.js +2 -0
  158. package/dist/components/p-D09HINr_.js.map +1 -0
  159. package/dist/components/p-D2xWcVv6.js +2 -0
  160. package/dist/components/p-D2xWcVv6.js.map +1 -0
  161. package/dist/components/p-DAW6G8Sl.js +2 -0
  162. package/dist/components/p-DAW6G8Sl.js.map +1 -0
  163. package/dist/components/p-DEaszb15.js +2 -0
  164. package/dist/components/p-DEaszb15.js.map +1 -0
  165. package/dist/components/{p-BgGP65Nn.js → p-DOUk4KoT.js} +1 -1
  166. package/dist/components/p-DOUk4KoT.js.map +1 -0
  167. package/dist/components/p-DbQr7Kwr.js +2 -0
  168. package/dist/components/p-DbQr7Kwr.js.map +1 -0
  169. package/dist/components/p-DdvEZyX4.js +2 -0
  170. package/dist/components/p-DdvEZyX4.js.map +1 -0
  171. package/dist/components/{p-38PfdMkF.js → p-DeQwkbT1.js} +2 -2
  172. package/dist/components/{p-Vdufoj7G.js → p-Df17mJqI.js} +2 -2
  173. package/dist/components/p-Dg1LBXJ2.js +2 -0
  174. package/dist/components/p-Dg1LBXJ2.js.map +1 -0
  175. package/dist/components/p-DsiCDzoF.js +2 -0
  176. package/dist/components/p-DsiCDzoF.js.map +1 -0
  177. package/dist/components/p-_DzUXA86.js +2 -0
  178. package/dist/components/p-_DzUXA86.js.map +1 -0
  179. package/dist/components/p-lyZNvRvO.js +2 -0
  180. package/dist/components/p-lyZNvRvO.js.map +1 -0
  181. package/dist/components/{p-69lHhJiX.js → p-ohsLFap_.js} +2 -2
  182. package/dist/components/{p-DjbTFy2B.js → p-ymtdhOU5.js} +1 -1
  183. package/dist/components/p-ymtdhOU5.js.map +1 -0
  184. package/dist/components/p-z5RIrXuX.js +2 -0
  185. package/dist/components/p-z5RIrXuX.js.map +1 -0
  186. package/dist/docs/components.json +1006 -2356
  187. package/dist/framework/angular.d.ts +0 -1
  188. package/dist/framework/angular.js +0 -1
  189. package/dist/lib/components/BarTitle/bar-title-types.d.ts +28 -0
  190. package/dist/lib/components/Breadcrumb/breadcrumb-types.d.ts +16 -0
  191. package/dist/lib/components/ButtonFilled/ButtonFilled.d.ts +83 -0
  192. package/dist/lib/components/MobileHeader/mobile-header-types.d.ts +4 -0
  193. package/dist/lib/components/PanelTools/panel-tools-types.d.ts +21 -7
  194. package/dist/lib/components/ShellApp/shell-app-types.d.ts +91 -0
  195. package/dist/lib/shell/index.d.ts +5 -13
  196. package/dist/lib/shell/index.js +87 -358
  197. package/dist/lib/shell/index.js.map +3 -3
  198. package/dist/lib/shell/shell-responsive.d.ts +2 -1
  199. package/dist/lib/utils/button-render.d.ts +17 -0
  200. package/dist/lib/utils/button-types.d.ts +10 -0
  201. package/dist/lib/utils/choice-list.d.ts +25 -0
  202. package/dist/lib/utils/control-press.d.ts +8 -0
  203. package/dist/lib/utils/form-association.d.ts +22 -0
  204. package/dist/lib/utils/index.d.ts +4 -0
  205. package/dist/lib/utils/index.js +63 -0
  206. package/dist/lib/utils/index.js.map +3 -3
  207. package/dist/lib/utils/required-validity.d.ts +1 -1
  208. package/dist/lib/utils/safe-url.d.ts +11 -0
  209. package/dist/mcp/cli.js +38 -10
  210. package/dist/mcp-data/registry/agent-source-list.json +1 -1
  211. package/dist/mcp-data/registry/attachment-list.json +1 -1
  212. package/dist/mcp-data/registry/bar-nav.json +3 -21
  213. package/dist/mcp-data/registry/breadcrumb.json +1 -1
  214. package/dist/mcp-data/registry/button-filled.json +4 -19
  215. package/dist/mcp-data/registry/button-unfilled.json +14 -11
  216. package/dist/mcp-data/registry/card-data-viz.json +241 -0
  217. package/dist/mcp-data/registry/card-overview.json +87 -16
  218. package/dist/mcp-data/registry/card-setting.json +2 -2
  219. package/dist/mcp-data/registry/chart-bar.json +66 -18
  220. package/dist/mcp-data/registry/chart-donut.json +5 -5
  221. package/dist/mcp-data/registry/chart-legend.json +7 -9
  222. package/dist/mcp-data/registry/chart-line.json +1 -2
  223. package/dist/mcp-data/registry/checkbox.json +2 -2
  224. package/dist/mcp-data/registry/conversation-list-item.json +1 -1
  225. package/dist/mcp-data/registry/field.json +2 -3
  226. package/dist/mcp-data/registry/input.json +1 -1
  227. package/dist/mcp-data/registry/markdown.json +1 -1
  228. package/dist/mcp-data/registry/menu.json +10 -21
  229. package/dist/mcp-data/registry/mobile-bar-nav.json +1 -1
  230. package/dist/mcp-data/registry/mobile-header.json +1 -37
  231. package/dist/mcp-data/registry/mobile-section-switcher.json +1 -19
  232. package/dist/mcp-data/registry/panel-nav.json +2 -2
  233. package/dist/mcp-data/registry/panel-tool-search.json +1 -1
  234. package/dist/mcp-data/registry/panel-tools.json +19 -55
  235. package/dist/mcp-data/registry/radio.json +1 -1
  236. package/dist/mcp-data/registry/registry.json +309 -1174
  237. package/dist/mcp-data/registry/select.json +59 -25
  238. package/dist/mcp-data/registry/shell-app.json +345 -20
  239. package/dist/mcp-data/registry/shell-page.json +1 -1
  240. package/dist/mcp-data/registry/shell-tools.json +12 -43
  241. package/dist/mcp-data/registry/slider.json +1 -1
  242. package/dist/mcp-data/registry/switch.json +1 -1
  243. package/dist/mcp-data/registry/text.json +2 -2
  244. package/dist/react/components.d.ts +1 -8
  245. package/dist/react/components.js +1 -8
  246. package/dist/react/ds-card-data-viz.d.ts +12 -0
  247. package/dist/react/ds-card-data-viz.js +12 -0
  248. package/dist/react/ds-menu.d.ts +1 -2
  249. package/dist/react/ds-menu.js +0 -1
  250. package/dist/react/ds-select.d.ts +2 -2
  251. package/dist/react/ds-shell-app.d.ts +24 -1
  252. package/dist/react/ds-shell-app.js +16 -1
  253. package/dist/types/components/AgentSourceList/AgentSourceList.d.ts +0 -1
  254. package/dist/types/components/BarNav/BarNav.d.ts +0 -2
  255. package/dist/types/components/BarNav/bar-nav-utils.d.ts +2 -4
  256. package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +5 -11
  257. package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +13 -10
  258. package/dist/types/components/CardDataViz/CardDataViz.d.ts +32 -0
  259. package/dist/types/components/CardDataViz/index.d.ts +2 -0
  260. package/dist/types/components/CardOverview/CardOverview.d.ts +25 -3
  261. package/dist/types/components/CardOverview/card-overview-controller.d.ts +16 -0
  262. package/dist/types/components/CardOverview/card-overview-types.d.ts +2 -0
  263. package/dist/types/components/CardOverview/index.d.ts +1 -1
  264. package/dist/types/components/ChartBar/ChartBar.d.ts +12 -1
  265. package/dist/types/components/ChartBar/index.d.ts +2 -1
  266. package/dist/types/components/Menu/Menu.d.ts +0 -5
  267. package/dist/types/components/Menu/menu-sections.d.ts +4 -0
  268. package/dist/types/components/Menu/menu-types.d.ts +2 -11
  269. package/dist/types/components/MobileHeader/MobileHeader.d.ts +0 -5
  270. package/dist/types/components/MobileSectionSwitcher/MobileSectionSwitcher.d.ts +0 -2
  271. package/dist/types/components/PanelTools/PanelTools.d.ts +0 -4
  272. package/dist/types/components/PanelTools/index.d.ts +1 -1
  273. package/dist/types/components/PanelTools/panel-tools-types.d.ts +21 -7
  274. package/dist/types/components/PanelTools/panel-tools-utils.d.ts +1 -1
  275. package/dist/types/components/Select/Select.d.ts +12 -4
  276. package/dist/types/components/Select/index.d.ts +1 -1
  277. package/dist/types/components/ShellApp/ShellApp.d.ts +98 -0
  278. package/dist/types/components/ShellApp/shell-app-types.d.ts +92 -0
  279. package/dist/types/components/ShellPage/ShellPage.d.ts +5 -0
  280. package/dist/types/components/ShellTools/ShellTools.d.ts +2 -2
  281. package/dist/types/components.d.ts +238 -895
  282. package/dist/types/shell/index.d.ts +5 -13
  283. package/dist/types/shell/shell-responsive.d.ts +2 -1
  284. package/dist/types/utils/anchored-position-controller.d.ts +9 -0
  285. package/dist/types/utils/button-render.d.ts +18 -0
  286. package/dist/types/utils/button-story-foundation.d.ts +9 -0
  287. package/dist/types/utils/button-types.d.ts +11 -0
  288. package/dist/types/utils/form-association.d.ts +23 -0
  289. package/dist/types/utils/index.d.ts +4 -0
  290. package/dist/types/utils/required-validity.d.ts +1 -1
  291. package/dist/types/utils/safe-url.d.ts +12 -0
  292. package/dist/types/utils/select-controller.d.ts +1 -1
  293. package/package.json +16 -8
  294. package/dist/.build-stamp +0 -1
  295. package/dist/angular/ds-card-data-viz-bar.d.ts +0 -18
  296. package/dist/angular/ds-card-data-viz-bar.js +0 -42
  297. package/dist/angular/ds-card-data-viz-donut.d.ts +0 -18
  298. package/dist/angular/ds-card-data-viz-line.d.ts +0 -18
  299. package/dist/angular/ds-card-data-viz-line.js +0 -42
  300. package/dist/angular/ds-card-shell-data-viz.d.ts +0 -12
  301. package/dist/angular/ds-card-shell-data-viz.js +0 -38
  302. package/dist/angular/ds-chart-bar-stacked.d.ts +0 -12
  303. package/dist/angular/ds-chart-bar-stacked.js +0 -38
  304. package/dist/angular/ds-select-multi.d.ts +0 -28
  305. package/dist/angular/ds-select-multi.js +0 -49
  306. package/dist/angular/ds-shell-gradient-picker.d.ts +0 -16
  307. package/dist/angular/ds-shell-gradient-picker.js +0 -42
  308. package/dist/angular/ds-shell-gradient-swatch.d.ts +0 -16
  309. package/dist/angular/ds-shell-gradient-swatch.js +0 -42
  310. package/dist/components/ds-card-data-viz-bar.d.ts +0 -11
  311. package/dist/components/ds-card-data-viz-bar.js +0 -2
  312. package/dist/components/ds-card-data-viz-bar.js.map +0 -1
  313. package/dist/components/ds-card-data-viz-donut.d.ts +0 -11
  314. package/dist/components/ds-card-data-viz-donut.js +0 -2
  315. package/dist/components/ds-card-data-viz-donut.js.map +0 -1
  316. package/dist/components/ds-card-data-viz-line.d.ts +0 -11
  317. package/dist/components/ds-card-data-viz-line.js +0 -2
  318. package/dist/components/ds-card-data-viz-line.js.map +0 -1
  319. package/dist/components/ds-card-shell-data-viz.d.ts +0 -11
  320. package/dist/components/ds-card-shell-data-viz.js +0 -2
  321. package/dist/components/ds-card-shell-data-viz.js.map +0 -1
  322. package/dist/components/ds-chart-bar-stacked.d.ts +0 -11
  323. package/dist/components/ds-chart-bar-stacked.js +0 -2
  324. package/dist/components/ds-chart-bar-stacked.js.map +0 -1
  325. package/dist/components/ds-select-multi.js +0 -2
  326. package/dist/components/ds-select-multi.js.map +0 -1
  327. package/dist/components/ds-shell-gradient-picker.d.ts +0 -11
  328. package/dist/components/ds-shell-gradient-picker.js +0 -2
  329. package/dist/components/ds-shell-gradient-picker.js.map +0 -1
  330. package/dist/components/ds-shell-gradient-swatch.d.ts +0 -11
  331. package/dist/components/ds-shell-gradient-swatch.js +0 -2
  332. package/dist/components/ds-shell-gradient-swatch.js.map +0 -1
  333. package/dist/components/p-2foAUsDN.js +0 -2
  334. package/dist/components/p-2foAUsDN.js.map +0 -1
  335. package/dist/components/p-B6NvVdeO.js +0 -2
  336. package/dist/components/p-B6NvVdeO.js.map +0 -1
  337. package/dist/components/p-B6vE3ujd.js +0 -2
  338. package/dist/components/p-B6vE3ujd.js.map +0 -1
  339. package/dist/components/p-BBePKRHC.js +0 -2
  340. package/dist/components/p-BBePKRHC.js.map +0 -1
  341. package/dist/components/p-BOQnc-M4.js +0 -2
  342. package/dist/components/p-BOQnc-M4.js.map +0 -1
  343. package/dist/components/p-BXaieghV.js +0 -2
  344. package/dist/components/p-BXaieghV.js.map +0 -1
  345. package/dist/components/p-BgGP65Nn.js.map +0 -1
  346. package/dist/components/p-BrusnEYP.js +0 -2
  347. package/dist/components/p-BrusnEYP.js.map +0 -1
  348. package/dist/components/p-By_s1wNN.js +0 -2
  349. package/dist/components/p-By_s1wNN.js.map +0 -1
  350. package/dist/components/p-C61yJkfr.js +0 -2
  351. package/dist/components/p-C61yJkfr.js.map +0 -1
  352. package/dist/components/p-CH3w-P2H.js +0 -2
  353. package/dist/components/p-CH3w-P2H.js.map +0 -1
  354. package/dist/components/p-CNjOyUQu.js +0 -2
  355. package/dist/components/p-CNjOyUQu.js.map +0 -1
  356. package/dist/components/p-CPwpgUWT.js.map +0 -1
  357. package/dist/components/p-CU2673IJ.js +0 -2
  358. package/dist/components/p-CU2673IJ.js.map +0 -1
  359. package/dist/components/p-CsX3ICz7.js.map +0 -1
  360. package/dist/components/p-D0e77tkd.js +0 -2
  361. package/dist/components/p-D0e77tkd.js.map +0 -1
  362. package/dist/components/p-D1HFMTdC.js +0 -2
  363. package/dist/components/p-D1HFMTdC.js.map +0 -1
  364. package/dist/components/p-D1bNvIpb.js +0 -2
  365. package/dist/components/p-D1bNvIpb.js.map +0 -1
  366. package/dist/components/p-D2x17ei-.js +0 -2
  367. package/dist/components/p-D2x17ei-.js.map +0 -1
  368. package/dist/components/p-DIFUsoM-.js +0 -2
  369. package/dist/components/p-DIFUsoM-.js.map +0 -1
  370. package/dist/components/p-DjbTFy2B.js.map +0 -1
  371. package/dist/components/p-DmAHufd5.js +0 -2
  372. package/dist/components/p-DmAHufd5.js.map +0 -1
  373. package/dist/components/p-DzM3SzPz.js +0 -2
  374. package/dist/components/p-DzM3SzPz.js.map +0 -1
  375. package/dist/components/p-G3qIxpQj.js +0 -2
  376. package/dist/components/p-G3qIxpQj.js.map +0 -1
  377. package/dist/components/p-Re2kPrXm.js +0 -2
  378. package/dist/components/p-Re2kPrXm.js.map +0 -1
  379. package/dist/components/p-huw1oRbt.js +0 -2
  380. package/dist/components/p-huw1oRbt.js.map +0 -1
  381. package/dist/framework/select-multi-value-accessor.d.ts +0 -9
  382. package/dist/framework/select-multi-value-accessor.js +0 -40
  383. package/dist/lib/shell/badge-gradient-ring.d.ts +0 -27
  384. package/dist/lib/shell/chrome-transition.d.ts +0 -32
  385. package/dist/lib/shell/shell-chrome-metrics.d.ts +0 -11
  386. package/dist/lib/shell/shell-gradient.d.ts +0 -41
  387. package/dist/lib/shell/shell-view-transition.d.ts +0 -23
  388. package/dist/mcp-data/registry/card-data-viz-bar.json +0 -213
  389. package/dist/mcp-data/registry/card-data-viz-donut.json +0 -214
  390. package/dist/mcp-data/registry/card-data-viz-line.json +0 -207
  391. package/dist/mcp-data/registry/card-shell-data-viz.json +0 -180
  392. package/dist/mcp-data/registry/chart-bar-stacked.json +0 -230
  393. package/dist/mcp-data/registry/select-multi.json +0 -758
  394. package/dist/mcp-data/registry/shell-gradient-picker.json +0 -195
  395. package/dist/mcp-data/registry/shell-gradient-swatch.json +0 -215
  396. package/dist/react/ds-card-data-viz-bar.d.ts +0 -12
  397. package/dist/react/ds-card-data-viz-bar.js +0 -12
  398. package/dist/react/ds-card-data-viz-donut.d.ts +0 -12
  399. package/dist/react/ds-card-data-viz-donut.js +0 -12
  400. package/dist/react/ds-card-data-viz-line.d.ts +0 -12
  401. package/dist/react/ds-card-data-viz-line.js +0 -12
  402. package/dist/react/ds-card-shell-data-viz.d.ts +0 -9
  403. package/dist/react/ds-card-shell-data-viz.js +0 -12
  404. package/dist/react/ds-chart-bar-stacked.d.ts +0 -9
  405. package/dist/react/ds-chart-bar-stacked.js +0 -12
  406. package/dist/react/ds-select-multi.d.ts +0 -14
  407. package/dist/react/ds-select-multi.js +0 -16
  408. package/dist/react/ds-shell-gradient-picker.d.ts +0 -12
  409. package/dist/react/ds-shell-gradient-picker.js +0 -12
  410. package/dist/react/ds-shell-gradient-swatch.d.ts +0 -12
  411. package/dist/react/ds-shell-gradient-swatch.js +0 -12
  412. package/dist/types/components/CardDataVizBar/CardDataVizBar.d.ts +0 -24
  413. package/dist/types/components/CardDataVizBar/index.d.ts +0 -2
  414. package/dist/types/components/CardDataVizDonut/CardDataVizDonut.d.ts +0 -28
  415. package/dist/types/components/CardDataVizDonut/index.d.ts +0 -2
  416. package/dist/types/components/CardDataVizLine/CardDataVizLine.d.ts +0 -24
  417. package/dist/types/components/CardDataVizLine/index.d.ts +0 -2
  418. package/dist/types/components/CardShellDataViz/CardShellDataViz.d.ts +0 -13
  419. package/dist/types/components/CardShellDataViz/index.d.ts +0 -2
  420. package/dist/types/components/ChartBarStacked/ChartBarStacked.d.ts +0 -26
  421. package/dist/types/components/ChartBarStacked/index.d.ts +0 -3
  422. package/dist/types/components/SelectMulti/SelectMulti.d.ts +0 -121
  423. package/dist/types/components/SelectMulti/index.d.ts +0 -2
  424. package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +0 -12
  425. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +0 -15
  426. package/dist/types/components/ShellGradientSwatch/shell-gradient-swatch-types.d.ts +0 -4
  427. /package/dist/components/{p-DB9XJm2c.js.map → p-6qXDqCj_.js.map} +0 -0
  428. /package/dist/components/{p-DhLmoCe_.js.map → p-B1cbQ0SG.js.map} +0 -0
  429. /package/dist/components/{p-BKOlvCGS.js.map → p-BUZXuod2.js.map} +0 -0
  430. /package/dist/components/{p-Utr23NC-.js.map → p-BaUsq_VK.js.map} +0 -0
  431. /package/dist/components/{p-BO5aqZcf.js.map → p-BcTYhmku.js.map} +0 -0
  432. /package/dist/components/{p-BlHzmszE.js.map → p-BroVnDzE.js.map} +0 -0
  433. /package/dist/components/{p-BFRMwl93.js.map → p-BsoqsbBh.js.map} +0 -0
  434. /package/dist/components/{p-ukB-dPOK.js.map → p-CBazaRlE.js.map} +0 -0
  435. /package/dist/components/{p-CNgPwd11.js.map → p-CHuh71-U.js.map} +0 -0
  436. /package/dist/components/{p-D9u1_OEZ.js.map → p-CMWata9g.js.map} +0 -0
  437. /package/dist/components/{p-38PfdMkF.js.map → p-DeQwkbT1.js.map} +0 -0
  438. /package/dist/components/{p-Vdufoj7G.js.map → p-Df17mJqI.js.map} +0 -0
  439. /package/dist/components/{p-69lHhJiX.js.map → p-ohsLFap_.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "panel-tools",
4
4
  "title": "PanelTools",
5
- "description": "Required ShellApp utility rail and persistent drawer host for the fixed set of global Search, Agents, Messages, Stacks, Activity, and Help products.",
5
+ "description": "Desktop and tablet utility rail-and-drawer primitive for explicitly defined application-wide tools, normally assembled by managed ShellApp through ShellTools.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
@@ -13,17 +13,17 @@
13
13
  "intent": {
14
14
  "audience": "specialized",
15
15
  "status": "stable",
16
- "summary": "Required ShellApp utility rail and persistent drawer host for the fixed set of global Search, Agents, Messages, Stacks, Activity, and Help products.",
16
+ "summary": "Desktop and tablet utility rail-and-drawer primitive for explicitly defined application-wide tools, normally assembled by managed ShellApp through ShellTools.",
17
17
  "useWhen": [
18
- "Composing the required global tools region inside the application's single ShellApp.",
18
+ "An advanced slotted ShellApp needs the desktop and tablet global-tools primitive directly.",
19
19
  "Users need to move between application-wide utilities without losing each tool's in-progress state.",
20
20
  "Search, AI conversations, user messages, review stacks, notifications, or help content must remain available alongside route content."
21
21
  ],
22
22
  "avoidWhen": [
23
23
  "The action is specific to the current page or selected content rather than globally available across the application.",
24
24
  "The item navigates to a primary or secondary route; use PanelNav or BarNav instead.",
25
- "A consumer wants to add arbitrary tools, reorder fixed tools, or repurpose a semantic tool id.",
26
- "Building the mobile tools experience; future mobile ShellApp composition uses a different bottom-bar and panel pattern.",
25
+ "Tool definitions lack stable ids, visible labels, explicit rail placement, or application-owned view content.",
26
+ "Building the mobile tools experience; use ShellTools or managed ShellApp.",
27
27
  "PanelTools would be mounted outside the application's single ShellApp."
28
28
  ],
29
29
  "alternatives": [
@@ -53,16 +53,16 @@
53
53
  ],
54
54
  "accessibility": [
55
55
  "Provide localized accessible labels for the rail, shortcut group, and every rendered tool.",
56
- "Keep Search first in the rail header and Help last in the rail footer so keyboard and visual order remain predictable.",
56
+ "Use the canonical default recipe when the product needs Search first and Help last; custom recipes must preserve a deliberate keyboard and visual order.",
57
57
  "Keep focus on the selected rail action when opening a drawer and allow normal Tab navigation into its content.",
58
58
  "Do not trigger global shortcuts while focus is in an editable control.",
59
59
  "Use notification dots only as supplemental unread or new-content status and never as the sole status indication.",
60
60
  "Explain temporarily inactive tools through an accessible label or adjacent product guidance; omit unauthorized tools entirely."
61
61
  ],
62
62
  "states": [
63
- "Search and Help are required; optional Agents, Messages, Stacks, and Activity tools are filtered by the application for authorization and entitlement.",
64
- "Use only the fixed semantic tool ids and canonical order: Search, Agents, Messages, Stacks, Activity, Help; do not reorder, duplicate, or repurpose them.",
65
- "Search is application-wide; Agents is user-to-AI chat; Messages is user-to-user chat; Stacks is a specialized inbox of grouped application events for review; Activity is the notification center; Help is drawer content that may link to external resources.",
63
+ "Every tool has a stable explicit id and accessible label. The application filters definitions for authorization and entitlement.",
64
+ "Tool order, header or footer rail placement, shortcut, and mobile grouping come from explicit item metadata rather than id-based layout assumptions.",
65
+ "PANEL_TOOLS_DEFAULT_ITEMS is the canonical Lab recipe: Search pinned first, Help pinned last, current shortcuts, and Messages, Stacks, and Activity grouped under mobile Inbox.",
66
66
  "Only one drawer is open at a time; selecting another tool switches directly and selecting the active tool closes to rail-only mode.",
67
67
  "The drawer starts closed, remains open across ordinary route changes, and retains every mounted tool's independent conversation, query, scroll, and work state.",
68
68
  "When storageKey is set, persist only the last active tool in this browser and still restore the drawer closed after reload.",
@@ -85,19 +85,19 @@
85
85
  "responsiveBehavior": [
86
86
  "The open drawer reduces main-content width and uses --dimension-panel-width-xs (300px) on every supported desktop and tablet viewport.",
87
87
  "ShellApp owns the fixed drawer-width variable; consumers must not hardcode alternate drawer widths.",
88
- "Below 768px, do not reuse desktop PanelTools: future ShellApp mobile composition uses a bottom bar for Search, Agents, Notifications, and Stacks, while Help moves into mobile primary navigation.",
88
+ "Below 768px, do not reuse PanelTools directly; ShellTools and managed ShellApp present the same persistent tool owners through mobile chrome.",
89
89
  "Keep named-slot content mounted while tools switch or the drawer closes so users can return to in-progress global work.",
90
90
  "Conversation products may use fullscreenHeaderMode split for a 300px history rail beside a flexible detail pane, each with its own canonical header; the application owns that internal split layout.",
91
91
  "Use fixed drawer widths for now; do not add user resizing or width persistence."
92
92
  ],
93
93
  "frameworkCaveats": {
94
94
  "customElements": [
95
- "Assign items as a JavaScript property for structured data; items-json is the string fallback.",
95
+ "Assign items and headers as JavaScript properties and replace their references when collections change.",
96
96
  "Assign headers as a JavaScript property and replace its object identity whenever an active tool changes title, depth, or actions.",
97
97
  "Keep PanelTools mounted at ShellApp scope across route changes and provide each product through its matching named slot."
98
98
  ],
99
99
  "react": [
100
- "Pass the fixed item collection through the generated React wrapper and keep named-slot product content mounted at ShellApp scope.",
100
+ "Pass a stable explicitly ordered item collection through the generated React wrapper and keep named-slot product content mounted at ShellApp scope.",
101
101
  "Use storageKey only for same-browser last-tool continuity; do not persist the open drawer."
102
102
  ],
103
103
  "angular": [
@@ -125,7 +125,7 @@
125
125
  "props": {
126
126
  "activeTool": {
127
127
  "type": "PanelToolsToolId | ''",
128
- "resolvedType": "\"\" | \"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\"",
128
+ "resolvedType": "string",
129
129
  "attribute": "active-tool",
130
130
  "default": "''",
131
131
  "required": false,
@@ -143,21 +143,12 @@
143
143
  },
144
144
  "headers": {
145
145
  "type": "PanelToolsHeaders",
146
- "resolvedType": "\"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\" | PanelToolsHeaderConfig",
146
+ "resolvedType": "PanelToolsHeaderConfig | string",
147
147
  "default": "{}",
148
148
  "required": false,
149
149
  "mutable": false,
150
150
  "description": "Active header state per tool. Replace the object when title, depth, or actions change."
151
151
  },
152
- "headersJson": {
153
- "type": "string",
154
- "resolvedType": "string",
155
- "attribute": "headers-json",
156
- "default": "''",
157
- "required": false,
158
- "mutable": false,
159
- "description": "JSON fallback for `headers`. The `headers` property takes precedence when non-empty."
160
- },
161
152
  "items": {
162
153
  "type": "PanelToolsItem[]",
163
154
  "resolvedType": "PanelToolsItem[]",
@@ -166,15 +157,6 @@
166
157
  "mutable": false,
167
158
  "description": "Rail items rendered in the right column.\nSet via JS property: `el.items = [...]`. Replace the array reference to update."
168
159
  },
169
- "itemsJson": {
170
- "type": "string",
171
- "resolvedType": "string",
172
- "attribute": "items-json",
173
- "default": "''",
174
- "required": false,
175
- "mutable": false,
176
- "description": "JSON fallback for `items` — useful when framework bindings don't propagate arrays."
177
- },
178
160
  "open": {
179
161
  "type": "boolean",
180
162
  "resolvedType": "boolean",
@@ -290,7 +272,7 @@
290
272
  "props": {
291
273
  "activeTool": {
292
274
  "type": "PanelToolsToolId | ''",
293
- "resolvedType": "\"\" | \"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\"",
275
+ "resolvedType": "string",
294
276
  "attribute": "active-tool",
295
277
  "default": "''",
296
278
  "required": false,
@@ -308,21 +290,12 @@
308
290
  },
309
291
  "headers": {
310
292
  "type": "PanelToolsHeaders",
311
- "resolvedType": "\"activity\" | \"agents\" | \"help\" | \"messages\" | \"search\" | \"stacks\" | PanelToolsHeaderConfig",
293
+ "resolvedType": "PanelToolsHeaderConfig | string",
312
294
  "default": "{}",
313
295
  "required": false,
314
296
  "mutable": false,
315
297
  "description": "Active header state per tool. Replace the object when title, depth, or actions change."
316
298
  },
317
- "headersJson": {
318
- "type": "string",
319
- "resolvedType": "string",
320
- "attribute": "headers-json",
321
- "default": "''",
322
- "required": false,
323
- "mutable": false,
324
- "description": "JSON fallback for `headers`. The `headers` property takes precedence when non-empty."
325
- },
326
299
  "items": {
327
300
  "type": "PanelToolsItem[]",
328
301
  "resolvedType": "PanelToolsItem[]",
@@ -331,15 +304,6 @@
331
304
  "mutable": false,
332
305
  "description": "Rail items rendered in the right column.\nSet via JS property: `el.items = [...]`. Replace the array reference to update."
333
306
  },
334
- "itemsJson": {
335
- "type": "string",
336
- "resolvedType": "string",
337
- "attribute": "items-json",
338
- "default": "''",
339
- "required": false,
340
- "mutable": false,
341
- "description": "JSON fallback for `items` — useful when framework bindings don't propagate arrays."
342
- },
343
307
  "open": {
344
308
  "type": "boolean",
345
309
  "resolvedType": "boolean",
@@ -498,17 +462,17 @@
498
462
  },
499
463
  {
500
464
  "path": "src/wc/components/PanelTools/PanelTools.tsx",
501
- "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_FOOTER_TOOL_ID,\n PANEL_TOOLS_LABELS,\n PANEL_TOOLS_PRIMARY_TOOL_ID,\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 parsePanelToolsItems,\n panelToolsDrawerResting,\n panelToolsDrawerTransitionMs,\n reconcilePanelToolsAvailability,\n resolvePanelToolActivation,\n} from './panel-tools-utils';\n\n@Component({\n tag: 'ds-panel-tools',\n styleUrl: 'PanelTools.css',\n scoped: 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 /** JSON fallback for `items` — useful when framework bindings don't propagate arrays. */\n @Prop({ attribute: 'items-json' }) itemsJson: string = '';\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 /** JSON fallback for `headers`. The `headers` property takes precedence when non-empty. */\n @Prop({ attribute: 'headers-json' }) headersJson: string = '';\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\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\n private get railItems(): PanelToolsItem[] {\n return parsePanelToolsItems(this.items, this.itemsJson);\n }\n\n private get orderedRailItems(): PanelToolsItem[] {\n const railItems = orderPanelToolsItems(this.railItems);\n const headerItem = railItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);\n const footerItem = railItems.find(item => item.id === PANEL_TOOLS_FOOTER_TOOL_ID);\n const bodyItems = railItems.filter(\n item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID && item.id !== PANEL_TOOLS_FOOTER_TOOL_ID\n );\n const ordered: PanelToolsItem[] = [];\n if (headerItem) ordered.push(headerItem);\n ordered.push(...bodyItems);\n if (footerItem) ordered.push(footerItem);\n return ordered;\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 }\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.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 @Watch('items')\n @Watch('itemsJson')\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.el.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.el.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: PanelToolsToolId[] = ['search', 'messages', 'stacks', 'activity', 'agents', 'help'];\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 headerLabel(): string {\n if (!this.isDrawerPresent() || !this.activeTool) return '';\n return PANEL_TOOLS_LABELS[this.activeTool as PanelToolsToolId] ?? '';\n }\n\n private resolvedHeaders(): PanelToolsHeaders {\n if (Object.keys(this.headers).length) return this.headers;\n if (!this.headersJson.trim()) return {};\n try {\n const parsed = JSON.parse(this.headersJson);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as PanelToolsHeaders)\n : {};\n } catch {\n return {};\n }\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.el.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.el.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 = item.ariaLabel ?? PANEL_TOOLS_LABELS[item.id];\n return (\n <ds-tooltip\n key={item.id}\n label={label}\n shortcutKey={PANEL_TOOLS_SHORTCUTS[item.id]}\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 headerItem = orderedRailItems.find(item => item.id === PANEL_TOOLS_PRIMARY_TOOL_ID);\n const footerItem = orderedRailItems.find(item => item.id === PANEL_TOOLS_FOOTER_TOOL_ID);\n const bodyItems = orderedRailItems.filter(\n item => item.id !== PANEL_TOOLS_PRIMARY_TOOL_ID && item.id !== PANEL_TOOLS_FOOTER_TOOL_ID\n );\n const footerIndex = footerItem ? (headerItem ? 1 : 0) + bodyItems.length : -1;\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: PanelToolsToolId[] = [\n 'search',\n 'messages',\n 'stacks',\n 'activity',\n 'agents',\n 'help',\n ];\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 {headerItem ? (\n <div class=\"panel-tools__rail-header ds-chrome-row ds-chrome-space--md\">\n {this.renderRailAction(headerItem, 0)}\n </div>\n ) : null}\n <div class=\"panel-tools__rail-body ds-chrome-column ds-chrome-space--md ds-scrollbar-hidden\">\n <div class=\"panel-tools__rail-actions\">\n {bodyItems.map((item, bodyIdx) =>\n this.renderRailAction(item, headerItem ? bodyIdx + 1 : bodyIdx)\n )}\n </div>\n </div>\n {footerItem ? (\n <div class=\"panel-tools__rail-footer ds-chrome-row ds-chrome-space--md\">\n {this.renderRailAction(footerItem, footerIndex)}\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",
465
+ "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 scoped: 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\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\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 }\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.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 @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.el.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.el.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 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.el.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.el.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 class=\"panel-tools__rail-body ds-chrome-column ds-chrome-space--md ds-scrollbar-hidden\">\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",
502
466
  "type": "registry:ui"
503
467
  },
504
468
  {
505
469
  "path": "src/wc/components/PanelTools/panel-tools-types.ts",
506
- "content": "/** Tool ids for the `ds-panel-tools` rail and drawer views. */\nexport type PanelToolsToolId = 'search' | 'agents' | 'messages' | 'stacks' | 'activity' | 'help';\n\nexport const PANEL_TOOLS_TOOL_IDS: PanelToolsToolId[] = [\n 'search',\n 'agents',\n 'messages',\n 'stacks',\n 'activity',\n 'help',\n];\n\n/** Rail header slot — mirrors panel-nav M logo row. */\nexport const PANEL_TOOLS_PRIMARY_TOOL_ID: PanelToolsToolId = 'search';\n\n/** Rail footer slot — flush to the bottom of the tools column. */\nexport const PANEL_TOOLS_FOOTER_TOOL_ID: PanelToolsToolId = 'help';\n\nexport const PANEL_TOOLS_LABELS: Record<PanelToolsToolId, 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<PanelToolsToolId, 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 /** 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\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",
470
+ "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 | 'agents'\n | 'messages'\n | 'stacks'\n | 'activity'\n | 'help';\n\nexport const PANEL_TOOLS_TOOL_IDS: CanonicalPanelToolsToolId[] = [\n 'search',\n 'agents',\n 'messages',\n 'stacks',\n 'activity',\n 'help',\n];\n\n/** Rail header slot — mirrors panel-nav M logo row. */\nexport const PANEL_TOOLS_PRIMARY_TOOL_ID: CanonicalPanelToolsToolId = 'search';\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 Search/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 existing defaults. */\n mobileDestination?: 'search' | 'agents' | 'inbox' | '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: 'search',\n icon: 'MagnifyingGlass',\n label: PANEL_TOOLS_LABELS.search,\n railPlacement: 'header',\n order: 0,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.search,\n mobileDestination: 'search',\n },\n {\n id: 'agents',\n icon: 'AI',\n label: PANEL_TOOLS_LABELS.agents,\n order: 1,\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: 2,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.messages,\n mobileDestination: 'inbox',\n },\n {\n id: 'stacks',\n icon: 'ViewMenu',\n label: PANEL_TOOLS_LABELS.stacks,\n order: 3,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.stacks,\n mobileDestination: 'inbox',\n },\n {\n id: 'activity',\n icon: 'Bell',\n label: PANEL_TOOLS_LABELS.activity,\n order: 4,\n shortcutKey: PANEL_TOOLS_SHORTCUTS.activity,\n mobileDestination: 'inbox',\n },\n {\n id: 'help',\n icon: 'CircleQuestion',\n label: PANEL_TOOLS_LABELS.help,\n railPlacement: 'footer',\n order: 5,\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",
507
471
  "type": "registry:ui"
508
472
  },
509
473
  {
510
474
  "path": "src/wc/components/PanelTools/panel-tools-utils.ts",
511
- "content": "import { parseJsonArrayProp } from '../BarNav/bar-nav-utils';\nimport { parseCssTimeMs } from '../../utils/resolve-css-time-ms';\nimport {\n PANEL_TOOLS_TOOL_IDS,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from './panel-tools-types';\n\nexport function parsePanelToolsItems(items: PanelToolsItem[], itemsJson: string): PanelToolsItem[] {\n if (itemsJson) {\n return parseJsonArrayProp<PanelToolsItem>(itemsJson, []);\n }\n return items ?? [];\n}\n\nexport function orderPanelToolsItems(items: PanelToolsItem[]): PanelToolsItem[] {\n const firstById = new Map<PanelToolsToolId, PanelToolsItem>();\n for (const item of items) {\n if (!firstById.has(item.id)) firstById.set(item.id, item);\n }\n return PANEL_TOOLS_TOOL_IDS.flatMap(id => {\n const item = firstById.get(id);\n return item ? [item] : [];\n });\n}\n\nexport function isPanelToolsToolId(value: string | null): value is PanelToolsToolId {\n return value !== null && PANEL_TOOLS_TOOL_IDS.includes(value as PanelToolsToolId);\n}\n\nexport function reconcilePanelToolsAvailability(\n items: PanelToolsItem[],\n open: boolean,\n activeTool: PanelToolsToolId | ''\n): { open: boolean; activeTool: PanelToolsToolId | ''; removedTool: PanelToolsToolId | '' } {\n if (!activeTool || items.some(item => item.id === activeTool)) {\n return { open, activeTool, removedTool: '' };\n }\n return { open: false, activeTool: '', removedTool: activeTool };\n}\n\nexport function shouldResyncPanelToolsItems(\n prev: PanelToolsItem[],\n next: PanelToolsItem[]\n): boolean {\n if (prev.length !== next.length) return true;\n return next.some((item, index) => {\n const prior = prev[index];\n if (!prior) return true;\n return (\n prior.id !== item.id ||\n prior.icon !== item.icon ||\n prior.selected !== item.selected ||\n prior.dot !== item.dot ||\n prior.isInactive !== item.isInactive ||\n prior.ariaLabel !== item.ariaLabel\n );\n });\n}\n\nexport type PanelToolsMotion = 'opening' | 'closing' | 'idle';\n\nexport interface PanelToolsTransitionStyle {\n transitionProperty: string;\n transitionDuration: string;\n transitionDelay: string;\n}\n\n/** Maximum computed time for the drawer's max-width transition, including delay. */\nexport function panelToolsDrawerTransitionMs(style: PanelToolsTransitionStyle): number {\n const properties = style.transitionProperty.split(',').map(item => item.trim());\n const durations = style.transitionDuration.split(',').map(item => parseCssTimeMs(item.trim(), 0));\n const delays = style.transitionDelay.split(',').map(item => parseCssTimeMs(item.trim(), 0));\n\n return properties.reduce((max, property, index) => {\n if (property !== 'all' && property !== 'max-width') return max;\n const duration = durations[index % durations.length] ?? 0;\n const delay = delays[index % delays.length] ?? 0;\n return Math.max(max, duration + delay, 0);\n }, 0);\n}\n\n/** True when the drawer clip is fully closed — safe to skip painting slot content. */\nexport function panelToolsDrawerResting(open: boolean, motion: PanelToolsMotion): boolean {\n return !open && motion === 'idle';\n}\n\n/** True only when the animated clip has reached the terminal width for its motion phase. */\nexport function panelToolsDrawerAtTerminal(\n width: number,\n targetWidth: number,\n motion: PanelToolsMotion\n): boolean {\n if (motion === 'closing') return width <= 0.5;\n if (motion === 'opening') return targetWidth > 0 && width >= targetWidth - 0.5;\n return true;\n}\n\n/** Toggle or switch rail tool selection — repeat activation closes the active tool. */\nexport function resolvePanelToolActivation(\n open: boolean,\n activeTool: PanelToolsToolId | '',\n id: PanelToolsToolId\n): { open: boolean; activeTool: PanelToolsToolId; selected: boolean } {\n if (open && activeTool === id) {\n return { open: false, activeTool: id, selected: false };\n }\n return { open: true, activeTool: id, selected: true };\n}\n",
475
+ "content": "import { parseCssTimeMs } from '../../utils/resolve-css-time-ms';\nimport {\n PANEL_TOOLS_TOOL_IDS,\n type PanelToolsItem,\n type PanelToolsToolId,\n} from './panel-tools-types';\n\nexport function orderPanelToolsItems(items: PanelToolsItem[]): PanelToolsItem[] {\n const firstById = new Map<PanelToolsToolId, PanelToolsItem>();\n for (const item of items) {\n if (item.id.trim() && !firstById.has(item.id)) firstById.set(item.id, item);\n }\n const regionOrder = { header: 0, body: 1, footer: 2 } as const;\n return Array.from(firstById.values()).sort((left, right) => {\n const leftRegion = panelToolsRailPlacement(left);\n const rightRegion = panelToolsRailPlacement(right);\n const regionDelta = regionOrder[leftRegion] - regionOrder[rightRegion];\n if (regionDelta !== 0) return regionDelta;\n\n const leftCanonical = PANEL_TOOLS_TOOL_IDS.findIndex(id => id === left.id);\n const rightCanonical = PANEL_TOOLS_TOOL_IDS.findIndex(id => id === right.id);\n const leftOrder = left.order ?? (leftCanonical < 0 ? Number.MAX_SAFE_INTEGER : leftCanonical);\n const rightOrder =\n right.order ?? (rightCanonical < 0 ? Number.MAX_SAFE_INTEGER : rightCanonical);\n return leftOrder - rightOrder;\n });\n}\n\nexport function isPanelToolsToolId(value: string | null): value is PanelToolsToolId {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nexport function panelToolsRailPlacement(\n item: PanelToolsItem\n): 'header' | 'body' | 'footer' {\n if (item.railPlacement) return item.railPlacement;\n if (item.id === 'search') return 'header';\n if (item.id === 'help') return 'footer';\n return 'body';\n}\n\nexport function reconcilePanelToolsAvailability(\n items: PanelToolsItem[],\n open: boolean,\n activeTool: PanelToolsToolId | ''\n): { open: boolean; activeTool: PanelToolsToolId | ''; removedTool: PanelToolsToolId | '' } {\n if (!activeTool || items.some(item => item.id === activeTool)) {\n return { open, activeTool, removedTool: '' };\n }\n return { open: false, activeTool: '', removedTool: activeTool };\n}\n\nexport function shouldResyncPanelToolsItems(\n prev: PanelToolsItem[],\n next: PanelToolsItem[]\n): boolean {\n if (prev.length !== next.length) return true;\n return next.some((item, index) => {\n const prior = prev[index];\n if (!prior) return true;\n return (\n prior.id !== item.id ||\n prior.icon !== item.icon ||\n prior.label !== item.label ||\n prior.railPlacement !== item.railPlacement ||\n prior.order !== item.order ||\n prior.shortcutKey !== item.shortcutKey ||\n prior.mobileDestination !== item.mobileDestination ||\n prior.selected !== item.selected ||\n prior.dot !== item.dot ||\n prior.isInactive !== item.isInactive ||\n prior.ariaLabel !== item.ariaLabel\n );\n });\n}\n\nexport type PanelToolsMotion = 'opening' | 'closing' | 'idle';\n\nexport interface PanelToolsTransitionStyle {\n transitionProperty: string;\n transitionDuration: string;\n transitionDelay: string;\n}\n\n/** Maximum computed time for the drawer's max-width transition, including delay. */\nexport function panelToolsDrawerTransitionMs(style: PanelToolsTransitionStyle): number {\n const properties = style.transitionProperty.split(',').map(item => item.trim());\n const durations = style.transitionDuration.split(',').map(item => parseCssTimeMs(item.trim(), 0));\n const delays = style.transitionDelay.split(',').map(item => parseCssTimeMs(item.trim(), 0));\n\n return properties.reduce((max, property, index) => {\n if (property !== 'all' && property !== 'max-width') return max;\n const duration = durations[index % durations.length] ?? 0;\n const delay = delays[index % delays.length] ?? 0;\n return Math.max(max, duration + delay, 0);\n }, 0);\n}\n\n/** True when the drawer clip is fully closed — safe to skip painting slot content. */\nexport function panelToolsDrawerResting(open: boolean, motion: PanelToolsMotion): boolean {\n return !open && motion === 'idle';\n}\n\n/** True only when the animated clip has reached the terminal width for its motion phase. */\nexport function panelToolsDrawerAtTerminal(\n width: number,\n targetWidth: number,\n motion: PanelToolsMotion\n): boolean {\n if (motion === 'closing') return width <= 0.5;\n if (motion === 'opening') return targetWidth > 0 && width >= targetWidth - 0.5;\n return true;\n}\n\n/** Toggle or switch rail tool selection — repeat activation closes the active tool. */\nexport function resolvePanelToolActivation(\n open: boolean,\n activeTool: PanelToolsToolId | '',\n id: PanelToolsToolId\n): { open: boolean; activeTool: PanelToolsToolId; selected: boolean } {\n if (open && activeTool === id) {\n return { open: false, activeTool: id, selected: false };\n }\n return { open: true, activeTool: id, selected: true };\n}\n",
512
476
  "type": "registry:ui"
513
477
  }
514
478
  ]
@@ -367,7 +367,7 @@
367
367
  },
368
368
  {
369
369
  "path": "src/wc/components/Radio/Radio.tsx",
370
- "content": "import { AttachInternals, Component, Prop, State, Event, EventEmitter, Element, Listen, Watch, h, Host } from '@stencil/core';\nimport { CONTROL_TEXT_VARIANT, DEFAULT_REQUIRED_MESSAGE, setRequiredValidity } from '../../utils';\n\nexport interface RadioOption {\n label: string;\n value: string;\n isInactive?: boolean;\n}\n\nexport type RadioSize = 'lg' | 'md' | 'sm' | 'xs';\n\n@Component({\n tag: 'ds-radio',\n styleUrl: 'Radio.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Radio {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Visible choices in this one-of-many set. */\n @Prop() options: RadioOption[] = [];\n /** Selected option value. */\n @Prop({ mutable: true }) value: string = '';\n /** Visual and placement density for every option. */\n @Prop() size: RadioSize = 'md';\n /** Native form field name. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Associates the radio set with a form by id when rendered outside that form. */\n @Prop({ reflect: true }) form: string | undefined;\n /** Native disabled state for the complete set. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require one option to be selected for form validity. */\n @Prop({ reflect: true }) required: boolean = false;\n /** Validation message used when a required set has no selection. */\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n /** Layout direction for the option set. */\n @Prop() direction: 'vertical' | 'horizontal' = 'vertical';\n /** Design-system inactive state for the complete set. */\n @Prop() isInactive: boolean = false;\n /** Accessible name when visible group labeling is unavailable. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** Id reference for a visible group label. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n\n /** Emitted after user selection with the selected option value. */\n @Event() dsChange!: EventEmitter<string>;\n\n private initialValue = '';\n @State() private formDisabled = false;\n\n componentWillLoad() {\n this.initialValue = this.value;\n this.syncFormValue();\n }\n\n @Watch('value')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n syncFormValue() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n this.internals.setFormValue(inactive ? null : this.value);\n const missing = this.required && !inactive && this.value.length === 0;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.initialValue;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n this.value = typeof state === 'string' ? state : '';\n }\n\n private get activeItems(): HTMLElement[] {\n return Array.from(this.el.querySelectorAll<HTMLElement>('[data-radio-item]:not([data-inactive])'));\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n const items = this.activeItems;\n if (!items.length) return;\n\n const focused = items.find(item => item === document.activeElement);\n if (!focused) return;\n\n const idx = items.indexOf(focused);\n let next = -1;\n\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n e.preventDefault();\n next = (idx + 1) % items.length;\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n e.preventDefault();\n next = (idx - 1 + items.length) % items.length;\n break;\n case 'Home':\n e.preventDefault();\n next = 0;\n break;\n case 'End':\n e.preventDefault();\n next = items.length - 1;\n break;\n }\n\n if (next >= 0) {\n items[next].focus();\n const nextValue = items[next].dataset['value'];\n if (nextValue && nextValue !== this.value) {\n this.value = nextValue;\n this.dsChange.emit(nextValue);\n }\n }\n }\n\n private selectItem(optValue: string) {\n if (this.isInactive || this.disabled || this.formDisabled) return;\n if (optValue !== this.value) {\n this.value = optValue;\n this.dsChange.emit(optValue);\n }\n }\n\n render() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const invalid = this.required && !inactive && this.value.length === 0;\n const selectedIdx = this.options.findIndex(option => option.value === this.value);\n const firstActiveIdx = this.options.findIndex(option => !inactive && !option.isInactive);\n const focusableIdx = selectedIdx >= 0 && !inactive && !this.options[selectedIdx]?.isInactive\n ? selectedIdx\n : firstActiveIdx;\n\n return (\n <Host\n role=\"radiogroup\"\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-required={this.required ? 'true' : undefined}\n aria-invalid={invalid ? 'true' : undefined}\n class={{\n radio: true,\n 'radio--horizontal': this.direction === 'horizontal',\n [`radio--${this.size}`]: true,\n }}\n >\n {this.options.map((option, index) => {\n const isItemInactive = inactive || !!option.isInactive;\n const isChecked = option.value === this.value;\n const tabIdx = isItemInactive ? -1 : index === focusableIdx ? 0 : -1;\n\n return (\n <div\n key={option.value}\n role=\"radio\"\n aria-checked={String(isChecked)}\n aria-disabled={isItemInactive ? 'true' : undefined}\n tabIndex={tabIdx}\n data-radio-item\n data-value={option.value}\n data-inactive={isItemInactive || undefined}\n class={{\n radio__item: true,\n [`ds-control--${this.size}`]: true,\n 'ds-control-inactive': isItemInactive,\n 'ds-focus-ring-inset': !isItemInactive,\n 'ds-interaction-fill': !isItemInactive,\n }}\n onClick={() => !isItemInactive && this.selectItem(option.value)}\n onKeyDown={(e: KeyboardEvent) => {\n if ((e.key === ' ' || e.key === 'Enter') && !isItemInactive) {\n e.preventDefault();\n this.selectItem(option.value);\n }\n }}\n >\n <span class=\"radio__placement ds-interaction-fill__content\" aria-hidden=\"true\">\n <span class={{ radio__circle: true, 'radio__circle--checked': isChecked }}>\n {isChecked && <span class=\"radio__dot\" />}\n </span>\n </span>\n <ds-text\n class=\"radio__label ds-interaction-fill__content\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n >\n {option.label}\n </ds-text>\n </div>\n );\n })}\n </Host>\n );\n }\n}\n",
370
+ "content": "import { AttachInternals, Component, Prop, State, Event, EventEmitter, Element, Listen, Watch, h, Host } from '@stencil/core';\nimport {\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n restoreStringFormState,\n setFormControlValue,\n setRequiredValidity,\n} from '../../utils';\n\nexport interface RadioOption {\n label: string;\n value: string;\n isInactive?: boolean;\n}\n\nexport type RadioSize = 'lg' | 'md' | 'sm' | 'xs';\n\n@Component({\n tag: 'ds-radio',\n styleUrl: 'Radio.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Radio {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Visible choices in this one-of-many set. */\n @Prop() options: RadioOption[] = [];\n /** Selected option value. */\n @Prop({ mutable: true }) value: string = '';\n /** Visual and placement density for every option. */\n @Prop() size: RadioSize = 'md';\n /** Native form field name. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Associates the radio set with a form by id when rendered outside that form. */\n @Prop({ reflect: true }) form: string | undefined;\n /** Native disabled state for the complete set. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require one option to be selected for form validity. */\n @Prop({ reflect: true }) required: boolean = false;\n /** Validation message used when a required set has no selection. */\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n /** Layout direction for the option set. */\n @Prop() direction: 'vertical' | 'horizontal' = 'vertical';\n /** Design-system inactive state for the complete set. */\n @Prop() isInactive: boolean = false;\n /** Accessible name when visible group labeling is unavailable. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** Id reference for a visible group label. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n\n /** Emitted after user selection with the selected option value. */\n @Event() dsChange!: EventEmitter<string>;\n\n private initialValue = '';\n @State() private formDisabled = false;\n\n componentWillLoad() {\n this.initialValue = this.value;\n this.syncFormValue();\n }\n\n @Watch('value')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n syncFormValue() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n setFormControlValue(this.internals, this.value, { inactive });\n const missing = this.required && !inactive && this.value.length === 0;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.initialValue;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n this.value = restoreStringFormState(state);\n }\n\n private get activeItems(): HTMLElement[] {\n return Array.from(this.el.querySelectorAll<HTMLElement>('[data-radio-item]:not([data-inactive])'));\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n const items = this.activeItems;\n if (!items.length) return;\n\n const focused = items.find(item => item === document.activeElement);\n if (!focused) return;\n\n const idx = items.indexOf(focused);\n let next = -1;\n\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n e.preventDefault();\n next = (idx + 1) % items.length;\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n e.preventDefault();\n next = (idx - 1 + items.length) % items.length;\n break;\n case 'Home':\n e.preventDefault();\n next = 0;\n break;\n case 'End':\n e.preventDefault();\n next = items.length - 1;\n break;\n }\n\n if (next >= 0) {\n items[next].focus();\n const nextValue = items[next].dataset['value'];\n if (nextValue && nextValue !== this.value) {\n this.value = nextValue;\n this.dsChange.emit(nextValue);\n }\n }\n }\n\n private selectItem(optValue: string) {\n if (this.isInactive || this.disabled || this.formDisabled) return;\n if (optValue !== this.value) {\n this.value = optValue;\n this.dsChange.emit(optValue);\n }\n }\n\n render() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const invalid = this.required && !inactive && this.value.length === 0;\n const selectedIdx = this.options.findIndex(option => option.value === this.value);\n const firstActiveIdx = this.options.findIndex(option => !inactive && !option.isInactive);\n const focusableIdx = selectedIdx >= 0 && !inactive && !this.options[selectedIdx]?.isInactive\n ? selectedIdx\n : firstActiveIdx;\n\n return (\n <Host\n role=\"radiogroup\"\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-required={this.required ? 'true' : undefined}\n aria-invalid={invalid ? 'true' : undefined}\n class={{\n radio: true,\n 'radio--horizontal': this.direction === 'horizontal',\n [`radio--${this.size}`]: true,\n }}\n >\n {this.options.map((option, index) => {\n const isItemInactive = inactive || !!option.isInactive;\n const isChecked = option.value === this.value;\n const tabIdx = isItemInactive ? -1 : index === focusableIdx ? 0 : -1;\n\n return (\n <div\n key={option.value}\n role=\"radio\"\n aria-checked={String(isChecked)}\n aria-disabled={isItemInactive ? 'true' : undefined}\n tabIndex={tabIdx}\n data-radio-item\n data-value={option.value}\n data-inactive={isItemInactive || undefined}\n class={{\n radio__item: true,\n [`ds-control--${this.size}`]: true,\n 'ds-control-inactive': isItemInactive,\n 'ds-focus-ring-inset': !isItemInactive,\n 'ds-interaction-fill': !isItemInactive,\n }}\n onClick={() => !isItemInactive && this.selectItem(option.value)}\n onKeyDown={(e: KeyboardEvent) => {\n if ((e.key === ' ' || e.key === 'Enter') && !isItemInactive) {\n e.preventDefault();\n this.selectItem(option.value);\n }\n }}\n >\n <span class=\"radio__placement ds-interaction-fill__content\" aria-hidden=\"true\">\n <span class={{ radio__circle: true, 'radio__circle--checked': isChecked }}>\n {isChecked && <span class=\"radio__dot\" />}\n </span>\n </span>\n <ds-text\n class=\"radio__label ds-interaction-fill__content\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n >\n {option.label}\n </ds-text>\n </div>\n );\n })}\n </Host>\n );\n }\n}\n",
371
371
  "type": "registry:ui"
372
372
  }
373
373
  ]