@ds-mo/ui 13.14.0 → 13.16.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 (329) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +73 -22
  3. package/dist/agent.json +300 -69
  4. package/dist/angular/ds-agent-questionnaire.d.ts +19 -0
  5. package/dist/angular/ds-agent-questionnaire.js +46 -0
  6. package/dist/angular/ds-agent-response.d.ts +1 -1
  7. package/dist/angular/ds-agent-response.js +3 -3
  8. package/dist/angular/ds-agent-source-list.d.ts +9 -2
  9. package/dist/angular/ds-agent-source-list.js +7 -3
  10. package/dist/angular/ds-agent-tool-call.d.ts +9 -2
  11. package/dist/angular/ds-agent-tool-call.js +9 -5
  12. package/dist/angular/ds-bar-title.d.ts +1 -1
  13. package/dist/angular/ds-bar-title.js +3 -3
  14. package/dist/angular/ds-checkbox.d.ts +1 -1
  15. package/dist/angular/ds-checkbox.js +3 -3
  16. package/dist/angular/ds-message-composer.d.ts +1 -1
  17. package/dist/angular/ds-message-composer.js +3 -3
  18. package/dist/angular/ds-radio.js +2 -1
  19. package/dist/angular/ds-shell-page.d.ts +1 -1
  20. package/dist/angular/ds-shell-page.js +3 -3
  21. package/dist/angular/ds-table.d.ts +1 -1
  22. package/dist/angular/ds-table.js +3 -3
  23. package/dist/angular/index.d.ts +1 -1
  24. package/dist/angular/index.js +1 -0
  25. package/dist/angular/proxies.d.ts +1 -0
  26. package/dist/angular/proxies.js +1 -0
  27. package/dist/components/ds-agent-activity.js +1 -1
  28. package/dist/components/ds-agent-questionnaire.d.ts +24 -0
  29. package/dist/components/ds-agent-questionnaire.js +2 -0
  30. package/dist/components/ds-agent-questionnaire.js.map +1 -0
  31. package/dist/components/ds-agent-response.js +1 -1
  32. package/dist/components/ds-agent-response.js.map +1 -1
  33. package/dist/components/ds-agent-source-list.js +1 -1
  34. package/dist/components/ds-agent-tool-call.js +1 -1
  35. package/dist/components/ds-attachment-list.js +1 -1
  36. package/dist/components/ds-badge.js +1 -1
  37. package/dist/components/ds-banner.js +1 -1
  38. package/dist/components/ds-bar-nav.js +1 -1
  39. package/dist/components/ds-bar-title.js +1 -1
  40. package/dist/components/ds-bar-workflow.js +1 -1
  41. package/dist/components/ds-breadcrumb.js +1 -1
  42. package/dist/components/ds-button-filled.js +1 -1
  43. package/dist/components/ds-button-unfilled.js +1 -1
  44. package/dist/components/ds-card-chart.js +1 -1
  45. package/dist/components/ds-card-overview.js +1 -1
  46. package/dist/components/ds-card-overview.js.map +1 -1
  47. package/dist/components/ds-card-setting.js +1 -1
  48. package/dist/components/ds-chart-legend.js +1 -1
  49. package/dist/components/ds-chart-legend.js.map +1 -1
  50. package/dist/components/ds-chart.js +1 -1
  51. package/dist/components/ds-chart.js.map +1 -1
  52. package/dist/components/ds-checkbox.js +1 -1
  53. package/dist/components/ds-chip.js +1 -1
  54. package/dist/components/ds-chip.js.map +1 -1
  55. package/dist/components/ds-code-block.js +1 -1
  56. package/dist/components/ds-conversation-list-item.js +1 -1
  57. package/dist/components/ds-conversation-list-item.js.map +1 -1
  58. package/dist/components/ds-conversation-list-section.js +1 -1
  59. package/dist/components/ds-conversation-list.js +1 -1
  60. package/dist/components/ds-divider.js +1 -1
  61. package/dist/components/ds-empty-state.js +1 -1
  62. package/dist/components/ds-field.js +1 -1
  63. package/dist/components/ds-input.js +1 -1
  64. package/dist/components/ds-input.js.map +1 -1
  65. package/dist/components/ds-markdown.js +1 -1
  66. package/dist/components/ds-menu.js +1 -1
  67. package/dist/components/ds-message-actions.js +1 -1
  68. package/dist/components/ds-message-bubble.js +1 -1
  69. package/dist/components/ds-message-bubble.js.map +1 -1
  70. package/dist/components/ds-message-composer.js +1 -1
  71. package/dist/components/ds-message-composer.js.map +1 -1
  72. package/dist/components/ds-message-scroller.js +1 -1
  73. package/dist/components/ds-message-scroller.js.map +1 -1
  74. package/dist/components/ds-message.js +1 -1
  75. package/dist/components/ds-mobile-bar-nav.js +1 -1
  76. package/dist/components/ds-mobile-header.js +1 -1
  77. package/dist/components/ds-mobile-section-switcher.js +1 -1
  78. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  79. package/dist/components/ds-modal.js +1 -1
  80. package/dist/components/ds-panel-nav.js +1 -1
  81. package/dist/components/ds-panel-sub-nav.js +1 -1
  82. package/dist/components/ds-panel-sub-nav.js.map +1 -1
  83. package/dist/components/ds-panel-tool-header.js +1 -1
  84. package/dist/components/ds-panel-tool-search.js +1 -1
  85. package/dist/components/ds-panel-tools.js +1 -1
  86. package/dist/components/ds-radio.js +1 -1
  87. package/dist/components/ds-radio.js.map +1 -1
  88. package/dist/components/ds-scroll-overlay.js +1 -1
  89. package/dist/components/ds-select.js +1 -1
  90. package/dist/components/ds-select.js.map +1 -1
  91. package/dist/components/ds-shell-app.js +1 -1
  92. package/dist/components/ds-shell-app.js.map +1 -1
  93. package/dist/components/ds-shell-page.js +1 -1
  94. package/dist/components/ds-shell-tools.js +1 -1
  95. package/dist/components/ds-skeleton.js +1 -1
  96. package/dist/components/ds-slider.js +1 -1
  97. package/dist/components/ds-slider.js.map +1 -1
  98. package/dist/components/ds-swatch-picker.js +1 -1
  99. package/dist/components/ds-switch.js +1 -1
  100. package/dist/components/ds-tab-group.js +1 -1
  101. package/dist/components/ds-table.js +1 -1
  102. package/dist/components/ds-table.js.map +1 -1
  103. package/dist/components/ds-tag.js +1 -1
  104. package/dist/components/ds-text.js +1 -1
  105. package/dist/components/ds-toast.js +1 -1
  106. package/dist/components/ds-tooltip-chart.js +1 -1
  107. package/dist/components/ds-tooltip.js +1 -1
  108. package/dist/components/ds-typing-indicator.js +1 -1
  109. package/dist/components/index.d.ts +10 -0
  110. package/dist/components/{p-PQgDzzn9.js → p-00or0yGw.js} +2 -2
  111. package/dist/components/p-B2SoinWf.js +2 -0
  112. package/dist/components/p-B2SoinWf.js.map +1 -0
  113. package/dist/components/{p-C5ecLNZA.js → p-B53Vpuqq.js} +2 -2
  114. package/dist/components/{p-BULWzaBs.js → p-B69Re7HW.js} +2 -2
  115. package/dist/components/{p-CeyoHpPn.js → p-B94M62Tx.js} +2 -2
  116. package/dist/components/p-BAAjiAIF.js +2 -0
  117. package/dist/components/p-BAAjiAIF.js.map +1 -0
  118. package/dist/components/p-BMAZWEDa.js +2 -0
  119. package/dist/components/p-BMAZWEDa.js.map +1 -0
  120. package/dist/components/p-BMc0DE59.js +2 -0
  121. package/dist/components/p-BMc0DE59.js.map +1 -0
  122. package/dist/components/{p-B6z51yBf.js → p-BNkU9dcc.js} +2 -2
  123. package/dist/components/p-BPbE-Kbl.js +2 -0
  124. package/dist/components/p-BPbE-Kbl.js.map +1 -0
  125. package/dist/components/{p-B6linhwY.js → p-BRze4tRe.js} +2 -2
  126. package/dist/components/p-BtUgncSJ.js +2 -0
  127. package/dist/components/p-BtUgncSJ.js.map +1 -0
  128. package/dist/components/p-BxR8omPQ.js +2 -0
  129. package/dist/components/p-BxR8omPQ.js.map +1 -0
  130. package/dist/components/p-C891ueIx.js +2 -0
  131. package/dist/components/p-C891ueIx.js.map +1 -0
  132. package/dist/components/{p-CY19gMiz.js → p-CJq3n5kA.js} +2 -2
  133. package/dist/components/p-CKo3OFG1.js +2 -0
  134. package/dist/components/p-CKo3OFG1.js.map +1 -0
  135. package/dist/components/p-CMbcRN92.js +2 -0
  136. package/dist/components/{p-cPh32CFB.js.map → p-CMbcRN92.js.map} +1 -1
  137. package/dist/components/p-CXduurfd.js.map +1 -1
  138. package/dist/components/{p-DLwM1RNL.js → p-CdKz-QT2.js} +2 -2
  139. package/dist/components/p-CdKz-QT2.js.map +1 -0
  140. package/dist/components/p-CnIvWEVm.js +2 -0
  141. package/dist/components/{p-BPS17WvI.js.map → p-CnIvWEVm.js.map} +1 -1
  142. package/dist/components/p-CnUq7E8S.js +2 -0
  143. package/dist/components/{p-DyDkQ8H9.js.map → p-CnUq7E8S.js.map} +1 -1
  144. package/dist/components/{p-C9StMCWg.js → p-Cohb8T7G.js} +2 -2
  145. package/dist/components/p-CqDJRVT8.js +2 -0
  146. package/dist/components/p-CqDJRVT8.js.map +1 -0
  147. package/dist/components/p-CsXC1cHI.js +2 -0
  148. package/dist/components/{p-DYAQVvOu.js.map → p-CsXC1cHI.js.map} +1 -1
  149. package/dist/components/p-D4j0deyd.js +2 -0
  150. package/dist/components/{p-DAM4CDXT.js.map → p-D4j0deyd.js.map} +1 -1
  151. package/dist/components/p-D5sIwLjA.js +2 -0
  152. package/dist/components/p-D5sIwLjA.js.map +1 -0
  153. package/dist/components/p-D9CDoQBx.js +2 -0
  154. package/dist/components/{p-C-aCs6ID.js.map → p-D9CDoQBx.js.map} +1 -1
  155. package/dist/components/p-D9FdOR_m.js +2 -0
  156. package/dist/components/{p-BSdAFIcG.js.map → p-D9FdOR_m.js.map} +1 -1
  157. package/dist/components/p-DStd98dM.js +2 -0
  158. package/dist/components/{p-Azjx2SsL.js.map → p-DStd98dM.js.map} +1 -1
  159. package/dist/components/p-Dbobj7DI.js +2 -0
  160. package/dist/components/p-Dbobj7DI.js.map +1 -0
  161. package/dist/components/{p-BNiIyZCn.js → p-Dhx_3_tf.js} +2 -2
  162. package/dist/components/p-Dhx_3_tf.js.map +1 -0
  163. package/dist/components/p-DrvEhJhO.js +2 -0
  164. package/dist/components/p-DrvEhJhO.js.map +1 -0
  165. package/dist/components/p-DyqLVdJp.js +2 -0
  166. package/dist/components/{p-CCYgBUUS.js.map → p-DyqLVdJp.js.map} +1 -1
  167. package/dist/components/p-GWUTrIlk.js +2 -0
  168. package/dist/components/p-GWUTrIlk.js.map +1 -0
  169. package/dist/components/p-NQOI5gN1.js +2 -0
  170. package/dist/components/p-NQOI5gN1.js.map +1 -0
  171. package/dist/components/p-Qf-os3lG.js +2 -0
  172. package/dist/components/{p-B8HG9OFh.js.map → p-Qf-os3lG.js.map} +1 -1
  173. package/dist/components/{p-DMJEOAwP.js → p-chA2WA5x.js} +2 -2
  174. package/dist/components/{p-DwKC_9u2.js → p-dEouiXJ8.js} +2 -2
  175. package/dist/components/p-m5CSrx_s.js +2 -0
  176. package/dist/components/{p-B1JB0Zn-.js.map → p-m5CSrx_s.js.map} +1 -1
  177. package/dist/docs/components.json +964 -11
  178. package/dist/lib/components/ShellApp/shell-app-types.d.ts +12 -0
  179. package/dist/lib/utils/index.js +41 -4
  180. package/dist/lib/utils/index.js.map +2 -2
  181. package/dist/lib/utils/resolve-css-length-px.d.ts +1 -1
  182. package/dist/mcp-data/registry/agent-activity.json +1 -1
  183. package/dist/mcp-data/registry/agent-questionnaire.json +273 -0
  184. package/dist/mcp-data/registry/agent-response.json +32 -11
  185. package/dist/mcp-data/registry/agent-source-list.json +33 -13
  186. package/dist/mcp-data/registry/agent-tool-call.json +59 -18
  187. package/dist/mcp-data/registry/attachment-list.json +1 -1
  188. package/dist/mcp-data/registry/avatar.json +1 -1
  189. package/dist/mcp-data/registry/badge.json +1 -1
  190. package/dist/mcp-data/registry/banner.json +1 -1
  191. package/dist/mcp-data/registry/bar-nav.json +2 -2
  192. package/dist/mcp-data/registry/bar-title.json +39 -4
  193. package/dist/mcp-data/registry/bar-workflow.json +1 -1
  194. package/dist/mcp-data/registry/breadcrumb.json +2 -2
  195. package/dist/mcp-data/registry/button-filled.json +1 -1
  196. package/dist/mcp-data/registry/button-unfilled.json +1 -1
  197. package/dist/mcp-data/registry/card-chart.json +1 -1
  198. package/dist/mcp-data/registry/card-overview.json +1 -1
  199. package/dist/mcp-data/registry/card-setting.json +1 -1
  200. package/dist/mcp-data/registry/chart-legend.json +2 -2
  201. package/dist/mcp-data/registry/chart.json +2 -2
  202. package/dist/mcp-data/registry/checkbox.json +26 -9
  203. package/dist/mcp-data/registry/chip.json +2 -2
  204. package/dist/mcp-data/registry/code-block.json +14 -6
  205. package/dist/mcp-data/registry/conversation-list-item.json +2 -2
  206. package/dist/mcp-data/registry/conversation-list-section.json +1 -1
  207. package/dist/mcp-data/registry/conversation-list.json +1 -1
  208. package/dist/mcp-data/registry/divider.json +1 -1
  209. package/dist/mcp-data/registry/empty-state.json +1 -1
  210. package/dist/mcp-data/registry/field.json +1 -1
  211. package/dist/mcp-data/registry/icon.json +1 -1
  212. package/dist/mcp-data/registry/input.json +1 -1
  213. package/dist/mcp-data/registry/loader.json +1 -1
  214. package/dist/mcp-data/registry/markdown.json +1 -1
  215. package/dist/mcp-data/registry/menu.json +2 -2
  216. package/dist/mcp-data/registry/message-actions.json +1 -1
  217. package/dist/mcp-data/registry/message-bubble.json +2 -2
  218. package/dist/mcp-data/registry/message-composer.json +30 -8
  219. package/dist/mcp-data/registry/message-scroller.json +17 -9
  220. package/dist/mcp-data/registry/message.json +1 -1
  221. package/dist/mcp-data/registry/mobile-bar-nav.json +1 -1
  222. package/dist/mcp-data/registry/mobile-header.json +1 -1
  223. package/dist/mcp-data/registry/mobile-section-switcher.json +1 -1
  224. package/dist/mcp-data/registry/mobile-sheet-nav.json +1 -1
  225. package/dist/mcp-data/registry/modal.json +1 -1
  226. package/dist/mcp-data/registry/panel-nav.json +2 -2
  227. package/dist/mcp-data/registry/panel-sub-nav.json +1 -1
  228. package/dist/mcp-data/registry/panel-tool-header.json +1 -1
  229. package/dist/mcp-data/registry/panel-tool-search.json +1 -1
  230. package/dist/mcp-data/registry/panel-tools.json +2 -2
  231. package/dist/mcp-data/registry/radio.json +24 -11
  232. package/dist/mcp-data/registry/registry.json +303 -69
  233. package/dist/mcp-data/registry/scroll-overlay.json +1 -1
  234. package/dist/mcp-data/registry/select.json +2 -2
  235. package/dist/mcp-data/registry/shell-app.json +5 -5
  236. package/dist/mcp-data/registry/shell-page.json +78 -5
  237. package/dist/mcp-data/registry/shell-tools.json +1 -1
  238. package/dist/mcp-data/registry/skeleton.json +1 -1
  239. package/dist/mcp-data/registry/slider.json +2 -2
  240. package/dist/mcp-data/registry/swatch-picker.json +2 -2
  241. package/dist/mcp-data/registry/switch.json +2 -2
  242. package/dist/mcp-data/registry/tab-group.json +1 -1
  243. package/dist/mcp-data/registry/table.json +89 -13
  244. package/dist/mcp-data/registry/tag.json +1 -1
  245. package/dist/mcp-data/registry/text.json +2 -2
  246. package/dist/mcp-data/registry/toast.json +1 -1
  247. package/dist/mcp-data/registry/tooltip-chart.json +5 -3
  248. package/dist/mcp-data/registry/tooltip.json +1 -1
  249. package/dist/mcp-data/registry/typing-indicator.json +1 -1
  250. package/dist/react/components.d.ts +1 -0
  251. package/dist/react/components.js +1 -0
  252. package/dist/react/ds-agent-questionnaire.d.ts +13 -0
  253. package/dist/react/ds-agent-questionnaire.js +15 -0
  254. package/dist/react/ds-agent-source-list.d.ts +7 -2
  255. package/dist/react/ds-agent-source-list.js +1 -1
  256. package/dist/react/ds-agent-tool-call.d.ts +7 -2
  257. package/dist/react/ds-agent-tool-call.js +1 -1
  258. package/dist/styles/prose.css +21 -4
  259. package/dist/styles/table.css +161 -10
  260. package/dist/types/components/AgentQuestionnaire/AgentQuestionnaire.d.ts +49 -0
  261. package/dist/types/components/AgentQuestionnaire/agent-questionnaire-model.d.ts +13 -0
  262. package/dist/types/components/AgentResponse/AgentResponse.d.ts +3 -1
  263. package/dist/types/components/AgentSourceList/AgentSourceList.d.ts +5 -0
  264. package/dist/types/components/AgentToolCall/AgentToolCall.d.ts +21 -0
  265. package/dist/types/components/BarTitle/BarTitle.d.ts +5 -0
  266. package/dist/types/components/Checkbox/Checkbox.d.ts +3 -0
  267. package/dist/types/components/MessageComposer/MessageComposer.d.ts +3 -0
  268. package/dist/types/components/MessageScroller/MessageScroller.d.ts +21 -1
  269. package/dist/types/components/Radio/Radio.d.ts +4 -0
  270. package/dist/types/components/ShellApp/shell-app-types.d.ts +12 -0
  271. package/dist/types/components/ShellPage/ShellPage.d.ts +9 -0
  272. package/dist/types/components/Table/Table.d.ts +30 -0
  273. package/dist/types/components/Table/table-cell-model.d.ts +2 -0
  274. package/dist/types/components/Table/table-layout-controller.d.ts +6 -0
  275. package/dist/types/components/Table/table-types.d.ts +3 -0
  276. package/dist/types/components/Table/table-viewport-fit-controller.d.ts +53 -0
  277. package/dist/types/components/conversation-types.d.ts +51 -2
  278. package/dist/types/components.d.ts +249 -2
  279. package/dist/types/utils/resolve-css-length-px.d.ts +1 -1
  280. package/package.json +5 -3
  281. package/dist/components/p-Azjx2SsL.js +0 -2
  282. package/dist/components/p-B1JB0Zn-.js +0 -2
  283. package/dist/components/p-B2t8alvH.js +0 -2
  284. package/dist/components/p-B2t8alvH.js.map +0 -1
  285. package/dist/components/p-B8HG9OFh.js +0 -2
  286. package/dist/components/p-BJMRBHKW.js +0 -2
  287. package/dist/components/p-BJMRBHKW.js.map +0 -1
  288. package/dist/components/p-BJVzrR_P.js +0 -2
  289. package/dist/components/p-BJVzrR_P.js.map +0 -1
  290. package/dist/components/p-BL0JHLJp.js +0 -2
  291. package/dist/components/p-BL0JHLJp.js.map +0 -1
  292. package/dist/components/p-BNiIyZCn.js.map +0 -1
  293. package/dist/components/p-BPS17WvI.js +0 -2
  294. package/dist/components/p-BSdAFIcG.js +0 -2
  295. package/dist/components/p-C-aCs6ID.js +0 -2
  296. package/dist/components/p-CCYgBUUS.js +0 -2
  297. package/dist/components/p-CE82aQZr.js +0 -2
  298. package/dist/components/p-CE82aQZr.js.map +0 -1
  299. package/dist/components/p-COmqMfQo.js +0 -2
  300. package/dist/components/p-COmqMfQo.js.map +0 -1
  301. package/dist/components/p-Ch5yT3UA.js +0 -2
  302. package/dist/components/p-Ch5yT3UA.js.map +0 -1
  303. package/dist/components/p-CiUciD5Z.js +0 -2
  304. package/dist/components/p-CiUciD5Z.js.map +0 -1
  305. package/dist/components/p-D6g070eY.js +0 -2
  306. package/dist/components/p-D6g070eY.js.map +0 -1
  307. package/dist/components/p-DAM4CDXT.js +0 -2
  308. package/dist/components/p-DLwM1RNL.js.map +0 -1
  309. package/dist/components/p-DYAQVvOu.js +0 -2
  310. package/dist/components/p-DmblUQ4D.js +0 -2
  311. package/dist/components/p-DmblUQ4D.js.map +0 -1
  312. package/dist/components/p-DyDkQ8H9.js +0 -2
  313. package/dist/components/p-Dz3Ufmr4.js +0 -2
  314. package/dist/components/p-Dz3Ufmr4.js.map +0 -1
  315. package/dist/components/p-cPh32CFB.js +0 -2
  316. package/dist/components/p-fkQhr5s9.js +0 -2
  317. package/dist/components/p-fkQhr5s9.js.map +0 -1
  318. package/dist/components/p-oOKTL-yx.js +0 -2
  319. package/dist/components/p-oOKTL-yx.js.map +0 -1
  320. /package/dist/components/{p-PQgDzzn9.js.map → p-00or0yGw.js.map} +0 -0
  321. /package/dist/components/{p-C5ecLNZA.js.map → p-B53Vpuqq.js.map} +0 -0
  322. /package/dist/components/{p-BULWzaBs.js.map → p-B69Re7HW.js.map} +0 -0
  323. /package/dist/components/{p-CeyoHpPn.js.map → p-B94M62Tx.js.map} +0 -0
  324. /package/dist/components/{p-B6z51yBf.js.map → p-BNkU9dcc.js.map} +0 -0
  325. /package/dist/components/{p-B6linhwY.js.map → p-BRze4tRe.js.map} +0 -0
  326. /package/dist/components/{p-CY19gMiz.js.map → p-CJq3n5kA.js.map} +0 -0
  327. /package/dist/components/{p-C9StMCWg.js.map → p-Cohb8T7G.js.map} +0 -0
  328. /package/dist/components/{p-DMJEOAwP.js.map → p-chA2WA5x.js.map} +0 -0
  329. /package/dist/components/{p-DwKC_9u2.js.map → p-dEouiXJ8.js.map} +0 -0
