@ds-mo/ui 9.0.0 → 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 (122) hide show
  1. package/README.md +28 -0
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +233 -0
  4. package/dist/agent.json +155 -19
  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-swatch-picker.js +42 -0
  9. package/dist/angular/index.d.ts +1 -1
  10. package/dist/angular/index.js +1 -0
  11. package/dist/angular/proxies.d.ts +1 -0
  12. package/dist/angular/proxies.js +1 -0
  13. package/dist/components/ds-app-shell.js +1 -1
  14. package/dist/components/ds-bar-nav.js +1 -1
  15. package/dist/components/ds-bar-nav.js.map +1 -1
  16. package/dist/components/ds-menu.js +1 -1
  17. package/dist/components/ds-panel-nav.js +1 -1
  18. package/dist/components/ds-panel-nav.js.map +1 -1
  19. package/dist/components/ds-select-multi.js +1 -1
  20. package/dist/components/ds-select-multi.js.map +1 -1
  21. package/dist/components/ds-select.js +1 -1
  22. package/dist/components/ds-select.js.map +1 -1
  23. package/dist/components/ds-shell-gradient-picker.js +1 -1
  24. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  25. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  26. package/dist/components/ds-shell-gradient-swatch.js.map +1 -1
  27. package/dist/components/ds-slider.js +1 -1
  28. package/dist/components/ds-slider.js.map +1 -1
  29. package/dist/components/ds-swatch-picker.d.ts +11 -0
  30. package/dist/components/ds-swatch-picker.js +2 -0
  31. package/dist/components/ds-swatch-picker.js.map +1 -0
  32. package/dist/components/ds-tooltip-data-viz.js +1 -1
  33. package/dist/components/p-D1HFMTdC.js +2 -0
  34. package/dist/components/p-D1HFMTdC.js.map +1 -0
  35. package/dist/components/p-HBIxvS5C.js +2 -0
  36. package/dist/components/p-HBIxvS5C.js.map +1 -0
  37. package/dist/components/p-l6PmnW_P.js +2 -0
  38. package/dist/components/p-l6PmnW_P.js.map +1 -0
  39. package/dist/components/p-tQ1QHeOE.js +2 -0
  40. package/dist/components/p-tQ1QHeOE.js.map +1 -0
  41. package/dist/docs/components.json +268 -30
  42. package/dist/lib/components/Menu/menu-placement.d.ts +6 -3
  43. package/dist/lib/shell/index.d.ts +2 -2
  44. package/dist/lib/shell/index.js +40 -39
  45. package/dist/lib/shell/index.js.map +3 -3
  46. package/dist/lib/shell/shell-gradient-presets.d.ts +15 -0
  47. package/dist/lib/utils/choice-popup-alignment.d.ts +20 -0
  48. package/dist/lib/utils/index.d.ts +2 -0
  49. package/dist/lib/utils/index.js +16 -0
  50. package/dist/lib/utils/index.js.map +3 -3
  51. package/dist/mcp/cli.js +31409 -0
  52. package/dist/mcp-data/registry/app-shell.json +210 -0
  53. package/dist/mcp-data/registry/badge.json +289 -0
  54. package/dist/mcp-data/registry/banner.json +206 -0
  55. package/dist/mcp-data/registry/bar-nav.json +341 -0
  56. package/dist/mcp-data/registry/button-filled.json +386 -0
  57. package/dist/mcp-data/registry/button-unfilled.json +537 -0
  58. package/dist/mcp-data/registry/card-data-viz-donut.json +143 -0
  59. package/dist/mcp-data/registry/card-setting.json +194 -0
  60. package/dist/mcp-data/registry/card-shell-data-viz.json +173 -0
  61. package/dist/mcp-data/registry/card.json +126 -0
  62. package/dist/mcp-data/registry/chart-bar.json +121 -0
  63. package/dist/mcp-data/registry/chart-donut.json +257 -0
  64. package/dist/mcp-data/registry/chart-legend.json +209 -0
  65. package/dist/mcp-data/registry/chart-line.json +153 -0
  66. package/dist/mcp-data/registry/checkbox.json +361 -0
  67. package/dist/mcp-data/registry/chip.json +276 -0
  68. package/dist/mcp-data/registry/divider.json +225 -0
  69. package/dist/mcp-data/registry/empty-state.json +197 -0
  70. package/dist/mcp-data/registry/field.json +222 -0
  71. package/dist/mcp-data/registry/icon.json +242 -0
  72. package/dist/mcp-data/registry/input.json +597 -0
  73. package/dist/mcp-data/registry/loader.json +192 -0
  74. package/dist/mcp-data/registry/menu.json +459 -0
  75. package/dist/mcp-data/registry/modal.json +150 -0
  76. package/dist/mcp-data/registry/panel-nav.json +622 -0
  77. package/dist/mcp-data/registry/panel-sub-nav.json +249 -0
  78. package/dist/mcp-data/registry/panel-tools.json +374 -0
  79. package/dist/mcp-data/registry/radio.json +374 -0
  80. package/dist/mcp-data/registry/registry.json +5389 -0
  81. package/dist/mcp-data/registry/select-multi.json +755 -0
  82. package/dist/mcp-data/registry/select.json +740 -0
  83. package/dist/mcp-data/registry/shell-gradient-picker.json +195 -0
  84. package/dist/mcp-data/registry/shell-gradient-swatch.json +215 -0
  85. package/dist/mcp-data/registry/skeleton.json +282 -0
  86. package/dist/mcp-data/registry/slider.json +657 -0
  87. package/dist/mcp-data/registry/swatch-picker.json +231 -0
  88. package/dist/mcp-data/registry/switch.json +361 -0
  89. package/dist/mcp-data/registry/tab-group.json +255 -0
  90. package/dist/mcp-data/registry/tag.json +352 -0
  91. package/dist/mcp-data/registry/text.json +371 -0
  92. package/dist/mcp-data/registry/tooltip-data-viz.json +158 -0
  93. package/dist/mcp-data/registry/tooltip.json +311 -0
  94. package/dist/react/components.d.ts +1 -0
  95. package/dist/react/components.js +1 -0
  96. package/dist/react/ds-menu.d.ts +1 -0
  97. package/dist/react/ds-menu.js +2 -1
  98. package/dist/react/ds-swatch-picker.d.ts +12 -0
  99. package/dist/react/ds-swatch-picker.js +12 -0
  100. package/dist/types/components/Menu/Menu.d.ts +7 -1
  101. package/dist/types/components/Menu/menu-placement.d.ts +6 -3
  102. package/dist/types/components/Menu/menu-types.d.ts +14 -1
  103. package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +2 -5
  104. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -0
  105. package/dist/types/components/SwatchPicker/SwatchPicker.d.ts +21 -0
  106. package/dist/types/components/SwatchPicker/swatch-picker-types.d.ts +26 -0
  107. package/dist/types/components.d.ts +117 -0
  108. package/dist/types/shell/index.d.ts +2 -2
  109. package/dist/types/shell/shell-gradient-presets.d.ts +15 -0
  110. package/dist/types/utils/choice-popup-alignment.d.ts +21 -0
  111. package/dist/types/utils/index.d.ts +2 -0
  112. package/package.json +6 -2
  113. package/dist/components/p-Bafp65bw.js +0 -2
  114. package/dist/components/p-Bafp65bw.js.map +0 -1
  115. package/dist/components/p-BdnR3jxi.js +0 -2
  116. package/dist/components/p-BdnR3jxi.js.map +0 -1
  117. package/dist/components/p-C_cu-HIT.js +0 -2
  118. package/dist/components/p-C_cu-HIT.js.map +0 -1
  119. package/dist/components/p-D0B9uDTP.js +0 -2
  120. package/dist/components/p-D0B9uDTP.js.map +0 -1
  121. package/dist/components/p-DF0yLZG_.js +0 -2
  122. package/dist/components/p-DF0yLZG_.js.map +0 -1
