@ds-mo/ui 9.2.0 → 10.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 (331) hide show
  1. package/README.md +2 -81
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +1 -1
  4. package/dist/agent.json +400 -135
  5. package/dist/angular/boolean-value-accessor.js +3 -3
  6. package/dist/angular/ds-agent-activity.js +3 -3
  7. package/dist/angular/ds-agent-response.js +3 -3
  8. package/dist/angular/ds-agent-source-list.js +3 -3
  9. package/dist/angular/ds-agent-tool-call.js +3 -3
  10. package/dist/angular/ds-app-shell.js +3 -3
  11. package/dist/angular/ds-attachment-list.js +3 -3
  12. package/dist/angular/ds-avatar.d.ts +1 -1
  13. package/dist/angular/ds-avatar.js +5 -5
  14. package/dist/angular/ds-badge.js +3 -3
  15. package/dist/angular/ds-bar-nav.js +3 -3
  16. package/dist/angular/ds-button-filled.js +3 -3
  17. package/dist/angular/ds-button-unfilled.js +3 -3
  18. package/dist/angular/ds-card-data-viz-donut.js +3 -3
  19. package/dist/angular/ds-card-setting.d.ts +5 -4
  20. package/dist/angular/ds-card-setting.js +6 -6
  21. package/dist/angular/ds-card-shell-data-viz.js +3 -3
  22. package/dist/angular/ds-chart-bar.js +3 -3
  23. package/dist/angular/ds-chart-donut.js +3 -3
  24. package/dist/angular/ds-chart-legend.js +3 -3
  25. package/dist/angular/ds-chart-line.js +3 -3
  26. package/dist/angular/ds-checkbox.js +3 -3
  27. package/dist/angular/ds-chip.js +3 -3
  28. package/dist/angular/ds-code-block.js +3 -3
  29. package/dist/angular/ds-conversation-list-item.d.ts +1 -1
  30. package/dist/angular/ds-conversation-list-item.js +5 -5
  31. package/dist/angular/ds-conversation-list-section.js +3 -3
  32. package/dist/angular/ds-conversation-list.js +3 -3
  33. package/dist/angular/ds-divider.js +3 -3
  34. package/dist/angular/ds-empty-state.js +3 -3
  35. package/dist/angular/ds-field.js +3 -3
  36. package/dist/angular/ds-icon.js +3 -3
  37. package/dist/angular/ds-input.js +3 -3
  38. package/dist/angular/ds-loader.js +3 -3
  39. package/dist/angular/ds-markdown.js +3 -3
  40. package/dist/angular/ds-menu.d.ts +6 -1
  41. package/dist/angular/ds-menu.js +7 -4
  42. package/dist/angular/ds-message-bubble.js +3 -3
  43. package/dist/angular/ds-message-composer.js +3 -3
  44. package/dist/angular/ds-message-scroller.js +3 -3
  45. package/dist/angular/ds-message.js +3 -3
  46. package/dist/angular/ds-modal.d.ts +12 -3
  47. package/dist/angular/ds-modal.js +9 -6
  48. package/dist/angular/ds-panel-nav.d.ts +1 -1
  49. package/dist/angular/ds-panel-nav.js +5 -5
  50. package/dist/angular/ds-panel-sub-nav.js +3 -3
  51. package/dist/angular/ds-panel-tool-header.js +3 -3
  52. package/dist/angular/ds-panel-tool-search.js +3 -3
  53. package/dist/angular/ds-panel-tools.js +3 -3
  54. package/dist/angular/ds-radio.js +3 -3
  55. package/dist/angular/ds-select-multi.js +3 -3
  56. package/dist/angular/ds-select.js +3 -3
  57. package/dist/angular/ds-shell-gradient-picker.js +3 -3
  58. package/dist/angular/ds-shell-gradient-swatch.js +3 -3
  59. package/dist/angular/ds-skeleton.js +3 -3
  60. package/dist/angular/ds-slider.js +3 -3
  61. package/dist/angular/ds-swatch-picker.js +3 -3
  62. package/dist/angular/ds-switch.js +3 -3
  63. package/dist/angular/ds-tab-group.js +3 -3
  64. package/dist/angular/ds-tag.js +3 -3
  65. package/dist/angular/ds-text.js +3 -3
  66. package/dist/angular/ds-toast.d.ts +22 -0
  67. package/dist/angular/{ds-banner.js → ds-toast.js} +21 -15
  68. package/dist/angular/ds-tooltip-data-viz.js +3 -3
  69. package/dist/angular/ds-tooltip.js +3 -3
  70. package/dist/angular/index.d.ts +1 -1
  71. package/dist/angular/index.js +1 -2
  72. package/dist/angular/proxies.d.ts +1 -2
  73. package/dist/angular/proxies.js +1 -2
  74. package/dist/angular/radio-value-accessor.js +3 -3
  75. package/dist/angular/select-value-accessor.js +3 -3
  76. package/dist/angular/text-value-accessor.js +3 -3
  77. package/dist/angular/value-accessor.js +3 -3
  78. package/dist/components/ds-agent-activity.js +1 -1
  79. package/dist/components/ds-agent-response.js +1 -1
  80. package/dist/components/ds-agent-response.js.map +1 -1
  81. package/dist/components/ds-agent-source-list.js +1 -1
  82. package/dist/components/ds-agent-tool-call.js +1 -1
  83. package/dist/components/ds-app-shell.js +1 -1
  84. package/dist/components/ds-attachment-list.js +1 -1
  85. package/dist/components/ds-avatar.js +1 -1
  86. package/dist/components/ds-avatar.js.map +1 -1
  87. package/dist/components/ds-badge.js +1 -1
  88. package/dist/components/ds-bar-nav.js +1 -1
  89. package/dist/components/ds-bar-nav.js.map +1 -1
  90. package/dist/components/ds-button-filled.js +1 -1
  91. package/dist/components/ds-button-unfilled.js +1 -1
  92. package/dist/components/ds-card-data-viz-donut.js +1 -1
  93. package/dist/components/ds-card-data-viz-donut.js.map +1 -1
  94. package/dist/components/ds-card-setting.js +1 -1
  95. package/dist/components/ds-card-setting.js.map +1 -1
  96. package/dist/components/ds-card-shell-data-viz.js +1 -1
  97. package/dist/components/ds-chart-bar.js +1 -1
  98. package/dist/components/ds-chart-donut.js +1 -1
  99. package/dist/components/ds-chart-legend.js +1 -1
  100. package/dist/components/ds-chart-line.js +1 -1
  101. package/dist/components/ds-checkbox.js +1 -1
  102. package/dist/components/ds-chip.js +1 -1
  103. package/dist/components/ds-code-block.js +1 -1
  104. package/dist/components/ds-conversation-list-item.js +1 -1
  105. package/dist/components/ds-conversation-list-item.js.map +1 -1
  106. package/dist/components/ds-conversation-list-section.js +1 -1
  107. package/dist/components/ds-conversation-list-section.js.map +1 -1
  108. package/dist/components/ds-conversation-list.js +1 -1
  109. package/dist/components/ds-conversation-list.js.map +1 -1
  110. package/dist/components/ds-divider.js +1 -1
  111. package/dist/components/ds-empty-state.js +1 -1
  112. package/dist/components/ds-field.js +1 -1
  113. package/dist/components/ds-icon.js +1 -1
  114. package/dist/components/ds-input.js +1 -1
  115. package/dist/components/ds-markdown.js +1 -1
  116. package/dist/components/ds-menu.js +1 -1
  117. package/dist/components/ds-message-bubble.js +1 -1
  118. package/dist/components/ds-message-composer.js +1 -1
  119. package/dist/components/ds-message-composer.js.map +1 -1
  120. package/dist/components/ds-message-scroller.js +1 -1
  121. package/dist/components/ds-message-scroller.js.map +1 -1
  122. package/dist/components/ds-message.js +1 -1
  123. package/dist/components/ds-modal.js +1 -1
  124. package/dist/components/ds-modal.js.map +1 -1
  125. package/dist/components/ds-panel-nav.js +1 -1
  126. package/dist/components/ds-panel-nav.js.map +1 -1
  127. package/dist/components/ds-panel-sub-nav.js +1 -1
  128. package/dist/components/ds-panel-tool-header.js +1 -1
  129. package/dist/components/ds-panel-tool-search.js +1 -1
  130. package/dist/components/ds-panel-tools.js +1 -1
  131. package/dist/components/ds-panel-tools.js.map +1 -1
  132. package/dist/components/ds-radio.js +1 -1
  133. package/dist/components/ds-select-multi.js +1 -1
  134. package/dist/components/ds-select.js +1 -1
  135. package/dist/components/ds-shell-gradient-picker.js +1 -1
  136. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  137. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  138. package/dist/components/ds-skeleton.js +1 -1
  139. package/dist/components/ds-slider.js +1 -1
  140. package/dist/components/ds-slider.js.map +1 -1
  141. package/dist/components/ds-swatch-picker.js +1 -1
  142. package/dist/components/ds-switch.js +1 -1
  143. package/dist/components/ds-tab-group.js +1 -1
  144. package/dist/components/ds-tag.js +1 -1
  145. package/dist/components/ds-tag.js.map +1 -1
  146. package/dist/components/ds-text.js +1 -1
  147. package/dist/components/{ds-card.d.ts → ds-toast.d.ts} +4 -4
  148. package/dist/components/ds-toast.js +2 -0
  149. package/dist/components/ds-toast.js.map +1 -0
  150. package/dist/components/ds-tooltip-data-viz.js +1 -1
  151. package/dist/components/ds-tooltip.js +1 -1
  152. package/dist/components/{p-Bo5wikia.js → p-0kEg-Z4f.js} +2 -2
  153. package/dist/components/p-B4RQoNmL.js +2 -0
  154. package/dist/components/p-B4RQoNmL.js.map +1 -0
  155. package/dist/components/p-B8C_yfLQ.js +2 -0
  156. package/dist/components/p-B8C_yfLQ.js.map +1 -0
  157. package/dist/components/{p-Di-ThR90.js → p-BJUcsBHt.js} +2 -2
  158. package/dist/components/p-BJUcsBHt.js.map +1 -0
  159. package/dist/components/{p-C0sq948W.js → p-BTo4pNdp.js} +2 -2
  160. package/dist/components/p-Bet_UjML.js +2 -0
  161. package/dist/components/{p-BFWC17em.js.map → p-Bet_UjML.js.map} +1 -1
  162. package/dist/components/{p-2br9YDbG.js → p-BkwBtZs3.js} +2 -2
  163. package/dist/components/{p-CZMEhdlA.js → p-CKnoWZd9.js} +2 -2
  164. package/dist/components/p-CKnoWZd9.js.map +1 -0
  165. package/dist/components/p-CLn7QnE0.js +2 -0
  166. package/dist/components/p-CLn7QnE0.js.map +1 -0
  167. package/dist/components/{p-Bx1G7P6W.js → p-CPPAndX2.js} +2 -2
  168. package/dist/components/{p-Di4HBckb.js → p-CS12e52R.js} +2 -2
  169. package/dist/components/{p-BSOZXagj.js → p-CZZYGqKz.js} +2 -2
  170. package/dist/components/p-C_0Q8Cnp.js +2 -0
  171. package/dist/components/p-C_0Q8Cnp.js.map +1 -0
  172. package/dist/components/{p-t9Ei1kHP.js → p-CxyjoTN1.js} +2 -2
  173. package/dist/components/p-CyoHRqy6.js +2 -0
  174. package/dist/components/p-CyoHRqy6.js.map +1 -0
  175. package/dist/components/p-DBJ2I3mH.js +2 -0
  176. package/dist/components/p-DBJ2I3mH.js.map +1 -0
  177. package/dist/components/p-DM-95BX1.js +2 -0
  178. package/dist/components/p-DM-95BX1.js.map +1 -0
  179. package/dist/components/p-DZAtgSZo.js +2 -0
  180. package/dist/components/p-DZAtgSZo.js.map +1 -0
  181. package/dist/components/{p--KbMQblZ.js → p-Dh4q5TSS.js} +2 -2
  182. package/dist/components/{p-DIsDiGcp.js → p-DhsO0939.js} +2 -2
  183. package/dist/components/p-DhsO0939.js.map +1 -0
  184. package/dist/components/{p-CiwdnKKp.js → p-DrDHC7V6.js} +2 -2
  185. package/dist/components/{p-BZAEZENv.js → p-DsbhBb5F.js} +2 -2
  186. package/dist/components/p-DuNZGFkV.js +2 -0
  187. package/dist/components/p-DuNZGFkV.js.map +1 -0
  188. package/dist/components/{p-B1pWGQn1.js → p-P_4vE1ub.js} +2 -2
  189. package/dist/docs/components.json +1647 -1435
  190. package/dist/framework/select-multi-value-accessor.js +3 -3
  191. package/dist/lib/toast/index.d.ts +2 -0
  192. package/dist/lib/toast/index.js +158 -0
  193. package/dist/lib/toast/index.js.map +7 -0
  194. package/dist/lib/toast/toast-manager.d.ts +3 -0
  195. package/dist/lib/toast/toast-types.d.ts +85 -0
  196. package/dist/mcp-data/registry/agent-activity.json +1 -1
  197. package/dist/mcp-data/registry/agent-response.json +3 -3
  198. package/dist/mcp-data/registry/agent-source-list.json +1 -1
  199. package/dist/mcp-data/registry/agent-tool-call.json +1 -1
  200. package/dist/mcp-data/registry/app-shell.json +1 -1
  201. package/dist/mcp-data/registry/attachment-list.json +1 -1
  202. package/dist/mcp-data/registry/avatar.json +20 -2
  203. package/dist/mcp-data/registry/badge.json +1 -1
  204. package/dist/mcp-data/registry/bar-nav.json +2 -2
  205. package/dist/mcp-data/registry/button-filled.json +1 -1
  206. package/dist/mcp-data/registry/button-unfilled.json +3 -3
  207. package/dist/mcp-data/registry/card-data-viz-donut.json +2 -2
  208. package/dist/mcp-data/registry/card-setting.json +99 -13
  209. package/dist/mcp-data/registry/card-shell-data-viz.json +8 -8
  210. package/dist/mcp-data/registry/chart-bar.json +1 -1
  211. package/dist/mcp-data/registry/chart-donut.json +1 -1
  212. package/dist/mcp-data/registry/chart-legend.json +1 -1
  213. package/dist/mcp-data/registry/chart-line.json +1 -1
  214. package/dist/mcp-data/registry/checkbox.json +1 -1
  215. package/dist/mcp-data/registry/chip.json +1 -1
  216. package/dist/mcp-data/registry/code-block.json +2 -2
  217. package/dist/mcp-data/registry/conversation-list-item.json +22 -4
  218. package/dist/mcp-data/registry/conversation-list-section.json +2 -2
  219. package/dist/mcp-data/registry/conversation-list.json +2 -2
  220. package/dist/mcp-data/registry/divider.json +5 -5
  221. package/dist/mcp-data/registry/empty-state.json +1 -1
  222. package/dist/mcp-data/registry/field.json +1 -1
  223. package/dist/mcp-data/registry/icon.json +2 -2
  224. package/dist/mcp-data/registry/input.json +1 -1
  225. package/dist/mcp-data/registry/loader.json +1 -1
  226. package/dist/mcp-data/registry/markdown.json +2 -2
  227. package/dist/mcp-data/registry/menu.json +18 -2
  228. package/dist/mcp-data/registry/message-bubble.json +7 -7
  229. package/dist/mcp-data/registry/message-composer.json +3 -3
  230. package/dist/mcp-data/registry/message-scroller.json +3 -3
  231. package/dist/mcp-data/registry/message.json +7 -6
  232. package/dist/mcp-data/registry/modal.json +154 -24
  233. package/dist/mcp-data/registry/panel-nav.json +21 -3
  234. package/dist/mcp-data/registry/panel-sub-nav.json +2 -2
  235. package/dist/mcp-data/registry/panel-tool-header.json +2 -2
  236. package/dist/mcp-data/registry/panel-tool-search.json +1 -1
  237. package/dist/mcp-data/registry/panel-tools.json +2 -2
  238. package/dist/mcp-data/registry/radio.json +1 -1
  239. package/dist/mcp-data/registry/registry.json +403 -141
  240. package/dist/mcp-data/registry/select-multi.json +1 -1
  241. package/dist/mcp-data/registry/select.json +1 -1
  242. package/dist/mcp-data/registry/shell-gradient-picker.json +1 -1
  243. package/dist/mcp-data/registry/shell-gradient-swatch.json +1 -1
  244. package/dist/mcp-data/registry/skeleton.json +2 -2
  245. package/dist/mcp-data/registry/slider.json +2 -2
  246. package/dist/mcp-data/registry/swatch-picker.json +1 -1
  247. package/dist/mcp-data/registry/switch.json +2 -2
  248. package/dist/mcp-data/registry/tab-group.json +1 -1
  249. package/dist/mcp-data/registry/tag.json +4 -4
  250. package/dist/mcp-data/registry/text.json +2 -2
  251. package/dist/mcp-data/registry/toast.json +318 -0
  252. package/dist/mcp-data/registry/tooltip-data-viz.json +1 -1
  253. package/dist/mcp-data/registry/tooltip.json +1 -1
  254. package/dist/react/components.d.ts +1 -2
  255. package/dist/react/components.js +1 -2
  256. package/dist/react/ds-card-setting.d.ts +2 -2
  257. package/dist/react/ds-card-setting.js +1 -1
  258. package/dist/react/ds-menu.d.ts +1 -0
  259. package/dist/react/ds-menu.js +1 -0
  260. package/dist/react/ds-modal.d.ts +3 -2
  261. package/dist/react/ds-modal.js +4 -1
  262. package/dist/react/ds-toast.d.ts +14 -0
  263. package/dist/react/ds-toast.js +16 -0
  264. package/dist/types/components/Avatar/Avatar.d.ts +3 -0
  265. package/dist/types/components/BarNav/BarNav.d.ts +2 -0
  266. package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +2 -0
  267. package/dist/types/components/CardSetting/CardSetting.d.ts +9 -6
  268. package/dist/types/components/CardSetting/index.d.ts +1 -1
  269. package/dist/types/components/ConversationListItem/ConversationListItem.d.ts +2 -0
  270. package/dist/types/components/Menu/Menu.d.ts +6 -0
  271. package/dist/types/components/MessageBubble/MessageBubble.d.ts +1 -1
  272. package/dist/types/components/MessageScroller/MessageScroller.d.ts +4 -0
  273. package/dist/types/components/Modal/Modal.d.ts +21 -18
  274. package/dist/types/components/PanelNav/PanelNav.d.ts +2 -0
  275. package/dist/types/components/Toast/Toast.d.ts +106 -0
  276. package/dist/types/components/Toast/index.d.ts +2 -0
  277. package/dist/types/components.d.ts +180 -186
  278. package/dist/types/toast/index.d.ts +3 -0
  279. package/dist/types/toast/toast-manager.d.ts +4 -0
  280. package/dist/types/toast/toast-types.d.ts +86 -0
  281. package/package.json +19 -18
  282. package/dist/angular/ds-banner.d.ts +0 -15
  283. package/dist/angular/ds-card.d.ts +0 -12
  284. package/dist/angular/ds-card.js +0 -38
  285. package/dist/components/ds-banner.d.ts +0 -11
  286. package/dist/components/ds-banner.js +0 -2
  287. package/dist/components/ds-banner.js.map +0 -1
  288. package/dist/components/ds-card.js +0 -2
  289. package/dist/components/ds-card.js.map +0 -1
  290. package/dist/components/p-41UAx2eq.js +0 -2
  291. package/dist/components/p-41UAx2eq.js.map +0 -1
  292. package/dist/components/p-B7NbIYjS.js +0 -2
  293. package/dist/components/p-B7NbIYjS.js.map +0 -1
  294. package/dist/components/p-BEQB8EnB.js +0 -2
  295. package/dist/components/p-BEQB8EnB.js.map +0 -1
  296. package/dist/components/p-BFWC17em.js +0 -2
  297. package/dist/components/p-BO6C8abO.js +0 -2
  298. package/dist/components/p-BO6C8abO.js.map +0 -1
  299. package/dist/components/p-CC1C8s6m.js +0 -2
  300. package/dist/components/p-CC1C8s6m.js.map +0 -1
  301. package/dist/components/p-CZMEhdlA.js.map +0 -1
  302. package/dist/components/p-CnM9SW_8.js +0 -2
  303. package/dist/components/p-CnM9SW_8.js.map +0 -1
  304. package/dist/components/p-CuKn9I5f.js +0 -2
  305. package/dist/components/p-CuKn9I5f.js.map +0 -1
  306. package/dist/components/p-DFMM60My.js +0 -2
  307. package/dist/components/p-DFMM60My.js.map +0 -1
  308. package/dist/components/p-DIsDiGcp.js.map +0 -1
  309. package/dist/components/p-Di-ThR90.js.map +0 -1
  310. package/dist/components/p-drBA73M_.js +0 -2
  311. package/dist/components/p-drBA73M_.js.map +0 -1
  312. package/dist/mcp-data/registry/banner.json +0 -206
  313. package/dist/mcp-data/registry/card.json +0 -126
  314. package/dist/react/ds-banner.d.ts +0 -12
  315. package/dist/react/ds-banner.js +0 -12
  316. package/dist/react/ds-card.d.ts +0 -9
  317. package/dist/react/ds-card.js +0 -12
  318. package/dist/types/components/Banner/Banner.d.ts +0 -31
  319. package/dist/types/components/Card/Card.d.ts +0 -21
  320. package/dist/types/components/Card/index.d.ts +0 -2
  321. /package/dist/components/{p-Bo5wikia.js.map → p-0kEg-Z4f.js.map} +0 -0
  322. /package/dist/components/{p-C0sq948W.js.map → p-BTo4pNdp.js.map} +0 -0
  323. /package/dist/components/{p-2br9YDbG.js.map → p-BkwBtZs3.js.map} +0 -0
  324. /package/dist/components/{p-Bx1G7P6W.js.map → p-CPPAndX2.js.map} +0 -0
  325. /package/dist/components/{p-Di4HBckb.js.map → p-CS12e52R.js.map} +0 -0
  326. /package/dist/components/{p-BSOZXagj.js.map → p-CZZYGqKz.js.map} +0 -0
  327. /package/dist/components/{p-t9Ei1kHP.js.map → p-CxyjoTN1.js.map} +0 -0
  328. /package/dist/components/{p--KbMQblZ.js.map → p-Dh4q5TSS.js.map} +0 -0
  329. /package/dist/components/{p-CiwdnKKp.js.map → p-DrDHC7V6.js.map} +0 -0
  330. /package/dist/components/{p-BZAEZENv.js.map → p-DsbhBb5F.js.map} +0 -0
  331. /package/dist/components/{p-B1pWGQn1.js.map → p-P_4vE1ub.js.map} +0 -0
