@ds-mo/ui 14.22.2 → 14.24.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 (215) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +14 -7
  3. package/dist/agent.json +112 -26
  4. package/dist/angular/ds-bar-title.d.ts +1 -1
  5. package/dist/angular/ds-bar-title.js +3 -3
  6. package/dist/angular/ds-button-filled.d.ts +3 -1
  7. package/dist/angular/ds-button-filled.js +7 -4
  8. package/dist/angular/ds-button-unfilled.d.ts +3 -1
  9. package/dist/angular/ds-button-unfilled.js +7 -4
  10. package/dist/angular/ds-panel-tools.d.ts +7 -1
  11. package/dist/angular/ds-panel-tools.js +7 -4
  12. package/dist/angular/ds-shell-app.d.ts +7 -1
  13. package/dist/angular/ds-shell-app.js +5 -2
  14. package/dist/angular/ds-shell-tools.d.ts +4 -1
  15. package/dist/angular/ds-shell-tools.js +7 -4
  16. package/dist/components/ds-agent-questionnaire.js +1 -1
  17. package/dist/components/ds-agent-response.js +1 -1
  18. package/dist/components/ds-banner.js +1 -1
  19. package/dist/components/ds-bar-nav.js +1 -1
  20. package/dist/components/ds-bar-title.js +1 -1
  21. package/dist/components/ds-bar-workflow.js +1 -1
  22. package/dist/components/ds-button-filled.js +1 -1
  23. package/dist/components/ds-button-unfilled.js +1 -1
  24. package/dist/components/ds-card-chart.js +1 -1
  25. package/dist/components/ds-card-setting.js +1 -1
  26. package/dist/components/ds-code-block.js +1 -1
  27. package/dist/components/ds-filter-menu.js +1 -1
  28. package/dist/components/ds-input.js +1 -1
  29. package/dist/components/ds-markdown.js +1 -1
  30. package/dist/components/ds-message-actions.js +1 -1
  31. package/dist/components/ds-message-composer.js +1 -1
  32. package/dist/components/ds-message-scroller.js +1 -1
  33. package/dist/components/ds-message-scroller.js.map +1 -1
  34. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  35. package/dist/components/ds-modal.js +1 -1
  36. package/dist/components/ds-pagination.js +1 -1
  37. package/dist/components/ds-panel-nav.js +1 -1
  38. package/dist/components/ds-panel-tool-header.js +1 -1
  39. package/dist/components/ds-panel-tool-search.js +1 -1
  40. package/dist/components/ds-panel-tools.js +1 -1
  41. package/dist/components/ds-select.js +1 -1
  42. package/dist/components/ds-shell-app.js +1 -1
  43. package/dist/components/ds-shell-app.js.map +1 -1
  44. package/dist/components/ds-shell-tools.js +1 -1
  45. package/dist/components/ds-table-filter.js +1 -1
  46. package/dist/components/ds-table-group.js +1 -1
  47. package/dist/components/ds-table-saved-views.js +1 -1
  48. package/dist/components/ds-table-search.js +1 -1
  49. package/dist/components/ds-table-sort.js +1 -1
  50. package/dist/components/ds-table.js +1 -1
  51. package/dist/components/ds-toast.js +1 -1
  52. package/dist/components/p-BR1ayqMU.js +2 -0
  53. package/dist/components/p-BR1ayqMU.js.map +1 -0
  54. package/dist/components/{p-DzVYGJJv.js → p-BSRIF4m8.js} +2 -2
  55. package/dist/components/{p-FitB53eM.js → p-BZlhBJZL.js} +2 -2
  56. package/dist/components/{p-BmqnrfTf.js → p-Bo6uW_8T.js} +2 -2
  57. package/dist/components/{p-BIaHDBfP.js → p-Bq8svcPs.js} +2 -2
  58. package/dist/components/{p-DAFR4U9P.js → p-BqbdABo3.js} +2 -2
  59. package/dist/components/{p-nVv71P3f.js → p-CEXchU5N.js} +2 -2
  60. package/dist/components/p-CEfxLsbX.js +2 -0
  61. package/dist/components/p-CEfxLsbX.js.map +1 -0
  62. package/dist/components/{p-DXbryJHz.js → p-CYMKmJ2s.js} +2 -2
  63. package/dist/components/p-CZHIkVgj.js +2 -0
  64. package/dist/components/p-CZHIkVgj.js.map +1 -0
  65. package/dist/components/{p-CL0tECXf.js → p-CZjjDhUl.js} +2 -2
  66. package/dist/components/p-C_FFU6W3.js +2 -0
  67. package/dist/components/p-C_FFU6W3.js.map +1 -0
  68. package/dist/components/{p-CilwchJH.js → p-CglHiyY2.js} +2 -2
  69. package/dist/components/{p-9M0ak2q_.js → p-DLaN9BPq.js} +2 -2
  70. package/dist/components/{p-CrYJnDev.js → p-DiS5BgLA.js} +2 -2
  71. package/dist/components/{p-DiyO47xX.js → p-PXdvBMXB.js} +2 -2
  72. package/dist/components/{p-7MWaWl8w.js → p-kslioYhf.js} +2 -2
  73. package/dist/components/p-kslioYhf.js.map +1 -0
  74. package/dist/docs/components.json +434 -32
  75. package/dist/lib/components/BarTitle/bar-title-types.d.ts +54 -1
  76. package/dist/lib/components/ButtonFilled/ButtonFilled.d.ts +12 -1
  77. package/dist/lib/components/PanelTools/panel-tools-types.d.ts +76 -1
  78. package/dist/lib/components/ShellApp/shell-app-types.d.ts +6 -2
  79. package/dist/lib/shell/index.d.ts +2 -1
  80. package/dist/lib/shell/index.js.map +2 -2
  81. package/dist/mcp-data/registry/agent-activity.json +1 -1
  82. package/dist/mcp-data/registry/agent-questionnaire.json +1 -1
  83. package/dist/mcp-data/registry/agent-response.json +1 -1
  84. package/dist/mcp-data/registry/agent-source-list.json +1 -1
  85. package/dist/mcp-data/registry/agent-tool-call.json +1 -1
  86. package/dist/mcp-data/registry/attachment-list.json +1 -1
  87. package/dist/mcp-data/registry/avatar.json +1 -1
  88. package/dist/mcp-data/registry/badge.json +1 -1
  89. package/dist/mcp-data/registry/banner.json +1 -1
  90. package/dist/mcp-data/registry/bar-action.json +1 -1
  91. package/dist/mcp-data/registry/bar-nav.json +1 -1
  92. package/dist/mcp-data/registry/bar-title.json +35 -13
  93. package/dist/mcp-data/registry/bar-workflow.json +1 -1
  94. package/dist/mcp-data/registry/breadcrumb.json +1 -1
  95. package/dist/mcp-data/registry/button-filled.json +61 -7
  96. package/dist/mcp-data/registry/button-unfilled.json +61 -7
  97. package/dist/mcp-data/registry/card-chart.json +1 -1
  98. package/dist/mcp-data/registry/card-overview.json +1 -1
  99. package/dist/mcp-data/registry/card-setting.json +1 -1
  100. package/dist/mcp-data/registry/chart-legend.json +1 -1
  101. package/dist/mcp-data/registry/chart.json +1 -1
  102. package/dist/mcp-data/registry/checkbox.json +1 -1
  103. package/dist/mcp-data/registry/chip.json +1 -1
  104. package/dist/mcp-data/registry/code-block.json +1 -1
  105. package/dist/mcp-data/registry/conversation-list-item.json +1 -1
  106. package/dist/mcp-data/registry/conversation-list-section.json +1 -1
  107. package/dist/mcp-data/registry/conversation-list.json +1 -1
  108. package/dist/mcp-data/registry/divider.json +1 -1
  109. package/dist/mcp-data/registry/empty-state.json +1 -1
  110. package/dist/mcp-data/registry/field.json +1 -1
  111. package/dist/mcp-data/registry/filter-menu.json +1 -1
  112. package/dist/mcp-data/registry/icon.json +1 -1
  113. package/dist/mcp-data/registry/input.json +1 -1
  114. package/dist/mcp-data/registry/loader.json +1 -1
  115. package/dist/mcp-data/registry/markdown.json +1 -1
  116. package/dist/mcp-data/registry/menu.json +1 -1
  117. package/dist/mcp-data/registry/message-actions.json +1 -1
  118. package/dist/mcp-data/registry/message-bubble.json +1 -1
  119. package/dist/mcp-data/registry/message-composer.json +1 -1
  120. package/dist/mcp-data/registry/message-scroller.json +9 -2
  121. package/dist/mcp-data/registry/message.json +1 -1
  122. package/dist/mcp-data/registry/mobile-bar-nav.json +1 -1
  123. package/dist/mcp-data/registry/mobile-header.json +1 -1
  124. package/dist/mcp-data/registry/mobile-section-switcher.json +1 -1
  125. package/dist/mcp-data/registry/mobile-sheet-nav.json +1 -1
  126. package/dist/mcp-data/registry/modal.json +1 -1
  127. package/dist/mcp-data/registry/pagination.json +1 -1
  128. package/dist/mcp-data/registry/panel-nav.json +1 -1
  129. package/dist/mcp-data/registry/panel-sub-nav.json +1 -1
  130. package/dist/mcp-data/registry/panel-tool-header.json +1 -1
  131. package/dist/mcp-data/registry/panel-tool-search.json +1 -1
  132. package/dist/mcp-data/registry/panel-tools.json +62 -15
  133. package/dist/mcp-data/registry/paper-texture.json +1 -1
  134. package/dist/mcp-data/registry/radio.json +1 -1
  135. package/dist/mcp-data/registry/registry.json +112 -26
  136. package/dist/mcp-data/registry/scroll-overlay.json +1 -1
  137. package/dist/mcp-data/registry/select.json +1 -1
  138. package/dist/mcp-data/registry/shell-app.json +26 -3
  139. package/dist/mcp-data/registry/shell-page.json +1 -1
  140. package/dist/mcp-data/registry/shell-tools.json +39 -6
  141. package/dist/mcp-data/registry/skeleton.json +1 -1
  142. package/dist/mcp-data/registry/slider.json +1 -1
  143. package/dist/mcp-data/registry/swatch-picker.json +1 -1
  144. package/dist/mcp-data/registry/switch.json +1 -1
  145. package/dist/mcp-data/registry/tab-group.json +1 -1
  146. package/dist/mcp-data/registry/table-filter.json +1 -1
  147. package/dist/mcp-data/registry/table-group.json +1 -1
  148. package/dist/mcp-data/registry/table-saved-views.json +1 -1
  149. package/dist/mcp-data/registry/table-search.json +1 -1
  150. package/dist/mcp-data/registry/table-sort.json +1 -1
  151. package/dist/mcp-data/registry/table-toolbar.json +1 -1
  152. package/dist/mcp-data/registry/table.json +1 -1
  153. package/dist/mcp-data/registry/tag.json +1 -1
  154. package/dist/mcp-data/registry/text.json +1 -1
  155. package/dist/mcp-data/registry/toast.json +1 -1
  156. package/dist/mcp-data/registry/tooltip-chart.json +1 -1
  157. package/dist/mcp-data/registry/tooltip.json +1 -1
  158. package/dist/mcp-data/registry/typing-indicator.json +1 -1
  159. package/dist/react/ds-button-filled.d.ts +1 -0
  160. package/dist/react/ds-button-filled.js +4 -1
  161. package/dist/react/ds-button-unfilled.d.ts +1 -0
  162. package/dist/react/ds-button-unfilled.js +1 -0
  163. package/dist/react/ds-panel-tools.d.ts +2 -1
  164. package/dist/react/ds-panel-tools.js +1 -0
  165. package/dist/react/ds-shell-app.d.ts +2 -1
  166. package/dist/react/ds-shell-app.js +1 -0
  167. package/dist/react/ds-shell-tools.d.ts +2 -1
  168. package/dist/react/ds-shell-tools.js +2 -1
  169. package/dist/types/components/BarTitle/BarTitle.d.ts +19 -6
  170. package/dist/types/components/BarTitle/bar-title-actions.d.ts +11 -0
  171. package/dist/types/components/BarTitle/bar-title-types.d.ts +54 -1
  172. package/dist/types/components/BarTitle/index.d.ts +1 -1
  173. package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +12 -1
  174. package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +12 -1
  175. package/dist/types/components/MessageScroller/MessageScroller.d.ts +14 -0
  176. package/dist/types/components/MessageScroller/message-scroller-transcript.d.ts +9 -0
  177. package/dist/types/components/PanelTools/PanelTools.d.ts +17 -1
  178. package/dist/types/components/PanelTools/index.d.ts +1 -1
  179. package/dist/types/components/PanelTools/panel-tools-types.d.ts +76 -1
  180. package/dist/types/components/PanelTools/panel-tools-utils.d.ts +17 -1
  181. package/dist/types/components/ShellApp/ShellApp.d.ts +16 -1
  182. package/dist/types/components/ShellApp/shell-app-types.d.ts +6 -2
  183. package/dist/types/components/ShellTools/ShellTools.d.ts +5 -1
  184. package/dist/types/components/ShellTools/index.d.ts +1 -1
  185. package/dist/types/components.d.ts +94 -6
  186. package/dist/types/shell/index.d.ts +2 -1
  187. package/dist/types/utils/button-story-foundation.d.ts +2 -0
  188. package/dist/vue/ds-bar-title.js +1 -0
  189. package/dist/vue/ds-button-filled.js +6 -2
  190. package/dist/vue/ds-button-unfilled.js +4 -0
  191. package/dist/vue/ds-panel-tools.js +3 -0
  192. package/dist/vue/ds-shell-app.js +2 -0
  193. package/dist/vue/ds-shell-tools.js +5 -2
  194. package/package.json +3 -3
  195. package/dist/components/p--68f7nIx.js +0 -2
  196. package/dist/components/p--68f7nIx.js.map +0 -1
  197. package/dist/components/p-7MWaWl8w.js.map +0 -1
  198. package/dist/components/p-BnTPuV3x.js +0 -2
  199. package/dist/components/p-BnTPuV3x.js.map +0 -1
  200. package/dist/components/p-COFw_fcj.js +0 -2
  201. package/dist/components/p-COFw_fcj.js.map +0 -1
  202. package/dist/components/p-CvHrCyuz.js +0 -2
  203. package/dist/components/p-CvHrCyuz.js.map +0 -1
  204. /package/dist/components/{p-DzVYGJJv.js.map → p-BSRIF4m8.js.map} +0 -0
  205. /package/dist/components/{p-FitB53eM.js.map → p-BZlhBJZL.js.map} +0 -0
  206. /package/dist/components/{p-BmqnrfTf.js.map → p-Bo6uW_8T.js.map} +0 -0
  207. /package/dist/components/{p-BIaHDBfP.js.map → p-Bq8svcPs.js.map} +0 -0
  208. /package/dist/components/{p-DAFR4U9P.js.map → p-BqbdABo3.js.map} +0 -0
  209. /package/dist/components/{p-nVv71P3f.js.map → p-CEXchU5N.js.map} +0 -0
  210. /package/dist/components/{p-DXbryJHz.js.map → p-CYMKmJ2s.js.map} +0 -0
  211. /package/dist/components/{p-CL0tECXf.js.map → p-CZjjDhUl.js.map} +0 -0
  212. /package/dist/components/{p-CilwchJH.js.map → p-CglHiyY2.js.map} +0 -0
  213. /package/dist/components/{p-9M0ak2q_.js.map → p-DLaN9BPq.js.map} +0 -0
  214. /package/dist/components/{p-CrYJnDev.js.map → p-DiS5BgLA.js.map} +0 -0
  215. /package/dist/components/{p-DiyO47xX.js.map → p-PXdvBMXB.js.map} +0 -0
