@ds-mo/ui 2.11.2 → 2.13.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 (262) hide show
  1. package/README.md +13 -13
  2. package/dist/.build-stamp +1 -1
  3. package/dist/components/ds-app-shell.js +1 -1
  4. package/dist/components/ds-banner.js +1 -1
  5. package/dist/components/ds-banner.js.map +1 -1
  6. package/dist/components/ds-bar-nav.js +1 -1
  7. package/dist/components/ds-bar-nav.js.map +1 -1
  8. package/dist/components/ds-button-filled.js +1 -1
  9. package/dist/components/{ds-accordion.d.ts → ds-button-unfilled.d.ts} +4 -4
  10. package/dist/components/ds-button-unfilled.js +2 -0
  11. package/dist/components/{ds-card-data-viz.d.ts → ds-card-data-viz-donut.d.ts} +4 -4
  12. package/dist/components/ds-card-data-viz-donut.js +2 -0
  13. package/dist/components/ds-card-data-viz-donut.js.map +1 -0
  14. package/dist/components/ds-card-setting.js +1 -1
  15. package/dist/components/ds-card-setting.js.map +1 -1
  16. package/dist/components/{ds-fade.d.ts → ds-card.d.ts} +4 -4
  17. package/dist/components/ds-card.js +2 -0
  18. package/dist/components/ds-card.js.map +1 -0
  19. package/dist/components/ds-chart-bar.js +1 -1
  20. package/dist/components/ds-chart-bar.js.map +1 -1
  21. package/dist/components/ds-chart-donut.js +1 -1
  22. package/dist/components/ds-chart-donut.js.map +1 -1
  23. package/dist/components/ds-chart-legend.js +1 -1
  24. package/dist/components/ds-chart-legend.js.map +1 -1
  25. package/dist/components/ds-chart-line.js +1 -1
  26. package/dist/components/ds-checkbox.js +1 -1
  27. package/dist/components/ds-checkbox.js.map +1 -1
  28. package/dist/components/ds-chip.js +1 -1
  29. package/dist/components/ds-chip.js.map +1 -1
  30. package/dist/components/ds-empty-state.js +1 -1
  31. package/dist/components/ds-empty-state.js.map +1 -1
  32. package/dist/components/ds-field.js +1 -1
  33. package/dist/components/ds-icon.js +1 -1
  34. package/dist/components/ds-input.js +1 -1
  35. package/dist/components/ds-input.js.map +1 -1
  36. package/dist/components/ds-loader.js +1 -1
  37. package/dist/components/ds-loader.js.map +1 -1
  38. package/dist/components/ds-menu.js +1 -1
  39. package/dist/components/ds-modal.js +1 -1
  40. package/dist/components/ds-pagination.js +1 -1
  41. package/dist/components/ds-pagination.js.map +1 -1
  42. package/dist/components/ds-panel-nav.js +1 -1
  43. package/dist/components/ds-panel-nav.js.map +1 -1
  44. package/dist/components/ds-panel-tools.js +1 -1
  45. package/dist/components/ds-panel-tools.js.map +1 -1
  46. package/dist/components/ds-radio-group.js +1 -1
  47. package/dist/components/ds-radio-group.js.map +1 -1
  48. package/dist/components/ds-select.js +1 -1
  49. package/dist/components/ds-select.js.map +1 -1
  50. package/dist/components/ds-shell-gradient-picker.js +1 -1
  51. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  52. package/dist/components/ds-skeleton.js +1 -1
  53. package/dist/components/ds-skeleton.js.map +1 -1
  54. package/dist/components/ds-slider.js +1 -1
  55. package/dist/components/ds-slider.js.map +1 -1
  56. package/dist/components/ds-tab-group-nav.js +1 -1
  57. package/dist/components/ds-tab-group.js +1 -1
  58. package/dist/components/ds-tab-group.js.map +1 -1
  59. package/dist/components/ds-table.js +1 -1
  60. package/dist/components/ds-table.js.map +1 -1
  61. package/dist/components/ds-tag.js +1 -1
  62. package/dist/components/ds-tag.js.map +1 -1
  63. package/dist/components/ds-text.js +1 -1
  64. package/dist/components/ds-toggle.js +1 -1
  65. package/dist/components/ds-toggle.js.map +1 -1
  66. package/dist/components/ds-tooltip-data-viz.js +1 -1
  67. package/dist/components/ds-tooltip-data-viz.js.map +1 -1
  68. package/dist/components/ds-tooltip.js +1 -1
  69. package/dist/components/ds-tooltip.js.map +1 -1
  70. package/dist/components/p-1m8r0wFg.js +2 -0
  71. package/dist/components/p-1m8r0wFg.js.map +1 -0
  72. package/dist/components/p-3FLHei19.js +2 -0
  73. package/dist/components/p-3FLHei19.js.map +1 -0
  74. package/dist/components/p-B2_D4IDN.js +2 -0
  75. package/dist/components/p-B2_D4IDN.js.map +1 -0
  76. package/dist/components/p-B5NcWtpp.js +2 -0
  77. package/dist/components/p-B5NcWtpp.js.map +1 -0
  78. package/dist/components/{p-8Nt92j1-.js → p-B82KZX9D.js} +2 -2
  79. package/dist/components/p-BQq26pt9.js.map +1 -1
  80. package/dist/components/p-B_g8X0eh.js +2 -0
  81. package/dist/components/p-B_g8X0eh.js.map +1 -0
  82. package/dist/components/{p-DdFUTZ3y.js → p-Be3PHv34.js} +2 -2
  83. package/dist/components/p-CQmf2CeW.js +2 -0
  84. package/dist/components/p-CQmf2CeW.js.map +1 -0
  85. package/dist/components/{p-DOU4cbpc.js → p-CtwNUCnp.js} +2 -2
  86. package/dist/components/p-CuRjYmBk.js +2 -0
  87. package/dist/components/p-CuRjYmBk.js.map +1 -0
  88. package/dist/components/p-CwARVsZQ.js +2 -0
  89. package/dist/components/p-CwARVsZQ.js.map +1 -0
  90. package/dist/components/p-DohXlNCJ.js +2 -0
  91. package/dist/components/p-DohXlNCJ.js.map +1 -0
  92. package/dist/components/p-DuFm-ieS.js +2 -0
  93. package/dist/components/p-DuFm-ieS.js.map +1 -0
  94. package/dist/components/p-EK1hOP19.js.map +1 -1
  95. package/dist/lib/nav/index.js +3 -0
  96. package/dist/lib/nav/index.js.map +2 -2
  97. package/dist/lib/utils/control-width.d.ts +4 -0
  98. package/dist/lib/utils/index.d.ts +2 -0
  99. package/dist/lib/utils/index.js +20 -1
  100. package/dist/lib/utils/index.js.map +3 -3
  101. package/dist/lib/utils/token-defaults.d.ts +7 -0
  102. package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +21 -3
  103. package/dist/types/components/ButtonFilled/index.d.ts +1 -1
  104. package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +70 -0
  105. package/dist/types/components/ButtonUnfilled/index.d.ts +2 -0
  106. package/dist/types/components/Card/Card.d.ts +20 -0
  107. package/dist/types/components/Card/index.d.ts +2 -0
  108. package/dist/types/components/CardDataVizDonut/CardDataVizDonut.d.ts +26 -0
  109. package/dist/types/components/CardDataVizDonut/index.d.ts +2 -0
  110. package/dist/types/components/CardSetting/CardSetting.d.ts +7 -2
  111. package/dist/types/components/ChartDonut/ChartDonut.d.ts +31 -1
  112. package/dist/types/components/Checkbox/Checkbox.d.ts +1 -1
  113. package/dist/types/components/Chip/Chip.d.ts +18 -14
  114. package/dist/types/components/Chip/index.d.ts +1 -1
  115. package/dist/types/components/Input/Input.d.ts +1 -1
  116. package/dist/types/components/Pagination/Pagination.d.ts +1 -1
  117. package/dist/types/components/PanelNav/PanelNav.d.ts +2 -0
  118. package/dist/types/components/PanelTools/panel-tools-types.d.ts +1 -1
  119. package/dist/types/components/RadioGroup/RadioGroup.d.ts +2 -2
  120. package/dist/types/components/Select/Select.d.ts +34 -2
  121. package/dist/types/components/Select/index.d.ts +2 -0
  122. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -1
  123. package/dist/types/components/Slider/Slider.d.ts +1 -1
  124. package/dist/types/components/TabGroup/TabGroup.d.ts +1 -1
  125. package/dist/types/components/TabGroup/tab-item-utils.d.ts +2 -1
  126. package/dist/types/components/TabGroupNav/TabGroupNav.d.ts +1 -1
  127. package/dist/types/components/Tag/Tag.d.ts +0 -2
  128. package/dist/types/components/Tag/index.d.ts +1 -1
  129. package/dist/types/components/Toggle/Toggle.d.ts +1 -1
  130. package/dist/types/components/Tooltip/Tooltip.d.ts +25 -6
  131. package/dist/types/components/TooltipDataViz/TooltipDataViz.d.ts +18 -0
  132. package/dist/types/components.d.ts +474 -576
  133. package/dist/types/utils/control-width.d.ts +5 -0
  134. package/dist/types/utils/index.d.ts +2 -0
  135. package/dist/types/utils/token-defaults.d.ts +7 -0
  136. package/package.json +7 -4
  137. package/src/angular/index.ts +3 -9
  138. package/src/angular/proxies.ts +62 -215
  139. package/src/react/components.ts +3 -9
  140. package/src/react/ds-button-unfilled.ts +33 -0
  141. package/src/react/ds-card-data-viz-donut.ts +27 -0
  142. package/src/react/{ds-fade.ts → ds-card.ts} +7 -7
  143. package/src/react/ds-chip.ts +2 -10
  144. package/src/wc/components/BarNav/BarNav.tsx +3 -2
  145. package/src/wc/components/BarNav/bar-nav-tabs-menu-utils.ts +1 -1
  146. package/src/wc/components/ButtonFilled/ButtonFilled.tsx +97 -11
  147. package/src/wc/components/ButtonFilled/index.ts +7 -1
  148. package/src/wc/components/ButtonUnfilled/ButtonUnfilled.tsx +230 -0
  149. package/src/wc/components/ButtonUnfilled/index.ts +7 -0
  150. package/src/wc/components/Card/Card.tsx +79 -0
  151. package/src/wc/components/Card/index.ts +1 -0
  152. package/src/wc/components/{CardDataViz/CardDataViz.tsx → CardDataVizDonut/CardDataVizDonut.tsx} +47 -41
  153. package/src/wc/components/CardDataVizDonut/index.ts +1 -0
  154. package/src/wc/components/CardSetting/CardSetting.tsx +21 -36
  155. package/src/wc/components/ChartDonut/ChartDonut.tsx +159 -11
  156. package/src/wc/components/ChartLegend/ChartLegend.tsx +3 -0
  157. package/src/wc/components/Checkbox/Checkbox.tsx +5 -5
  158. package/src/wc/components/Chip/Chip.tsx +50 -67
  159. package/src/wc/components/Chip/index.ts +1 -1
  160. package/src/wc/components/Input/Input.tsx +4 -4
  161. package/src/wc/components/Menu/Menu.tsx +7 -4
  162. package/src/wc/components/Pagination/Pagination.tsx +6 -6
  163. package/src/wc/components/PanelNav/PanelNav.tsx +77 -48
  164. package/src/wc/components/PanelTools/PanelTools.tsx +19 -16
  165. package/src/wc/components/PanelTools/panel-tools-types.ts +1 -1
  166. package/src/wc/components/PanelTools/panel-tools-utils.ts +1 -1
  167. package/src/wc/components/RadioGroup/RadioGroup.tsx +7 -7
  168. package/src/wc/components/Select/Select.tsx +145 -64
  169. package/src/wc/components/Select/index.ts +1 -0
  170. package/src/wc/components/ShellGradientSwatch/ShellGradientSwatch.tsx +4 -3
  171. package/src/wc/components/Slider/Slider.tsx +7 -3
  172. package/src/wc/components/TabGroup/TabGroup.tsx +13 -7
  173. package/src/wc/components/TabGroup/tab-item-utils.ts +2 -1
  174. package/src/wc/components/TabGroupNav/TabGroupNav.tsx +14 -13
  175. package/src/wc/components/Tag/Tag.tsx +25 -24
  176. package/src/wc/components/Tag/index.ts +1 -1
  177. package/src/wc/components/Toggle/Toggle.tsx +6 -6
  178. package/src/wc/components/Tooltip/Tooltip.tsx +166 -64
  179. package/src/wc/components/TooltipDataViz/TooltipDataViz.tsx +57 -2
  180. package/src/wc/components.d.ts +474 -579
  181. package/src/wc/utils/control-density.css +42 -0
  182. package/src/wc/utils/control-inactive.css +20 -0
  183. package/src/wc/utils/control-width.css +28 -0
  184. package/src/wc/utils/control-width.ts +10 -0
  185. package/src/wc/utils/index.ts +2 -0
  186. package/src/wc/utils/interaction-fill.css +162 -0
  187. package/src/wc/utils/scroll-edge-fade.css +5 -0
  188. package/src/wc/utils/token-defaults.ts +10 -0
  189. package/dist/components/ds-accordion.js +0 -2
  190. package/dist/components/ds-accordion.js.map +0 -1
  191. package/dist/components/ds-breadcrumb.d.ts +0 -11
  192. package/dist/components/ds-breadcrumb.js +0 -2
  193. package/dist/components/ds-breadcrumb.js.map +0 -1
  194. package/dist/components/ds-button-unfilled-icon.d.ts +0 -11
  195. package/dist/components/ds-button-unfilled-icon.js +0 -2
  196. package/dist/components/ds-card-data-viz.js +0 -2
  197. package/dist/components/ds-card-data-viz.js.map +0 -1
  198. package/dist/components/ds-fade.js +0 -2
  199. package/dist/components/ds-fade.js.map +0 -1
  200. package/dist/components/ds-header.d.ts +0 -11
  201. package/dist/components/ds-header.js +0 -2
  202. package/dist/components/ds-header.js.map +0 -1
  203. package/dist/components/ds-scrollbar.d.ts +0 -11
  204. package/dist/components/ds-scrollbar.js +0 -2
  205. package/dist/components/ds-scrollbar.js.map +0 -1
  206. package/dist/components/ds-surface.d.ts +0 -11
  207. package/dist/components/ds-surface.js +0 -2
  208. package/dist/components/ds-surface.js.map +0 -1
  209. package/dist/components/ds-toast-provider.d.ts +0 -11
  210. package/dist/components/ds-toast-provider.js +0 -2
  211. package/dist/components/ds-toast-provider.js.map +0 -1
  212. package/dist/components/p-BMQbEIrQ.js +0 -2
  213. package/dist/components/p-BMQbEIrQ.js.map +0 -1
  214. package/dist/components/p-BSFgy5r6.js +0 -2
  215. package/dist/components/p-BSFgy5r6.js.map +0 -1
  216. package/dist/components/p-CBUIn8dk.js +0 -2
  217. package/dist/components/p-CBUIn8dk.js.map +0 -1
  218. package/dist/components/p-ChVwCVHy.js +0 -2
  219. package/dist/components/p-ChVwCVHy.js.map +0 -1
  220. package/dist/components/p-CpNIQXz6.js +0 -2
  221. package/dist/components/p-CpNIQXz6.js.map +0 -1
  222. package/dist/components/p-CvgHQfvT.js +0 -2
  223. package/dist/components/p-CvgHQfvT.js.map +0 -1
  224. package/dist/components/p-bDf6yBQd.js +0 -2
  225. package/dist/components/p-bDf6yBQd.js.map +0 -1
  226. package/dist/types/components/Accordion/Accordion.d.ts +0 -28
  227. package/dist/types/components/Breadcrumb/Breadcrumb.d.ts +0 -16
  228. package/dist/types/components/ButtonUnfilledIcon/ButtonUnfilledIcon.d.ts +0 -45
  229. package/dist/types/components/ButtonUnfilledIcon/index.d.ts +0 -2
  230. package/dist/types/components/CardDataViz/CardDataViz.d.ts +0 -22
  231. package/dist/types/components/CardDataViz/index.d.ts +0 -2
  232. package/dist/types/components/Fade/Fade.d.ts +0 -22
  233. package/dist/types/components/Fade/index.d.ts +0 -2
  234. package/dist/types/components/Header/Header.d.ts +0 -7
  235. package/dist/types/components/Scrollbar/Scrollbar.d.ts +0 -27
  236. package/dist/types/components/Surface/Surface.d.ts +0 -22
  237. package/dist/types/components/Toast/Toast.d.ts +0 -16
  238. package/dist/types/components/Toast/toast-service.d.ts +0 -24
  239. package/src/react/ds-accordion.ts +0 -27
  240. package/src/react/ds-breadcrumb.ts +0 -27
  241. package/src/react/ds-button-unfilled-icon.ts +0 -33
  242. package/src/react/ds-card-data-viz.ts +0 -26
  243. package/src/react/ds-header.ts +0 -26
  244. package/src/react/ds-scrollbar.ts +0 -26
  245. package/src/react/ds-surface.ts +0 -26
  246. package/src/react/ds-toast-provider.ts +0 -26
  247. package/src/wc/components/Accordion/Accordion.tsx +0 -144
  248. package/src/wc/components/Breadcrumb/Breadcrumb.tsx +0 -61
  249. package/src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.tsx +0 -129
  250. package/src/wc/components/ButtonUnfilledIcon/index.ts +0 -1
  251. package/src/wc/components/CardDataViz/index.ts +0 -1
  252. package/src/wc/components/Fade/Fade.tsx +0 -58
  253. package/src/wc/components/Fade/index.ts +0 -1
  254. package/src/wc/components/Header/Header.tsx +0 -33
  255. package/src/wc/components/Scrollbar/Scrollbar.tsx +0 -189
  256. package/src/wc/components/Surface/Surface.tsx +0 -81
  257. package/src/wc/components/Toast/Toast.tsx +0 -179
  258. package/src/wc/components/Toast/toast-service.ts +0 -67
  259. /package/dist/components/{ds-button-unfilled-icon.js.map → ds-button-unfilled.js.map} +0 -0
  260. /package/dist/components/{p-8Nt92j1-.js.map → p-B82KZX9D.js.map} +0 -0
  261. /package/dist/components/{p-DdFUTZ3y.js.map → p-Be3PHv34.js.map} +0 -0
  262. /package/dist/components/{p-DOU4cbpc.js.map → p-CtwNUCnp.js.map} +0 -0