@@ -505,7 +505,7 @@
505
505
  "peerDependencies": {
506
506
  "required": [
507
507
  "@ds-mo/tokens ^3.1.1",
508
- "@ds-mo/icons ^6.2.0"
508
+ "@ds-mo/icons ^6.3.0"
509
509
  ],
510
510
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
511
511
  }
@@ -525,12 +525,12 @@
525
525
  "files": [
526
526
  {
527
527
  "path": "src/wc/components/ButtonUnfilled/ButtonUnfilled.css",
528
- "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/control-inactive.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-width.css';\n\n:host {\n display: inline-flex;\n outline: none;\n --ds-button-unfilled-fg: var(--color-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-secondary);\n --ds-button-unfilled-border: var(--color-border-secondary);\n}\n\n/* Icon-only: fixed square from density height (hug). Fill stretches the hit target. */\n:host(.button-unfilled-host--icon.ds-control-width--hug) {\n width: var(--ds-control-height, var(--dimension-size-400));\n height: var(--ds-control-height, var(--dimension-size-400));\n min-width: var(--ds-control-height, var(--dimension-size-400));\n min-height: var(--ds-control-height, var(--dimension-size-400));\n}\n\n:host(.button-unfilled-host--icon.ds-control-width--fill) {\n height: var(--ds-control-height, var(--dimension-size-400));\n min-height: var(--ds-control-height, var(--dimension-size-400));\n}\n\n:host(.ds-control-width--fill) .button-unfilled {\n width: 100%;\n}\n\n.button-unfilled {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: var(--ds-control-height, var(--dimension-size-400));\n padding: 0 var(--ds-control-padding-inline, var(--dimension-space-075));\n gap: var(--ds-control-gap, var(--dimension-space-050));\n background: transparent;\n border: none;\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n color: var(--ds-button-unfilled-fg);\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n overflow: hidden;\n transition: color var(--effect-motion-short-2);\n /* Hover/press/selected fill: `.ds-interaction-fill` (+ `--selected` / `--on-*`).\n Active always promotes foreground to primary (toggle mode). */\n}\n\n.button-unfilled--icon {\n width: 100%;\n height: 100%;\n padding: 0;\n}\n\n.button-unfilled--rounded {\n --ds-control-radius: var(--dimension-radius-half);\n}\n\n.button-unfilled--active {\n color: var(--ds-button-unfilled-fg-active);\n}\n\n/* Inset stroke via interaction-fill ::after (above selected / hover washes). */\n.button-unfilled--bordered {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--ds-button-unfilled-border);\n}\n\n/* Fallback if interaction-fill is omitted (should be rare — prefer keep the util class). */\n.button-unfilled--bordered:not(.ds-interaction-fill) {\n box-shadow: inset 0 0 0 var(--ds-interaction-border-width) var(--ds-interaction-border-color);\n}\n\n/* Inactive: `.ds-control-inactive` (25% opacity). */\n\n/* Foreground / chrome tokens per surface — interaction tokens come from util `--on-*`. */\n.button-unfilled--background-medium {\n --ds-button-unfilled-fg: var(--color-foreground-on-medium-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-medium-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-medium-neutral);\n --ds-button-unfilled-border: var(--color-border-on-medium-background-secondary);\n}\n\n.button-unfilled--background-bold {\n --ds-button-unfilled-fg: var(--color-foreground-on-bold-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-bold-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-bold-neutral);\n --ds-button-unfilled-border: var(--color-border-on-bold-background-secondary);\n}\n\n.button-unfilled--background-strong {\n --ds-button-unfilled-fg: var(--color-foreground-on-strong-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-strong-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-strong-neutral);\n --ds-button-unfilled-border: var(--color-border-on-strong-background-secondary);\n}\n\n.button-unfilled--background-translucent {\n --ds-button-unfilled-fg: var(--color-translucent-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-translucent-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-translucent-translucent);\n --ds-button-unfilled-border: var(--color-translucent-border-secondary);\n}\n\n.button-unfilled--background-inverted {\n --ds-button-unfilled-fg: var(--color-inverted-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-inverted-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-inverted-background);\n --ds-button-unfilled-border: var(--color-inverted-border-secondary);\n}\n\n.button-unfilled--background-media {\n --ds-button-unfilled-fg: var(--color-media-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-media-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-media-background);\n --ds-button-unfilled-border: var(--color-media-border-secondary);\n}\n\n.button-unfilled--on-always-dark {\n --ds-button-unfilled-fg: var(--color-always-dark-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-always-dark-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-always-dark-background);\n --ds-button-unfilled-border: var(--color-always-dark-border-secondary);\n}\n\n/*\n * After surface ring defaults: selected fill remaps the knock-out to the active\n * wash so the badge halo matches the selected background.\n */\n.button-unfilled.ds-interaction-fill--selected {\n --ds-button-unfilled-dot-ring: var(--ds-interaction-active);\n}\n\n.button-unfilled__icon-wrap {\n position: relative;\n width: var(--ds-control-icon, var(--dimension-iconography-md));\n height: var(--ds-control-icon, var(--dimension-iconography-md));\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n z-index: 2;\n}\n\n.button-unfilled__label {\n position: relative;\n z-index: 2;\n padding: 0 var(--ds-control-label-inset, var(--dimension-space-025));\n white-space: nowrap;\n flex-shrink: 0;\n min-width: 0;\n}\n\n.button-unfilled__label--loading {\n visibility: hidden;\n}\n\n.button-unfilled > .button-unfilled__loader-overlay {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.button-unfilled__dot {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n pointer-events: none;\n}\n",
528
+ "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/control-inactive.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-width.css';\n\n:host {\n display: inline-flex;\n outline: none;\n --ds-button-unfilled-fg: var(--color-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-secondary);\n --ds-button-unfilled-border: var(--color-border-secondary);\n}\n\n/* Icon-only: fixed square from density height (hug). Fill stretches the hit target. */\n:host(.button-unfilled-host--icon.ds-control-width--hug) {\n width: var(--ds-control-height, var(--dimension-size-400));\n height: var(--ds-control-height, var(--dimension-size-400));\n min-width: var(--ds-control-height, var(--dimension-size-400));\n min-height: var(--ds-control-height, var(--dimension-size-400));\n}\n\n:host(.button-unfilled-host--icon.ds-control-width--fill) {\n height: var(--ds-control-height, var(--dimension-size-400));\n min-height: var(--ds-control-height, var(--dimension-size-400));\n}\n\n:host(.ds-control-width--fill) .button-unfilled {\n width: 100%;\n}\n\n.button-unfilled {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: var(--ds-control-height, var(--dimension-size-400));\n padding: 0 var(--ds-control-padding-inline, var(--dimension-space-075));\n gap: var(--ds-control-gap, var(--dimension-space-050));\n background: transparent;\n border: none;\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n color: var(--ds-button-unfilled-fg);\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n overflow: hidden;\n transition: color var(--effect-motion-short-2);\n /* Hover/press/selected fill: `.ds-interaction-fill` (+ `--selected` / `--on-*`).\n Active always promotes foreground to primary (toggle mode). */\n}\n\n.button-unfilled--icon {\n width: 100%;\n height: 100%;\n padding: 0;\n}\n\n.button-unfilled--rounded {\n --ds-control-radius: var(--dimension-radius-half);\n}\n\n.button-unfilled--active {\n color: var(--ds-button-unfilled-fg-active);\n}\n\n/*\n * An open popup is a transient pressed state, not a selected state. Keep the\n * pressed wash visible for the popup's complete rendered lifecycle even when\n * shell chrome opts out of selected fills with `activeFill=false`.\n */\n.button-unfilled.button-unfilled--expanded::after,\n.button-unfilled.button-unfilled--expanded:hover:not(:disabled)::after {\n background: var(--ds-interaction-pressed);\n}\n\n/* Inset stroke via interaction-fill ::after (above selected / hover washes). */\n.button-unfilled--bordered {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--ds-button-unfilled-border);\n}\n\n/* Fallback if interaction-fill is omitted (should be rare — prefer keep the util class). */\n.button-unfilled--bordered:not(.ds-interaction-fill) {\n box-shadow: inset 0 0 0 var(--ds-interaction-border-width) var(--ds-interaction-border-color);\n}\n\n/* Inactive: `.ds-control-inactive` (25% opacity). */\n\n/* Foreground / chrome tokens per surface — interaction tokens come from util `--on-*`. */\n.button-unfilled--background-medium {\n --ds-button-unfilled-fg: var(--color-foreground-on-medium-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-medium-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-medium-neutral);\n --ds-button-unfilled-border: var(--color-border-on-medium-background-secondary);\n}\n\n.button-unfilled--background-bold {\n --ds-button-unfilled-fg: var(--color-foreground-on-bold-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-bold-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-bold-neutral);\n --ds-button-unfilled-border: var(--color-border-on-bold-background-secondary);\n}\n\n.button-unfilled--background-strong {\n --ds-button-unfilled-fg: var(--color-foreground-on-strong-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-strong-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-strong-neutral);\n --ds-button-unfilled-border: var(--color-border-on-strong-background-secondary);\n}\n\n.button-unfilled--background-translucent {\n --ds-button-unfilled-fg: var(--color-translucent-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-translucent-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-translucent-translucent);\n --ds-button-unfilled-border: var(--color-translucent-border-secondary);\n}\n\n.button-unfilled--background-inverted {\n --ds-button-unfilled-fg: var(--color-inverted-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-inverted-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-inverted-background);\n --ds-button-unfilled-border: var(--color-inverted-border-secondary);\n}\n\n.button-unfilled--background-media {\n --ds-button-unfilled-fg: var(--color-media-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-media-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-media-background);\n --ds-button-unfilled-border: var(--color-media-border-secondary);\n}\n\n.button-unfilled--on-always-dark {\n --ds-button-unfilled-fg: var(--color-always-dark-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-always-dark-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-always-dark-background);\n --ds-button-unfilled-border: var(--color-always-dark-border-secondary);\n}\n\n/*\n * After surface ring defaults: selected fill remaps the knock-out to the active\n * wash so the badge halo matches the selected background.\n */\n.button-unfilled.ds-interaction-fill--selected {\n --ds-button-unfilled-dot-ring: var(--ds-interaction-active);\n}\n\n.button-unfilled__icon-wrap {\n position: relative;\n width: var(--ds-control-icon, var(--dimension-iconography-md));\n height: var(--ds-control-icon, var(--dimension-iconography-md));\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n z-index: 2;\n}\n\n.button-unfilled__label {\n position: relative;\n z-index: 2;\n padding: 0 var(--ds-control-label-inset, var(--dimension-space-025));\n white-space: nowrap;\n flex-shrink: 0;\n min-width: 0;\n}\n\n.button-unfilled__label--loading {\n visibility: hidden;\n}\n\n.button-unfilled > .button-unfilled__loader-overlay {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.button-unfilled__dot {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n pointer-events: none;\n}\n",
529
529
  "type": "registry:ui"
530
530
  },
531
531
  {
532
532
  "path": "src/wc/components/ButtonUnfilled/ButtonUnfilled.tsx",
533
- "content": "import { Component, Element, Event, EventEmitter, h, Host, Method, Prop } from '@stencil/core';\nimport { controlWidthClass, CONTROL_TEXT_VARIANT, type ControlWidth } from '../../utils';\n\nexport type ButtonUnfilledBackground =\n | 'faint'\n | 'medium'\n | 'bold'\n | 'strong'\n | 'translucent'\n | 'inverted'\n | 'media'\n | 'always-dark';\n\nexport type ButtonUnfilledVariant = 'icon' | 'label' | 'icon-label';\n\nexport type ButtonUnfilledSize = 'md' | 'sm' | 'xs';\n\nexport type ButtonUnfilledWidth = ControlWidth;\nexport type ButtonUnfilledPopup = 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\n/**\n * `ds-icon` size prop matching control-density icon metrics\n * (md→20 / sm→16 / xs→12 via `--dimension-iconography-*`).\n */\nconst ICON_SIZE: Record<ButtonUnfilledSize, 'md' | 'sm' | 'xs'> = {\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\n@Component({\n tag: 'ds-button-unfilled',\n styleUrl: 'ButtonUnfilled.css',\n scoped: true,\n})\nexport class ButtonUnfilled {\n @Element() el!: HTMLElement;\n\n /**\n * Content layout. Default is label-only; pass `icon` for icon-only chrome\n * (nav / tool rails) or `icon-label` for leading icon + text.\n */\n @Prop() variant: ButtonUnfilledVariant = 'label';\n\n /** Control density (height, padding, icon, type). */\n @Prop() size: ButtonUnfilledSize = 'md';\n\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: ButtonUnfilledWidth = 'hug';\n\n /** Visible text for `label` / `icon-label` variants. */\n @Prop() label: string = '';\n\n /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */\n @Prop() icon: string = '';\n\n /** Active/selected visual state. Always promotes foreground to primary. */\n @Prop() isActive: boolean = false;\n\n /**\n * When active, render the selected interaction fill.\n * Default `true` for general UI. Shell chrome (nav / tool rails) should pass\n * `false` so selection is foreground-only (primary color, no fill).\n */\n @Prop() activeFill: boolean = true;\n\n /** Show a 1px secondary inset border. Default on; shell chrome can pass `false`. */\n @Prop() hasBorder: boolean = true;\n\n /** Use the half-radius treatment instead of the default control radius. */\n @Prop() rounded: boolean = false;\n\n /** Show a notification dot at the top-right of the icon zone (icon variant only). */\n @Prop() dot: boolean = false;\n\n /** Disables interaction. */\n @Prop() isInactive: boolean = false;\n\n /** Shows an inline loader and prevents interaction without applying inactive opacity. */\n @Prop() isLoading: boolean = false;\n\n /** Native button type. */\n @Prop() type: 'button' | 'submit' | 'reset' = 'button';\n\n /** Actual parent surface context. Omit on primary and secondary surfaces. */\n @Prop() background: ButtonUnfilledBackground | undefined;\n\n /** Accessible name override. Required for icon-only buttons. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n @Prop() controls: string | undefined;\n @Prop() expanded: boolean | undefined;\n @Prop() haspopup: ButtonUnfilledPopup | undefined;\n @Prop() pressed: boolean | undefined;\n\n /**\n * Native `tabindex` for roving keyboard groups in shell chrome.\n * Omit for the default button tab stop (`0`).\n */\n @Prop({ attribute: 'tab-index' }) focusTabIndex?: number;\n\n @Event() dsClick!: EventEmitter<MouseEvent>;\n @Event() dsChange!: EventEmitter<boolean>;\n\n private buttonEl: HTMLButtonElement | null = null;\n\n @Method()\n async setFocus() {\n this.buttonEl?.focus();\n }\n\n private handleClick = (event: MouseEvent) => {\n if (this.isInactive || this.isLoading) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.dsClick.emit(event);\n this.dsChange.emit(!this.isActive);\n };\n\n private get showIcon(): boolean {\n return this.variant === 'icon' || this.variant === 'icon-label';\n }\n\n private get showLabel(): boolean {\n return this.variant === 'label' || this.variant === 'icon-label';\n }\n\n private get showDot(): boolean {\n return this.variant === 'icon' && this.dot && !this.isLoading;\n }\n\n /** Knock-out ring: selected fill → active wash; otherwise surface token. */\n private get dotRing(): string {\n if (this.isActive && this.activeFill) {\n return 'var(--ds-interaction-active)';\n }\n return 'var(--ds-button-unfilled-dot-ring)';\n }\n\n private get accessibleName(): string | undefined {\n if (this.ariaLabel) return this.ariaLabel;\n if (this.isLoading && this.variant === 'label' && this.label) return this.label;\n return undefined;\n }\n\n render() {\n const bg = this.background;\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n\n const cls: Record<string, boolean> = {\n 'button-unfilled': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': this.isActive && this.activeFill && !this.isInactive,\n 'ds-interaction-fill--on-faint': bg === 'faint',\n 'ds-interaction-fill--on-medium': bg === 'medium',\n 'ds-interaction-fill--on-bold': bg === 'bold',\n 'ds-interaction-fill--on-strong': bg === 'strong',\n 'ds-interaction-fill--on-translucent': bg === 'translucent',\n 'ds-interaction-fill--on-inverted': bg === 'inverted',\n 'ds-interaction-fill--on-media': bg === 'media',\n 'ds-interaction-fill--on-always-dark': bg === 'always-dark',\n 'button-unfilled--active': this.isActive,\n 'button-unfilled--bordered': this.hasBorder,\n 'button-unfilled--rounded': this.rounded,\n 'ds-control-inactive': this.isInactive,\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n 'button-unfilled--icon': this.variant === 'icon',\n 'button-unfilled--label': this.variant === 'label',\n 'button-unfilled--icon-label': this.variant === 'icon-label',\n 'button-unfilled--background-faint': bg === 'faint',\n 'button-unfilled--background-medium': bg === 'medium',\n 'button-unfilled--background-bold': bg === 'bold',\n 'button-unfilled--background-strong': bg === 'strong',\n 'button-unfilled--background-translucent': bg === 'translucent',\n 'button-unfilled--background-inverted': bg === 'inverted',\n 'button-unfilled--background-media': bg === 'media',\n 'button-unfilled--on-always-dark': bg === 'always-dark',\n };\n\n return (\n <Host\n class={{\n 'button-unfilled-host': true,\n 'button-unfilled-host--icon': this.variant === 'icon',\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n ...controlWidthClass(this.width),\n }}\n tabIndex={-1}\n >\n <button\n ref={el => {\n this.buttonEl = el ?? null;\n }}\n type={this.type}\n class={cls}\n disabled={this.isInactive}\n tabIndex={this.focusTabIndex ?? 0}\n aria-label={this.accessibleName}\n aria-busy={this.isLoading ? 'true' : undefined}\n aria-disabled={this.isLoading ? 'true' : undefined}\n aria-controls={this.controls}\n aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}\n aria-haspopup={this.haspopup}\n aria-pressed={this.pressed === undefined ? undefined : String(this.pressed)}\n onClick={this.handleClick}\n >\n {this.showIcon && (\n <span class=\"button-unfilled__icon-wrap ds-interaction-fill__content\">\n {this.isLoading\n ? <ds-loader size={iconSize} color=\"inherit\" />\n : <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n }\n {this.showDot && (\n <ds-badge\n class=\"button-unfilled__dot\"\n variant=\"dot\"\n background={this.dotRing}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n )}\n {this.showLabel && (\n <ds-text\n class={{\n 'button-unfilled__label': true,\n 'button-unfilled__label--loading': this.isLoading && this.variant === 'label',\n 'ds-interaction-fill__content': true,\n }}\n as=\"span\"\n variant={textVariant}\n emphasis\n color=\"inherit\"\n >\n {this.label}\n </ds-text>\n )}\n {this.isLoading && this.variant === 'label' && (\n <span class=\"button-unfilled__loader-overlay ds-interaction-fill__content\">\n <ds-loader size={iconSize} color=\"inherit\" />\n </span>\n )}\n </button>\n </Host>\n );\n }\n}\n",
533
+ "content": "import { Component, Element, Event, EventEmitter, h, Host, Method, Prop } from '@stencil/core';\nimport { controlWidthClass, CONTROL_TEXT_VARIANT, type ControlWidth } from '../../utils';\n\nexport type ButtonUnfilledBackground =\n | 'faint'\n | 'medium'\n | 'bold'\n | 'strong'\n | 'translucent'\n | 'inverted'\n | 'media'\n | 'always-dark';\n\nexport type ButtonUnfilledVariant = 'icon' | 'label' | 'icon-label';\n\nexport type ButtonUnfilledSize = 'md' | 'sm' | 'xs';\n\nexport type ButtonUnfilledWidth = ControlWidth;\nexport type ButtonUnfilledPopup = 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\n/**\n * `ds-icon` size prop matching control-density icon metrics\n * (md→20 / sm→16 / xs→12 via `--dimension-iconography-*`).\n */\nconst ICON_SIZE: Record<ButtonUnfilledSize, 'md' | 'sm' | 'xs'> = {\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\n@Component({\n tag: 'ds-button-unfilled',\n styleUrl: 'ButtonUnfilled.css',\n scoped: true,\n})\nexport class ButtonUnfilled {\n @Element() el!: HTMLElement;\n\n /**\n * Content layout. Default is label-only; pass `icon` for icon-only chrome\n * (nav / tool rails) or `icon-label` for leading icon + text.\n */\n @Prop() variant: ButtonUnfilledVariant = 'label';\n\n /** Control density (height, padding, icon, type). */\n @Prop() size: ButtonUnfilledSize = 'md';\n\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: ButtonUnfilledWidth = 'hug';\n\n /** Visible text for `label` / `icon-label` variants. */\n @Prop() label: string = '';\n\n /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */\n @Prop() icon: string = '';\n\n /** Active/selected visual state. Always promotes foreground to primary. */\n @Prop() isActive: boolean = false;\n\n /**\n * When active, render the selected interaction fill.\n * Default `true` for general UI. Shell chrome (nav / tool rails) should pass\n * `false` so selection is foreground-only (primary color, no fill).\n */\n @Prop() activeFill: boolean = true;\n\n /** Show a 1px secondary inset border. Default on; shell chrome can pass `false`. */\n @Prop() hasBorder: boolean = true;\n\n /** Use the half-radius treatment instead of the default control radius. */\n @Prop() rounded: boolean = false;\n\n /** Show a notification dot at the top-right of the icon zone (icon variant only). */\n @Prop() dot: boolean = false;\n\n /** Disables interaction. */\n @Prop() isInactive: boolean = false;\n\n /** Shows an inline loader and prevents interaction without applying inactive opacity. */\n @Prop() isLoading: boolean = false;\n\n /** Native button type. */\n @Prop() type: 'button' | 'submit' | 'reset' = 'button';\n\n /** Actual parent surface context. Omit on primary and secondary surfaces. */\n @Prop() background: ButtonUnfilledBackground | undefined;\n\n /** Accessible name override. Required for icon-only buttons. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n @Prop() controls: string | undefined;\n @Prop() expanded: boolean | undefined;\n @Prop() haspopup: ButtonUnfilledPopup | undefined;\n @Prop() pressed: boolean | undefined;\n\n /**\n * Native `tabindex` for roving keyboard groups in shell chrome.\n * Omit for the default button tab stop (`0`).\n */\n @Prop({ attribute: 'tab-index' }) focusTabIndex?: number;\n\n @Event() dsClick!: EventEmitter<MouseEvent>;\n @Event() dsChange!: EventEmitter<boolean>;\n\n private buttonEl: HTMLButtonElement | null = null;\n\n @Method()\n async setFocus() {\n this.buttonEl?.focus();\n }\n\n private handleClick = (event: MouseEvent) => {\n if (this.isInactive || this.isLoading) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.dsClick.emit(event);\n this.dsChange.emit(!this.isActive);\n };\n\n private get showIcon(): boolean {\n return this.variant === 'icon' || this.variant === 'icon-label';\n }\n\n private get showLabel(): boolean {\n return this.variant === 'label' || this.variant === 'icon-label';\n }\n\n private get showDot(): boolean {\n return this.variant === 'icon' && this.dot && !this.isLoading;\n }\n\n /** An expanded popup trigger is visually active even when selection is controlled separately. */\n private get visuallyActive(): boolean {\n return this.isActive || this.expanded === true;\n }\n\n /** Knock-out ring: selected fill → active wash; otherwise surface token. */\n private get dotRing(): string {\n if (this.isActive && this.activeFill) {\n return 'var(--ds-interaction-active)';\n }\n return 'var(--ds-button-unfilled-dot-ring)';\n }\n\n private get accessibleName(): string | undefined {\n if (this.ariaLabel) return this.ariaLabel;\n if (this.isLoading && this.variant === 'label' && this.label) return this.label;\n return undefined;\n }\n\n render() {\n const bg = this.background;\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n\n const cls: Record<string, boolean> = {\n 'button-unfilled': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': this.isActive && this.activeFill && !this.isInactive,\n 'ds-interaction-fill--on-faint': bg === 'faint',\n 'ds-interaction-fill--on-medium': bg === 'medium',\n 'ds-interaction-fill--on-bold': bg === 'bold',\n 'ds-interaction-fill--on-strong': bg === 'strong',\n 'ds-interaction-fill--on-translucent': bg === 'translucent',\n 'ds-interaction-fill--on-inverted': bg === 'inverted',\n 'ds-interaction-fill--on-media': bg === 'media',\n 'ds-interaction-fill--on-always-dark': bg === 'always-dark',\n 'button-unfilled--active': this.visuallyActive,\n 'button-unfilled--expanded': this.expanded === true && !this.isInactive,\n 'button-unfilled--bordered': this.hasBorder,\n 'button-unfilled--rounded': this.rounded,\n 'ds-control-inactive': this.isInactive,\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n 'button-unfilled--icon': this.variant === 'icon',\n 'button-unfilled--label': this.variant === 'label',\n 'button-unfilled--icon-label': this.variant === 'icon-label',\n 'button-unfilled--background-faint': bg === 'faint',\n 'button-unfilled--background-medium': bg === 'medium',\n 'button-unfilled--background-bold': bg === 'bold',\n 'button-unfilled--background-strong': bg === 'strong',\n 'button-unfilled--background-translucent': bg === 'translucent',\n 'button-unfilled--background-inverted': bg === 'inverted',\n 'button-unfilled--background-media': bg === 'media',\n 'button-unfilled--on-always-dark': bg === 'always-dark',\n };\n\n return (\n <Host\n class={{\n 'button-unfilled-host': true,\n 'button-unfilled-host--icon': this.variant === 'icon',\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n ...controlWidthClass(this.width),\n }}\n tabIndex={-1}\n >\n <button\n ref={el => {\n this.buttonEl = el ?? null;\n }}\n type={this.type}\n class={cls}\n disabled={this.isInactive}\n tabIndex={this.focusTabIndex ?? 0}\n aria-label={this.accessibleName}\n aria-busy={this.isLoading ? 'true' : undefined}\n aria-disabled={this.isLoading ? 'true' : undefined}\n aria-controls={this.controls}\n aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}\n aria-haspopup={this.haspopup}\n aria-pressed={this.pressed === undefined ? undefined : String(this.pressed)}\n onClick={this.handleClick}\n >\n {this.showIcon && (\n <span class=\"button-unfilled__icon-wrap ds-interaction-fill__content\">\n {this.isLoading\n ? <ds-loader size={iconSize} color=\"inherit\" />\n : <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n }\n {this.showDot && (\n <ds-badge\n class=\"button-unfilled__dot\"\n variant=\"dot\"\n background={this.dotRing}\n label=\"\"\n aria-hidden=\"true\"\n />\n )}\n </span>\n )}\n {this.showLabel && (\n <ds-text\n class={{\n 'button-unfilled__label': true,\n 'button-unfilled__label--loading': this.isLoading && this.variant === 'label',\n 'ds-interaction-fill__content': true,\n }}\n as=\"span\"\n variant={textVariant}\n emphasis\n color=\"inherit\"\n >\n {this.label}\n </ds-text>\n )}\n {this.isLoading && this.variant === 'label' && (\n <span class=\"button-unfilled__loader-overlay ds-interaction-fill__content\">\n <ds-loader size={iconSize} color=\"inherit\" />\n </span>\n )}\n </button>\n </Host>\n );\n }\n}\n",
534
534
  "type": "registry:ui"
535
535
  }
536
536
  ]
