@ds-mo/ui 14.36.0 → 15.1.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 (496) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +7 -0
  3. package/dist/.package-ready.json +1 -1
  4. package/dist/agent-patterns.json +1 -1
  5. package/dist/agent.json +7485 -5096
  6. package/dist/angular/ds-conversation-list.d.ts +1 -1
  7. package/dist/angular/ds-conversation-list.js +3 -4
  8. package/dist/angular/ds-data-customize.d.ts +18 -0
  9. package/dist/angular/ds-data-customize.js +46 -0
  10. package/dist/angular/ds-data-filter.d.ts +50 -0
  11. package/dist/angular/{ds-table-filter.js → ds-data-filter.js} +10 -10
  12. package/dist/angular/ds-data-group.d.ts +29 -0
  13. package/dist/angular/{ds-table-group.js → ds-data-group.js} +10 -10
  14. package/dist/angular/ds-data-preferences.d.ts +39 -0
  15. package/dist/angular/{ds-table-preferences.js → ds-data-preferences.js} +15 -15
  16. package/dist/angular/ds-data-saved-views.d.ts +49 -0
  17. package/dist/angular/{ds-table-saved-views.js → ds-data-saved-views.js} +10 -10
  18. package/dist/angular/ds-data-search.d.ts +20 -0
  19. package/dist/angular/{ds-table-search.js → ds-data-search.js} +12 -12
  20. package/dist/angular/ds-data-sort.d.ts +16 -0
  21. package/dist/angular/{ds-table-sort.js → ds-data-sort.js} +12 -12
  22. package/dist/angular/ds-data-toolbar.d.ts +12 -0
  23. package/dist/angular/{ds-table-toolbar.js → ds-data-toolbar.js} +10 -10
  24. package/dist/angular/ds-table.d.ts +7 -7
  25. package/dist/angular/ds-table.js +6 -6
  26. package/dist/angular/index.d.ts +1 -1
  27. package/dist/angular/index.js +8 -7
  28. package/dist/angular/proxies.d.ts +8 -7
  29. package/dist/angular/proxies.js +8 -7
  30. package/dist/components/ds-agent-activity.js +1 -1
  31. package/dist/components/ds-agent-questionnaire.js +1 -1
  32. package/dist/components/ds-agent-response.js +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-avatar.js +1 -1
  37. package/dist/components/ds-badge.js +1 -1
  38. package/dist/components/ds-banner.js +1 -1
  39. package/dist/components/ds-banner.js.map +1 -1
  40. package/dist/components/ds-bar-action.js +1 -1
  41. package/dist/components/ds-bar-nav.js +1 -1
  42. package/dist/components/ds-bar-page-title.js +1 -1
  43. package/dist/components/ds-bar-title.js +1 -1
  44. package/dist/components/ds-bar-workflow.js +1 -1
  45. package/dist/components/ds-breadcrumb.js +1 -1
  46. package/dist/components/ds-button-filled.js +1 -1
  47. package/dist/components/ds-button-inverted.js +1 -1
  48. package/dist/components/ds-button-unfilled.js +1 -1
  49. package/dist/components/ds-calendar.js +1 -1
  50. package/dist/components/ds-card-action-center.js +1 -1
  51. package/dist/components/ds-card-chart.js +1 -1
  52. package/dist/components/ds-card-chart.js.map +1 -1
  53. package/dist/components/ds-card-navigation.js +1 -1
  54. package/dist/components/ds-card-overview.js +1 -1
  55. package/dist/components/ds-card-overview.js.map +1 -1
  56. package/dist/components/ds-card-setting.js +1 -1
  57. package/dist/components/ds-card-setting.js.map +1 -1
  58. package/dist/components/ds-card-settings-scope.js +1 -1
  59. package/dist/components/ds-chart-legend.js +1 -1
  60. package/dist/components/ds-chart.js +1 -1
  61. package/dist/components/ds-chart.js.map +1 -1
  62. package/dist/components/ds-checkbox.js +1 -1
  63. package/dist/components/ds-chip.js +1 -1
  64. package/dist/components/ds-code-block.js +1 -1
  65. package/dist/components/ds-conversation-list-item.js +1 -1
  66. package/dist/components/ds-conversation-list-item.js.map +1 -1
  67. package/dist/components/ds-conversation-list-section.js +1 -1
  68. package/dist/components/ds-conversation-list.js +1 -1
  69. package/dist/components/ds-conversation-list.js.map +1 -1
  70. package/dist/components/ds-data-customize.d.ts +11 -0
  71. package/dist/components/ds-data-customize.js +2 -0
  72. package/dist/components/ds-data-customize.js.map +1 -0
  73. package/dist/components/{ds-table-group.d.ts → ds-data-filter.d.ts} +4 -4
  74. package/dist/components/ds-data-filter.js +2 -0
  75. package/dist/components/ds-data-filter.js.map +1 -0
  76. package/dist/components/{ds-table-sort.d.ts → ds-data-group.d.ts} +4 -4
  77. package/dist/components/ds-data-group.js +2 -0
  78. package/dist/components/{ds-table-saved-views.d.ts → ds-data-preferences.d.ts} +4 -4
  79. package/dist/components/ds-data-preferences.js +2 -0
  80. package/dist/components/ds-data-preferences.js.map +1 -0
  81. package/dist/components/ds-data-saved-views.d.ts +11 -0
  82. package/dist/components/ds-data-saved-views.js +2 -0
  83. package/dist/components/ds-data-saved-views.js.map +1 -0
  84. package/dist/components/{ds-table-search.d.ts → ds-data-search.d.ts} +4 -4
  85. package/dist/components/ds-data-search.js +2 -0
  86. package/dist/components/ds-data-search.js.map +1 -0
  87. package/dist/components/ds-data-sort.d.ts +11 -0
  88. package/dist/components/ds-data-sort.js +2 -0
  89. package/dist/components/ds-data-sort.js.map +1 -0
  90. package/dist/components/{ds-table-filter.d.ts → ds-data-toolbar.d.ts} +4 -4
  91. package/dist/components/ds-data-toolbar.js +2 -0
  92. package/dist/components/ds-data-toolbar.js.map +1 -0
  93. package/dist/components/ds-divider.js +1 -1
  94. package/dist/components/ds-empty-state.js +1 -1
  95. package/dist/components/ds-field.js +1 -1
  96. package/dist/components/ds-filter-menu.js +1 -1
  97. package/dist/components/ds-icon.js +1 -1
  98. package/dist/components/ds-input-date.js +1 -1
  99. package/dist/components/ds-input-time.js +1 -1
  100. package/dist/components/ds-input-time.js.map +1 -1
  101. package/dist/components/ds-input.js +1 -1
  102. package/dist/components/ds-markdown.js +1 -1
  103. package/dist/components/ds-menu.js +1 -1
  104. package/dist/components/ds-message-actions.js +1 -1
  105. package/dist/components/ds-message-bubble.js +1 -1
  106. package/dist/components/ds-message-composer.js +1 -1
  107. package/dist/components/ds-message-composer.js.map +1 -1
  108. package/dist/components/ds-message-scroller.js +1 -1
  109. package/dist/components/ds-message-scroller.js.map +1 -1
  110. package/dist/components/ds-message.js +1 -1
  111. package/dist/components/ds-mobile-bar-nav.js +1 -1
  112. package/dist/components/ds-mobile-header.js +1 -1
  113. package/dist/components/ds-mobile-section-switcher.js +1 -1
  114. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  115. package/dist/components/ds-modal.js +1 -1
  116. package/dist/components/ds-pagination.js +1 -1
  117. package/dist/components/ds-panel-nav.js +1 -1
  118. package/dist/components/ds-panel-sub-nav.js +1 -1
  119. package/dist/components/ds-panel-tool-header.js +1 -1
  120. package/dist/components/ds-panel-tool-search.js +1 -1
  121. package/dist/components/ds-panel-tools.js +1 -1
  122. package/dist/components/ds-paper-texture.js +1 -1
  123. package/dist/components/ds-radio.js +1 -1
  124. package/dist/components/ds-score.js +1 -1
  125. package/dist/components/ds-scroll-overlay.js +1 -1
  126. package/dist/components/ds-scroll-overlay.js.map +1 -1
  127. package/dist/components/ds-select.js +1 -1
  128. package/dist/components/ds-shell-app.js +1 -1
  129. package/dist/components/ds-shell-page.js +1 -1
  130. package/dist/components/ds-shell-tools.js +1 -1
  131. package/dist/components/ds-skeleton.js +1 -1
  132. package/dist/components/ds-slider.js +1 -1
  133. package/dist/components/ds-swatch-picker.js +1 -1
  134. package/dist/components/ds-switch.js +1 -1
  135. package/dist/components/ds-tab-group.js +1 -1
  136. package/dist/components/ds-table.js +1 -1
  137. package/dist/components/ds-table.js.map +1 -1
  138. package/dist/components/ds-tag.js +1 -1
  139. package/dist/components/ds-text.js +1 -1
  140. package/dist/components/ds-textarea.js +1 -1
  141. package/dist/components/ds-time-picker.js +1 -1
  142. package/dist/components/ds-toast.js +1 -1
  143. package/dist/components/ds-tooltip-chart.js +1 -1
  144. package/dist/components/ds-tooltip.js +1 -1
  145. package/dist/components/ds-typing-indicator.js +1 -1
  146. package/dist/components/index.d.ts +5 -5
  147. package/dist/components/{p-D8rpKod8.js → p-0FYD2bev.js} +2 -2
  148. package/dist/components/p-0qY_HDId.js +2 -0
  149. package/dist/components/{p-C0CE2v5D.js.map → p-0qY_HDId.js.map} +1 -1
  150. package/dist/components/{p-DK9Ex3aY.js → p-5URekSny.js} +2 -2
  151. package/dist/components/{p-Ds51RgCi.js → p-7V1AhetH.js} +2 -2
  152. package/dist/components/{p-C2JnoaWI.js → p-8yBdCXCn.js} +2 -2
  153. package/dist/components/{p-CQIh2yRP.js → p-BKZdqJQB.js} +2 -2
  154. package/dist/components/p-BKZdqJQB.js.map +1 -0
  155. package/dist/components/p-BYrBU_U9.js +2 -0
  156. package/dist/components/p-BYrBU_U9.js.map +1 -0
  157. package/dist/components/{p-ClJhuG5Y.js → p-B_jdoM7e.js} +2 -2
  158. package/dist/components/{p-ClJhuG5Y.js.map → p-B_jdoM7e.js.map} +1 -1
  159. package/dist/components/{p-su6y4hLK.js → p-BciTtTwx.js} +2 -2
  160. package/dist/components/{p-DgG9W9N4.js → p-BeNa6tw5.js} +2 -2
  161. package/dist/components/{p-DgG9W9N4.js.map → p-BeNa6tw5.js.map} +1 -1
  162. package/dist/components/p-BgQMEHRa.js +2 -0
  163. package/dist/components/{p-D7yBIPB6.js.map → p-BgQMEHRa.js.map} +1 -1
  164. package/dist/components/{p-CQ5RwY2C.js → p-BhJXmuRA.js} +2 -2
  165. package/dist/components/{p-neWTRxmf.js → p-Bm7rqs3C.js} +2 -2
  166. package/dist/components/{p-BV5T1hzN.js → p-Bn8I7iw6.js} +2 -2
  167. package/dist/components/{p-PmcUwTQ_.js → p-Bpd6YCo4.js} +2 -2
  168. package/dist/components/p-C-3YqeTb.js +2 -0
  169. package/dist/components/{p-TjXGRPik.js.map → p-C-3YqeTb.js.map} +1 -1
  170. package/dist/components/{p-B27_chI7.js → p-C6xVxqOn.js} +2 -2
  171. package/dist/components/{p-C4m8y9ki.js → p-CD4DZPy_.js} +2 -2
  172. package/dist/components/{p-CuwopElg.js → p-CGidq1iC.js} +2 -2
  173. package/dist/components/{p-CWog26I_.js → p-CNxvnQ6A.js} +2 -2
  174. package/dist/components/{p-2GVGhrh0.js → p-CUcUHwI3.js} +2 -2
  175. package/dist/components/{p-CbSUyv5B.js → p-CYzFfFcH.js} +2 -2
  176. package/dist/components/{p-8oRkhvjy.js → p-CdGXzRP8.js} +2 -2
  177. package/dist/components/p-CdgOLWX9.js +2 -0
  178. package/dist/components/p-CdgOLWX9.js.map +1 -0
  179. package/dist/components/{p-Jaqdfw4E.js → p-CiH4GG6r.js} +2 -2
  180. package/dist/components/{p-Cl9phSf0.js → p-CiJ1WR11.js} +2 -2
  181. package/dist/components/{p-Cus1Sigh.js → p-CmexIVnE.js} +2 -2
  182. package/dist/components/{p-Dryt3COt.js → p-Cqc2U1ZQ.js} +2 -2
  183. package/dist/components/{p-Dryt3COt.js.map → p-Cqc2U1ZQ.js.map} +1 -1
  184. package/dist/components/{p-DLXXGgmf.js → p-CzIpquvw.js} +2 -2
  185. package/dist/components/{p-tNQ0YulU.js → p-D0y7Sja6.js} +2 -2
  186. package/dist/components/{p-x1wf38q3.js → p-D1vQGJzc.js} +2 -2
  187. package/dist/components/{p-x1wf38q3.js.map → p-D1vQGJzc.js.map} +1 -1
  188. package/dist/components/{p-ji09W9el.js → p-D922f6Tb.js} +2 -2
  189. package/dist/components/{p-DtkLtKY0.js → p-DEW7r0U9.js} +2 -2
  190. package/dist/components/p-DEW7r0U9.js.map +1 -0
  191. package/dist/components/{p-CaNCGKEJ.js → p-DHICPS4n.js} +2 -2
  192. package/dist/components/{p-Cga6Ex0E.js → p-DOAybCp0.js} +2 -2
  193. package/dist/components/{p-CWPT3aOB.js → p-DVINzOoK.js} +2 -2
  194. package/dist/components/{p-CWPT3aOB.js.map → p-DVINzOoK.js.map} +1 -1
  195. package/dist/components/{p-7O-2D29W.js → p-DVOjjuXc.js} +2 -2
  196. package/dist/components/{p-CMQUuGvl.js → p-Dfs98tpL.js} +2 -2
  197. package/dist/components/{p-CMQUuGvl.js.map → p-Dfs98tpL.js.map} +1 -1
  198. package/dist/components/{p-DkikNSSO.js → p-Di5FH7eh.js} +2 -2
  199. package/dist/components/{p-DkikNSSO.js.map → p-Di5FH7eh.js.map} +1 -1
  200. package/dist/components/p-DjkUFlDW.js +2 -0
  201. package/dist/components/p-DjkUFlDW.js.map +1 -0
  202. package/dist/components/{p-DVd1TIxi.js → p-Dn9QHL4O.js} +2 -2
  203. package/dist/components/{p-C-dHtQMi.js → p-Do2FJd1y.js} +2 -2
  204. package/dist/components/{p-BWfzaSc3.js → p-DpoTwJAR.js} +2 -2
  205. package/dist/components/p-Dw9sE6Iw.js +2 -0
  206. package/dist/components/p-Dw9sE6Iw.js.map +1 -0
  207. package/dist/components/{p-BgLI9JoT.js → p-DwbcNDJF.js} +2 -2
  208. package/dist/components/p-Dzndkdx5.js +2 -0
  209. package/dist/components/{p-BOhpMUwO.js.map → p-Dzndkdx5.js.map} +1 -1
  210. package/dist/components/{p-DvzI2A_M.js → p-E5W27Fu_.js} +2 -2
  211. package/dist/components/{p-DkfM55x8.js → p-FIBCluwV.js} +2 -2
  212. package/dist/components/{p-DBwa1OXP.js → p-ZhP2tVmM.js} +2 -2
  213. package/dist/components/p-ZhP2tVmM.js.map +1 -0
  214. package/dist/components/p-aD5cw_m2.js +2 -0
  215. package/dist/components/p-aD5cw_m2.js.map +1 -0
  216. package/dist/components/{p-J5x-QtlD.js → p-etifxiDc.js} +2 -2
  217. package/dist/components/p-etifxiDc.js.map +1 -0
  218. package/dist/components/p-kDZ0sEII.js +2 -0
  219. package/dist/components/p-kDZ0sEII.js.map +1 -0
  220. package/dist/components/{p-rYePOPbJ.js → p-ljbw3UMZ.js} +2 -2
  221. package/dist/components/{p-DZ58Sg5a.js → p-vfCu7XSc.js} +2 -2
  222. package/dist/components/{p-Ce4zy8q1.js → p-xdmJCPCg.js} +2 -2
  223. package/dist/components/{p-CDJ2Ntcq.js → p-xtGGIZmU.js} +2 -2
  224. package/dist/docs/components.json +13115 -12615
  225. package/dist/lib/components/Icon/flag-icon-catalog.d.ts +2 -0
  226. package/dist/lib/components/Icon/icon-cache.d.ts +36 -4
  227. package/dist/lib/components/Icon/map-icon-catalog.d.ts +2 -0
  228. package/dist/lib/shell/index.js +2 -2
  229. package/dist/lib/shell/index.js.map +1 -1
  230. package/dist/lib/utils/index.js +68 -5
  231. package/dist/lib/utils/index.js.map +3 -3
  232. package/dist/lint/STYLELINT-LICENSE +20 -0
  233. package/dist/lint/THIRD-PARTY-NOTICES +553 -0
  234. package/dist/lint/index.d.ts +7 -0
  235. package/dist/lint/index.js +51385 -0
  236. package/dist/lint/types.d.ts +13 -0
  237. package/dist/mcp-data/registry/agent-activity.json +8 -3
  238. package/dist/mcp-data/registry/agent-questionnaire.json +8 -3
  239. package/dist/mcp-data/registry/agent-response.json +13 -2
  240. package/dist/mcp-data/registry/agent-source-list.json +7 -2
  241. package/dist/mcp-data/registry/agent-tool-call.json +8 -3
  242. package/dist/mcp-data/registry/attachment-list.json +7 -2
  243. package/dist/mcp-data/registry/avatar.json +22 -2
  244. package/dist/mcp-data/registry/badge.json +31 -3
  245. package/dist/mcp-data/registry/banner.json +29 -3
  246. package/dist/mcp-data/registry/bar-action.json +25 -2
  247. package/dist/mcp-data/registry/bar-nav.json +7 -2
  248. package/dist/mcp-data/registry/bar-page-title.json +25 -2
  249. package/dist/mcp-data/registry/bar-title.json +27 -2
  250. package/dist/mcp-data/registry/bar-workflow.json +25 -2
  251. package/dist/mcp-data/registry/breadcrumb.json +7 -2
  252. package/dist/mcp-data/registry/button-filled.json +69 -2
  253. package/dist/mcp-data/registry/button-inverted.json +65 -2
  254. package/dist/mcp-data/registry/button-unfilled.json +67 -2
  255. package/dist/mcp-data/registry/calendar.json +15 -2
  256. package/dist/mcp-data/registry/card-action-center.json +25 -2
  257. package/dist/mcp-data/registry/card-chart.json +38 -3
  258. package/dist/mcp-data/registry/card-navigation.json +37 -2
  259. package/dist/mcp-data/registry/card-overview.json +28 -3
  260. package/dist/mcp-data/registry/card-setting.json +36 -3
  261. package/dist/mcp-data/registry/card-settings-scope.json +25 -2
  262. package/dist/mcp-data/registry/chart-legend.json +7 -2
  263. package/dist/mcp-data/registry/chart.json +8 -3
  264. package/dist/mcp-data/registry/checkbox.json +39 -3
  265. package/dist/mcp-data/registry/chip.json +51 -2
  266. package/dist/mcp-data/registry/code-block.json +31 -2
  267. package/dist/mcp-data/registry/conversation-list-item.json +26 -3
  268. package/dist/mcp-data/registry/conversation-list-section.json +15 -2
  269. package/dist/mcp-data/registry/conversation-list.json +16 -33
  270. package/dist/mcp-data/registry/data-customize.json +399 -0
  271. package/dist/mcp-data/registry/{table-filter.json → data-filter.json} +59 -34
  272. package/dist/mcp-data/registry/data-group.json +383 -0
  273. package/dist/mcp-data/registry/data-preferences.json +591 -0
  274. package/dist/mcp-data/registry/data-saved-views.json +458 -0
  275. package/dist/mcp-data/registry/data-search.json +392 -0
  276. package/dist/mcp-data/registry/data-sort.json +279 -0
  277. package/dist/mcp-data/registry/data-toolbar.json +194 -0
  278. package/dist/mcp-data/registry/divider.json +7 -2
  279. package/dist/mcp-data/registry/empty-state.json +7 -2
  280. package/dist/mcp-data/registry/field.json +15 -2
  281. package/dist/mcp-data/registry/filter-menu.json +51 -4
  282. package/dist/mcp-data/registry/icon.json +34 -8
  283. package/dist/mcp-data/registry/input-date.json +49 -2
  284. package/dist/mcp-data/registry/input-time.json +49 -2
  285. package/dist/mcp-data/registry/input.json +50 -3
  286. package/dist/mcp-data/registry/loader.json +22 -2
  287. package/dist/mcp-data/registry/markdown.json +18 -2
  288. package/dist/mcp-data/registry/menu.json +29 -4
  289. package/dist/mcp-data/registry/message-actions.json +7 -2
  290. package/dist/mcp-data/registry/message-bubble.json +27 -2
  291. package/dist/mcp-data/registry/message-composer.json +26 -3
  292. package/dist/mcp-data/registry/message-scroller.json +13 -2
  293. package/dist/mcp-data/registry/message.json +7 -2
  294. package/dist/mcp-data/registry/mobile-bar-nav.json +26 -3
  295. package/dist/mcp-data/registry/mobile-header.json +27 -2
  296. package/dist/mcp-data/registry/mobile-section-switcher.json +27 -2
  297. package/dist/mcp-data/registry/mobile-sheet-nav.json +28 -3
  298. package/dist/mcp-data/registry/modal.json +25 -2
  299. package/dist/mcp-data/registry/pagination.json +7 -2
  300. package/dist/mcp-data/registry/panel-nav.json +29 -3
  301. package/dist/mcp-data/registry/panel-sub-nav.json +26 -1
  302. package/dist/mcp-data/registry/panel-tool-header.json +25 -2
  303. package/dist/mcp-data/registry/panel-tool-search.json +25 -2
  304. package/dist/mcp-data/registry/panel-tools.json +28 -4
  305. package/dist/mcp-data/registry/paper-texture.json +7 -2
  306. package/dist/mcp-data/registry/radio.json +36 -2
  307. package/dist/mcp-data/registry/registry.json +7465 -5073
  308. package/dist/mcp-data/registry/score.json +39 -2
  309. package/dist/mcp-data/registry/scroll-overlay.json +7 -2
  310. package/dist/mcp-data/registry/select.json +53 -2
  311. package/dist/mcp-data/registry/shell-app.json +27 -2
  312. package/dist/mcp-data/registry/shell-page.json +7 -2
  313. package/dist/mcp-data/registry/shell-tools.json +26 -2
  314. package/dist/mcp-data/registry/skeleton.json +42 -2
  315. package/dist/mcp-data/registry/slider.json +43 -2
  316. package/dist/mcp-data/registry/swatch-picker.json +25 -2
  317. package/dist/mcp-data/registry/switch.json +38 -2
  318. package/dist/mcp-data/registry/tab-group.json +50 -2
  319. package/dist/mcp-data/registry/table.json +173 -72
  320. package/dist/mcp-data/registry/tag.json +53 -2
  321. package/dist/mcp-data/registry/text.json +23 -2
  322. package/dist/mcp-data/registry/textarea.json +39 -2
  323. package/dist/mcp-data/registry/time-picker.json +15 -2
  324. package/dist/mcp-data/registry/toast.json +25 -2
  325. package/dist/mcp-data/registry/tooltip-chart.json +25 -2
  326. package/dist/mcp-data/registry/tooltip.json +27 -2
  327. package/dist/mcp-data/registry/typing-indicator.json +7 -2
  328. package/dist/react/components.d.ts +8 -7
  329. package/dist/react/components.js +8 -7
  330. package/dist/react/ds-data-customize.d.ts +13 -0
  331. package/dist/react/ds-data-customize.js +15 -0
  332. package/dist/react/ds-data-filter.d.ts +18 -0
  333. package/dist/react/{ds-table-filter.js → ds-data-filter.js} +5 -5
  334. package/dist/react/ds-data-group.d.ts +14 -0
  335. package/dist/react/{ds-table-group.js → ds-data-group.js} +5 -5
  336. package/dist/react/ds-data-preferences.d.ts +21 -0
  337. package/dist/react/{ds-table-preferences.js → ds-data-preferences.js} +6 -6
  338. package/dist/react/ds-data-saved-views.d.ts +17 -0
  339. package/dist/react/{ds-table-saved-views.js → ds-data-saved-views.js} +5 -5
  340. package/dist/react/ds-data-search.d.ts +14 -0
  341. package/dist/react/{ds-table-search.js → ds-data-search.js} +5 -5
  342. package/dist/react/ds-data-sort.d.ts +12 -0
  343. package/dist/react/ds-data-sort.js +12 -0
  344. package/dist/react/{ds-table-toolbar.d.ts → ds-data-toolbar.d.ts} +3 -3
  345. package/dist/react/ds-data-toolbar.js +12 -0
  346. package/dist/react/ds-table.d.ts +3 -3
  347. package/dist/react/ds-table.js +1 -1
  348. package/dist/styles/table.css +11 -11
  349. package/dist/types/components/ConversationList/ConversationList.d.ts +0 -7
  350. package/dist/types/components/DataCustomize/DataCustomize.d.ts +67 -0
  351. package/dist/types/components/{TableFilter/TableFilter.d.ts → DataFilter/DataFilter.d.ts} +2 -2
  352. package/dist/types/components/{TableGroup/TableGroup.d.ts → DataGroup/DataGroup.d.ts} +8 -8
  353. package/dist/types/components/{TablePreferences/TablePreferences.d.ts → DataPreferences/DataPreferences.d.ts} +29 -13
  354. package/dist/types/components/{TableSavedViews/TableSavedViews.d.ts → DataSavedViews/DataSavedViews.d.ts} +10 -10
  355. package/dist/types/components/DataSavedViews/data-saved-views-types.d.ts +24 -0
  356. package/dist/types/components/DataSavedViews/index.d.ts +2 -0
  357. package/dist/types/components/{TableSearch/TableSearch.d.ts → DataSearch/DataSearch.d.ts} +6 -6
  358. package/dist/types/components/{TableSearch/table-search-model.d.ts → DataSearch/data-search-model.d.ts} +6 -6
  359. package/dist/types/components/{TableSearch/table-search-types.d.ts → DataSearch/data-search-types.d.ts} +3 -3
  360. package/dist/types/components/{TableSort/TableSort.d.ts → DataSort/DataSort.d.ts} +7 -6
  361. package/dist/types/components/DataSort/data-sort-menu.d.ts +15 -0
  362. package/dist/types/components/{TableToolbar/TableToolbar.d.ts → DataToolbar/DataToolbar.d.ts} +3 -3
  363. package/dist/types/components/Icon/icon-cache.d.ts +36 -4
  364. package/dist/types/components/Icon/map-icon-catalog.d.ts +3 -0
  365. package/dist/types/components/PanelTools/PanelTools.d.ts +1 -1
  366. package/dist/types/components/Table/Table.d.ts +9 -9
  367. package/dist/types/components/Table/index.d.ts +1 -1
  368. package/dist/types/components/Table/table-action-menu.d.ts +1 -1
  369. package/dist/types/components/Table/table-body-renderer.d.ts +2 -2
  370. package/dist/types/components/Table/table-column-customizer.d.ts +9 -9
  371. package/dist/types/components/Table/table-group-load-controller.d.ts +2 -2
  372. package/dist/types/components/Table/table-model.d.ts +7 -7
  373. package/dist/types/components/Table/table-render-model.d.ts +3 -3
  374. package/dist/types/components/Table/table-types.d.ts +17 -36
  375. package/dist/types/components/Table/table-virtual-model.d.ts +2 -2
  376. package/dist/types/components.d.ts +3837 -3640
  377. package/dist/types/utils/data-field.d.ts +38 -0
  378. package/dist/types/utils/list-customizer.d.ts +38 -0
  379. package/dist/types/utils/table-caption-compact.d.ts +1 -1
  380. package/dist/vue/components.d.ts +8 -7
  381. package/dist/vue/components.js +8 -7
  382. package/dist/vue/ds-conversation-list.js +1 -3
  383. package/dist/vue/ds-data-customize.d.ts +3 -0
  384. package/dist/vue/ds-data-customize.js +23 -0
  385. package/dist/vue/{ds-table-group.d.ts → ds-data-filter.d.ts} +1 -1
  386. package/dist/vue/{ds-table-filter.js → ds-data-filter.js} +2 -2
  387. package/dist/vue/{ds-table-sort.d.ts → ds-data-group.d.ts} +1 -1
  388. package/dist/vue/{ds-table-group.js → ds-data-group.js} +2 -2
  389. package/dist/vue/{ds-table-saved-views.d.ts → ds-data-preferences.d.ts} +1 -1
  390. package/dist/vue/{ds-table-preferences.js → ds-data-preferences.js} +10 -7
  391. package/dist/vue/ds-data-saved-views.d.ts +3 -0
  392. package/dist/vue/{ds-table-saved-views.js → ds-data-saved-views.js} +2 -2
  393. package/dist/vue/{ds-table-filter.d.ts → ds-data-search.d.ts} +1 -1
  394. package/dist/vue/{ds-table-search.js → ds-data-search.js} +3 -3
  395. package/dist/vue/{ds-table-search.d.ts → ds-data-sort.d.ts} +1 -1
  396. package/dist/vue/{ds-table-sort.js → ds-data-sort.js} +3 -3
  397. package/dist/vue/ds-data-toolbar.d.ts +3 -0
  398. package/dist/vue/ds-data-toolbar.js +9 -0
  399. package/dist/vue/ds-table.js +4 -4
  400. package/package.json +34 -9
  401. package/dist/angular/ds-table-filter.d.ts +0 -50
  402. package/dist/angular/ds-table-group.d.ts +0 -29
  403. package/dist/angular/ds-table-preferences.d.ts +0 -39
  404. package/dist/angular/ds-table-saved-views.d.ts +0 -49
  405. package/dist/angular/ds-table-search.d.ts +0 -20
  406. package/dist/angular/ds-table-sort.d.ts +0 -16
  407. package/dist/angular/ds-table-toolbar.d.ts +0 -12
  408. package/dist/components/ds-table-filter.js +0 -2
  409. package/dist/components/ds-table-filter.js.map +0 -1
  410. package/dist/components/ds-table-group.js +0 -2
  411. package/dist/components/ds-table-preferences.d.ts +0 -11
  412. package/dist/components/ds-table-preferences.js +0 -2
  413. package/dist/components/ds-table-preferences.js.map +0 -1
  414. package/dist/components/ds-table-saved-views.js +0 -2
  415. package/dist/components/ds-table-saved-views.js.map +0 -1
  416. package/dist/components/ds-table-search.js +0 -2
  417. package/dist/components/ds-table-search.js.map +0 -1
  418. package/dist/components/ds-table-sort.js +0 -2
  419. package/dist/components/ds-table-sort.js.map +0 -1
  420. package/dist/components/ds-table-toolbar.d.ts +0 -11
  421. package/dist/components/ds-table-toolbar.js +0 -2
  422. package/dist/components/ds-table-toolbar.js.map +0 -1
  423. package/dist/components/p-BOhpMUwO.js +0 -2
  424. package/dist/components/p-C0CE2v5D.js +0 -2
  425. package/dist/components/p-CKarsuR2.js +0 -2
  426. package/dist/components/p-CKarsuR2.js.map +0 -1
  427. package/dist/components/p-CQIh2yRP.js.map +0 -1
  428. package/dist/components/p-Cea7rROU.js +0 -2
  429. package/dist/components/p-Cea7rROU.js.map +0 -1
  430. package/dist/components/p-D7yBIPB6.js +0 -2
  431. package/dist/components/p-DBwa1OXP.js.map +0 -1
  432. package/dist/components/p-DtkLtKY0.js.map +0 -1
  433. package/dist/components/p-J5x-QtlD.js.map +0 -1
  434. package/dist/components/p-KBrC9IG4.js +0 -2
  435. package/dist/components/p-KBrC9IG4.js.map +0 -1
  436. package/dist/components/p-MQ36mEgn.js +0 -2
  437. package/dist/components/p-MQ36mEgn.js.map +0 -1
  438. package/dist/components/p-TjXGRPik.js +0 -2
  439. package/dist/components/p-bM3LH3kc.js +0 -2
  440. package/dist/components/p-bM3LH3kc.js.map +0 -1
  441. package/dist/mcp-data/registry/table-group.json +0 -358
  442. package/dist/mcp-data/registry/table-preferences.json +0 -511
  443. package/dist/mcp-data/registry/table-saved-views.json +0 -433
  444. package/dist/mcp-data/registry/table-search.json +0 -366
  445. package/dist/mcp-data/registry/table-sort.json +0 -253
  446. package/dist/mcp-data/registry/table-toolbar.json +0 -180
  447. package/dist/react/ds-table-filter.d.ts +0 -18
  448. package/dist/react/ds-table-group.d.ts +0 -14
  449. package/dist/react/ds-table-preferences.d.ts +0 -21
  450. package/dist/react/ds-table-saved-views.d.ts +0 -17
  451. package/dist/react/ds-table-search.d.ts +0 -14
  452. package/dist/react/ds-table-sort.d.ts +0 -12
  453. package/dist/react/ds-table-sort.js +0 -12
  454. package/dist/react/ds-table-toolbar.js +0 -12
  455. package/dist/types/components/TableSavedViews/index.d.ts +0 -2
  456. package/dist/types/components/TableSavedViews/table-saved-views-types.d.ts +0 -24
  457. package/dist/types/components/TableSort/table-sort-menu.d.ts +0 -15
  458. package/dist/vue/ds-table-preferences.d.ts +0 -3
  459. package/dist/vue/ds-table-toolbar.d.ts +0 -3
  460. package/dist/vue/ds-table-toolbar.js +0 -9
  461. /package/dist/components/{ds-table-group.js.map → ds-data-group.js.map} +0 -0
  462. /package/dist/components/{p-D8rpKod8.js.map → p-0FYD2bev.js.map} +0 -0
  463. /package/dist/components/{p-DK9Ex3aY.js.map → p-5URekSny.js.map} +0 -0
  464. /package/dist/components/{p-Ds51RgCi.js.map → p-7V1AhetH.js.map} +0 -0
  465. /package/dist/components/{p-C2JnoaWI.js.map → p-8yBdCXCn.js.map} +0 -0
  466. /package/dist/components/{p-su6y4hLK.js.map → p-BciTtTwx.js.map} +0 -0
  467. /package/dist/components/{p-CQ5RwY2C.js.map → p-BhJXmuRA.js.map} +0 -0
  468. /package/dist/components/{p-neWTRxmf.js.map → p-Bm7rqs3C.js.map} +0 -0
  469. /package/dist/components/{p-BV5T1hzN.js.map → p-Bn8I7iw6.js.map} +0 -0
  470. /package/dist/components/{p-PmcUwTQ_.js.map → p-Bpd6YCo4.js.map} +0 -0
  471. /package/dist/components/{p-B27_chI7.js.map → p-C6xVxqOn.js.map} +0 -0
  472. /package/dist/components/{p-C4m8y9ki.js.map → p-CD4DZPy_.js.map} +0 -0
  473. /package/dist/components/{p-CuwopElg.js.map → p-CGidq1iC.js.map} +0 -0
  474. /package/dist/components/{p-CWog26I_.js.map → p-CNxvnQ6A.js.map} +0 -0
  475. /package/dist/components/{p-2GVGhrh0.js.map → p-CUcUHwI3.js.map} +0 -0
  476. /package/dist/components/{p-CbSUyv5B.js.map → p-CYzFfFcH.js.map} +0 -0
  477. /package/dist/components/{p-8oRkhvjy.js.map → p-CdGXzRP8.js.map} +0 -0
  478. /package/dist/components/{p-Jaqdfw4E.js.map → p-CiH4GG6r.js.map} +0 -0
  479. /package/dist/components/{p-Cl9phSf0.js.map → p-CiJ1WR11.js.map} +0 -0
  480. /package/dist/components/{p-Cus1Sigh.js.map → p-CmexIVnE.js.map} +0 -0
  481. /package/dist/components/{p-DLXXGgmf.js.map → p-CzIpquvw.js.map} +0 -0
  482. /package/dist/components/{p-tNQ0YulU.js.map → p-D0y7Sja6.js.map} +0 -0
  483. /package/dist/components/{p-ji09W9el.js.map → p-D922f6Tb.js.map} +0 -0
  484. /package/dist/components/{p-CaNCGKEJ.js.map → p-DHICPS4n.js.map} +0 -0
  485. /package/dist/components/{p-Cga6Ex0E.js.map → p-DOAybCp0.js.map} +0 -0
  486. /package/dist/components/{p-7O-2D29W.js.map → p-DVOjjuXc.js.map} +0 -0
  487. /package/dist/components/{p-DVd1TIxi.js.map → p-Dn9QHL4O.js.map} +0 -0
  488. /package/dist/components/{p-C-dHtQMi.js.map → p-Do2FJd1y.js.map} +0 -0
  489. /package/dist/components/{p-BWfzaSc3.js.map → p-DpoTwJAR.js.map} +0 -0
  490. /package/dist/components/{p-BgLI9JoT.js.map → p-DwbcNDJF.js.map} +0 -0
  491. /package/dist/components/{p-DvzI2A_M.js.map → p-E5W27Fu_.js.map} +0 -0
  492. /package/dist/components/{p-DkfM55x8.js.map → p-FIBCluwV.js.map} +0 -0
  493. /package/dist/components/{p-rYePOPbJ.js.map → p-ljbw3UMZ.js.map} +0 -0
  494. /package/dist/components/{p-DZ58Sg5a.js.map → p-vfCu7XSc.js.map} +0 -0
  495. /package/dist/components/{p-Ce4zy8q1.js.map → p-xdmJCPCg.js.map} +0 -0
  496. /package/dist/components/{p-CDJ2Ntcq.js.map → p-xtGGIZmU.js.map} +0 -0