@@ -18,7 +18,8 @@
18
18
  "A secondary command accompanies the single filled primary action in a local decision area.",
19
19
  "A toolbar, tool rail, overflow area, or shell region needs a compact icon action.",
20
20
  "A command represents a persistent toggle or mode whose active state remains meaningful after activation.",
21
- "A row, card, or toolbar needs an overflow or more-options menu; this is the only component that carries that role."
21
+ "A row, card, or toolbar needs an overflow or more-options menu; this is the only component that carries that role.",
22
+ "A secondary command has closely related alternatives and needs split mode without losing its border, surface, active, variant, or loading treatment."
22
23
  ],
23
24
  "avoidWhen": [
24
25
  "The command is the single primary action in a local decision area; use ButtonFilled instead.",
@@ -53,6 +54,7 @@
53
54
  ],
54
55
  "accessibility": [
55
56
  "Provide an explicit accessible name for every icon-only action.",
57
+ "In split mode, provide menuAriaLabel for the appended ChevronDown segment, pass controls with the Menu id, and synchronize expanded with the Menu open state.",
56
58
  "Set hasMenu when the button triggers a Menu; it implies aria-haspopup of menu. Pass controls with the Menu id and expanded from the same open state given to Menu. Expanded popup triggers hold only the pressed wash while open, without changing their resting foreground; expanded disclosure controls without popup semantics retain the normal resting appearance.",
57
59
  "Give an icon-only menu button an accessible name that states the role, such as More options or Customize table, because no visible label or chevron carries it.",
58
60
  "Use haspopup directly for a non-menu popup such as a listbox or dialog; those cases get no chevron.",
@@ -78,7 +80,9 @@
78
80
  "Icon and icon-label loading replace the icon in place. Label-only loading centers the loader while preserving the button's measured width.",
79
81
  "hasMenu covers two distinct menu-button shapes and the variant selects between them. With label or icon-label the action has a menu, and a trailing chevron carries the affordance. With icon the button is a menu and no chevron is added.",
80
82
  "Because the icon-only shape has no chevron, its glyph must convey the menu on its own. Use Ellipses for a generic more-options or overflow control. Use a specific icon when the menu has a named purpose, such as Table for Customize table.",
81
- "An open popup is a transient pressed state rather than a selected one. While expanded is true and haspopup or hasMenu identifies a popup, the pressed wash is held for the popup's complete rendered lifecycle and survives hover without promoting foreground or selected fill, including when chrome opts out of selected fills with activeFill set to false. Expanded disclosure buttons without popup semantics remain visually neutral."
83
+ "An open popup is a transient pressed state rather than a selected one. While expanded is true and haspopup or hasMenu identifies a popup, the pressed wash is held for the popup's complete rendered lifecycle and survives hover without promoting foreground or selected fill, including when chrome opts out of selected fills with activeFill set to false. Expanded disclosure buttons without popup semantics remain visually neutral.",
84
+ "Split mode supports label, icon, and icon-label primary segments. The primary segment emits dsClick and the appended menu segment emits dsMenuClick.",
85
+ "Split mode keeps both segments flush and uses a tertiary surface-aware separator. Bordered mode also preserves the shared outer border; borderless mode limits the separator to the content line height."
82
86
  ],
