@ds-mo/ui 9.2.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +2 -2
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +1 -1
  4. package/dist/agent.json +384 -135
  5. package/dist/angular/boolean-value-accessor.js +3 -3
  6. package/dist/angular/ds-agent-activity.js +3 -3
  7. package/dist/angular/ds-agent-response.js +3 -3
  8. package/dist/angular/ds-agent-source-list.js +3 -3
  9. package/dist/angular/ds-agent-tool-call.js +3 -3
  10. package/dist/angular/ds-app-shell.js +3 -3
  11. package/dist/angular/ds-attachment-list.js +3 -3
  12. package/dist/angular/ds-avatar.js +3 -3
  13. package/dist/angular/ds-badge.js +3 -3
  14. package/dist/angular/ds-bar-nav.js +3 -3
  15. package/dist/angular/ds-button-filled.js +3 -3
  16. package/dist/angular/ds-button-unfilled.js +3 -3
  17. package/dist/angular/ds-card-data-viz-donut.js +3 -3
  18. package/dist/angular/ds-card-setting.d.ts +5 -4
  19. package/dist/angular/ds-card-setting.js +6 -6
  20. package/dist/angular/ds-card-shell-data-viz.js +3 -3
  21. package/dist/angular/ds-chart-bar.js +3 -3
  22. package/dist/angular/ds-chart-donut.js +3 -3
  23. package/dist/angular/ds-chart-legend.js +3 -3
  24. package/dist/angular/ds-chart-line.js +3 -3
  25. package/dist/angular/ds-checkbox.js +3 -3
  26. package/dist/angular/ds-chip.js +3 -3
  27. package/dist/angular/ds-code-block.js +3 -3
  28. package/dist/angular/ds-conversation-list-item.js +3 -3
  29. package/dist/angular/ds-conversation-list-section.js +3 -3
  30. package/dist/angular/ds-conversation-list.js +3 -3
  31. package/dist/angular/ds-divider.js +3 -3
  32. package/dist/angular/ds-empty-state.js +3 -3
  33. package/dist/angular/ds-field.js +3 -3
  34. package/dist/angular/ds-icon.js +3 -3
  35. package/dist/angular/ds-input.js +3 -3
  36. package/dist/angular/ds-loader.js +3 -3
  37. package/dist/angular/ds-markdown.js +3 -3
  38. package/dist/angular/ds-menu.js +3 -3
  39. package/dist/angular/ds-message-bubble.js +3 -3
  40. package/dist/angular/ds-message-composer.js +3 -3
  41. package/dist/angular/ds-message-scroller.js +3 -3
  42. package/dist/angular/ds-message.js +3 -3
  43. package/dist/angular/ds-modal.d.ts +12 -3
  44. package/dist/angular/ds-modal.js +9 -6
  45. package/dist/angular/ds-panel-nav.js +3 -3
  46. package/dist/angular/ds-panel-sub-nav.js +3 -3
  47. package/dist/angular/ds-panel-tool-header.js +3 -3
  48. package/dist/angular/ds-panel-tool-search.js +3 -3
  49. package/dist/angular/ds-panel-tools.js +3 -3
  50. package/dist/angular/ds-radio.js +3 -3
  51. package/dist/angular/ds-select-multi.js +3 -3
  52. package/dist/angular/ds-select.js +3 -3
  53. package/dist/angular/ds-shell-gradient-picker.js +3 -3
  54. package/dist/angular/ds-shell-gradient-swatch.js +3 -3
  55. package/dist/angular/ds-skeleton.js +3 -3
  56. package/dist/angular/ds-slider.js +3 -3
  57. package/dist/angular/ds-swatch-picker.js +3 -3
  58. package/dist/angular/ds-switch.js +3 -3
  59. package/dist/angular/ds-tab-group.js +3 -3
  60. package/dist/angular/ds-tag.js +3 -3
  61. package/dist/angular/ds-text.js +3 -3
  62. package/dist/angular/ds-toast.d.ts +22 -0
  63. package/dist/angular/{ds-banner.js → ds-toast.js} +21 -15
  64. package/dist/angular/ds-tooltip-data-viz.js +3 -3
  65. package/dist/angular/ds-tooltip.js +3 -3
  66. package/dist/angular/index.d.ts +1 -1
  67. package/dist/angular/index.js +1 -2
  68. package/dist/angular/proxies.d.ts +1 -2
  69. package/dist/angular/proxies.js +1 -2
  70. package/dist/angular/radio-value-accessor.js +3 -3
  71. package/dist/angular/select-value-accessor.js +3 -3
  72. package/dist/angular/text-value-accessor.js +3 -3
  73. package/dist/angular/value-accessor.js +3 -3
  74. package/dist/components/ds-agent-activity.js +1 -1
  75. package/dist/components/ds-agent-response.js +1 -1
  76. package/dist/components/ds-agent-response.js.map +1 -1
  77. package/dist/components/ds-agent-source-list.js +1 -1
  78. package/dist/components/ds-agent-tool-call.js +1 -1
  79. package/dist/components/ds-app-shell.js +1 -1
  80. package/dist/components/ds-attachment-list.js +1 -1
  81. package/dist/components/ds-avatar.js +1 -1
  82. package/dist/components/ds-badge.js +1 -1
  83. package/dist/components/ds-bar-nav.js +1 -1
  84. package/dist/components/ds-button-filled.js +1 -1
  85. package/dist/components/ds-button-unfilled.js +1 -1
  86. package/dist/components/ds-card-data-viz-donut.js +1 -1
  87. package/dist/components/ds-card-data-viz-donut.js.map +1 -1
  88. package/dist/components/ds-card-setting.js +1 -1
  89. package/dist/components/ds-card-setting.js.map +1 -1
  90. package/dist/components/ds-card-shell-data-viz.js +1 -1
  91. package/dist/components/ds-chart-bar.js +1 -1
  92. package/dist/components/ds-chart-donut.js +1 -1
  93. package/dist/components/ds-chart-legend.js +1 -1
  94. package/dist/components/ds-chart-line.js +1 -1
  95. package/dist/components/ds-checkbox.js +1 -1
  96. package/dist/components/ds-chip.js +1 -1
  97. package/dist/components/ds-code-block.js +1 -1
  98. package/dist/components/ds-conversation-list-item.js +1 -1
  99. package/dist/components/ds-conversation-list-item.js.map +1 -1
  100. package/dist/components/ds-conversation-list-section.js +1 -1
  101. package/dist/components/ds-conversation-list.js +1 -1
  102. package/dist/components/ds-divider.js +1 -1
  103. package/dist/components/ds-empty-state.js +1 -1
  104. package/dist/components/ds-field.js +1 -1
  105. package/dist/components/ds-icon.js +1 -1
  106. package/dist/components/ds-input.js +1 -1
  107. package/dist/components/ds-markdown.js +1 -1
  108. package/dist/components/ds-menu.js +1 -1
  109. package/dist/components/ds-message-bubble.js +1 -1
  110. package/dist/components/ds-message-composer.js +1 -1
  111. package/dist/components/ds-message-composer.js.map +1 -1
  112. package/dist/components/ds-message-scroller.js +1 -1
  113. package/dist/components/ds-message-scroller.js.map +1 -1
  114. package/dist/components/ds-message.js +1 -1
  115. package/dist/components/ds-modal.js +1 -1
  116. package/dist/components/ds-modal.js.map +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-radio.js +1 -1
  123. package/dist/components/ds-select-multi.js +1 -1
  124. package/dist/components/ds-select.js +1 -1
  125. package/dist/components/ds-shell-gradient-picker.js +1 -1
  126. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  127. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  128. package/dist/components/ds-skeleton.js +1 -1
  129. package/dist/components/ds-slider.js +1 -1
  130. package/dist/components/ds-slider.js.map +1 -1
  131. package/dist/components/ds-swatch-picker.js +1 -1
  132. package/dist/components/ds-switch.js +1 -1
  133. package/dist/components/ds-tab-group.js +1 -1
  134. package/dist/components/ds-tag.js +1 -1
  135. package/dist/components/ds-text.js +1 -1
  136. package/dist/components/{ds-card.d.ts → ds-toast.d.ts} +4 -4
  137. package/dist/components/ds-toast.js +2 -0
  138. package/dist/components/ds-toast.js.map +1 -0
  139. package/dist/components/ds-tooltip-data-viz.js +1 -1
  140. package/dist/components/ds-tooltip.js +1 -1
  141. package/dist/components/{p-DnMCRAwx.js → p-0kEg-Z4f.js} +2 -2
  142. package/dist/components/{p-Cijzn3YQ.js → p-B4RQoNmL.js} +2 -2
  143. package/dist/components/p-B8C_yfLQ.js +2 -0
  144. package/dist/components/p-B8C_yfLQ.js.map +1 -0
  145. package/dist/components/{p-BHpWYKtP.js → p-BJUcsBHt.js} +2 -2
  146. package/dist/components/{p-DaQ0ZE_V.js → p-BTo4pNdp.js} +2 -2
  147. package/dist/components/p-Bet_UjML.js +2 -0
  148. package/dist/components/{p-CJlgONwR.js.map → p-Bet_UjML.js.map} +1 -1
  149. package/dist/components/{p-Ci_nVk5P.js → p-BkwBtZs3.js} +2 -2
  150. package/dist/components/{p-Dst-OliP.js → p-CKnoWZd9.js} +2 -2
  151. package/dist/components/{p-BRfOCc_h.js → p-CLn7QnE0.js} +2 -2
  152. package/dist/components/{p-kezp2_Xu.js → p-CPPAndX2.js} +2 -2
  153. package/dist/components/{p-xB0GPG3-.js → p-CS12e52R.js} +2 -2
  154. package/dist/components/{p-BSOZXagj.js → p-CZZYGqKz.js} +2 -2
  155. package/dist/components/p-C_0Q8Cnp.js +2 -0
  156. package/dist/components/p-C_0Q8Cnp.js.map +1 -0
  157. package/dist/components/{p-DiooIO2B.js → p-CxyjoTN1.js} +2 -2
  158. package/dist/components/p-CyoHRqy6.js +2 -0
  159. package/dist/components/p-CyoHRqy6.js.map +1 -0
  160. package/dist/components/p-DBJ2I3mH.js +2 -0
  161. package/dist/components/p-DBJ2I3mH.js.map +1 -0
  162. package/dist/components/p-DM-95BX1.js +2 -0
  163. package/dist/components/p-DM-95BX1.js.map +1 -0
  164. package/dist/components/{p-T0Mzhv3-.js → p-DZAtgSZo.js} +2 -2
  165. package/dist/components/{p-CeFBA7Dx.js → p-Dh4q5TSS.js} +2 -2
  166. package/dist/components/{p-Dj5MejHv.js → p-DhsO0939.js} +2 -2
  167. package/dist/components/{p-CiwdnKKp.js → p-DrDHC7V6.js} +2 -2
  168. package/dist/components/{p-CblAABEo.js → p-DsbhBb5F.js} +2 -2
  169. package/dist/components/p-DuNZGFkV.js +2 -0
  170. package/dist/components/p-DuNZGFkV.js.map +1 -0
  171. package/dist/components/{p-B1pWGQn1.js → p-P_4vE1ub.js} +2 -2
  172. package/dist/docs/components.json +447 -515
  173. package/dist/framework/select-multi-value-accessor.js +3 -3
  174. package/dist/lib/toast/index.d.ts +2 -0
  175. package/dist/lib/toast/index.js +158 -0
  176. package/dist/lib/toast/index.js.map +7 -0
  177. package/dist/lib/toast/toast-manager.d.ts +3 -0
  178. package/dist/lib/toast/toast-types.d.ts +85 -0
  179. package/dist/mcp-data/registry/agent-activity.json +1 -1
  180. package/dist/mcp-data/registry/agent-response.json +2 -2
  181. package/dist/mcp-data/registry/agent-source-list.json +1 -1
  182. package/dist/mcp-data/registry/agent-tool-call.json +1 -1
  183. package/dist/mcp-data/registry/app-shell.json +1 -1
  184. package/dist/mcp-data/registry/attachment-list.json +1 -1
  185. package/dist/mcp-data/registry/avatar.json +1 -1
  186. package/dist/mcp-data/registry/badge.json +1 -1
  187. package/dist/mcp-data/registry/bar-nav.json +1 -1
  188. package/dist/mcp-data/registry/button-filled.json +1 -1
  189. package/dist/mcp-data/registry/button-unfilled.json +1 -1
  190. package/dist/mcp-data/registry/card-data-viz-donut.json +2 -2
  191. package/dist/mcp-data/registry/card-setting.json +99 -13
  192. package/dist/mcp-data/registry/card-shell-data-viz.json +8 -8
  193. package/dist/mcp-data/registry/chart-bar.json +1 -1
  194. package/dist/mcp-data/registry/chart-donut.json +1 -1
  195. package/dist/mcp-data/registry/chart-legend.json +1 -1
  196. package/dist/mcp-data/registry/chart-line.json +1 -1
  197. package/dist/mcp-data/registry/checkbox.json +1 -1
  198. package/dist/mcp-data/registry/chip.json +1 -1
  199. package/dist/mcp-data/registry/code-block.json +1 -1
  200. package/dist/mcp-data/registry/conversation-list-item.json +4 -4
  201. package/dist/mcp-data/registry/conversation-list-section.json +1 -1
  202. package/dist/mcp-data/registry/conversation-list.json +1 -1
  203. package/dist/mcp-data/registry/divider.json +5 -5
  204. package/dist/mcp-data/registry/empty-state.json +1 -1
  205. package/dist/mcp-data/registry/field.json +1 -1
  206. package/dist/mcp-data/registry/icon.json +2 -2
  207. package/dist/mcp-data/registry/input.json +1 -1
  208. package/dist/mcp-data/registry/loader.json +1 -1
  209. package/dist/mcp-data/registry/markdown.json +1 -1
  210. package/dist/mcp-data/registry/menu.json +1 -1
  211. package/dist/mcp-data/registry/message-bubble.json +7 -7
  212. package/dist/mcp-data/registry/message-composer.json +2 -2
  213. package/dist/mcp-data/registry/message-scroller.json +1 -1
  214. package/dist/mcp-data/registry/message.json +7 -6
  215. package/dist/mcp-data/registry/modal.json +154 -24
  216. package/dist/mcp-data/registry/panel-nav.json +1 -1
  217. package/dist/mcp-data/registry/panel-sub-nav.json +2 -2
  218. package/dist/mcp-data/registry/panel-tool-header.json +1 -1
  219. package/dist/mcp-data/registry/panel-tool-search.json +1 -1
  220. package/dist/mcp-data/registry/panel-tools.json +1 -1
  221. package/dist/mcp-data/registry/radio.json +1 -1
  222. package/dist/mcp-data/registry/registry.json +387 -141
  223. package/dist/mcp-data/registry/select-multi.json +1 -1
  224. package/dist/mcp-data/registry/select.json +1 -1
  225. package/dist/mcp-data/registry/shell-gradient-picker.json +1 -1
  226. package/dist/mcp-data/registry/shell-gradient-swatch.json +1 -1
  227. package/dist/mcp-data/registry/skeleton.json +2 -2
  228. package/dist/mcp-data/registry/slider.json +1 -1
  229. package/dist/mcp-data/registry/swatch-picker.json +1 -1
  230. package/dist/mcp-data/registry/switch.json +1 -1
  231. package/dist/mcp-data/registry/tab-group.json +1 -1
  232. package/dist/mcp-data/registry/tag.json +2 -2
  233. package/dist/mcp-data/registry/text.json +2 -2
  234. package/dist/mcp-data/registry/toast.json +318 -0
  235. package/dist/mcp-data/registry/tooltip-data-viz.json +1 -1
  236. package/dist/mcp-data/registry/tooltip.json +1 -1
  237. package/dist/react/components.d.ts +1 -2
  238. package/dist/react/components.js +1 -2
  239. package/dist/react/ds-card-setting.d.ts +2 -2
  240. package/dist/react/ds-card-setting.js +1 -1
  241. package/dist/react/ds-modal.d.ts +3 -2
  242. package/dist/react/ds-modal.js +4 -1
  243. package/dist/react/ds-toast.d.ts +14 -0
  244. package/dist/react/ds-toast.js +16 -0
  245. package/dist/types/components/CardSetting/CardSetting.d.ts +9 -6
  246. package/dist/types/components/CardSetting/index.d.ts +1 -1
  247. package/dist/types/components/MessageBubble/MessageBubble.d.ts +1 -1
  248. package/dist/types/components/Modal/Modal.d.ts +21 -18
  249. package/dist/types/components/Toast/Toast.d.ts +106 -0
  250. package/dist/types/components/Toast/index.d.ts +2 -0
  251. package/dist/types/components.d.ts +136 -180
  252. package/dist/types/toast/index.d.ts +3 -0
  253. package/dist/types/toast/toast-manager.d.ts +4 -0
  254. package/dist/types/toast/toast-types.d.ts +86 -0
  255. package/package.json +12 -8
  256. package/dist/angular/ds-banner.d.ts +0 -15
  257. package/dist/angular/ds-card.d.ts +0 -12
  258. package/dist/angular/ds-card.js +0 -38
  259. package/dist/components/ds-banner.d.ts +0 -11
  260. package/dist/components/ds-banner.js +0 -2
  261. package/dist/components/ds-banner.js.map +0 -1
  262. package/dist/components/ds-card.js +0 -2
  263. package/dist/components/ds-card.js.map +0 -1
  264. package/dist/components/p-BO6C8abO.js +0 -2
  265. package/dist/components/p-BO6C8abO.js.map +0 -1
  266. package/dist/components/p-CBYuvZKM.js +0 -2
  267. package/dist/components/p-CBYuvZKM.js.map +0 -1
  268. package/dist/components/p-CJlgONwR.js +0 -2
  269. package/dist/components/p-CWNdl52Y.js +0 -2
  270. package/dist/components/p-CWNdl52Y.js.map +0 -1
  271. package/dist/components/p-CiKGqsA2.js +0 -2
  272. package/dist/components/p-CiKGqsA2.js.map +0 -1
  273. package/dist/components/p-DFxqk71w.js +0 -2
  274. package/dist/components/p-DFxqk71w.js.map +0 -1
  275. package/dist/components/p-drBA73M_.js +0 -2
  276. package/dist/components/p-drBA73M_.js.map +0 -1
  277. package/dist/mcp-data/registry/banner.json +0 -206
  278. package/dist/mcp-data/registry/card.json +0 -126
  279. package/dist/react/ds-banner.d.ts +0 -12
  280. package/dist/react/ds-banner.js +0 -12
  281. package/dist/react/ds-card.d.ts +0 -9
  282. package/dist/react/ds-card.js +0 -12
  283. package/dist/types/components/Banner/Banner.d.ts +0 -31
  284. package/dist/types/components/Card/Card.d.ts +0 -21
  285. package/dist/types/components/Card/index.d.ts +0 -2
  286. /package/dist/components/{p-DnMCRAwx.js.map → p-0kEg-Z4f.js.map} +0 -0
  287. /package/dist/components/{p-Cijzn3YQ.js.map → p-B4RQoNmL.js.map} +0 -0
  288. /package/dist/components/{p-BHpWYKtP.js.map → p-BJUcsBHt.js.map} +0 -0
  289. /package/dist/components/{p-DaQ0ZE_V.js.map → p-BTo4pNdp.js.map} +0 -0
  290. /package/dist/components/{p-Ci_nVk5P.js.map → p-BkwBtZs3.js.map} +0 -0
  291. /package/dist/components/{p-Dst-OliP.js.map → p-CKnoWZd9.js.map} +0 -0
  292. /package/dist/components/{p-BRfOCc_h.js.map → p-CLn7QnE0.js.map} +0 -0
  293. /package/dist/components/{p-kezp2_Xu.js.map → p-CPPAndX2.js.map} +0 -0
  294. /package/dist/components/{p-xB0GPG3-.js.map → p-CS12e52R.js.map} +0 -0
  295. /package/dist/components/{p-BSOZXagj.js.map → p-CZZYGqKz.js.map} +0 -0
  296. /package/dist/components/{p-DiooIO2B.js.map → p-CxyjoTN1.js.map} +0 -0
  297. /package/dist/components/{p-T0Mzhv3-.js.map → p-DZAtgSZo.js.map} +0 -0
  298. /package/dist/components/{p-CeFBA7Dx.js.map → p-Dh4q5TSS.js.map} +0 -0
  299. /package/dist/components/{p-Dj5MejHv.js.map → p-DhsO0939.js.map} +0 -0
  300. /package/dist/components/{p-CiwdnKKp.js.map → p-DrDHC7V6.js.map} +0 -0
  301. /package/dist/components/{p-CblAABEo.js.map → p-DsbhBb5F.js.map} +0 -0
  302. /package/dist/components/{p-B1pWGQn1.js.map → p-P_4vE1ub.js.map} +0 -0