@@ -101,7 +101,32 @@
101
101
  "label": "Trend resolution unit coverage",
102
102
  "path": "tests/metric-change.test.ts"
103
103
  }
104
- ]
104
+ ],
105
+ "styling": {
106
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
107
+ "protected": [
108
+ {
109
+ "properties": [
110
+ "background",
111
+ "border",
112
+ "box-shadow",
113
+ "color"
114
+ ],
115
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
116
+ "props": [
117
+ "variant"
118
+ ]
119
+ },
120
+ {
121
+ "properties": [
122
+ "border-radius"
123
+ ],
124
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
125
+ "props": []
126
+ }
127
+ ],
128
+ "customProperties": []
129
+ }
105
130
  },
106
131
  "api": {
107
132
  "props": {
@@ -352,7 +377,7 @@
352
377
  "peerDependencies": {
353
378
  "required": [
354
379
  "@ds-mo/tokens ^6.5.1",
355
- "@ds-mo/icons ^7.0.0"
380
+ "@ds-mo/icons ^8.1.0"
356
381
  ],
357
382
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
358
383
  }
@@ -376,7 +401,7 @@
376
401
  },
377
402
  {
378
403
  "path": "src/wc/components/CardOverview/CardOverview.tsx",
379
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n forceUpdate,\n h,\n Host,\n Prop,\n State,\n} from '@stencil/core';\nimport { resolveCssLengthPx, TOKEN_DEFAULTS } from '../../utils';\nimport type { TextColor, TextVariant } from '../Text/text-types';\nimport type { MetricTrend } from '../../utils/metric-change';\nimport type {\n CardOverviewLayout,\n CardOverviewVariant,\n OverviewMetric,\n OverviewScore,\n} from './card-overview-types';\nimport {\n findNextOverviewMetricIndex,\n resolveCardOverviewCollapseGeometry,\n resolveOverviewGridColumns,\n resolveOverviewRovingIndex,\n} from './card-overview-controller';\n\n/**\n * Metric cells below this width drop a grid column rather than compress further.\n *\n * A literal rather than a token: this is a layout threshold for a grid cell, and\n * the dimension scale has no token for that. The 200px tokens it could borrow\n * (`menu-width-xs`, `form-width-xs`, `panel-width-2xs`) all name other\n * components, so binding to one would couple this grid to an unrelated contract.\n * Consumers override with the `metricMinWidth` prop.\n */\nconst DEFAULT_METRIC_MIN_WIDTH = '200px';\n\n/** Skeleton cells rendered while loading with no metrics supplied yet. */\nconst DEFAULT_LOADING_METRIC_COUNT = 5;\n\n/** Maximum number of comparable measures the summary bar presents at once. */\nconst MAX_METRIC_COUNT = 7;\n\nconst TREND_COLORS: Record<MetricTrend['tone'], TextColor> = {\n positive: 'var(--color-always-dark-foreground-positive)',\n negative: 'var(--color-always-dark-foreground-negative)',\n neutral: 'var(--color-always-dark-foreground-secondary)',\n};\n\nconst ALWAYS_DARK_PRIMARY: TextColor = 'var(--color-always-dark-foreground-primary)';\nconst ALWAYS_DARK_SECONDARY: TextColor = 'var(--color-always-dark-foreground-secondary)';\n\n@Component({\n tag: 'ds-card-overview',\n styleUrl: 'CardOverview.css',\n scoped: true,\n})\nexport class CardOverview {\n @Element() el!: HTMLElement;\n\n /** Full summary card, or the condensed 48px summary bar. */\n @Prop() variant: CardOverviewVariant = 'default';\n\n /** Intrinsic responsive layout, or a page-owned single-column stack. */\n @Prop() layout: CardOverviewLayout = 'auto';\n\n /** Optional nonselectable safety score rendered as the first grid cell. */\n @Prop() score: OverviewScore | undefined;\n\n /** Fixed current date or range. Replaced by content in the `period` slot. */\n @Prop() periodLabel: string = '';\n\n /** Copy between the current period and comparison control, usually `vs.`. */\n @Prop() comparisonLabel: string = '';\n\n /** Measures rendered in the responsive grid. Only the first seven are shown. */\n @Prop() metrics: OverviewMetric[] = [];\n\n /**\n * Width a metric cell may shrink to before the grid drops a column. The grid\n * uses this threshold while choosing an evenly distributed column count.\n */\n @Prop() metricMinWidth: string = DEFAULT_METRIC_MIN_WIDTH;\n\n /** Replace the score and metrics with skeletons while data resolves. */\n @Prop() isLoading: boolean = false;\n\n /** Message shown in place of the score when its figure cannot be resolved. */\n @Prop() scoreErrorMessage: string | undefined;\n\n /** Accessible name for the region. */\n @Prop() overviewLabel: string = 'Overview';\n\n /**\n * Page-controlled visual collapse from the full card (`0`) toward its 48px\n * compact handoff height (`1`). The component preserves its expanded flow\n * height, keeps the period bar stationary, and moves/clips the grid beneath\n * it. The page still owns sticky positioning and the final compact handoff.\n */\n @Prop() scrollCollapseProgress: number = 0;\n\n /** Emitted when a metric that is not inactive is activated. */\n @Event() dsMetricSelect!: EventEmitter<OverviewMetric>;\n\n /** Roving tab stop across selectable metrics. */\n @State() private focusedMetricIndex: number = 0;\n @State() private expandedHeight: number = 0;\n @State() private gridColumns: number | undefined;\n\n private layoutEl?: HTMLElement;\n private layoutResizeObserver: ResizeObserver | null = null;\n private layoutResizeFrame: number | undefined;\n /** The node the observer is bound to, which a later render can replace. */\n private observedLayoutEl?: HTMLElement;\n\n componentDidLoad() {\n this.observeLayout();\n }\n\n componentDidRender() {\n // Re-bind after HMR when DidLoad's observer was dropped, and whenever a\n // render swapped the layout node out from under the observer: the ref points\n // at the new node while the observer still watches the detached one, which\n // never resizes again. The grid would then keep its first measured column\n // count for the life of the component.\n if (!this.layoutResizeObserver || this.observedLayoutEl !== this.layoutEl) {\n this.observeLayout();\n } else {\n this.updateLayoutGeometry();\n }\n }\n\n disconnectedCallback() {\n this.layoutResizeObserver?.disconnect();\n this.layoutResizeObserver = null;\n if (this.layoutResizeFrame !== undefined) cancelAnimationFrame(this.layoutResizeFrame);\n this.layoutResizeFrame = undefined;\n this.observedLayoutEl = undefined;\n }\n\n private get visibleMetrics(): OverviewMetric[] {\n return this.metrics.slice(0, MAX_METRIC_COUNT);\n }\n\n private get visibleCellCount(): number {\n const metricCount = this.isLoading ? this.loadingMetricCount : this.visibleMetrics.length;\n return 1 + metricCount;\n }\n\n /** Preserve an authored data shape while loading, or use the standard six-cell grid. */\n private get loadingMetricCount(): number {\n return this.visibleMetrics.length || DEFAULT_LOADING_METRIC_COUNT;\n }\n\n private get resolvedScrollCollapseProgress(): number {\n if (this.variant === 'compact' || !Number.isFinite(this.scrollCollapseProgress)) return 0;\n return Math.min(1, Math.max(0, this.scrollCollapseProgress));\n }\n\n private get scrollCollapseGeometry() {\n const compactHeight = resolveCssLengthPx(TOKEN_DEFAULTS.size600, 48);\n return resolveCardOverviewCollapseGeometry({\n variant: this.variant,\n progress: this.resolvedScrollCollapseProgress,\n expandedHeight: this.expandedHeight,\n compactHeight,\n });\n }\n\n private observeLayout() {\n const layout = this.layoutEl;\n if (!layout) return;\n\n const bounds = layout.getBoundingClientRect();\n this.updateLayoutGeometry(bounds.width, bounds.height);\n if (typeof ResizeObserver === 'undefined') return;\n\n this.layoutResizeObserver?.disconnect();\n this.layoutResizeObserver = new ResizeObserver(entries => {\n const rect = entries[0]?.contentRect;\n if (!rect) return;\n if (this.layoutResizeFrame !== undefined) cancelAnimationFrame(this.layoutResizeFrame);\n this.layoutResizeFrame = requestAnimationFrame(() => {\n this.layoutResizeFrame = undefined;\n this.updateLayoutGeometry(rect.width, rect.height);\n });\n });\n this.layoutResizeObserver.observe(layout);\n this.observedLayoutEl = layout;\n }\n\n private updateLayoutGeometry(width?: number, height?: number) {\n const layout = this.layoutEl;\n if (!layout) return;\n\n const bounds =\n width === undefined || height === undefined ? layout.getBoundingClientRect() : null;\n const resolvedWidth = width ?? bounds?.width ?? 0;\n const resolvedHeight = height ?? bounds?.height ?? 0;\n\n let changed = false;\n\n if (resolvedHeight > 0 && Math.abs(resolvedHeight - this.expandedHeight) >= 0.5) {\n this.expandedHeight = resolvedHeight;\n changed = true;\n }\n\n const columns = resolveOverviewGridColumns({\n cellCount: this.visibleCellCount,\n availableWidth: resolvedWidth,\n minCellWidth: resolveCssLengthPx(this.metricMinWidth, DEFAULT_METRIC_MIN_WIDTH),\n });\n if (columns !== this.gridColumns) {\n this.gridColumns = columns;\n changed = true;\n }\n\n // The column count is written straight to the host rather than through the\n // rendered style object. Measurement happens in `componentDidLoad` and in the\n // ResizeObserver callback, both outside a render cycle, and the host's style\n // is not patched again after the first render — so a rendered value would stay\n // pinned to whatever the first paint produced, before any width was known.\n if (this.gridColumns) {\n this.el.style.setProperty('--ds-card-overview-grid-columns', String(this.gridColumns));\n } else {\n this.el.style.removeProperty('--ds-card-overview-grid-columns');\n }\n\n if (changed) forceUpdate(this);\n }\n\n /** Index of the roving tab stop, skipping inactive metrics. */\n private get rovingIndex(): number {\n return resolveOverviewRovingIndex(this.visibleMetrics, this.focusedMetricIndex);\n }\n\n private selectMetric(metric: OverviewMetric, index: number) {\n if (metric.isInactive) return;\n this.focusedMetricIndex = index;\n this.dsMetricSelect.emit(metric);\n }\n\n /** Move the roving tab stop, wrapping and skipping inactive metrics. */\n private moveFocus(from: number, step: number) {\n const metrics = this.visibleMetrics;\n const next = findNextOverviewMetricIndex(metrics, from, step < 0 ? -1 : 1);\n if (next < 0) return;\n\n this.focusedMetricIndex = next;\n const cells = this.el.querySelectorAll<HTMLElement>('.card-overview__metric-action');\n cells[next]?.focus();\n }\n\n private handleMetricKeyDown = (event: KeyboardEvent, index: number) => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n this.moveFocus(index, 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n this.moveFocus(index, -1);\n break;\n default:\n break;\n }\n };\n\n /**\n * A skeleton bar matching the type metrics of the text it stands in for, so the\n * loading state holds the same shape and rhythm as the resolved content.\n *\n * `background` is always passed so the base and shimmer follow the card's\n * permanently dark surface in both themes.\n */\n private bar(textVariant: TextVariant, width: string, className?: string) {\n return (\n <ds-skeleton\n class={className}\n variant=\"text\"\n textVariant={textVariant}\n width={width}\n background=\"always-dark\"\n />\n );\n }\n\n /**\n * Arrow and change in one text node.\n *\n * The arrow is a typeface glyph rather than an icon so it inherits the exact\n * size, weight, and baseline of the value beside it; an icon would need its\n * own size scale and would drift whenever the type scale changed.\n *\n * Tone is supplied by the caller and never inferred here.\n */\n private renderTrend(trend: MetricTrend | undefined, variant: TextVariant) {\n if (!trend) return null;\n return (\n <ds-text\n as=\"span\"\n class={{\n 'card-overview__trend': true,\n 'ds-control-label-box': true,\n [`card-overview__trend--${trend.tone}`]: true,\n }}\n variant={variant}\n color={TREND_COLORS[trend.tone]}\n fontFeature=\"tabular-nums\"\n >\n {trend.direction === 'up' ? '↑' : '↓'} {trend.value}\n </ds-text>\n );\n }\n\n private renderScore() {\n if (this.isLoading) {\n return (\n <div class=\"card-overview__score\" part=\"score\">\n <div class=\"card-overview__score-content\">\n <ds-score size=\"lg\" variant=\"dense\" isLoading></ds-score>\n <div class=\"card-overview__score-copy\">\n {this.bar('text-body-small', '64px', 'card-overview__score-label-spacer')}\n {this.bar('text-body-medium', '28px')}\n </div>\n </div>\n </div>\n );\n }\n\n const unavailableMessage =\n this.scoreErrorMessage || (!this.score ? 'Score unavailable' : undefined);\n if (unavailableMessage) {\n return (\n /* eslint-disable-next-line local/prefer-direct-ds-text -- The score track owns the grid divider and 8px outer inset. */\n <div class=\"card-overview__score\" part=\"score\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-content card-overview__score-content--error\"\n variant=\"text-body-medium\"\n color=\"inherit\"\n role=\"alert\"\n >\n {unavailableMessage}\n </ds-text>\n </div>\n );\n }\n\n const score = this.score;\n if (!score) return null;\n\n return (\n <div class=\"card-overview__score\" part=\"score\">\n <div class=\"card-overview__score-content\">\n <ds-score\n size=\"lg\"\n variant=\"dense\"\n value={score.value}\n level={score.level}\n label=\"Safety score\"\n />\n <div class=\"card-overview__score-copy\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-label-spacer ds-control-label-box\"\n variant=\"text-body-small\"\n color={ALWAYS_DARK_SECONDARY}\n aria-hidden=\"true\"\n >\n &nbsp;\n </ds-text>\n {this.renderTrend(score.trend, 'text-body-medium')}\n </div>\n </div>\n </div>\n );\n }\n\n private renderMetric(metric: OverviewMetric, index: number) {\n const selectable = !metric.isInactive;\n const label = (\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-label ds-control-label-box\"\n variant=\"text-body-small\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {metric.label}\n </ds-text>\n );\n\n return (\n <div class=\"card-overview__metric\" part=\"metric\">\n <div\n class={{\n 'card-overview__metric-action': true,\n 'card-overview__metric-action--inactive': !selectable,\n // Selection targets get the shared wash; press scaling is not used here\n // so the grid keeps its columns aligned. See docs/control-press-policy.md.\n 'ds-interaction-fill': selectable,\n 'ds-interaction-fill--on-always-dark': selectable,\n 'ds-focus-ring-inset': selectable,\n }}\n role={selectable ? 'button' : undefined}\n tabIndex={selectable && index === this.rovingIndex ? 0 : -1}\n onClick={() => this.selectMetric(metric, index)}\n onKeyDown={event => this.handleMetricKeyDown(event, index)}\n onFocusin={() => {\n if (selectable) this.focusedMetricIndex = index;\n }}\n >\n <div class=\"ds-interaction-fill__content card-overview__metric-content\">\n {metric.labelTooltip ? (\n <ds-tooltip label={metric.labelTooltip} side=\"top\" size=\"sm\">\n {label}\n </ds-tooltip>\n ) : (\n label\n )}\n <div class=\"card-overview__metric-figure\">\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-value ds-control-label-box\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_PRIMARY}\n fontFeature=\"tabular-nums\"\n >\n {metric.value}\n </ds-text>\n {this.renderTrend(metric.trend, 'text-body-medium')}\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n private renderMetrics() {\n if (this.isLoading) {\n return [\n this.renderScore(),\n ...Array.from({ length: this.loadingMetricCount }, (_, index) => (\n <div class=\"card-overview__metric\" key={`loading-${index}`}>\n <div class=\"card-overview__metric-action card-overview__metric-action--inactive\">\n <div class=\"card-overview__metric-content\">\n {this.bar('text-body-small', '35%', 'card-overview__metric-label')}\n <div class=\"card-overview__metric-figure\">\n {this.bar('text-body-medium', '40px')}\n {/* Match renderTrend's type canvas and the score trend's 28px width. */}\n {this.bar('text-body-medium', '28px')}\n </div>\n </div>\n </div>\n </div>\n )),\n ];\n }\n\n return [\n this.renderScore(),\n ...this.visibleMetrics.map((metric, index) => this.renderMetric(metric, index)),\n ];\n }\n\n render() {\n const compact = this.variant === 'compact';\n const collapse = this.scrollCollapseGeometry;\n\n // Omit absent custom properties rather than handing them an undefined value.\n // Stencil serialises undefined to the string \"undefined\", which is invalid in\n // `repeat()` and voids the whole grid-template-columns declaration — the grid\n // then collapses to one implicit column instead of using the `auto-fit`\n // fallback baked into the stylesheet.\n const customProperties: Record<string, string> = {\n '--ds-card-overview-metric-min': this.metricMinWidth,\n };\n if (this.gridColumns) {\n customProperties['--ds-card-overview-grid-columns'] = String(this.gridColumns);\n }\n if (collapse.active) {\n customProperties['--ds-card-overview-expanded-height'] = `${collapse.expandedHeight}px`;\n customProperties['--ds-card-overview-visible-height'] = `${collapse.visibleHeight}px`;\n customProperties['--ds-card-overview-content-offset'] = `${collapse.offset}px`;\n }\n\n return (\n <Host\n class={{\n 'card-overview': true,\n 'card-overview--compact': compact,\n 'card-overview--stacked': !compact && this.layout === 'stacked',\n 'card-overview--scroll-collapsing': collapse.active,\n }}\n role=\"region\"\n aria-label={this.overviewLabel}\n aria-busy={this.isLoading ? 'true' : undefined}\n style={customProperties}\n >\n <div\n class={{\n 'card-overview__surface': true,\n // Split shadow and inset highlight, so the opaque surface cannot cover\n // the highlight the way a combined elevation shadow would.\n 'ds-control-elevation': true,\n 'ds-control-elevation--sm': !compact,\n 'ds-control-elevation--floating': compact,\n }}\n >\n <div class=\"card-overview__clip\">\n <div\n ref={element => {\n this.layoutEl = (element as HTMLElement) ?? undefined;\n }}\n class={{\n 'card-overview__layout': true,\n }}\n >\n <div class=\"card-overview__summary\">\n <div class=\"card-overview__header ds-control--md\">\n <div class=\"card-overview__period\">\n <div class=\"card-overview__period-current\">\n <slot name=\"period\">\n {this.periodLabel && (\n /* eslint-disable-next-line local/prefer-direct-ds-text -- Fixed copy uses the same structural frame and label inset as a slotted Select. */\n <div class=\"card-overview__period-fixed ds-control-frame\">\n <ds-text\n as=\"span\"\n class=\"ds-control-label-box\"\n variant=\"text-body-medium\"\n emphasis\n color={ALWAYS_DARK_PRIMARY}\n >\n {this.periodLabel}\n </ds-text>\n </div>\n )}\n </slot>\n </div>\n {this.comparisonLabel && (\n <ds-text\n as=\"span\"\n class=\"card-overview__period-comparison ds-control-label-box\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {this.comparisonLabel}\n </ds-text>\n )}\n </div>\n <div class=\"card-overview__filter\">\n <slot name=\"filter\" />\n </div>\n </div>\n </div>\n\n {!compact && (\n <div class=\"card-overview__body\">\n <div class=\"card-overview__metrics\" part=\"metrics\">\n {this.renderMetrics()}\n </div>\n\n <slot name=\"footer\" />\n </div>\n )}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
404
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n forceUpdate,\n h,\n Host,\n Prop,\n State,\n} from '@stencil/core';\nimport { resolveCssLengthPx, TOKEN_DEFAULTS } from '../../utils';\nimport type { TextColor, TextVariant } from '../Text/text-types';\nimport type { MetricTrend } from '../../utils/metric-change';\nimport type {\n CardOverviewLayout,\n CardOverviewVariant,\n OverviewMetric,\n OverviewScore,\n} from './card-overview-types';\nimport {\n findNextOverviewMetricIndex,\n resolveCardOverviewCollapseGeometry,\n resolveOverviewGridColumns,\n resolveOverviewRovingIndex,\n} from './card-overview-controller';\n\n/**\n * Metric cells below this width drop a grid column rather than compress further.\n *\n * A literal rather than a token: this is a layout threshold for a grid cell, and\n * the dimension scale has no token for that. The 200px tokens it could borrow\n * (`menu-width-xs`, `form-width-xs`, `panel-width-2xs`) all name other\n * components, so binding to one would couple this grid to an unrelated contract.\n * Consumers override with the `metricMinWidth` prop.\n */\nconst DEFAULT_METRIC_MIN_WIDTH = '200px';\n\n/** Skeleton cells rendered while loading with no metrics supplied yet. */\nconst DEFAULT_LOADING_METRIC_COUNT = 5;\n\n/** Maximum number of comparable measures the summary bar presents at once. */\nconst MAX_METRIC_COUNT = 7;\n\nconst TREND_COLORS: Record<MetricTrend['tone'], TextColor> = {\n positive: 'var(--color-always-dark-foreground-positive)',\n negative: 'var(--color-always-dark-foreground-negative)',\n neutral: 'var(--color-always-dark-foreground-secondary)',\n};\n\nconst ALWAYS_DARK_PRIMARY: TextColor = 'var(--color-always-dark-foreground-primary)';\nconst ALWAYS_DARK_SECONDARY: TextColor = 'var(--color-always-dark-foreground-secondary)';\n\n@Component({\n tag: 'ds-card-overview',\n styleUrl: 'CardOverview.css',\n scoped: true,\n})\nexport class CardOverview {\n @Element() el!: HTMLElement;\n\n /** Full summary card, or the condensed 48px summary bar. */\n @Prop() variant: CardOverviewVariant = 'default';\n\n /** Intrinsic responsive layout, or a page-owned single-column stack. */\n @Prop() layout: CardOverviewLayout = 'auto';\n\n /** Optional nonselectable safety score rendered as the first grid cell. */\n @Prop() score: OverviewScore | undefined;\n\n /** Fixed current date or range. Replaced by content in the `period` slot. */\n @Prop() periodLabel: string = '';\n\n /** Copy between the current period and comparison control, usually `vs.`. */\n @Prop() comparisonLabel: string = '';\n\n /** Measures rendered in the responsive grid. Only the first seven are shown. */\n @Prop() metrics: OverviewMetric[] = [];\n\n /**\n * Width a metric cell may shrink to before the grid drops a column. The grid\n * uses this threshold while choosing an evenly distributed column count.\n */\n @Prop() metricMinWidth: string = DEFAULT_METRIC_MIN_WIDTH;\n\n /** Replace the score and metrics with skeletons while data resolves. */\n @Prop() isLoading: boolean = false;\n\n /** Message shown in place of the score when its figure cannot be resolved. */\n @Prop() scoreErrorMessage: string | undefined;\n\n /** Accessible name for the region. */\n @Prop() overviewLabel: string = 'Overview';\n\n /**\n * Page-controlled visual collapse from the full card (`0`) toward its 48px\n * compact handoff height (`1`). The component preserves its expanded flow\n * height, keeps the period bar stationary, and moves/clips the grid beneath\n * it. The page still owns sticky positioning and the final compact handoff.\n */\n @Prop() scrollCollapseProgress: number = 0;\n\n /** Emitted when a metric that is not inactive is activated. */\n @Event() dsMetricSelect!: EventEmitter<OverviewMetric>;\n\n /** Roving tab stop across selectable metrics. */\n @State() private focusedMetricIndex: number = 0;\n @State() private expandedHeight: number = 0;\n @State() private gridColumns: number | undefined;\n\n private layoutEl?: HTMLElement;\n private layoutResizeObserver: ResizeObserver | null = null;\n private layoutResizeFrame: number | undefined;\n /** The node the observer is bound to, which a later render can replace. */\n private observedLayoutEl?: HTMLElement;\n\n componentDidLoad() {\n this.observeLayout();\n }\n\n componentDidRender() {\n // Re-bind after HMR when DidLoad's observer was dropped, and whenever a\n // render swapped the layout node out from under the observer: the ref points\n // at the new node while the observer still watches the detached one, which\n // never resizes again. The grid would then keep its first measured column\n // count for the life of the component.\n if (!this.layoutResizeObserver || this.observedLayoutEl !== this.layoutEl) {\n this.observeLayout();\n } else {\n this.updateLayoutGeometry();\n }\n }\n\n disconnectedCallback() {\n this.layoutResizeObserver?.disconnect();\n this.layoutResizeObserver = null;\n if (this.layoutResizeFrame !== undefined) cancelAnimationFrame(this.layoutResizeFrame);\n this.layoutResizeFrame = undefined;\n this.observedLayoutEl = undefined;\n }\n\n private get visibleMetrics(): OverviewMetric[] {\n return this.metrics.slice(0, MAX_METRIC_COUNT);\n }\n\n private get visibleCellCount(): number {\n const metricCount = this.isLoading ? this.loadingMetricCount : this.visibleMetrics.length;\n return 1 + metricCount;\n }\n\n /** Preserve an authored data shape while loading, or use the standard six-cell grid. */\n private get loadingMetricCount(): number {\n return this.visibleMetrics.length || DEFAULT_LOADING_METRIC_COUNT;\n }\n\n private get resolvedScrollCollapseProgress(): number {\n if (this.variant === 'compact' || !Number.isFinite(this.scrollCollapseProgress)) return 0;\n return Math.min(1, Math.max(0, this.scrollCollapseProgress));\n }\n\n private get scrollCollapseGeometry() {\n const compactHeight = resolveCssLengthPx(TOKEN_DEFAULTS.size600, 48);\n return resolveCardOverviewCollapseGeometry({\n variant: this.variant,\n progress: this.resolvedScrollCollapseProgress,\n expandedHeight: this.expandedHeight,\n compactHeight,\n });\n }\n\n private observeLayout() {\n const layout = this.layoutEl;\n if (!layout) return;\n\n const bounds = layout.getBoundingClientRect();\n this.updateLayoutGeometry(bounds.width, bounds.height);\n if (typeof ResizeObserver === 'undefined') return;\n\n this.layoutResizeObserver?.disconnect();\n this.layoutResizeObserver = new ResizeObserver(entries => {\n const rect = entries[0]?.contentRect;\n if (!rect) return;\n if (this.layoutResizeFrame !== undefined) cancelAnimationFrame(this.layoutResizeFrame);\n this.layoutResizeFrame = requestAnimationFrame(() => {\n this.layoutResizeFrame = undefined;\n this.updateLayoutGeometry(rect.width, rect.height);\n });\n });\n this.layoutResizeObserver.observe(layout);\n this.observedLayoutEl = layout;\n }\n\n private updateLayoutGeometry(width?: number, height?: number) {\n const layout = this.layoutEl;\n if (!layout) return;\n\n const bounds =\n width === undefined || height === undefined ? layout.getBoundingClientRect() : null;\n const resolvedWidth = width ?? bounds?.width ?? 0;\n const resolvedHeight = height ?? bounds?.height ?? 0;\n\n let changed = false;\n\n if (resolvedHeight > 0 && Math.abs(resolvedHeight - this.expandedHeight) >= 0.5) {\n this.expandedHeight = resolvedHeight;\n changed = true;\n }\n\n const columns = resolveOverviewGridColumns({\n cellCount: this.visibleCellCount,\n availableWidth: resolvedWidth,\n minCellWidth: resolveCssLengthPx(this.metricMinWidth, DEFAULT_METRIC_MIN_WIDTH),\n });\n if (columns !== this.gridColumns) {\n this.gridColumns = columns;\n changed = true;\n }\n\n // The column count is written straight to the host rather than through the\n // rendered style object. Measurement happens in `componentDidLoad` and in the\n // ResizeObserver callback, both outside a render cycle, and the host's style\n // is not patched again after the first render — so a rendered value would stay\n // pinned to whatever the first paint produced, before any width was known.\n if (this.gridColumns) {\n this.el.style.setProperty('--ds-card-overview-grid-columns', String(this.gridColumns));\n } else {\n this.el.style.removeProperty('--ds-card-overview-grid-columns');\n }\n\n if (changed) forceUpdate(this);\n }\n\n /** Index of the roving tab stop, skipping inactive metrics. */\n private get rovingIndex(): number {\n return resolveOverviewRovingIndex(this.visibleMetrics, this.focusedMetricIndex);\n }\n\n private selectMetric(metric: OverviewMetric, index: number) {\n if (metric.isInactive) return;\n this.focusedMetricIndex = index;\n this.dsMetricSelect.emit(metric);\n }\n\n /** Move the roving tab stop, wrapping and skipping inactive metrics. */\n private moveFocus(from: number, step: number) {\n const metrics = this.visibleMetrics;\n const next = findNextOverviewMetricIndex(metrics, from, step < 0 ? -1 : 1);\n if (next < 0) return;\n\n this.focusedMetricIndex = next;\n const cells = this.el.querySelectorAll<HTMLElement>('.card-overview__metric-action');\n cells[next]?.focus();\n }\n\n private handleMetricKeyDown = (event: KeyboardEvent, index: number) => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n this.moveFocus(index, 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n this.moveFocus(index, -1);\n break;\n default:\n break;\n }\n };\n\n /**\n * A skeleton bar matching the type metrics of the text it stands in for, so the\n * loading state holds the same shape and rhythm as the resolved content.\n *\n * `background` is always passed so the base and shimmer follow the card's\n * permanently dark surface in both themes.\n */\n private bar(textVariant: TextVariant, width: string, className?: string) {\n return (\n <ds-skeleton\n class={className}\n variant=\"text\"\n textVariant={textVariant}\n width={width}\n background=\"always-dark\"\n />\n );\n }\n\n /**\n * Arrow and change in one text node.\n *\n * The arrow is a typeface glyph rather than an icon so it inherits the exact\n * size, weight, and baseline of the value beside it; an icon would need its\n * own size scale and would drift whenever the type scale changed.\n *\n * Tone is supplied by the caller and never inferred here.\n */\n private renderTrend(trend: MetricTrend | undefined, variant: TextVariant) {\n if (!trend) return null;\n return (\n <ds-text\n as=\"span\"\n class={{\n 'card-overview__trend': true,\n 'ds-control-label-box': true,\n [`card-overview__trend--${trend.tone}`]: true,\n }}\n variant={variant}\n color={TREND_COLORS[trend.tone]}\n fontFeature=\"tabular-nums\"\n >\n {trend.direction === 'up' ? '↑' : '↓'} {trend.value}\n </ds-text>\n );\n }\n\n private renderScore() {\n if (this.isLoading) {\n return (\n <div class=\"card-overview__score\" part=\"score\">\n <div class=\"card-overview__score-content\">\n <ds-score size=\"lg\" variant=\"dense\" isLoading></ds-score>\n <div class=\"card-overview__score-copy\">\n {this.bar('text-body-small', '64px', 'card-overview__score-label-spacer')}\n {this.bar('text-body-medium', '28px')}\n </div>\n </div>\n </div>\n );\n }\n\n const unavailableMessage =\n this.scoreErrorMessage || (!this.score ? 'Score unavailable' : undefined);\n if (unavailableMessage) {\n return (\n /* eslint-disable-next-line compomo/prefer-direct-ds-text -- The score track owns the grid divider and 8px outer inset. */\n <div class=\"card-overview__score\" part=\"score\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-content card-overview__score-content--error\"\n variant=\"text-body-medium\"\n color=\"inherit\"\n role=\"alert\"\n >\n {unavailableMessage}\n </ds-text>\n </div>\n );\n }\n\n const score = this.score;\n if (!score) return null;\n\n return (\n <div class=\"card-overview__score\" part=\"score\">\n <div class=\"card-overview__score-content\">\n <ds-score\n size=\"lg\"\n variant=\"dense\"\n value={score.value}\n level={score.level}\n label=\"Safety score\"\n />\n <div class=\"card-overview__score-copy\">\n <ds-text\n as=\"span\"\n class=\"card-overview__score-label-spacer ds-control-label-box\"\n variant=\"text-body-small\"\n color={ALWAYS_DARK_SECONDARY}\n aria-hidden=\"true\"\n >\n &nbsp;\n </ds-text>\n {this.renderTrend(score.trend, 'text-body-medium')}\n </div>\n </div>\n </div>\n );\n }\n\n private renderMetric(metric: OverviewMetric, index: number) {\n const selectable = !metric.isInactive;\n const label = (\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-label ds-control-label-box\"\n variant=\"text-body-small\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {metric.label}\n </ds-text>\n );\n\n return (\n <div class=\"card-overview__metric\" part=\"metric\">\n <div\n class={{\n 'card-overview__metric-action': true,\n 'card-overview__metric-action--inactive': !selectable,\n // Selection targets get the shared wash; press scaling is not used here\n // so the grid keeps its columns aligned. See docs/control-press-policy.md.\n 'ds-interaction-fill': selectable,\n 'ds-interaction-fill--on-always-dark': selectable,\n 'ds-focus-ring-inset': selectable,\n }}\n role={selectable ? 'button' : undefined}\n tabIndex={selectable && index === this.rovingIndex ? 0 : -1}\n onClick={() => this.selectMetric(metric, index)}\n onKeyDown={event => this.handleMetricKeyDown(event, index)}\n onFocusin={() => {\n if (selectable) this.focusedMetricIndex = index;\n }}\n >\n <div class=\"ds-interaction-fill__content card-overview__metric-content\">\n {metric.labelTooltip ? (\n <ds-tooltip label={metric.labelTooltip} side=\"top\" size=\"sm\">\n {label}\n </ds-tooltip>\n ) : (\n label\n )}\n <div class=\"card-overview__metric-figure\">\n <ds-text\n as=\"span\"\n class=\"card-overview__metric-value ds-control-label-box\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_PRIMARY}\n fontFeature=\"tabular-nums\"\n >\n {metric.value}\n </ds-text>\n {this.renderTrend(metric.trend, 'text-body-medium')}\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n private renderMetrics() {\n if (this.isLoading) {\n return [\n this.renderScore(),\n ...Array.from({ length: this.loadingMetricCount }, (_, index) => (\n <div class=\"card-overview__metric\" key={`loading-${index}`}>\n <div class=\"card-overview__metric-action card-overview__metric-action--inactive\">\n <div class=\"card-overview__metric-content\">\n {this.bar('text-body-small', '35%', 'card-overview__metric-label')}\n <div class=\"card-overview__metric-figure\">\n {this.bar('text-body-medium', '40px')}\n {/* Match renderTrend's type canvas and the score trend's 28px width. */}\n {this.bar('text-body-medium', '28px')}\n </div>\n </div>\n </div>\n </div>\n )),\n ];\n }\n\n return [\n this.renderScore(),\n ...this.visibleMetrics.map((metric, index) => this.renderMetric(metric, index)),\n ];\n }\n\n render() {\n const compact = this.variant === 'compact';\n const collapse = this.scrollCollapseGeometry;\n\n // Omit absent custom properties rather than handing them an undefined value.\n // Stencil serialises undefined to the string \"undefined\", which is invalid in\n // `repeat()` and voids the whole grid-template-columns declaration — the grid\n // then collapses to one implicit column instead of using the `auto-fit`\n // fallback baked into the stylesheet.\n const customProperties: Record<string, string> = {\n '--ds-card-overview-metric-min': this.metricMinWidth,\n };\n if (this.gridColumns) {\n customProperties['--ds-card-overview-grid-columns'] = String(this.gridColumns);\n }\n if (collapse.active) {\n customProperties['--ds-card-overview-expanded-height'] = `${collapse.expandedHeight}px`;\n customProperties['--ds-card-overview-visible-height'] = `${collapse.visibleHeight}px`;\n customProperties['--ds-card-overview-content-offset'] = `${collapse.offset}px`;\n }\n\n return (\n <Host\n class={{\n 'card-overview': true,\n 'card-overview--compact': compact,\n 'card-overview--stacked': !compact && this.layout === 'stacked',\n 'card-overview--scroll-collapsing': collapse.active,\n }}\n role=\"region\"\n aria-label={this.overviewLabel}\n aria-busy={this.isLoading ? 'true' : undefined}\n style={customProperties}\n >\n <div\n class={{\n 'card-overview__surface': true,\n // Split shadow and inset highlight, so the opaque surface cannot cover\n // the highlight the way a combined elevation shadow would.\n 'ds-control-elevation': true,\n 'ds-control-elevation--sm': !compact,\n 'ds-control-elevation--floating': compact,\n }}\n >\n <div class=\"card-overview__clip\">\n <div\n ref={element => {\n this.layoutEl = (element as HTMLElement) ?? undefined;\n }}\n class={{\n 'card-overview__layout': true,\n }}\n >\n <div class=\"card-overview__summary\">\n <div class=\"card-overview__header ds-control--md\">\n <div class=\"card-overview__period\">\n <div class=\"card-overview__period-current\">\n <slot name=\"period\">\n {this.periodLabel && (\n /* eslint-disable-next-line compomo/prefer-direct-ds-text -- Fixed copy uses the same structural frame and label inset as a slotted Select. */\n <div class=\"card-overview__period-fixed ds-control-frame\">\n <ds-text\n as=\"span\"\n class=\"ds-control-label-box\"\n variant=\"text-body-medium\"\n emphasis\n color={ALWAYS_DARK_PRIMARY}\n >\n {this.periodLabel}\n </ds-text>\n </div>\n )}\n </slot>\n </div>\n {this.comparisonLabel && (\n <ds-text\n as=\"span\"\n class=\"card-overview__period-comparison ds-control-label-box\"\n variant=\"text-body-medium\"\n color={ALWAYS_DARK_SECONDARY}\n >\n {this.comparisonLabel}\n </ds-text>\n )}\n </div>\n <div class=\"card-overview__filter\">\n <slot name=\"filter\" />\n </div>\n </div>\n </div>\n\n {!compact && (\n <div class=\"card-overview__body\">\n <div class=\"card-overview__metrics\" part=\"metrics\">\n {this.renderMetrics()}\n </div>\n\n <slot name=\"footer\" />\n </div>\n )}\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
380
405
  "type": "registry:ui"
381
406
  },
382
407
  {
@@ -95,7 +95,40 @@
95
95
  "label": "Cross-browser shell and action coverage",
96
96
  "path": "tests/e2e/card-setting.spec.ts"
97
97
  }
98
- ]
98
+ ],
99
+ "styling": {
100
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
101
+ "protected": [
102
+ {
103
+ "properties": [
104
+ "background",
105
+ "border",
106
+ "box-shadow",
107
+ "color"
108
+ ],
109
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
110
+ "props": []
111
+ },
112
+ {
113
+ "properties": [
114
+ "border-radius"
115
+ ],
116
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
117
+ "props": []
118
+ },
119
+ {
120
+ "properties": [
121
+ "width",
122
+ "min-height"
123
+ ],
124
+ "reason": "Card width selects the component geometry; use cardWidth and let the parent constrain available space.",
125
+ "props": [
126
+ "cardWidth"
127
+ ]
128
+ }
129
+ ],
130
+ "customProperties": []
131
+ }
99
132
  },