83
87
  "responsiveBehavior": [
84
88
  "The consuming layout decides when an icon-label action becomes icon-only and must provide an explicit accessible name after changing variants. Table caption Customize is one such layout: below 900px it uses the icon shape while keeping the same neutral resting foreground regardless of the controlled column configuration.",
@@ -270,6 +274,15 @@
270
274
  "mutable": false,
271
275
  "description": "Whether the visible label uses the emphasized weight."
272
276
  },
277
+ "menuAriaLabel": {
278
+ "type": "string",
279
+ "resolvedType": "string",
280
+ "attribute": "menu-aria-label",
281
+ "default": "'More options'",
282
+ "required": false,
283
+ "mutable": false,
284
+ "description": "Accessible name for the appended menu segment in split mode."
285
+ },
273
286
  "pressScale": {
274
287
  "type": "boolean",
275
288
  "resolvedType": "boolean",
@@ -305,6 +318,15 @@
305
318
  "mutable": false,
306
319
  "description": "Control density (height, padding, icon, type)."
307
320
  },
321
+ "split": {
322
+ "type": "boolean",
323
+ "resolvedType": "boolean",
324
+ "attribute": "split",
325
+ "default": "false",
326
+ "required": false,
327
+ "mutable": false,
328
+ "description": "Append a separate ChevronDown menu segment while preserving this button's\nvariant, size, surface, active, loading, border, and width treatment."
329
+ },
308
330
  "type": {
309
331
  "type": "'button' | 'submit' | 'reset'",
310
332
  "resolvedType": "\"button\" | \"reset\" | \"submit\"",
@@ -347,12 +369,19 @@
347
369
  "bubbles": true,
348
370
  "cancelable": true,
349
371
  "composed": true
372
+ },
373
+ {
374
+ "name": "dsMenuClick",
375
+ "detail": "MouseEvent",
376
+ "bubbles": true,
377
+ "cancelable": true,
378
+ "composed": true
350
379
  }
351
380
  ],
352
381
  "methods": [
353
382
  {
354
383
  "name": "setFocus",
355
- "signature": "setFocus() => Promise<void>"
384
+ "signature": "setFocus(segment?: \"primary\" | \"menu\") => Promise<void>"
356
385
  }
357
386
  ],
358
387
  "slots": []
@@ -522,6 +551,15 @@
522
551
  "mutable": false,
523
552
  "description": "Whether the visible label uses the emphasized weight."
524
553
  },
554
+ "menuAriaLabel": {
555
+ "type": "string",
556
+ "resolvedType": "string",
557
+ "attribute": "menu-aria-label",
558
+ "default": "'More options'",
559
+ "required": false,
560
+ "mutable": false,
561
+ "description": "Accessible name for the appended menu segment in split mode."
562
+ },
525
563
  "pressScale": {
526
564
  "type": "boolean",
527
565
  "resolvedType": "boolean",
@@ -557,6 +595,15 @@
557
595
  "mutable": false,
558
596
  "description": "Control density (height, padding, icon, type)."
559
597
  },
598
+ "split": {
599
+ "type": "boolean",
600
+ "resolvedType": "boolean",
601
+ "attribute": "split",
602
+ "default": "false",
603
+ "required": false,
604
+ "mutable": false,
605
+ "description": "Append a separate ChevronDown menu segment while preserving this button's\nvariant, size, surface, active, loading, border, and width treatment."
606
+ },
560
607
  "type": {
561
608
  "type": "'button' | 'submit' | 'reset'",
562
609
  "resolvedType": "\"button\" | \"reset\" | \"submit\"",
@@ -599,12 +646,19 @@
599
646
  "bubbles": true,
600
647
  "cancelable": true,
601
648
  "composed": true
649
+ },
650
+ {
651
+ "name": "dsMenuClick",
652
+ "detail": "MouseEvent",
653
+ "bubbles": true,
654
+ "cancelable": true,
655
+ "composed": true
602
656
  }
603
657
  ],
604
658
  "methods": [
605
659
  {
606
660
  "name": "setFocus",
607
- "signature": "setFocus() => Promise<void>"
661
+ "signature": "setFocus(segment?: \"primary\" | \"menu\") => Promise<void>"
608
662
  }
609
663
  ],
610
664
  "slots": [],
@@ -635,7 +689,7 @@
635
689
  },