@@ -2,17 +2,126 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "modal",
4
4
  "title": "Modal",
5
- "description": "Dialog overlay with portal rendering, escape-to-close, backdrop click, focus management, and entry/exit animations.",
5
+ "description": "Dismissible native top-layer dialog for a focused confirmation or short blocking task, with a fixed title-and-close header, composable content, and an optional two-action footer.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
9
9
  "distribution": "npm",
10
10
  "storybook": "https://zainadeel.github.io/compomo/?path=/story/modal",
11
11
  "source": "src/wc/components/Modal/Modal.tsx",
12
- "intentStatus": "migration-pending",
13
- "intent": null,
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "general",
15
+ "status": "stable",
16
+ "summary": "Dismissible native top-layer dialog for a focused confirmation or short blocking task, with a fixed title-and-close header, composable content, and an optional two-action footer.",
17
+ "useWhen": [
18
+ "The user must confirm or cancel one focused decision before returning to the underlying interface.",
19
+ "A short blocking task or compact form needs dialog semantics, focus containment, and explicit completion actions.",
20
+ "The content can be safely dismissed through Close, Escape, or the backdrop."
21
+ ],
22
+ "avoidWhen": [
23
+ "The message is non-blocking feedback or a transient status update.",
24
+ "The interaction is a contextual list of commands anchored to a trigger.",
25
+ "The workflow is multi-step, long-form, or requires substantial navigation.",
26
+ "Dismissal would lose work or leave an irreversible operation in an unsafe state.",
27
+ "A second modal would be opened over an existing modal."
28
+ ],
29
+ "alternatives": [
30
+ {
31
+ "when": "The user needs non-blocking feedback or a transient status message.",
32
+ "component": "component:ds-toast",
33
+ "reason": "Toast communicates transient status without trapping focus or blocking the page."
34
+ },
35
+ {
36
+ "when": "A trigger reveals a short contextual list of commands.",
37
+ "component": "component:ds-menu",
38
+ "reason": "Menu provides anchored command semantics without creating a blocking dialog."
39
+ },
40
+ {
41
+ "when": "Editable settings should remain visible without blocking the rest of the interface.",
42
+ "component": "component:ds-card-setting",
43
+ "reason": "CardSetting provides a persistent controlled settings workflow without modal focus containment."
44
+ }
45
+ ],
46
+ "commonlyComposedWith": [
47
+ "component:ds-button-filled",
48
+ "component:ds-button-unfilled",
49
+ "component:ds-field",
50
+ "component:ds-text"
51
+ ],
52
+ "accessibility": [
53
+ "Provide a concise visible heading; the component uses it as the dialog's accessible name.",
54
+ "Give the primary explanatory copy a stable id and pass it through aria-describedby so assistive technology receives the decision context.",
55
+ "Opening the native modal dialog moves focus to the header Close action, traps Tab navigation inside the top layer, and makes outside content inert.",
56
+ "Close, Escape, and backdrop activation emit dsClose with close-button, escape, or backdrop reason before beginning dismissal; use the component only when every dismissal path is safe.",
57
+ "After exit motion completes, the native dialog leaves the top layer, focus returns to the previously focused element, and dsAfterClose emits.",
58
+ "Use visible label-only footer buttons. Keep at most two actions: one bold filled primary action followed by one bordered unfilled action, or two bordered unfilled actions.",
59
+ "Use negative intent for a filled destructive action and state the irreversible consequence in the content.",
60
+ "Do not open a modal from another modal; keep one blocking decision in focus at a time."
61
+ ],
62
+ "states": [
63
+ "The owner opens the modal through the open property and responds to the typed dsClose detail for application state synchronization or dismissal analytics.",
64
+ "The component mutates open to false for Close, Escape, and backdrop dismissal while retaining its rendered surface through exit motion.",
65
+ "The default slot owns task content; the named footer slot is optional and is hidden when no actions are assigned.",
66
+ "Primary actions use a bold filled button with semantic intent. Secondary and cancel actions use bordered label-only unfilled buttons.",
67
+ "The owner performs save, delete, cancel, and other product consequences before or while closing the modal."
68
+ ],
69
+ "responsiveBehavior": [
70
+ "Choose the smallest sm, md, or lg width that fits the focused task; the top-layer dialog clamps to the viewport with token-based outer space.",
71
+ "Content scrolls independently when needed while the 64px header and optional 64px footer remain fixed.",
72
+ "Avoid layouts whose minimum content width exceeds the narrow viewport clamp.",
73
+ "The native top layer escapes transformed ancestors, clipping containers, and application stacking contexts without moving slotted content."
74
+ ],
75
+ "frameworkCaveats": {
76
+ "customElements": [
77
+ "Set open as a JavaScript property when controlling the modal; inspect dsClose.detail.reason and use dsAfterClose for cleanup that must wait for exit motion and focus restoration.",
78
+ "Connect the most relevant slotted explanatory text through aria-describedby.",
79
+ "Footer actions are slotted application-owned controls and must implement their own product consequence."
80
+ ],
81
+ "react": [
82
+ "Pass open to DsModal, inspect onDsClose event detail, and use onDsAfterClose only for cleanup after exit motion and focus restoration.",
83
+ "Connect the most relevant slotted explanatory text through aria-describedby.",
84
+ "Assign footer controls with slot=\"footer\" and handle each action before updating open."
85
+ ],
86
+ "angular": [
87
+ "Bind [open], inspect $event.detail from (dsClose), and use (dsAfterClose) only for cleanup after exit motion and focus restoration.",
88
+ "Connect the most relevant slotted explanatory text through aria-describedby.",
89
+ "Assign footer controls with slot=\"footer\" and handle each action before updating open."
90
+ ]
91
+ },
92
+ "references": [
93
+ {
94
+ "label": "Storybook confirmation and footer examples",
95
+ "path": "src/wc/components/Modal/Modal.stories.ts"
96
+ },
97
+ {
98
+ "label": "Cross-browser focus, dismissal, geometry, and accessibility coverage",
99
+ "path": "tests/e2e/accessibility-overlays.spec.ts"
100
+ },
101
+ {
102
+ "label": "Reduced-motion overlay coverage",
103
+ "path": "tests/e2e/reduced-motion.spec.ts"
104
+ }
105
+ ]
106
+ },
14
107
  "api": {
15
108
  "props": {
109
+ "ariaDescribedby": {
110
+ "type": "string | undefined",
111
+ "resolvedType": "string | undefined",
112
+ "attribute": "aria-describedby",
113
+ "required": false,
114
+ "mutable": false,
115
+ "description": "Optional id reference for explanatory content in the default slot."
116
+ },
117
+ "closeAriaLabel": {
118
+ "type": "string",
119
+ "resolvedType": "string",
120
+ "attribute": "close-aria-label",
121
+ "default": "'Close'",
122
+ "required": false,
123
+ "mutable": false
124
+ },
16
125
  "heading": {
17
126
  "type": "string",
18
127
  "resolvedType": "string",
@@ -35,28 +144,46 @@
35
144
  "default": "false",
36
145
  "required": false,
37
146
  "mutable": true
38
- },
39
- "subtitle": {
40
- "type": "string | undefined",
41
- "resolvedType": "string | undefined",
42
- "attribute": "subtitle",
43
- "required": false,
44
- "mutable": false
45
147
  }
46
148
  },
47
149
  "events": [
48
150
  {
49
- "name": "dsClose",
151
+ "name": "dsAfterClose",
50
152
  "detail": "void",
51
153
  "bubbles": true,
52
154
  "cancelable": true,
53
- "composed": true
155
+ "composed": true,
156
+ "description": "Emitted after exit motion completes, the top layer closes, and focus is restored."
157
+ },
158
+ {
159
+ "name": "dsClose",
160
+ "detail": "ModalCloseDetail",
161
+ "bubbles": true,
162
+ "cancelable": true,
163
+ "composed": true,
164
+ "description": "Emitted when an internal dismissal control requests that the modal close."
54
165
  }
55
166
  ],
56
167
  "methods": [],
57
168
  "slots": []
58
169
  },