100
133
  "api": {
101
134
  "props": {
@@ -255,7 +288,7 @@
255
288
  "peerDependencies": {
256
289
  "required": [
257
290
  "@ds-mo/tokens ^6.5.1",
258
- "@ds-mo/icons ^7.0.0"
291
+ "@ds-mo/icons ^8.1.0"
259
292
  ],
260
293
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
261
294
  }
@@ -278,7 +311,7 @@
278
311
  },
279
312
  {
280
313
  "path": "src/wc/components/CardSetting/CardSetting.tsx",
281
- "content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\n\nexport type CardSettingWidth = 'sm' | 'md' | 'lg';\nexport type CardSettingAction = 'edit' | 'save' | 'cancel';\n\nexport interface CardSettingActionDetail {\n action: CardSettingAction;\n originalEvent: MouseEvent;\n}\n\nconst CARD_WIDTH_VARS: Record<CardSettingWidth, string> = {\n sm: 'var(--dimension-card-width-sm)',\n md: 'var(--dimension-card-width-md)',\n lg: 'var(--dimension-card-width-lg)',\n};\n\nconst CARD_HEIGHT_VARS: Record<CardSettingWidth, string> = {\n sm: 'var(--dimension-card-height-sm)',\n md: 'var(--dimension-card-height-md)',\n lg: 'var(--dimension-card-height-lg)',\n};\n\nconst FAINT_BRAND_TITLE_COLOR = 'var(--color-foreground-faint-brand)';\n\n@Component({\n tag: 'ds-card-setting',\n styleUrl: 'CardSetting.css',\n scoped: true,\n})\nexport class CardSetting {\n /** Section heading shown in the card header. */\n @Prop() heading!: string;\n\n /**\n * Card width token (`sm` / `md` / `lg`). Also sets host `min-height` to the\n * matching `--dimension-card-height-*` so the body fills available space\n * even when the slot is empty.\n */\n @Prop() cardWidth: CardSettingWidth = 'md';\n\n /** Controlled edit state — parent owns single-edit orchestration. */\n @Prop() editing = false;\n @Prop() editLabel: string = 'Edit';\n @Prop() cancelLabel: string = 'Cancel';\n @Prop() saveLabel: string = 'Save';\n\n /** Emits a controlled edit, save, or cancel request. */\n @Event() dsAction!: EventEmitter<CardSettingActionDetail>;\n\n private emitAction(action: CardSettingAction, originalEvent: MouseEvent) {\n this.dsAction.emit({ action, originalEvent });\n }\n\n render() {\n const editing = this.editing;\n\n return (\n <Host\n class={{\n 'card-setting': true,\n 'card-setting--editing': editing,\n }}\n style={{\n '--_card-setting-width': CARD_WIDTH_VARS[this.cardWidth],\n '--_card-setting-min-height': CARD_HEIGHT_VARS[this.cardWidth],\n }}\n >\n <header class=\"card-setting__header ds-chrome-header\">\n {/* eslint-disable-next-line local/prefer-direct-ds-text -- Shared header copy owns the control-density geometry around the semantic heading. */}\n <div class=\"card-setting__copy ds-chrome-header__copy ds-control--md\">\n <ds-text\n class=\"card-setting__title ds-chrome-header__heading\"\n variant=\"text-title-small\"\n emphasis\n color={editing ? FAINT_BRAND_TITLE_COLOR : 'primary'}\n as=\"h2\"\n >\n {this.heading}\n </ds-text>\n </div>\n <div class=\"card-setting__actions ds-chrome-header__trailing\">\n {!editing ? (\n <ds-button-unfilled\n variant=\"icon\"\n type=\"button\"\n icon=\"Pencil\"\n aria-label={this.editLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('edit', event.detail)\n }\n />\n ) : (\n [\n <ds-button-unfilled\n key=\"cancel\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Cross\"\n background=\"bold\"\n aria-label={this.cancelLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('cancel', event.detail)\n }\n />,\n <ds-button-filled\n key=\"save\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Check\"\n intent=\"brand\"\n contrast=\"faint\"\n aria-label={this.saveLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('save', event.detail)\n }\n />,\n ]\n )}\n </div>\n </header>\n <div class=\"card-setting__body\">\n <slot />\n </div>\n </Host>\n );\n }\n}\n",
314
+ "content": "import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\n\nexport type CardSettingWidth = 'sm' | 'md' | 'lg';\nexport type CardSettingAction = 'edit' | 'save' | 'cancel';\n\nexport interface CardSettingActionDetail {\n action: CardSettingAction;\n originalEvent: MouseEvent;\n}\n\nconst CARD_WIDTH_VARS: Record<CardSettingWidth, string> = {\n sm: 'var(--dimension-card-width-sm)',\n md: 'var(--dimension-card-width-md)',\n lg: 'var(--dimension-card-width-lg)',\n};\n\nconst CARD_HEIGHT_VARS: Record<CardSettingWidth, string> = {\n sm: 'var(--dimension-card-height-sm)',\n md: 'var(--dimension-card-height-md)',\n lg: 'var(--dimension-card-height-lg)',\n};\n\nconst FAINT_BRAND_TITLE_COLOR = 'var(--color-foreground-faint-brand)';\n\n@Component({\n tag: 'ds-card-setting',\n styleUrl: 'CardSetting.css',\n scoped: true,\n})\nexport class CardSetting {\n /** Section heading shown in the card header. */\n @Prop() heading!: string;\n\n /**\n * Card width token (`sm` / `md` / `lg`). Also sets host `min-height` to the\n * matching `--dimension-card-height-*` so the body fills available space\n * even when the slot is empty.\n */\n @Prop() cardWidth: CardSettingWidth = 'md';\n\n /** Controlled edit state — parent owns single-edit orchestration. */\n @Prop() editing = false;\n @Prop() editLabel: string = 'Edit';\n @Prop() cancelLabel: string = 'Cancel';\n @Prop() saveLabel: string = 'Save';\n\n /** Emits a controlled edit, save, or cancel request. */\n @Event() dsAction!: EventEmitter<CardSettingActionDetail>;\n\n private emitAction(action: CardSettingAction, originalEvent: MouseEvent) {\n this.dsAction.emit({ action, originalEvent });\n }\n\n render() {\n const editing = this.editing;\n\n return (\n <Host\n class={{\n 'card-setting': true,\n 'card-setting--editing': editing,\n }}\n style={{\n '--_card-setting-width': CARD_WIDTH_VARS[this.cardWidth],\n '--_card-setting-min-height': CARD_HEIGHT_VARS[this.cardWidth],\n }}\n >\n <header class=\"card-setting__header ds-chrome-header\">\n {/* eslint-disable-next-line compomo/prefer-direct-ds-text -- Shared header copy owns the control-density geometry around the semantic heading. */}\n <div class=\"card-setting__copy ds-chrome-header__copy ds-control--md\">\n <ds-text\n class=\"card-setting__title ds-chrome-header__heading\"\n variant=\"text-title-small\"\n emphasis\n color={editing ? FAINT_BRAND_TITLE_COLOR : 'primary'}\n as=\"h2\"\n >\n {this.heading}\n </ds-text>\n </div>\n <div class=\"card-setting__actions ds-chrome-header__trailing\">\n {!editing ? (\n <ds-button-unfilled\n variant=\"icon\"\n type=\"button\"\n icon=\"Pencil\"\n aria-label={this.editLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('edit', event.detail)\n }\n />\n ) : (\n [\n <ds-button-unfilled\n key=\"cancel\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Cross\"\n background=\"bold\"\n aria-label={this.cancelLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('cancel', event.detail)\n }\n />,\n <ds-button-filled\n key=\"save\"\n variant=\"icon\"\n type=\"button\"\n icon=\"Check\"\n intent=\"brand\"\n contrast=\"faint\"\n aria-label={this.saveLabel}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.emitAction('save', event.detail)\n }\n />,\n ]\n )}\n </div>\n </header>\n <div class=\"card-setting__body\">\n <slot />\n </div>\n </Host>\n );\n }\n}\n",
282
315
  "type": "registry:ui"
283
316
  }
284
317
  ]