636
690
  "peerDependencies": {
637
691
  "required": [
638
- "@ds-mo/tokens ^6.5.0",
692
+ "@ds-mo/tokens ^6.5.1",
639
693
  "@ds-mo/icons ^7.0.0"
640
694
  ],
641
695
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -657,12 +711,12 @@
657
711
  "files": [
658
712
  {
659
713
  "path": "src/wc/components/ButtonUnfilled/ButtonUnfilled.css",
660
- "content": "@import '../../utils/button-base.css';\n@import '../../utils/table-caption-control.css';\n\n:host {\n --ds-button-unfilled-fg: var(--color-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-secondary);\n --ds-button-unfilled-border: var(--color-border-secondary);\n}\n\n.button-unfilled {\n background: transparent;\n color: var(--ds-button-unfilled-fg);\n /* Hover/press/selected fill: `.ds-interaction-fill` (+ `--selected` / `--on-*`).\n Active foreground changes remain independent from popup-open pressed paint. */\n}\n\n.button-unfilled--active {\n color: var(--ds-button-unfilled-fg-active);\n}\n\n/*\n * An open popup is a transient pressed state, not a selected state. Keep the\n * pressed wash visible for the popup's complete rendered lifecycle without\n * promoting the resting foreground or selected fill.\n */\n/* Inset stroke via interaction-fill ::after (above selected / hover washes). */\n.button-unfilled--bordered {\n --ds-interaction-border-color: var(--ds-button-unfilled-border);\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n/* Foreground / chrome tokens per surface — interaction tokens come from util `--on-*`. */\n.button-unfilled--background-medium {\n --ds-button-unfilled-fg: var(--color-foreground-on-medium-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-medium-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-medium-neutral);\n --ds-button-unfilled-border: var(--color-border-on-medium-background-secondary);\n}\n\n.button-unfilled--background-bold {\n --ds-button-unfilled-fg: var(--color-foreground-on-bold-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-bold-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-bold-neutral);\n --ds-button-unfilled-border: var(--color-border-on-bold-background-secondary);\n}\n\n.button-unfilled--background-strong {\n --ds-button-unfilled-fg: var(--color-foreground-on-strong-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-strong-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-strong-neutral);\n --ds-button-unfilled-border: var(--color-border-on-strong-background-secondary);\n}\n\n.button-unfilled--background-translucent {\n --ds-button-unfilled-fg: var(--color-translucent-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-translucent-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-translucent-translucent);\n --ds-button-unfilled-border: var(--color-translucent-border-secondary);\n}\n\n.button-unfilled--background-inverted {\n --ds-button-unfilled-fg: var(--color-inverted-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-inverted-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-inverted-background);\n --ds-button-unfilled-border: var(--color-inverted-border-secondary);\n}\n\n.button-unfilled--background-media {\n --ds-button-unfilled-fg: var(--color-media-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-media-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-media-background);\n --ds-button-unfilled-border: var(--color-media-border-secondary);\n}\n\n.button-unfilled--on-always-dark {\n --ds-button-unfilled-fg: var(--color-always-dark-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-always-dark-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-always-dark-background);\n --ds-button-unfilled-border: var(--color-always-dark-border-secondary);\n}\n\n/*\n * After surface ring defaults: selected fill remaps the knock-out to the active\n * wash so the badge halo matches the selected background.\n */\n.button-unfilled.ds-interaction-fill--selected {\n --ds-button-unfilled-dot-ring: var(--ds-interaction-active);\n}\n",
714
+ "content": "@import '../../utils/button-base.css';\n@import '../../utils/button-split.css';\n@import '../../utils/table-caption-control.css';\n\n:host {\n --ds-button-unfilled-fg: var(--color-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-secondary);\n --ds-button-unfilled-border: var(--color-border-secondary);\n --ds-button-split-border: var(--color-border-secondary);\n --ds-button-split-divider: var(--color-border-tertiary);\n}\n\n:host(.button-unfilled-host--background-medium) {\n --ds-button-split-border: var(--color-border-on-medium-background-secondary);\n --ds-button-split-divider: var(--color-border-on-medium-background-tertiary);\n}\n\n:host(.button-unfilled-host--background-bold) {\n --ds-button-split-border: var(--color-border-on-bold-background-secondary);\n --ds-button-split-divider: var(--color-border-on-bold-background-tertiary);\n}\n\n:host(.button-unfilled-host--background-strong) {\n --ds-button-split-border: var(--color-border-on-strong-background-secondary);\n --ds-button-split-divider: var(--color-border-on-strong-background-tertiary);\n}\n\n:host(.button-unfilled-host--background-translucent) {\n --ds-button-split-border: var(--color-translucent-border-secondary);\n --ds-button-split-divider: var(--color-translucent-border-tertiary);\n}\n\n:host(.button-unfilled-host--background-inverted) {\n --ds-button-split-border: var(--color-inverted-border-secondary);\n --ds-button-split-divider: var(--color-inverted-border-tertiary);\n}\n\n:host(.button-unfilled-host--background-media) {\n --ds-button-split-border: var(--color-media-border-secondary);\n --ds-button-split-divider: var(--color-media-border-tertiary);\n}\n\n:host(.button-unfilled-host--background-always-dark) {\n --ds-button-split-border: var(--color-always-dark-border-secondary);\n --ds-button-split-divider: var(--color-always-dark-border-tertiary);\n}\n\n.button-unfilled {\n background: transparent;\n color: var(--ds-button-unfilled-fg);\n /* Hover/press/selected fill: `.ds-interaction-fill` (+ `--selected` / `--on-*`).\n Active foreground changes remain independent from popup-open pressed paint. */\n}\n\n.button-unfilled--active {\n color: var(--ds-button-unfilled-fg-active);\n}\n\n/*\n * An open popup is a transient pressed state, not a selected state. Keep the\n * pressed wash visible for the popup's complete rendered lifecycle without\n * promoting the resting foreground or selected fill.\n */\n/* Inset stroke via interaction-fill ::after (above selected / hover washes). */\n.button-unfilled--bordered {\n --ds-interaction-border-color: var(--ds-button-unfilled-border);\n}\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n/* Foreground / chrome tokens per surface — interaction tokens come from util `--on-*`. */\n.button-unfilled--background-medium {\n --ds-button-unfilled-fg: var(--color-foreground-on-medium-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-medium-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-medium-neutral);\n --ds-button-unfilled-border: var(--color-border-on-medium-background-secondary);\n}\n\n.button-unfilled--background-bold {\n --ds-button-unfilled-fg: var(--color-foreground-on-bold-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-bold-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-bold-neutral);\n --ds-button-unfilled-border: var(--color-border-on-bold-background-secondary);\n}\n\n.button-unfilled--background-strong {\n --ds-button-unfilled-fg: var(--color-foreground-on-strong-background-secondary);\n --ds-button-unfilled-fg-active: var(--color-foreground-on-strong-background-primary);\n --ds-button-unfilled-dot-ring: var(--color-background-strong-neutral);\n --ds-button-unfilled-border: var(--color-border-on-strong-background-secondary);\n}\n\n.button-unfilled--background-translucent {\n --ds-button-unfilled-fg: var(--color-translucent-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-translucent-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-translucent-translucent);\n --ds-button-unfilled-border: var(--color-translucent-border-secondary);\n}\n\n.button-unfilled--background-inverted {\n --ds-button-unfilled-fg: var(--color-inverted-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-inverted-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-inverted-background);\n --ds-button-unfilled-border: var(--color-inverted-border-secondary);\n}\n\n.button-unfilled--background-media {\n --ds-button-unfilled-fg: var(--color-media-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-media-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-media-background);\n --ds-button-unfilled-border: var(--color-media-border-secondary);\n}\n\n.button-unfilled--on-always-dark {\n --ds-button-unfilled-fg: var(--color-always-dark-foreground-secondary);\n --ds-button-unfilled-fg-active: var(--color-always-dark-foreground-primary);\n --ds-button-unfilled-dot-ring: var(--color-always-dark-background);\n --ds-button-unfilled-border: var(--color-always-dark-border-secondary);\n}\n\n/*\n * After surface ring defaults: selected fill remaps the knock-out to the active\n * wash so the badge halo matches the selected background.\n */\n.button-unfilled.ds-interaction-fill--selected {\n --ds-button-unfilled-dot-ring: var(--ds-interaction-active);\n}\n",
661
715
  "type": "registry:ui"
662
716
  },
663
717
  {
664
718
  "path": "src/wc/components/ButtonUnfilled/ButtonUnfilled.tsx",
665
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport { controlWidthClass } from '../../utils';\nimport { beginElevatedControlPress } from '../../utils/control-press';\nimport { renderButtonContent } from '../../utils/button-render';\nimport type { ControlInsetDepth } from '../../utils/control-text';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\nimport type { ButtonPopup, ButtonSize, ButtonVariant, ButtonWidth } from '../../utils/button-types';\n\nexport type ButtonUnfilledBackground =\n | 'faint'\n | 'medium'\n | 'bold'\n | 'strong'\n | 'translucent'\n | 'inverted'\n | 'media'\n | 'always-dark';\n\nexport type ButtonUnfilledVariant = ButtonVariant;\nexport type ButtonUnfilledSize = ButtonSize;\nexport type ButtonUnfilledWidth = ButtonWidth;\nexport type ButtonUnfilledPopup = ButtonPopup;\n\n@Component({\n tag: 'ds-button-unfilled',\n styleUrl: 'ButtonUnfilled.css',\n scoped: true,\n})\nexport class ButtonUnfilled {\n @Element() el!: HTMLElement;\n\n /**\n * Content layout. Default is label-only; pass `icon` for icon-only chrome\n * (nav / tool rails) or `icon-label` for leading icon + text.\n */\n @Prop() variant: ButtonUnfilledVariant = 'label';\n\n /** Control density (height, padding, icon, type). */\n @Prop() size: ButtonUnfilledSize = 'md';\n\n /** Use reduced outer geometry when nested inside a control of the same size. */\n @Prop() isInset: boolean = false;\n\n /** Single removes 4px overall; double removes 8px overall (xs stays single). */\n @Prop() insetDepth: ControlInsetDepth = 'single';\n\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: ButtonUnfilledWidth = 'hug';\n\n /** Visible text for `label` / `icon-label` variants. */\n @Prop() label: string = '';\n\n /** Whether the visible label uses the emphasized weight. */\n @Prop() labelEmphasis: boolean = true;\n\n /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */\n @Prop() icon: string = '';\n\n /**\n * Owner-controlled visual emphasis inside a composite. This does not add\n * toggle semantics and does not emit dsChange.\n */\n @Prop() isActive: boolean = false;\n\n /**\n * When active, render the selected interaction fill.\n * Default `true` for general UI. Shell chrome (nav / tool rails) should pass\n * `false` so selection is foreground-only (primary color, no fill).\n */\n @Prop() activeFill: boolean = true;\n\n /** Show a 1px secondary inset border. Default on; shell chrome can pass `false`. */\n @Prop() hasBorder: boolean = true;\n\n /** Use the half-radius treatment instead of the default control radius. */\n @Prop() rounded: boolean = false;\n\n /**\n * Scale down during a physical pointer press.\n * Disable when an owning composite requires fixed child or background geometry.\n */\n @Prop() pressScale: boolean = true;\n\n /** Show a notification dot at the top-right of the icon zone (icon variant only). */\n @Prop() dot: boolean = false;\n\n /** Disables interaction. */\n @Prop() isInactive: boolean = false;\n\n /** Shows an inline loader and prevents interaction without applying inactive opacity. */\n @Prop() isLoading: boolean = false;\n\n /** Native button type. */\n @Prop() type: 'button' | 'submit' | 'reset' = 'button';\n\n /** Actual parent surface context. Omit on primary and secondary surfaces. */\n @Prop() background: ButtonUnfilledBackground | undefined;\n\n /** Accessible name override. Required for icon-only buttons. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n @Prop() controls: string | undefined;\n /**\n * Controlled disclosure or popup state forwarded to `aria-expanded`.\n * Popup triggers hold only the pressed wash while open; their resting\n * foreground is unchanged. Ordinary disclosures remain visually neutral.\n */\n @Prop() expanded: boolean | undefined;\n @Prop() haspopup: ButtonUnfilledPopup | undefined;\n /**\n * Controlled state for a genuine toggle button. Adds aria-pressed, promotes\n * active styling, and makes activation emit dsChange with the requested state.\n */\n @Prop() pressed: boolean | undefined;\n\n /**\n * Collapse the visible label and chevron to an icon-only control when the\n * owning `ds-table` caption is narrower than 900px. The trigger omits those\n * parts rather than clipping them.\n */\n @Prop({ reflect: true }) collapseLabel: boolean = false;\n\n /**\n * Mark the button as a Menu trigger. Implies `aria-haspopup=\"menu\"` and covers\n * both menu-button shapes:\n *\n * - `label` / `icon-label` — the action *has* a menu; a trailing chevron carries\n * the affordance.\n * - `icon` — the button *is* a menu. No chevron is added, so the glyph must\n * convey the menu on its own. Use `Ellipses` for generic more-options;\n * use a specific icon when the menu has a named purpose, such as\n * `Table` for Customize table.\n *\n * Use `haspopup` directly for non-menu popups.\n */\n @Prop() hasMenu: boolean = false;\n\n /**\n * Native `tabindex` for roving keyboard groups in shell chrome.\n * Omit for the default button tab stop (`0`).\n */\n @Prop({ attribute: 'tab-index' }) focusTabIndex?: number;\n\n @Event() dsClick!: EventEmitter<MouseEvent>;\n @Event() dsChange!: EventEmitter<boolean>;\n\n @State() private captionCompact = false;\n\n private buttonEl: HTMLButtonElement | null = null;\n private captionCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.syncCaptionCompactObserver();\n }\n\n connectedCallback(): void {\n if (this.hasLoaded) this.syncCaptionCompactObserver();\n }\n\n disconnectedCallback(): void {\n this.disconnectCaptionCompactObserver();\n }\n\n @Watch('collapseLabel')\n onCollapseLabelChange() {\n this.syncCaptionCompactObserver();\n }\n\n @Method()\n async setFocus() {\n this.buttonEl?.focus();\n }\n\n private handleClick = (event: MouseEvent) => {\n if (this.isInactive || this.isLoading) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.dsClick.emit(event);\n if (this.pressed !== undefined) this.dsChange.emit(!this.pressed);\n };\n\n private get captionIconOnly(): boolean {\n return (\n this.collapseLabel &&\n this.captionCompact &&\n this.variant === 'icon-label' &&\n Boolean(this.icon)\n );\n }\n\n private get visualVariant(): ButtonUnfilledVariant {\n return this.captionIconOnly ? 'icon' : this.variant;\n }\n\n private get showDot(): boolean {\n return this.variant === 'icon' && this.dot && !this.isLoading;\n }\n\n private syncCaptionCompactObserver(): void {\n this.disconnectCaptionCompactObserver();\n if (!this.collapseLabel) {\n if (this.captionCompact) this.captionCompact = false;\n return;\n }\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n private get resolvedHaspopup(): ButtonUnfilledPopup | undefined {\n return this.haspopup ?? (this.hasMenu ? 'menu' : undefined);\n }\n\n private get doubleInset(): boolean {\n return this.isInset && this.insetDepth === 'double' && this.size !== 'xs';\n }\n\n /** Only popup triggers hold a pressed visual while expanded. */\n private get expandedPopup(): boolean {\n return this.expanded === true && this.resolvedHaspopup !== undefined && !this.isInactive;\n }\n\n private get visuallyActive(): boolean {\n return this.isActive || this.pressed === true;\n }\n\n /** Knock-out ring: selected fill → active wash; otherwise surface token. */\n private get dotRing(): string {\n if ((this.isActive || this.pressed === true) && this.activeFill) {\n return 'var(--ds-interaction-active)';\n }\n return 'var(--ds-button-unfilled-dot-ring)';\n }\n\n private get accessibleName(): string | undefined {\n if (this.ariaLabel) return this.ariaLabel;\n if (this.isLoading && this.variant === 'label' && this.label) return this.label;\n return undefined;\n }\n\n render() {\n const bg = this.background;\n const cls: Record<string, boolean> = {\n 'button-unfilled': true,\n 'ds-button': true,\n 'ds-focus-ring-inset': true,\n 'ds-control-press-scale': this.pressScale,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected':\n (this.isActive || this.pressed === true) && this.activeFill && !this.isInactive,\n 'ds-interaction-fill--on-faint': bg === 'faint',\n 'ds-interaction-fill--on-medium': bg === 'medium',\n 'ds-interaction-fill--on-bold': bg === 'bold',\n 'ds-interaction-fill--on-strong': bg === 'strong',\n 'ds-interaction-fill--on-translucent': bg === 'translucent',\n 'ds-interaction-fill--on-inverted': bg === 'inverted',\n 'ds-interaction-fill--on-media': bg === 'media',\n 'ds-interaction-fill--on-always-dark': bg === 'always-dark',\n 'button-unfilled--active': this.visuallyActive,\n 'button-unfilled--expanded': this.expandedPopup,\n 'ds-button--expanded': this.expandedPopup,\n 'button-unfilled--bordered': this.hasBorder,\n 'ds-button--bordered': this.hasBorder,\n 'button-unfilled--rounded': this.rounded,\n 'ds-button--rounded': this.rounded,\n 'ds-control-inactive': this.isInactive,\n 'ds-control--lg': this.size === 'lg',\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n 'ds-control--inset': this.isInset && !this.doubleInset,\n 'ds-control--inset-double': this.doubleInset,\n 'ds-control-frame': true,\n 'button-unfilled--icon': this.visualVariant === 'icon',\n 'ds-button--icon': this.visualVariant === 'icon',\n 'button-unfilled--label': this.visualVariant === 'label',\n 'button-unfilled--icon-label': this.visualVariant === 'icon-label',\n 'button-unfilled--background-faint': bg === 'faint',\n 'button-unfilled--background-medium': bg === 'medium',\n 'button-unfilled--background-bold': bg === 'bold',\n 'button-unfilled--background-strong': bg === 'strong',\n 'button-unfilled--background-translucent': bg === 'translucent',\n 'button-unfilled--background-inverted': bg === 'inverted',\n 'button-unfilled--background-media': bg === 'media',\n 'button-unfilled--on-always-dark': bg === 'always-dark',\n };\n\n const button = (\n <button\n ref={el => {\n this.buttonEl = el ?? null;\n }}\n type={this.type}\n class={cls}\n disabled={this.isInactive}\n tabIndex={this.focusTabIndex ?? 0}\n aria-label={this.accessibleName}\n aria-busy={this.isLoading ? 'true' : undefined}\n aria-disabled={this.isLoading ? 'true' : undefined}\n aria-controls={this.controls}\n aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}\n aria-haspopup={this.resolvedHaspopup}\n aria-pressed={this.pressed === undefined ? undefined : String(this.pressed)}\n onPointerDown={event =>\n beginElevatedControlPress(event, this.pressScale && !this.isInactive && !this.isLoading)\n }\n onClick={this.handleClick}\n >\n {renderButtonContent({\n namespace: 'button-unfilled',\n variant: this.visualVariant,\n size: this.size,\n label: this.label,\n labelEmphasis: this.labelEmphasis,\n icon: this.icon,\n hasMenu: this.hasMenu,\n isLoading: this.isLoading,\n dot: {\n visible: this.showDot,\n background: this.dotRing,\n },\n })}\n </button>\n );\n\n return (\n <Host\n class={{\n 'button-unfilled-host': true,\n 'button-unfilled-host--icon': this.visualVariant === 'icon',\n 'ds-button-host--icon': this.visualVariant === 'icon',\n 'ds-control--lg': this.size === 'lg',\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n 'ds-control--inset': this.isInset && !this.doubleInset,\n 'ds-control--inset-double': this.doubleInset,\n 'ds-table-caption-control': this.collapseLabel,\n 'ds-table-caption-control--compact': this.captionIconOnly,\n ...controlWidthClass(this.width),\n }}\n tabIndex={-1}\n >\n {this.collapseLabel ? (\n <ds-tooltip\n label={this.captionIconOnly ? this.label || this.accessibleName || '' : ''}\n side=\"top\"\n size=\"sm\"\n >\n {button}\n </ds-tooltip>\n ) : (\n button\n )}\n </Host>\n );\n }\n}\n",
719
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport { controlWidthClass } from '../../utils';\nimport { beginElevatedControlPress } from '../../utils/control-press';\nimport { renderButtonContent } from '../../utils/button-render';\nimport type { ControlInsetDepth } from '../../utils/control-text';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\nimport type { ButtonPopup, ButtonSize, ButtonVariant, ButtonWidth } from '../../utils/button-types';\n\nexport type ButtonUnfilledBackground =\n | 'faint'\n | 'medium'\n | 'bold'\n | 'strong'\n | 'translucent'\n | 'inverted'\n | 'media'\n | 'always-dark';\n\nexport type ButtonUnfilledVariant = ButtonVariant;\nexport type ButtonUnfilledSize = ButtonSize;\nexport type ButtonUnfilledWidth = ButtonWidth;\nexport type ButtonUnfilledPopup = ButtonPopup;\n\n@Component({\n tag: 'ds-button-unfilled',\n styleUrl: 'ButtonUnfilled.css',\n scoped: true,\n})\nexport class ButtonUnfilled {\n @Element() el!: HTMLElement;\n\n /**\n * Content layout. Default is label-only; pass `icon` for icon-only chrome\n * (nav / tool rails) or `icon-label` for leading icon + text.\n */\n @Prop() variant: ButtonUnfilledVariant = 'label';\n\n /** Control density (height, padding, icon, type). */\n @Prop() size: ButtonUnfilledSize = 'md';\n\n /** Use reduced outer geometry when nested inside a control of the same size. */\n @Prop() isInset: boolean = false;\n\n /** Single removes 4px overall; double removes 8px overall (xs stays single). */\n @Prop() insetDepth: ControlInsetDepth = 'single';\n\n /** Width fit — hug content (default) or fill the parent. */\n @Prop() width: ButtonUnfilledWidth = 'hug';\n\n /** Visible text for `label` / `icon-label` variants. */\n @Prop() label: string = '';\n\n /** Whether the visible label uses the emphasized weight. */\n @Prop() labelEmphasis: boolean = true;\n\n /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */\n @Prop() icon: string = '';\n\n /**\n * Owner-controlled visual emphasis inside a composite. This does not add\n * toggle semantics and does not emit dsChange.\n */\n @Prop() isActive: boolean = false;\n\n /**\n * When active, render the selected interaction fill.\n * Default `true` for general UI. Shell chrome (nav / tool rails) should pass\n * `false` so selection is foreground-only (primary color, no fill).\n */\n @Prop() activeFill: boolean = true;\n\n /** Show a 1px secondary inset border. Default on; shell chrome can pass `false`. */\n @Prop() hasBorder: boolean = true;\n\n /** Use the half-radius treatment instead of the default control radius. */\n @Prop() rounded: boolean = false;\n\n /**\n * Scale down during a physical pointer press.\n * Disable when an owning composite requires fixed child or background geometry.\n */\n @Prop() pressScale: boolean = true;\n\n /** Show a notification dot at the top-right of the icon zone (icon variant only). */\n @Prop() dot: boolean = false;\n\n /** Disables interaction. */\n @Prop() isInactive: boolean = false;\n\n /** Shows an inline loader and prevents interaction without applying inactive opacity. */\n @Prop() isLoading: boolean = false;\n\n /** Native button type. */\n @Prop() type: 'button' | 'submit' | 'reset' = 'button';\n\n /** Actual parent surface context. Omit on primary and secondary surfaces. */\n @Prop() background: ButtonUnfilledBackground | undefined;\n\n /** Accessible name override. Required for icon-only buttons. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n @Prop() controls: string | undefined;\n /**\n * Controlled disclosure or popup state forwarded to `aria-expanded`.\n * Popup triggers hold only the pressed wash while open; their resting\n * foreground is unchanged. Ordinary disclosures remain visually neutral.\n */\n @Prop() expanded: boolean | undefined;\n @Prop() haspopup: ButtonUnfilledPopup | undefined;\n\n /**\n * Append a separate ChevronDown menu segment while preserving this button's\n * variant, size, surface, active, loading, border, and width treatment.\n */\n @Prop() split: boolean = false;\n\n /** Accessible name for the appended menu segment in split mode. */\n @Prop() menuAriaLabel: string = 'More options';\n\n /**\n * Controlled state for a genuine toggle button. Adds aria-pressed, promotes\n * active styling, and makes activation emit dsChange with the requested state.\n */\n @Prop() pressed: boolean | undefined;\n\n /**\n * Collapse the visible label and chevron to an icon-only control when the\n * owning `ds-table` caption is narrower than 900px. The trigger omits those\n * parts rather than clipping them.\n */\n @Prop({ reflect: true }) collapseLabel: boolean = false;\n\n /**\n * Mark the button as a Menu trigger. Implies `aria-haspopup=\"menu\"` and covers\n * both menu-button shapes:\n *\n * - `label` / `icon-label` — the action *has* a menu; a trailing chevron carries\n * the affordance.\n * - `icon` — the button *is* a menu. No chevron is added, so the glyph must\n * convey the menu on its own. Use `Ellipses` for generic more-options;\n * use a specific icon when the menu has a named purpose, such as\n * `Table` for Customize table.\n *\n * Use `haspopup` directly for non-menu popups.\n */\n @Prop() hasMenu: boolean = false;\n\n /**\n * Native `tabindex` for roving keyboard groups in shell chrome.\n * Omit for the default button tab stop (`0`).\n */\n @Prop({ attribute: 'tab-index' }) focusTabIndex?: number;\n\n @Event() dsClick!: EventEmitter<MouseEvent>;\n @Event() dsMenuClick!: EventEmitter<MouseEvent>;\n @Event() dsChange!: EventEmitter<boolean>;\n\n @State() private captionCompact = false;\n\n private buttonEl: HTMLButtonElement | null = null;\n private menuButtonEl: HTMLButtonElement | null = null;\n private captionCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.syncCaptionCompactObserver();\n }\n\n connectedCallback(): void {\n if (this.hasLoaded) this.syncCaptionCompactObserver();\n }\n\n disconnectedCallback(): void {\n this.disconnectCaptionCompactObserver();\n }\n\n @Watch('collapseLabel')\n onCollapseLabelChange() {\n this.syncCaptionCompactObserver();\n }\n\n @Method()\n async setFocus(segment: 'primary' | 'menu' = 'primary') {\n (segment === 'menu' ? this.menuButtonEl : this.buttonEl)?.focus();\n }\n\n private handleClick = (event: MouseEvent) => {\n if (this.isInactive || this.isLoading) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.dsClick.emit(event);\n if (this.pressed !== undefined) this.dsChange.emit(!this.pressed);\n };\n\n private handleMenuClick = (event: MouseEvent) => {\n if (this.isInactive || this.isLoading) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n this.dsMenuClick.emit(event);\n };\n\n private get captionIconOnly(): boolean {\n return (\n this.collapseLabel &&\n this.captionCompact &&\n this.variant === 'icon-label' &&\n Boolean(this.icon)\n );\n }\n\n private get visualVariant(): ButtonUnfilledVariant {\n return this.captionIconOnly ? 'icon' : this.variant;\n }\n\n private get showDot(): boolean {\n return this.variant === 'icon' && this.dot && !this.isLoading;\n }\n\n private syncCaptionCompactObserver(): void {\n this.disconnectCaptionCompactObserver();\n if (!this.collapseLabel) {\n if (this.captionCompact) this.captionCompact = false;\n return;\n }\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n private get resolvedHaspopup(): ButtonUnfilledPopup | undefined {\n return this.haspopup ?? (this.hasMenu ? 'menu' : undefined);\n }\n\n private get doubleInset(): boolean {\n return this.isInset && this.insetDepth === 'double' && this.size !== 'xs';\n }\n\n /** Only popup triggers hold a pressed visual while expanded. */\n private get expandedPopup(): boolean {\n return this.expanded === true && this.resolvedHaspopup !== undefined && !this.isInactive;\n }\n\n private get visuallyActive(): boolean {\n return this.isActive || this.pressed === true;\n }\n\n /** Knock-out ring: selected fill → active wash; otherwise surface token. */\n private get dotRing(): string {\n if ((this.isActive || this.pressed === true) && this.activeFill) {\n return 'var(--ds-interaction-active)';\n }\n return 'var(--ds-button-unfilled-dot-ring)';\n }\n\n private get accessibleName(): string | undefined {\n if (this.ariaLabel) return this.ariaLabel;\n if (this.isLoading && this.variant === 'label' && this.label) return this.label;\n return undefined;\n }\n\n private buttonClass(\n variant: ButtonUnfilledVariant,\n expanded: boolean,\n bordered: boolean\n ): Record<string, boolean> {\n const bg = this.background;\n const cls: Record<string, boolean> = {\n 'button-unfilled': true,\n 'ds-button': true,\n 'ds-focus-ring-inset': true,\n 'ds-control-press-scale': this.pressScale && !this.split,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected':\n (this.isActive || this.pressed === true) && this.activeFill && !this.isInactive,\n 'ds-interaction-fill--on-faint': bg === 'faint',\n 'ds-interaction-fill--on-medium': bg === 'medium',\n 'ds-interaction-fill--on-bold': bg === 'bold',\n 'ds-interaction-fill--on-strong': bg === 'strong',\n 'ds-interaction-fill--on-translucent': bg === 'translucent',\n 'ds-interaction-fill--on-inverted': bg === 'inverted',\n 'ds-interaction-fill--on-media': bg === 'media',\n 'ds-interaction-fill--on-always-dark': bg === 'always-dark',\n 'button-unfilled--active': this.visuallyActive,\n 'button-unfilled--expanded': expanded,\n 'ds-button--expanded': expanded,\n 'button-unfilled--bordered': bordered,\n 'ds-button--bordered': bordered,\n 'button-unfilled--rounded': this.rounded,\n 'ds-button--rounded': this.rounded,\n 'ds-control-inactive': this.isInactive,\n 'ds-control--lg': this.size === 'lg',\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n 'ds-control--inset': this.isInset && !this.doubleInset,\n 'ds-control--inset-double': this.doubleInset,\n 'ds-control-frame': true,\n 'button-unfilled--icon': variant === 'icon',\n 'ds-button--icon': variant === 'icon',\n 'button-unfilled--label': variant === 'label',\n 'button-unfilled--icon-label': variant === 'icon-label',\n 'button-unfilled--background-faint': bg === 'faint',\n 'button-unfilled--background-medium': bg === 'medium',\n 'button-unfilled--background-bold': bg === 'bold',\n 'button-unfilled--background-strong': bg === 'strong',\n 'button-unfilled--background-translucent': bg === 'translucent',\n 'button-unfilled--background-inverted': bg === 'inverted',\n 'button-unfilled--background-media': bg === 'media',\n 'button-unfilled--on-always-dark': bg === 'always-dark',\n };\n return cls;\n }\n\n render() {\n const primaryButton = (\n <button\n ref={el => {\n this.buttonEl = el ?? null;\n }}\n type={this.type}\n class={{\n ...this.buttonClass(\n this.visualVariant,\n !this.split && this.expandedPopup,\n this.hasBorder && !this.split\n ),\n 'ds-button-split__primary': this.split,\n }}\n disabled={this.isInactive}\n tabIndex={this.focusTabIndex ?? 0}\n aria-label={this.accessibleName}\n aria-busy={this.isLoading ? 'true' : undefined}\n aria-disabled={this.isLoading ? 'true' : undefined}\n aria-controls={this.split ? undefined : this.controls}\n aria-expanded={\n this.split || this.expanded === undefined ? undefined : String(this.expanded)\n }\n aria-haspopup={this.split ? undefined : this.resolvedHaspopup}\n aria-pressed={this.pressed === undefined ? undefined : String(this.pressed)}\n onPointerDown={event =>\n beginElevatedControlPress(\n event,\n this.pressScale && !this.split && !this.isInactive && !this.isLoading\n )\n }\n onClick={this.handleClick}\n >\n {renderButtonContent({\n namespace: 'button-unfilled',\n variant: this.visualVariant,\n size: this.size,\n label: this.label,\n labelEmphasis: this.labelEmphasis,\n icon: this.icon,\n hasMenu: !this.split && this.hasMenu,\n isLoading: this.isLoading,\n dot: {\n visible: this.showDot,\n background: this.dotRing,\n },\n })}\n </button>\n );\n\n const control = this.split ? (\n <div\n class={{\n 'ds-button-split': true,\n 'ds-button-split--rounded': this.rounded,\n }}\n >\n {primaryButton}\n <span\n class={{\n 'ds-button-split__divider': true,\n 'ds-button-split__divider--content-height': !this.hasBorder,\n }}\n aria-hidden=\"true\"\n />\n <button\n ref={el => {\n this.menuButtonEl = el ?? null;\n }}\n class={{\n ...this.buttonClass('icon', this.expanded === true && !this.isInactive, false),\n 'ds-button-split__menu': true,\n }}\n type=\"button\"\n disabled={this.isInactive || this.isLoading}\n tabIndex={this.focusTabIndex ?? 0}\n aria-label={this.menuAriaLabel}\n aria-controls={this.controls}\n aria-expanded={String(this.expanded === true)}\n aria-haspopup=\"menu\"\n onClick={this.handleMenuClick}\n >\n {renderButtonContent({\n namespace: 'button-unfilled',\n variant: 'icon',\n size: this.size,\n label: '',\n labelEmphasis: true,\n icon: 'ChevronDown',\n hasMenu: false,\n isLoading: false,\n })}\n </button>\n {this.hasBorder ? <span class=\"ds-button-split__outline\" aria-hidden=\"true\" /> : null}\n </div>\n ) : (\n primaryButton\n );\n\n return (\n <Host\n class={{\n 'button-unfilled-host': true,\n 'button-unfilled-host--icon': this.visualVariant === 'icon' && !this.split,\n 'ds-button-host--icon': this.visualVariant === 'icon' && !this.split,\n 'ds-button-split-host': this.split,\n 'ds-button-split-host--primary-icon': this.split && this.visualVariant === 'icon',\n [`button-unfilled-host--background-${this.background}`]:\n this.split && this.background !== undefined,\n 'ds-control--lg': this.size === 'lg',\n 'ds-control--md': this.size === 'md',\n 'ds-control--sm': this.size === 'sm',\n 'ds-control--xs': this.size === 'xs',\n 'ds-control--inset': this.isInset && !this.doubleInset,\n 'ds-control--inset-double': this.doubleInset,\n 'ds-table-caption-control': this.collapseLabel,\n 'ds-table-caption-control--compact': this.captionIconOnly,\n ...controlWidthClass(this.width),\n }}\n tabIndex={-1}\n >\n {this.collapseLabel ? (\n <ds-tooltip\n label={this.captionIconOnly ? this.label || this.accessibleName || '' : ''}\n side=\"top\"\n size=\"sm\"\n >\n {control}\n </ds-tooltip>\n ) : (\n control\n )}\n </Host>\n );\n }\n}\n",
666
720
  "type": "registry:ui"
667
721
  }
668
722
  ]
@@ -213,7 +213,7 @@
213
213
  },
214
214
  "peerDependencies": {
215
215
  "required": [
216
- "@ds-mo/tokens ^6.5.0",
216
+ "@ds-mo/tokens ^6.5.1",
217
217
  "@ds-mo/icons ^7.0.0"
218
218
  ],
219
219
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -350,7 +350,7 @@
350
350
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: metrics, score.",
351
351
  "peerDependencies": {
352
352
  "required": [
353
- "@ds-mo/tokens ^6.5.0",
353
+ "@ds-mo/tokens ^6.5.1",
354
354
  "@ds-mo/icons ^7.0.0"
355
355
  ],
356
356
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -254,7 +254,7 @@
254
254
  },
255
255
  "peerDependencies": {
256
256
  "required": [
257
- "@ds-mo/tokens ^6.5.0",
257
+ "@ds-mo/tokens ^6.5.1",
258
258
  "@ds-mo/icons ^7.0.0"
259
259
  ],
260
260
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -272,7 +272,7 @@
272
272
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: items.",
273
273
  "peerDependencies": {
274
274
  "required": [
275
- "@ds-mo/tokens ^6.5.0",
275
+ "@ds-mo/tokens ^6.5.1",
276
276
  "@ds-mo/icons ^7.0.0"
277
277
  ],
278
278
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -234,7 +234,7 @@
234
234
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: definition.",
235
235
  "peerDependencies": {
236
236
  "required": [
237
- "@ds-mo/tokens ^6.5.0",
237
+ "@ds-mo/tokens ^6.5.1",
238
238
  "@ds-mo/icons ^7.0.0"
239
239
  ],
240
240
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -357,7 +357,7 @@
357
357
  },
358
358
  "peerDependencies": {
359
359
  "required": [
360
- "@ds-mo/tokens ^6.5.0",
360
+ "@ds-mo/tokens ^6.5.1",
361
361
  "@ds-mo/icons ^7.0.0"
362
362
  ],
363
363
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -287,7 +287,7 @@
287
287
  },
288
288
  "peerDependencies": {
289
289
  "required": [
290
- "@ds-mo/tokens ^6.5.0",
290
+ "@ds-mo/tokens ^6.5.1",
291
291
  "@ds-mo/icons ^7.0.0"
292
292
  ],
293
293
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -133,7 +133,7 @@
133
133
  },
134
134
  "peerDependencies": {
135
135
  "required": [
136
- "@ds-mo/tokens ^6.5.0",
136
+ "@ds-mo/tokens ^6.5.1",
137
137
  "@ds-mo/icons ^7.0.0"
138
138
  ],
139
139
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -258,7 +258,7 @@
258
258
  },
259
259
  "peerDependencies": {
260
260
  "required": [
261
- "@ds-mo/tokens ^6.5.0",
261
+ "@ds-mo/tokens ^6.5.1",
262
262
  "@ds-mo/icons ^7.0.0"
263
263
  ],
264
264
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -115,7 +115,7 @@
115
115
  },
116
116
  "peerDependencies": {
117
117
  "required": [
118
- "@ds-mo/tokens ^6.5.0",
118
+ "@ds-mo/tokens ^6.5.1",
119
119
  "@ds-mo/icons ^7.0.0"
120
120
  ],
121
121
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -107,7 +107,7 @@
107
107
  },
108
108
  "peerDependencies": {
109
109
  "required": [
110
- "@ds-mo/tokens ^6.5.0",
110
+ "@ds-mo/tokens ^6.5.1",
111
111
  "@ds-mo/icons ^7.0.0"
112
112
  ],
113
113
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -203,7 +203,7 @@
203
203
  },
204
204
  "peerDependencies": {
205
205
  "required": [
206
- "@ds-mo/tokens ^6.5.0",
206
+ "@ds-mo/tokens ^6.5.1",
207
207
  "@ds-mo/icons ^7.0.0"
208
208
  ],
209
209
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -176,7 +176,7 @@
176
176
  },
177
177
  "peerDependencies": {
178
178
  "required": [
179
- "@ds-mo/tokens ^6.5.0",
179
+ "@ds-mo/tokens ^6.5.1",
180
180
  "@ds-mo/icons ^7.0.0"
181
181
  ],
182
182
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -198,7 +198,7 @@
198
198
  },
199
199
  "peerDependencies": {
200
200
  "required": [
201
- "@ds-mo/tokens ^6.5.0",
201
+ "@ds-mo/tokens ^6.5.1",
202
202
  "@ds-mo/icons ^7.0.0"
203
203
  ],
204
204
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -759,7 +759,7 @@
759
759
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: anchor, boundary, filters, matchModes, values.",
760
760
  "peerDependencies": {
761
761
  "required": [
762
- "@ds-mo/tokens ^6.5.0",
762
+ "@ds-mo/tokens ^6.5.1",
763
763
  "@ds-mo/icons ^7.0.0"
764
764
  ],
765
765
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -199,7 +199,7 @@
199
199
  },
200
200
  "peerDependencies": {
201
201
  "required": [
202
- "@ds-mo/tokens ^6.5.0",
202
+ "@ds-mo/tokens ^6.5.1",
203
203
  "@ds-mo/icons ^7.0.0"
204
204
  ],
205
205
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -695,7 +695,7 @@
695
695
  },
696
696
  "peerDependencies": {
697
697
  "required": [
698
- "@ds-mo/tokens ^6.5.0",
698
+ "@ds-mo/tokens ^6.5.1",
699
699
  "@ds-mo/icons ^7.0.0"
700
700
  ],
701
701
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -171,7 +171,7 @@
171
171
  },
172
172
  "peerDependencies": {
173
173
  "required": [
174
- "@ds-mo/tokens ^6.5.0",
174
+ "@ds-mo/tokens ^6.5.1",
175
175
  "@ds-mo/icons ^7.0.0"
176
176
  ],
177
177
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -122,7 +122,7 @@
122
122
  },
123
123
  "peerDependencies": {
124
124
  "required": [
125
- "@ds-mo/tokens ^6.5.0",
125
+ "@ds-mo/tokens ^6.5.1",
126
126
  "@ds-mo/icons ^7.0.0"
127
127
  ],
128
128
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -500,7 +500,7 @@
500
500
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: anchor, boundary, items, sections.",
501
501
  "peerDependencies": {
502
502
  "required": [
503
- "@ds-mo/tokens ^6.5.0",
503
+ "@ds-mo/tokens ^6.5.1",
504
504
  "@ds-mo/icons ^7.0.0"
505
505
  ],
506
506
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -162,7 +162,7 @@
162
162
  },
163
163
  "peerDependencies": {
164
164
  "required": [
165
- "@ds-mo/tokens ^6.5.0",
165
+ "@ds-mo/tokens ^6.5.1",
166
166
  "@ds-mo/icons ^7.0.0"
167
167
  ],
168
168
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -102,7 +102,7 @@
102
102
  },
103
103
  "peerDependencies": {
104
104
  "required": [
105
- "@ds-mo/tokens ^6.5.0",
105
+ "@ds-mo/tokens ^6.5.1",
106
106
  "@ds-mo/icons ^7.0.0"
107
107
  ],
108
108
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
@@ -262,7 +262,7 @@
262
262
  },
263
263
  "peerDependencies": {
264
264
  "required": [
265
- "@ds-mo/tokens ^6.5.0",
265
+ "@ds-mo/tokens ^6.5.1",
266
266
  "@ds-mo/icons ^7.0.0"
267
267
  ],
268
268
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."