@ds-mo/ui 8.3.1 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +28 -3
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +233 -0
  4. package/dist/agent.json +155 -174
  5. package/dist/angular/ds-menu.d.ts +6 -1
  6. package/dist/angular/ds-menu.js +7 -4
  7. package/dist/angular/ds-swatch-picker.d.ts +15 -0
  8. package/dist/angular/{ds-pagination.js → ds-swatch-picker.js} +15 -15
  9. package/dist/angular/index.d.ts +1 -1
  10. package/dist/angular/index.js +1 -2
  11. package/dist/angular/proxies.d.ts +1 -2
  12. package/dist/angular/proxies.js +1 -2
  13. package/dist/components/ds-app-shell.js +1 -1
  14. package/dist/components/ds-badge.js +1 -1
  15. package/dist/components/ds-banner.js +1 -1
  16. package/dist/components/ds-bar-nav.js +1 -1
  17. package/dist/components/ds-bar-nav.js.map +1 -1
  18. package/dist/components/ds-button-filled.js +1 -1
  19. package/dist/components/ds-button-unfilled.js +1 -1
  20. package/dist/components/ds-card-data-viz-donut.js +1 -1
  21. package/dist/components/ds-card-setting.js +1 -1
  22. package/dist/components/ds-card-shell-data-viz.js +1 -1
  23. package/dist/components/ds-card.js +1 -1
  24. package/dist/components/ds-chart-bar.js +1 -1
  25. package/dist/components/ds-chart-bar.js.map +1 -1
  26. package/dist/components/ds-chart-donut.js +1 -1
  27. package/dist/components/ds-chart-legend.js +1 -1
  28. package/dist/components/ds-chart-line.js +1 -1
  29. package/dist/components/ds-checkbox.js +1 -1
  30. package/dist/components/ds-chip.js +1 -1
  31. package/dist/components/ds-empty-state.js +1 -1
  32. package/dist/components/ds-field.js +1 -1
  33. package/dist/components/ds-input.js +1 -1
  34. package/dist/components/ds-menu.js +1 -1
  35. package/dist/components/ds-modal.js +1 -1
  36. package/dist/components/ds-panel-nav.js +1 -1
  37. package/dist/components/ds-panel-nav.js.map +1 -1
  38. package/dist/components/ds-panel-sub-nav.js +1 -1
  39. package/dist/components/ds-panel-tools.js +1 -1
  40. package/dist/components/ds-radio.js +1 -1
  41. package/dist/components/ds-select-multi.js +1 -1
  42. package/dist/components/ds-select-multi.js.map +1 -1
  43. package/dist/components/ds-select.js +1 -1
  44. package/dist/components/ds-select.js.map +1 -1
  45. package/dist/components/ds-shell-gradient-picker.js +1 -1
  46. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  47. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  48. package/dist/components/ds-shell-gradient-swatch.js.map +1 -1
  49. package/dist/components/ds-skeleton.js +1 -1
  50. package/dist/components/ds-skeleton.js.map +1 -1
  51. package/dist/components/ds-slider.js +1 -1
  52. package/dist/components/ds-slider.js.map +1 -1
  53. package/dist/components/{ds-pagination.d.ts → ds-swatch-picker.d.ts} +4 -4
  54. package/dist/components/ds-swatch-picker.js +2 -0
  55. package/dist/components/ds-swatch-picker.js.map +1 -0
  56. package/dist/components/ds-tab-group.js +1 -1
  57. package/dist/components/ds-tag.js +1 -1
  58. package/dist/components/ds-text.js +1 -1
  59. package/dist/components/ds-tooltip-data-viz.js +1 -1
  60. package/dist/components/ds-tooltip.js +1 -1
  61. package/dist/components/{p-CysJosvS.js → p-B-zhgAvL.js} +2 -2
  62. package/dist/components/{p-C21sIElQ.js → p-BLzivrE3.js} +2 -2
  63. package/dist/components/{p-DAuadHYu.js → p-BbV68F23.js} +2 -2
  64. package/dist/components/{p-_y99wWNU.js → p-C7RObioO.js} +2 -2
  65. package/dist/components/{p-DxQ4gzbA.js → p-CZ9wemOA.js} +2 -2
  66. package/dist/components/{p-BKA6houZ.js → p-CbS_WWs2.js} +2 -2
  67. package/dist/components/p-D1HFMTdC.js +2 -0
  68. package/dist/components/p-D1HFMTdC.js.map +1 -0
  69. package/dist/components/{p-BGcSrHKl.js → p-DT2MK8Kt.js} +2 -2
  70. package/dist/components/{p-DV23KMU-.js → p-DyZj3k9S.js} +2 -2
  71. package/dist/components/p-HBIxvS5C.js +2 -0
  72. package/dist/components/p-HBIxvS5C.js.map +1 -0
  73. package/dist/components/{p-D6hvtWDk.js → p-KRGBGTu5.js} +2 -2
  74. package/dist/components/p-l6PmnW_P.js +2 -0
  75. package/dist/components/p-l6PmnW_P.js.map +1 -0
  76. package/dist/components/p-tQ1QHeOE.js +2 -0
  77. package/dist/components/p-tQ1QHeOE.js.map +1 -0
  78. package/dist/docs/components.json +546 -1024
  79. package/dist/lib/components/Menu/menu-placement.d.ts +6 -3
  80. package/dist/lib/shell/index.d.ts +2 -2
  81. package/dist/lib/shell/index.js +40 -39
  82. package/dist/lib/shell/index.js.map +3 -3
  83. package/dist/lib/shell/shell-gradient-presets.d.ts +15 -0
  84. package/dist/lib/utils/choice-popup-alignment.d.ts +20 -0
  85. package/dist/lib/utils/index.d.ts +2 -0
  86. package/dist/lib/utils/index.js +16 -0
  87. package/dist/lib/utils/index.js.map +3 -3
  88. package/dist/mcp/cli.js +31409 -0
  89. package/dist/mcp-data/registry/app-shell.json +210 -0
  90. package/dist/mcp-data/registry/badge.json +289 -0
  91. package/dist/mcp-data/registry/banner.json +206 -0
  92. package/dist/mcp-data/registry/bar-nav.json +341 -0
  93. package/dist/mcp-data/registry/button-filled.json +386 -0
  94. package/dist/mcp-data/registry/button-unfilled.json +537 -0
  95. package/dist/mcp-data/registry/card-data-viz-donut.json +143 -0
  96. package/dist/mcp-data/registry/card-setting.json +194 -0
  97. package/dist/mcp-data/registry/card-shell-data-viz.json +173 -0
  98. package/dist/mcp-data/registry/card.json +126 -0
  99. package/dist/mcp-data/registry/chart-bar.json +121 -0
  100. package/dist/mcp-data/registry/chart-donut.json +257 -0
  101. package/dist/mcp-data/registry/chart-legend.json +209 -0
  102. package/dist/mcp-data/registry/chart-line.json +153 -0
  103. package/dist/mcp-data/registry/checkbox.json +361 -0
  104. package/dist/mcp-data/registry/chip.json +276 -0
  105. package/dist/mcp-data/registry/divider.json +225 -0
  106. package/dist/mcp-data/registry/empty-state.json +197 -0
  107. package/dist/mcp-data/registry/field.json +222 -0
  108. package/dist/mcp-data/registry/icon.json +242 -0
  109. package/dist/mcp-data/registry/input.json +597 -0
  110. package/dist/mcp-data/registry/loader.json +192 -0
  111. package/dist/mcp-data/registry/menu.json +459 -0
  112. package/dist/mcp-data/registry/modal.json +150 -0
  113. package/dist/mcp-data/registry/panel-nav.json +622 -0
  114. package/dist/mcp-data/registry/panel-sub-nav.json +249 -0
  115. package/dist/mcp-data/registry/panel-tools.json +374 -0
  116. package/dist/mcp-data/registry/radio.json +374 -0
  117. package/dist/mcp-data/registry/registry.json +5389 -0
  118. package/dist/mcp-data/registry/select-multi.json +755 -0
  119. package/dist/mcp-data/registry/select.json +740 -0
  120. package/dist/mcp-data/registry/shell-gradient-picker.json +195 -0
  121. package/dist/mcp-data/registry/shell-gradient-swatch.json +215 -0
  122. package/dist/mcp-data/registry/skeleton.json +282 -0
  123. package/dist/mcp-data/registry/slider.json +657 -0
  124. package/dist/mcp-data/registry/swatch-picker.json +231 -0
  125. package/dist/mcp-data/registry/switch.json +361 -0
  126. package/dist/mcp-data/registry/tab-group.json +255 -0
  127. package/dist/mcp-data/registry/tag.json +352 -0
  128. package/dist/mcp-data/registry/text.json +371 -0
  129. package/dist/mcp-data/registry/tooltip-data-viz.json +158 -0
  130. package/dist/mcp-data/registry/tooltip.json +311 -0
  131. package/dist/react/components.d.ts +1 -2
  132. package/dist/react/components.js +1 -2
  133. package/dist/react/ds-menu.d.ts +1 -0
  134. package/dist/react/ds-menu.js +2 -1
  135. package/dist/react/ds-swatch-picker.d.ts +12 -0
  136. package/dist/react/ds-swatch-picker.js +12 -0
  137. package/dist/types/components/Menu/Menu.d.ts +7 -1
  138. package/dist/types/components/Menu/menu-placement.d.ts +6 -3
  139. package/dist/types/components/Menu/menu-types.d.ts +14 -1
  140. package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +2 -5
  141. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -0
  142. package/dist/types/components/SwatchPicker/SwatchPicker.d.ts +21 -0
  143. package/dist/types/components/SwatchPicker/swatch-picker-types.d.ts +26 -0
  144. package/dist/types/components.d.ts +117 -237
  145. package/dist/types/shell/index.d.ts +2 -2
  146. package/dist/types/shell/shell-gradient-presets.d.ts +15 -0
  147. package/dist/types/utils/choice-popup-alignment.d.ts +21 -0
  148. package/dist/types/utils/index.d.ts +2 -0
  149. package/package.json +6 -2
  150. package/dist/angular/ds-pagination.d.ts +0 -15
  151. package/dist/angular/ds-table.d.ts +0 -33
  152. package/dist/angular/ds-table.js +0 -48
  153. package/dist/components/ds-pagination.js +0 -2
  154. package/dist/components/ds-pagination.js.map +0 -1
  155. package/dist/components/ds-table.d.ts +0 -11
  156. package/dist/components/ds-table.js +0 -2
  157. package/dist/components/ds-table.js.map +0 -1
  158. package/dist/components/p-CKV6rOGa.js +0 -2
  159. package/dist/components/p-CKV6rOGa.js.map +0 -1
  160. package/dist/components/p-CSTr2132.js +0 -2
  161. package/dist/components/p-CSTr2132.js.map +0 -1
  162. package/dist/components/p-C_cu-HIT.js +0 -2
  163. package/dist/components/p-C_cu-HIT.js.map +0 -1
  164. package/dist/components/p-D0B9uDTP.js +0 -2
  165. package/dist/components/p-D0B9uDTP.js.map +0 -1
  166. package/dist/components/p-DhxWfdX0.js +0 -2
  167. package/dist/components/p-DhxWfdX0.js.map +0 -1
  168. package/dist/components/p-Pip-LNhl.js +0 -2
  169. package/dist/components/p-Pip-LNhl.js.map +0 -1
  170. package/dist/react/ds-pagination.d.ts +0 -12
  171. package/dist/react/ds-pagination.js +0 -12
  172. package/dist/react/ds-table.d.ts +0 -21
  173. package/dist/react/ds-table.js +0 -16
  174. package/dist/types/components/Pagination/Pagination.d.ts +0 -14
  175. package/dist/types/components/Table/Table.d.ts +0 -56
  176. /package/dist/components/{p-CysJosvS.js.map → p-B-zhgAvL.js.map} +0 -0
  177. /package/dist/components/{p-C21sIElQ.js.map → p-BLzivrE3.js.map} +0 -0
  178. /package/dist/components/{p-DAuadHYu.js.map → p-BbV68F23.js.map} +0 -0
  179. /package/dist/components/{p-_y99wWNU.js.map → p-C7RObioO.js.map} +0 -0
  180. /package/dist/components/{p-DxQ4gzbA.js.map → p-CZ9wemOA.js.map} +0 -0
  181. /package/dist/components/{p-BKA6houZ.js.map → p-CbS_WWs2.js.map} +0 -0
  182. /package/dist/components/{p-BGcSrHKl.js.map → p-DT2MK8Kt.js.map} +0 -0
  183. /package/dist/components/{p-DV23KMU-.js.map → p-DyZj3k9S.js.map} +0 -0
  184. /package/dist/components/{p-D6hvtWDk.js.map → p-KRGBGTu5.js.map} +0 -0