@@ -50,7 +50,30 @@
50
50
  "label": "Storybook examples",
51
51
  "path": "src/wc/components/CardSettingsScope/CardSettingsScope.stories.ts"
52
52
  }
53
- ]
53
+ ],
54
+ "styling": {
55
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
56
+ "protected": [
57
+ {
58
+ "properties": [
59
+ "background",
60
+ "border",
61
+ "box-shadow",
62
+ "color"
63
+ ],
64
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
65
+ "props": []
66
+ },
67
+ {
68
+ "properties": [
69
+ "border-radius"
70
+ ],
71
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
72
+ "props": []
73
+ }
74
+ ],
75
+ "customProperties": []
76
+ }
54
77
  },
55
78
  "api": {
56
79
  "props": {
@@ -304,7 +327,7 @@
304
327
  "peerDependencies": {
305
328
  "required": [
306
329
  "@ds-mo/tokens ^6.5.1",
307
- "@ds-mo/icons ^7.0.0"
330
+ "@ds-mo/icons ^8.1.0"
308
331
  ],
309
332
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
310
333
  }
@@ -78,7 +78,12 @@
78
78
  "label": "Rendered interaction tests",
79
79
  "path": "tests/e2e/chart-legend.spec.ts"
80
80
  }
81
- ]
81
+ ],
82
+ "styling": {
83
+ "rationale": "Legend content owns internal rows; application layout may constrain and position its host.",
84
+ "protected": [],
85
+ "customProperties": []
86
+ }
82
87
  },