@@ -229,7 +229,7 @@
229
229
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: definition.",
230
230
  "peerDependencies": {
231
231
  "required": [
232
- "@ds-mo/tokens ^6.0.0",
232
+ "@ds-mo/tokens ^6.2.1",
233
233
  "@ds-mo/icons ^6.6.0"
234
234
  ],
235
235
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -246,7 +246,7 @@
246
246
  "files": [
247
247
  {
248
248
  "path": "src/wc/components/Chart/Chart.css",
249
- "content": "@import '../../utils/forced-colors.css';\n\n:host {\n position: relative;\n display: block;\n width: var(--ds-chart-width, 100%);\n max-width: 100%;\n height: var(--ds-chart-height, 320px);\n font-family: var(--typography-font-family);\n}\n\n.chart__surface {\n display: block;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: none;\n touch-action: none;\n}\n\n.chart__surface:focus-visible {\n outline: var(--dimension-stroke-width-025) solid var(--color-border-focus);\n outline-offset: var(--dimension-space-025);\n}\n\n.chart__interaction-surface {\n fill: transparent;\n pointer-events: all;\n}\n\n.chart__grid,\n.chart__axis-line,\n.chart__plot-boundary,\n.chart__mark {\n vector-effect: non-scaling-stroke;\n}\n\n.chart__grid {\n stroke: var(--color-foreground-quaternary);\n stroke-width: var(--dimension-stroke-width-012);\n}\n\n.chart__plot-boundary {\n stroke: var(--color-foreground-quaternary);\n stroke-width: var(--dimension-stroke-width-012);\n}\n\n.chart__axis-line {\n stroke: var(--color-foreground-tertiary);\n stroke-width: var(--dimension-stroke-width-012);\n}\n\n.chart__tick,\n.chart__text-mark {\n fill: var(--color-foreground-secondary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-xs);\n font-weight: var(--typography-weight-medium);\n letter-spacing: var(--typography-letterspacing-positive);\n text-transform: uppercase;\n}\n\n.chart__axis-title {\n fill: var(--color-foreground-primary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-xs);\n font-weight: var(--typography-weight-semibold);\n letter-spacing: var(--typography-letterspacing-positive);\n text-transform: uppercase;\n}\n\n.chart__center-value {\n fill: var(--color-foreground-primary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-xl);\n font-weight: var(--typography-weight-semibold);\n letter-spacing: var(--typography-letterspacing-negative-double);\n}\n\n.chart__center-caption {\n fill: var(--color-foreground-secondary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.chart__focus {\n fill: none;\n stroke: var(--color-border-focus);\n stroke-width: var(--dimension-stroke-width-025);\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n}\n\n.chart__focus-guide {\n stroke: var(--color-foreground-quaternary);\n stroke-width: var(--dimension-stroke-width-012);\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n}\n\n.chart__focus-point {\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n}\n\n.chart__description,\n.chart__status {\n position: absolute;\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-stroke-width-012);\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n@media (forced-colors: active) {\n .chart__mark,\n .chart__focus,\n .chart__focus-point {\n forced-color-adjust: none;\n }\n\n .chart__focus {\n stroke: var(--ds-forced-color-focus);\n }\n}\n",
249
+ "content": "@import '../../utils/forced-colors.css';\n\n:host {\n position: relative;\n display: block;\n width: var(--ds-chart-width, 100%);\n max-width: 100%;\n height: var(--ds-chart-height, 320px);\n font-family: var(--typography-font-family-ui);\n}\n\n.chart__surface {\n display: block;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: none;\n touch-action: none;\n}\n\n.chart__surface:focus-visible {\n outline: var(--dimension-stroke-width-025) solid var(--color-border-focus);\n outline-offset: var(--dimension-space-025);\n}\n\n.chart__interaction-surface {\n fill: transparent;\n pointer-events: all;\n}\n\n.chart__grid,\n.chart__axis-line,\n.chart__plot-boundary,\n.chart__mark {\n vector-effect: non-scaling-stroke;\n}\n\n.chart__grid {\n stroke: var(--color-foreground-quaternary);\n stroke-width: var(--dimension-stroke-width-012);\n}\n\n.chart__plot-boundary {\n stroke: var(--color-foreground-quaternary);\n stroke-width: var(--dimension-stroke-width-012);\n}\n\n.chart__axis-line {\n stroke: var(--color-foreground-tertiary);\n stroke-width: var(--dimension-stroke-width-012);\n}\n\n.chart__tick,\n.chart__text-mark {\n fill: var(--color-foreground-secondary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-xs);\n font-weight: var(--typography-weight-medium);\n letter-spacing: var(--typography-letterspacing-positive);\n text-transform: uppercase;\n}\n\n.chart__axis-title {\n fill: var(--color-foreground-primary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-xs);\n font-weight: var(--typography-weight-semibold);\n letter-spacing: var(--typography-letterspacing-positive);\n text-transform: uppercase;\n}\n\n.chart__center-value {\n fill: var(--color-foreground-primary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-xl);\n font-weight: var(--typography-weight-semibold);\n letter-spacing: var(--typography-letterspacing-negative-double);\n}\n\n.chart__center-caption {\n fill: var(--color-foreground-secondary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n}\n\n.chart__focus {\n fill: none;\n stroke: var(--color-border-focus);\n stroke-width: var(--dimension-stroke-width-025);\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n}\n\n.chart__focus-guide {\n stroke: var(--color-foreground-quaternary);\n stroke-width: var(--dimension-stroke-width-012);\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n}\n\n.chart__focus-point {\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n}\n\n.chart__description,\n.chart__status {\n position: absolute;\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-stroke-width-012);\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n@media (forced-colors: active) {\n .chart__mark,\n .chart__focus,\n .chart__focus-point {\n forced-color-adjust: none;\n }\n\n .chart__focus {\n stroke: var(--ds-forced-color-focus);\n }\n}\n",
250
250
  "type": "registry:ui"
251
251
  },
252
252
  {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "checkbox",
4
4
  "title": "Checkbox",
5
- "description": "Form-associated checkbox for independent choices, acknowledgment, or consent, with checked, unchecked, mixed, and four density states.",
5
+ "description": "Form-associated checkbox for independent choices, acknowledgment, or consent, with optional supporting description, checked, unchecked, mixed, and four density states.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
@@ -13,7 +13,7 @@
13
13
  "intent": {
14
14
  "audience": "general",
15
15
  "status": "stable",
16
- "summary": "Form-associated checkbox for independent choices, acknowledgment, or consent, with checked, unchecked, mixed, and four density states.",
16
+ "summary": "Form-associated checkbox for independent choices, acknowledgment, or consent, with optional supporting description, checked, unchecked, mixed, and four density states.",
17
17
  "useWhen": [
18
18
  "A user may independently select zero or more choices.",
19
19
  "A form requires acknowledgment, consent, or an opt-in value before submission.",
@@ -47,7 +47,7 @@
47
47
  "component:ds-text"
48
48
  ],
49
49
  "accessibility": [
50
- "Every interactive checkbox requires a visible label that provides its accessible name.",
50
+ "Every interactive checkbox requires a visible label that provides its accessible name; an optional visible description is associated as supporting context.",
51
51
  "Enter and Space activate the checkbox; checked and mixed state are exposed through aria-checked.",
52
52
  "Disabled and inactive checkboxes leave the tab order and are omitted from form submission.",
53
53
  "Use presentation mode only inside a composite option that owns selection semantics, focus, and interaction."
@@ -57,16 +57,17 @@
57
57
  "Activating an indeterminate checkbox clears the mixed state, toggles checked, and emits dsChange with the resulting boolean.",
58
58
  "The initial checked and indeterminate values are restored by native form reset; checked values submit under name and unchecked values submit nothing.",
59
59
  "Sizes are lg with a 20px box in a 24px placement, md with a 16px box in a 20px placement, sm with a 12px box in a 16px placement, and xs with an 8px box in a 12px placement.",
60
- "Presentation mode removes checkbox semantics, focus, interaction, label rendering, and form submission while preserving the visual state."
60
+ "Presentation mode removes checkbox semantics, focus, interaction, label rendering, description rendering, and form submission while preserving the visual state.",
61
+ "Description-rich checkboxes grow beyond the fixed density height while retaining its minimum target size and primitive-owned selection anatomy."
61
62
  ],
62
63
  "responsiveBehavior": [
63
- "Choose size from the density of the containing row; labels may wrap while the placement box stays fixed.",
64
+ "Choose size from the density of the containing row; labels and descriptions may wrap while the placement box stays fixed.",
64
65
  "Use the same density as the shared option icon zone in multi-select rows."
65
66
  ],
66
67
  "frameworkCaveats": {
67
68
  "customElements": [
68
69
  "Bind checked and indeterminate as properties for application state updates and listen for dsChange.",
69
- "Use the form attribute on the host when associating a checkbox rendered outside its owning form."
70
+ "Use the form attribute on the host when associating a checkbox rendered outside its owning form or isolating it from an ancestor form."
70
71
  ],
71
72
  "react": [
72
73
  "Use DsCheckbox and handle onDsChange; checked and indeterminate are mutable and update after user activation."
@@ -97,6 +98,14 @@
97
98
  "mutable": true,
98
99
  "description": "Current checked state."
99
100
  },
101
+ "description": {
102
+ "type": "string",
103
+ "resolvedType": "string | undefined",
104
+ "attribute": "description",
105
+ "required": false,
106
+ "mutable": false,
107
+ "description": "Optional supporting description associated with the checkbox."
108
+ },
100
109
  "disabled": {
101
110
  "type": "boolean",
102
111
  "resolvedType": "boolean",
@@ -209,6 +218,14 @@
209
218
  "mutable": true,
210
219
  "description": "Current checked state."
211
220
  },
221
+ "description": {
222
+ "type": "string",
223
+ "resolvedType": "string | undefined",
224
+ "attribute": "description",
225
+ "required": false,
226
+ "mutable": false,
227
+ "description": "Optional supporting description associated with the checkbox."
228
+ },
212
229
  "disabled": {
213
230
  "type": "boolean",
214
231
  "resolvedType": "boolean",
@@ -332,7 +349,7 @@
332
349
  },
333
350
  "peerDependencies": {
334
351
  "required": [
335
- "@ds-mo/tokens ^6.0.0",
352
+ "@ds-mo/tokens ^6.2.1",
336
353
  "@ds-mo/icons ^6.6.0"
337
354
  ],
338
355
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -349,12 +366,12 @@
349
366
  "files": [
350
367
  {
351
368
  "path": "src/wc/components/Checkbox/Checkbox.css",
352
- "content": "@import '../../utils/control-inactive.css';\n@import '../../utils/control-density.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: inline-flex;\n align-items: center;\n box-sizing: border-box;\n gap: var(--ds-control-gap);\n height: var(--ds-control-height);\n padding: 0 var(--ds-control-padding-inline);\n border-radius: var(--ds-control-radius);\n cursor: default;\n user-select: none;\n}\n\n:host(.checkbox--presentation) {\n width: var(--ds-control-icon);\n height: var(--ds-control-icon);\n padding: 0;\n gap: 0;\n pointer-events: none;\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n:host(.checkbox--lg) {\n --_checkbox-box: var(--dimension-iconography-md);\n --_checkbox-stroke: var(--dimension-stroke-width-018);\n}\n\n:host(.checkbox--md) {\n --_checkbox-box: var(--dimension-iconography-sm);\n --_checkbox-stroke: var(--dimension-stroke-width-015);\n}\n\n:host(.checkbox--sm) {\n --_checkbox-box: var(--dimension-iconography-xs);\n --_checkbox-stroke: var(--dimension-stroke-width-012);\n}\n\n:host(.checkbox--xs) {\n --_checkbox-box: var(--dimension-size-100);\n --_checkbox-stroke: calc(var(--dimension-stroke-width-base) * 3 / 32);\n}\n\n.checkbox__placement {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--ds-control-icon);\n height: var(--ds-control-icon);\n}\n\n.box {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--_checkbox-box);\n height: var(--_checkbox-box);\n border: 0;\n border-radius: var(--dimension-radius-025);\n box-shadow: inset 0 0 0 var(--_checkbox-stroke) var(--color-foreground-tertiary);\n transition:\n background-color var(--effect-motion-short-2),\n box-shadow var(--effect-motion-short-2);\n}\n\n.box--marked {\n color: var(--color-foreground-on-bold-background-primary);\n background-color: var(--color-background-bold-brand);\n box-shadow: none;\n}\n\n.checkbox__mark {\n width: 100%;\n height: 100%;\n flex-shrink: 0;\n stroke: currentColor;\n stroke-width: var(--_checkbox-stroke);\n stroke-linecap: butt;\n stroke-linejoin: round;\n}\n\n.checkbox__mark--checked {\n transform: translateY(-0.5px);\n}\n\n.checkbox__label {\n flex: 1;\n min-width: 0;\n padding: 0 var(--ds-control-label-inset);\n}\n\n@media (forced-colors: active) {\n .box {\n border: var(--_checkbox-stroke) solid var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n .box--marked {\n color: var(--ds-forced-color-on-selected);\n background-color: var(--ds-forced-color-selected);\n border-color: var(--ds-forced-color-selected);\n }\n}\n",
369
+ "content": "@import '../../utils/control-inactive.css';\n@import '../../utils/control-density.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n display: inline-flex;\n align-items: center;\n box-sizing: border-box;\n gap: var(--ds-control-gap);\n height: var(--ds-control-height);\n padding: 0 var(--ds-control-padding-inline);\n border-radius: var(--ds-control-radius);\n cursor: default;\n user-select: none;\n}\n\n:host(.checkbox--described) {\n height: auto;\n min-height: var(--ds-control-height);\n padding-block: var(--dimension-space-075);\n}\n\n:host(.checkbox--presentation) {\n width: var(--ds-control-icon);\n height: var(--ds-control-icon);\n padding: 0;\n gap: 0;\n pointer-events: none;\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n:host(.checkbox--lg) {\n --_checkbox-box: var(--dimension-iconography-md);\n --_checkbox-stroke: var(--dimension-stroke-width-018);\n}\n\n:host(.checkbox--md) {\n --_checkbox-box: var(--dimension-iconography-sm);\n --_checkbox-stroke: var(--dimension-stroke-width-015);\n}\n\n:host(.checkbox--sm) {\n --_checkbox-box: var(--dimension-iconography-xs);\n --_checkbox-stroke: var(--dimension-stroke-width-012);\n}\n\n:host(.checkbox--xs) {\n --_checkbox-box: var(--dimension-size-100);\n --_checkbox-stroke: calc(var(--dimension-stroke-width-base) * 3 / 32);\n}\n\n.checkbox__placement {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--ds-control-icon);\n height: var(--ds-control-icon);\n}\n\n.box {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--_checkbox-box);\n height: var(--_checkbox-box);\n border: 0;\n border-radius: var(--dimension-radius-025);\n box-shadow: inset 0 0 0 var(--_checkbox-stroke) var(--color-foreground-tertiary);\n transition:\n background-color var(--effect-motion-short-2),\n box-shadow var(--effect-motion-short-2);\n}\n\n.box--marked {\n color: var(--color-foreground-on-bold-background-primary);\n background-color: var(--color-background-bold-brand);\n box-shadow: none;\n}\n\n.checkbox__mark {\n width: 100%;\n height: 100%;\n flex-shrink: 0;\n stroke: currentColor;\n stroke-width: var(--_checkbox-stroke);\n stroke-linecap: butt;\n stroke-linejoin: round;\n}\n\n.checkbox__mark--checked {\n transform: translateY(-0.5px);\n}\n\n.checkbox__copy {\n display: grid;\n flex: 1;\n gap: var(--dimension-space-025);\n min-width: 0;\n padding: 0 var(--ds-control-label-inset);\n}\n\n@media (forced-colors: active) {\n .box {\n border: var(--_checkbox-stroke) solid var(--ds-forced-color-content);\n box-shadow: none;\n }\n\n .box--marked {\n color: var(--ds-forced-color-on-selected);\n background-color: var(--ds-forced-color-selected);\n border-color: var(--ds-forced-color-selected);\n }\n}\n",
353
370
  "type": "registry:ui"
354
371
  },
355
372
  {
356
373
  "path": "src/wc/components/Checkbox/Checkbox.tsx",
357
- "content": "import { AttachInternals, Component, Prop, State, Event, EventEmitter, Watch, h, Host } from '@stencil/core';\nimport {\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n setFormControlValue,\n setRequiredValidity,\n} from '../../utils';\n\nexport type CheckboxSize = 'lg' | 'md' | 'sm' | 'xs';\n\nlet idCounter = 0;\n\n@Component({\n tag: 'ds-checkbox',\n styleUrl: 'Checkbox.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Checkbox {\n @AttachInternals() internals!: ElementInternals;\n private labelId = `ds-checkbox-label-${++idCounter}`;\n\n /** Visible label and accessible name. Omitted only in presentation mode. */\n @Prop() label!: string;\n /** Current checked state. */\n @Prop({ mutable: true }) checked: boolean = false;\n /** Visual and placement density. */\n @Prop() size: CheckboxSize = 'md';\n /** Native form field name. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Submitted value when checked. */\n @Prop() value: string = 'on';\n /** Native disabled state. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require the checkbox to be checked for form validity. */\n @Prop({ reflect: true }) required: boolean = false;\n /** Validation message used when a required checkbox is unchecked. */\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n /** Mixed visual state. Activation clears it before toggling checked. */\n @Prop({ mutable: true }) indeterminate: boolean = false;\n /** Design-system inactive state. */\n @Prop() isInactive: boolean = false;\n /** Visual-only indicator for a composite control that owns selection semantics. */\n @Prop() presentation: boolean = false;\n\n /** Emitted after user activation with the new checked state. */\n @Event() dsChange!: EventEmitter<boolean>;\n\n private initialChecked = false;\n private initialIndeterminate = false;\n @State() private formDisabled = false;\n\n componentWillLoad() {\n this.initialChecked = this.checked;\n this.initialIndeterminate = this.indeterminate;\n this.syncFormValue();\n }\n\n @Watch('checked')\n @Watch('value')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n @Watch('presentation')\n syncFormValue() {\n const inactive = this.isInactive || this.disabled || this.formDisabled || this.presentation;\n setFormControlValue(this.internals, this.checked ? this.value : null, { inactive });\n const missing = this.required && !inactive && !this.checked;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.checked = this.initialChecked;\n this.indeterminate = this.initialIndeterminate;\n }\n\n private handleActivate = () => {\n if (this.isInactive || this.disabled || this.formDisabled || this.presentation) return;\n if (this.indeterminate) this.indeterminate = false;\n this.checked = !this.checked;\n this.dsChange.emit(this.checked);\n };\n\n private handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.handleActivate();\n }\n };\n\n render() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const isMarked = this.checked || this.indeterminate;\n const invalid = this.required && !inactive && !this.checked;\n\n return (\n <Host\n role={this.presentation ? undefined : 'checkbox'}\n aria-checked={this.presentation ? undefined : this.indeterminate ? 'mixed' : String(this.checked)}\n aria-disabled={!this.presentation && inactive ? 'true' : undefined}\n aria-required={!this.presentation && this.required ? 'true' : undefined}\n aria-invalid={!this.presentation && invalid ? 'true' : undefined}\n aria-labelledby={this.presentation ? undefined : this.labelId}\n aria-hidden={this.presentation ? 'true' : undefined}\n tabIndex={this.presentation || inactive ? -1 : 0}\n class={{\n checkbox: true,\n 'checkbox--presentation': this.presentation,\n [`checkbox--${this.size}`]: true,\n [`ds-control--${this.size}`]: true,\n 'ds-control-inactive': inactive && !this.presentation,\n 'ds-focus-ring-inset': !this.presentation,\n 'ds-interaction-fill': !inactive && !this.presentation,\n }}\n onClick={this.presentation ? undefined : this.handleActivate}\n onKeyDown={this.presentation ? undefined : this.handleKeyDown}\n >\n <span class=\"checkbox__placement ds-interaction-fill__content\" aria-hidden=\"true\">\n <span class={{ box: true, 'box--marked': isMarked }}>\n {isMarked && (\n // eslint-disable-next-line local/prefer-ds-icon -- Checkbox owns this density-specific state mark and its optical stroke.\n <svg\n class={{\n checkbox__mark: true,\n 'checkbox__mark--checked': !this.indeterminate,\n }}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n focusable=\"false\"\n >\n <path\n d={this.indeterminate ? 'M4 8H12' : 'M3.5 8.25L6.75 11.5L12.5 4.75'}\n vector-effect=\"non-scaling-stroke\"\n />\n </svg>\n )}\n </span>\n </span>\n {!this.presentation && (\n <ds-text\n class=\"checkbox__label ds-interaction-fill__content\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n textId={this.labelId}\n >\n {this.label}\n </ds-text>\n )}\n </Host>\n );\n }\n}\n",
374
+ "content": "import { AttachInternals, Component, Prop, State, Event, EventEmitter, Watch, h, Host } from '@stencil/core';\nimport {\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n setFormControlValue,\n setRequiredValidity,\n} from '../../utils';\n\nexport type CheckboxSize = 'lg' | 'md' | 'sm' | 'xs';\n\nlet idCounter = 0;\n\n@Component({\n tag: 'ds-checkbox',\n styleUrl: 'Checkbox.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Checkbox {\n @AttachInternals() internals!: ElementInternals;\n private labelId = `ds-checkbox-label-${++idCounter}`;\n private descriptionId = `${this.labelId}-description`;\n\n /** Visible label and accessible name. Omitted only in presentation mode. */\n @Prop() label!: string;\n /** Optional supporting description associated with the checkbox. */\n @Prop() description?: string;\n /** Current checked state. */\n @Prop({ mutable: true }) checked: boolean = false;\n /** Visual and placement density. */\n @Prop() size: CheckboxSize = 'md';\n /** Native form field name. */\n @Prop({ reflect: true }) name: string | undefined;\n /** Submitted value when checked. */\n @Prop() value: string = 'on';\n /** Native disabled state. */\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Require the checkbox to be checked for form validity. */\n @Prop({ reflect: true }) required: boolean = false;\n /** Validation message used when a required checkbox is unchecked. */\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n /** Mixed visual state. Activation clears it before toggling checked. */\n @Prop({ mutable: true }) indeterminate: boolean = false;\n /** Design-system inactive state. */\n @Prop() isInactive: boolean = false;\n /** Visual-only indicator for a composite control that owns selection semantics. */\n @Prop() presentation: boolean = false;\n\n /** Emitted after user activation with the new checked state. */\n @Event() dsChange!: EventEmitter<boolean>;\n\n private initialChecked = false;\n private initialIndeterminate = false;\n @State() private formDisabled = false;\n\n componentWillLoad() {\n this.initialChecked = this.checked;\n this.initialIndeterminate = this.indeterminate;\n this.syncFormValue();\n }\n\n @Watch('checked')\n @Watch('value')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n @Watch('presentation')\n syncFormValue() {\n const inactive = this.isInactive || this.disabled || this.formDisabled || this.presentation;\n setFormControlValue(this.internals, this.checked ? this.value : null, { inactive });\n const missing = this.required && !inactive && !this.checked;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.checked = this.initialChecked;\n this.indeterminate = this.initialIndeterminate;\n }\n\n private handleActivate = () => {\n if (this.isInactive || this.disabled || this.formDisabled || this.presentation) return;\n if (this.indeterminate) this.indeterminate = false;\n this.checked = !this.checked;\n this.dsChange.emit(this.checked);\n };\n\n private handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.handleActivate();\n }\n };\n\n render() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const isMarked = this.checked || this.indeterminate;\n const invalid = this.required && !inactive && !this.checked;\n\n return (\n <Host\n role={this.presentation ? undefined : 'checkbox'}\n aria-checked={this.presentation ? undefined : this.indeterminate ? 'mixed' : String(this.checked)}\n aria-disabled={!this.presentation && inactive ? 'true' : undefined}\n aria-required={!this.presentation && this.required ? 'true' : undefined}\n aria-invalid={!this.presentation && invalid ? 'true' : undefined}\n aria-labelledby={this.presentation ? undefined : this.labelId}\n aria-describedby={\n !this.presentation && this.description ? this.descriptionId : undefined\n }\n aria-hidden={this.presentation ? 'true' : undefined}\n tabIndex={this.presentation || inactive ? -1 : 0}\n class={{\n checkbox: true,\n 'checkbox--presentation': this.presentation,\n 'checkbox--described': Boolean(this.description),\n [`checkbox--${this.size}`]: true,\n [`ds-control--${this.size}`]: true,\n 'ds-control-inactive': inactive && !this.presentation,\n 'ds-focus-ring-inset': !this.presentation,\n 'ds-interaction-fill': !inactive && !this.presentation,\n }}\n onClick={this.presentation ? undefined : this.handleActivate}\n onKeyDown={this.presentation ? undefined : this.handleKeyDown}\n >\n <span class=\"checkbox__placement ds-interaction-fill__content\" aria-hidden=\"true\">\n <span class={{ box: true, 'box--marked': isMarked }}>\n {isMarked && (\n // eslint-disable-next-line local/prefer-ds-icon -- Checkbox owns this density-specific state mark and its optical stroke.\n <svg\n class={{\n checkbox__mark: true,\n 'checkbox__mark--checked': !this.indeterminate,\n }}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n focusable=\"false\"\n >\n <path\n d={this.indeterminate ? 'M4 8H12' : 'M3.5 8.25L6.75 11.5L12.5 4.75'}\n vector-effect=\"non-scaling-stroke\"\n />\n </svg>\n )}\n </span>\n </span>\n {!this.presentation && (\n <span class=\"checkbox__copy ds-interaction-fill__content\">\n <ds-text\n class=\"checkbox__label\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n textId={this.labelId}\n >\n {this.label}\n </ds-text>\n {this.description ? (\n <ds-text\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n textId={this.descriptionId}\n >\n {this.description}\n </ds-text>\n ) : null}\n </span>\n )}\n </Host>\n );\n }\n}\n",
358
375
  "type": "registry:ui"
359
376
  }
360
377
  ]
@@ -245,7 +245,7 @@
245
245
  },
246
246
  "peerDependencies": {
247
247
  "required": [
248
- "@ds-mo/tokens ^6.0.0",
248
+ "@ds-mo/tokens ^6.2.1",
249
249
  "@ds-mo/icons ^6.6.0"
250
250
  ],
251
251
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -264,7 +264,7 @@
264
264
  "files": [
265
265
  {
266
266
  "path": "src/wc/components/Chip/Chip.css",
267
- "content": "@import '../../utils/tag-chip.css';\n\n/*\n * Semantic state colors — Chip’s differentiator from Tag.\n * All states use the faint contrast weight; hue comes from the state.\n */\n:host(.chip--default) {\n background-color: var(--color-background-faint-neutral);\n color: var(--color-foreground-bold-neutral);\n}\n\n:host(.chip--active) {\n background-color: var(--color-background-faint-brand);\n color: var(--color-foreground-bold-brand);\n}\n\n:host(.chip--error) {\n background-color: var(--color-background-faint-negative);\n color: var(--color-foreground-bold-negative);\n}\n\n:host(.chip--caution) {\n background-color: var(--color-background-faint-caution);\n color: var(--color-foreground-bold-caution);\n}\n\n/*\n * Label↔dismiss uses host gap, mirroring Tag icon↔label spacing:\n * [inset][text][inset][gap][icon].\n */\n.tag__remove {\n padding: 0;\n margin: 0;\n border: none;\n background: none;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n color: inherit;\n flex-shrink: 0;\n position: relative;\n z-index: 2;\n border-radius: var(--dimension-radius-half);\n}\n\n.tag__remove::before {\n content: '';\n position: absolute;\n inset: var(--dimension-space-n025);\n border-radius: var(--dimension-radius-half);\n background-color: transparent;\n transition: var(--effect-transition-interaction-background-short-2);\n pointer-events: none;\n}\n\n.tag__remove:hover::before {\n background-color: var(--color-interaction-hover);\n}\n\n.tag__remove:active::before {\n background-color: var(--color-interaction-pressed);\n}\n\n:host(.tag--size-sm) .tag__remove::before {\n inset: var(--dimension-space-n012);\n}\n\n:host(.tag--size-xs) .tag__remove::before {\n inset: 0;\n}\n\n.tag__remove-x {\n display: inline-flex;\n flex-shrink: 0;\n width: var(--ds-control-icon);\n height: var(--ds-control-icon);\n}\n",
267
+ "content": "@import '../../utils/tag-chip.css';\n\n/*\n * Semantic state colors — Chip’s differentiator from Tag.\n * All states use the faint contrast weight; hue comes from the state.\n */\n:host(.chip--default) {\n background-color: var(--color-background-faint-neutral);\n color: var(--color-foreground-bold-neutral);\n}\n\n:host(.chip--active) {\n background-color: var(--color-background-faint-brand);\n color: var(--color-foreground-bold-brand);\n}\n\n:host(.chip--error) {\n background-color: var(--color-background-faint-negative);\n color: var(--color-foreground-bold-negative);\n}\n\n:host(.chip--caution) {\n background-color: var(--color-background-faint-caution);\n color: var(--color-foreground-bold-caution);\n}\n\n/*\n * Label↔dismiss uses host gap, mirroring Tag icon↔label spacing:\n * [inset][text][inset][gap][icon].\n */\n.tag__remove {\n padding: 0;\n margin: 0;\n border: none;\n background: none;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n color: inherit;\n flex-shrink: 0;\n position: relative;\n z-index: 2;\n border-radius: var(--dimension-radius-half);\n}\n\n.tag__remove::before {\n content: '';\n position: absolute;\n inset: var(--dimension-space-n025);\n border-radius: var(--dimension-radius-half);\n background-color: transparent;\n transition: var(--effect-transition-interaction-background-short-2);\n pointer-events: none;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .tag__remove:hover::before {\n background-color: var(--color-interaction-hover);\n }\n}\n\n.tag__remove:active::before {\n background-color: var(--color-interaction-pressed);\n}\n\n:host(.tag--size-sm) .tag__remove::before {\n inset: var(--dimension-space-n012);\n}\n\n:host(.tag--size-xs) .tag__remove::before {\n inset: 0;\n}\n\n.tag__remove-x {\n display: inline-flex;\n flex-shrink: 0;\n width: var(--ds-control-icon);\n height: var(--ds-control-icon);\n}\n",
268
268
  "type": "registry:ui"
269
269
  },
270
270
  {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "code-block",
4
4
  "title": "CodeBlock",
5
- "description": "Scrollable monospace code surface with optional language or filename context and copy action.",
5
+ "description": "Scrollable code surface with optional language or filename context and copy action.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
@@ -13,7 +13,7 @@
13
13
  "intent": {
14
14
  "audience": "general",
15
15
  "status": "experimental",
16
- "summary": "Scrollable monospace code surface with optional language or filename context and copy action.",
16
+ "summary": "Scrollable code surface with optional language or filename context and copy action.",
17
17
  "useWhen": [
18
18
  "A message or document displays fenced code that readers may copy."
19
19
  ],
@@ -26,7 +26,11 @@
26
26
  ],
27
27
  "accessibility": [
28
28
  "The copy action has matching accessible-name and visible-tooltip status wording, and the code remains selectable text.",
29
- "Language and filename labels supplement rather than replace the code content."
29
+ "Language and filename labels supplement rather than replace the code content.",
30
+ "The semantic code-family token retains a system-monospace fallback when an application does not load Fira Code; applications may disable programming ligatures through the shared code-surface custom property."
31
+ ],
32
+ "states": [
33
+ "Applications load font assets once at their root. The component consumes --typography-font-family-code and never fetches or packages a font."
30
34
  ],
31
35
  "responsiveBehavior": [
32
36
  "Long lines scroll horizontally and do not widen the owning transcript."
@@ -34,7 +38,11 @@
34
38
  "references": [
35
39
  {
36
40
  "label": "Storybook examples",
37
- "path": "src/wc/components/MessageScroller/MessageScroller.stories.ts"
41
+ "path": "src/wc/components/CodeBlock/CodeBlock.stories.ts"
42
+ },
43
+ {
44
+ "label": "Font ownership and loading",
45
+ "path": "docs/font-ownership.md"
38
46
  }
39
47
  ]
40
48
  },
@@ -120,7 +128,7 @@
120
128
  },
121
129
  "peerDependencies": {
122
130
  "required": [
123
- "@ds-mo/tokens ^6.0.0",
131
+ "@ds-mo/tokens ^6.2.1",
124
132
  "@ds-mo/icons ^6.6.0"
125
133
  ],
126
134
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -139,7 +147,7 @@
139
147
  "files": [
140
148
  {
141
149
  "path": "src/wc/components/CodeBlock/CodeBlock.css",
142
- "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",
150
+ "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(\n --typography-font-family-code,\n 'Fira Code', ui-monospace, 'SFMono-Regular', 'Cascadia Code', 'Roboto Mono',\n Menlo, Monaco, Consolas, 'Liberation Mono', monospace\n );\n font-size: var(--typography-fontsize-sm);\n font-variant-ligatures: var(--ds-code-font-variant-ligatures, normal);\n font-weight: var(--typography-weight-regular);\n line-height: var(--typography-lineheight-sm);\n letter-spacing: var(--typography-letterspacing-none);\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",
143
151
  "type": "registry:ui"
144
152
  },
145
153
  {
@@ -253,7 +253,7 @@
253
253
  },
254
254
  "peerDependencies": {
255
255
  "required": [
256
- "@ds-mo/tokens ^6.0.0",
256
+ "@ds-mo/tokens ^6.2.1",
257
257
  "@ds-mo/icons ^6.6.0"
258
258
  ],
259
259
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -272,7 +272,7 @@
272
272
  "files": [
273
273
  {
274
274
  "path": "src/wc/components/ConversationListItem/ConversationListItem.css",
275
- "content": "@import '../../utils/choice-list.css';\n@import '../../utils/visually-hidden.css';\n@import '../../styles/control-elevation.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n:host(:hover),\n:host(:focus-within),\n:host(.conversation-list-item--actions-open) {\n position: relative;\n z-index: var(--dimension-z-index-floating);\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 -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",
275
+ "content": "@import '../../utils/choice-list.css';\n@import '../../utils/visually-hidden.css';\n@import '../../styles/control-elevation.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n -webkit-user-select: none;\n user-select: none;\n}\n\n:host(:focus-within),\n:host(.conversation-list-item--actions-open) {\n position: relative;\n z-index: var(--dimension-z-index-floating);\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 -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: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: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: hover) and (pointer: fine) {\n :host(:hover) {\n position: relative;\n z-index: var(--dimension-z-index-floating);\n }\n\n .conversation-list-item__row:hover .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\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",
276
276
  "type": "registry:ui"
277
277
  },
278
278
  {
@@ -110,7 +110,7 @@
110
110
  },
111
111
  "peerDependencies": {
112
112
  "required": [
113
- "@ds-mo/tokens ^6.0.0",
113
+ "@ds-mo/tokens ^6.2.1",
114
114
  "@ds-mo/icons ^6.6.0"
115
115
  ],
116
116
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -102,7 +102,7 @@
102
102
  },
103
103
  "peerDependencies": {
104
104
  "required": [
105
- "@ds-mo/tokens ^6.0.0",
105
+ "@ds-mo/tokens ^6.2.1",
106
106
  "@ds-mo/icons ^6.6.0"
107
107
  ],
108
108
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -198,7 +198,7 @@
198
198
  },
199
199
  "peerDependencies": {
200
200
  "required": [
201
- "@ds-mo/tokens ^6.0.0",
201
+ "@ds-mo/tokens ^6.2.1",
202
202
  "@ds-mo/icons ^6.6.0"
203
203
  ],
204
204
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -170,7 +170,7 @@
170
170
  },
171
171
  "peerDependencies": {
172
172
  "required": [
173
- "@ds-mo/tokens ^6.0.0",
173
+ "@ds-mo/tokens ^6.2.1",
174
174
  "@ds-mo/icons ^6.6.0"
175
175
  ],
176
176
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -193,7 +193,7 @@
193
193
  },
194
194
  "peerDependencies": {
195
195
  "required": [
196
- "@ds-mo/tokens ^6.0.0",
196
+ "@ds-mo/tokens ^6.2.1",
197
197
  "@ds-mo/icons ^6.6.0"
198
198
  ],
199
199
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -194,7 +194,7 @@
194
194
  },
195
195
  "peerDependencies": {
196
196
  "required": [
197
- "@ds-mo/tokens ^6.0.0",
197
+ "@ds-mo/tokens ^6.2.1",
198
198
  "@ds-mo/icons ^6.6.0"
199
199
  ],
200
200
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -566,7 +566,7 @@
566
566
  },
567
567
  "peerDependencies": {
568
568
  "required": [
569
- "@ds-mo/tokens ^6.0.0",
569
+ "@ds-mo/tokens ^6.2.1",
570
570
  "@ds-mo/icons ^6.6.0"
571
571
  ],
572
572
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -165,7 +165,7 @@
165
165
  },
166
166
  "peerDependencies": {
167
167
  "required": [
168
- "@ds-mo/tokens ^6.0.0",
168
+ "@ds-mo/tokens ^6.2.1",
169
169
  "@ds-mo/icons ^6.6.0"
170
170
  ],
171
171
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -117,7 +117,7 @@
117
117
  },
118
118
  "peerDependencies": {
119
119
  "required": [
120
- "@ds-mo/tokens ^6.0.0",
120
+ "@ds-mo/tokens ^6.2.1",
121
121
  "@ds-mo/icons ^6.6.0"
122
122
  ],
123
123
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -453,7 +453,7 @@
453
453
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: anchor, items, sections.",
454
454
  "peerDependencies": {
455
455
  "required": [
456
- "@ds-mo/tokens ^6.0.0",
456
+ "@ds-mo/tokens ^6.2.1",
457
457
  "@ds-mo/icons ^6.6.0"
458
458
  ],
459
459
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -478,7 +478,7 @@
478
478
  },
479
479
  {
480
480
  "path": "src/wc/components/Menu/Menu.tsx",
481
- "content": "import {\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Watch,\n Listen,\n h,\n Host,\n} from '@stencil/core';\nimport {\n choicePopupMinWidth,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n resolveChoicePopupAlignOffset,\n resolveCssLengthPx,\n resolveMotionTimeMs,\n TOKEN_CSS_LENGTHS,\n TOKEN_DEFAULTS,\n type ChoicePopupAnchorAlignment,\n type ControlSize,\n} from '../../utils';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\nimport type { MenuAlign, MenuSide } from './menu-position';\nimport type { MenuItemData, MenuSection } from './menu-types';\nimport { snapshotMenuSections } from './menu-sections';\n\nexport type MenuSelectionMode = 'none' | 'single';\nexport type MenuSize = ControlSize;\nimport {\n isMenuPickerSection,\n isMenuSwatchPickerSection,\n} from './menu-types';\n\nconst MENU_FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n@Component({\n tag: 'ds-menu',\n styleUrl: 'Menu.css',\n scoped: true,\n})\nexport class Menu {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() items: MenuItemData[] = [];\n /** Choice-row density. */\n @Prop() size: MenuSize = 'md';\n /** Give ordinary rows mutually-exclusive menu semantics using isSelected. */\n @Prop() selectionMode: MenuSelectionMode = 'none';\n @Prop() sections: MenuSection[] = [];\n /** Preferred side; placement flips to the opposite side when that offers a better fit. */\n @Prop() side: MenuSide = 'bottom';\n @Prop() align: MenuAlign = 'start';\n /** Align choice-row edges to the anchor by default; use `popup-frame` only for custom frame geometry. */\n @Prop() anchorAlignment: ChoicePopupAnchorAlignment = 'choice-cell';\n /** Gap between anchor and menu — number (px) or TokoMo length (`var(--dimension-space-050)`, etc.). */\n @Prop() sideOffset: number | string = TOKEN_CSS_LENGTHS.space050;\n /** Cross-axis offset — number (px) or TokoMo length. */\n @Prop() alignOffset: number | string = 0;\n @Prop() menuWidth: string | undefined;\n @Prop() minWidth: string | undefined;\n /** External trigger element to position against. Set via JS: menuEl.anchor = buttonEl */\n @Prop() anchor: HTMLElement | undefined;\n /** ID of the external trigger element for positioning */\n @Prop() anchorId: string | undefined;\n /** Show a visible ring on the initially focused menu item. Use only when the opener was keyboard-driven. */\n @Prop() initialFocusVisible: boolean = false;\n /** Accessible name for the popup menu. */\n @Prop() menuLabel: string = 'Menu';\n\n @State() private shouldRender: boolean = false;\n @State() private closing: boolean = false;\n @State() private pos: { x: number; y: number } = { x: 0, y: 0 };\n @State() private focusedIndex: number = 0;\n @State() private positionReady: boolean = false;\n @State() private focusRingVisible: boolean = false;\n\n @Event() dsClose!: EventEmitter<void>;\n /** Emitted after the popup's exit motion is complete and its rendered content is removed. */\n @Event() dsAfterClose!: EventEmitter<void>;\n @Event() dsSelect!: EventEmitter<MenuItemData>;\n /** Emitted when a generic `swatch-picker` section option is chosen. */\n @Event() dsSwatchSelect!: EventEmitter<string>;\n\n private clickOutsideHandler: ((e: MouseEvent) => void) | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n /**\n * Anchored placement. The choice-cell measurement stays here on purpose: the\n * inner-cell align offset and the `--dimension-menu-width-xs` floor are Menu\n * semantics, not shared geometry.\n */\n private readonly position = new AnchoredPositionController({\n getAnchor: () => this.resolvedAnchor ?? null,\n getPopup: () => this.el.querySelector<HTMLElement>('.menu-popup'),\n measure: (anchorEl, popup) => {\n if (!this.open) return null;\n\n const anchorRect = anchorEl.getBoundingClientRect();\n const sectionInsetPx = this.viewportPadPx;\n if (!this.minWidth) {\n if (\n this.anchorAlignment === 'choice-cell' &&\n (this.side === 'top' || this.side === 'bottom')\n ) {\n popup.style.minWidth = `max(var(--dimension-menu-width-xs), ${choicePopupMinWidth(\n anchorRect.width,\n sectionInsetPx\n )}px)`;\n } else {\n popup.style.removeProperty('min-width');\n }\n }\n\n return {\n anchorRect,\n popupWidth: popup.offsetWidth || this.popupFallbackWidthPx,\n popupHeight: popup.offsetHeight || this.popupFallbackHeightPx,\n side: this.side,\n align: this.align,\n sideOffsetPx: this.sideOffsetPx,\n alignOffsetPx: resolveChoicePopupAlignOffset({\n align: this.align,\n alignOffsetPx: this.alignOffsetPx,\n sectionInsetPx,\n anchorAlignment: this.anchorAlignment,\n }),\n viewportPadPx: this.viewportPadPx,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n };\n },\n apply: ({ x, y }) => {\n this.pos = { x, y };\n },\n onReady: () => {\n this.positionReady = true;\n },\n liveUpdate: 'double-frame',\n topLayer: true,\n });\n /** Last content actually painted while open; retained unchanged through exit motion. */\n private lastRenderedSections: MenuSection[] = [];\n private closingSections: MenuSection[] | null = null;\n\n componentDidLoad() {\n if (this.open) this.onOpenChange(true);\n }\n\n disconnectedCallback() {\n this.cancelPositionRetry();\n this.cancelLivePositionUpdate();\n this.teardownListeners();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (isOpen) {\n this.teardownListeners();\n this.closingSections = null;\n this.shouldRender = true;\n this.closing = false;\n this.positionReady = false;\n this.focusRingVisible = this.initialFocusVisible;\n this.setupListeners();\n this.schedulePositionUpdate(() => {\n this.focusInitialItem();\n });\n } else if (this.shouldRender) {\n this.cancelPositionRetry();\n this.captureClosingSections();\n this.closing = true;\n this.teardownListeners();\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n }\n\n @Watch('anchor')\n @Watch('anchorId')\n onAnchorChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n @Watch('side')\n @Watch('align')\n @Watch('anchorAlignment')\n @Watch('sideOffset')\n @Watch('alignOffset')\n onPositionPropsChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n private get viewportPadPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n }\n\n private get sideOffsetPx(): number {\n return resolveCssLengthPx(this.sideOffset, TOKEN_DEFAULTS.space050);\n }\n\n private get alignOffsetPx(): number {\n return resolveCssLengthPx(this.alignOffset, 0);\n }\n\n private get popupFallbackWidthPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuWidthXs, TOKEN_DEFAULTS.menuWidthXs);\n }\n\n private get popupFallbackHeightPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuFallbackHeight, TOKEN_DEFAULTS.menuFallbackHeight);\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort2, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n const popup = this.el.querySelector<HTMLElement>('.menu-popup');\n if (popup?.matches(':popover-open')) popup.hidePopover();\n this.shouldRender = false;\n this.closing = false;\n this.lastRenderedSections = [];\n this.closingSections = null;\n this.dsAfterClose.emit();\n }\n\n private captureClosingSections() {\n if (this.closingSections) return;\n const visibleSections = this.lastRenderedSections.length\n ? this.lastRenderedSections\n : this.activeSections;\n this.closingSections = snapshotMenuSections(visibleSections);\n }\n\n private get resolvedAnchor(): HTMLElement | null {\n if (this.anchor) return this.anchor;\n if (this.anchorId) return document.getElementById(this.anchorId);\n return null;\n }\n\n private get anchorFocusTarget(): HTMLElement | null {\n const anchor = this.resolvedAnchor;\n if (!anchor) return null;\n if (anchor.matches(MENU_FOCUSABLE_SELECTOR)) return anchor;\n return anchor.querySelector<HTMLElement>(MENU_FOCUSABLE_SELECTOR) ?? anchor;\n }\n\n private focusAnchor() {\n const anchor = this.resolvedAnchor as\n (HTMLElement & { setFocus?: () => Promise<void> | void }) | null;\n if (anchor?.setFocus) {\n anchor.setFocus();\n return;\n }\n this.anchorFocusTarget?.focus();\n }\n\n private moveFocusAfterTab(backwards: boolean) {\n const anchor = this.anchorFocusTarget;\n if (!anchor) return;\n if (backwards) {\n anchor.focus();\n return;\n }\n\n const candidates = Array.from(\n document.querySelectorAll<HTMLElement>(MENU_FOCUSABLE_SELECTOR)\n ).filter(\n element =>\n !element.closest('.menu-popup') &&\n !element.closest('[inert]') &&\n element.getClientRects().length > 0\n );\n const index = candidates.indexOf(anchor);\n (candidates[index + 1] ?? anchor).focus();\n }\n\n private compositeTabLeavesPopup(event: KeyboardEvent): boolean {\n const popup = this.el.querySelector<HTMLElement>('.menu-popup');\n if (!popup) return true;\n const focusables = Array.from(\n popup.querySelectorAll<HTMLElement>(MENU_FOCUSABLE_SELECTOR)\n ).filter(element => element.getClientRects().length > 0);\n const currentIndex = focusables.indexOf(event.target as HTMLElement);\n if (currentIndex < 0) return true;\n return event.shiftKey ? currentIndex === 0 : currentIndex === focusables.length - 1;\n }\n\n private get activeSections(): MenuSection[] {\n if (this.sections.length > 0) return this.sections;\n if (this.items.length > 0) return [{ items: this.items }];\n return [];\n }\n\n private get flatItems(): MenuItemData[] {\n return this.activeSections.flatMap(section =>\n isMenuPickerSection(section) ? [] : section.items\n );\n }\n\n /** Rich preference content is a non-modal dialog, not an ARIA action menu. */\n private get hasCompositeSections(): boolean {\n return this.activeSections.some(isMenuPickerSection);\n }\n\n private cancelPositionRetry() {\n this.position.cancel();\n }\n\n private cancelLivePositionUpdate() {\n this.position.cancel();\n }\n\n /** Reposition after the current scroll/layout frame without hiding the open popup. */\n private scheduleLivePositionUpdate() {\n this.position.scheduleLiveUpdate();\n }\n\n /** Retry until anchor + popup exist — do not reveal at 0,0 on a failed first pass. */\n private schedulePositionUpdate(onReady?: () => void) {\n if (!this.open) return;\n this.positionReady = false;\n this.position.schedule(onReady);\n }\n\n /** @returns `true` when anchor and popup were found and `pos` was updated. */\n private calculatePosition(): boolean {\n return this.position.update();\n }\n\n /** Focus the selected item when present, otherwise the first enabled item. */\n private focusInitialItem() {\n if (this.hasCompositeSections) {\n requestAnimationFrame(() => {\n this.el.querySelector<HTMLElement>('.menu-popup [tabindex=\"0\"]')?.focus();\n });\n return;\n }\n\n const flat = this.flatItems;\n const selectedIdx = flat.findIndex(it => it.isSelected && !it.isInactive);\n const firstEnabledIdx = flat.findIndex(it => !it.isInactive);\n this.focusedIndex = selectedIdx >= 0 ? selectedIdx : firstEnabledIdx >= 0 ? firstEnabledIdx : 0;\n\n requestAnimationFrame(() => {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[this.focusedIndex]?.focus();\n });\n }\n\n private setupListeners() {\n this.clickOutsideHandler = (e: MouseEvent) => {\n const t = e.target as Node;\n const popup = this.el.querySelector('.menu-popup');\n const anchorEl = this.resolvedAnchor;\n if (popup?.contains(t) || anchorEl?.contains(t)) return;\n this.close();\n };\n\n document.addEventListener('mousedown', this.clickOutsideHandler, true);\n this.position.observe();\n }\n\n private teardownListeners() {\n this.position.unobserve();\n if (this.clickOutsideHandler) {\n document.removeEventListener('mousedown', this.clickOutsideHandler, true);\n this.clickOutsideHandler = null;\n }\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private close(restoreFocus = true) {\n this.captureClosingSections();\n if (restoreFocus) this.focusAnchor();\n this.dsClose.emit();\n this.open = false;\n this.onOpenChange(false);\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n if (!this.shouldRender || this.closing) return;\n\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n\n if (e.key === 'Tab') {\n if (this.hasCompositeSections && !this.compositeTabLeavesPopup(e)) return;\n e.preventDefault();\n this.close(false);\n this.moveFocusAfterTab(e.shiftKey);\n return;\n }\n\n if (this.hasCompositeSections) return;\n\n const flat = this.flatItems;\n const enabled = flat\n .map((it, i) => ({ it, i }))\n .filter(({ it }) => !it.isInactive)\n .map(({ i }) => i);\n if (!enabled.length) return;\n\n const cur = enabled.indexOf(this.focusedIndex);\n const safe = cur < 0 ? 0 : cur;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe + 1) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'ArrowUp':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe - 1 + enabled.length) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'Home':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[0];\n this.focusItem(this.focusedIndex);\n break;\n case 'End':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[enabled.length - 1];\n this.focusItem(this.focusedIndex);\n break;\n }\n }\n\n private focusItem(idx: number) {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[idx]?.focus();\n }\n\n private handleItemClick(item: MenuItemData) {\n if (item.isInactive) return;\n // Consumers commonly change the item source while handling selection.\n // Capture first so the exit animation never paints the next menu context.\n this.captureClosingSections();\n this.dsSelect.emit(item);\n }\n\n private handleSwatchSelect(value: string) {\n this.dsSwatchSelect.emit(value);\n }\n\n render() {\n if (!this.shouldRender) return <Host style={{ display: 'contents' }} />;\n\n const sections = this.closing\n ? (this.closingSections ?? this.lastRenderedSections)\n : this.activeSections;\n const hasCompositeSections = sections.some(isMenuPickerSection);\n if (!this.closing) {\n this.lastRenderedSections = snapshotMenuSections(sections);\n this.closingSections = null;\n }\n let flatIdx = 0;\n\n const popupStyle: Record<string, string> = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.pos.x)}px, ${Math.round(this.pos.y)}px)`,\n zIndex: '9998',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n\n if (this.menuWidth) popupStyle['width'] = this.menuWidth;\n if (this.minWidth) popupStyle['min-width'] = this.minWidth;\n\n return (\n <Host style={{ display: 'contents' }}>\n <div\n popover=\"manual\"\n class={{\n 'menu-popup': true,\n 'menu-popup--closing': this.closing,\n 'ds-choice-popup': true,\n 'ds-choice-popup--closing': this.closing,\n }}\n style={popupStyle}\n role={hasCompositeSections ? 'dialog' : 'menu'}\n aria-label={this.menuLabel}\n aria-orientation={hasCompositeSections ? undefined : 'vertical'}\n >\n <div class=\"ds-choice-list\">\n {sections.map((section, si) => (\n <div\n key={si}\n class={{\n 'menu-section': true,\n 'menu-section--divided': si < sections.length - 1,\n 'menu-section--swatch-picker': isMenuPickerSection(section),\n 'ds-choice-section': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--sm': true,\n 'ds-choice-section--divided': si < sections.length - 1,\n }}\n role={section.header ? 'group' : undefined}\n aria-label={section.header}\n >\n {section.header && (\n <ds-text\n class={`section-header ds-choice-section__header ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n aria-hidden=\"true\"\n >\n {section.header}\n </ds-text>\n )}\n {isMenuSwatchPickerSection(section) ? (\n <ds-swatch-picker\n value={section.value}\n groupLabel={section.groupLabel ?? section.header ?? 'Swatch options'}\n options={section.options ?? []}\n sections={section.sections ?? []}\n onDsChange={(e: CustomEvent<string>) => {\n e.stopPropagation();\n this.handleSwatchSelect(e.detail);\n }}\n />\n ) : (\n section.items.map(item => {\n const idx = flatIdx++;\n const isFocused = this.focusedIndex === idx;\n const isSingleSelectionItem =\n !hasCompositeSections && this.selectionMode === 'single' && !item.showSwitch;\n return (\n <button\n key={idx}\n type=\"button\"\n class={{\n 'menu-item': true,\n 'ds-choice-item': true,\n 'ds-control-frame': true,\n [`ds-control--${this.size}`]: true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible': isFocused && this.focusRingVisible,\n 'ds-interaction-fill': !item.isInactive,\n 'ds-interaction-fill--selected': !!item.isSelected && !item.isInactive,\n 'menu-item--selected': !!item.isSelected,\n 'ds-control-inactive': !!item.isInactive,\n 'menu-item--destructive': !!item.isDestructive,\n 'menu-item--focused': isFocused,\n }}\n role={\n hasCompositeSections\n ? undefined\n : item.showSwitch\n ? 'menuitemcheckbox'\n : isSingleSelectionItem\n ? 'menuitemradio'\n : 'menuitem'\n }\n aria-checked={\n !hasCompositeSections && item.showSwitch\n ? String(!!item.switchValue)\n : isSingleSelectionItem\n ? String(!!item.isSelected)\n : undefined\n }\n aria-pressed={\n hasCompositeSections\n ? String(item.showSwitch ? !!item.switchValue : !!item.isSelected)\n : undefined\n }\n aria-current={\n !hasCompositeSections &&\n !item.showSwitch &&\n !isSingleSelectionItem &&\n item.isSelected\n ? 'true'\n : undefined\n }\n disabled={item.isInactive}\n tabIndex={hasCompositeSections ? 0 : isFocused ? 0 : -1}\n onMouseDown={() => {\n this.focusRingVisible = false;\n }}\n onClick={() => this.handleItemClick(item)}\n onFocus={() => {\n this.focusedIndex = idx;\n }}\n >\n <div class=\"menu-item__content ds-choice-item__content ds-interaction-fill__content\">\n <ds-text\n class=\"menu-item__label ds-choice-item__label ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color={item.isSelected ? 'primary' : 'secondary'}\n >\n {item.label}\n </ds-text>\n {item.subtext && (\n <ds-text\n class=\"menu-item__subtext ds-choice-item__subtext ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n >\n {item.subtext}\n </ds-text>\n )}\n </div>\n {item.dot && (\n <span\n class=\"menu-item__dot-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-badge\n class=\"menu-item__dot\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n />\n </span>\n )}\n {item.showSwitch && (\n <ds-switch\n class=\"menu-item__switch ds-interaction-fill__content\"\n size={this.size}\n checked={!!item.switchValue}\n presentation\n />\n )}\n </button>\n );\n })\n )}\n </div>\n ))}\n </div>\n </div>\n </Host>\n );\n }\n}\n",
481
+ "content": "import {\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Watch,\n Listen,\n h,\n Host,\n} from '@stencil/core';\nimport {\n choicePopupMinWidth,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n resolveChoicePopupAlignOffset,\n resolveCssLengthPx,\n resolveMotionTimeMs,\n TOKEN_CSS_LENGTHS,\n TOKEN_DEFAULTS,\n type ChoicePopupAnchorAlignment,\n type ControlSize,\n} from '../../utils';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\nimport type { MenuAlign, MenuSide } from './menu-position';\nimport type { MenuItemData, MenuSection } from './menu-types';\nimport { snapshotMenuSections } from './menu-sections';\n\nexport type MenuSelectionMode = 'none' | 'single';\nexport type MenuSize = ControlSize;\nimport {\n isMenuPickerSection,\n isMenuSwatchPickerSection,\n} from './menu-types';\n\nconst MENU_FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n@Component({\n tag: 'ds-menu',\n styleUrl: 'Menu.css',\n scoped: true,\n})\nexport class Menu {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() items: MenuItemData[] = [];\n /** Choice-row density. */\n @Prop() size: MenuSize = 'md';\n /** Give ordinary rows mutually-exclusive menu semantics using isSelected. */\n @Prop() selectionMode: MenuSelectionMode = 'none';\n @Prop() sections: MenuSection[] = [];\n /** Preferred side; placement flips to the opposite side when that offers a better fit. */\n @Prop() side: MenuSide = 'bottom';\n @Prop() align: MenuAlign = 'start';\n /** Align choice-row edges to the anchor by default; use `popup-frame` only for custom frame geometry. */\n @Prop() anchorAlignment: ChoicePopupAnchorAlignment = 'choice-cell';\n /** Gap between anchor and menu — number (px) or TokoMo length (`var(--dimension-space-050)`, etc.). */\n @Prop() sideOffset: number | string = TOKEN_CSS_LENGTHS.space050;\n /** Cross-axis offset — number (px) or TokoMo length. */\n @Prop() alignOffset: number | string = 0;\n @Prop() menuWidth: string | undefined;\n @Prop() minWidth: string | undefined;\n /** External trigger element to position against. Set via JS: menuEl.anchor = buttonEl */\n @Prop() anchor: HTMLElement | undefined;\n /** ID of the external trigger element for positioning */\n @Prop() anchorId: string | undefined;\n /** Show a visible ring on the initially focused menu item. Use only when the opener was keyboard-driven. */\n @Prop() initialFocusVisible: boolean = false;\n /** Accessible name for the popup menu. */\n @Prop() menuLabel: string = 'Menu';\n\n @State() private shouldRender: boolean = false;\n @State() private closing: boolean = false;\n @State() private pos: { x: number; y: number } = { x: 0, y: 0 };\n @State() private focusedIndex: number = 0;\n @State() private positionReady: boolean = false;\n @State() private focusRingVisible: boolean = false;\n\n @Event() dsClose!: EventEmitter<void>;\n /** Emitted after the popup's exit motion is complete and its rendered content is removed. */\n @Event() dsAfterClose!: EventEmitter<void>;\n @Event() dsSelect!: EventEmitter<MenuItemData>;\n /** Emitted when a generic `swatch-picker` section option is chosen. */\n @Event() dsSwatchSelect!: EventEmitter<string>;\n\n private clickOutsideHandler: ((e: MouseEvent) => void) | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n /**\n * Anchored placement. The choice-cell measurement stays here on purpose: the\n * inner-cell align offset and the `--dimension-menu-width-xs` floor are Menu\n * semantics, not shared geometry.\n */\n private readonly position = new AnchoredPositionController({\n getAnchor: () => this.resolvedAnchor ?? null,\n getPopup: () => this.el.querySelector<HTMLElement>('.menu-popup'),\n measure: (anchorEl, popup) => {\n if (!this.open) return null;\n\n const anchorRect = anchorEl.getBoundingClientRect();\n const sectionInsetPx = this.viewportPadPx;\n if (!this.minWidth) {\n if (\n this.anchorAlignment === 'choice-cell' &&\n (this.side === 'top' || this.side === 'bottom')\n ) {\n popup.style.minWidth = `max(var(--dimension-menu-width-xs), ${choicePopupMinWidth(\n anchorRect.width,\n sectionInsetPx\n )}px)`;\n } else {\n popup.style.removeProperty('min-width');\n }\n }\n\n return {\n anchorRect,\n popupWidth: popup.offsetWidth || this.popupFallbackWidthPx,\n popupHeight: popup.offsetHeight || this.popupFallbackHeightPx,\n side: this.side,\n align: this.align,\n sideOffsetPx: this.sideOffsetPx,\n alignOffsetPx: resolveChoicePopupAlignOffset({\n align: this.align,\n alignOffsetPx: this.alignOffsetPx,\n sectionInsetPx,\n anchorAlignment: this.anchorAlignment,\n }),\n viewportPadPx: this.viewportPadPx,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n };\n },\n apply: ({ x, y }) => {\n this.pos = { x, y };\n },\n onReady: () => {\n this.positionReady = true;\n },\n liveUpdate: 'double-frame',\n topLayer: true,\n });\n /** Last content actually painted while open; retained unchanged through exit motion. */\n private lastRenderedSections: MenuSection[] = [];\n private closingSections: MenuSection[] | null = null;\n\n componentDidLoad() {\n if (this.open) this.onOpenChange(true);\n }\n\n disconnectedCallback() {\n this.cancelPositionRetry();\n this.cancelLivePositionUpdate();\n this.teardownListeners();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (isOpen) {\n this.teardownListeners();\n this.closingSections = null;\n this.shouldRender = true;\n this.closing = false;\n this.positionReady = false;\n this.focusRingVisible = this.initialFocusVisible;\n this.setupListeners();\n this.schedulePositionUpdate(() => {\n this.focusInitialItem();\n });\n } else if (this.shouldRender) {\n this.cancelPositionRetry();\n this.captureClosingSections();\n this.closing = true;\n this.teardownListeners();\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n }\n\n @Watch('anchor')\n @Watch('anchorId')\n onAnchorChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n @Watch('side')\n @Watch('align')\n @Watch('anchorAlignment')\n @Watch('sideOffset')\n @Watch('alignOffset')\n onPositionPropsChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n private get viewportPadPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n }\n\n private get sideOffsetPx(): number {\n return resolveCssLengthPx(this.sideOffset, TOKEN_DEFAULTS.space050);\n }\n\n private get alignOffsetPx(): number {\n return resolveCssLengthPx(this.alignOffset, 0);\n }\n\n private get popupFallbackWidthPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuWidthXs, TOKEN_DEFAULTS.menuWidthXs);\n }\n\n private get popupFallbackHeightPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuFallbackHeight, TOKEN_DEFAULTS.menuFallbackHeight);\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort2, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n const popup = this.el.querySelector<HTMLElement>('.menu-popup');\n if (popup?.matches(':popover-open')) popup.hidePopover();\n this.shouldRender = false;\n this.closing = false;\n this.lastRenderedSections = [];\n this.closingSections = null;\n this.dsAfterClose.emit();\n }\n\n private captureClosingSections() {\n if (this.closingSections) return;\n const visibleSections = this.lastRenderedSections.length\n ? this.lastRenderedSections\n : this.activeSections;\n this.closingSections = snapshotMenuSections(visibleSections);\n }\n\n private get resolvedAnchor(): HTMLElement | null {\n if (this.anchor) return this.anchor;\n if (this.anchorId) return document.getElementById(this.anchorId);\n return null;\n }\n\n private get anchorFocusTarget(): HTMLElement | null {\n const anchor = this.resolvedAnchor;\n if (!anchor) return null;\n if (anchor.matches(MENU_FOCUSABLE_SELECTOR)) return anchor;\n return anchor.querySelector<HTMLElement>(MENU_FOCUSABLE_SELECTOR) ?? anchor;\n }\n\n private focusAnchor() {\n const anchor = this.resolvedAnchor as\n (HTMLElement & { setFocus?: () => Promise<void> | void }) | null;\n if (anchor?.setFocus) {\n anchor.setFocus();\n return;\n }\n this.anchorFocusTarget?.focus();\n }\n\n private moveFocusAfterTab(backwards: boolean) {\n const anchor = this.anchorFocusTarget;\n if (!anchor) return;\n if (backwards) {\n anchor.focus();\n return;\n }\n\n const candidates = Array.from(\n document.querySelectorAll<HTMLElement>(MENU_FOCUSABLE_SELECTOR)\n ).filter(\n element =>\n !element.closest('.menu-popup') &&\n !element.closest('[inert]') &&\n element.getClientRects().length > 0\n );\n const index = candidates.indexOf(anchor);\n (candidates[index + 1] ?? anchor).focus();\n }\n\n private compositeTabLeavesPopup(event: KeyboardEvent): boolean {\n const popup = this.el.querySelector<HTMLElement>('.menu-popup');\n if (!popup) return true;\n const focusables = Array.from(\n popup.querySelectorAll<HTMLElement>(MENU_FOCUSABLE_SELECTOR)\n ).filter(element => element.getClientRects().length > 0);\n const currentIndex = focusables.indexOf(event.target as HTMLElement);\n if (currentIndex < 0) return true;\n return event.shiftKey ? currentIndex === 0 : currentIndex === focusables.length - 1;\n }\n\n private get activeSections(): MenuSection[] {\n if (this.sections.length > 0) return this.sections;\n if (this.items.length > 0) return [{ items: this.items }];\n return [];\n }\n\n private get flatItems(): MenuItemData[] {\n return this.activeSections.flatMap(section =>\n isMenuPickerSection(section) ? [] : section.items\n );\n }\n\n /** Rich preference content is a non-modal dialog, not an ARIA action menu. */\n private get hasCompositeSections(): boolean {\n return this.activeSections.some(isMenuPickerSection);\n }\n\n private cancelPositionRetry() {\n this.position.cancel();\n }\n\n private cancelLivePositionUpdate() {\n this.position.cancel();\n }\n\n /** Reposition after the current scroll/layout frame without hiding the open popup. */\n private scheduleLivePositionUpdate() {\n this.position.scheduleLiveUpdate();\n }\n\n /** Retry until anchor + popup exist — do not reveal at 0,0 on a failed first pass. */\n private schedulePositionUpdate(onReady?: () => void) {\n if (!this.open) return;\n this.positionReady = false;\n this.position.schedule(onReady);\n }\n\n /** @returns `true` when anchor and popup were found and `pos` was updated. */\n private calculatePosition(): boolean {\n return this.position.update();\n }\n\n /** Focus the selected item when present, otherwise the first enabled item. */\n private focusInitialItem() {\n if (this.hasCompositeSections) {\n requestAnimationFrame(() => {\n this.el.querySelector<HTMLElement>('.menu-popup [tabindex=\"0\"]')?.focus();\n });\n return;\n }\n\n const flat = this.flatItems;\n const selectedIdx = flat.findIndex(it => it.isSelected && !it.isInactive);\n const firstEnabledIdx = flat.findIndex(it => !it.isInactive);\n this.focusedIndex = selectedIdx >= 0 ? selectedIdx : firstEnabledIdx >= 0 ? firstEnabledIdx : 0;\n\n requestAnimationFrame(() => {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[this.focusedIndex]?.focus();\n });\n }\n\n private setupListeners() {\n this.clickOutsideHandler = (e: MouseEvent) => {\n const t = e.target as Node;\n const popup = this.el.querySelector('.menu-popup');\n const anchorEl = this.resolvedAnchor;\n if (popup?.contains(t) || anchorEl?.contains(t)) return;\n this.close();\n };\n\n document.addEventListener('mousedown', this.clickOutsideHandler, true);\n this.position.observe();\n }\n\n private teardownListeners() {\n this.position.unobserve();\n if (this.clickOutsideHandler) {\n document.removeEventListener('mousedown', this.clickOutsideHandler, true);\n this.clickOutsideHandler = null;\n }\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private close(restoreFocus = true) {\n this.captureClosingSections();\n if (restoreFocus) this.focusAnchor();\n this.dsClose.emit();\n this.open = false;\n this.onOpenChange(false);\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n if (!this.shouldRender || this.closing) return;\n\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n\n if (e.key === 'Tab') {\n if (this.hasCompositeSections && !this.compositeTabLeavesPopup(e)) return;\n e.preventDefault();\n this.close(false);\n this.moveFocusAfterTab(e.shiftKey);\n return;\n }\n\n if (this.hasCompositeSections) return;\n\n const flat = this.flatItems;\n const enabled = flat\n .map((it, i) => ({ it, i }))\n .filter(({ it }) => !it.isInactive)\n .map(({ i }) => i);\n if (!enabled.length) return;\n\n const cur = enabled.indexOf(this.focusedIndex);\n const safe = cur < 0 ? 0 : cur;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe + 1) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'ArrowUp':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe - 1 + enabled.length) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'Home':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[0];\n this.focusItem(this.focusedIndex);\n break;\n case 'End':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[enabled.length - 1];\n this.focusItem(this.focusedIndex);\n break;\n }\n }\n\n private focusItem(idx: number) {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[idx]?.focus();\n }\n\n private handleItemClick(item: MenuItemData) {\n if (item.isInactive) return;\n // Consumers commonly change the item source while handling selection.\n // Capture first so the exit animation never paints the next menu context.\n this.captureClosingSections();\n this.dsSelect.emit(item);\n }\n\n private handleSwatchSelect(value: string) {\n this.dsSwatchSelect.emit(value);\n }\n\n render() {\n if (!this.shouldRender) return <Host style={{ display: 'contents' }} />;\n\n const sections = this.closing\n ? (this.closingSections ?? this.lastRenderedSections)\n : this.activeSections;\n const hasCompositeSections = sections.some(isMenuPickerSection);\n if (!this.closing) {\n this.lastRenderedSections = snapshotMenuSections(sections);\n this.closingSections = null;\n }\n let flatIdx = 0;\n\n const popupStyle: Record<string, string> = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.pos.x)}px, ${Math.round(this.pos.y)}px)`,\n zIndex: '9998',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n\n if (this.menuWidth) popupStyle['width'] = this.menuWidth;\n if (this.minWidth) popupStyle['min-width'] = this.minWidth;\n\n return (\n <Host style={{ display: 'contents' }}>\n <div\n popover=\"manual\"\n class={{\n 'menu-popup': true,\n 'menu-popup--closing': this.closing,\n 'ds-choice-popup': true,\n 'ds-choice-popup--closing': this.closing,\n }}\n style={popupStyle}\n role={hasCompositeSections ? 'dialog' : 'menu'}\n aria-label={this.menuLabel}\n aria-orientation={hasCompositeSections ? undefined : 'vertical'}\n >\n <div class=\"ds-choice-list\">\n {sections.map((section, si) => (\n <div\n key={si}\n class={{\n 'menu-section': true,\n 'menu-section--divided': si < sections.length - 1,\n 'menu-section--swatch-picker': isMenuPickerSection(section),\n 'ds-choice-section': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--sm': true,\n 'ds-choice-section--divided': si < sections.length - 1,\n }}\n role={section.header ? 'group' : undefined}\n aria-label={section.header}\n >\n {section.header && (\n <ds-text\n class={`section-header ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n aria-hidden=\"true\"\n >\n {section.header}\n </ds-text>\n )}\n {isMenuSwatchPickerSection(section) ? (\n <ds-swatch-picker\n value={section.value}\n groupLabel={section.groupLabel ?? section.header ?? 'Swatch options'}\n options={section.options ?? []}\n sections={section.sections ?? []}\n onDsChange={(e: CustomEvent<string>) => {\n e.stopPropagation();\n this.handleSwatchSelect(e.detail);\n }}\n />\n ) : (\n section.items.map(item => {\n const idx = flatIdx++;\n const isFocused = this.focusedIndex === idx;\n const isSingleSelectionItem =\n !hasCompositeSections && this.selectionMode === 'single' && !item.showSwitch;\n return (\n <button\n key={idx}\n type=\"button\"\n class={{\n 'menu-item': true,\n 'ds-choice-item': true,\n 'ds-control-frame': true,\n [`ds-control--${this.size}`]: true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible': isFocused && this.focusRingVisible,\n 'ds-interaction-fill': !item.isInactive,\n 'ds-interaction-fill--selected': !!item.isSelected && !item.isInactive,\n 'menu-item--selected': !!item.isSelected,\n 'ds-control-inactive': !!item.isInactive,\n 'menu-item--destructive': !!item.isDestructive,\n 'menu-item--focused': isFocused,\n }}\n role={\n hasCompositeSections\n ? undefined\n : item.showSwitch\n ? 'menuitemcheckbox'\n : isSingleSelectionItem\n ? 'menuitemradio'\n : 'menuitem'\n }\n aria-checked={\n !hasCompositeSections && item.showSwitch\n ? String(!!item.switchValue)\n : isSingleSelectionItem\n ? String(!!item.isSelected)\n : undefined\n }\n aria-pressed={\n hasCompositeSections\n ? String(item.showSwitch ? !!item.switchValue : !!item.isSelected)\n : undefined\n }\n aria-current={\n !hasCompositeSections &&\n !item.showSwitch &&\n !isSingleSelectionItem &&\n item.isSelected\n ? 'true'\n : undefined\n }\n disabled={item.isInactive}\n tabIndex={hasCompositeSections ? 0 : isFocused ? 0 : -1}\n onMouseDown={() => {\n this.focusRingVisible = false;\n }}\n onClick={() => this.handleItemClick(item)}\n onFocus={() => {\n this.focusedIndex = idx;\n }}\n >\n <div class=\"menu-item__content ds-choice-item__content ds-interaction-fill__content\">\n <ds-text\n class=\"menu-item__label ds-choice-item__label ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color={item.isSelected ? 'primary' : 'secondary'}\n >\n {item.label}\n </ds-text>\n {item.subtext && (\n <ds-text\n class=\"menu-item__subtext ds-choice-item__subtext ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n >\n {item.subtext}\n </ds-text>\n )}\n </div>\n {item.dot && (\n <span\n class=\"menu-item__dot-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-badge\n class=\"menu-item__dot\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n />\n </span>\n )}\n {item.showSwitch && (\n <ds-switch\n class=\"menu-item__switch ds-interaction-fill__content\"\n size={this.size}\n checked={!!item.switchValue}\n presentation\n />\n )}\n </button>\n );\n })\n )}\n </div>\n ))}\n </div>\n </div>\n </Host>\n );\n }\n}\n",
482
482
  "type": "registry:ui"
483
483
  },
484
484
  {
@@ -157,7 +157,7 @@
157
157
  },
158
158
  "peerDependencies": {
159
159
  "required": [
160
- "@ds-mo/tokens ^6.0.0",
160
+ "@ds-mo/tokens ^6.2.1",
161
161
  "@ds-mo/icons ^6.6.0"
162
162
  ],
163
163
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -97,7 +97,7 @@
97
97
  },
98
98
  "peerDependencies": {
99
99
  "required": [
100
- "@ds-mo/tokens ^6.0.0",
100
+ "@ds-mo/tokens ^6.2.1",
101
101
  "@ds-mo/icons ^6.6.0"
102
102
  ],
103
103
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
@@ -112,7 +112,7 @@
112
112
  "files": [
113
113
  {
114
114
  "path": "src/wc/components/MessageBubble/MessageBubble.css",
115
- "content": ":host {\n display: inline-flex;\n max-width: 80%;\n box-sizing: border-box;\n -webkit-user-select: text;\n user-select: text;\n}\n\n.message-bubble {\n min-width: 0;\n padding-block: var(--dimension-space-100);\n padding-inline: calc(var(--dimension-space-100) + var(--dimension-space-025));\n border-radius: var(--dimension-radius-050);\n background: transparent;\n color: var(--color-foreground-primary);\n font-family: var(--typography-font-family);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n box-sizing: border-box;\n overflow-wrap: anywhere;\n}\n\n:host(.message-bubble--user) .message-bubble {\n background: var(--color-translucent-translucent);\n color: var(--color-foreground-secondary);\n box-shadow: inset 0 0 0 var(--dimension-stroke-width-012) var(--color-border-tertiary);\n}\n\n:host(.message-bubble--received) .message-bubble {\n box-shadow: inset 0 0 0 var(--dimension-stroke-width-012) var(--color-border-tertiary);\n}\n",
115
+ "content": ":host {\n display: inline-flex;\n max-width: 80%;\n box-sizing: border-box;\n -webkit-user-select: text;\n user-select: text;\n}\n\n.message-bubble {\n min-width: 0;\n padding-block: var(--dimension-space-100);\n padding-inline: calc(var(--dimension-space-100) + var(--dimension-space-025));\n border-radius: var(--dimension-radius-050);\n background: transparent;\n color: var(--color-foreground-primary);\n font-family: var(--typography-font-family-ui);\n font-size: var(--typography-fontsize-md);\n line-height: var(--typography-lineheight-md);\n font-weight: var(--typography-weight-regular);\n letter-spacing: var(--typography-letterspacing-negative-half);\n box-sizing: border-box;\n overflow-wrap: anywhere;\n}\n\n:host(.message-bubble--user) .message-bubble {\n background: var(--color-translucent-translucent);\n color: var(--color-foreground-secondary);\n box-shadow: inset 0 0 0 var(--dimension-stroke-width-012) var(--color-border-tertiary);\n}\n\n:host(.message-bubble--received) .message-bubble {\n box-shadow: inset 0 0 0 var(--dimension-stroke-width-012) var(--color-border-tertiary);\n}\n",
116
116
  "type": "registry:ui"
117
117
  },
118
118
  {