@@ -114,7 +114,7 @@
114
114
  "peerDependencies": {
115
115
  "required": [
116
116
  "@ds-mo/tokens ^3.1.1",
117
- "@ds-mo/icons ^6.2.0"
117
+ "@ds-mo/icons ^6.3.0"
118
118
  ],
119
119
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
120
120
  }
@@ -136,7 +136,7 @@
136
136
  },
137
137
  {
138
138
  "path": "src/wc/components/CardDataVizDonut/CardDataVizDonut.tsx",
139
- "content": "import { Component, Element, Event, EventEmitter, State, h, Host, Prop } from '@stencil/core';\nimport type { CardShellDataVizWidth } from '../CardShellDataViz/CardShellDataViz';\n\nexport type CardDataVizDonutWidth = CardShellDataVizWidth;\n\n/** Matches the `dsSliceHover`/`dsItemHover` detail shape emitted by `ds-chart-*` and `ds-chart-legend`. */\ntype HoveredDatum = { label: string } | null;\n\n/** A slotted chart/legend that opts into hover sync by exposing an `activeLabel` prop. */\ntype SyncableSlot = HTMLElement & { activeLabel?: string | null };\n\n/**\n * Donut data-viz card — dedicated `ds-card-shell-data-viz` chrome with a fill chart region and\n * content-sized legend. Hover sync between chart and legend stays here.\n */\n@Component({\n tag: 'ds-card-data-viz-donut',\n styleUrl: 'CardDataVizDonut.css',\n scoped: true,\n})\nexport class CardDataVizDonut {\n @Element() el!: HTMLElement;\n\n /** Widget heading shown in the card header. */\n @Prop() heading!: string;\n\n /** Card width token — also sets matching min-height. */\n @Prop() cardWidth: CardDataVizDonutWidth = 'md';\n @Prop() filterLabel: string = 'Filter';\n\n /** Emits when the header filter control is activated. */\n @Event() dsFilterClick!: EventEmitter<void>;\n\n // Scoped (light-DOM) components have no native slot projection to detect emptiness from CSS —\n // check the actual children so an unused region (e.g. no legend) doesn't render a dangling\n // padded box plus a body gap next to nothing.\n @State() private hasChartSlot = false;\n @State() private hasLegendSlot = false;\n\n componentWillRender() {\n // Not `:scope >` — Stencil's scoped slot polyfill relocates slotted children into\n // region wrappers (and here into nested `ds-card`), so they may not stay direct\n // host children across renders.\n this.hasChartSlot = !!this.el.querySelector('[slot=\"chart\"]');\n this.hasLegendSlot = !!this.el.querySelector('[slot=\"legend\"]');\n }\n\n componentDidLoad() {\n // Sync the chart's and legend's `activeLabel` from each other's hover events, so any\n // ds-chart-donut + ds-chart-legend pair cross-highlights without every consumer having to\n // wire it up by hand. Delegated on the host (rather than bound to the specific slotted\n // elements) since Stencil events bubble and the slot content can be swapped at runtime.\n this.el.addEventListener('dsSliceHover', this.handleChartHover);\n this.el.addEventListener('dsItemHover', this.handleLegendHover);\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('dsSliceHover', this.handleChartHover);\n this.el.removeEventListener('dsItemHover', this.handleLegendHover);\n }\n\n private handleChartHover = (e: Event) => {\n // Not `:scope >` — Stencil's slot polyfill for scoped (non-shadow) components relocates\n // slotted children into an inner region wrapper on render, so they're no longer direct\n // children of the host by the time this fires.\n const legend = this.el.querySelector('[slot=\"legend\"]') as SyncableSlot | null;\n if (!legend || !('activeLabel' in legend)) return;\n legend.activeLabel = (e as CustomEvent<HoveredDatum>).detail?.label ?? null;\n };\n\n private handleLegendHover = (e: Event) => {\n const chart = this.el.querySelector('[slot=\"chart\"]') as SyncableSlot | null;\n if (!chart || !('activeLabel' in chart)) return;\n chart.activeLabel = (e as CustomEvent<HoveredDatum>).detail?.label ?? null;\n };\n\n private handleFilterClick = () => {\n this.dsFilterClick.emit();\n };\n\n render() {\n return (\n <Host class=\"card-data-viz-donut\">\n <ds-card-shell-data-viz heading={this.heading} cardWidth={this.cardWidth}>\n <ds-button-unfilled\n slot=\"actions\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Filters\"\n aria-label={this.filterLabel}\n onDsClick={this.handleFilterClick}\n />\n <div class=\"card-data-viz-donut__layout\">\n {this.hasChartSlot && (\n <div class=\"card-data-viz-donut__chart-region\">\n <slot name=\"chart\" />\n </div>\n )}\n {this.hasLegendSlot && (\n <div class=\"card-data-viz-donut__legend-region\">\n <slot name=\"legend\" />\n </div>\n )}\n </div>\n </ds-card-shell-data-viz>\n </Host>\n );\n }\n}\n",
139
+ "content": "import { Component, Element, Event, EventEmitter, State, h, Host, Prop } from '@stencil/core';\nimport type { CardShellDataVizWidth } from '../CardShellDataViz/CardShellDataViz';\n\nexport type CardDataVizDonutWidth = CardShellDataVizWidth;\n\n/** Matches the `dsSliceHover`/`dsItemHover` detail shape emitted by `ds-chart-*` and `ds-chart-legend`. */\ntype HoveredDatum = { label: string } | null;\n\n/** A slotted chart/legend that opts into hover sync by exposing an `activeLabel` prop. */\ntype SyncableSlot = HTMLElement & { activeLabel?: string | null };\n\n/**\n * Donut data-viz card — dedicated `ds-card-shell-data-viz` chrome with a fill chart region and\n * content-sized legend. Hover sync between chart and legend stays here.\n */\n@Component({\n tag: 'ds-card-data-viz-donut',\n styleUrl: 'CardDataVizDonut.css',\n scoped: true,\n})\nexport class CardDataVizDonut {\n @Element() el!: HTMLElement;\n\n /** Widget heading shown in the card header. */\n @Prop() heading!: string;\n\n /** Card width token — also sets matching min-height. */\n @Prop() cardWidth: CardDataVizDonutWidth = 'md';\n @Prop() filterLabel: string = 'Filter';\n\n /** Emits when the header filter control is activated. */\n @Event() dsFilterClick!: EventEmitter<void>;\n\n // Scoped (light-DOM) components have no native slot projection to detect emptiness from CSS —\n // check the actual children so an unused region (e.g. no legend) doesn't render a dangling\n // padded box plus a body gap next to nothing.\n @State() private hasChartSlot = false;\n @State() private hasLegendSlot = false;\n\n componentWillRender() {\n // Not `:scope >` — Stencil's scoped slot polyfill relocates slotted children into\n // region wrappers (and here into nested `ds-card-shell-data-viz`), so they may not stay direct\n // host children across renders.\n this.hasChartSlot = !!this.el.querySelector('[slot=\"chart\"]');\n this.hasLegendSlot = !!this.el.querySelector('[slot=\"legend\"]');\n }\n\n componentDidLoad() {\n // Sync the chart's and legend's `activeLabel` from each other's hover events, so any\n // ds-chart-donut + ds-chart-legend pair cross-highlights without every consumer having to\n // wire it up by hand. Delegated on the host (rather than bound to the specific slotted\n // elements) since Stencil events bubble and the slot content can be swapped at runtime.\n this.el.addEventListener('dsSliceHover', this.handleChartHover);\n this.el.addEventListener('dsItemHover', this.handleLegendHover);\n }\n\n disconnectedCallback() {\n this.el.removeEventListener('dsSliceHover', this.handleChartHover);\n this.el.removeEventListener('dsItemHover', this.handleLegendHover);\n }\n\n private handleChartHover = (e: Event) => {\n // Not `:scope >` — Stencil's slot polyfill for scoped (non-shadow) components relocates\n // slotted children into an inner region wrapper on render, so they're no longer direct\n // children of the host by the time this fires.\n const legend = this.el.querySelector('[slot=\"legend\"]') as SyncableSlot | null;\n if (!legend || !('activeLabel' in legend)) return;\n legend.activeLabel = (e as CustomEvent<HoveredDatum>).detail?.label ?? null;\n };\n\n private handleLegendHover = (e: Event) => {\n const chart = this.el.querySelector('[slot=\"chart\"]') as SyncableSlot | null;\n if (!chart || !('activeLabel' in chart)) return;\n chart.activeLabel = (e as CustomEvent<HoveredDatum>).detail?.label ?? null;\n };\n\n private handleFilterClick = () => {\n this.dsFilterClick.emit();\n };\n\n render() {\n return (\n <Host class=\"card-data-viz-donut\">\n <ds-card-shell-data-viz heading={this.heading} cardWidth={this.cardWidth}>\n <ds-button-unfilled\n slot=\"actions\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Filters\"\n aria-label={this.filterLabel}\n onDsClick={this.handleFilterClick}\n />\n <div class=\"card-data-viz-donut__layout\">\n {this.hasChartSlot && (\n <div class=\"card-data-viz-donut__chart-region\">\n <slot name=\"chart\" />\n </div>\n )}\n {this.hasLegendSlot && (\n <div class=\"card-data-viz-donut__legend-region\">\n <slot name=\"legend\" />\n </div>\n )}\n </div>\n </ds-card-shell-data-viz>\n </Host>\n );\n }\n}\n",
140
140
  "type": "registry:ui"
141
141
  }