83
88
  "api": {
84
89
  "props": {
@@ -273,7 +278,7 @@
273
278
  "peerDependencies": {
274
279
  "required": [
275
280
  "@ds-mo/tokens ^6.5.1",
276
- "@ds-mo/icons ^7.0.0"
281
+ "@ds-mo/icons ^8.1.0"
277
282
  ],
278
283
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
279
284
  }
@@ -64,7 +64,12 @@
64
64
  "label": "Chart guide",
65
65
  "path": "src/docs/Charts.mdx"
66
66
  }
67
- ]
67
+ ],
68
+ "styling": {
69
+ "rationale": "The renderer observes the host; application-supplied dimensions and chart definition remain authoritative.",
70
+ "protected": [],
71
+ "customProperties": []
72
+ }
68
73
  },
69
74
  "api": {
70
75
  "props": {
@@ -235,7 +240,7 @@
235
240
  "peerDependencies": {
236
241
  "required": [
237
242
  "@ds-mo/tokens ^6.5.1",
238
- "@ds-mo/icons ^7.0.0"
243
+ "@ds-mo/icons ^8.1.0"
239
244
  ],
240
245
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
241
246
  }
@@ -256,7 +261,7 @@
256
261
  },
257
262
  {
258
263
  "path": "src/wc/components/Chart/Chart.tsx",
259
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n Prop,\n State,\n Watch,\n h,\n Host,\n} from '@stencil/core';\nimport type {\n ChartDefinition,\n ChartPoint,\n ChartTooltipItem,\n ChartTooltipOptions,\n} from '../../utils/chart-grammar';\nimport {\n compileChartScene,\n findChartFocus,\n type ChartScene,\n type ChartSceneNode,\n} from '../../utils/chart-scene';\nimport { resolveChartTheme } from '../../utils/chart-theme';\n\nconst DEFAULT_WIDTH = 480;\nconst DEFAULT_HEIGHT = 320;\n\nexport type ChartFocusSource = 'pointer' | 'keyboard';\n\nexport interface ChartFocusChangeDetail {\n primary: ChartPoint | null;\n points: readonly ChartPoint[];\n source: ChartFocusSource;\n}\n\ninterface FocusState {\n primary: ChartPoint;\n points: ChartPoint[];\n source: ChartFocusSource;\n}\n\ninterface PointerAnchor {\n x: number;\n y: number;\n}\n\n@Component({\n tag: 'ds-chart',\n styleUrl: 'Chart.css',\n scoped: true,\n})\nexport class Chart {\n @Element() el!: HTMLElement;\n\n /** Typed chart definition. Set as a JavaScript property, not an HTML attribute. */\n @Prop() definition!: ChartDefinition;\n /** Required accessible name for the visualization. */\n @Prop() label!: string;\n /** Optional longer accessible description. */\n @Prop() description?: string;\n /** Locale used by axes and default tooltip formatters. */\n @Prop() locale?: string;\n /** Fixed surface width. Otherwise the chart observes its container. */\n @Prop() width?: number;\n /** Product-owned surface height. Wins over aspectRatio. */\n @Prop() height?: number;\n /** Derives height from the current width when height is not supplied. */\n @Prop() aspectRatio?: number;\n\n /** Fires whenever pointer or keyboard focus resolves to semantic chart points. */\n @Event() dsChartFocusChange!: EventEmitter<ChartFocusChangeDetail>;\n\n @State() private surfaceWidth = DEFAULT_WIDTH;\n @State() private containerHeight?: number;\n @State() private scene?: ChartScene;\n @State() private focusState?: FocusState;\n @State() private pointerAnchor?: PointerAnchor;\n\n private resizeObserver?: ResizeObserver;\n private resizeFrame?: number;\n private compileFrame?: number;\n private warnedConflictingHeight = false;\n private measuredLabels = new Map<string, { width: number; height: number }>();\n private descriptionId = `ds-chart-description-${Math.random().toString(36).slice(2)}`;\n private clipId = `ds-chart-clip-${Math.random().toString(36).slice(2)}`;\n\n componentWillLoad() {\n if (this.width && this.width > 0) this.surfaceWidth = this.width;\n this.compile();\n }\n\n componentDidLoad() {\n this.connectResizeObserver();\n this.waitForFonts();\n }\n\n componentDidRender() {\n this.measureRenderedLabels();\n }\n\n disconnectedCallback() {\n this.resizeObserver?.disconnect();\n if (this.resizeFrame) cancelAnimationFrame(this.resizeFrame);\n if (this.compileFrame) cancelAnimationFrame(this.compileFrame);\n }\n\n @Watch('definition')\n @Watch('locale')\n @Watch('height')\n @Watch('aspectRatio')\n onDefinitionInputChange() {\n this.scheduleCompile();\n }\n\n @Watch('width')\n onWidthChange() {\n if (this.width && this.width > 0) this.surfaceWidth = this.width;\n else this.measureHost();\n this.scheduleCompile();\n }\n\n private get surfaceHeight(): number {\n if (this.height && this.height > 0) {\n if (this.aspectRatio && !this.warnedConflictingHeight) {\n this.warnedConflictingHeight = true;\n console.warn('[ds-chart] height wins when both height and aspectRatio are supplied.');\n }\n return this.height;\n }\n if (this.aspectRatio && this.aspectRatio > 0) {\n return Math.max(1, this.surfaceWidth / this.aspectRatio);\n }\n return this.containerHeight ?? DEFAULT_HEIGHT;\n }\n\n private connectResizeObserver() {\n this.resizeObserver?.disconnect();\n if (typeof ResizeObserver === 'undefined') return;\n this.resizeObserver = new ResizeObserver(entries => {\n const entry = entries[0];\n if (!entry) return;\n if (this.resizeFrame) cancelAnimationFrame(this.resizeFrame);\n this.resizeFrame = requestAnimationFrame(() => {\n this.resizeFrame = undefined;\n this.applyMeasuredSize(entry.contentRect.width, entry.contentRect.height);\n });\n });\n this.resizeObserver.observe(this.el);\n this.measureHost();\n }\n\n private measureHost() {\n const bounds = this.el.getBoundingClientRect();\n this.applyMeasuredSize(bounds.width, bounds.height);\n }\n\n private applyMeasuredSize(width: number, height: number) {\n let changed = false;\n if (!this.width && width > 0) {\n const nextWidth = Math.round(width * 100) / 100;\n if (nextWidth !== this.surfaceWidth) {\n this.surfaceWidth = nextWidth;\n changed = true;\n }\n }\n if (!this.height && !this.aspectRatio && height > 0) {\n const nextHeight = Math.round(height * 100) / 100;\n if (nextHeight !== this.containerHeight) {\n this.containerHeight = nextHeight;\n changed = true;\n }\n }\n if (changed) {\n this.scheduleCompile();\n }\n }\n\n private scheduleCompile() {\n if (this.compileFrame) cancelAnimationFrame(this.compileFrame);\n this.compileFrame = requestAnimationFrame(() => {\n this.compileFrame = undefined;\n this.compile();\n });\n }\n\n private compile() {\n if (!this.definition || !(this.surfaceWidth > 0) || !(this.surfaceHeight > 0)) return;\n const previousKey = this.focusState?.primary.sceneKey;\n const scene = compileChartScene(\n this.definition,\n this.surfaceWidth,\n this.surfaceHeight,\n this.locale ?? document.documentElement.lang ?? 'en',\n text => this.measuredLabels.get(text) ?? { width: text.length * 7, height: 14 },\n resolveChartTheme(this.el)\n );\n this.scene = scene;\n if (previousKey) {\n const restored = scene.points.find(point => point.sceneKey === previousKey);\n if (restored && this.focusState) {\n const grouped = findChartFocus(\n scene.points,\n scene.focus,\n restored.x,\n restored.y,\n scene.maxFocusDistance,\n scene.arcHitRegions\n );\n this.focusState = grouped ? { ...grouped, source: this.focusState.source } : undefined;\n } else {\n this.focusState = undefined;\n }\n }\n }\n\n private waitForFonts() {\n if (!document.fonts) return;\n void document.fonts.ready.then(() => {\n this.measuredLabels.clear();\n this.scheduleCompile();\n });\n }\n\n private measureRenderedLabels() {\n const labels = this.el.querySelectorAll<SVGTextElement>('[data-chart-measure]');\n let changed = false;\n labels.forEach(label => {\n const text = label.textContent ?? '';\n let box: DOMRect;\n try {\n box = label.getBBox();\n } catch {\n return;\n }\n const previous = this.measuredLabels.get(text);\n if (\n !previous ||\n Math.abs(previous.width - box.width) > 0.5 ||\n Math.abs(previous.height - box.height) > 0.5\n ) {\n this.measuredLabels.set(text, { width: box.width, height: box.height });\n changed = true;\n }\n });\n if (changed) this.scheduleCompile();\n }\n\n private resolveFocus(x: number, y: number, source: ChartFocusSource) {\n if (!this.scene) return;\n const result = findChartFocus(\n this.scene.points,\n this.scene.focus,\n x,\n y,\n this.scene.maxFocusDistance,\n this.scene.arcHitRegions\n );\n if (!result) {\n this.clearFocus(source);\n return;\n }\n if (\n this.focusState?.primary.sceneKey === result.primary.sceneKey &&\n this.focusState.source === source\n )\n return;\n this.focusState = { ...result, source };\n this.dsChartFocusChange.emit({ ...result, source });\n }\n\n private clearFocus(source: ChartFocusSource) {\n if (!this.focusState) return;\n this.focusState = undefined;\n this.dsChartFocusChange.emit({ primary: null, points: [], source });\n }\n\n private onPointerMove = (event: PointerEvent) => {\n const svg = event.currentTarget as SVGSVGElement;\n const rect = svg.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n this.pointerAnchor = this.scene?.arcHitRegions.length ? { x, y } : undefined;\n this.resolveFocus(x, y, 'pointer');\n };\n\n private onPointerLeave = () => {\n this.pointerAnchor = undefined;\n this.clearFocus('pointer');\n };\n\n private keyboardPoints(): ChartPoint[] {\n if (!this.scene) return [];\n return [...this.scene.points].sort((a, b) => a.x - b.x || a.y - b.y);\n }\n\n private onKeyDown = (event: KeyboardEvent) => {\n const points = this.keyboardPoints();\n if (points.length === 0) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n this.clearFocus('keyboard');\n return;\n }\n const current = this.focusState\n ? points.findIndex(point => point.sceneKey === this.focusState?.primary.sceneKey)\n : -1;\n let next: number | undefined;\n if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = points.length - 1;\n else if (event.key === 'ArrowRight' || event.key === 'ArrowDown')\n next = Math.min(points.length - 1, current + 1);\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')\n next = Math.max(0, current < 0 ? 0 : current - 1);\n else return;\n event.preventDefault();\n const point = points[next ?? 0] ?? points[0];\n this.resolveFocus(point.x, point.y, 'keyboard');\n };\n\n private renderNode(node: ChartSceneNode, className = 'chart__mark') {\n const resolvedClassName = node.className ?? className;\n const presentation = {\n fill: node.style.fill,\n stroke: node.style.stroke,\n opacity: node.style.opacity,\n 'fill-opacity': node.style.fillOpacity,\n 'stroke-opacity': node.style.strokeOpacity,\n 'stroke-width': node.style.strokeWidth,\n 'stroke-dasharray': node.style.strokeDasharray || undefined,\n };\n if (node.type === 'path')\n return (\n <path\n key={node.key}\n class={resolvedClassName}\n d={node.d}\n transform={node.transform}\n {...presentation}\n />\n );\n if (node.type === 'rect') {\n return (\n <rect\n key={node.key}\n class={resolvedClassName}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={node.style.radius}\n {...presentation}\n />\n );\n }\n if (node.type === 'circle')\n return (\n <circle\n key={node.key}\n class={resolvedClassName}\n cx={node.x}\n cy={node.y}\n r={node.radius}\n {...presentation}\n />\n );\n if (node.type === 'line')\n return (\n <line\n key={node.key}\n class={resolvedClassName}\n x1={node.x1}\n x2={node.x2}\n y1={node.y1}\n y2={node.y2}\n {...presentation}\n />\n );\n return (\n <text\n key={node.key}\n class={node.className ?? `${className} chart__text-mark`}\n data-chart-measure={node.measure ? '' : undefined}\n x={node.x}\n y={node.y}\n dx={node.dx}\n dy={node.dy}\n text-anchor={node.style.textAnchor}\n font-family={node.style.fontFamily}\n font-size={node.style.fontSize}\n font-weight={node.style.fontWeight}\n dominant-baseline={node.dominantBaseline ?? 'middle'}\n transform={node.rotate ? `rotate(${node.rotate} ${node.x} ${node.y})` : undefined}\n {...presentation}\n >\n {node.text}\n </text>\n );\n }\n\n private renderMarkLayers(scene: ChartScene) {\n const layers: Array<{ clipped: boolean; nodes: ChartSceneNode[] }> = [];\n scene.nodes.forEach(node => {\n const clipped = scene.clip && node.clipToPlot !== false;\n const current = layers[layers.length - 1];\n if (current?.clipped === clipped) current.nodes.push(node);\n else layers.push({ clipped, nodes: [node] });\n });\n return layers.map((layer, index) => (\n <g\n key={`marks-${index}`}\n class=\"chart__marks\"\n aria-hidden=\"true\"\n clip-path={layer.clipped ? `url(#${this.clipId})` : undefined}\n >\n {layer.nodes.map(node => this.renderNode(node))}\n </g>\n ));\n }\n\n private focusedDotPoints(scene: ChartScene): ChartPoint[] {\n if (!this.focusState) return [];\n const focusDotPointKeys = new Set(scene.focusDotPointKeys);\n return this.focusState.points.filter(point => focusDotPointKeys.has(point.sceneKey));\n }\n\n private tooltipContent(): { heading?: string; items: ChartTooltipItem[] } | undefined {\n if (!this.scene || !this.focusState || this.scene.tooltip === false) return undefined;\n const options: ChartTooltipOptions =\n typeof this.scene.tooltip === 'object' ? this.scene.tooltip : {};\n const locale = this.locale ?? document.documentElement.lang ?? 'en';\n const points = this.focusState.points;\n const formatValue = (point: ChartPoint) => {\n const value = point.value ?? point.yValue;\n return (\n options.format?.(point, locale) ??\n (typeof value === 'number'\n ? new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value)\n : String(value))\n );\n };\n return {\n heading:\n points.length > 1\n ? (options.formatGroupHeading?.(points, locale) ?? String(this.focusState.primary.xValue))\n : undefined,\n items: points.map(\n point =>\n options.formatGroupItem?.(point, locale) ?? {\n label: point.groupLabel ?? point.markId,\n value: formatValue(point),\n color: point.color,\n }\n ),\n };\n }\n\n private statusText(): string {\n const content = this.tooltipContent();\n if (!content) return '';\n return [content.heading, ...content.items.map(item => `${item.label}: ${item.value}`)]\n .filter(Boolean)\n .join('. ');\n }\n\n render() {\n const scene = this.scene;\n const tooltip = this.tooltipContent();\n const focus = this.focusState?.primary;\n const tooltipAnchor =\n focus &&\n this.focusState?.source === 'pointer' &&\n scene?.arcHitRegions.length &&\n this.pointerAnchor\n ? this.pointerAnchor\n : focus;\n const focusedDotPoints = scene ? this.focusedDotPoints(scene) : [];\n const groupedPointFocus =\n scene?.coordinate === 'cartesian' && scene.focus === 'group-x' && focusedDotPoints.length > 1;\n const center =\n focus && scene?.center?.focused\n ? scene.center.focused(focus, this.locale ?? document.documentElement.lang ?? 'en')\n : scene?.center;\n return (\n <Host\n class=\"chart\"\n style={{\n '--ds-chart-width': this.width ? `${this.width}px` : '100%',\n '--ds-chart-height':\n this.height || this.aspectRatio\n ? `${this.surfaceHeight}px`\n : 'var(--ds-chart-container-height, 320px)',\n }}\n >\n {this.description && (\n <span id={this.descriptionId} class=\"chart__description\">\n {this.description}\n </span>\n )}\n {scene && (\n /* eslint-disable-next-line local/prefer-ds-icon -- SVG is the chart renderer. */\n <svg\n class=\"chart__surface\"\n width={scene.width}\n height={scene.height}\n viewBox={`0 0 ${scene.width} ${scene.height}`}\n role=\"img\"\n aria-label={this.label || 'Chart'}\n aria-describedby={this.description ? this.descriptionId : undefined}\n tabindex={0}\n onPointerMove={this.onPointerMove}\n onPointerLeave={this.onPointerLeave}\n onKeyDown={this.onKeyDown}\n >\n <defs aria-hidden=\"true\">\n <clipPath id={this.clipId}>\n <rect\n x={scene.plot.left}\n y={scene.plot.top}\n width={scene.plot.width}\n height={scene.plot.height}\n />\n </clipPath>\n </defs>\n <rect\n class=\"chart__interaction-surface\"\n x=\"0\"\n y=\"0\"\n width={scene.width}\n height={scene.height}\n aria-hidden=\"true\"\n />\n <g class=\"chart__guides\" aria-hidden=\"true\">\n {scene.guides.map(node => this.renderNode(node, 'chart__grid chart__polar-guide'))}\n {scene.yAxis.line && (\n <line\n class=\"chart__axis-line\"\n x1={scene.plot.left}\n x2={scene.plot.left}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n />\n )}\n {scene.yAxis.ticks.map(tick => (\n <g key={`y-${tick.key}`}>\n {scene.yAxis.grid && (\n <line\n class=\"chart__grid\"\n x1={scene.plot.left}\n x2={scene.plot.right}\n y1={tick.position}\n y2={tick.position}\n />\n )}\n <line\n class=\"chart__axis-line chart__tick-stub\"\n x1={scene.plot.left - scene.yAxis.tickSize}\n x2={scene.plot.left}\n y1={tick.position}\n y2={tick.position}\n />\n {tick.labelVisible && (\n <text\n data-chart-measure\n class=\"chart__tick chart__tick--y\"\n x={scene.plot.left - scene.yAxis.tickSize - scene.yAxis.tickPadding}\n y={tick.position}\n text-anchor=\"end\"\n dominant-baseline=\"middle\"\n >\n {tick.label}\n </text>\n )}\n </g>\n ))}\n {scene.xAxis.line && (\n <line\n class=\"chart__axis-line\"\n x1={scene.plot.left}\n x2={scene.plot.right}\n y1={scene.plot.bottom}\n y2={scene.plot.bottom}\n />\n )}\n {scene.xAxis.ticks.map(tick => (\n <g key={`x-${tick.key}`}>\n {scene.xAxis.grid && (\n <line\n class=\"chart__grid\"\n x1={tick.position}\n x2={tick.position}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n />\n )}\n <line\n class=\"chart__axis-line chart__tick-stub\"\n x1={tick.position}\n x2={tick.position}\n y1={scene.plot.bottom}\n y2={scene.plot.bottom + scene.xAxis.tickSize}\n />\n {tick.labelVisible && (\n <text\n data-chart-measure\n class=\"chart__tick chart__tick--x\"\n x={tick.position}\n y={scene.plot.bottom + scene.xAxis.tickSize + scene.xAxis.tickPadding}\n text-anchor=\"middle\"\n dominant-baseline=\"hanging\"\n transform={\n scene.xAxis.rotate\n ? `rotate(${scene.xAxis.rotate} ${tick.position} ${scene.plot.bottom + scene.xAxis.tickSize + scene.xAxis.tickPadding})`\n : undefined\n }\n >\n {tick.label}\n </text>\n )}\n </g>\n ))}\n {scene.xAxis.label && (\n <text\n class=\"chart__axis-title\"\n x={(scene.plot.left + scene.plot.right) / 2}\n y={scene.height - 4}\n text-anchor=\"middle\"\n >\n {scene.xAxis.label}\n </text>\n )}\n {scene.yAxis.label && (\n <text\n class=\"chart__axis-title\"\n x=\"8\"\n y={(scene.plot.top + scene.plot.bottom) / 2}\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n transform={`rotate(-90 8 ${(scene.plot.top + scene.plot.bottom) / 2})`}\n >\n {scene.yAxis.label}\n </text>\n )}\n {scene.coordinate === 'cartesian' && (\n <g class=\"chart__plot-frame\">\n <line\n class=\"chart__plot-boundary chart__plot-boundary--top\"\n x1={scene.plot.left}\n x2={scene.plot.right}\n y1={scene.plot.top}\n y2={scene.plot.top}\n />\n <line\n class=\"chart__plot-boundary chart__plot-boundary--right\"\n x1={scene.plot.right}\n x2={scene.plot.right}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n />\n </g>\n )}\n </g>\n {this.renderMarkLayers(scene)}\n {groupedPointFocus && focus && (\n <line\n class=\"chart__focus-guide\"\n x1={focus.x}\n x2={focus.x}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n aria-hidden=\"true\"\n />\n )}\n {focusedDotPoints.map(point => (\n <circle\n key={`focus-${point.sceneKey}`}\n class=\"chart__focus-point\"\n cx={point.x}\n cy={point.y}\n r={scene.focusDotCircleRadius}\n fill={point.color}\n stroke=\"var(--color-background-primary)\"\n stroke-width={scene.focusDotHaloWidth}\n aria-hidden=\"true\"\n />\n ))}\n {focus && focusedDotPoints.length === 0 && (\n <circle class=\"chart__focus\" cx={focus.x} cy={focus.y} r=\"6\" aria-hidden=\"true\" />\n )}\n {center?.value && (\n <text\n class=\"chart__center-value\"\n x={(scene.plot.left + scene.plot.right) / 2}\n y={(scene.plot.top + scene.plot.bottom) / 2 - (center.caption ? 10 : 0)}\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n aria-hidden=\"true\"\n >\n {center.value}\n </text>\n )}\n {center?.caption && (\n <text\n class=\"chart__center-caption\"\n x={(scene.plot.left + scene.plot.right) / 2}\n y={(scene.plot.top + scene.plot.bottom) / 2 + 14}\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n aria-hidden=\"true\"\n >\n {center.caption}\n </text>\n )}\n </svg>\n )}\n {tooltip && tooltipAnchor && (\n <ds-tooltip-chart\n heading={tooltip.heading}\n items={tooltip.items}\n x={tooltipAnchor.x}\n y={tooltipAnchor.y}\n />\n )}\n <span class=\"chart__status\" aria-live=\"polite\" aria-atomic=\"true\">\n {this.statusText()}\n </span>\n </Host>\n );\n }\n}\n",
264
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n Prop,\n State,\n Watch,\n h,\n Host,\n} from '@stencil/core';\nimport type {\n ChartDefinition,\n ChartPoint,\n ChartTooltipItem,\n ChartTooltipOptions,\n} from '../../utils/chart-grammar';\nimport {\n compileChartScene,\n findChartFocus,\n type ChartScene,\n type ChartSceneNode,\n} from '../../utils/chart-scene';\nimport { resolveChartTheme } from '../../utils/chart-theme';\n\nconst DEFAULT_WIDTH = 480;\nconst DEFAULT_HEIGHT = 320;\n\nexport type ChartFocusSource = 'pointer' | 'keyboard';\n\nexport interface ChartFocusChangeDetail {\n primary: ChartPoint | null;\n points: readonly ChartPoint[];\n source: ChartFocusSource;\n}\n\ninterface FocusState {\n primary: ChartPoint;\n points: ChartPoint[];\n source: ChartFocusSource;\n}\n\ninterface PointerAnchor {\n x: number;\n y: number;\n}\n\n@Component({\n tag: 'ds-chart',\n styleUrl: 'Chart.css',\n scoped: true,\n})\nexport class Chart {\n @Element() el!: HTMLElement;\n\n /** Typed chart definition. Set as a JavaScript property, not an HTML attribute. */\n @Prop() definition!: ChartDefinition;\n /** Required accessible name for the visualization. */\n @Prop() label!: string;\n /** Optional longer accessible description. */\n @Prop() description?: string;\n /** Locale used by axes and default tooltip formatters. */\n @Prop() locale?: string;\n /** Fixed surface width. Otherwise the chart observes its container. */\n @Prop() width?: number;\n /** Product-owned surface height. Wins over aspectRatio. */\n @Prop() height?: number;\n /** Derives height from the current width when height is not supplied. */\n @Prop() aspectRatio?: number;\n\n /** Fires whenever pointer or keyboard focus resolves to semantic chart points. */\n @Event() dsChartFocusChange!: EventEmitter<ChartFocusChangeDetail>;\n\n @State() private surfaceWidth = DEFAULT_WIDTH;\n @State() private containerHeight?: number;\n @State() private scene?: ChartScene;\n @State() private focusState?: FocusState;\n @State() private pointerAnchor?: PointerAnchor;\n\n private resizeObserver?: ResizeObserver;\n private resizeFrame?: number;\n private compileFrame?: number;\n private warnedConflictingHeight = false;\n private measuredLabels = new Map<string, { width: number; height: number }>();\n private descriptionId = `ds-chart-description-${Math.random().toString(36).slice(2)}`;\n private clipId = `ds-chart-clip-${Math.random().toString(36).slice(2)}`;\n\n componentWillLoad() {\n if (this.width && this.width > 0) this.surfaceWidth = this.width;\n this.compile();\n }\n\n componentDidLoad() {\n this.connectResizeObserver();\n this.waitForFonts();\n }\n\n componentDidRender() {\n this.measureRenderedLabels();\n }\n\n disconnectedCallback() {\n this.resizeObserver?.disconnect();\n if (this.resizeFrame) cancelAnimationFrame(this.resizeFrame);\n if (this.compileFrame) cancelAnimationFrame(this.compileFrame);\n }\n\n @Watch('definition')\n @Watch('locale')\n @Watch('height')\n @Watch('aspectRatio')\n onDefinitionInputChange() {\n this.scheduleCompile();\n }\n\n @Watch('width')\n onWidthChange() {\n if (this.width && this.width > 0) this.surfaceWidth = this.width;\n else this.measureHost();\n this.scheduleCompile();\n }\n\n private get surfaceHeight(): number {\n if (this.height && this.height > 0) {\n if (this.aspectRatio && !this.warnedConflictingHeight) {\n this.warnedConflictingHeight = true;\n console.warn('[ds-chart] height wins when both height and aspectRatio are supplied.');\n }\n return this.height;\n }\n if (this.aspectRatio && this.aspectRatio > 0) {\n return Math.max(1, this.surfaceWidth / this.aspectRatio);\n }\n return this.containerHeight ?? DEFAULT_HEIGHT;\n }\n\n private connectResizeObserver() {\n this.resizeObserver?.disconnect();\n if (typeof ResizeObserver === 'undefined') return;\n this.resizeObserver = new ResizeObserver(entries => {\n const entry = entries[0];\n if (!entry) return;\n if (this.resizeFrame) cancelAnimationFrame(this.resizeFrame);\n this.resizeFrame = requestAnimationFrame(() => {\n this.resizeFrame = undefined;\n this.applyMeasuredSize(entry.contentRect.width, entry.contentRect.height);\n });\n });\n this.resizeObserver.observe(this.el);\n this.measureHost();\n }\n\n private measureHost() {\n const bounds = this.el.getBoundingClientRect();\n this.applyMeasuredSize(bounds.width, bounds.height);\n }\n\n private applyMeasuredSize(width: number, height: number) {\n let changed = false;\n if (!this.width && width > 0) {\n const nextWidth = Math.round(width * 100) / 100;\n if (nextWidth !== this.surfaceWidth) {\n this.surfaceWidth = nextWidth;\n changed = true;\n }\n }\n if (!this.height && !this.aspectRatio && height > 0) {\n const nextHeight = Math.round(height * 100) / 100;\n if (nextHeight !== this.containerHeight) {\n this.containerHeight = nextHeight;\n changed = true;\n }\n }\n if (changed) {\n this.scheduleCompile();\n }\n }\n\n private scheduleCompile() {\n if (this.compileFrame) cancelAnimationFrame(this.compileFrame);\n this.compileFrame = requestAnimationFrame(() => {\n this.compileFrame = undefined;\n this.compile();\n });\n }\n\n private compile() {\n if (!this.definition || !(this.surfaceWidth > 0) || !(this.surfaceHeight > 0)) return;\n const previousKey = this.focusState?.primary.sceneKey;\n const scene = compileChartScene(\n this.definition,\n this.surfaceWidth,\n this.surfaceHeight,\n this.locale ?? document.documentElement.lang ?? 'en',\n text => this.measuredLabels.get(text) ?? { width: text.length * 7, height: 14 },\n resolveChartTheme(this.el)\n );\n this.scene = scene;\n if (previousKey) {\n const restored = scene.points.find(point => point.sceneKey === previousKey);\n if (restored && this.focusState) {\n const grouped = findChartFocus(\n scene.points,\n scene.focus,\n restored.x,\n restored.y,\n scene.maxFocusDistance,\n scene.arcHitRegions\n );\n this.focusState = grouped ? { ...grouped, source: this.focusState.source } : undefined;\n } else {\n this.focusState = undefined;\n }\n }\n }\n\n private waitForFonts() {\n if (!document.fonts) return;\n void document.fonts.ready.then(() => {\n this.measuredLabels.clear();\n this.scheduleCompile();\n });\n }\n\n private measureRenderedLabels() {\n const labels = this.el.querySelectorAll<SVGTextElement>('[data-chart-measure]');\n let changed = false;\n labels.forEach(label => {\n const text = label.textContent ?? '';\n let box: DOMRect;\n try {\n box = label.getBBox();\n } catch {\n return;\n }\n const previous = this.measuredLabels.get(text);\n if (\n !previous ||\n Math.abs(previous.width - box.width) > 0.5 ||\n Math.abs(previous.height - box.height) > 0.5\n ) {\n this.measuredLabels.set(text, { width: box.width, height: box.height });\n changed = true;\n }\n });\n if (changed) this.scheduleCompile();\n }\n\n private resolveFocus(x: number, y: number, source: ChartFocusSource) {\n if (!this.scene) return;\n const result = findChartFocus(\n this.scene.points,\n this.scene.focus,\n x,\n y,\n this.scene.maxFocusDistance,\n this.scene.arcHitRegions\n );\n if (!result) {\n this.clearFocus(source);\n return;\n }\n if (\n this.focusState?.primary.sceneKey === result.primary.sceneKey &&\n this.focusState.source === source\n )\n return;\n this.focusState = { ...result, source };\n this.dsChartFocusChange.emit({ ...result, source });\n }\n\n private clearFocus(source: ChartFocusSource) {\n if (!this.focusState) return;\n this.focusState = undefined;\n this.dsChartFocusChange.emit({ primary: null, points: [], source });\n }\n\n private onPointerMove = (event: PointerEvent) => {\n const svg = event.currentTarget as SVGSVGElement;\n const rect = svg.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n this.pointerAnchor = this.scene?.arcHitRegions.length ? { x, y } : undefined;\n this.resolveFocus(x, y, 'pointer');\n };\n\n private onPointerLeave = () => {\n this.pointerAnchor = undefined;\n this.clearFocus('pointer');\n };\n\n private keyboardPoints(): ChartPoint[] {\n if (!this.scene) return [];\n return [...this.scene.points].sort((a, b) => a.x - b.x || a.y - b.y);\n }\n\n private onKeyDown = (event: KeyboardEvent) => {\n const points = this.keyboardPoints();\n if (points.length === 0) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n this.clearFocus('keyboard');\n return;\n }\n const current = this.focusState\n ? points.findIndex(point => point.sceneKey === this.focusState?.primary.sceneKey)\n : -1;\n let next: number | undefined;\n if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = points.length - 1;\n else if (event.key === 'ArrowRight' || event.key === 'ArrowDown')\n next = Math.min(points.length - 1, current + 1);\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')\n next = Math.max(0, current < 0 ? 0 : current - 1);\n else return;\n event.preventDefault();\n const point = points[next ?? 0] ?? points[0];\n this.resolveFocus(point.x, point.y, 'keyboard');\n };\n\n private renderNode(node: ChartSceneNode, className = 'chart__mark') {\n const resolvedClassName = node.className ?? className;\n const presentation = {\n fill: node.style.fill,\n stroke: node.style.stroke,\n opacity: node.style.opacity,\n 'fill-opacity': node.style.fillOpacity,\n 'stroke-opacity': node.style.strokeOpacity,\n 'stroke-width': node.style.strokeWidth,\n 'stroke-dasharray': node.style.strokeDasharray || undefined,\n };\n if (node.type === 'path')\n return (\n <path\n key={node.key}\n class={resolvedClassName}\n d={node.d}\n transform={node.transform}\n {...presentation}\n />\n );\n if (node.type === 'rect') {\n return (\n <rect\n key={node.key}\n class={resolvedClassName}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={node.style.radius}\n {...presentation}\n />\n );\n }\n if (node.type === 'circle')\n return (\n <circle\n key={node.key}\n class={resolvedClassName}\n cx={node.x}\n cy={node.y}\n r={node.radius}\n {...presentation}\n />\n );\n if (node.type === 'line')\n return (\n <line\n key={node.key}\n class={resolvedClassName}\n x1={node.x1}\n x2={node.x2}\n y1={node.y1}\n y2={node.y2}\n {...presentation}\n />\n );\n return (\n <text\n key={node.key}\n class={node.className ?? `${className} chart__text-mark`}\n data-chart-measure={node.measure ? '' : undefined}\n x={node.x}\n y={node.y}\n dx={node.dx}\n dy={node.dy}\n text-anchor={node.style.textAnchor}\n font-family={node.style.fontFamily}\n font-size={node.style.fontSize}\n font-weight={node.style.fontWeight}\n dominant-baseline={node.dominantBaseline ?? 'middle'}\n transform={node.rotate ? `rotate(${node.rotate} ${node.x} ${node.y})` : undefined}\n {...presentation}\n >\n {node.text}\n </text>\n );\n }\n\n private renderMarkLayers(scene: ChartScene) {\n const layers: Array<{ clipped: boolean; nodes: ChartSceneNode[] }> = [];\n scene.nodes.forEach(node => {\n const clipped = scene.clip && node.clipToPlot !== false;\n const current = layers[layers.length - 1];\n if (current?.clipped === clipped) current.nodes.push(node);\n else layers.push({ clipped, nodes: [node] });\n });\n return layers.map((layer, index) => (\n <g\n key={`marks-${index}`}\n class=\"chart__marks\"\n aria-hidden=\"true\"\n clip-path={layer.clipped ? `url(#${this.clipId})` : undefined}\n >\n {layer.nodes.map(node => this.renderNode(node))}\n </g>\n ));\n }\n\n private focusedDotPoints(scene: ChartScene): ChartPoint[] {\n if (!this.focusState) return [];\n const focusDotPointKeys = new Set(scene.focusDotPointKeys);\n return this.focusState.points.filter(point => focusDotPointKeys.has(point.sceneKey));\n }\n\n private tooltipContent(): { heading?: string; items: ChartTooltipItem[] } | undefined {\n if (!this.scene || !this.focusState || this.scene.tooltip === false) return undefined;\n const options: ChartTooltipOptions =\n typeof this.scene.tooltip === 'object' ? this.scene.tooltip : {};\n const locale = this.locale ?? document.documentElement.lang ?? 'en';\n const points = this.focusState.points;\n const formatValue = (point: ChartPoint) => {\n const value = point.value ?? point.yValue;\n return (\n options.format?.(point, locale) ??\n (typeof value === 'number'\n ? new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value)\n : String(value))\n );\n };\n return {\n heading:\n points.length > 1\n ? (options.formatGroupHeading?.(points, locale) ?? String(this.focusState.primary.xValue))\n : undefined,\n items: points.map(\n point =>\n options.formatGroupItem?.(point, locale) ?? {\n label: point.groupLabel ?? point.markId,\n value: formatValue(point),\n color: point.color,\n }\n ),\n };\n }\n\n private statusText(): string {\n const content = this.tooltipContent();\n if (!content) return '';\n return [content.heading, ...content.items.map(item => `${item.label}: ${item.value}`)]\n .filter(Boolean)\n .join('. ');\n }\n\n render() {\n const scene = this.scene;\n const tooltip = this.tooltipContent();\n const focus = this.focusState?.primary;\n const tooltipAnchor =\n focus &&\n this.focusState?.source === 'pointer' &&\n scene?.arcHitRegions.length &&\n this.pointerAnchor\n ? this.pointerAnchor\n : focus;\n const focusedDotPoints = scene ? this.focusedDotPoints(scene) : [];\n const groupedPointFocus =\n scene?.coordinate === 'cartesian' && scene.focus === 'group-x' && focusedDotPoints.length > 1;\n const center =\n focus && scene?.center?.focused\n ? scene.center.focused(focus, this.locale ?? document.documentElement.lang ?? 'en')\n : scene?.center;\n return (\n <Host\n class=\"chart\"\n style={{\n '--ds-chart-width': this.width ? `${this.width}px` : '100%',\n '--ds-chart-height':\n this.height || this.aspectRatio\n ? `${this.surfaceHeight}px`\n : 'var(--ds-chart-container-height, 320px)',\n }}\n >\n {this.description && (\n <span id={this.descriptionId} class=\"chart__description\">\n {this.description}\n </span>\n )}\n {scene && (\n /* eslint-disable-next-line compomo/prefer-ds-icon -- SVG is the chart renderer. */\n <svg\n class=\"chart__surface\"\n width={scene.width}\n height={scene.height}\n viewBox={`0 0 ${scene.width} ${scene.height}`}\n role=\"img\"\n aria-label={this.label || 'Chart'}\n aria-describedby={this.description ? this.descriptionId : undefined}\n tabindex={0}\n onPointerMove={this.onPointerMove}\n onPointerLeave={this.onPointerLeave}\n onKeyDown={this.onKeyDown}\n >\n <defs aria-hidden=\"true\">\n <clipPath id={this.clipId}>\n <rect\n x={scene.plot.left}\n y={scene.plot.top}\n width={scene.plot.width}\n height={scene.plot.height}\n />\n </clipPath>\n </defs>\n <rect\n class=\"chart__interaction-surface\"\n x=\"0\"\n y=\"0\"\n width={scene.width}\n height={scene.height}\n aria-hidden=\"true\"\n />\n <g class=\"chart__guides\" aria-hidden=\"true\">\n {scene.guides.map(node => this.renderNode(node, 'chart__grid chart__polar-guide'))}\n {scene.yAxis.line && (\n <line\n class=\"chart__axis-line\"\n x1={scene.plot.left}\n x2={scene.plot.left}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n />\n )}\n {scene.yAxis.ticks.map(tick => (\n <g key={`y-${tick.key}`}>\n {scene.yAxis.grid && (\n <line\n class=\"chart__grid\"\n x1={scene.plot.left}\n x2={scene.plot.right}\n y1={tick.position}\n y2={tick.position}\n />\n )}\n <line\n class=\"chart__axis-line chart__tick-stub\"\n x1={scene.plot.left - scene.yAxis.tickSize}\n x2={scene.plot.left}\n y1={tick.position}\n y2={tick.position}\n />\n {tick.labelVisible && (\n <text\n data-chart-measure\n class=\"chart__tick chart__tick--y\"\n x={scene.plot.left - scene.yAxis.tickSize - scene.yAxis.tickPadding}\n y={tick.position}\n text-anchor=\"end\"\n dominant-baseline=\"middle\"\n >\n {tick.label}\n </text>\n )}\n </g>\n ))}\n {scene.xAxis.line && (\n <line\n class=\"chart__axis-line\"\n x1={scene.plot.left}\n x2={scene.plot.right}\n y1={scene.plot.bottom}\n y2={scene.plot.bottom}\n />\n )}\n {scene.xAxis.ticks.map(tick => (\n <g key={`x-${tick.key}`}>\n {scene.xAxis.grid && (\n <line\n class=\"chart__grid\"\n x1={tick.position}\n x2={tick.position}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n />\n )}\n <line\n class=\"chart__axis-line chart__tick-stub\"\n x1={tick.position}\n x2={tick.position}\n y1={scene.plot.bottom}\n y2={scene.plot.bottom + scene.xAxis.tickSize}\n />\n {tick.labelVisible && (\n <text\n data-chart-measure\n class=\"chart__tick chart__tick--x\"\n x={tick.position}\n y={scene.plot.bottom + scene.xAxis.tickSize + scene.xAxis.tickPadding}\n text-anchor=\"middle\"\n dominant-baseline=\"hanging\"\n transform={\n scene.xAxis.rotate\n ? `rotate(${scene.xAxis.rotate} ${tick.position} ${scene.plot.bottom + scene.xAxis.tickSize + scene.xAxis.tickPadding})`\n : undefined\n }\n >\n {tick.label}\n </text>\n )}\n </g>\n ))}\n {scene.xAxis.label && (\n <text\n class=\"chart__axis-title\"\n x={(scene.plot.left + scene.plot.right) / 2}\n y={scene.height - 4}\n text-anchor=\"middle\"\n >\n {scene.xAxis.label}\n </text>\n )}\n {scene.yAxis.label && (\n <text\n class=\"chart__axis-title\"\n x=\"8\"\n y={(scene.plot.top + scene.plot.bottom) / 2}\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n transform={`rotate(-90 8 ${(scene.plot.top + scene.plot.bottom) / 2})`}\n >\n {scene.yAxis.label}\n </text>\n )}\n {scene.coordinate === 'cartesian' && (\n <g class=\"chart__plot-frame\">\n <line\n class=\"chart__plot-boundary chart__plot-boundary--top\"\n x1={scene.plot.left}\n x2={scene.plot.right}\n y1={scene.plot.top}\n y2={scene.plot.top}\n />\n <line\n class=\"chart__plot-boundary chart__plot-boundary--right\"\n x1={scene.plot.right}\n x2={scene.plot.right}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n />\n </g>\n )}\n </g>\n {this.renderMarkLayers(scene)}\n {groupedPointFocus && focus && (\n <line\n class=\"chart__focus-guide\"\n x1={focus.x}\n x2={focus.x}\n y1={scene.plot.top}\n y2={scene.plot.bottom}\n aria-hidden=\"true\"\n />\n )}\n {focusedDotPoints.map(point => (\n <circle\n key={`focus-${point.sceneKey}`}\n class=\"chart__focus-point\"\n cx={point.x}\n cy={point.y}\n r={scene.focusDotCircleRadius}\n fill={point.color}\n stroke=\"var(--color-background-primary)\"\n stroke-width={scene.focusDotHaloWidth}\n aria-hidden=\"true\"\n />\n ))}\n {focus && focusedDotPoints.length === 0 && (\n <circle class=\"chart__focus\" cx={focus.x} cy={focus.y} r=\"6\" aria-hidden=\"true\" />\n )}\n {center?.value && (\n <text\n class=\"chart__center-value\"\n x={(scene.plot.left + scene.plot.right) / 2}\n y={(scene.plot.top + scene.plot.bottom) / 2 - (center.caption ? 10 : 0)}\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n aria-hidden=\"true\"\n >\n {center.value}\n </text>\n )}\n {center?.caption && (\n <text\n class=\"chart__center-caption\"\n x={(scene.plot.left + scene.plot.right) / 2}\n y={(scene.plot.top + scene.plot.bottom) / 2 + 14}\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n aria-hidden=\"true\"\n >\n {center.caption}\n </text>\n )}\n </svg>\n )}\n {tooltip && tooltipAnchor && (\n <ds-tooltip-chart\n heading={tooltip.heading}\n items={tooltip.items}\n x={tooltipAnchor.x}\n y={tooltipAnchor.y}\n />\n )}\n <span class=\"chart__status\" aria-live=\"polite\" aria-atomic=\"true\">\n {this.statusText()}\n </span>\n </Host>\n );\n }\n}\n",
260
265
  "type": "registry:ui"
261
266
  }
262
267
  ]