59
170
  "props": {
171
+ "ariaDescribedby": {
172
+ "type": "string | undefined",
173
+ "resolvedType": "string | undefined",
174
+ "attribute": "aria-describedby",
175
+ "required": false,
176
+ "mutable": false,
177
+ "description": "Optional id reference for explanatory content in the default slot."
178
+ },
179
+ "closeAriaLabel": {
180
+ "type": "string",
181
+ "resolvedType": "string",
182
+ "attribute": "close-aria-label",
183
+ "default": "'Close'",
184
+ "required": false,
185
+ "mutable": false
186
+ },
60
187
  "heading": {
61
188
  "type": "string",
62
189
  "resolvedType": "string",
@@ -79,22 +206,24 @@
79
206
  "default": "false",
80
207
  "required": false,
81
208
  "mutable": true
82
- },
83
- "subtitle": {
84
- "type": "string | undefined",
85
- "resolvedType": "string | undefined",
86
- "attribute": "subtitle",
87
- "required": false,
88
- "mutable": false
89
209
  }
90
210
  },
91
211
  "events": [
92
212
  {
93
- "name": "dsClose",
213
+ "name": "dsAfterClose",
94
214
  "detail": "void",
95
215
  "bubbles": true,
96
216
  "cancelable": true,
97
- "composed": true
217
+ "composed": true,
218
+ "description": "Emitted after exit motion completes, the top layer closes, and focus is restored."
219
+ },
220
+ {
221
+ "name": "dsClose",
222
+ "detail": "ModalCloseDetail",
223
+ "bubbles": true,
224
+ "cancelable": true,
225
+ "composed": true,
226
+ "description": "Emitted when an internal dismissal control requests that the modal close."
98
227
  }
99
228
  ],