@@ -0,0 +1,657 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "slider",
4
+ "title": "Slider",
5
+ "description": "Form-associated single or two-thumb range input with native slider semantics, pointer dragging, keyboard operation, formatting, density, and orientation controls.",
6
+ "type": "registry:ui",
7
+ "meta": {
8
+ "library": "@ds-mo/ui",
9
+ "distribution": "npm",
10
+ "storybook": "https://zainadeel.github.io/compomo/?path=/story/slider",
11
+ "source": "src/wc/components/Slider/Slider.tsx",
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "general",
15
+ "status": "stable",
16
+ "summary": "Form-associated single or two-thumb range input with native slider semantics, pointer dragging, keyboard operation, formatting, density, and orientation controls.",
17
+ "useWhen": [
18
+ "A person should choose or tune an approximate numeric value within known bounds.",
19
+ "A bounded minimum and maximum range needs two independently focusable thumbs.",
20
+ "A value benefits from direct manipulation, immediate preview, and native form submission."
21
+ ],
22
+ "avoidWhen": [
23
+ "The exact numeric value is more important than direct spatial adjustment.",
24
+ "The choices are categorical rather than ordered numeric values.",
25
+ "The value is progress, measurement, or read-only status rather than user input."
26
+ ],
27
+ "alternatives": [
28
+ {
29
+ "when": "People must enter or verify a precise numeric value.",
30
+ "component": "component:ds-input",
31
+ "reason": "A typed field supports exact entry and review more efficiently than spatial adjustment."
32
+ },
33
+ {
34
+ "when": "The available values are a short finite set with meaningful labels.",
35
+ "component": "component:ds-radio",
36
+ "reason": "Visible named choices communicate categorical meaning more clearly."
37
+ },
38
+ {
39
+ "when": "The setting is binary and applies immediately.",
40
+ "component": "component:ds-switch",
41
+ "reason": "Switch communicates an immediate on/off state without implying intermediate values."
42
+ }
43
+ ],
44
+ "commonlyComposedWith": [
45
+ "component:ds-text",
46
+ "component:ds-input"
47
+ ],
48
+ "accessibility": [
49
+ "Each thumb contains a native range input and must have an accessible name; a visible label names a single thumb, while startLabel and endLabel distinguish a two-thumb range.",
50
+ "Arrow keys adjust one step, Home and End reach the allowed bounds, and browser-supported Page Up and Page Down make larger changes.",
51
+ "Every range thumb remains in a constant tab order and exposes dynamic minimum and maximum values when the other thumb constrains it.",
52
+ "Use valueText for one authored description or valueTexts for per-thumb range descriptions when numeric values are not understandable on their own, such as named levels, dates, durations, or units.",
53
+ "Read-only sliders remain focusable and submittable; inactive or disabled sliders leave the tab order and form submission."
54
+ ],
55
+ "states": [
56
+ "value is mutable: user pointer, keyboard, or assistive-technology input updates it and emits dsChange continuously.",
57
+ "dsCommit emits once a pointer interaction completes or the native range input commits a keyboard or assistive-technology change.",
58
+ "Assign a two-number array through the JavaScript value property to create a range; values are ordered, clamped, stepped, and prevented from crossing.",
59
+ "minStepsBetweenValues reserves a minimum stepped distance between range thumbs.",
60
+ "The initial normalized value is the native form-reset baseline and range values submit as repeated entries under the same name.",
61
+ "Focused, filled, dirty, touched, dragging, valid, disabled, read-only, and orientation data hooks expose interaction state without changing the value contract."
62
+ ],
63
+ "responsiveBehavior": [
64
+ "The visible label uses the same primary emphasized text-body-small recipe as Field at every density; the value uses the same primary metric without emphasis.",
65
+ "Horizontal Slider fills the width supplied by its parent; the parent chooses an appropriate readable track length.",
66
+ "Vertical Slider owns a token-based default length that may be overridden with --ds-slider-vertical-length in layout-specific compositions.",
67
+ "Use md in ordinary forms and sm or xs only when the surrounding control density uses the same recipe.",
68
+ "Track thickness scales with density: 8px at md, 6px at sm, and 4px at xs. The unfilled track is transparent with a tertiary-foreground inset outline, the filled range is brand, and the square thumb uses a 2px radius with a 1.5px brand inset ring.",
69
+ "Horizontal value direction is left-to-right; the design system does not author RTL-specific component behavior."
70
+ ],
71
+ "frameworkCaveats": {
72
+ "customElements": [
73
+ "Assign range arrays and formatOptions through JavaScript properties, not string attributes.",
74
+ "Listen for dsChange during adjustment and dsCommit for expensive work that should run after the interaction settles."
75
+ ],
76
+ "react": [
77
+ "Use DsSlider with a number or two-number value array and handle onDsChange and onDsCommit custom events."
78
+ ],
79
+ "angular": [
80
+ "Use DsSlider with property binding for range arrays; the generated number value accessor is appropriate only for a single numeric value."
81
+ ]
82
+ },
83
+ "references": [
84
+ {
85
+ "label": "Storybook design review",
86
+ "path": "src/wc/components/Slider/Slider.stories.ts"
87
+ },
88
+ {
89
+ "label": "Cross-browser form and interaction coverage",
90
+ "path": "tests/e2e/forms.spec.ts"
91
+ }
92
+ ]
93
+ },
94
+ "api": {
95
+ "props": {
96
+ "ariaDescribedby": {
97
+ "type": "string | undefined",
98
+ "resolvedType": "string | undefined",
99
+ "attribute": "aria-describedby",
100
+ "required": false,
101
+ "mutable": false,
102
+ "description": "Id references for supporting guidance or error text."
103
+ },
104
+ "ariaLabel": {
105
+ "type": "string | null",
106
+ "resolvedType": "null | string",
107
+ "attribute": "aria-label",
108
+ "default": "null",
109
+ "required": false,
110
+ "mutable": false,
111
+ "description": "Direct accessible name for a single-thumb slider without a visible label."
112
+ },
113
+ "ariaLabelledby": {
114
+ "type": "string | undefined",
115
+ "resolvedType": "string | undefined",
116
+ "attribute": "aria-labelledby",
117
+ "required": false,
118
+ "mutable": false,
119
+ "description": "Id references for a visible single-thumb slider label."
120
+ },
121
+ "disabled": {
122
+ "type": "boolean",
123
+ "resolvedType": "boolean",
124
+ "attribute": "disabled",
125
+ "default": "false",
126
+ "required": false,
127
+ "mutable": false
128
+ },
129
+ "endLabel": {
130
+ "type": "string",
131
+ "resolvedType": "string",
132
+ "attribute": "end-label",
133
+ "default": "'Maximum value'",
134
+ "required": false,
135
+ "mutable": false,
136
+ "description": "Accessible label for the upper thumb in a range slider."
137
+ },
138
+ "form": {
139
+ "type": "string | undefined",
140
+ "resolvedType": "string | undefined",
141
+ "attribute": "form",
142
+ "required": false,
143
+ "mutable": false
144
+ },
145
+ "formatOptions": {
146
+ "type": "Intl.NumberFormatOptions | undefined",
147
+ "resolvedType": "NumberFormatOptions | undefined",
148
+ "required": false,
149
+ "mutable": false,
150
+ "description": "Intl.NumberFormat options. Assign objects through the JavaScript property."
151
+ },
152
+ "inputId": {
153
+ "type": "string | undefined",
154
+ "resolvedType": "string | undefined",
155
+ "attribute": "input-id",
156
+ "required": false,
157
+ "mutable": false,
158
+ "description": "Id applied to the first native range input."
159
+ },
160
+ "isInactive": {
161
+ "type": "boolean",
162
+ "resolvedType": "boolean",
163
+ "attribute": "is-inactive",
164
+ "default": "false",
165
+ "required": false,
166
+ "mutable": false
167
+ },
168
+ "label": {
169
+ "type": "string | undefined",
170
+ "resolvedType": "string | undefined",
171
+ "attribute": "label",
172
+ "required": false,
173
+ "mutable": false,
174
+ "description": "Visible field label. Supply aria-label when the label is intentionally hidden."
175
+ },
176
+ "locale": {
177
+ "type": "string | undefined",
178
+ "resolvedType": "string | undefined",
179
+ "attribute": "locale",
180
+ "required": false,
181
+ "mutable": false,
182
+ "description": "Locale used by Intl.NumberFormat for the visible value."
183
+ },
184
+ "max": {
185
+ "type": "number",
186
+ "resolvedType": "number",
187
+ "attribute": "max",
188
+ "default": "100",
189
+ "required": false,
190
+ "mutable": false
191
+ },
192
+ "min": {
193
+ "type": "number",
194
+ "resolvedType": "number",
195
+ "attribute": "min",
196
+ "default": "0",
197
+ "required": false,
198
+ "mutable": false
199
+ },
200
+ "minStepsBetweenValues": {
201
+ "type": "number",
202
+ "resolvedType": "number",
203
+ "attribute": "min-steps-between-values",
204
+ "default": "0",
205
+ "required": false,
206
+ "mutable": false,
207
+ "description": "Minimum number of steps kept between two range thumbs."
208
+ },
209
+ "name": {
210
+ "type": "string | undefined",
211
+ "resolvedType": "string | undefined",
212
+ "attribute": "name",
213
+ "required": false,
214
+ "mutable": false
215
+ },
216
+ "orientation": {
217
+ "type": "SliderOrientation",
218
+ "resolvedType": "\"horizontal\" | \"vertical\"",
219
+ "attribute": "orientation",
220
+ "default": "'horizontal'",
221
+ "required": false,
222
+ "mutable": false
223
+ },
224
+ "rangeSeparator": {
225
+ "type": "string",
226
+ "resolvedType": "string",
227
+ "attribute": "range-separator",
228
+ "default": "'–'",
229
+ "required": false,
230
+ "mutable": false
231
+ },
232
+ "readOnly": {
233
+ "type": "boolean",
234
+ "resolvedType": "boolean",
235
+ "attribute": "read-only",
236
+ "default": "false",
237
+ "required": false,
238
+ "mutable": false,
239
+ "description": "Keep the value focusable and submittable while preventing changes."
240
+ },
241
+ "showValue": {
242
+ "type": "boolean",
243
+ "resolvedType": "boolean",
244
+ "attribute": "show-value",
245
+ "default": "true",
246
+ "required": false,
247
+ "mutable": false,
248
+ "description": "Show the formatted current value beside the visible label."
249
+ },
250
+ "size": {
251
+ "type": "SliderSize",
252
+ "resolvedType": "\"md\" | \"sm\" | \"xs\"",
253
+ "attribute": "size",
254
+ "default": "'md'",
255
+ "required": false,
256
+ "mutable": false
257
+ },
258
+ "startLabel": {
259
+ "type": "string",
260
+ "resolvedType": "string",
261
+ "attribute": "start-label",
262
+ "default": "'Minimum value'",
263
+ "required": false,
264
+ "mutable": false,
265
+ "description": "Accessible label for the lower thumb in a range slider."
266
+ },
267
+ "step": {
268
+ "type": "number",
269
+ "resolvedType": "number",
270
+ "attribute": "step",
271
+ "default": "1",
272
+ "required": false,
273
+ "mutable": false
274
+ },
275
+ "thumbAlignment": {
276
+ "type": "SliderThumbAlignment",
277
+ "resolvedType": "\"center\" | \"edge\"",
278
+ "attribute": "thumb-alignment",
279
+ "default": "'edge'",
280
+ "required": false,
281
+ "mutable": false,
282
+ "description": "Keep the complete thumb inside the rail bounds, or center it on the endpoints."
283
+ },
284
+ "value": {
285
+ "type": "SliderValue",
286
+ "resolvedType": "number | number[]",
287
+ "attribute": "value",
288
+ "default": "0",
289
+ "required": false,
290
+ "mutable": true,
291
+ "description": "Current value. Assign a two-number array through the JavaScript property for a range slider."
292
+ },
293
+ "valuePrefix": {
294
+ "type": "string",
295
+ "resolvedType": "string",
296
+ "attribute": "value-prefix",
297
+ "default": "''",
298
+ "required": false,
299
+ "mutable": false
300
+ },
301
+ "valueSuffix": {
302
+ "type": "string",
303
+ "resolvedType": "string",
304
+ "attribute": "value-suffix",
305
+ "default": "''",
306
+ "required": false,
307
+ "mutable": false
308
+ },
309
+ "valueText": {
310
+ "type": "string | undefined",
311
+ "resolvedType": "string | undefined",
312
+ "attribute": "value-text",
313
+ "required": false,
314
+ "mutable": false,
315
+ "description": "Human-readable value text for a single slider or both range thumbs."
316
+ },
317
+ "valueTexts": {
318
+ "type": "string[]",
319
+ "resolvedType": "string[]",
320
+ "default": "[]",
321
+ "required": false,
322
+ "mutable": false,
323
+ "description": "Per-thumb human-readable value text for a range slider. Assign through the JavaScript property."
324
+ }
325
+ },
326
+ "events": [
327
+ {
328
+ "name": "dsChange",
329
+ "detail": "SliderValue",
330
+ "bubbles": true,
331
+ "cancelable": true,
332
+ "composed": true,
333
+ "description": "Emitted continuously while the value changes."
334
+ },
335
+ {
336
+ "name": "dsCommit",
337
+ "detail": "SliderValue",
338
+ "bubbles": true,
339
+ "cancelable": true,
340
+ "composed": true,
341
+ "description": "Emitted when a pointer or keyboard value change is committed."
342
+ }
343
+ ],
344
+ "methods": [
345
+ {
346
+ "name": "setFocus",
347
+ "signature": "setFocus(index?: number) => Promise<void>"
348
+ }
349
+ ],
350
+ "slots": []
351
+ },
352
+ "props": {
353
+ "ariaDescribedby": {
354
+ "type": "string | undefined",
355
+ "resolvedType": "string | undefined",
356
+ "attribute": "aria-describedby",
357
+ "required": false,
358
+ "mutable": false,
359
+ "description": "Id references for supporting guidance or error text."
360
+ },
361
+ "ariaLabel": {
362
+ "type": "string | null",
363
+ "resolvedType": "null | string",
364
+ "attribute": "aria-label",
365
+ "default": "null",
366
+ "required": false,
367
+ "mutable": false,
368
+ "description": "Direct accessible name for a single-thumb slider without a visible label."
369
+ },
370
+ "ariaLabelledby": {
371
+ "type": "string | undefined",
372
+ "resolvedType": "string | undefined",
373
+ "attribute": "aria-labelledby",
374
+ "required": false,
375
+ "mutable": false,
376
+ "description": "Id references for a visible single-thumb slider label."
377
+ },
378
+ "disabled": {
379
+ "type": "boolean",
380
+ "resolvedType": "boolean",
381
+ "attribute": "disabled",
382
+ "default": "false",
383
+ "required": false,
384
+ "mutable": false
385
+ },
386
+ "endLabel": {
387
+ "type": "string",
388
+ "resolvedType": "string",
389
+ "attribute": "end-label",
390
+ "default": "'Maximum value'",
391
+ "required": false,
392
+ "mutable": false,
393
+ "description": "Accessible label for the upper thumb in a range slider."
394
+ },
395
+ "form": {
396
+ "type": "string | undefined",
397
+ "resolvedType": "string | undefined",
398
+ "attribute": "form",
399
+ "required": false,
400
+ "mutable": false
401
+ },
402
+ "formatOptions": {
403
+ "type": "Intl.NumberFormatOptions | undefined",
404
+ "resolvedType": "NumberFormatOptions | undefined",
405
+ "required": false,
406
+ "mutable": false,
407
+ "description": "Intl.NumberFormat options. Assign objects through the JavaScript property."
408
+ },
409
+ "inputId": {
410
+ "type": "string | undefined",
411
+ "resolvedType": "string | undefined",
412
+ "attribute": "input-id",
413
+ "required": false,
414
+ "mutable": false,
415
+ "description": "Id applied to the first native range input."
416
+ },
417
+ "isInactive": {
418
+ "type": "boolean",
419
+ "resolvedType": "boolean",
420
+ "attribute": "is-inactive",
421
+ "default": "false",
422
+ "required": false,
423
+ "mutable": false
424
+ },
425
+ "label": {
426
+ "type": "string | undefined",
427
+ "resolvedType": "string | undefined",
428
+ "attribute": "label",
429
+ "required": false,
430
+ "mutable": false,
431
+ "description": "Visible field label. Supply aria-label when the label is intentionally hidden."
432
+ },
433
+ "locale": {
434
+ "type": "string | undefined",
435
+ "resolvedType": "string | undefined",
436
+ "attribute": "locale",
437
+ "required": false,
438
+ "mutable": false,
439
+ "description": "Locale used by Intl.NumberFormat for the visible value."
440
+ },
441
+ "max": {
442
+ "type": "number",
443
+ "resolvedType": "number",
444
+ "attribute": "max",
445
+ "default": "100",
446
+ "required": false,
447
+ "mutable": false
448
+ },
449
+ "min": {
450
+ "type": "number",
451
+ "resolvedType": "number",
452
+ "attribute": "min",
453
+ "default": "0",
454
+ "required": false,
455
+ "mutable": false
456
+ },
457
+ "minStepsBetweenValues": {
458
+ "type": "number",
459
+ "resolvedType": "number",
460
+ "attribute": "min-steps-between-values",
461
+ "default": "0",
462
+ "required": false,
463
+ "mutable": false,
464
+ "description": "Minimum number of steps kept between two range thumbs."
465
+ },
466
+ "name": {
467
+ "type": "string | undefined",
468
+ "resolvedType": "string | undefined",
469
+ "attribute": "name",
470
+ "required": false,
471
+ "mutable": false
472
+ },
473
+ "orientation": {
474
+ "type": "SliderOrientation",
475
+ "resolvedType": "\"horizontal\" | \"vertical\"",
476
+ "attribute": "orientation",
477
+ "default": "'horizontal'",
478
+ "required": false,
479
+ "mutable": false
480
+ },
481
+ "rangeSeparator": {
482
+ "type": "string",
483
+ "resolvedType": "string",
484
+ "attribute": "range-separator",
485
+ "default": "'–'",
486
+ "required": false,
487
+ "mutable": false
488
+ },
489
+ "readOnly": {
490
+ "type": "boolean",
491
+ "resolvedType": "boolean",
492
+ "attribute": "read-only",
493
+ "default": "false",
494
+ "required": false,
495
+ "mutable": false,
496
+ "description": "Keep the value focusable and submittable while preventing changes."
497
+ },
498
+ "showValue": {
499
+ "type": "boolean",
500
+ "resolvedType": "boolean",
501
+ "attribute": "show-value",
502
+ "default": "true",
503
+ "required": false,
504
+ "mutable": false,
505
+ "description": "Show the formatted current value beside the visible label."
506
+ },
507
+ "size": {
508
+ "type": "SliderSize",
509
+ "resolvedType": "\"md\" | \"sm\" | \"xs\"",
510
+ "attribute": "size",
511
+ "default": "'md'",
512
+ "required": false,
513
+ "mutable": false
514
+ },
515
+ "startLabel": {
516
+ "type": "string",
517
+ "resolvedType": "string",
518
+ "attribute": "start-label",
519
+ "default": "'Minimum value'",
520
+ "required": false,
521
+ "mutable": false,
522
+ "description": "Accessible label for the lower thumb in a range slider."
523
+ },
524
+ "step": {
525
+ "type": "number",
526
+ "resolvedType": "number",
527
+ "attribute": "step",
528
+ "default": "1",
529
+ "required": false,
530
+ "mutable": false
531
+ },
532
+ "thumbAlignment": {
533
+ "type": "SliderThumbAlignment",
534
+ "resolvedType": "\"center\" | \"edge\"",
535
+ "attribute": "thumb-alignment",
536
+ "default": "'edge'",
537
+ "required": false,
538
+ "mutable": false,
539
+ "description": "Keep the complete thumb inside the rail bounds, or center it on the endpoints."
540
+ },
541
+ "value": {
542
+ "type": "SliderValue",
543
+ "resolvedType": "number | number[]",
544
+ "attribute": "value",
545
+ "default": "0",
546
+ "required": false,
547
+ "mutable": true,
548
+ "description": "Current value. Assign a two-number array through the JavaScript property for a range slider."
549
+ },
550
+ "valuePrefix": {
551
+ "type": "string",
552
+ "resolvedType": "string",
553
+ "attribute": "value-prefix",
554
+ "default": "''",
555
+ "required": false,
556
+ "mutable": false
557
+ },
558
+ "valueSuffix": {
559
+ "type": "string",
560
+ "resolvedType": "string",
561
+ "attribute": "value-suffix",
562
+ "default": "''",
563
+ "required": false,
564
+ "mutable": false
565
+ },
566
+ "valueText": {
567
+ "type": "string | undefined",
568
+ "resolvedType": "string | undefined",
569
+ "attribute": "value-text",
570
+ "required": false,
571
+ "mutable": false,
572
+ "description": "Human-readable value text for a single slider or both range thumbs."
573
+ },
574
+ "valueTexts": {
575
+ "type": "string[]",
576
+ "resolvedType": "string[]",
577
+ "default": "[]",
578
+ "required": false,
579
+ "mutable": false,
580
+ "description": "Per-thumb human-readable value text for a range slider. Assign through the JavaScript property."
581
+ }
582
+ },
583
+ "events": [
584
+ {
585
+ "name": "dsChange",
586
+ "detail": "SliderValue",
587
+ "bubbles": true,
588
+ "cancelable": true,
589
+ "composed": true,
590
+ "description": "Emitted continuously while the value changes."
591
+ },
592
+ {
593
+ "name": "dsCommit",
594
+ "detail": "SliderValue",
595
+ "bubbles": true,
596
+ "cancelable": true,
597
+ "composed": true,
598
+ "description": "Emitted when a pointer or keyboard value change is committed."
599
+ }
600
+ ],
601
+ "methods": [
602
+ {
603
+ "name": "setFocus",
604
+ "signature": "setFocus(index?: number) => Promise<void>"
605
+ }
606
+ ],
607
+ "slots": [],
608
+ "exports": {
609
+ "customElement": "ds-slider",
610
+ "react": "DsSlider",
611
+ "angular": "DsSlider"
612
+ },
613
+ "consumption": {
614
+ "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
615
+ "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
616
+ "customElements": {
617
+ "import": "import '@ds-mo/ui/dist/components/ds-slider.js';",
618
+ "example": "<ds-slider></ds-slider>"
619
+ },
620
+ "react": {
621
+ "import": "import { DsSlider } from '@ds-mo/ui/react';",
622
+ "example": "<DsSlider />"
623
+ },
624
+ "angular": {
625
+ "import": "import { DsSlider } from '@ds-mo/ui/angular/ds-slider';",
626
+ "example": "<ds-slider></ds-slider>"
627
+ },
628
+ "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: formatOptions, valueTexts.",
629
+ "peerDependencies": {
630
+ "required": [
631
+ "@ds-mo/tokens ^3.1.1",
632
+ "@ds-mo/icons ^6.2.0"
633
+ ],
634
+ "frameworks": "Custom Elements; React 18/19 wrappers; Angular 19-22 standalone adapters."
635
+ }
636
+ }
637
+ },
638
+ "dependencies": [
639
+ "@ds-mo/ui",
640
+ "@ds-mo/tokens"
641
+ ],
642
+ "registryDependencies": [
643
+ "text"
644
+ ],
645
+ "files": [
646
+ {
647
+ "path": "src/wc/components/Slider/Slider.css",
648
+ "content": "@import '../../utils/control-inactive.css';\n\n:host {\n --ds-slider-control-size: var(--dimension-size-400);\n --ds-slider-thumb-size: var(--dimension-size-200);\n --ds-slider-track-size: var(--dimension-size-100);\n --ds-slider-track-stroke: var(--dimension-stroke-width-015);\n --ds-slider-thumb-stroke: var(--dimension-stroke-width-018);\n --ds-slider-vertical-length: calc(var(--dimension-size-800) * 3);\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n gap: var(--dimension-space-050);\n width: 100%;\n color: var(--color-foreground-primary);\n}\n\n:host(.slider--sm) {\n --ds-slider-control-size: var(--dimension-size-300);\n --ds-slider-thumb-size: var(--dimension-size-150);\n --ds-slider-track-size: var(--dimension-size-075);\n --ds-slider-track-stroke: var(--dimension-stroke-width-012);\n}\n\n:host(.slider--xs) {\n --ds-slider-control-size: var(--dimension-size-200);\n --ds-slider-thumb-size: var(--dimension-size-100);\n --ds-slider-track-size: var(--dimension-size-050);\n --ds-slider-track-stroke: calc(var(--dimension-stroke-width-base) * 3 / 32);\n}\n\n:host(.slider--vertical) {\n width: fit-content;\n min-width: var(--dimension-form-width-xs);\n}\n\n.slider__label-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--dimension-space-100);\n min-width: 0;\n}\n\n.slider__label {\n min-width: 0;\n}\n\n.slider__value {\n flex-shrink: 0;\n}\n\n.slider__control {\n position: relative;\n width: 100%;\n height: var(--ds-slider-control-size);\n border-radius: var(--dimension-radius-half);\n cursor: pointer;\n touch-action: none;\n user-select: none;\n}\n\n:host(.slider--vertical) .slider__control {\n align-self: center;\n width: var(--ds-slider-control-size);\n height: var(--ds-slider-vertical-length);\n}\n\n:host(.slider--readonly) .slider__control {\n cursor: default;\n}\n\n/* Inactive: `.ds-control-inactive` (25% opacity) on the host. */\n:host(.ds-control-inactive) .slider__control {\n cursor: not-allowed;\n}\n\n.slider__rail {\n position: absolute;\n top: 50%;\n right: calc(var(--ds-slider-thumb-size) / 2);\n left: calc(var(--ds-slider-thumb-size) / 2);\n height: var(--ds-slider-track-size);\n overflow: hidden;\n border-radius: var(--dimension-radius-025);\n background-color: transparent;\n box-shadow: inset 0 0 0 var(--ds-slider-track-stroke) var(--color-foreground-tertiary);\n transform: translateY(-50%);\n pointer-events: none;\n}\n\n:host(.slider--thumb-center) .slider__rail {\n right: 0;\n left: 0;\n}\n\n.slider__indicator {\n position: absolute;\n top: 0;\n bottom: 0;\n left: calc(var(--ds-slider-start-pct, 0) * 1%);\n width: calc((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) * 1%);\n border-radius: inherit;\n background-color: var(--color-background-bold-brand);\n}\n\n.slider__thumb {\n position: absolute;\n top: 50%;\n left: calc((var(--ds-slider-thumb-pct, 0) / 100) * (100% - var(--ds-slider-thumb-size)));\n width: var(--ds-slider-thumb-size);\n height: var(--ds-slider-thumb-size);\n transform: translateY(-50%);\n z-index: 1;\n}\n\n:host(.slider--thumb-center) .slider__thumb {\n left: calc(var(--ds-slider-thumb-pct, 0) * 1%);\n transform: translate(-50%, -50%);\n}\n\n.slider__thumb--active {\n z-index: 2;\n}\n\n.slider__input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n border: 0;\n opacity: 0;\n appearance: none;\n pointer-events: none;\n z-index: 2;\n}\n\n.slider__thumb-visual {\n position: absolute;\n inset: 0;\n box-sizing: border-box;\n overflow: visible;\n border-radius: var(--dimension-radius-025);\n background-color: var(--color-background-primary);\n box-shadow: inset 0 0 0 var(--ds-slider-thumb-stroke) var(--color-border-bold-brand);\n pointer-events: none;\n transition:\n background-color var(--effect-motion-short-2),\n box-shadow var(--effect-motion-short-2);\n}\n\n.slider__thumb-wash {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background-color: transparent;\n transition: background-color var(--effect-motion-short-2);\n}\n\n:host(:not(.slider--readonly):not(.ds-control-inactive)) .slider__thumb:hover .slider__thumb-wash {\n background-color: var(--color-interaction-hover);\n}\n\n:host([data-dragging]:not(.slider--readonly):not(.ds-control-inactive))\n .slider__thumb--active .slider__thumb-wash {\n background-color: var(--color-interaction-pressed);\n}\n\n.slider__input:focus-visible + .slider__thumb-visual,\n:host([data-review-focus]) .slider__thumb[data-index='0'] .slider__thumb-visual {\n outline: var(--dimension-stroke-width-025) solid var(--color-interaction-focus);\n outline-offset: var(--dimension-space-025);\n}\n\n:host(.slider--vertical) .slider__rail {\n top: calc(var(--ds-slider-thumb-size) / 2);\n right: auto;\n bottom: calc(var(--ds-slider-thumb-size) / 2);\n left: 50%;\n width: var(--ds-slider-track-size);\n height: auto;\n transform: translateX(-50%);\n}\n\n:host(.slider--vertical.slider--thumb-center) .slider__rail {\n top: 0;\n bottom: 0;\n}\n\n:host(.slider--vertical) .slider__indicator {\n top: auto;\n right: 0;\n bottom: calc(var(--ds-slider-start-pct, 0) * 1%);\n left: 0;\n width: auto;\n height: calc((var(--ds-slider-end-pct, 0) - var(--ds-slider-start-pct, 0)) * 1%);\n}\n\n:host(.slider--vertical) .slider__thumb {\n top: auto;\n bottom: calc((var(--ds-slider-thumb-pct, 0) / 100) * (100% - var(--ds-slider-thumb-size)));\n left: 50%;\n transform: translateX(-50%);\n}\n\n:host(.slider--vertical.slider--thumb-center) .slider__thumb {\n bottom: calc(var(--ds-slider-thumb-pct, 0) * 1%);\n transform: translate(-50%, 50%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .slider__thumb-visual,\n .slider__thumb-wash {\n transition: none;\n }\n}\n",
649
+ "type": "registry:ui"
650
+ },
651
+ {
652
+ "path": "src/wc/components/Slider/Slider.tsx",
653
+ "content": "import {\n AttachInternals,\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\n\nexport type SliderValue = number | number[];\nexport type SliderSize = 'md' | 'sm' | 'xs';\nexport type SliderOrientation = 'horizontal' | 'vertical';\nexport type SliderThumbAlignment = 'edge' | 'center';\n\nlet sliderId = 0;\nlet sliderExternalLabelId = 0;\n\n@Component({\n tag: 'ds-slider',\n styleUrl: 'Slider.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Slider {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n /** Current value. Assign a two-number array through the JavaScript property for a range slider. */\n @Prop({ mutable: true }) value: SliderValue = 0;\n @Prop() min: number = 0;\n @Prop() max: number = 100;\n @Prop() step: number = 1;\n /** Minimum number of steps kept between two range thumbs. */\n @Prop() minStepsBetweenValues: number = 0;\n /** Visible field label. Supply aria-label when the label is intentionally hidden. */\n @Prop() label: string | undefined;\n /** Show the formatted current value beside the visible label. */\n @Prop() showValue: boolean = true;\n @Prop() size: SliderSize = 'md';\n @Prop() orientation: SliderOrientation = 'horizontal';\n /** Keep the complete thumb inside the rail bounds, or center it on the endpoints. */\n @Prop() thumbAlignment: SliderThumbAlignment = 'edge';\n @Prop({ reflect: true }) name: string | undefined;\n @Prop({ reflect: true }) form: string | undefined;\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Keep the value focusable and submittable while preventing changes. */\n @Prop({ reflect: true }) readOnly: boolean = false;\n @Prop() isInactive: boolean = false;\n /** Direct accessible name for a single-thumb slider without a visible label. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n /** Id references for a visible single-thumb slider label. */\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n /** Id references for supporting guidance or error text. */\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n /** Accessible label for the lower thumb in a range slider. */\n @Prop() startLabel: string = 'Minimum value';\n /** Accessible label for the upper thumb in a range slider. */\n @Prop() endLabel: string = 'Maximum value';\n /** Human-readable value text for a single slider or both range thumbs. */\n @Prop() valueText: string | undefined;\n /** Per-thumb human-readable value text for a range slider. Assign through the JavaScript property. */\n @Prop() valueTexts: string[] = [];\n /** Locale used by Intl.NumberFormat for the visible value. */\n @Prop() locale: string | undefined;\n /** Intl.NumberFormat options. Assign objects through the JavaScript property. */\n @Prop() formatOptions: Intl.NumberFormatOptions | undefined;\n @Prop() valuePrefix: string = '';\n @Prop() valueSuffix: string = '';\n @Prop() rangeSeparator: string = '–';\n /** Id applied to the first native range input. */\n @Prop() inputId: string | undefined;\n\n /** Emitted continuously while the value changes. */\n @Event() dsChange!: EventEmitter<SliderValue>;\n /** Emitted when a pointer or keyboard value change is committed. */\n @Event() dsCommit!: EventEmitter<SliderValue>;\n\n @State() private formDisabled = false;\n @State() private dragging = false;\n @State() private focused = false;\n @State() private touched = false;\n @State() private activeThumbIndex = 0;\n @State() private externalLabelledby: string | undefined;\n\n private readonly generatedId = `ds-slider-${++sliderId}`;\n private readonly labelId = `${this.generatedId}-label`;\n private initialValue: SliderValue = 0;\n private lastCommittedValue: SliderValue = 0;\n private pointerId: number | null = null;\n private pointerStartValue: SliderValue = 0;\n private controlEl: HTMLDivElement | null = null;\n\n componentWillLoad() {\n this.initialValue = this.cloneValue(this.publicValue);\n this.lastCommittedValue = this.cloneValue(this.publicValue);\n this.syncFormValue();\n }\n\n componentDidLoad() {\n this.syncNativeLabels();\n }\n\n @Watch('value')\n @Watch('min')\n @Watch('max')\n @Watch('step')\n @Watch('minStepsBetweenValues')\n @Watch('name')\n @Watch('disabled')\n @Watch('isInactive')\n syncFormValue() {\n const values = this.resolvedValues;\n if (this.isDisabled || !this.name) {\n this.internals.setFormValue(null);\n return;\n }\n\n if (this.isRange) {\n const data = new FormData();\n values.forEach(value => data.append(this.name as string, String(value)));\n this.internals.setFormValue(data, JSON.stringify(values));\n } else {\n this.internals.setFormValue(String(values[0]), String(values[0]));\n }\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.cloneValue(this.initialValue);\n this.lastCommittedValue = this.cloneValue(this.initialValue);\n this.dragging = false;\n this.focused = false;\n this.touched = false;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n if (typeof state !== 'string') return;\n if (this.isRange) {\n try {\n const restored = JSON.parse(state);\n if (Array.isArray(restored)) this.value = restored.map(Number).slice(0, 2);\n } catch {\n this.value = this.cloneValue(this.initialValue);\n }\n } else {\n const restored = Number(state);\n if (Number.isFinite(restored)) this.value = restored;\n }\n this.lastCommittedValue = this.cloneValue(this.publicValue);\n }\n\n @Method()\n async setFocus(index: number = 0) {\n const inputs = this.el.querySelectorAll<HTMLInputElement>('.slider__input');\n inputs[Math.max(0, Math.min(index, inputs.length - 1))]?.focus();\n }\n\n private get isDisabled(): boolean {\n return this.disabled || this.isInactive || this.formDisabled;\n }\n\n private get isRange(): boolean {\n return Array.isArray(this.value);\n }\n\n private get safeStep(): number {\n return Number.isFinite(this.step) && this.step > 0 ? this.step : 1;\n }\n\n private get safeMin(): number {\n return Number.isFinite(this.min) ? this.min : 0;\n }\n\n private get safeMax(): number {\n const minimum = this.safeMin;\n return Number.isFinite(this.max) && this.max > minimum ? this.max : minimum + this.safeStep;\n }\n\n private get minimumGap(): number {\n const steps = Number.isFinite(this.minStepsBetweenValues)\n ? Math.max(0, Math.floor(this.minStepsBetweenValues))\n : 0;\n return steps * this.safeStep;\n }\n\n private get resolvedValues(): number[] {\n const minimum = this.safeMin;\n const maximum = this.safeMax;\n if (!this.isRange) return [this.snap(Number(this.value))];\n\n const raw = this.value as number[];\n let lower = this.snap(Number(raw[0] ?? minimum));\n let upper = this.snap(Number(raw[1] ?? maximum));\n if (lower > upper) [lower, upper] = [upper, lower];\n\n const gap = Math.min(this.minimumGap, maximum - minimum);\n if (upper - lower < gap) {\n upper = Math.min(maximum, this.snap(lower + gap));\n lower = Math.max(minimum, this.snap(upper - gap));\n }\n return [lower, upper];\n }\n\n private get publicValue(): SliderValue {\n const values = this.resolvedValues;\n return this.isRange ? [...values] : values[0];\n }\n\n private cloneValue(value: SliderValue): SliderValue {\n return Array.isArray(value) ? [...value] : value;\n }\n\n private valuesEqual(a: SliderValue, b: SliderValue): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n }\n return !Array.isArray(a) && !Array.isArray(b) && a === b;\n }\n\n private decimalPlaces(value: number): number {\n const text = String(value).toLowerCase();\n if (text.includes('e-')) return Number(text.split('e-')[1]) || 0;\n return text.includes('.') ? text.split('.')[1].length : 0;\n }\n\n private snap(value: number): number {\n const minimum = this.safeMin;\n const maximum = this.safeMax;\n const candidate = Number.isFinite(value) ? value : minimum;\n const stepped = minimum + Math.round((candidate - minimum) / this.safeStep) * this.safeStep;\n const precision = Math.min(10, Math.max(\n this.decimalPlaces(minimum),\n this.decimalPlaces(maximum),\n this.decimalPlaces(this.safeStep),\n ));\n return Number(Math.min(maximum, Math.max(minimum, stepped)).toFixed(precision));\n }\n\n private percent(value: number): number {\n return ((value - this.safeMin) / (this.safeMax - this.safeMin)) * 100;\n }\n\n private allowedMin(index: number, values = this.resolvedValues): number {\n return this.isRange && index === 1\n ? Math.min(this.safeMax, values[0] + this.minimumGap)\n : this.safeMin;\n }\n\n private allowedMax(index: number, values = this.resolvedValues): number {\n return this.isRange && index === 0\n ? Math.max(this.safeMin, values[1] - this.minimumGap)\n : this.safeMax;\n }\n\n private setValueAt(index: number, next: number): boolean {\n const previous = this.publicValue;\n const values = this.resolvedValues;\n const constrained = Math.min(this.allowedMax(index, values), Math.max(this.allowedMin(index, values), this.snap(next)));\n values[index] = constrained;\n const nextValue: SliderValue = this.isRange ? [...values] : values[0];\n if (this.valuesEqual(previous, nextValue)) return false;\n\n this.value = nextValue;\n this.dsChange.emit(this.cloneValue(nextValue));\n return true;\n }\n\n private commitIfChanged() {\n const current = this.publicValue;\n if (this.valuesEqual(current, this.lastCommittedValue)) return;\n this.lastCommittedValue = this.cloneValue(current);\n this.dsCommit.emit(this.cloneValue(current));\n }\n\n private syncNativeLabels() {\n if (this.label || this.ariaLabel || this.ariaLabelledby || this.isRange) return;\n const ids = (Array.from(this.internals.labels) as HTMLLabelElement[]).map(label => {\n if (!label.id) label.id = `ds-slider-label-${++sliderExternalLabelId}`;\n return label.id;\n });\n if (ids.length) this.externalLabelledby = ids.join(' ');\n }\n\n private formattedValue(value: number, index: number): string {\n const authored = this.valueTexts[index] ?? this.valueText;\n if (authored) return authored;\n let number: string;\n try {\n number = new Intl.NumberFormat(this.locale, this.formatOptions).format(value);\n } catch {\n number = String(value);\n }\n return `${this.valuePrefix}${number}${this.valueSuffix}`;\n }\n\n private ariaValueText(value: number, index: number): string | undefined {\n if (this.valueTexts[index] || this.valueText || this.valuePrefix || this.valueSuffix || this.formatOptions) {\n return this.formattedValue(value, index);\n }\n return undefined;\n }\n\n private handleNativeInput = (event: Event, index: number) => {\n const input = event.target as HTMLInputElement;\n this.activeThumbIndex = index;\n if (this.readOnly || this.isDisabled) {\n input.value = String(this.resolvedValues[index]);\n return;\n }\n this.setValueAt(index, Number(input.value));\n };\n\n private handleNativeChange = () => {\n if (!this.readOnly && !this.isDisabled) this.commitIfChanged();\n };\n\n private handleKeyDown = (event: KeyboardEvent) => {\n if (!this.readOnly) return;\n if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(event.key)) {\n event.preventDefault();\n }\n };\n\n private handleFocus = (index: number) => {\n this.activeThumbIndex = index;\n this.focused = true;\n };\n\n private handleBlur = (event: FocusEvent) => {\n const next = event.relatedTarget;\n if (next instanceof Node && this.el.contains(next)) return;\n this.focused = false;\n this.touched = true;\n };\n\n private handleHostClick = (event: MouseEvent) => {\n if (event.target !== this.el || this.isDisabled) return;\n const inputs = this.el.querySelectorAll<HTMLInputElement>('.slider__input');\n inputs[Math.min(this.activeThumbIndex, inputs.length - 1)]?.focus();\n };\n\n private pointerPercent(event: PointerEvent): number {\n const control = this.controlEl;\n if (!control) return 0;\n const rect = control.getBoundingClientRect();\n const thumb = this.el.querySelector<HTMLElement>('.slider__thumb');\n const thumbSize = thumb\n ? this.orientation === 'horizontal' ? thumb.offsetWidth : thumb.offsetHeight\n : 0;\n const inset = this.thumbAlignment === 'edge' ? thumbSize / 2 : 0;\n\n if (this.orientation === 'vertical') {\n const travel = Math.max(1, rect.height - inset * 2);\n return Math.min(1, Math.max(0, (rect.bottom - inset - event.clientY) / travel));\n }\n\n const travel = Math.max(1, rect.width - inset * 2);\n const percent = (event.clientX - rect.left - inset) / travel;\n return Math.min(1, Math.max(0, percent));\n }\n\n private valueFromPointer(event: PointerEvent): number {\n return this.snap(this.safeMin + this.pointerPercent(event) * (this.safeMax - this.safeMin));\n }\n\n private closestThumbIndex(value: number, target: EventTarget | null): number {\n const targetElement = target instanceof HTMLElement\n ? target.closest<HTMLElement>('.slider__thumb')\n : null;\n if (targetElement?.dataset['index']) return Number(targetElement.dataset['index']);\n if (!this.isRange) return 0;\n\n const values = this.resolvedValues;\n const lowerDistance = Math.abs(value - values[0]);\n const upperDistance = Math.abs(value - values[1]);\n if (lowerDistance === upperDistance) return value >= values[0] ? 1 : 0;\n return lowerDistance < upperDistance ? 0 : 1;\n }\n\n private handlePointerDown = (event: PointerEvent) => {\n if (event.button !== 0 || this.isDisabled || this.readOnly) return;\n event.preventDefault();\n const value = this.valueFromPointer(event);\n const index = this.closestThumbIndex(value, event.target);\n this.activeThumbIndex = index;\n this.pointerId = event.pointerId;\n this.pointerStartValue = this.cloneValue(this.publicValue);\n this.dragging = true;\n this.setValueAt(index, value);\n this.controlEl?.setPointerCapture(event.pointerId);\n requestAnimationFrame(() => this.el.querySelectorAll<HTMLInputElement>('.slider__input')[index]?.focus({ preventScroll: true }));\n };\n\n private handlePointerMove = (event: PointerEvent) => {\n if (this.pointerId !== event.pointerId || !this.dragging) return;\n event.preventDefault();\n this.setValueAt(this.activeThumbIndex, this.valueFromPointer(event));\n };\n\n private endPointerInteraction(event: PointerEvent, canceled: boolean) {\n if (this.pointerId !== event.pointerId) return;\n if (this.controlEl?.hasPointerCapture(event.pointerId)) this.controlEl.releasePointerCapture(event.pointerId);\n this.pointerId = null;\n this.dragging = false;\n this.touched = true;\n if (!canceled && !this.valuesEqual(this.pointerStartValue, this.publicValue)) this.commitIfChanged();\n }\n\n private renderThumb(value: number, index: number) {\n const inputId = index === 0\n ? this.inputId ?? `${this.generatedId}-input`\n : `${this.inputId ?? `${this.generatedId}-input`}-end`;\n const rangeLabel = index === 0 ? this.startLabel : this.endLabel;\n const labelledby = this.isRange\n ? undefined\n : this.ariaLabelledby ?? (this.label ? this.labelId : this.externalLabelledby);\n const directLabel = this.isRange ? rangeLabel : labelledby ? undefined : this.ariaLabel;\n\n return (\n <div\n class={{\n slider__thumb: true,\n 'slider__thumb--active': index === this.activeThumbIndex,\n }}\n data-index={String(index)}\n style={{ '--ds-slider-thumb-pct': String(this.percent(value)) } as Record<string, string>}\n >\n <input\n class=\"slider__input\"\n type=\"range\"\n id={inputId}\n min={this.allowedMin(index)}\n max={this.allowedMax(index)}\n step={this.safeStep}\n value={value}\n disabled={this.isDisabled}\n aria-label={directLabel}\n aria-labelledby={labelledby}\n aria-describedby={this.ariaDescribedby}\n aria-valuetext={this.ariaValueText(value, index)}\n aria-orientation={this.orientation === 'vertical' ? 'vertical' : undefined}\n aria-readonly={this.readOnly ? 'true' : undefined}\n onInput={(event: Event) => this.handleNativeInput(event, index)}\n onChange={this.handleNativeChange}\n onKeyDown={this.handleKeyDown}\n onFocus={() => this.handleFocus(index)}\n onBlur={this.handleBlur}\n />\n <span class=\"slider__thumb-visual\" aria-hidden=\"true\"><span class=\"slider__thumb-wash\" /></span>\n </div>\n );\n }\n\n render() {\n const values = this.resolvedValues;\n const first = values[0];\n const last = values[values.length - 1];\n const firstInputId = this.inputId ?? `${this.generatedId}-input`;\n const dirty = !this.valuesEqual(this.publicValue, this.initialValue);\n const valueDisplay = values.map((value, index) => this.formattedValue(value, index)).join(` ${this.rangeSeparator} `);\n\n return (\n <Host\n aria-disabled={this.isDisabled ? 'true' : undefined}\n class={{\n slider: true,\n [`slider--${this.size}`]: true,\n [`slider--${this.orientation}`]: true,\n [`slider--thumb-${this.thumbAlignment}`]: true,\n 'slider--range': this.isRange,\n 'slider--readonly': this.readOnly,\n 'ds-control-inactive': this.isDisabled,\n }}\n data-orientation={this.orientation}\n data-disabled={this.isDisabled ? '' : undefined}\n data-readonly={this.readOnly ? '' : undefined}\n data-dragging={this.dragging ? '' : undefined}\n data-focused={this.focused ? '' : undefined}\n data-filled=\"\"\n data-dirty={dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n data-valid={!this.isDisabled ? '' : undefined}\n onClick={this.handleHostClick}\n >\n {(this.label || this.showValue) && (\n <div class=\"slider__label-row\">\n {this.label && (\n <ds-text\n class=\"slider__label\"\n as=\"label\"\n variant=\"text-body-small\"\n color=\"primary\"\n emphasis\n for={firstInputId}\n textId={this.labelId}\n >\n {this.label}\n </ds-text>\n )}\n {this.showValue && (\n <ds-text\n class=\"slider__value\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"primary\"\n fontFeature=\"tabular-nums\"\n >\n {valueDisplay}\n </ds-text>\n )}\n </div>\n )}\n <div\n class=\"slider__control\"\n ref={element => { this.controlEl = element ?? null; }}\n style={{\n '--ds-slider-start-pct': String(this.isRange ? this.percent(first) : 0),\n '--ds-slider-end-pct': String(this.percent(last)),\n } as Record<string, string>}\n onPointerDown={this.handlePointerDown}\n onPointerMove={this.handlePointerMove}\n onPointerUp={(event: PointerEvent) => this.endPointerInteraction(event, false)}\n onPointerCancel={(event: PointerEvent) => this.endPointerInteraction(event, true)}\n >\n <div class=\"slider__rail\" aria-hidden=\"true\"><div class=\"slider__indicator\" /></div>\n {values.map((value, index) => this.renderThumb(value, index))}\n </div>\n </Host>\n );\n }\n}\n",
654
+ "type": "registry:ui"
655
+ }
656
+ ]
657
+ }