@@ -88,7 +88,43 @@
88
88
  "label": "Cross-browser form and accessibility coverage",
89
89
  "path": "tests/e2e/forms.spec.ts"
90
90
  }
91
- ]
91
+ ],
92
+ "styling": {
93
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
94
+ "protected": [
95
+ {
96
+ "properties": [
97
+ "font",
98
+ "font-size",
99
+ "font-weight",
100
+ "font-family",
101
+ "line-height",
102
+ "letter-spacing",
103
+ "padding",
104
+ "border-radius"
105
+ ],
106
+ "reason": "The field primitive owns its density and selection anatomy; use size and presentation props without replacing the recipe.",
107
+ "props": [
108
+ "size",
109
+ "presentation"
110
+ ]
111
+ },
112
+ {
113
+ "properties": [
114
+ "background",
115
+ "border",
116
+ "box-shadow",
117
+ "color"
118
+ ],
119
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
120
+ "props": [
121
+ "size",
122
+ "presentation"
123
+ ]
124
+ }
125
+ ],
126
+ "customProperties": []
127
+ }
92
128
  },
93
129
  "api": {
94
130
  "props": {
@@ -358,7 +394,7 @@
358
394
  "peerDependencies": {
359
395
  "required": [
360
396
  "@ds-mo/tokens ^6.5.1",
361
- "@ds-mo/icons ^7.0.0"
397
+ "@ds-mo/icons ^8.1.0"
362
398
  ],
363
399
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
364
400
  }
@@ -379,7 +415,7 @@
379
415
  },
380
416
  {
381
417
  "path": "src/wc/components/Checkbox/Checkbox.tsx",
382
- "content": "import {\n AttachInternals,\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Watch,\n h,\n Host,\n} 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={\n this.presentation ? undefined : this.indeterminate ? 'mixed' : String(this.checked)\n }\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={!this.presentation && this.description ? this.descriptionId : undefined}\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 fixed state-mark geometry.\n <svg class=\"checkbox__mark\" viewBox=\"0 0 16 16\" fill=\"currentColor\" focusable=\"false\">\n <path\n d={\n this.indeterminate\n ? 'M12 7.375V8.625H4V7.375H12Z'\n : 'M12.9756 4.65527L7.22559 11.4053C7.11258 11.5379 6.94946 11.6169 6.77539 11.624C6.60111 11.631 6.43095 11.5657 6.30762 11.4424L3.05762 8.19238L3.94238 7.30762L6.71289 10.0781L12.0244 3.84473L12.9756 4.65527Z'\n }\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",
418
+ "content": "import {\n AttachInternals,\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Watch,\n h,\n Host,\n} 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={\n this.presentation ? undefined : this.indeterminate ? 'mixed' : String(this.checked)\n }\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={!this.presentation && this.description ? this.descriptionId : undefined}\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 compomo/prefer-ds-icon -- Checkbox owns this fixed state-mark geometry.\n <svg class=\"checkbox__mark\" viewBox=\"0 0 16 16\" fill=\"currentColor\" focusable=\"false\">\n <path\n d={\n this.indeterminate\n ? 'M12 7.375V8.625H4V7.375H12Z'\n : 'M12.9756 4.65527L7.22559 11.4053C7.11258 11.5379 6.94946 11.6169 6.77539 11.624C6.60111 11.631 6.43095 11.5657 6.30762 11.4424L3.05762 8.19238L3.94238 7.30762L6.71289 10.0781L12.0244 3.84473L12.9756 4.65527Z'\n }\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",
383
419
  "type": "registry:ui"
384
420
  }
385
421
  ]