@@ -1,4 +1,6 @@
1
1
  import { Component, Prop, State, Event, EventEmitter, Element, Watch, Listen, h, Host } from '@stencil/core';
2
+ import { controlWidthClass, type ControlWidth } from '../../utils/control-width';
3
+ import { resolveCssLengthPx, TOKEN_DEFAULTS } from '../../utils';
2
4
  import type { MenuItemData } from '../Menu/menu-types';
3
5
 
4
6
  export interface SelectOption {
@@ -6,6 +8,28 @@ export interface SelectOption {
6
8
  value: string;
7
9
  }
8
10
 
11
+ export type SelectSize = 'md' | 'sm' | 'xs';
12
+
13
+ export type SelectWidth = ControlWidth;
14
+
15
+ /** Regular (non-emphasis) type per density — selects stay visually lighter than buttons. */
16
+ const TEXT_VARIANT: Record<SelectSize, string> = {
17
+ md: 'text-body-medium',
18
+ sm: 'text-body-small',
19
+ xs: 'text-caption',
20
+ };
21
+
22
+ /** `ds-icon` size matching control-density icon metrics. */
23
+ const ICON_SIZE: Record<SelectSize, 'md' | 'sm' | 'xs'> = {
24
+ md: 'md',
25
+ sm: 'sm',
26
+ xs: 'xs',
27
+ };
28
+
29
+ /**
30
+ * Dropdown select — unfilled-button chrome (label + trailing ChevronDown) in
31
+ * control-density sizes, with `ds-menu` for the option list.
32
+ */
9
33
  @Component({
10
34
  tag: 'ds-select',
11
35
  styleUrl: 'Select.css',
@@ -24,10 +48,27 @@ export class Select {
24
48
  @Prop({ mutable: true }) value: string = '';
25
49
 
26
50
  /** Placeholder shown when no value is selected. */
27
- @Prop() placeholder: string = 'Select option';
51
+ @Prop() placeholder: string = 'Select';
52
+
53
+ /** Control density (height, padding, icon, type). */
54
+ @Prop() size: SelectSize = 'md';
28
55
 
29
- /** Disables interaction. */
30
- @Prop() inactive: boolean = false;
56
+ /** Width fit — fill the parent (default) or hug content. */
57
+ @Prop() width: SelectWidth = 'fill';
58
+
59
+ /** Disables interaction (25% opacity via ds-control-inactive). */
60
+ @Prop() isInactive: boolean = false;
61
+
62
+ /**
63
+ * When a value is selected, render the selected interaction fill (same recipe
64
+ * as unfilled-button `activeFill`). Default `true`. Pass `false` for
65
+ * foreground-only selection (primary label, no fill).
66
+ * Selected/open promotes the label to primary; chevron stays secondary.
67
+ */
68
+ @Prop() activeFill: boolean = true;
69
+
70
+ /** Show a 1px secondary inset border. Default on (matches unfilled button). */
71
+ @Prop() hasBorder: boolean = true;
31
72
 
32
73
  @Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;
33
74
  @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;
@@ -39,32 +80,28 @@ export class Select {
39
80
 
40
81
  private triggerEl: HTMLButtonElement | null = null;
41
82
  private menuEl: HTMLDsMenuElement | null = null;
83
+ /** True when the open was keyboard-driven — menu shows initial focus ring. */
84
+ private openWithFocusVisible = false;
42
85
 
43
86
  componentDidLoad() {
44
- // Wire up the menu anchor to the trigger button
45
87
  if (this.menuEl && this.triggerEl) {
46
88
  this.menuEl.anchor = this.triggerEl;
47
89
  }
48
- // Listen for menu selection events
49
- this.menuEl?.addEventListener('dsSelect', (e: Event) => {
50
- const detail = (e as CustomEvent<MenuItemData>).detail;
51
- if (detail?.value !== undefined) {
52
- this.value = String(detail.value);
53
- this.dsChange.emit(this.value);
54
- }
55
- this.isOpen = false;
56
- });
57
- // Listen for menu close event
58
- this.menuEl?.addEventListener('dsClose', () => {
59
- this.isOpen = false;
60
- });
90
+ this.menuEl?.addEventListener('dsSelect', this.handleMenuSelect);
91
+ this.menuEl?.addEventListener('dsClose', this.handleMenuClose);
92
+ this.syncMenuItems();
93
+ }
94
+
95
+ disconnectedCallback() {
96
+ this.menuEl?.removeEventListener('dsSelect', this.handleMenuSelect);
97
+ this.menuEl?.removeEventListener('dsClose', this.handleMenuClose);
61
98
  }
62
99
 
63
100
  @Watch('isOpen')
64
101
  onIsOpenChange(open: boolean) {
65
- if (this.menuEl) {
66
- this.menuEl.open = open;
67
- }
102
+ if (!this.menuEl) return;
103
+ this.menuEl.initialFocusVisible = open && this.openWithFocusVisible;
104
+ this.menuEl.open = open;
68
105
  }
69
106
 
70
107
  @Watch('options')
@@ -77,6 +114,19 @@ export class Select {
77
114
  this.syncMenuItems();
78
115
  }
79
116
 
117
+ private handleMenuSelect = (e: Event) => {
118
+ const detail = (e as CustomEvent<MenuItemData>).detail;
119
+ if (detail?.value !== undefined) {
120
+ this.value = String(detail.value);
121
+ this.dsChange.emit(this.value);
122
+ }
123
+ this.close();
124
+ };
125
+
126
+ private handleMenuClose = () => {
127
+ this.close();
128
+ };
129
+
80
130
  private syncMenuItems() {
81
131
  if (!this.menuEl) return;
82
132
  this.menuEl.items = this.options.map(opt => ({
@@ -86,32 +136,46 @@ export class Select {
86
136
  }));
87
137
  }
88
138
 
89
- private open() {
90
- if (this.inactive || !this.options.length) return;
91
- // Sync menu width to trigger width
92
- if (this.menuEl && this.triggerEl) {
93
- this.menuEl.menuWidth = `${this.triggerEl.offsetWidth}px`;
94
- this.menuEl.anchor = this.triggerEl;
95
- }
139
+ /**
140
+ * Size/place the menu so option labels line up with the select label.
141
+ * Menu section padding insets items; we expand min-width by that chrome and
142
+ * shift left by the same amount (`alignOffset`). Menu can grow wider than
143
+ * the field (left-bottom / `align="start"`) when labels need more room.
144
+ */
145
+ private syncMenuLayout() {
146
+ if (!this.menuEl || !this.triggerEl) return;
147
+ const sectionPadPx = resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);
148
+ const triggerW = this.triggerEl.offsetWidth;
149
+ // Prefer min-width over fixed width so long options can grow the menu.
150
+ this.menuEl.menuWidth = '';
151
+ this.menuEl.minWidth = `${triggerW + sectionPadPx * 2}px`;
152
+ this.menuEl.alignOffset = -sectionPadPx;
153
+ this.menuEl.anchor = this.triggerEl;
154
+ }
155
+
156
+ private open(opts: { focusVisible: boolean } = { focusVisible: false }) {
157
+ if (this.isInactive || !this.options.length) return;
158
+ this.openWithFocusVisible = opts.focusVisible;
159
+ this.syncMenuLayout();
96
160
  this.syncMenuItems();
97
161
  this.isOpen = true;
98
162
  }
99
163
 
100
164
  private close() {
101
165
  this.isOpen = false;
166
+ this.openWithFocusVisible = false;
102
167
  }
103
168
 
104
- private toggle() {
169
+ private toggle(opts: { focusVisible: boolean }) {
105
170
  if (this.isOpen) {
106
171
  this.close();
107
172
  } else {
108
- this.open();
173
+ this.open(opts);
109
174
  }
110
175
  }
111
176
 
112
177
  @Listen('keydown')
113
178
  handleKeyDown(e: KeyboardEvent) {
114
- // Only handle keydown on the trigger button
115
179
  if (document.activeElement !== this.triggerEl) return;
116
180
 
117
181
  if (!this.isOpen) {
@@ -121,67 +185,84 @@ export class Select {
121
185
  case 'Enter':
122
186
  case ' ':
123
187
  e.preventDefault();
124
- this.open();
188
+ this.open({ focusVisible: true });
125
189
  break;
126
190
  }
127
- } else {
128
- if (e.key === 'Escape') {
129
- e.preventDefault();
130
- this.close();
131
- this.triggerEl?.focus();
132
- }
191
+ } else if (e.key === 'Escape') {
192
+ e.preventDefault();
193
+ this.close();
194
+ this.triggerEl?.focus();
133
195
  }
134
196
  }
135
197
 
136
198
  private get selectedLabel(): string {
137
- const found = this.options.find(o => o.value === this.value);
138
- return found?.label ?? '';
199
+ return this.options.find(o => o.value === this.value)?.label ?? '';
139
200
  }
140
201
 
141
202
  private get hasSelection(): boolean {
142
- return !!this.options.find(o => o.value === this.value);
203
+ return this.options.some(o => o.value === this.value);
143
204
  }
144
205
 
145
206
  render() {
146
- const label = this.hasSelection ? this.selectedLabel : this.placeholder;
147
207
  const showPlaceholder = !this.hasSelection;
208
+ const hasValue = !showPlaceholder;
209
+ const label = showPlaceholder ? this.placeholder : this.selectedLabel;
210
+ const textVariant = TEXT_VARIANT[this.size];
211
+ const iconSize = ICON_SIZE[this.size];
212
+ const density = `ds-control--${this.size}`;
213
+ // Selected fill when a value is set (or while open), gated by `activeFill`.
214
+ const showSelectedFill =
215
+ !this.isInactive && this.activeFill && (hasValue || this.isOpen);
148
216
 
149
217
  return (
150
- <Host class={{ 'select-host': true, inactive: this.inactive }}>
218
+ <Host
219
+ class={{
220
+ 'select-host': true,
221
+ 'ds-control-inactive': this.isInactive,
222
+ 'ds-control--md': this.size === 'md',
223
+ 'ds-control--sm': this.size === 'sm',
224
+ 'ds-control--xs': this.size === 'xs',
225
+ ...controlWidthClass(this.width),
226
+ }}
227
+ >
151
228
  <button
152
229
  ref={el => {
153
230
  this.triggerEl = (el as HTMLButtonElement) ?? null;
154
231
  }}
155
232
  type="button"
156
- class={{ trigger: true, 'trigger--open': this.isOpen }}
157
- disabled={this.inactive}
233
+ class={{
234
+ trigger: true,
235
+ 'ds-focus-ring-inset': true,
236
+ 'ds-interaction-fill': true,
237
+ 'ds-interaction-fill--selected': showSelectedFill,
238
+ 'trigger--open': this.isOpen,
239
+ 'trigger--bordered': this.hasBorder,
240
+ 'trigger--placeholder': showPlaceholder,
241
+ 'trigger--has-value': hasValue,
242
+ [density]: true,
243
+ }}
244
+ disabled={this.isInactive}
158
245
  role="combobox"
159
- aria-haspopup="listbox"
160
- aria-expanded={this.isOpen}
246
+ aria-haspopup="menu"
247
+ aria-expanded={String(this.isOpen)}
161
248
  aria-label={this.ariaLabel}
162
249
  aria-labelledby={this.ariaLabelledby}
163
- onClick={() => this.toggle()}
250
+ onClick={() => this.toggle({ focusVisible: false })}
164
251
  >
165
- <span class={{ 'trigger__label': true, 'trigger__label--placeholder': showPlaceholder }}>
252
+ <span
253
+ class={{
254
+ 'trigger__label': true,
255
+ [textVariant]: true,
256
+ 'ds-interaction-fill__content': true,
257
+ }}
258
+ >
166
259
  {label}
167
260
  </span>
168
- <svg
169
- class="trigger__chevron"
170
- width="16"
171
- height="16"
172
- viewBox="0 0 16 16"
173
- fill="none"
174
- aria-hidden="true"
175
- >
176
- <path
177
- d="M4 6L8 10L12 6"
178
- stroke="currentColor"
179
- stroke-width="1.25"
180
- stroke-linecap="round"
181
- stroke-linejoin="round"
182
- />
183
- </svg>
261
+ <span class="trigger__chevron ds-interaction-fill__content" aria-hidden="true">
262
+ <ds-icon name="ChevronDown" size={iconSize} color="inherit" />
263
+ </span>
184
264
  </button>
265
+ {/* bottom + start: left-aligned under the field; wider menus grow right. */}
185
266
  <ds-menu
186
267
  ref={el => {
187
268
  this.menuEl = (el as HTMLDsMenuElement) ?? null;
@@ -0,0 +1 @@
1
+ export type { SelectOption, SelectSize, SelectWidth } from './Select';
@@ -19,14 +19,14 @@ export class ShellGradientSwatch {
19
19
  /** Selected — brand stroke + halo over inner fill/border/interaction stack. */
20
20
  @Prop({ reflect: true }) selected = false;
21
21
 
22
- @Prop() inactive = false;
22
+ @Prop() isInactive = false;
23
23
 
24
24
  @Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;
25
25
 
26
26
  @Event() dsSelect!: EventEmitter<ShellGradientPreset>;
27
27
 
28
28
  private handleClick = () => {
29
- if (this.inactive) return;
29
+ if (this.isInactive) return;
30
30
  this.dsSelect.emit(this.preset);
31
31
  };
32
32
 
@@ -40,6 +40,7 @@ export class ShellGradientSwatch {
40
40
  class={{
41
41
  'shell-gradient-swatch': true,
42
42
  'ds-focus-ring-inset': true,
43
+ 'ds-control-inactive': this.isInactive,
43
44
  'shell-gradient-swatch--selected': this.selected,
44
45
  'shell-gradient-swatch--none': this.preset === 'none',
45
46
  }}
@@ -53,7 +54,7 @@ export class ShellGradientSwatch {
53
54
  }
54
55
  aria-label={label}
55
56
  aria-pressed={this.selected ? 'true' : 'false'}
56
- disabled={this.inactive}
57
+ disabled={this.isInactive}
57
58
  onClick={this.handleClick}
58
59
  >
59
60
  <span class="shell-gradient-swatch__fill" aria-hidden="true" />
@@ -15,7 +15,7 @@ export class Slider {
15
15
  @Prop() max: number = 100;
16
16
  @Prop() step: number = 1;
17
17
  @Prop() label!: string;
18
- @Prop() inactive: boolean = false;
18
+ @Prop() isInactive: boolean = false;
19
19
  @Prop() valueText: string | undefined;
20
20
  /** Associates with an external <label>. Defaults to an auto-generated ID. */
21
21
  @Prop() inputId: string | undefined;
@@ -42,7 +42,11 @@ export class Slider {
42
42
  <span class="text-body-small-emphasis">{this.value}</span>
43
43
  </div>
44
44
  <div
45
- class={{ track: true, 'track--inactive': this.inactive, 'track--at-min': pct === 0 }}
45
+ class={{
46
+ track: true,
47
+ 'ds-control-inactive': this.isInactive,
48
+ 'track--at-min': pct === 0,
49
+ }}
46
50
  style={{ '--slider-pct': String(pct) } as Record<string, string>}
47
51
  >
48
52
  <div class="track-inner">
@@ -56,7 +60,7 @@ export class Slider {
56
60
  max={this.max}
57
61
  step={this.step}
58
62
  value={this.value}
59
- disabled={this.inactive}
63
+ disabled={this.isInactive}
60
64
  class="range-input"
61
65
  aria-valuetext={this.valueText}
62
66
  onInput={this.handleInput}
@@ -64,7 +64,7 @@ export class TabGroup {
64
64
  }
65
65
 
66
66
  /**
67
- * Find the next non-disabled tab index relative to `from`, stepping by `step`.
67
+ * Find the next non-inactive tab index relative to `from`, stepping by `step`.
68
68
  * Returns `from` unchanged if no other enabled tab exists.
69
69
  */
70
70
  private findEnabled(from: number, step: 1 | -1): number {
@@ -72,7 +72,7 @@ export class TabGroup {
72
72
  const len = tabs.length;
73
73
  for (let n = 0; n < len; n++) {
74
74
  const i = (from + step * (n + 1) + len * (n + 1)) % len;
75
- if (!tabs[i]?.disabled) return i;
75
+ if (!tabs[i]?.isInactive) return i;
76
76
  }
77
77
  return from;
78
78
  }
@@ -102,10 +102,10 @@ export class TabGroup {
102
102
  }
103
103
 
104
104
  if (e.key === 'Home') {
105
- const first = tabs.findIndex(t => !t.disabled);
105
+ const first = tabs.findIndex(t => !t.isInactive);
106
106
  nextIdx = first === -1 ? null : first;
107
107
  } else if (e.key === 'End') {
108
- const lastEnabled = [...tabs].reverse().findIndex(t => !t.disabled);
108
+ const lastEnabled = [...tabs].reverse().findIndex(t => !t.isInactive);
109
109
  nextIdx = lastEnabled === -1 ? null : tabs.length - 1 - lastEnabled;
110
110
  }
111
111
 
@@ -162,14 +162,20 @@ export class TabGroup {
162
162
  tab: true,
163
163
  'tab--selected': isSelected,
164
164
  'ds-focus-ring-inset': true,
165
+ 'ds-interaction-fill': !tab.isInactive,
166
+ 'ds-interaction-fill--on-medium': bgClass === 'on-medium',
167
+ 'ds-interaction-fill--on-bold': bgClass === 'on-bold',
168
+ 'ds-interaction-fill--on-strong': bgClass === 'on-strong',
169
+ 'ds-interaction-fill--on-always-dark': bgClass === 'on-always-dark',
170
+ 'ds-control-inactive': !!tab.isInactive,
165
171
  [bgClass]: !!bgClass,
166
172
  }}
167
173
  aria-selected={isSelected}
168
- aria-disabled={tab.disabled || undefined}
174
+ aria-disabled={tab.isInactive || undefined}
169
175
  aria-controls={tab.panelId ?? undefined}
170
- disabled={tab.disabled}
176
+ disabled={tab.isInactive}
171
177
  tabIndex={isSelected ? 0 : -1}
172
- onClick={() => !tab.disabled && this.selectTab(tab.id)}
178
+ onClick={() => !tab.isInactive && this.selectTab(tab.id)}
173
179
  >
174
180
  <span class={{
175
181
  tab__label: true,
@@ -5,7 +5,8 @@ export interface TabItemDivider {
5
5
  export interface TabItemTab {
6
6
  id: string;
7
7
  label: string;
8
- disabled?: boolean;
8
+ /** Disables the tab (same meaning as control `isInactive`). */
9
+ isInactive?: boolean;
9
10
  /** id of the tabpanel this tab controls */
10
11
  panelId?: string;
11
12
  /** Show a notification dot (brand) on the tab — no count, matching panel-nav. */
@@ -91,23 +91,23 @@ export class TabGroupNav {
91
91
  @Method()
92
92
  async focusLastTab() {
93
93
  const tabs = this.selectableTabs;
94
- const last = [...tabs].reverse().find(tab => !tab.disabled);
94
+ const last = [...tabs].reverse().find(tab => !tab.isInactive);
95
95
  if (last) await this.focusTab(last.id);
96
96
  }
97
97
 
98
98
  @Method()
99
99
  async focusFirstTab() {
100
- const first = this.selectableTabs.find(tab => !tab.disabled);
100
+ const first = this.selectableTabs.find(tab => !tab.isInactive);
101
101
  if (first) await this.focusTab(first.id);
102
102
  }
103
103
 
104
104
  private syncFocusedTabId(preferred: string) {
105
105
  const tabs = this.selectableTabs;
106
- if (tabs.some(tab => tab.id === preferred && !tab.disabled)) {
106
+ if (tabs.some(tab => tab.id === preferred && !tab.isInactive)) {
107
107
  this.focusedTabId = preferred;
108
108
  return;
109
109
  }
110
- const first = tabs.find(tab => !tab.disabled);
110
+ const first = tabs.find(tab => !tab.isInactive);
111
111
  this.focusedTabId = first?.id ?? '';
112
112
  }
113
113
 
@@ -127,13 +127,13 @@ export class TabGroupNav {
127
127
  private findEnabledLinear(from: number, step: 1 | -1): number | null {
128
128
  const tabs = this.selectableTabs;
129
129
  for (let i = from + step; i >= 0 && i < tabs.length; i += step) {
130
- if (!tabs[i]?.disabled) return i;
130
+ if (!tabs[i]?.isInactive) return i;
131
131
  }
132
132
  return null;
133
133
  }
134
134
 
135
135
  /**
136
- * Find the next non-disabled tab index relative to `from`, stepping by `step`.
136
+ * Find the next non-inactive tab index relative to `from`, stepping by `step`.
137
137
  * Returns `from` unchanged if no other enabled tab exists.
138
138
  */
139
139
  private findEnabled(from: number, step: 1 | -1): number {
@@ -141,7 +141,7 @@ export class TabGroupNav {
141
141
  const len = tabs.length;
142
142
  for (let n = 0; n < len; n++) {
143
143
  const i = (from + step * (n + 1) + len * (n + 1)) % len;
144
- if (!tabs[i]?.disabled) return i;
144
+ if (!tabs[i]?.isInactive) return i;
145
145
  }
146
146
  return from;
147
147
  }
@@ -156,7 +156,7 @@ export class TabGroupNav {
156
156
  private activateFocusedTab(e: KeyboardEvent) {
157
157
  if (e.key !== 'Enter' && e.key !== ' ') return;
158
158
  const tab = this.selectableTabs.find(item => item.id === this.focusedTabId);
159
- if (!tab || tab.disabled) return;
159
+ if (!tab || tab.isInactive) return;
160
160
  e.preventDefault();
161
161
  this.selectTab(tab.id, { focus: true });
162
162
  }
@@ -221,10 +221,10 @@ export class TabGroupNav {
221
221
  }
222
222
 
223
223
  if (e.key === 'Home') {
224
- const first = tabs.findIndex(t => !t.disabled);
224
+ const first = tabs.findIndex(t => !t.isInactive);
225
225
  nextIdx = first === -1 ? null : first;
226
226
  } else if (e.key === 'End') {
227
- const lastEnabled = [...tabs].reverse().findIndex(t => !t.disabled);
227
+ const lastEnabled = [...tabs].reverse().findIndex(t => !t.isInactive);
228
228
  nextIdx = lastEnabled === -1 ? null : tabs.length - 1 - lastEnabled;
229
229
  }
230
230
 
@@ -305,14 +305,15 @@ export class TabGroupNav {
305
305
  tab: true,
306
306
  'tab--selected': isSelected,
307
307
  'ds-focus-ring-inset': true,
308
+ 'ds-control-inactive': !!tab.isInactive,
308
309
  [bgClass]: !!bgClass,
309
310
  }}
310
311
  aria-selected={isSelected}
311
- aria-disabled={tab.disabled || undefined}
312
+ aria-disabled={tab.isInactive || undefined}
312
313
  aria-controls={tab.panelId ?? undefined}
313
- disabled={tab.disabled}
314
+ disabled={tab.isInactive}
314
315
  tabIndex={this.tabIndexForTab(tab)}
315
- onClick={() => !tab.disabled && this.selectTab(tab.id)}
316
+ onClick={() => !tab.isInactive && this.selectTab(tab.id)}
316
317
  onFocus={() => {
317
318
  if (!this.selectionFollowsFocus) {
318
319
  this.focusedTabId = tab.id;
@@ -1,17 +1,25 @@
1
1
  import { Component, Prop, h, Host } from '@stencil/core';
2
2
 
3
- export type TagIntent = 'neutral' | 'brand' | 'ai' | 'negative' | 'warning' | 'caution' | 'positive';
4
- export type TagContrast = 'strong' | 'bold' | 'medium' | 'faint';
5
- export type TagElevation = 'none' | 'flat' | 'elevated';
6
- export type TagSize = 'md' | 'sm' | 'xs';
3
+ export type TagIntent = 'neutral' | 'brand' | 'ai' | 'negative' | 'warning' | 'caution' | 'positive';
4
+ export type TagContrast = 'strong' | 'bold' | 'medium' | 'faint';
5
+ export type TagSize = 'md' | 'sm' | 'xs';
7
6
 
7
+ /** Text variant per control-density recipe (md / sm / xs). */
8
8
  const TEXT_VARIANT: Record<TagSize, string> = {
9
9
  md: 'text-body-medium',
10
10
  sm: 'text-body-small',
11
11
  xs: 'text-caption',
12
12
  };
13
13
 
14
- const ICON_SIZE: Record<TagSize, number> = { md: 20, sm: 16, xs: 12 };
14
+ /**
15
+ * `ds-icon` size prop matching control-density icon metrics
16
+ * (md→20 / sm→16 / xs→12 via `--dimension-iconography-*`).
17
+ */
18
+ const ICON_SIZE: Record<TagSize, 'md' | 'sm' | 'xs'> = {
19
+ md: 'md',
20
+ sm: 'sm',
21
+ xs: 'xs',
22
+ };
15
23
 
16
24
  @Component({
17
25
  tag: 'ds-tag',
@@ -22,13 +30,11 @@ export class Tag {
22
30
  @Prop() label!: string;
23
31
  @Prop() intent: TagIntent = 'neutral';
24
32
  @Prop() contrast: TagContrast = 'faint';
25
- @Prop() elevation: TagElevation = 'none';
26
33
  @Prop() size: TagSize = 'md';
27
34
  @Prop() rounded: boolean = false;
28
35
  @Prop() maxWidth: string | number | undefined;
29
36
 
30
37
  render() {
31
- const sz = this.size.toUpperCase() as 'MD' | 'SM' | 'XS';
32
38
  const textVariant = TEXT_VARIANT[this.size];
33
39
  const iconSize = ICON_SIZE[this.size];
34
40
 
@@ -36,29 +42,24 @@ export class Tag {
36
42
  ? { maxWidth: typeof this.maxWidth === 'number' ? `${this.maxWidth}px` : this.maxWidth }
37
43
  : undefined;
38
44
 
39
- const hostCls: Record<string, boolean> = {
40
- 'tag': true,
41
- [`tag--intent-${this.intent}`]: true,
42
- [`tag--contrast-${this.contrast}`]: true,
43
- [`tag--elevation-${this.elevation}`]: true,
44
- [`tag--size-${this.size}`]: true,
45
- 'tag--rounded': this.rounded,
46
- 'tag--rounded-no-icon-left': this.rounded, // consumer overrides via slot presence hack
47
- 'tag--rounded-no-remove-right': this.rounded,
48
- [`tag--icon-left-${sz}`]: false, // toggled by slotchange
49
- [`tag--icon-right-${sz}`]: false,
50
- };
51
-
52
45
  return (
53
46
  <Host
54
- class={hostCls}
47
+ class={{
48
+ 'tag': true,
49
+ [`tag--intent-${this.intent}`]: true,
50
+ [`tag--contrast-${this.contrast}`]: true,
51
+ [`tag--size-${this.size}`]: true,
52
+ 'ds-control--md': this.size === 'md',
53
+ 'ds-control--sm': this.size === 'sm',
54
+ 'ds-control--xs': this.size === 'xs',
55
+ 'tag--rounded': this.rounded,
56
+ }}
55
57
  style={maxWidthStyle}
56
58
  >
57
- {/* Leading icon slot — consumer provides an icon element */}
58
- <span class="tag__icon-slot" style={{ fontSize: `${iconSize}px`, lineHeight: '0' }}>
59
+ {/* Leading icon — consumer provides ds-icon (or any SVG); prefer size matching tag size. */}
60
+ <span class="tag__icon-slot" data-icon-size={iconSize}>
59
61
  <slot name="icon" />
60
62
  </span>
61
- {/* Label */}
62
63
  <span class={{ 'tag__label': true, [textVariant]: true }}>
63
64
  {this.label}
64
65
  </span>
@@ -1 +1 @@
1
- export type { TagIntent, TagContrast, TagElevation, TagSize } from './Tag';
1
+ export type { TagIntent, TagContrast, TagSize } from './Tag';
@@ -7,18 +7,18 @@ import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
7
7
  })
8
8
  export class Toggle {
9
9
  @Prop({ mutable: true }) checked: boolean = false;
10
- @Prop() inactive: boolean = false;
10
+ @Prop() isInactive: boolean = false;
11
11
 
12
12
  @Event() dsChange!: EventEmitter<boolean>;
13
13
 
14
14
  private handleClick = () => {
15
- if (this.inactive) return;
15
+ if (this.isInactive) return;
16
16
  this.checked = !this.checked;
17
17
  this.dsChange.emit(this.checked);
18
18
  };
19
19
 
20
20
  private handleKeyDown = (e: KeyboardEvent) => {
21
- if (this.inactive) return;
21
+ if (this.isInactive) return;
22
22
  if (e.key === ' ' || e.key === 'Enter') {
23
23
  e.preventDefault();
24
24
  this.handleClick();
@@ -30,9 +30,9 @@ export class Toggle {
30
30
  <Host
31
31
  role="switch"
32
32
  aria-checked={String(this.checked)}
33
- aria-disabled={this.inactive || undefined}
34
- tabIndex={this.inactive ? -1 : 0}
35
- class={{ toggle: true, checked: this.checked, inactive: this.inactive }}
33
+ aria-disabled={this.isInactive || undefined}
34
+ tabIndex={this.isInactive ? -1 : 0}
35
+ class={{ toggle: true, checked: this.checked, 'ds-control-inactive': this.isInactive }}
36
36
  onClick={this.handleClick}
37
37
  onKeyDown={this.handleKeyDown}
38
38
  >