142
142
  ]
@@ -2,15 +2,101 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "card-setting",
4
4
  "title": "CardSetting",
5
- "description": "Settings page section shell composed on ds-card with view/edit header actions. Edit state is controlled by the parent.",
5
+ "description": "Persistent settings-section surface with a heading, controlled view/edit chrome, fixed edit-save-cancel actions, and application-owned body content.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
9
9
  "distribution": "npm",
10
10
  "storybook": "https://zainadeel.github.io/compomo/?path=/story/card-setting",
11
11
  "source": "src/wc/components/CardSetting/CardSetting.tsx",
12
- "intentStatus": "migration-pending",
13
- "intent": null,
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "general",
15
+ "status": "stable",
16
+ "summary": "Persistent settings-section surface with a heading, controlled view/edit chrome, fixed edit-save-cancel actions, and application-owned body content.",
17
+ "useWhen": [
18
+ "A settings page needs a persistent section that switches between read-only and editable content.",
19
+ "The parent can control one active editing section and owns validation, persistence, cancellation, and body content.",
20
+ "The section uses the standard Pencil, Save, and Cancel header workflow."
21
+ ],
22
+ "avoidWhen": [
23
+ "The surface contains a chart, legend, metric visualization, or other data-visualization composition.",
24
+ "The content is a general-purpose read-only card with no settings edit workflow.",
25
+ "The task must block the rest of the interface or requires confirmation in a modal.",
26
+ "Several sections must remain editable at the same time.",
27
+ "The header requires custom actions beyond the fixed edit-save-cancel workflow."
28
+ ],
29
+ "alternatives": [
30
+ {
31
+ "when": "The surface contains a chart or specialized data-visualization composition.",
32
+ "component": "component:ds-card-shell-data-viz",
33
+ "reason": "CardShellDataViz owns the independently evolving data-visualization shell contract."
34
+ },
35
+ {
36
+ "when": "The task must block the interface for one focused decision or short workflow.",
37
+ "component": "component:ds-modal",
38
+ "reason": "Modal provides top-layer focus containment and explicit blocking-task dismissal."
39
+ },
40
+ {
41
+ "when": "Only one labeled form control is needed without a section surface.",
42
+ "component": "component:ds-field",
43
+ "reason": "Field provides the control label, supporting copy, and validation relationship without card chrome."
44
+ }
45
+ ],
46
+ "commonlyComposedWith": [
47
+ "component:ds-field",
48
+ "component:ds-input",
49
+ "component:ds-select",
50
+ "component:ds-switch",
51
+ "component:ds-text"
52
+ ],
53
+ "accessibility": [
54
+ "Provide a concise heading that identifies the settings section; CardSetting renders it as an h2.",
55
+ "Edit, Save, and Cancel are icon-only controls with localized accessible labels supplied by the matching label props. When several sections share a page, include the section name in each label.",
56
+ "The owner must expose persistent field labels, descriptions, and validation messages inside the body.",
57
+ "Save must not remove validation errors or exit editing until the parent accepts the action and updates the controlled editing state.",
58
+ "Cancel must restore or retain the last persisted values according to product policy before the parent exits editing.",
59
+ "Keep only one CardSetting in edit mode so keyboard and screen-reader users have one unambiguous set of Save and Cancel actions.",
60
+ "When another section requests editing, resolve dirty changes in the current section before switching; never silently discard user input."
61
+ ],
62
+ "states": [
63
+ "The editing property is controlled; CardSetting never changes it internally.",
64
+ "dsAction emits edit, save, or cancel with the original pointer event. The parent decides whether and when editing changes.",
65
+ "When another section requests edit, the parent keeps one active section and resolves any dirty current section before switching.",
66
+ "The parent owns read-only versus editable body composition, dirty state, validation, persistence, loading, success, and error consequences.",
67
+ "A save request keeps the section editing while validation or persistence is pending; the parent exits only after success.",
68
+ "A cancel request does not persist changes; the parent restores its chosen value snapshot and exits editing."
69
+ ],
70
+ "responsiveBehavior": [
71
+ "Choose sm, md, or lg from the parent layout; each width uses its matching token-based minimum height.",
72
+ "The surface never exceeds its parent width, while the parent owns wrapping, columns, stacking, and breakpoint changes.",
73
+ "At narrow widths, keep the fixed header actions visible and let application-owned body layouts collapse before allowing horizontal overflow."
74
+ ],
75
+ "frameworkCaveats": {
76
+ "customElements": [
77
+ "Set editing as a JavaScript property and inspect dsAction.detail.action before updating the controlled state.",
78
+ "When one section emits edit, update every sibling CardSetting so only the requested section remains editing."
79
+ ],
80
+ "react": [
81
+ "Pass editing from parent state and inspect onDsAction event detail; do not treat Save as successful until validation and persistence complete.",
82
+ "Store one active section id and derive each CardSetting editing prop from that id."
83
+ ],
84
+ "angular": [
85
+ "Bind [editing] and inspect $event.detail.action from (dsAction); do not treat Save as successful until validation and persistence complete.",
86
+ "Store one active section id and derive each CardSetting editing binding from that id."
87
+ ]
88
+ },
89
+ "references": [
90
+ {
91
+ "label": "Storybook controlled and single-edit examples",
92
+ "path": "src/wc/components/CardSetting/CardSetting.stories.ts"
93
+ },
94
+ {
95
+ "label": "Cross-browser shell and action coverage",
96
+ "path": "tests/e2e/card-setting.spec.ts"
97
+ }
98
+ ]
99
+ },
14
100
  "api": {
15
101
  "props": {
16
102
  "cancelLabel": {
@@ -66,12 +152,12 @@
66
152
  },
67
153
  "events": [
68
154
  {
69
- "name": "dsEditingChange",
70
- "detail": "boolean",
155
+ "name": "dsAction",
156
+ "detail": "CardSettingActionDetail",
71
157
  "bubbles": true,
72
158
  "cancelable": true,
73
159
  "composed": true,
74
- "description": "Emits when the user enters or exits edit mode."
160
+ "description": "Emits a controlled edit, save, or cancel request."
75
161
  }
76
162
  ],
77
163
  "methods": [],
@@ -131,12 +217,12 @@
131
217
  },
132
218
  "events": [
133
219
  {
134
- "name": "dsEditingChange",
135
- "detail": "boolean",
220
+ "name": "dsAction",
221
+ "detail": "CardSettingActionDetail",
136
222
  "bubbles": true,
137
223
  "cancelable": true,
138
224
  "composed": true,
139
- "description": "Emits when the user enters or exits edit mode."
225
+ "description": "Emits a controlled edit, save, or cancel request."
140
226
  }
141
227
  ],
142
228
  "methods": [],
@@ -164,7 +250,7 @@
164
250
  "peerDependencies": {
165
251
  "required": [
166
252
  "@ds-mo/tokens ^3.1.1",
167
- "@ds-mo/icons ^6.2.0"
253
+ "@ds-mo/icons ^6.3.0"
168
254
  ],
169
255
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
170
256
  }
@@ -177,17 +263,17 @@
177
263
  "registryDependencies": [
178
264
  "button-filled",
179
265
  "button-unfilled",
180
- "card"
266
+ "text"
181
267
  ],
182
268
  "files": [
183
269
  {
184
270
  "path": "src/wc/components/CardSetting/CardSetting.css",
185
- "content": ":host {\n display: contents;\n}\n\n.card-setting__actions {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n gap: var(--dimension-space-100);\n}\n",
271
+ "content": ":host {\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n width: var(--_card-setting-width);\n min-height: var(--_card-setting-min-height);\n max-width: 100%;\n overflow: hidden;\n background-color: var(--color-background-primary);\n border: none;\n border-radius: var(--dimension-radius-025);\n box-shadow: 0 0 0 var(--dimension-stroke-width-012) var(--color-border-tertiary);\n}\n\n:host(.card-setting--editing) {\n background-color: var(--color-background-bold-brand);\n box-shadow: 0 0 0 var(--dimension-stroke-width-025) var(--color-border-bold-brand);\n}\n\n.card-setting__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n height: var(--dimension-size-600);\n padding: var(--dimension-space-100);\n flex: 0 0 auto;\n box-sizing: border-box;\n background-color: var(--color-background-secondary);\n border-bottom: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n}\n\n:host(.card-setting--editing) .card-setting__header {\n background-color: transparent;\n border-bottom-color: transparent;\n}\n\nds-text.card-setting__title {\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n min-width: 0;\n padding: calc(var(--dimension-space-075) + var(--dimension-space-025));\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.card-setting__actions {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n gap: var(--dimension-space-100);\n}\n\n.card-setting__body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n min-height: 0;\n box-sizing: border-box;\n background-color: var(--color-background-primary);\n border-radius: 0 0 var(--dimension-radius-025) var(--dimension-radius-025);\n}\n\n:host(.card-setting--editing) .card-setting__body {\n border-radius: var(--dimension-radius-025);\n}\n",
186
272
  "type": "registry:ui"
187
273
  },
188
274
  {
189
275
  "path": "src/wc/components/CardSetting/CardSetting.tsx",
190
- "content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport type { CardWidth } from '../Card/Card';\n\nexport type CardSettingWidth = CardWidth;\n\n@Component({\n tag: 'ds-card-setting',\n styleUrl: 'CardSetting.css',\n scoped: true,\n})\nexport class CardSetting {\n /** Section heading shown in the card header. */\n @Prop() heading!: string;\n\n /**\n * Card width token (`sm` / `md` / `lg`). Also sets host `min-height` to the\n * matching `--dimension-card-height-*` so the body fills available space\n * even when the slot is empty.\n */\n @Prop() cardWidth: CardSettingWidth = 'md';\n\n /** Controlled edit state — parent owns single-edit orchestration. */\n @Prop() editing = false;\n @Prop() editLabel: string = 'Edit';\n @Prop() cancelLabel: string = 'Cancel';\n @Prop() saveLabel: string = 'Save';\n\n /** Emits when the user enters or exits edit mode. */\n @Event() dsEditingChange!: EventEmitter<boolean>;\n\n private enterEdit = () => {\n this.dsEditingChange.emit(true);\n };\n\n private exitEdit = () => {\n this.dsEditingChange.emit(false);\n };\n\n render() {\n const editing = this.editing;\n\n return (\n <Host class=\"card-setting\">\n <ds-card\n heading={this.heading}\n cardWidth={this.cardWidth}\n appearance={editing ? 'editing' : 'default'}\n >\n {!editing ? (\n <ds-button-unfilled\n slot=\"actions\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Pencil\"\n aria-label={this.editLabel}\n onDsClick={this.enterEdit}\n />\n ) : (\n <div slot=\"actions\" class=\"card-setting__actions\">\n <ds-button-unfilled\n variant=\"icon\"\n type=\"button\"\n icon=\"Cross\"\n background=\"bold\"\n aria-label={this.cancelLabel}\n onDsClick={this.exitEdit}\n />\n <ds-button-filled\n variant=\"icon\"\n type=\"button\"\n icon=\"Check\"\n intent=\"brand\"\n contrast=\"faint\"\n aria-label={this.saveLabel}\n onDsClick={this.exitEdit}\n />\n </div>\n )}\n <slot />\n </ds-card>\n </Host>\n );\n }\n}\n",
276
+ "content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\n\nexport type CardSettingWidth = 'sm' | 'md' | 'lg';\nexport type CardSettingAction = 'edit' | 'save' | 'cancel';\n\nexport interface CardSettingActionDetail {\n action: CardSettingAction;\n originalEvent: MouseEvent;\n}\n\nconst CARD_WIDTH_VARS: Record<CardSettingWidth, string> = {\n sm: 'var(--dimension-card-width-sm)',\n md: 'var(--dimension-card-width-md)',\n lg: 'var(--dimension-card-width-lg)',\n};\n\nconst CARD_HEIGHT_VARS: Record<CardSettingWidth, string> = {\n sm: 'var(--dimension-card-height-sm)',\n md: 'var(--dimension-card-height-md)',\n lg: 'var(--dimension-card-height-lg)',\n};\n\nconst FAINT_BRAND_TITLE_COLOR = 'var(--color-foreground-faint-brand)';\n\n@Component({\n tag: 'ds-card-setting',\n styleUrl: 'CardSetting.css',\n scoped: true,\n})\nexport class CardSetting {\n /** Section heading shown in the card header. */\n @Prop() heading!: string;\n\n /**\n * Card width token (`sm` / `md` / `lg`). Also sets host `min-height` to the\n * matching `--dimension-card-height-*` so the body fills available space\n * even when the slot is empty.\n */\n @Prop() cardWidth: CardSettingWidth = 'md';\n\n /** Controlled edit state — parent owns single-edit orchestration. */\n @Prop() editing = false;\n @Prop() editLabel: string = 'Edit';\n @Prop() cancelLabel: string = 'Cancel';\n @Prop() saveLabel: string = 'Save';\n\n /** Emits a controlled edit, save, or cancel request. */\n @Event() dsAction!: EventEmitter<CardSettingActionDetail>;\n\n private emitAction(action: CardSettingAction, originalEvent: MouseEvent) {\n this.dsAction.emit({ action, originalEvent });\n }\n\n render() {\n const editing = this.editing;\n\n return (\n <Host\n class={{\n 'card-setting': true,\n 'card-setting--editing': editing,\n }}\n style={{\n '--_card-setting-width': CARD_WIDTH_VARS[this.cardWidth],\n '--_card-setting-min-height': CARD_HEIGHT_VARS[this.cardWidth],\n }}\n >\n <header class=\"card-setting__header\">\n <ds-text\n class=\"card-setting__title\"\n variant=\"text-title-small\"\n emphasis\n color={editing ? FAINT_BRAND_TITLE_COLOR : 'primary'}\n as=\"h2\"\n >\n {this.heading}\n </ds-text>\n <div class=\"card-setting__actions\">\n {!editing ? (\n <ds-button-unfilled\n variant=\"icon\"\n type=\"button\"\n icon=\"Pencil\"\n aria-label={this.editLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('edit', event.detail)\n }\n />\n ) : (\n [\n <ds-button-unfilled\n key=\"cancel\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Cross\"\n background=\"bold\"\n aria-label={this.cancelLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('cancel', event.detail)\n }\n />,\n <ds-button-filled\n key=\"save\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Check\"\n intent=\"brand\"\n contrast=\"faint\"\n aria-label={this.saveLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('save', event.detail)\n }\n />,\n ]\n )}\n </div>\n </header>\n <div class=\"card-setting__body\">\n <slot />\n </div>\n </Host>\n );\n }\n}\n",
191
277
  "type": "registry:ui"
192
278
  }
193
279
  ]
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "card-shell-data-viz",
4
4
  "title": "CardShellDataViz",
5
- "description": "Dedicated header, action, sizing, and body chrome for data-visualization cards whose layouts will evolve independently from general and settings cards.",
5
+ "description": "Dedicated header, action, sizing, and body chrome for data-visualization cards whose layouts evolve independently from settings sections.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
@@ -13,22 +13,22 @@
13
13
  "intent": {
14
14
  "audience": "specialized",
15
15
  "status": "experimental",
16
- "summary": "Dedicated header, action, sizing, and body chrome for data-visualization cards whose layouts will evolve independently from general and settings cards.",
16
+ "summary": "Dedicated header, action, sizing, and body chrome for data-visualization cards whose layouts evolve independently from settings sections.",
17
17
  "useWhen": [
18
18
  "A card contains a chart, data-visualization legend, metric visualization, or a specialized data-viz composition.",
19
19
  "A data-visualization card needs a heading and optional header actions while retaining a flexible body for its composing component.",
20
- "Several chart-specific cards need one shell boundary that can evolve without changing general or settings cards."
20
+ "Several chart-specific cards need one shell boundary that can evolve without changing settings sections."
21
21
  ],
22
22
  "avoidWhen": [
23
23
  "The card contains settings or editable form content; use the settings card composition.",
24
- "The surface is ordinary content without data visualization; use the general card shell.",
24
+ "The surface is ordinary content without data visualization; use a product-owned layout rather than data-viz card chrome.",
25
25
  "The shell would own chart-specific layout, hover synchronization, legends, or empty-state policy; keep those in the composing data-viz card."
26
26
  ],
27
27
  "alternatives": [
28
28
  {
29
- "when": "The card contains general content rather than data visualization.",
30
- "component": "component:ds-card",
31
- "reason": "Card owns the existing general and settings-compatible chrome contract."
29
+ "when": "The surface contains editable settings rather than data visualization.",
30
+ "component": "component:ds-card-setting",
31
+ "reason": "CardSetting owns the persistent controlled settings-section workflow."
32
32
  },
33
33
  {
34
34
  "when": "The composition is the standard donut chart plus synchronized legend.",
@@ -145,7 +145,7 @@
145
145
  "peerDependencies": {
146
146
  "required": [
147
147
  "@ds-mo/tokens ^3.1.1",
148
- "@ds-mo/icons ^6.2.0"
148
+ "@ds-mo/icons ^6.3.0"
149
149
  ],
150
150
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
151
151
  }
@@ -95,7 +95,7 @@
95
95
  "peerDependencies": {
96
96
  "required": [
97
97
  "@ds-mo/tokens ^3.1.1",
98
- "@ds-mo/icons ^6.2.0"
98
+ "@ds-mo/icons ^6.3.0"
99
99
  ],
100
100
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
101
101
  }
@@ -231,7 +231,7 @@
231
231
  "peerDependencies": {
232
232
  "required": [
233
233
  "@ds-mo/tokens ^3.1.1",
234
- "@ds-mo/icons ^6.2.0"
234
+ "@ds-mo/icons ^6.3.0"
235
235
  ],
236
236
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
237
237
  }
@@ -181,7 +181,7 @@
181
181
  "peerDependencies": {
182
182
  "required": [
183
183
  "@ds-mo/tokens ^3.1.1",
184
- "@ds-mo/icons ^6.2.0"
184
+ "@ds-mo/icons ^6.3.0"
185
185
  ],
186
186
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
187
187
  }
@@ -127,7 +127,7 @@
127
127
  "peerDependencies": {
128
128
  "required": [
129
129
  "@ds-mo/tokens ^3.1.1",
130
- "@ds-mo/icons ^6.2.0"
130
+ "@ds-mo/icons ^6.3.0"
131
131
  ],
132
132
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
133
133
  }
@@ -333,7 +333,7 @@
333
333
  "peerDependencies": {
334
334
  "required": [
335
335
  "@ds-mo/tokens ^3.1.1",
336
- "@ds-mo/icons ^6.2.0"
336
+ "@ds-mo/icons ^6.3.0"
337
337
  ],
338
338
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
339
339
  }
@@ -246,7 +246,7 @@
246
246
  "peerDependencies": {
247
247
  "required": [
248
248
  "@ds-mo/tokens ^3.1.1",
249
- "@ds-mo/icons ^6.2.0"
249
+ "@ds-mo/icons ^6.3.0"
250
250
  ],
251
251
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
252
252
  }
@@ -121,7 +121,7 @@
121
121
  "peerDependencies": {
122
122
  "required": [
123
123
  "@ds-mo/tokens ^3.1.1",
124
- "@ds-mo/icons ^6.2.0"
124
+ "@ds-mo/icons ^6.3.0"
125
125
  ],
126
126
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
127
127
  }
@@ -138,7 +138,7 @@
138
138
  "files": [
139
139
  {
140
140
  "path": "src/wc/components/CodeBlock/CodeBlock.css",
141
- "content": ":host {\n display: block;\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n}\n\n.code-block {\n width: 100%;\n min-width: 0;\n margin: 0;\n overflow: hidden;\n border-radius: var(--dimension-radius-050);\n background: var(--color-background-strong-neutral);\n color: var(--color-foreground-faint-neutral);\n box-sizing: border-box;\n}\n\n.code-block__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n min-height: var(--dimension-size-400);\n padding: var(--dimension-space-050) var(--dimension-space-100);\n background: var(--color-background-strong-neutral);\n box-sizing: border-box;\n}\n\npre {\n margin: 0;\n padding: var(--dimension-space-150);\n overflow: auto;\n font-family: var(--typography-fontfamily-mono);\n font-size: var(--typography-fontsize-sm);\n line-height: var(--typography-lineheight-sm);\n tab-size: 2;\n}\n\ncode {\n font: inherit;\n white-space: pre;\n}\n",
141
+ "content": ":host {\n display: block;\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.code-block {\n width: 100%;\n min-width: 0;\n margin: 0;\n overflow: hidden;\n border-radius: var(--dimension-radius-050);\n background: var(--color-background-strong-neutral);\n color: var(--color-foreground-faint-neutral);\n box-sizing: border-box;\n}\n\n.code-block__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n min-height: var(--dimension-size-400);\n padding: var(--dimension-space-050) var(--dimension-space-100);\n background: var(--color-background-strong-neutral);\n box-sizing: border-box;\n}\n\npre {\n margin: 0;\n padding: var(--dimension-space-150);\n overflow: auto;\n font-family: var(--typography-fontfamily-mono);\n font-size: var(--typography-fontsize-sm);\n line-height: var(--typography-lineheight-sm);\n tab-size: 2;\n -webkit-user-select: text;\n user-select: text;\n}\n\ncode {\n font: inherit;\n white-space: pre;\n}\n",
142
142
  "type": "registry:ui"
143
143
  },
144
144
  {
@@ -36,7 +36,7 @@
36
36
  ],
37
37
  "states": [
38
38
  "Rows reuse the md choice-item label/subtext recipe used by Menu, including 6px row padding, 2px text inset, and the shared content gap. The enclosing conversation-list scroll region supplies the larger 8px outer container padding.",
39
- "Busy and error are generic channel-neutral states; the owning product supplies suitable status wording.",
39
+ "Busy and error are generic channel-neutral states; the owning product supplies suitable status wording. A busy row pairs its body-small status with the sm 16px Loader at an 8px gap.",
40
40
  "Any positive unreadCount renders one dot on the title row rather than a visible counter; the accessible row name may still announce the exact unread quantity.",
41
41
  "Hover and press use the shared interaction hover and pressed overlays. Only the selected conversation uses the persistent active fill.",
42
42
  "The title uses body-medium emphasis, not title-small, and the body-small preview truncates after one line. Unread titles use primary foreground; read titles use secondary foreground.",
@@ -54,6 +54,15 @@
54
54
  },
55
55
  "api": {
56
56
  "props": {
57
+ "actionsOpen": {
58
+ "type": "boolean",
59
+ "resolvedType": "boolean",
60
+ "attribute": "actions-open",
61
+ "default": "false",
62
+ "required": false,
63
+ "mutable": false,
64
+ "description": "Keep the contextual-actions surface visible while its popup is open or closing."
65
+ },
57
66
  "conversationId": {
58
67
  "type": "string",
59
68
  "resolvedType": "string",
@@ -134,6 +143,15 @@
134
143
  "slots": []
135
144
  },
136
145
  "props": {
146
+ "actionsOpen": {
147
+ "type": "boolean",
148
+ "resolvedType": "boolean",
149
+ "attribute": "actions-open",
150
+ "default": "false",
151
+ "required": false,
152
+ "mutable": false,
153
+ "description": "Keep the contextual-actions surface visible while its popup is open or closing."
154
+ },
137
155
  "conversationId": {
138
156
  "type": "string",
139
157
  "resolvedType": "string",
@@ -235,7 +253,7 @@
235
253
  "peerDependencies": {
236
254
  "required": [
237
255
  "@ds-mo/tokens ^3.1.1",
238
- "@ds-mo/icons ^6.2.0"
256
+ "@ds-mo/icons ^6.3.0"
239
257
  ],
240
258
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
241
259
  }
@@ -253,12 +271,12 @@
253
271
  "files": [
254
272
  {
255
273
  "path": "src/wc/components/ConversationListItem/ConversationListItem.css",
256
- "content": "@import '../../utils/choice-list.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n}\n\n.conversation-list-item__row {\n position: relative;\n isolation: isolate;\n}\n\n.conversation-list-item {\n color: var(--color-foreground-primary);\n}\n\n.conversation-list-item--error {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-bold-negative);\n}\n\n.conversation-list-item__leading:empty,\n.conversation-list-item__trailing:empty {\n display: none;\n}\n\n.conversation-list-item__leading,\n.conversation-list-item__trailing,\n.conversation-list-item__preview-row {\n display: flex;\n align-items: center;\n}\n\n.conversation-list-item__leading {\n align-self: center;\n width: auto;\n height: auto;\n margin-inline-start: var(--ds-control-label-inset, var(--dimension-space-025));\n margin-inline-end: var(--dimension-space-050);\n}\n\n.conversation-list-item__content {\n min-width: 0;\n}\n\n.conversation-list-item__title-row {\n display: flex;\n align-items: center;\n min-width: 0;\n}\n\n.conversation-list-item__preview-row {\n min-width: 0;\n gap: var(--dimension-space-050);\n}\n\nds-text.conversation-list-item__title,\nds-text.conversation-list-item__preview {\n display: block;\n min-width: 0;\n}\n\nds-text.conversation-list-item__title {\n flex: 1;\n}\n\n.conversation-list-item__unread {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--dimension-size-200);\n margin-inline-end: var(--dimension-space-025);\n}\n\nds-text.conversation-list-item__preview.ds-choice-item__subtext {\n -webkit-line-clamp: 1;\n}\n\n.conversation-list-item__actions {\n position: absolute;\n top: 50%;\n right: var(--dimension-space-100);\n z-index: var(--dimension-z-index-floating);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--dimension-radius-half);\n background: var(--color-background-transparent);\n box-shadow: var(--effect-elevation-elevated-md);\n -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n backdrop-filter: blur(var(--effect-blur-sm));\n opacity: 0;\n pointer-events: none;\n transform: translateY(-50%);\n}\n\n.conversation-list-item__row:hover .conversation-list-item__actions,\n.conversation-list-item__row:has(:focus-visible) .conversation-list-item__actions {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Keep the row wash continuous while the pointer moves onto its sibling action. */\n.conversation-list-item__row:hover .conversation-list-item::after {\n background: var(--ds-interaction-hover);\n}\n\n.conversation-list-item__row:active .conversation-list-item::after {\n background: var(--ds-interaction-pressed);\n}\n\n@media (hover: none) {\n .conversation-list-item__actions {\n opacity: 1;\n pointer-events: auto;\n }\n\n :host(.conversation-list-item--has-actions) .conversation-list-item__title-row {\n padding-inline-end: var(--dimension-size-400);\n }\n}\n\n.conversation-list-item__time {\n position: absolute;\n width: var(--dimension-size-050);\n height: var(--dimension-size-050);\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n",
274
+ "content": "@import '../../utils/choice-list.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.conversation-list-item__row {\n position: relative;\n isolation: isolate;\n}\n\n.conversation-list-item {\n color: var(--color-foreground-primary);\n}\n\n.conversation-list-item--error {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--color-border-bold-negative);\n}\n\n.conversation-list-item__leading:empty,\n.conversation-list-item__trailing:empty {\n display: none;\n}\n\n.conversation-list-item__leading,\n.conversation-list-item__trailing,\n.conversation-list-item__preview-row {\n display: flex;\n align-items: center;\n}\n\n.conversation-list-item__leading {\n align-self: center;\n width: auto;\n height: auto;\n margin-inline-start: var(--ds-control-label-inset, var(--dimension-space-025));\n margin-inline-end: var(--dimension-space-050);\n}\n\n.conversation-list-item__content {\n min-width: 0;\n}\n\n.conversation-list-item__title-row {\n display: flex;\n align-items: center;\n min-width: 0;\n}\n\n.conversation-list-item__preview-row {\n min-width: 0;\n gap: var(--dimension-space-100);\n}\n\nds-text.conversation-list-item__title,\nds-text.conversation-list-item__preview {\n display: block;\n min-width: 0;\n}\n\nds-text.conversation-list-item__title {\n flex: 1;\n}\n\n.conversation-list-item__unread {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--dimension-size-200);\n margin-inline-end: var(--dimension-space-025);\n}\n\nds-text.conversation-list-item__preview.ds-choice-item__subtext {\n -webkit-line-clamp: 1;\n}\n\n.conversation-list-item__actions {\n position: absolute;\n top: 50%;\n right: var(--dimension-space-100);\n z-index: var(--dimension-z-index-floating);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--dimension-radius-half);\n background: var(--color-background-transparent);\n box-shadow: var(--effect-elevation-elevated-md);\n -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n backdrop-filter: blur(var(--effect-blur-sm));\n opacity: 0;\n pointer-events: none;\n transform: translateY(-50%);\n}\n\n.conversation-list-item__row:hover .conversation-list-item__actions,\n.conversation-list-item__row:has(:focus-visible) .conversation-list-item__actions,\n:host(.conversation-list-item--actions-open) .conversation-list-item__actions {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Keep the row wash continuous while the pointer moves onto its sibling action. */\n.conversation-list-item__row:hover .conversation-list-item::after {\n background: var(--ds-interaction-hover);\n}\n\n:host(.conversation-list-item--actions-open) .conversation-list-item::after {\n background: var(--ds-interaction-hover);\n}\n\n.conversation-list-item__row:active .conversation-list-item::after {\n background: var(--ds-interaction-pressed);\n}\n\n@media (hover: none) {\n .conversation-list-item__actions {\n opacity: 1;\n pointer-events: auto;\n }\n\n :host(.conversation-list-item--has-actions) .conversation-list-item__title-row {\n padding-inline-end: var(--dimension-size-400);\n }\n}\n\n.conversation-list-item__time {\n position: absolute;\n width: var(--dimension-size-050);\n height: var(--dimension-size-050);\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n",
257
275
  "type": "registry:ui"
258
276
  },
259
277
  {
260
278
  "path": "src/wc/components/ConversationListItem/ConversationListItem.tsx",
261
- "content": "import { Component, Element, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport type { ConversationItemState } from '../conversation-types';\n\n@Component({\n tag: 'ds-conversation-list-item',\n styleUrl: 'ConversationListItem.css',\n scoped: true,\n})\nexport class ConversationListItem {\n @Element() el!: HTMLElement;\n\n @Prop() conversationId: string = '';\n /** Visible conversation title. Named to avoid the native HTMLElement.title contract. */\n @Prop() conversationTitle: string = '';\n @Prop() preview: string = '';\n /** ISO timestamp for semantic activity metadata. */\n @Prop() updatedAt: string = '';\n @Prop() selected: boolean = false;\n @Prop() state: ConversationItemState = 'default';\n @Prop() statusLabel: string = '';\n @Prop() unreadCount: number = 0;\n\n @Event() dsSelect!: EventEmitter<{ id: string }>;\n\n private handleClick = () => this.dsSelect.emit({ id: this.conversationId });\n\n private handleActionsSlotChange = (event: Event) => {\n const slot = event.target as HTMLSlotElement;\n this.el.classList.toggle(\n 'conversation-list-item--has-actions',\n slot.assignedElements({ flatten: true }).length > 0\n );\n };\n\n render() {\n const busy = this.state === 'busy';\n const subtext = busy && this.statusLabel ? this.statusLabel : this.preview;\n const accessibleName = [\n this.conversationTitle,\n subtext,\n this.unreadCount > 0 ? `${this.unreadCount} unread` : '',\n ]\n .filter(Boolean)\n .join('. ');\n\n return (\n <Host role=\"listitem\">\n <div class=\"conversation-list-item__row\">\n <button\n type=\"button\"\n class={{\n 'conversation-list-item': true,\n 'ds-choice-item': true,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': this.selected,\n 'conversation-list-item--error': this.state === 'error',\n }}\n aria-label={accessibleName}\n aria-current={this.selected ? 'true' : undefined}\n aria-busy={busy ? 'true' : undefined}\n onClick={this.handleClick}\n >\n <span class=\"conversation-list-item__leading ds-choice-item__icon ds-interaction-fill__content\">\n <slot name=\"leading\" />\n </span>\n <span class=\"conversation-list-item__content ds-choice-item__content ds-interaction-fill__content\">\n <span class=\"conversation-list-item__title-row\">\n <ds-text\n class=\"conversation-list-item__title ds-choice-item__label\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color={this.unreadCount > 0 ? 'primary' : 'secondary'}\n lineTruncation={1}\n >\n {this.conversationTitle}\n </ds-text>\n {this.unreadCount > 0 ? (\n <span class=\"conversation-list-item__unread\">\n <ds-badge variant=\"dot\" surface=\"secondary\" />\n </span>\n ) : null}\n </span>\n <span class=\"conversation-list-item__preview-row\">\n {busy ? <ds-loader size=\"xs\" color=\"secondary\" /> : null}\n <ds-text\n class=\"conversation-list-item__preview ds-choice-item__subtext\"\n as=\"span\"\n variant=\"text-body-small\"\n color={this.state === 'error' ? 'negative' : 'secondary'}\n lineTruncation={1}\n >\n {subtext}\n </ds-text>\n </span>\n {this.updatedAt ? (\n <time class=\"conversation-list-item__time\" dateTime={this.updatedAt}>\n {this.updatedAt}\n </time>\n ) : null}\n </span>\n <span class=\"conversation-list-item__trailing ds-interaction-fill__content\">\n <slot name=\"trailing\" />\n </span>\n </button>\n <div class=\"conversation-list-item__actions\">\n <slot name=\"actions\" onSlotchange={this.handleActionsSlotChange} />\n </div>\n </div>\n </Host>\n );\n }\n}\n",
279
+ "content": "import { Component, Element, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\nimport type { ConversationItemState } from '../conversation-types';\n\n@Component({\n tag: 'ds-conversation-list-item',\n styleUrl: 'ConversationListItem.css',\n scoped: true,\n})\nexport class ConversationListItem {\n @Element() el!: HTMLElement;\n\n @Prop() conversationId: string = '';\n /** Visible conversation title. Named to avoid the native HTMLElement.title contract. */\n @Prop() conversationTitle: string = '';\n @Prop() preview: string = '';\n /** ISO timestamp for semantic activity metadata. */\n @Prop() updatedAt: string = '';\n @Prop() selected: boolean = false;\n @Prop() state: ConversationItemState = 'default';\n @Prop() statusLabel: string = '';\n @Prop() unreadCount: number = 0;\n /** Keep the contextual-actions surface visible while its popup is open or closing. */\n @Prop({ reflect: true }) actionsOpen: boolean = false;\n\n @Event() dsSelect!: EventEmitter<{ id: string }>;\n\n private handleClick = () => this.dsSelect.emit({ id: this.conversationId });\n\n private handleActionsSlotChange = (event: Event) => {\n const slot = event.target as HTMLSlotElement;\n this.el.classList.toggle(\n 'conversation-list-item--has-actions',\n slot.assignedElements({ flatten: true }).length > 0\n );\n };\n\n render() {\n const busy = this.state === 'busy';\n const subtext = busy && this.statusLabel ? this.statusLabel : this.preview;\n const accessibleName = [\n this.conversationTitle,\n subtext,\n this.unreadCount > 0 ? `${this.unreadCount} unread` : '',\n ]\n .filter(Boolean)\n .join('. ');\n\n return (\n <Host\n role=\"listitem\"\n class={{ 'conversation-list-item--actions-open': this.actionsOpen }}\n >\n <div class=\"conversation-list-item__row\">\n <button\n type=\"button\"\n class={{\n 'conversation-list-item': true,\n 'ds-choice-item': true,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': this.selected,\n 'conversation-list-item--error': this.state === 'error',\n }}\n aria-label={accessibleName}\n aria-current={this.selected ? 'true' : undefined}\n aria-busy={busy ? 'true' : undefined}\n onClick={this.handleClick}\n >\n <span class=\"conversation-list-item__leading ds-choice-item__icon ds-interaction-fill__content\">\n <slot name=\"leading\" />\n </span>\n <span class=\"conversation-list-item__content ds-choice-item__content ds-interaction-fill__content\">\n <span class=\"conversation-list-item__title-row\">\n <ds-text\n class=\"conversation-list-item__title ds-choice-item__label\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis\n color={this.unreadCount > 0 ? 'primary' : 'secondary'}\n lineTruncation={1}\n >\n {this.conversationTitle}\n </ds-text>\n {this.unreadCount > 0 ? (\n <span class=\"conversation-list-item__unread\">\n <ds-badge variant=\"dot\" surface=\"secondary\" />\n </span>\n ) : null}\n </span>\n <span class=\"conversation-list-item__preview-row\">\n {busy ? <ds-loader size=\"sm\" color=\"secondary\" /> : null}\n <ds-text\n class=\"conversation-list-item__preview ds-choice-item__subtext\"\n as=\"span\"\n variant=\"text-body-small\"\n color={this.state === 'error' ? 'negative' : 'secondary'}\n lineTruncation={1}\n >\n {subtext}\n </ds-text>\n </span>\n {this.updatedAt ? (\n <time class=\"conversation-list-item__time\" dateTime={this.updatedAt}>\n {this.updatedAt}\n </time>\n ) : null}\n </span>\n <span class=\"conversation-list-item__trailing ds-interaction-fill__content\">\n <slot name=\"trailing\" />\n </span>\n </button>\n <div class=\"conversation-list-item__actions\">\n <slot name=\"actions\" onSlotchange={this.handleActionsSlotChange} />\n </div>\n </div>\n </Host>\n );\n }\n}\n",
262
280
  "type": "registry:ui"
263
281
  }
264
282
  ]
@@ -111,7 +111,7 @@
111
111
  "peerDependencies": {
112
112
  "required": [
113
113
  "@ds-mo/tokens ^3.1.1",
114
- "@ds-mo/icons ^6.2.0"
114
+ "@ds-mo/icons ^6.3.0"
115
115
  ],
116
116
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
117
117
  }
@@ -127,7 +127,7 @@
127
127
  "files": [
128
128
  {
129
129
  "path": "src/wc/components/ConversationListSection/ConversationListSection.css",
130
- "content": ":host {\n display: block;\n box-sizing: border-box;\n}\n\n:host(:not(:first-child)) {\n /* Match the 16px separation between PanelNav groups. */\n margin-top: var(--dimension-space-200);\n}\n\n.conversation-list-section {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n}\n\nds-text.conversation-list-section__heading {\n display: block;\n padding: var(--dimension-space-050) var(--dimension-space-100);\n}\n\n.conversation-list-section__items {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-025);\n}\n",
130
+ "content": ":host {\n display: block;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n:host(:not(:first-child)) {\n /* Match the 16px separation between PanelNav groups. */\n margin-top: var(--dimension-space-200);\n}\n\n.conversation-list-section {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-050);\n}\n\nds-text.conversation-list-section__heading {\n display: block;\n padding: var(--dimension-space-050) var(--dimension-space-100);\n}\n\n.conversation-list-section__items {\n display: flex;\n flex-direction: column;\n gap: var(--dimension-space-025);\n}\n",
131
131
  "type": "registry:ui"
132
132
  },
133
133
  {
@@ -75,7 +75,7 @@
75
75
  "peerDependencies": {
76
76
  "required": [
77
77
  "@ds-mo/tokens ^3.1.1",
78
- "@ds-mo/icons ^6.2.0"
78
+ "@ds-mo/icons ^6.3.0"
79
79
  ],
80
80
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
81
81
  }
@@ -89,7 +89,7 @@
89
89
  "files": [
90
90
  {
91
91
  "path": "src/wc/components/ConversationList/ConversationList.css",
92
- "content": ":host {\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n.conversation-list {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n.conversation-list__content {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: var(--dimension-space-100);\n padding-bottom: calc(var(--dimension-size-600) + var(--dimension-size-100));\n box-sizing: border-box;\n}\n\n.conversation-list__empty {\n display: contents;\n}\n\n.conversation-list__actions {\n position: absolute;\n right: var(--dimension-space-100);\n bottom: var(--dimension-space-100);\n z-index: var(--dimension-z-index-floating);\n}\n\n.conversation-list__actions-surface {\n display: flex;\n border-radius: var(--dimension-radius-half);\n box-shadow: var(--effect-elevation-elevated-floating);\n}\n\n.conversation-list__actions-surface:empty {\n display: none;\n}\n",
92
+ "content": ":host {\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.conversation-list {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n box-sizing: border-box;\n}\n\n.conversation-list__content {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: var(--dimension-space-100);\n padding-bottom: calc(var(--dimension-size-600) + var(--dimension-size-100));\n box-sizing: border-box;\n}\n\n.conversation-list__empty {\n display: contents;\n}\n\n.conversation-list__actions {\n position: absolute;\n right: var(--dimension-space-200);\n bottom: var(--dimension-space-200);\n z-index: var(--dimension-z-index-floating);\n}\n\n.conversation-list__actions-surface {\n display: flex;\n border-radius: var(--dimension-radius-half);\n box-shadow: var(--effect-elevation-elevated-md);\n}\n\n.conversation-list__actions-surface:empty {\n display: none;\n}\n",
93
93
  "type": "registry:ui"
94
94
  },
95
95
  {