100
229
  "methods": [],
@@ -122,7 +251,7 @@
122
251
  "peerDependencies": {
123
252
  "required": [
124
253
  "@ds-mo/tokens ^3.1.1",
125
- "@ds-mo/icons ^6.2.0"
254
+ "@ds-mo/icons ^6.3.0"
126
255
  ],
127
256
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
128
257
  }
@@ -133,17 +262,18 @@
133
262
  "@ds-mo/tokens"
134
263
  ],
135
264
  "registryDependencies": [
265
+ "button-unfilled",
136
266
  "text"
137
267
  ],
138
268
  "files": [
139
269
  {
140
270
  "path": "src/wc/components/Modal/Modal.css",
141
- "content": ".modal-backdrop {\n background-color: var(--color-background-shade);\n backdrop-filter: blur(var(--effect-blur-sm));\n -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n animation: modalBackdropIn var(--effect-motion-short-2) forwards;\n box-sizing: border-box;\n}\n\n.modal-backdrop--closing {\n animation: modalBackdropOut var(--effect-motion-short-3) forwards;\n}\n\n@keyframes modalBackdropIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes modalBackdropOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n.modal-dialog {\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 2 * var(--dimension-space-200));\n overflow: hidden;\n background-color: var(--color-background-primary);\n border-radius: var(--dimension-radius-300);\n box-shadow: var(--effect-elevation-elevated-floating);\n animation: modalDialogIn var(--effect-motion-short-2) forwards;\n outline: none;\n box-sizing: border-box;\n}\n\n.modal-backdrop--closing .modal-dialog {\n animation: modalDialogOut var(--effect-motion-short-3) forwards;\n}\n\n@keyframes modalDialogIn {\n from { opacity: 0; transform: scale(0.97) translateY(4px); }\n to { opacity: 1; transform: scale(1) translateY(0); }\n}\n\n@keyframes modalDialogOut {\n from { opacity: 1; transform: scale(1) translateY(0); }\n to { opacity: 0; transform: scale(0.97) translateY(4px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal-backdrop,\n .modal-dialog {\n animation: none;\n transform: none;\n }\n\n .modal-backdrop {\n opacity: 1;\n }\n\n .modal-backdrop--closing,\n .modal-backdrop--closing .modal-dialog {\n opacity: 0;\n }\n}\n\n.modal-header {\n padding: var(--dimension-space-200) var(--dimension-space-200) var(--dimension-space-100);\n flex-shrink: 0;\n}\n\n.modal-subtitle {\n margin-top: var(--dimension-space-100);\n}\n\n.modal-body {\n padding: var(--dimension-space-100) var(--dimension-space-200);\n overflow-y: auto;\n flex: 1;\n}\n\n.modal-footer {\n padding: var(--dimension-space-100) var(--dimension-space-200) var(--dimension-space-200);\n flex-shrink: 0;\n}\n\n.modal-footer:empty { display: none; }\n",
271
+ "content": ":host {\n display: contents;\n}\n\n.modal-dialog {\n display: none;\n flex-direction: column;\n max-width: none;\n max-height: calc(100vh - 2 * var(--dimension-space-200));\n margin: auto;\n padding: 0;\n overflow: hidden;\n color: var(--color-foreground-primary);\n background-color: var(--color-background-primary);\n border: 0;\n border-radius: var(--dimension-radius-050);\n box-shadow: var(--effect-elevation-elevated-floating);\n outline: none;\n box-sizing: border-box;\n}\n\n.modal-dialog[open] {\n display: flex;\n animation: modalDialogIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog::backdrop {\n background-color: var(--color-background-shade);\n animation: modalBackdropIn var(--effect-motion-short-2) forwards;\n}\n\n.modal-dialog--closing {\n animation: modalDialogOut var(--effect-motion-short-3) forwards;\n}\n\n.modal-dialog--closing::backdrop {\n animation: modalBackdropOut var(--effect-motion-short-3) forwards;\n}\n\n@keyframes modalBackdropIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes modalBackdropOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@keyframes modalDialogIn {\n from { opacity: 0; transform: scale(0.97) translateY(4px); }\n to { opacity: 1; transform: scale(1) translateY(0); }\n}\n\n@keyframes modalDialogOut {\n from { opacity: 1; transform: scale(1) translateY(0); }\n to { opacity: 0; transform: scale(0.97) translateY(4px); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal-dialog[open],\n .modal-dialog--closing {\n animation: none;\n transform: none;\n }\n\n .modal-dialog::backdrop {\n animation: none;\n opacity: 1;\n }\n\n .modal-dialog--closing,\n .modal-dialog--closing::backdrop {\n opacity: 0;\n }\n}\n\n.modal-header {\n display: flex;\n align-items: center;\n gap: var(--dimension-space-050);\n height: var(--dimension-size-800);\n padding: var(--dimension-space-200);\n flex: 0 0 auto;\n box-sizing: border-box;\n border-bottom: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n color: var(--color-foreground-primary);\n}\n\nds-text.modal-heading {\n flex: 1 1 0;\n width: auto;\n min-width: 0;\n text-align: left;\n -webkit-user-select: none;\n user-select: none;\n}\n\n.modal-close {\n flex: 0 0 auto;\n}\n\n.modal-content {\n min-height: 0;\n padding: var(--dimension-space-200);\n overflow-y: auto;\n flex: 1 1 auto;\n box-sizing: border-box;\n}\n\n.modal-footer {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: var(--dimension-space-100);\n height: var(--dimension-size-800);\n padding: var(--dimension-space-200);\n flex: 0 0 auto;\n box-sizing: border-box;\n border-top: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n}\n\n.modal-footer--empty {\n display: none;\n}\n\n.modal-footer slot {\n display: contents;\n}\n",
142
272
  "type": "registry:ui"
143
273
  },
144
274
  {
145
275
  "path": "src/wc/components/Modal/Modal.tsx",
146
- "content": "import { Component, Prop, State, Event, EventEmitter, Element, Watch, h, Host } from '@stencil/core';\nimport { resolveMotionTimeMs, TOKEN_DEFAULTS } from '../../utils';\n\nexport type ModalWidth = 'sm' | 'md' | 'lg';\n\nconst WIDTH_MAP: Record<ModalWidth, string> = {\n sm: 'var(--dimension-modal-width-sm)',\n md: 'var(--dimension-modal-width-md)',\n lg: 'var(--dimension-modal-width-lg)',\n};\n\nconst FOCUSABLE_SEL = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'iframe',\n '[contenteditable=\"true\"]',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nlet modalIdCounter = 0;\nconst activeModalStack: object[] = [];\nconst modalInertState = new WeakMap<HTMLElement, { count: number; wasInert: boolean }>();\n\nfunction makeInert(element: HTMLElement) {\n const state = modalInertState.get(element);\n if (state) {\n state.count += 1;\n return;\n }\n modalInertState.set(element, { count: 1, wasInert: element.inert });\n element.inert = true;\n}\n\nfunction releaseInert(element: HTMLElement) {\n const state = modalInertState.get(element);\n if (!state) return;\n state.count -= 1;\n if (state.count > 0) return;\n element.inert = state.wasInert;\n modalInertState.delete(element);\n}\n\n@Component({\n tag: 'ds-modal',\n styleUrl: 'Modal.css',\n scoped: true,\n})\nexport class Modal {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() heading!: string;\n @Prop() subtitle: string | undefined;\n @Prop() modalWidth: ModalWidth | string = 'md';\n\n @State() private shouldRender: boolean = false;\n @State() private closing: boolean = false;\n\n @Event() dsClose!: EventEmitter<void>;\n\n private instanceId = ++modalIdCounter;\n private titleId = `ds-modal-title-${modalIdCounter}`;\n private subtitleId = `ds-modal-subtitle-${modalIdCounter}`;\n private previousFocus: HTMLElement | null = null;\n private escapeHandler: ((e: KeyboardEvent) => void) | null = null;\n private tabHandler: ((e: KeyboardEvent) => void) | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private inertedElements: HTMLElement[] = [];\n\n componentDidLoad() {\n if (this.open) this.onOpenChange(true);\n }\n\n disconnectedCallback() {\n this.teardownListeners();\n this.deactivateModal();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (isOpen) {\n this.teardownListeners();\n this.shouldRender = true;\n this.closing = false;\n this.previousFocus = document.activeElement as HTMLElement | null;\n this.activateModal();\n requestAnimationFrame(() => {\n this.focusFirst();\n this.setupListeners();\n });\n } else if (this.shouldRender) {\n this.closing = true;\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n }\n\n private focusFirst() {\n const dialog = this.el.querySelector('.modal-dialog') as HTMLElement | null;\n if (!dialog) return;\n const focusables = this.getFocusables(dialog);\n (focusables[0] ?? dialog).focus();\n }\n\n private getFocusables(root: HTMLElement): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SEL)).filter(\n el => !el.hasAttribute('disabled') && el.getAttribute('aria-hidden') !== 'true'\n );\n }\n\n private setupListeners() {\n this.escapeHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.isTopModal) { e.preventDefault(); this.close(); }\n };\n\n this.tabHandler = (e: KeyboardEvent) => {\n if (e.key !== 'Tab' || !this.isTopModal) return;\n const dialog = this.el.querySelector('.modal-dialog') as HTMLElement | null;\n if (!dialog) return;\n const focusables = this.getFocusables(dialog);\n if (!focusables.length) { e.preventDefault(); dialog.focus(); return; }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (e.shiftKey) {\n if (active === first || active === dialog || !dialog.contains(active)) {\n e.preventDefault(); last.focus();\n }\n } else {\n if (active === last || !dialog.contains(active)) {\n e.preventDefault(); first.focus();\n }\n }\n };\n\n document.addEventListener('keydown', this.escapeHandler);\n document.addEventListener('keydown', this.tabHandler);\n }\n\n private teardownListeners() {\n if (this.escapeHandler) {\n document.removeEventListener('keydown', this.escapeHandler);\n this.escapeHandler = null;\n }\n if (this.tabHandler) {\n document.removeEventListener('keydown', this.tabHandler);\n this.tabHandler = null;\n }\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private close() {\n if (!this.open || this.closing || !this.isTopModal) return;\n this.dsClose.emit();\n this.open = false;\n }\n\n private handleBackdropMouseDown(e: MouseEvent) {\n if (e.target === e.currentTarget && this.isTopModal) this.close();\n }\n\n private get isTopModal(): boolean {\n return activeModalStack[activeModalStack.length - 1] === this;\n }\n\n /** Inert every sibling branch outside the modal without assuming it is a body child. */\n private activateModal() {\n if (!activeModalStack.includes(this)) activeModalStack.push(this);\n if (this.inertedElements.length) return;\n\n let branch: HTMLElement = this.el;\n let parent = branch.parentElement;\n while (parent) {\n for (const sibling of Array.from(parent.children)) {\n if (sibling === branch || !(sibling instanceof HTMLElement)) continue;\n makeInert(sibling);\n this.inertedElements.push(sibling);\n }\n if (parent === document.body) break;\n branch = parent;\n parent = parent.parentElement;\n }\n }\n\n private deactivateModal() {\n const stackIndex = activeModalStack.lastIndexOf(this);\n if (stackIndex >= 0) activeModalStack.splice(stackIndex, 1);\n for (const element of this.inertedElements) releaseInert(element);\n this.inertedElements = [];\n }\n\n private get resolvedWidth(): string {\n if (this.modalWidth === 'sm' || this.modalWidth === 'md' || this.modalWidth === 'lg') {\n return WIDTH_MAP[this.modalWidth];\n }\n return this.modalWidth as string;\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort3, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n this.shouldRender = false;\n this.closing = false;\n this.teardownListeners();\n this.deactivateModal();\n this.previousFocus?.focus?.();\n this.previousFocus = null;\n }\n\n render() {\n // Always render slots so scoped-mode slot distribution doesn't leak content.\n // The backdrop is hidden via CSS when not active.\n const hidden = !this.shouldRender && !this.closing;\n\n return (\n <Host style={{ display: 'contents' }}>\n <div\n class={{ 'modal-backdrop': true, 'modal-backdrop--closing': this.closing, 'modal-backdrop--hidden': hidden }}\n onMouseDown={(e: MouseEvent) => this.handleBackdropMouseDown(e)}\n style={{ position: 'fixed', inset: '0', zIndex: '9999', display: hidden ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center', padding: 'var(--dimension-space-200)' }}\n >\n <div\n class={{ 'modal-dialog': true, 'modal-dialog--closing': this.closing }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={this.titleId}\n aria-describedby={this.subtitle ? this.subtitleId : undefined}\n tabIndex={-1}\n style={{ width: `min(${this.resolvedWidth}, calc(100vw - 2 * var(--dimension-space-200)))` }}\n >\n <div class=\"modal-header\">\n <ds-text\n as=\"h2\"\n variant=\"text-title-small\"\n textId={this.titleId}\n >\n {this.heading}\n </ds-text>\n {this.subtitle && (\n <ds-text\n class=\"modal-subtitle\"\n as=\"div\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n wrap=\"balance\"\n textId={this.subtitleId}\n >\n {this.subtitle}\n </ds-text>\n )}\n </div>\n <div class=\"modal-body\">\n <slot />\n </div>\n <div class=\"modal-footer\">\n <slot name=\"footer\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n}\n",
276
+ "content": "import { Component, Prop, State, Event, EventEmitter, Element, Watch, h, Host } from '@stencil/core';\nimport { resolveMotionTimeMs, TOKEN_DEFAULTS } from '../../utils';\n\nexport type ModalWidth = 'sm' | 'md' | 'lg';\nexport type ModalCloseReason = 'close-button' | 'escape' | 'backdrop';\n\nexport interface ModalCloseDetail {\n reason: ModalCloseReason;\n originalEvent: Event;\n}\n\nconst WIDTH_MAP: Record<ModalWidth, string> = {\n sm: 'var(--dimension-modal-width-sm)',\n md: 'var(--dimension-modal-width-md)',\n lg: 'var(--dimension-modal-width-lg)',\n};\n\nconst FOCUSABLE_SEL = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'iframe',\n '[contenteditable=\"true\"]',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nlet modalIdCounter = 0;\n\n@Component({\n tag: 'ds-modal',\n styleUrl: 'Modal.css',\n scoped: true,\n})\nexport class Modal {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() heading!: string;\n @Prop() closeAriaLabel: string = 'Close';\n @Prop() modalWidth: ModalWidth | string = 'md';\n /** Optional id reference for explanatory content in the default slot. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n\n @State() private closing: boolean = false;\n @State() private hasFooter: boolean = false;\n\n /** Emitted when an internal dismissal control requests that the modal close. */\n @Event() dsClose!: EventEmitter<ModalCloseDetail>;\n /** Emitted after exit motion completes, the top layer closes, and focus is restored. */\n @Event() dsAfterClose!: EventEmitter<void>;\n\n private titleId = `ds-modal-title-${++modalIdCounter}`;\n private dialogEl: HTMLDialogElement | null = null;\n private previousFocus: HTMLElement | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n\n componentDidLoad() {\n this.updateFooterPresence();\n if (this.open) this.onOpenChange(true);\n }\n\n disconnectedCallback() {\n this.clearCloseTimer();\n if (this.dialogEl?.open) this.dialogEl.close();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (isOpen) {\n this.clearCloseTimer();\n const alreadyOpen = !!this.dialogEl?.open;\n this.closing = false;\n if (!this.dialogEl || alreadyOpen) return;\n this.previousFocus = document.activeElement as HTMLElement | null;\n this.dialogEl.showModal();\n requestAnimationFrame(() => {\n if (this.open && this.dialogEl?.open) this.focusClose();\n });\n } else if (this.dialogEl?.open) {\n this.closing = true;\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n }\n\n private focusClose() {\n const close = this.el.querySelector('ds-button-unfilled.modal-close') as\n | (HTMLElement & { setFocus?: () => Promise<void> })\n | null;\n void close?.setFocus?.();\n }\n\n private clearCloseTimer() {\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private requestClose(reason: ModalCloseReason, originalEvent: Event) {\n if (!this.open || this.closing || !this.dialogEl?.open) return;\n this.dsClose.emit({ reason, originalEvent });\n this.open = false;\n }\n\n private updateFooterPresence(slot?: HTMLSlotElement) {\n const assignedNodes = slot?.assignedNodes({ flatten: true }) ?? [];\n this.hasFooter =\n assignedNodes.some(node => node.nodeType !== Node.TEXT_NODE || !!node.textContent?.trim()) ||\n this.el.querySelector('[slot=\"footer\"]') !== null;\n }\n\n private handleCancel(event: Event) {\n event.preventDefault();\n this.requestClose('escape', event);\n }\n\n private handleBackdropPointerDown(event: PointerEvent) {\n if (event.target === this.dialogEl) this.requestClose('backdrop', event);\n }\n\n private handleKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab' || !this.dialogEl) return;\n const focusables = Array.from(\n this.dialogEl.querySelectorAll<HTMLElement>(FOCUSABLE_SEL)\n ).filter(element =>\n !element.hasAttribute('disabled') && element.getAttribute('aria-hidden') !== 'true'\n );\n if (!focusables.length) {\n event.preventDefault();\n this.dialogEl.focus();\n return;\n }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (event.shiftKey && (active === first || active === this.dialogEl)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n private get resolvedWidth(): string {\n if (this.modalWidth === 'sm' || this.modalWidth === 'md' || this.modalWidth === 'lg') {\n return WIDTH_MAP[this.modalWidth];\n }\n return this.modalWidth as string;\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort3, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n if (!this.dialogEl?.open) return;\n this.dialogEl.close();\n this.closing = false;\n this.clearCloseTimer();\n this.previousFocus?.focus?.();\n this.previousFocus = null;\n this.dsAfterClose.emit();\n }\n\n render() {\n return (\n <Host>\n <dialog\n ref={(element?: HTMLDialogElement) => {\n this.dialogEl = element ?? null;\n }}\n class={{ 'modal-dialog': true, 'modal-dialog--closing': this.closing }}\n aria-labelledby={this.titleId}\n aria-describedby={this.ariaDescribedby}\n onCancel={(event: Event) => this.handleCancel(event)}\n onPointerDown={(event: PointerEvent) => this.handleBackdropPointerDown(event)}\n onKeyDown={(event: KeyboardEvent) => this.handleKeyDown(event)}\n style={{\n width: `min(${this.resolvedWidth}, calc(100vw - 2 * var(--dimension-space-200)))`,\n }}\n >\n <div class=\"modal-header\">\n <ds-text\n class=\"modal-heading\"\n as=\"h2\"\n variant=\"text-title-medium\"\n emphasis\n color=\"primary\"\n lineTruncation={1}\n textId={this.titleId}\n >\n {this.heading}\n </ds-text>\n <ds-button-unfilled\n class=\"modal-close\"\n variant=\"icon\"\n icon=\"Cross\"\n size=\"md\"\n aria-label={this.closeAriaLabel}\n activeFill={false}\n hasBorder={false}\n onDsClick={(event: CustomEvent<MouseEvent>) =>\n this.requestClose('close-button', event.detail)\n }\n />\n </div>\n <div class=\"modal-content\">\n <slot />\n </div>\n <div\n class={{\n 'modal-footer': true,\n 'modal-footer--empty': !this.hasFooter,\n }}\n >\n <slot\n name=\"footer\"\n onSlotchange={(event: Event) =>\n this.updateFooterPresence(event.currentTarget as HTMLSlotElement)\n }\n />\n </div>\n </dialog>\n </Host>\n );\n }\n}\n",
147
277
  "type": "registry:ui"
148
278
  }
149
279
  ]