@@ -0,0 +1,231 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "swatch-picker",
4
+ "title": "SwatchPicker",
5
+ "description": "Compact one-of-many picker for curated flat-color, gradient, or visually absent presets with optional section separators and roving radio-group interaction.",
6
+ "type": "registry:ui",
7
+ "meta": {
8
+ "library": "@ds-mo/ui",
9
+ "distribution": "npm",
10
+ "storybook": "https://zainadeel.github.io/compomo/?path=/story/swatch-picker",
11
+ "source": "src/wc/components/SwatchPicker/SwatchPicker.tsx",
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "general",
15
+ "status": "stable",
16
+ "summary": "Compact one-of-many picker for curated flat-color, gradient, or visually absent presets with optional section separators and roving radio-group interaction.",
17
+ "useWhen": [
18
+ "Users choose exactly one visual preset from a small curated set.",
19
+ "Options are best compared through compact flat-color or gradient previews.",
20
+ "A settings surface or richer menu needs an immediately applied visual preference."
21
+ ],
22
+ "avoidWhen": [
23
+ "Users need an unrestricted spectrum, numeric channels, opacity editing, or custom CSS gradient construction.",
24
+ "Text labels are the primary distinction between choices; use Radio instead.",
25
+ "The choice is a submitted form value with validation or a long compact list; use Radio or Select.",
26
+ "Color or gradient is the only indication of status, meaning, or required action."
27
+ ],
28
+ "alternatives": [
29
+ {
30
+ "when": "The user needs an arbitrary color rather than a curated preset.",
31
+ "component": "component:ds-input",
32
+ "reason": "SwatchPicker intentionally does not provide spectrum, channel, or free-form value editing."
33
+ },
34
+ {
35
+ "when": "Visible text labels are more important than visual previews.",
36
+ "component": "component:ds-radio",
37
+ "reason": "Radio presents explicit one-of-many option labels and native form behavior."
38
+ },
39
+ {
40
+ "when": "The option set is long or must remain compact.",
41
+ "component": "component:ds-select",
42
+ "reason": "Select scales a larger named choice set into an anchored listbox."
43
+ }
44
+ ],
45
+ "commonlyComposedWith": [
46
+ "component:ds-menu",
47
+ "component:ds-app-shell"
48
+ ],
49
+ "accessibility": [
50
+ "Provide a localized groupLabel describing the complete choice.",
51
+ "Give every option a meaningful label because its visual preview is never its accessible name.",
52
+ "The options form one radio group with one Tab stop; Arrow keys wrap across active options while Home and End select the boundaries.",
53
+ "Inactive options remain named and disabled, and keyboard movement skips them."
54
+ ],
55
+ "states": [
56
+ "value is the selected option value and updates when the user chooses an active option.",
57
+ "options provides one flat list; sections takes precedence and adds visual separators while preserving one radio group.",
58
+ "A preview may use a token-based backgroundColor, a CSS gradient backgroundImage, and an optional opacity from zero through one.",
59
+ "The parent owns persistence and application of the selected value; SwatchPicker owns only choice interaction and dsChange."
60
+ ],
61
+ "responsiveBehavior": [
62
+ "Options wrap within the width supplied by the parent while retaining compact fixed swatch geometry.",
63
+ "Keep the set small enough for direct visual comparison; switch to a named list when wrapping harms comprehension."
64
+ ],
65
+ "frameworkCaveats": {
66
+ "customElements": [
67
+ "Assign options or sections as JavaScript properties, bind value, and listen for dsChange."
68
+ ],
69
+ "react": [
70
+ "Use DsSwatchPicker, pass option arrays as props, and handle onDsChange."
71
+ ],
72
+ "angular": [
73
+ "Use property bindings for options or sections and handle dsChange to update application state."
74
+ ]
75
+ },
76
+ "references": [
77
+ {
78
+ "label": "Executable stories",
79
+ "path": "src/wc/components/SwatchPicker/SwatchPicker.stories.ts"
80
+ }
81
+ ]
82
+ },
83
+ "api": {
84
+ "props": {
85
+ "groupLabel": {
86
+ "type": "string",
87
+ "resolvedType": "string",
88
+ "attribute": "group-label",
89
+ "default": "'Swatch options'",
90
+ "required": false,
91
+ "mutable": false,
92
+ "description": "Accessible name for the complete one-of-many choice."
93
+ },
94
+ "options": {
95
+ "type": "SwatchPickerOption[]",
96
+ "resolvedType": "SwatchPickerOption[]",
97
+ "default": "[]",
98
+ "required": false,
99
+ "mutable": false,
100
+ "description": "Flat option list. Sections take precedence when supplied. Assign as a JavaScript property."
101
+ },
102
+ "sections": {
103
+ "type": "SwatchPickerSection[]",
104
+ "resolvedType": "SwatchPickerSection[]",
105
+ "default": "[]",
106
+ "required": false,
107
+ "mutable": false,
108
+ "description": "Visually separated option groups that remain one radio group. Assign as a JavaScript property."
109
+ },
110
+ "value": {
111
+ "type": "string",
112
+ "resolvedType": "string",
113
+ "attribute": "value",
114
+ "default": "''",
115
+ "required": false,
116
+ "mutable": true,
117
+ "description": "Selected option value."
118
+ }
119
+ },
120
+ "events": [
121
+ {
122
+ "name": "dsChange",
123
+ "detail": "string",
124
+ "bubbles": true,
125
+ "cancelable": true,
126
+ "composed": true
127
+ }
128
+ ],
129
+ "methods": [],
130
+ "slots": []
131
+ },
132
+ "props": {
133
+ "groupLabel": {
134
+ "type": "string",
135
+ "resolvedType": "string",
136
+ "attribute": "group-label",
137
+ "default": "'Swatch options'",
138
+ "required": false,
139
+ "mutable": false,
140
+ "description": "Accessible name for the complete one-of-many choice."
141
+ },
142
+ "options": {
143
+ "type": "SwatchPickerOption[]",
144
+ "resolvedType": "SwatchPickerOption[]",
145
+ "default": "[]",
146
+ "required": false,
147
+ "mutable": false,
148
+ "description": "Flat option list. Sections take precedence when supplied. Assign as a JavaScript property."
149
+ },
150
+ "sections": {
151
+ "type": "SwatchPickerSection[]",
152
+ "resolvedType": "SwatchPickerSection[]",
153
+ "default": "[]",
154
+ "required": false,
155
+ "mutable": false,
156
+ "description": "Visually separated option groups that remain one radio group. Assign as a JavaScript property."
157
+ },
158
+ "value": {
159
+ "type": "string",
160
+ "resolvedType": "string",
161
+ "attribute": "value",
162
+ "default": "''",
163
+ "required": false,
164
+ "mutable": true,
165
+ "description": "Selected option value."
166
+ }
167
+ },
168
+ "events": [
169
+ {
170
+ "name": "dsChange",
171
+ "detail": "string",
172
+ "bubbles": true,
173
+ "cancelable": true,
174
+ "composed": true
175
+ }
176
+ ],
177
+ "methods": [],
178
+ "slots": [],
179
+ "exports": {
180
+ "customElement": "ds-swatch-picker",
181
+ "react": "DsSwatchPicker",
182
+ "angular": "DsSwatchPicker"
183
+ },
184
+ "consumption": {
185
+ "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
186
+ "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
187
+ "customElements": {
188
+ "import": "import '@ds-mo/ui/dist/components/ds-swatch-picker.js';",
189
+ "example": "<ds-swatch-picker></ds-swatch-picker>"
190
+ },
191
+ "react": {
192
+ "import": "import { DsSwatchPicker } from '@ds-mo/ui/react';",
193
+ "example": "<DsSwatchPicker />"
194
+ },
195
+ "angular": {
196
+ "import": "import { DsSwatchPicker } from '@ds-mo/ui/angular/ds-swatch-picker';",
197
+ "example": "<ds-swatch-picker></ds-swatch-picker>"
198
+ },
199
+ "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: options, sections.",
200
+ "peerDependencies": {
201
+ "required": [
202
+ "@ds-mo/tokens ^3.1.1",
203
+ "@ds-mo/icons ^6.2.0"
204
+ ],
205
+ "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
206
+ }
207
+ }
208
+ },
209
+ "dependencies": [
210
+ "@ds-mo/ui",
211
+ "@ds-mo/tokens"
212
+ ],
213
+ "registryDependencies": [],
214
+ "files": [
215
+ {
216
+ "path": "src/wc/components/SwatchPicker/SwatchPicker.css",
217
+ "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/control-inactive.css';\n\n:host {\n box-sizing: border-box;\n display: block;\n width: 100%;\n --_swatch-inner-inset: calc(\n var(--dimension-stroke-width-025) + var(--dimension-space-025)\n );\n --_swatch-ring-bg: var(--color-background-primary);\n --_swatch-select-motion: var(--effect-motion-short-3);\n}\n\n.swatch-picker {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n width: 100%;\n min-height: var(--dimension-size-400);\n gap: var(--dimension-space-100);\n padding: var(--dimension-space-100);\n}\n\n.swatch-picker__option {\n position: relative;\n box-sizing: border-box;\n flex: 0 0 auto;\n width: var(--dimension-size-300);\n height: var(--dimension-size-300);\n padding: 0;\n border: none;\n border-radius: var(--dimension-radius-half);\n background: transparent;\n cursor: pointer;\n isolation: isolate;\n}\n\n.swatch-picker__fill,\n.swatch-picker__border,\n.swatch-picker__interaction,\n.swatch-picker__halo,\n.swatch-picker__stroke {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n}\n\n.swatch-picker__fill {\n z-index: 1;\n background-color: var(--_swatch-preview-color);\n background-image: var(--_swatch-preview-image);\n background-repeat: no-repeat;\n background-position: center;\n background-size: cover;\n opacity: var(--_swatch-preview-opacity);\n transition:\n inset var(--_swatch-select-motion);\n}\n\n.swatch-picker__border {\n z-index: 2;\n box-shadow: inset 0 0 0 var(--dimension-stroke-width-012) var(--color-border-secondary);\n transition:\n inset var(--_swatch-select-motion);\n}\n\n.swatch-picker__interaction {\n z-index: 3;\n background-color: transparent;\n transition:\n inset var(--_swatch-select-motion),\n background-color 0s;\n}\n\n.swatch-picker__option:hover:not(:disabled) .swatch-picker__interaction {\n background-color: var(--color-interaction-hover);\n}\n\n.swatch-picker__option:active:not(:disabled) .swatch-picker__interaction {\n background-color: var(--color-interaction-pressed);\n}\n\n.swatch-picker__halo,\n.swatch-picker__stroke {\n opacity: 0;\n transform: scale(1.2);\n transform-origin: center;\n transition:\n opacity var(--_swatch-select-motion),\n transform var(--_swatch-select-motion);\n}\n\n.swatch-picker__halo {\n z-index: 4;\n box-shadow: inset 0 0 0 var(--_swatch-inner-inset) var(--_swatch-ring-bg);\n}\n\n.swatch-picker__stroke {\n z-index: 5;\n box-shadow: inset 0 0 0 var(--dimension-stroke-width-025) var(--color-border-bold-brand);\n}\n\n.swatch-picker__option--selected .swatch-picker__fill,\n.swatch-picker__option--selected .swatch-picker__border,\n.swatch-picker__option--selected .swatch-picker__interaction {\n inset: var(--_swatch-inner-inset);\n}\n\n.swatch-picker__option--selected .swatch-picker__halo,\n.swatch-picker__option--selected .swatch-picker__stroke {\n opacity: 1;\n transform: scale(1);\n}\n\n.swatch-picker__divider {\n flex: 0 0 auto;\n align-self: center;\n width: var(--dimension-stroke-width-012);\n height: var(--dimension-size-300);\n background-color: var(--color-border-tertiary);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .swatch-picker__fill,\n .swatch-picker__border,\n .swatch-picker__interaction,\n .swatch-picker__halo,\n .swatch-picker__stroke {\n transition: none;\n }\n}\n",
218
+ "type": "registry:ui"
219
+ },
220
+ {
221
+ "path": "src/wc/components/SwatchPicker/SwatchPicker.tsx",
222
+ "content": "import { Component, Element, Event, EventEmitter, Prop, h, Host } from '@stencil/core';\nimport {\n flattenSwatchPickerOptions,\n normalizeSwatchPickerOpacity,\n resolveSwatchPickerNavigationIndex,\n resolveSwatchPickerTabIndex,\n type SwatchPickerNavigationKey,\n type SwatchPickerOption,\n type SwatchPickerSection,\n} from './swatch-picker-types';\n\n@Component({\n tag: 'ds-swatch-picker',\n styleUrl: 'SwatchPicker.css',\n scoped: true,\n})\nexport class SwatchPicker {\n @Element() el!: HTMLElement;\n\n /** Selected option value. */\n @Prop({ mutable: true, reflect: true }) value: string = '';\n\n /** Flat option list. Sections take precedence when supplied. Assign as a JavaScript property. */\n @Prop() options: SwatchPickerOption[] = [];\n\n /** Visually separated option groups that remain one radio group. Assign as a JavaScript property. */\n @Prop() sections: SwatchPickerSection[] = [];\n\n /** Accessible name for the complete one-of-many choice. */\n @Prop() groupLabel: string = 'Swatch options';\n\n @Event() dsChange!: EventEmitter<string>;\n\n private get activeOptions(): SwatchPickerOption[] {\n return flattenSwatchPickerOptions(this.options, this.sections);\n }\n\n private selectOption(option: SwatchPickerOption) {\n if (option.isInactive || option.value === this.value) return;\n this.value = option.value;\n this.dsChange.emit(option.value);\n }\n\n private focusOption(index: number) {\n requestAnimationFrame(() => {\n const optionButtons = this.el\n .querySelectorAll<HTMLButtonElement>('.swatch-picker__option');\n optionButtons[index]?.focus();\n });\n }\n\n private handleOptionKeyDown(\n event: KeyboardEvent,\n currentIndex: number,\n ) {\n const key = event.key as SwatchPickerNavigationKey;\n if (!['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Home', 'End'].includes(key)) {\n return;\n }\n\n const nextIndex = resolveSwatchPickerNavigationIndex(\n this.activeOptions,\n currentIndex,\n key,\n );\n if (nextIndex === null) return;\n\n event.preventDefault();\n const nextOption = this.activeOptions[nextIndex];\n this.selectOption(nextOption);\n this.focusOption(nextIndex);\n }\n\n private renderOption(\n option: SwatchPickerOption,\n index: number,\n tabIndex: number,\n ) {\n const selected = option.value === this.value;\n const opacity = normalizeSwatchPickerOpacity(option.preview.opacity);\n\n return (\n <button\n key={option.value}\n type=\"button\"\n class={{\n 'swatch-picker__option': true,\n 'swatch-picker__option--selected': selected,\n 'ds-focus-ring-inset': true,\n 'ds-control-inactive': !!option.isInactive,\n }}\n style={{\n '--_swatch-preview-color': option.preview.backgroundColor ?? 'var(--color-background-secondary)',\n '--_swatch-preview-image': option.preview.backgroundImage ?? 'none',\n '--_swatch-preview-opacity': String(opacity),\n }}\n role=\"radio\"\n aria-label={option.label}\n aria-checked={selected ? 'true' : 'false'}\n tabIndex={index === tabIndex ? 0 : -1}\n disabled={option.isInactive}\n onClick={() => this.selectOption(option)}\n onKeyDown={(event: KeyboardEvent) => this.handleOptionKeyDown(event, index)}\n >\n <span class=\"swatch-picker__fill\" aria-hidden=\"true\" />\n <span class=\"swatch-picker__border\" aria-hidden=\"true\" />\n <span class=\"swatch-picker__interaction\" aria-hidden=\"true\" />\n <span class=\"swatch-picker__halo\" aria-hidden=\"true\" />\n <span class=\"swatch-picker__stroke\" aria-hidden=\"true\" />\n </button>\n );\n }\n\n render() {\n const activeOptions = this.activeOptions;\n const tabIndex = resolveSwatchPickerTabIndex(activeOptions, this.value);\n let optionIndex = 0;\n\n return (\n <Host>\n <div\n class=\"swatch-picker\"\n role=\"radiogroup\"\n aria-label={this.groupLabel}\n >\n {(this.sections.length > 0 ? this.sections : [{ options: this.options }]).map((section, sectionIndex) => [\n section.options.map(option => {\n const currentIndex = optionIndex++;\n return this.renderOption(option, currentIndex, tabIndex);\n }),\n sectionIndex < (this.sections.length > 0 ? this.sections.length : 1) - 1 && (\n <span class=\"swatch-picker__divider\" aria-hidden=\"true\" />\n ),\n ])}\n </div>\n </Host>\n );\n }\n}\n",
223
+ "type": "registry:ui"
224
+ },
225
+ {
226
+ "path": "src/wc/components/SwatchPicker/swatch-picker-types.ts",
227
+ "content": "export interface SwatchPickerPreview {\n /** CSS color used as the swatch base or as a complete flat-color preview. */\n backgroundColor?: string;\n /** CSS gradient used as the swatch image. */\n backgroundImage?: string;\n /** Optional preview-layer opacity, clamped to the inclusive 0–1 range. */\n opacity?: number;\n}\n\nexport interface SwatchPickerOption {\n /** Stable value emitted when this option is selected. */\n value: string;\n /** Accessible option name; the visual preview never replaces this label. */\n label: string;\n preview: SwatchPickerPreview;\n isInactive?: boolean;\n}\n\nexport interface SwatchPickerSection {\n /** Sections remain one radio group and are separated visually. */\n options: SwatchPickerOption[];\n}\n\nexport type SwatchPickerNavigationKey =\n | 'ArrowDown'\n | 'ArrowLeft'\n | 'ArrowRight'\n | 'ArrowUp'\n | 'Home'\n | 'End';\n\nexport function flattenSwatchPickerOptions(\n options: readonly SwatchPickerOption[],\n sections: readonly SwatchPickerSection[],\n): SwatchPickerOption[] {\n return sections.length > 0\n ? sections.flatMap(section => section.options)\n : [...options];\n}\n\nexport function resolveSwatchPickerTabIndex(\n options: readonly SwatchPickerOption[],\n value: string,\n): number {\n const selectedIndex = options.findIndex(option => (\n option.value === value && !option.isInactive\n ));\n if (selectedIndex >= 0) return selectedIndex;\n return options.findIndex(option => !option.isInactive);\n}\n\nexport function resolveSwatchPickerNavigationIndex(\n options: readonly SwatchPickerOption[],\n currentIndex: number,\n key: SwatchPickerNavigationKey,\n): number | null {\n const enabledIndexes = options\n .map((option, index) => ({ option, index }))\n .filter(({ option }) => !option.isInactive)\n .map(({ index }) => index);\n\n if (enabledIndexes.length === 0) return null;\n if (key === 'Home') return enabledIndexes[0];\n if (key === 'End') return enabledIndexes[enabledIndexes.length - 1];\n\n const direction = key === 'ArrowRight' || key === 'ArrowDown' ? 1 : -1;\n const currentEnabledIndex = enabledIndexes.indexOf(currentIndex);\n if (currentEnabledIndex < 0) {\n return direction > 0\n ? enabledIndexes[0]\n : enabledIndexes[enabledIndexes.length - 1];\n }\n\n return enabledIndexes[\n (currentEnabledIndex + direction + enabledIndexes.length) % enabledIndexes.length\n ];\n}\n\nexport function normalizeSwatchPickerOpacity(opacity: number | undefined): number {\n if (opacity === undefined || !Number.isFinite(opacity)) return 1;\n return Math.min(1, Math.max(0, opacity));\n}\n",
228
+ "type": "registry:ui"
229
+ }
230
+ ]
231
+ }
@@ -0,0 +1,361 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "switch",
4
+ "title": "Switch",
5
+ "description": "Compact on/off setting control for changes that take effect immediately, with three sizes and form-associated behavior.",
6
+ "type": "registry:ui",
7
+ "meta": {
8
+ "library": "@ds-mo/ui",
9
+ "distribution": "npm",
10
+ "storybook": "https://zainadeel.github.io/compomo/?path=/story/switch",
11
+ "source": "src/wc/components/Switch/Switch.tsx",
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "general",
15
+ "status": "stable",
16
+ "summary": "Compact on/off setting control for changes that take effect immediately, with three sizes and form-associated behavior.",
17
+ "useWhen": [
18
+ "A binary setting takes effect immediately when the user turns it on or off.",
19
+ "A settings, form, or menu row needs a compact on/off control.",
20
+ "The checked value needs native form submission, required validation, reset, or external form association."
21
+ ],
22
+ "avoidWhen": [
23
+ "The user is providing consent, acknowledging a statement, or selecting multiple independent choices.",
24
+ "The user must choose one option from a mutually exclusive set.",
25
+ "The interaction triggers a one-time action instead of maintaining an on/off state."
26
+ ],
27
+ "alternatives": [
28
+ {
29
+ "when": "The interaction represents consent, acknowledgment, or multi-select choices.",
30
+ "component": "component:ds-checkbox",
31
+ "reason": "Checkbox semantics communicate selection or agreement rather than an immediately applied setting."
32
+ },
33
+ {
34
+ "when": "The user must choose one option from a mutually exclusive group.",
35
+ "component": "component:ds-radio",
36
+ "reason": "Radio communicates one-of-many selection."
37
+ },
38
+ {
39
+ "when": "The control triggers a one-time command or submission.",
40
+ "component": "component:ds-button-filled",
41
+ "reason": "Button semantics communicate an action rather than persistent state."
42
+ }
43
+ ],
44
+ "commonlyComposedWith": [
45
+ "component:ds-field",
46
+ "component:ds-menu",
47
+ "component:ds-text"
48
+ ],
49
+ "accessibility": [
50
+ "Every standalone switch requires an accessible name from a native label, aria-label, or aria-labelledby.",
51
+ "Checked state is exposed through switch semantics and thumb position, so state does not rely on color alone.",
52
+ "Read-only switches remain focusable and submitted but cannot change; inactive or disabled switches leave the tab order and are omitted from form submission.",
53
+ "Required and invalid state are exposed explicitly on the switch role; focused, filled, dirty, touched, valid, and invalid data hooks support form diagnostics without imposing visual validation timing.",
54
+ "Use presentation mode only when a composite owner such as a menuitemcheckbox provides the interaction and accessible state."
55
+ ],
56
+ "states": [
57
+ "Activation updates checked immediately and emits dsChange with the new boolean value.",
58
+ "The initial checked value is the form-reset baseline; unchecked switches submit nothing unless uncheckedValue is provided.",
59
+ "Sizes are md 32×20 with a 12px thumb and 4px body inset, sm 24×16 with a 10px thumb and 3px inset, and xs 20×12 with an 8px thumb and 2px inset.",
60
+ "Unchecked uses a transparent track with a density-specific tertiary-foreground inset stroke and a solid tertiary-foreground thumb without a border. Checked uses a brand track and primary-background thumb with no strokes.",
61
+ "Presentation mode removes switch semantics, focus, interaction, and form submission while retaining the checked visual."
62
+ ],
63
+ "responsiveBehavior": [
64
+ "Choose size from the density of the containing row; do not enlarge the switch to the full control height.",
65
+ "Keep the switch from shrinking when adjacent labels wrap.",
66
+ "Thumb movement is left-to-right; the design system does not author RTL-specific component behavior."
67
+ ],
68
+ "frameworkCaveats": {
69
+ "customElements": [
70
+ "Set checked as a property for application state updates and listen for dsChange.",
71
+ "Use the form attribute to associate a switch rendered outside its owning form."
72
+ ],
73
+ "react": [
74
+ "Use DsSwitch and handle onDsChange; checked is mutable and updates immediately when the component is activated."
75
+ ],
76
+ "angular": [
77
+ "Use DsSwitch with checked binding and dsChange, or the generated boolean value accessor in Angular forms."
78
+ ]
79
+ },
80
+ "references": [
81
+ {
82
+ "label": "Storybook examples",
83
+ "path": "src/wc/components/Switch/Switch.stories.ts"
84
+ },
85
+ {
86
+ "label": "Cross-browser form and accessibility coverage",
87
+ "path": "tests/e2e/forms.spec.ts"
88
+ }
89
+ ]
90
+ },
91
+ "api": {
92
+ "props": {
93
+ "checked": {
94
+ "type": "boolean",
95
+ "resolvedType": "boolean",
96
+ "attribute": "checked",
97
+ "default": "false",
98
+ "required": false,
99
+ "mutable": true,
100
+ "description": "Current on/off state. The initial value is restored when the owning form resets."
101
+ },
102
+ "disabled": {
103
+ "type": "boolean",
104
+ "resolvedType": "boolean",
105
+ "attribute": "disabled",
106
+ "default": "false",
107
+ "required": false,
108
+ "mutable": false
109
+ },
110
+ "form": {
111
+ "type": "string | undefined",
112
+ "resolvedType": "string | undefined",
113
+ "attribute": "form",
114
+ "required": false,
115
+ "mutable": false,
116
+ "description": "Associates the switch with a form by id when it is rendered outside that form."
117
+ },
118
+ "isInactive": {
119
+ "type": "boolean",
120
+ "resolvedType": "boolean",
121
+ "attribute": "is-inactive",
122
+ "default": "false",
123
+ "required": false,
124
+ "mutable": false
125
+ },
126
+ "name": {
127
+ "type": "string | undefined",
128
+ "resolvedType": "string | undefined",
129
+ "attribute": "name",
130
+ "required": false,
131
+ "mutable": false
132
+ },
133
+ "presentation": {
134
+ "type": "boolean",
135
+ "resolvedType": "boolean",
136
+ "attribute": "presentation",
137
+ "default": "false",
138
+ "required": false,
139
+ "mutable": false,
140
+ "description": "Removes standalone control semantics so a composite owner such as a\n`menuitemcheckbox` can use the switch as an aria-hidden visual indicator."
141
+ },
142
+ "readOnly": {
143
+ "type": "boolean",
144
+ "resolvedType": "boolean",
145
+ "attribute": "read-only",
146
+ "default": "false",
147
+ "required": false,
148
+ "mutable": false
149
+ },
150
+ "required": {
151
+ "type": "boolean",
152
+ "resolvedType": "boolean",
153
+ "attribute": "required",
154
+ "default": "false",
155
+ "required": false,
156
+ "mutable": false
157
+ },
158
+ "requiredMessage": {
159
+ "type": "string",
160
+ "resolvedType": "string",
161
+ "attribute": "required-message",
162
+ "default": "'This field is required.'",
163
+ "required": false,
164
+ "mutable": false
165
+ },
166
+ "size": {
167
+ "type": "SwitchSize",
168
+ "resolvedType": "\"md\" | \"sm\" | \"xs\"",
169
+ "attribute": "size",
170
+ "default": "'md'",
171
+ "required": false,
172
+ "mutable": false,
173
+ "description": "Compact track size for placement inside controls, menus, and form rows."
174
+ },
175
+ "uncheckedValue": {
176
+ "type": "string | undefined",
177
+ "resolvedType": "string | undefined",
178
+ "attribute": "unchecked-value",
179
+ "required": false,
180
+ "mutable": false,
181
+ "description": "Optional value submitted when unchecked; omitted by default like a native checkbox."
182
+ },
183
+ "value": {
184
+ "type": "string",
185
+ "resolvedType": "string",
186
+ "attribute": "value",
187
+ "default": "'on'",
188
+ "required": false,
189
+ "mutable": false
190
+ }
191
+ },
192
+ "events": [
193
+ {
194
+ "name": "dsChange",
195
+ "detail": "boolean",
196
+ "bubbles": true,
197
+ "cancelable": true,
198
+ "composed": true
199
+ }
200
+ ],
201
+ "methods": [],
202
+ "slots": []
203
+ },
204
+ "props": {
205
+ "checked": {
206
+ "type": "boolean",
207
+ "resolvedType": "boolean",
208
+ "attribute": "checked",
209
+ "default": "false",
210
+ "required": false,
211
+ "mutable": true,
212
+ "description": "Current on/off state. The initial value is restored when the owning form resets."
213
+ },
214
+ "disabled": {
215
+ "type": "boolean",
216
+ "resolvedType": "boolean",
217
+ "attribute": "disabled",
218
+ "default": "false",
219
+ "required": false,
220
+ "mutable": false
221
+ },
222
+ "form": {
223
+ "type": "string | undefined",
224
+ "resolvedType": "string | undefined",
225
+ "attribute": "form",
226
+ "required": false,
227
+ "mutable": false,
228
+ "description": "Associates the switch with a form by id when it is rendered outside that form."
229
+ },
230
+ "isInactive": {
231
+ "type": "boolean",
232
+ "resolvedType": "boolean",
233
+ "attribute": "is-inactive",
234
+ "default": "false",
235
+ "required": false,
236
+ "mutable": false
237
+ },
238
+ "name": {
239
+ "type": "string | undefined",
240
+ "resolvedType": "string | undefined",
241
+ "attribute": "name",
242
+ "required": false,
243
+ "mutable": false
244
+ },
245
+ "presentation": {
246
+ "type": "boolean",
247
+ "resolvedType": "boolean",
248
+ "attribute": "presentation",
249
+ "default": "false",
250
+ "required": false,
251
+ "mutable": false,
252
+ "description": "Removes standalone control semantics so a composite owner such as a\n`menuitemcheckbox` can use the switch as an aria-hidden visual indicator."
253
+ },
254
+ "readOnly": {
255
+ "type": "boolean",
256
+ "resolvedType": "boolean",
257
+ "attribute": "read-only",
258
+ "default": "false",
259
+ "required": false,
260
+ "mutable": false
261
+ },
262
+ "required": {
263
+ "type": "boolean",
264
+ "resolvedType": "boolean",
265
+ "attribute": "required",
266
+ "default": "false",
267
+ "required": false,
268
+ "mutable": false
269
+ },
270
+ "requiredMessage": {
271
+ "type": "string",
272
+ "resolvedType": "string",
273
+ "attribute": "required-message",
274
+ "default": "'This field is required.'",
275
+ "required": false,
276
+ "mutable": false
277
+ },
278
+ "size": {
279
+ "type": "SwitchSize",
280
+ "resolvedType": "\"md\" | \"sm\" | \"xs\"",
281
+ "attribute": "size",
282
+ "default": "'md'",
283
+ "required": false,
284
+ "mutable": false,
285
+ "description": "Compact track size for placement inside controls, menus, and form rows."
286
+ },
287
+ "uncheckedValue": {
288
+ "type": "string | undefined",
289
+ "resolvedType": "string | undefined",
290
+ "attribute": "unchecked-value",
291
+ "required": false,
292
+ "mutable": false,
293
+ "description": "Optional value submitted when unchecked; omitted by default like a native checkbox."
294
+ },
295
+ "value": {
296
+ "type": "string",
297
+ "resolvedType": "string",
298
+ "attribute": "value",
299
+ "default": "'on'",
300
+ "required": false,
301
+ "mutable": false
302
+ }
303
+ },
304
+ "events": [
305
+ {
306
+ "name": "dsChange",
307
+ "detail": "boolean",
308
+ "bubbles": true,
309
+ "cancelable": true,
310
+ "composed": true
311
+ }
312
+ ],
313
+ "methods": [],
314
+ "slots": [],
315
+ "exports": {
316
+ "customElement": "ds-switch",
317
+ "react": "DsSwitch",
318
+ "angular": "DsSwitch"
319
+ },
320
+ "consumption": {
321
+ "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
322
+ "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
323
+ "customElements": {
324
+ "import": "import '@ds-mo/ui/dist/components/ds-switch.js';",
325
+ "example": "<ds-switch></ds-switch>"
326
+ },
327
+ "react": {
328
+ "import": "import { DsSwitch } from '@ds-mo/ui/react';",
329
+ "example": "<DsSwitch />"
330
+ },
331
+ "angular": {
332
+ "import": "import { DsSwitch } from '@ds-mo/ui/angular/ds-switch';",
333
+ "example": "<ds-switch></ds-switch>"
334
+ },
335
+ "peerDependencies": {
336
+ "required": [
337
+ "@ds-mo/tokens ^3.1.1",
338
+ "@ds-mo/icons ^6.2.0"
339
+ ],
340
+ "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
341
+ }
342
+ }
343
+ },
344
+ "dependencies": [
345
+ "@ds-mo/ui",
346
+ "@ds-mo/tokens"
347
+ ],
348
+ "registryDependencies": [],
349
+ "files": [
350
+ {
351
+ "path": "src/wc/components/Switch/Switch.css",
352
+ "content": "@import '../../utils/focus-ring.css';\n@import '../../utils/control-inactive.css';\n\n:host {\n --ds-switch-width: var(--dimension-size-400);\n --ds-switch-height: var(--dimension-size-250);\n --ds-switch-thumb-size: var(--dimension-size-150);\n --ds-switch-gap: var(--dimension-space-050);\n --ds-switch-stroke: var(--dimension-stroke-width-015);\n --ds-switch-inset: var(--ds-switch-gap);\n --ds-switch-thumb-hover: var(--color-interaction-hover);\n --ds-switch-thumb-pressed: var(--color-interaction-pressed);\n --ds-switch-travel: calc(\n var(--ds-switch-width) - var(--ds-switch-thumb-size) - var(--ds-switch-inset) -\n var(--ds-switch-inset)\n );\n position: relative;\n display: inline-flex;\n width: var(--ds-switch-width);\n height: var(--ds-switch-height);\n padding: 0;\n border: none;\n border-radius: var(--dimension-radius-half);\n box-sizing: border-box;\n flex-shrink: 0;\n overflow: visible;\n cursor: pointer;\n user-select: none;\n outline: none;\n background-color: transparent;\n box-shadow: inset 0 0 0 var(--ds-switch-stroke) var(--color-foreground-tertiary);\n transition:\n background-color var(--effect-motion-short-3),\n box-shadow var(--effect-motion-short-3);\n}\n\n:host(.switch--sm) {\n --ds-switch-width: var(--dimension-size-300);\n --ds-switch-height: var(--dimension-size-200);\n --ds-switch-thumb-size: calc(var(--dimension-size-base) * 5 / 4);\n --ds-switch-gap: calc(var(--dimension-space-025) + var(--dimension-space-012));\n --ds-switch-stroke: var(--dimension-stroke-width-012);\n}\n\n:host(.switch--xs) {\n --ds-switch-width: var(--dimension-size-250);\n --ds-switch-height: var(--dimension-size-150);\n --ds-switch-thumb-size: var(--dimension-size-100);\n --ds-switch-gap: var(--dimension-space-025);\n --ds-switch-stroke: calc(var(--dimension-stroke-width-base) * 3 / 32);\n}\n\n:host(.checked) {\n background-color: var(--color-background-bold-brand);\n box-shadow: none;\n}\n\n:host(.switch--readonly),\n:host(.switch--presentation) {\n cursor: default;\n}\n\n:host(.switch--presentation) {\n pointer-events: none;\n}\n\n/* Inactive: `.ds-control-inactive` (25% opacity). */\n\n.thumb {\n position: absolute;\n top: 50%;\n left: var(--ds-switch-inset);\n width: var(--ds-switch-thumb-size);\n height: var(--ds-switch-thumb-size);\n border-radius: var(--dimension-radius-half);\n background-color: var(--color-foreground-tertiary);\n box-shadow: none;\n transform: translateY(-50%);\n transition:\n background-color var(--effect-motion-short-3),\n transform var(--effect-motion-short-3);\n pointer-events: none;\n}\n\n.thumb::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: transparent;\n pointer-events: none;\n}\n\n:host(:hover:not(:disabled):not(.ds-control-inactive):not(.switch--readonly):not(.switch--presentation))\n .thumb::after {\n background: var(--ds-switch-thumb-hover);\n}\n\n:host(:active:not(:disabled):not(.ds-control-inactive):not(.switch--readonly):not(.switch--presentation))\n .thumb::after {\n background: var(--ds-switch-thumb-pressed);\n}\n\n:host(.checked) .thumb {\n background-color: var(--color-background-primary);\n box-shadow: none;\n transform: translate3d(var(--ds-switch-travel), -50%, 0);\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host {\n transition: none;\n }\n\n .thumb {\n transition: none;\n }\n}\n",
353
+ "type": "registry:ui"
354
+ },
355
+ {
356
+ "path": "src/wc/components/Switch/Switch.tsx",
357
+ "content": "import { AttachInternals, Component, Element, Prop, State, Event, EventEmitter, Watch, h, Host } from '@stencil/core';\n\nexport type SwitchSize = 'md' | 'sm' | 'xs';\n\nlet generatedLabelId = 0;\n\n@Component({\n tag: 'ds-switch',\n styleUrl: 'Switch.css',\n shadow: true,\n formAssociated: true,\n})\nexport class Switch {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Current on/off state. The initial value is restored when the owning form resets. */\n @Prop({ mutable: true }) checked: boolean = false;\n\n @Prop({ reflect: true }) name: string | undefined;\n @Prop() value: string = 'on';\n\n /** Optional value submitted when unchecked; omitted by default like a native checkbox. */\n @Prop() uncheckedValue: string | undefined;\n\n /** Associates the switch with a form by id when it is rendered outside that form. */\n @Prop({ reflect: true }) form: string | undefined;\n\n @Prop({ reflect: true }) disabled: boolean = false;\n @Prop({ reflect: true }) readOnly: boolean = false;\n @Prop({ reflect: true }) required: boolean = false;\n @Prop() requiredMessage: string = 'This field is required.';\n @Prop() isInactive: boolean = false;\n\n /** Compact track size for placement inside controls, menus, and form rows. */\n @Prop() size: SwitchSize = 'md';\n\n /**\n * Removes standalone control semantics so a composite owner such as a\n * `menuitemcheckbox` can use the switch as an aria-hidden visual indicator.\n */\n @Prop() presentation: boolean = false;\n\n @Event() dsChange!: EventEmitter<boolean>;\n\n private initialChecked = false;\n @State() private formDisabled = false;\n @State() private focused = false;\n @State() private touched = false;\n\n componentWillLoad() {\n this.initialChecked = this.checked;\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.syncNativeLabels();\n }\n\n @Watch('checked')\n @Watch('value')\n @Watch('uncheckedValue')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('presentation')\n @Watch('required')\n syncFormValue() {\n const inactive = this.presentation || this.isInactive || this.disabled || this.formDisabled;\n const submissionValue = this.checked ? this.value : this.uncheckedValue ?? null;\n const state = this.checked ? 'checked' : 'unchecked';\n this.internals.setFormValue(inactive ? null : submissionValue, state);\n\n const missing = this.required && !inactive && !this.checked;\n this.internals.setValidity(missing ? { valueMissing: true } : {}, missing ? this.requiredMessage : '');\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.checked = this.initialChecked;\n this.focused = false;\n this.touched = false;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n this.checked = state === 'checked';\n }\n\n private syncNativeLabels() {\n if (this.presentation) return;\n if (this.el.hasAttribute('aria-label') || this.el.hasAttribute('aria-labelledby')) return;\n\n const labelIds = (Array.from(this.internals.labels) as HTMLLabelElement[]).map(label => {\n if (!label.id) label.id = `ds-switch-label-${++generatedLabelId}`;\n return label.id;\n });\n if (labelIds.length) this.el.setAttribute('aria-labelledby', labelIds.join(' '));\n }\n\n private handleClick = () => {\n if (this.presentation || this.readOnly || this.isInactive || this.disabled || this.formDisabled) return;\n this.checked = !this.checked;\n this.dsChange.emit(this.checked);\n };\n\n private handleKeyDown = (event: KeyboardEvent) => {\n if (this.presentation || this.readOnly || this.isInactive || this.disabled || this.formDisabled) return;\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault();\n this.handleClick();\n }\n };\n\n private handleFocus = () => {\n if (!this.presentation) this.focused = true;\n };\n\n private handleBlur = () => {\n if (this.presentation) return;\n this.focused = false;\n this.touched = true;\n };\n\n render() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const invalid = this.required && !inactive && !this.checked;\n const dirty = this.checked !== this.initialChecked;\n // Switch owns its structural track and thumb strokes so nested interaction layers cannot cover them.\n return (\n <Host\n role={this.presentation ? undefined : 'switch'}\n aria-checked={this.presentation ? undefined : String(this.checked)}\n aria-disabled={!this.presentation && inactive ? 'true' : undefined}\n aria-readonly={!this.presentation && this.readOnly ? 'true' : undefined}\n aria-required={!this.presentation && this.required ? 'true' : undefined}\n aria-invalid={!this.presentation && invalid ? 'true' : undefined}\n aria-hidden={this.presentation ? 'true' : undefined}\n data-checked={this.checked ? '' : undefined}\n data-unchecked={!this.checked ? '' : undefined}\n data-disabled={inactive ? '' : undefined}\n data-readonly={this.readOnly ? '' : undefined}\n data-required={this.required ? '' : undefined}\n data-focused={!this.presentation && this.focused ? '' : undefined}\n data-filled={!this.presentation && this.checked ? '' : undefined}\n data-dirty={!this.presentation && dirty ? '' : undefined}\n data-touched={!this.presentation && this.touched ? '' : undefined}\n data-valid={!this.presentation && !inactive && !invalid ? '' : undefined}\n data-invalid={!this.presentation && invalid ? '' : undefined}\n tabIndex={this.presentation || inactive ? -1 : 0}\n class={{\n switch: true,\n checked: this.checked,\n 'switch--md': this.size === 'md',\n 'switch--sm': this.size === 'sm',\n 'switch--xs': this.size === 'xs',\n 'switch--readonly': this.readOnly,\n 'switch--presentation': this.presentation,\n 'ds-focus-ring': true,\n 'ds-control-inactive': inactive,\n }}\n onClick={this.handleClick}\n onKeyDown={this.handleKeyDown}\n onFocus={this.handleFocus}\n onBlur={this.handleBlur}\n >\n <span class=\"thumb\" />\n </Host>\n );\n }\n}\n",
358
+ "type": "registry:ui"
359
+ }
360
+ ]
361
+ }