@@ -81,7 +81,56 @@
81
81
  "label": "Storybook examples",
82
82
  "path": "src/wc/components/Chip/Chip.stories.ts"
83
83
  }
84
- ]
84
+ ],
85
+ "styling": {
86
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
87
+ "protected": [
88
+ {
89
+ "properties": [
90
+ "font",
91
+ "font-size",
92
+ "font-weight",
93
+ "font-family",
94
+ "line-height",
95
+ "letter-spacing",
96
+ "height",
97
+ "min-height",
98
+ "max-height",
99
+ "padding",
100
+ "gap"
101
+ ],
102
+ "reason": "The control density is one atomic recipe; use the size and inset props instead of replacing internal metrics.",
103
+ "props": [
104
+ "size",
105
+ "isInset",
106
+ "insetDepth"
107
+ ]
108
+ },
109
+ {
110
+ "properties": [
111
+ "border-radius"
112
+ ],
113
+ "reason": "The component owns its corner treatment; select the supported radius treatment.",
114
+ "props": [
115
+ "rounded",
116
+ "size"
117
+ ]
118
+ },
119
+ {
120
+ "properties": [
121
+ "background",
122
+ "border",
123
+ "box-shadow",
124
+ "color"
125
+ ],
126
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
127
+ "props": [
128
+ "size"
129
+ ]
130
+ }
131
+ ],
132
+ "customProperties": []
133
+ }
85
134
  },
86
135
  "api": {
87
136
  "props": {
@@ -289,7 +338,7 @@
289
338
  "peerDependencies": {
290
339
  "required": [
291
340
  "@ds-mo/tokens ^6.5.1",
292
- "@ds-mo/icons ^7.0.0"
341
+ "@ds-mo/icons ^8.1.0"
293
342
  ],
294
343
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
295
344
  }