@@ -597,7 +597,7 @@
597
597
  "peerDependencies": {
598
598
  "required": [
599
599
  "@ds-mo/tokens ^3.1.1",
600
- "@ds-mo/icons ^6.2.0"
600
+ "@ds-mo/icons ^6.3.0"
601
601
  ],
602
602
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
603
603
  }
@@ -48,7 +48,7 @@
48
48
  }
49
49
  ],
50
50
  "commonlyComposedWith": [
51
- "component:ds-card",
51
+ "component:ds-card-setting",
52
52
  "component:ds-text"
53
53
  ],
54
54
  "accessibility": [
@@ -216,7 +216,7 @@
216
216
  "peerDependencies": {
217
217
  "required": [
218
218
  "@ds-mo/tokens ^3.1.1",
219
- "@ds-mo/icons ^6.2.0"
219
+ "@ds-mo/icons ^6.3.0"
220
220
  ],
221
221
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
222
222
  }
@@ -313,7 +313,7 @@
313
313
  "peerDependencies": {
314
314
  "required": [
315
315
  "@ds-mo/tokens ^3.1.1",
316
- "@ds-mo/icons ^6.2.0"
316
+ "@ds-mo/icons ^6.3.0"
317
317
  ],
318
318
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
319
319
  }
@@ -355,7 +355,7 @@
355
355
  "peerDependencies": {
356
356
  "required": [
357
357
  "@ds-mo/tokens ^3.1.1",
358
- "@ds-mo/icons ^6.2.0"
358
+ "@ds-mo/icons ^6.3.0"
359
359
  ],
360
360
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
361
361
  }
@@ -474,7 +474,7 @@
474
474
  "peerDependencies": {
475
475
  "required": [
476
476
  "@ds-mo/tokens ^3.1.1",
477
- "@ds-mo/icons ^6.2.0"
477
+ "@ds-mo/icons ^6.3.0"
478
478
  ],
479
479
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
480
480
  }
@@ -346,7 +346,7 @@
346
346
  "peerDependencies": {
347
347
  "required": [
348
348
  "@ds-mo/tokens ^3.1.1",
349
- "@ds-mo/icons ^6.2.0"
349
+ "@ds-mo/icons ^6.3.0"
350
350
  ],
351
351
  "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
352
352
  }