@duskmoon-dev/core 1.19.2 → 1.19.4

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 +1 -1
  2. package/dist/base.css +484 -0
  3. package/dist/components/accordion.css +33 -9
  4. package/dist/components/autocomplete.css +67 -13
  5. package/dist/components/avatar.css +39 -15
  6. package/dist/components/badge.css +15 -3
  7. package/dist/components/card.css +12 -0
  8. package/dist/components/carousel.css +28 -0
  9. package/dist/components/chat.css +15 -11
  10. package/dist/components/checkbox.css +83 -13
  11. package/dist/components/collapse.css +67 -16
  12. package/dist/components/countdown.css +22 -0
  13. package/dist/components/datepicker.css +76 -0
  14. package/dist/components/diff.css +33 -0
  15. package/dist/components/file-input.css +107 -0
  16. package/dist/components/filter-group.css +452 -0
  17. package/dist/components/footer.css +48 -0
  18. package/dist/components/form-group.css +95 -204
  19. package/dist/components/form.css +1551 -421
  20. package/dist/components/hero.css +57 -0
  21. package/dist/components/index.css +7657 -7321
  22. package/dist/components/indicator.css +63 -0
  23. package/dist/components/input.css +114 -31
  24. package/dist/components/join.css +70 -0
  25. package/dist/components/kbd.css +28 -0
  26. package/dist/components/list.css +22 -3
  27. package/dist/components/mask.css +38 -0
  28. package/dist/components/otp-input.css +124 -6
  29. package/dist/components/radio.css +70 -0
  30. package/dist/components/range.css +41 -0
  31. package/dist/components/rating.css +39 -12
  32. package/dist/components/select.css +74 -0
  33. package/dist/components/sidebar-layout.css +66 -0
  34. package/dist/components/stack.css +56 -0
  35. package/dist/components/stat.css +40 -0
  36. package/dist/components/switch.css +70 -0
  37. package/dist/components/table.css +8 -0
  38. package/dist/components/textarea.css +74 -0
  39. package/dist/components/timeline.css +40 -25
  40. package/dist/components/toggle-switch.css +165 -0
  41. package/dist/components/toggle.css +198 -32
  42. package/dist/components/validator.css +66 -0
  43. package/dist/effects/aura.css +22 -0
  44. package/dist/effects/hover-3d.css +8 -0
  45. package/dist/effects/hover-gallery.css +14 -0
  46. package/dist/effects/index.css +65 -0
  47. package/dist/effects/text-rotate.css +16 -0
  48. package/dist/esm/components/accordion.js +33 -9
  49. package/dist/esm/components/autocomplete.js +67 -13
  50. package/dist/esm/components/avatar.js +39 -15
  51. package/dist/esm/components/badge.js +15 -3
  52. package/dist/esm/components/card.js +12 -0
  53. package/dist/esm/components/carousel.d.ts +5 -0
  54. package/dist/esm/components/carousel.js +37 -0
  55. package/dist/esm/components/chat.js +15 -11
  56. package/dist/esm/components/checkbox.js +84 -14
  57. package/dist/esm/components/collapse.js +67 -16
  58. package/dist/esm/components/countdown.d.ts +5 -0
  59. package/dist/esm/components/countdown.js +31 -0
  60. package/dist/esm/components/datepicker.js +77 -1
  61. package/dist/esm/components/diff.d.ts +5 -0
  62. package/dist/esm/components/diff.js +42 -0
  63. package/dist/esm/components/file-input.d.ts +5 -0
  64. package/dist/esm/components/file-input.js +116 -0
  65. package/dist/esm/components/filter-group.d.ts +5 -0
  66. package/dist/esm/components/filter-group.js +461 -0
  67. package/dist/esm/components/footer.d.ts +5 -0
  68. package/dist/esm/components/footer.js +57 -0
  69. package/dist/esm/components/form-group.js +96 -205
  70. package/dist/esm/components/form.js +1539 -409
  71. package/dist/esm/components/hero.d.ts +5 -0
  72. package/dist/esm/components/hero.js +66 -0
  73. package/dist/esm/components/indicator.d.ts +5 -0
  74. package/dist/esm/components/indicator.js +72 -0
  75. package/dist/esm/components/input.js +115 -32
  76. package/dist/esm/components/join.d.ts +5 -0
  77. package/dist/esm/components/join.js +79 -0
  78. package/dist/esm/components/kbd.d.ts +5 -0
  79. package/dist/esm/components/kbd.js +37 -0
  80. package/dist/esm/components/list.js +22 -3
  81. package/dist/esm/components/mask.d.ts +5 -0
  82. package/dist/esm/components/mask.js +47 -0
  83. package/dist/esm/components/otp-input.js +125 -7
  84. package/dist/esm/components/radio.js +71 -1
  85. package/dist/esm/components/range.d.ts +5 -0
  86. package/dist/esm/components/range.js +50 -0
  87. package/dist/esm/components/rating.js +39 -12
  88. package/dist/esm/components/select.js +75 -1
  89. package/dist/esm/components/sidebar-layout.d.ts +5 -0
  90. package/dist/esm/components/sidebar-layout.js +75 -0
  91. package/dist/esm/components/stack.d.ts +5 -0
  92. package/dist/esm/components/stack.js +65 -0
  93. package/dist/esm/components/stat.d.ts +5 -0
  94. package/dist/esm/components/stat.js +49 -0
  95. package/dist/esm/components/switch.js +71 -1
  96. package/dist/esm/components/table.js +8 -0
  97. package/dist/esm/components/textarea.js +75 -1
  98. package/dist/esm/components/timeline.js +40 -25
  99. package/dist/esm/components/toggle-switch.d.ts +5 -0
  100. package/dist/esm/components/toggle-switch.js +174 -0
  101. package/dist/esm/components/toggle.js +198 -32
  102. package/dist/esm/components/validator.d.ts +5 -0
  103. package/dist/esm/components/validator.js +75 -0
  104. package/dist/esm/effects/aura.d.ts +5 -0
  105. package/dist/esm/effects/aura.js +31 -0
  106. package/dist/esm/effects/hover-3d.d.ts +5 -0
  107. package/dist/esm/effects/hover-3d.js +17 -0
  108. package/dist/esm/effects/hover-gallery.d.ts +5 -0
  109. package/dist/esm/effects/hover-gallery.js +23 -0
  110. package/dist/esm/effects/index.d.ts +5 -0
  111. package/dist/esm/effects/index.js +74 -0
  112. package/dist/esm/effects/text-rotate.d.ts +5 -0
  113. package/dist/esm/effects/text-rotate.js +25 -0
  114. package/dist/index.css +8324 -7989
  115. package/dist/index.min.css +1 -1
  116. package/dist/standalone/duskmoonui-themes.css +1 -1
  117. package/dist/standalone/duskmoonui.css +9103 -8759
  118. package/dist/standalone/duskmoonui.js +12 -13
  119. package/dist/standalone/duskmoonui.mjs +12 -13
  120. package/dist/types/types/plugin.d.ts +1 -1
  121. package/dist/types/types/plugin.d.ts.map +1 -1
  122. package/package.json +140 -1
@@ -17,7 +17,7 @@
17
17
  content: '';
18
18
  position: absolute;
19
19
  top: 0;
20
- left: 1.25rem;
20
+ inset-inline-start: 1.25rem;
21
21
  width: 2px;
22
22
  height: 100%;
23
23
  background-color: var(--color-outline-variant);
@@ -64,6 +64,7 @@
64
64
 
65
65
  /* Timeline Content */
66
66
  .timeline-content {
67
+ overflow-wrap: anywhere;
67
68
  flex: 1;
68
69
  padding-top: 0.375rem;
69
70
  min-width: 0;
@@ -163,9 +164,18 @@
163
164
  background-color: var(--color-primary-content);
164
165
  }
165
166
 
167
+ .timeline-marker-secondary { border-color: var(--color-secondary); background: var(--color-secondary-container); }
168
+ .timeline-marker-tertiary { border-color: var(--color-tertiary); background: var(--color-tertiary-container); }
169
+ .timeline-marker-success { border-color: var(--color-success); background: var(--color-success-container); }
170
+ .timeline-marker-error { border-color: var(--color-error); background: var(--color-error-container); }
171
+ .timeline-item-active > .timeline-marker { border-color: var(--color-primary); background: var(--color-primary-container); }
172
+ .timeline-item-pending > .timeline-marker { border-style: dashed; }
173
+ .timeline-compact > .timeline-item { padding-block-end: 0.75rem; gap: 0.5rem; }
174
+ .timeline-content-card { padding: 1rem; border: 1px solid var(--color-outline-variant); border-radius: var(--radius-md); background: var(--color-surface-container); box-shadow: var(--shadow-sm); }
175
+
166
176
  /* Size Variants */
167
177
  .timeline-sm::before {
168
- left: 0.75rem;
178
+ inset-inline-start: 0.75rem;
169
179
  }
170
180
 
171
181
  .timeline-sm .timeline-marker {
@@ -183,7 +193,7 @@
183
193
  }
184
194
 
185
195
  .timeline-lg::before {
186
- left: 1.5rem;
196
+ inset-inline-start: 1.5rem;
187
197
  }
188
198
 
189
199
  .timeline-lg .timeline-marker {
@@ -197,38 +207,38 @@
197
207
 
198
208
  /* Alternate Layout (zigzag) */
199
209
  .timeline-alternate {
200
- padding-left: 0;
210
+ padding-inline-start: 0;
201
211
  }
202
212
 
203
213
  .timeline-alternate::before {
204
- left: 50%;
214
+ inset-inline-start: 50%;
205
215
  transform: translateX(-50%);
206
216
  }
207
217
 
208
218
  .timeline-alternate .timeline-item {
209
219
  width: 50%;
210
- padding-right: 2rem;
220
+ padding-inline-end: 2rem;
211
221
  }
212
222
 
213
223
  .timeline-alternate .timeline-item:nth-child(even) {
214
- margin-left: 50%;
215
- padding-right: 0;
216
- padding-left: 2rem;
224
+ margin-inline-start: 50%;
225
+ padding-inline-end: 0;
226
+ padding-inline-start: 2rem;
217
227
  flex-direction: row-reverse;
218
228
  }
219
229
 
220
230
  .timeline-alternate .timeline-item:nth-child(even) .timeline-content {
221
- text-align: right;
231
+ text-align: end;
222
232
  }
223
233
 
224
234
  .timeline-alternate .timeline-marker {
225
235
  position: absolute;
226
- left: 100%;
236
+ inset-inline-start: 100%;
227
237
  transform: translateX(-50%);
228
238
  }
229
239
 
230
240
  .timeline-alternate .timeline-item:nth-child(even) .timeline-marker {
231
- left: 0;
241
+ inset-inline-start: 0;
232
242
  transform: translateX(-50%);
233
243
  }
234
244
 
@@ -238,8 +248,8 @@
238
248
  }
239
249
 
240
250
  .timeline-right::before {
241
- left: auto;
242
- right: 1.25rem;
251
+ inset-inline-start: auto;
252
+ inset-inline-end: 1.25rem;
243
253
  }
244
254
 
245
255
  .timeline-right .timeline-item {
@@ -247,7 +257,7 @@
247
257
  }
248
258
 
249
259
  .timeline-right .timeline-content {
250
- text-align: right;
260
+ text-align: end;
251
261
  }
252
262
 
253
263
  /* Horizontal Timeline */
@@ -259,7 +269,7 @@
259
269
 
260
270
  .timeline-horizontal::before {
261
271
  top: 1.25rem;
262
- left: 0;
272
+ inset-inline-start: 0;
263
273
  width: 100%;
264
274
  height: 2px;
265
275
  }
@@ -267,12 +277,12 @@
267
277
  .timeline-horizontal .timeline-item {
268
278
  flex-direction: column;
269
279
  padding-bottom: 0;
270
- padding-right: 2rem;
280
+ padding-inline-end: 2rem;
271
281
  min-width: 10rem;
272
282
  }
273
283
 
274
284
  .timeline-horizontal .timeline-item:last-child {
275
- padding-right: 0;
285
+ padding-inline-end: 0;
276
286
  }
277
287
 
278
288
  .timeline-horizontal .timeline-content {
@@ -280,6 +290,9 @@
280
290
  text-align: center;
281
291
  }
282
292
 
293
+ .timeline-alternate:dir(rtl)::before,
294
+ .timeline-alternate:dir(rtl) .timeline-marker { transform: translateX(50%); }
295
+
283
296
  /* Collapsible Timeline */
284
297
  .timeline-collapsible .timeline-item {
285
298
  cursor: pointer;
@@ -318,28 +331,30 @@
318
331
 
319
332
  /* Responsive */
320
333
  @media (max-width: 640px) {
321
- .timeline-alternate::before {
322
- left: 1.25rem;
334
+ .timeline-alternate::before,
335
+ .timeline-alternate:dir(rtl)::before {
336
+ inset-inline-start: 1.25rem;
323
337
  transform: none;
324
338
  }
325
339
 
326
340
  .timeline-alternate .timeline-item,
327
341
  .timeline-alternate .timeline-item:nth-child(even) {
328
342
  width: 100%;
329
- margin-left: 0;
330
- padding-left: 0;
331
- padding-right: 0;
343
+ margin-inline-start: 0;
344
+ padding-inline-start: 0;
345
+ padding-inline-end: 0;
332
346
  flex-direction: row;
333
347
  }
334
348
 
335
349
  .timeline-alternate .timeline-item:nth-child(even) .timeline-content {
336
- text-align: left;
350
+ text-align: start;
337
351
  }
338
352
 
339
353
  .timeline-alternate .timeline-marker,
354
+ .timeline-alternate:dir(rtl) .timeline-marker,
340
355
  .timeline-alternate .timeline-item:nth-child(even) .timeline-marker {
341
356
  position: relative;
342
- left: 0;
357
+ inset-inline-start: 0;
343
358
  transform: none;
344
359
  }
345
360
  }
@@ -0,0 +1,165 @@
1
+ /** Opt-in native presentation. Applications own errors, messages and announcements. */
2
+ @layer components {
3
+ /* Compatibility wrapper states are intentionally immediate. */
4
+ :is(.form-group-info, .form-control.info) { --dm-validation-color: var(--color-info); }
5
+ :is(.form-group-warning, .form-control.warning) { --dm-validation-color: var(--color-warning); }
6
+ :is(.form-group-success, .form-control.success) { --dm-validation-color: var(--color-success); }
7
+
8
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :valid, .validate:valid, .validator.validator-success:user-valid):not([aria-invalid="true"]):not(:where(.form-group-error *, .form-control.error *)):not(:disabled) {
9
+ --dm-validation-color: var(--color-success);
10
+ --dm-validation-content: var(--color-success-content);
11
+ }
12
+
13
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :invalid, .validate:invalid, .validator:user-invalid):not(:disabled) {
14
+ --dm-validation-color: var(--color-error);
15
+ --dm-validation-content: var(--color-error-content);
16
+ }
17
+
18
+ :is(.form-group-error, .form-control.error) {
19
+ --dm-validation-color: var(--color-error);
20
+ --dm-validation-content: var(--color-error-content);
21
+ }
22
+
23
+ /* Higher specificity than native states, including server-invalid + user-valid. */
24
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .form-group-error *, .form-control.error *):not(:disabled):is(:focus, :not(:focus)) {
25
+ --dm-validation-color: var(--color-error);
26
+ --dm-validation-content: var(--color-error-content);
27
+ }
28
+
29
+ /* This shared selector wins over filled/ghost focus and semantic modifiers. */
30
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled) {
31
+ color: var(--dm-validation-color);
32
+ border-color: var(--dm-validation-color);
33
+ --checkbox-color: var(--dm-validation-color);
34
+ --radio-color: var(--dm-validation-color);
35
+ --switch-color: var(--dm-validation-color);
36
+ --switch-content-color: var(--dm-validation-content, var(--color-primary-content));
37
+ --toggle-color: var(--dm-validation-color);
38
+ }
39
+
40
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled):focus {
41
+ border-color: var(--dm-validation-color);
42
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--dm-validation-color) 20%, transparent);
43
+ }
44
+
45
+ :is(.form-group-error, .form-control.error) :is(.form-label, .label-text) { color: var(--color-error); }
46
+ :is(.form-group-success, .form-control.success) :is(.form-label, .label-text) { color: var(--color-success); }
47
+ :is(.form-group-warning, .form-control.warning) :is(.form-label, .label-text) { color: var(--color-warning); }
48
+ :is(.form-group-info, .form-control.info) :is(.form-label, .label-text) { color: var(--color-info); }
49
+
50
+ .validator-hint { font-size: 0.75rem; color: var(--color-on-surface-variant); }
51
+ .validator-error { display: none; font-size: 0.75rem; color: var(--color-error); }
52
+ /* Keep OTP actions stationary when blur reveals an error during a click. */
53
+ .otp-code ~ .validator-error { display: block; visibility: hidden; }
54
+ :is(.form-group, .form-control):has(:is([aria-invalid="true"], .validator:user-invalid, .validate :invalid, .validate:invalid):not(:disabled)) > .validator-error,
55
+ :is(.form-group-error, .form-control.error) > .validator-error { display: block; visibility: visible; }
56
+
57
+ @media (forced-colors: active) {
58
+ :is(input, select, textarea):is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):focus-visible {
59
+ outline: 2px solid Highlight;
60
+ outline-offset: 2px;
61
+ }
62
+ input:is(.checkbox, .radio, .switch, .toggle) { appearance: auto; }
63
+ input:is(.checkbox, .radio, .switch, .toggle)::before,
64
+ input:is(.checkbox, .radio, .switch, .toggle)::after { content: none; }
65
+ }
66
+ }
67
+
68
+
69
+ @layer components {
70
+ /* Toggle/Switch */
71
+ .toggle {
72
+ --toggle-color: var(--color-primary);
73
+ box-sizing: border-box;
74
+ color: var(--color-on-surface);
75
+ border: 1px solid currentColor;
76
+ appearance: none;
77
+ width: 3rem;
78
+ height: 1.5rem;
79
+ border-radius: var(--radius-lg);
80
+ background-color: var(--color-surface-container-highest);
81
+ cursor: pointer;
82
+ transition: all 150ms ease-in-out;
83
+ position: relative;
84
+ }
85
+
86
+ .toggle::after {
87
+ content: '';
88
+ position: absolute;
89
+ inset-inline-start: 0.25rem;
90
+ top: 50%;
91
+ transform: translateY(-50%);
92
+ width: 1rem;
93
+ height: 1rem;
94
+ background-color: var(--color-on-surface-variant);
95
+ border-radius: var(--radius-full);
96
+ transition: all 150ms ease-in-out;
97
+ }
98
+
99
+ .toggle:checked {
100
+ background-color: var(--toggle-color);
101
+ }
102
+
103
+ .toggle:checked::after {
104
+ inset-inline-start: calc(100% - 1.25rem);
105
+ background-color: var(--dm-validation-content, var(--color-primary-content));
106
+ }
107
+
108
+ .toggle:focus-visible {
109
+ outline: none;
110
+ box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 20%, transparent);
111
+ }
112
+
113
+ .toggle:disabled {
114
+ cursor: not-allowed;
115
+ opacity: 0.5;
116
+ }
117
+
118
+ /* Toggle Sizes */
119
+ .toggle-xs {
120
+ width: 2rem;
121
+ height: 1rem;
122
+ }
123
+
124
+ .toggle-xs::after {
125
+ width: 0.625rem;
126
+ height: 0.625rem;
127
+ inset-inline-start: 0.1875rem;
128
+ }
129
+
130
+ .toggle-xs:checked::after {
131
+ inset-inline-start: calc(100% - 0.8125rem);
132
+ }
133
+
134
+ .toggle-sm {
135
+ width: 2.5rem;
136
+ height: 1.25rem;
137
+ }
138
+
139
+ .toggle-sm::after {
140
+ width: 0.75rem;
141
+ height: 0.75rem;
142
+ }
143
+
144
+ .toggle-lg {
145
+ width: 4rem;
146
+ height: 2rem;
147
+ }
148
+
149
+ .toggle-lg::after {
150
+ width: 1.5rem;
151
+ height: 1.5rem;
152
+ }
153
+
154
+ .toggle-sm:checked::after { inset-inline-start: calc(100% - 1rem); }
155
+ .toggle-lg:checked::after { inset-inline-start: calc(100% - 1.75rem); }
156
+ .toggle-primary { --toggle-color: var(--color-primary); }
157
+ .toggle-secondary { --toggle-color: var(--color-secondary); }
158
+ .toggle-tertiary { --toggle-color: var(--color-tertiary); }
159
+ .toggle-info { --toggle-color: var(--color-info); }
160
+ .toggle-success { --toggle-color: var(--color-success); }
161
+ .toggle-warning { --toggle-color: var(--color-warning); }
162
+ .toggle-error { --toggle-color: var(--color-error); }
163
+ .toggle-ghost { border-color: transparent; }
164
+ @media (prefers-reduced-motion: reduce) { .toggle, .toggle::after { transition: none; } }
165
+ }
@@ -3,6 +3,172 @@
3
3
  * DuskMoonUI - Material Design 3 inspired toggle button
4
4
  * Used for selecting between multiple options or toggling states
5
5
  */
6
+ /** Opt-in native presentation. Applications own errors, messages and announcements. */
7
+ @layer components {
8
+ /* Compatibility wrapper states are intentionally immediate. */
9
+ :is(.form-group-info, .form-control.info) { --dm-validation-color: var(--color-info); }
10
+ :is(.form-group-warning, .form-control.warning) { --dm-validation-color: var(--color-warning); }
11
+ :is(.form-group-success, .form-control.success) { --dm-validation-color: var(--color-success); }
12
+
13
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :valid, .validate:valid, .validator.validator-success:user-valid):not([aria-invalid="true"]):not(:where(.form-group-error *, .form-control.error *)):not(:disabled) {
14
+ --dm-validation-color: var(--color-success);
15
+ --dm-validation-content: var(--color-success-content);
16
+ }
17
+
18
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is(.validate :invalid, .validate:invalid, .validator:user-invalid):not(:disabled) {
19
+ --dm-validation-color: var(--color-error);
20
+ --dm-validation-content: var(--color-error-content);
21
+ }
22
+
23
+ :is(.form-group-error, .form-control.error) {
24
+ --dm-validation-color: var(--color-error);
25
+ --dm-validation-content: var(--color-error-content);
26
+ }
27
+
28
+ /* Higher specificity than native states, including server-invalid + user-valid. */
29
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .form-group-error *, .form-control.error *):not(:disabled):is(:focus, :not(:focus)) {
30
+ --dm-validation-color: var(--color-error);
31
+ --dm-validation-content: var(--color-error-content);
32
+ }
33
+
34
+ /* This shared selector wins over filled/ghost focus and semantic modifiers. */
35
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled) {
36
+ color: var(--dm-validation-color);
37
+ border-color: var(--dm-validation-color);
38
+ --checkbox-color: var(--dm-validation-color);
39
+ --radio-color: var(--dm-validation-color);
40
+ --switch-color: var(--dm-validation-color);
41
+ --switch-content-color: var(--dm-validation-content, var(--color-primary-content));
42
+ --toggle-color: var(--dm-validation-color);
43
+ }
44
+
45
+ :is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):is([aria-invalid="true"], .validator:user-invalid, .validator.validator-success:user-valid, .validate :invalid, .validate :valid, .validate:invalid, .validate:valid, .form-group-error *, .form-group-success *, .form-group-warning *, .form-group-info *, .form-control.error *, .form-control.success *, .form-control.warning *, .form-control.info *):not(:disabled):focus {
46
+ border-color: var(--dm-validation-color);
47
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--dm-validation-color) 20%, transparent);
48
+ }
49
+
50
+ :is(.form-group-error, .form-control.error) :is(.form-label, .label-text) { color: var(--color-error); }
51
+ :is(.form-group-success, .form-control.success) :is(.form-label, .label-text) { color: var(--color-success); }
52
+ :is(.form-group-warning, .form-control.warning) :is(.form-label, .label-text) { color: var(--color-warning); }
53
+ :is(.form-group-info, .form-control.info) :is(.form-label, .label-text) { color: var(--color-info); }
54
+
55
+ .validator-hint { font-size: 0.75rem; color: var(--color-on-surface-variant); }
56
+ .validator-error { display: none; font-size: 0.75rem; color: var(--color-error); }
57
+ /* Keep OTP actions stationary when blur reveals an error during a click. */
58
+ .otp-code ~ .validator-error { display: block; visibility: hidden; }
59
+ :is(.form-group, .form-control):has(:is([aria-invalid="true"], .validator:user-invalid, .validate :invalid, .validate:invalid):not(:disabled)) > .validator-error,
60
+ :is(.form-group-error, .form-control.error) > .validator-error { display: block; visibility: visible; }
61
+
62
+ @media (forced-colors: active) {
63
+ :is(input, select, textarea):is(.input, .select, .textarea, .checkbox, .radio, .switch, .toggle, .file-input, .otp-input-field, .otp-code, .datepicker-input):focus-visible {
64
+ outline: 2px solid Highlight;
65
+ outline-offset: 2px;
66
+ }
67
+ input:is(.checkbox, .radio, .switch, .toggle) { appearance: auto; }
68
+ input:is(.checkbox, .radio, .switch, .toggle)::before,
69
+ input:is(.checkbox, .radio, .switch, .toggle)::after { content: none; }
70
+ }
71
+ }
72
+
73
+
74
+ @layer components {
75
+ /* Toggle/Switch */
76
+ .toggle {
77
+ --toggle-color: var(--color-primary);
78
+ box-sizing: border-box;
79
+ color: var(--color-on-surface);
80
+ border: 1px solid currentColor;
81
+ appearance: none;
82
+ width: 3rem;
83
+ height: 1.5rem;
84
+ border-radius: var(--radius-lg);
85
+ background-color: var(--color-surface-container-highest);
86
+ cursor: pointer;
87
+ transition: all 150ms ease-in-out;
88
+ position: relative;
89
+ }
90
+
91
+ .toggle::after {
92
+ content: '';
93
+ position: absolute;
94
+ inset-inline-start: 0.25rem;
95
+ top: 50%;
96
+ transform: translateY(-50%);
97
+ width: 1rem;
98
+ height: 1rem;
99
+ background-color: var(--color-on-surface-variant);
100
+ border-radius: var(--radius-full);
101
+ transition: all 150ms ease-in-out;
102
+ }
103
+
104
+ .toggle:checked {
105
+ background-color: var(--toggle-color);
106
+ }
107
+
108
+ .toggle:checked::after {
109
+ inset-inline-start: calc(100% - 1.25rem);
110
+ background-color: var(--dm-validation-content, var(--color-primary-content));
111
+ }
112
+
113
+ .toggle:focus-visible {
114
+ outline: none;
115
+ box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 20%, transparent);
116
+ }
117
+
118
+ .toggle:disabled {
119
+ cursor: not-allowed;
120
+ opacity: 0.5;
121
+ }
122
+
123
+ /* Toggle Sizes */
124
+ .toggle-xs {
125
+ width: 2rem;
126
+ height: 1rem;
127
+ }
128
+
129
+ .toggle-xs::after {
130
+ width: 0.625rem;
131
+ height: 0.625rem;
132
+ inset-inline-start: 0.1875rem;
133
+ }
134
+
135
+ .toggle-xs:checked::after {
136
+ inset-inline-start: calc(100% - 0.8125rem);
137
+ }
138
+
139
+ .toggle-sm {
140
+ width: 2.5rem;
141
+ height: 1.25rem;
142
+ }
143
+
144
+ .toggle-sm::after {
145
+ width: 0.75rem;
146
+ height: 0.75rem;
147
+ }
148
+
149
+ .toggle-lg {
150
+ width: 4rem;
151
+ height: 2rem;
152
+ }
153
+
154
+ .toggle-lg::after {
155
+ width: 1.5rem;
156
+ height: 1.5rem;
157
+ }
158
+
159
+ .toggle-sm:checked::after { inset-inline-start: calc(100% - 1rem); }
160
+ .toggle-lg:checked::after { inset-inline-start: calc(100% - 1.75rem); }
161
+ .toggle-primary { --toggle-color: var(--color-primary); }
162
+ .toggle-secondary { --toggle-color: var(--color-secondary); }
163
+ .toggle-tertiary { --toggle-color: var(--color-tertiary); }
164
+ .toggle-info { --toggle-color: var(--color-info); }
165
+ .toggle-success { --toggle-color: var(--color-success); }
166
+ .toggle-warning { --toggle-color: var(--color-warning); }
167
+ .toggle-error { --toggle-color: var(--color-error); }
168
+ .toggle-ghost { border-color: transparent; }
169
+ @media (prefers-reduced-motion: reduce) { .toggle, .toggle::after { transition: none; } }
170
+ }
171
+
6
172
 
7
173
  @layer components {
8
174
  /* Base toggle button */
@@ -30,93 +196,93 @@
30
196
  }
31
197
 
32
198
  /* Active/Selected state */
33
- .toggle-btn-active,
34
- .toggle-btn.active {
199
+ :is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
200
+ .toggle-btn.active:not([aria-pressed]) {
35
201
  color: var(--color-on-primary-container);
36
202
  background-color: var(--color-primary-container);
37
203
  border-color: var(--color-primary);
38
204
  }
39
205
 
40
- .toggle-btn-active:hover:not(:disabled),
41
- .toggle-btn.active:hover:not(:disabled) {
206
+ :is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]):hover:not(:disabled),
207
+ .toggle-btn.active:not([aria-pressed]):hover:not(:disabled) {
42
208
  background-color: color-mix(in oklch, var(--color-primary-container) 90%, transparent);
43
209
  }
44
210
 
45
211
  /* Primary variant */
46
- .toggle-btn-primary.toggle-btn-active,
47
- .toggle-btn-primary.active {
212
+ .toggle-btn-primary:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
213
+ .toggle-btn-primary.active:not([aria-pressed]) {
48
214
  color: var(--color-on-primary-container);
49
215
  background-color: var(--color-primary-container);
50
216
  border-color: var(--color-primary);
51
217
  }
52
218
 
53
219
  /* Secondary variant */
54
- .toggle-btn-secondary.toggle-btn-active,
55
- .toggle-btn-secondary.active {
220
+ .toggle-btn-secondary:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
221
+ .toggle-btn-secondary.active:not([aria-pressed]) {
56
222
  color: var(--color-on-secondary-container);
57
223
  background-color: var(--color-secondary-container);
58
224
  border-color: var(--color-secondary);
59
225
  }
60
226
 
61
227
  /* Tertiary variant */
62
- .toggle-btn-tertiary.toggle-btn-active,
63
- .toggle-btn-tertiary.active {
228
+ .toggle-btn-tertiary:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
229
+ .toggle-btn-tertiary.active:not([aria-pressed]) {
64
230
  color: var(--color-on-tertiary-container);
65
231
  background-color: var(--color-tertiary-container);
66
232
  border-color: var(--color-tertiary);
67
233
  }
68
234
 
69
235
  /* Info variant */
70
- .toggle-btn-info.toggle-btn-active,
71
- .toggle-btn-info.active {
236
+ .toggle-btn-info:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
237
+ .toggle-btn-info.active:not([aria-pressed]) {
72
238
  color: var(--color-on-info-container);
73
239
  background-color: var(--color-info-container);
74
240
  border-color: var(--color-info);
75
241
  }
76
242
 
77
243
  /* Success variant */
78
- .toggle-btn-success.toggle-btn-active,
79
- .toggle-btn-success.active {
244
+ .toggle-btn-success:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
245
+ .toggle-btn-success.active:not([aria-pressed]) {
80
246
  color: var(--color-on-success-container);
81
247
  background-color: var(--color-success-container);
82
248
  border-color: var(--color-success);
83
249
  }
84
250
 
85
251
  /* Warning variant */
86
- .toggle-btn-warning.toggle-btn-active,
87
- .toggle-btn-warning.active {
252
+ .toggle-btn-warning:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
253
+ .toggle-btn-warning.active:not([aria-pressed]) {
88
254
  color: var(--color-on-warning-container);
89
255
  background-color: var(--color-warning-container);
90
256
  border-color: var(--color-warning);
91
257
  }
92
258
 
93
259
  /* Error variant */
94
- .toggle-btn-error.toggle-btn-active,
95
- .toggle-btn-error.active {
260
+ .toggle-btn-error:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
261
+ .toggle-btn-error.active:not([aria-pressed]) {
96
262
  color: var(--color-on-error-container);
97
263
  background-color: var(--color-error-container);
98
264
  border-color: var(--color-error);
99
265
  }
100
266
 
101
267
  /* Accent variant */
102
- .toggle-btn-accent.toggle-btn-active,
103
- .toggle-btn-accent.active {
268
+ .toggle-btn-accent:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
269
+ .toggle-btn-accent.active:not([aria-pressed]) {
104
270
  color: var(--color-on-surface);
105
271
  background-color: color-mix(in oklch, var(--color-accent) 15%, var(--color-surface));
106
272
  border-color: var(--color-accent);
107
273
  }
108
274
 
109
275
  /* Neutral variant */
110
- .toggle-btn-neutral.toggle-btn-active,
111
- .toggle-btn-neutral.active {
276
+ .toggle-btn-neutral:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
277
+ .toggle-btn-neutral.active:not([aria-pressed]) {
112
278
  color: var(--color-on-surface);
113
279
  background-color: color-mix(in oklch, var(--color-neutral) 15%, var(--color-surface));
114
280
  border-color: var(--color-neutral);
115
281
  }
116
282
 
117
283
  /* Base variant */
118
- .toggle-btn-base.toggle-btn-active,
119
- .toggle-btn-base.active {
284
+ .toggle-btn-base:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
285
+ .toggle-btn-base.active:not([aria-pressed]) {
120
286
  color: var(--color-base-content);
121
287
  background-color: var(--color-base-200);
122
288
  border-color: var(--color-base-content);
@@ -245,8 +411,8 @@
245
411
  border-radius: var(--radius-xs);
246
412
  }
247
413
 
248
- .toggle-segmented .toggle-btn.toggle-btn-active,
249
- .toggle-segmented .toggle-btn.active {
414
+ .toggle-segmented .toggle-btn:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
415
+ .toggle-segmented .toggle-btn.active:not([aria-pressed]) {
250
416
  background-color: var(--color-surface-variant);
251
417
  border-color: transparent;
252
418
  box-shadow: var(--shadow-sm);
@@ -258,8 +424,8 @@
258
424
  padding: 0.5rem 1rem;
259
425
  }
260
426
 
261
- .toggle-chip.toggle-btn-active,
262
- .toggle-chip.active {
427
+ .toggle-chip:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
428
+ .toggle-chip.active:not([aria-pressed]) {
263
429
  background-color: var(--color-primary-container);
264
430
  border-color: var(--color-primary-container);
265
431
  }
@@ -269,8 +435,8 @@
269
435
  background-color: transparent;
270
436
  }
271
437
 
272
- .toggle-outlined.toggle-btn-active,
273
- .toggle-outlined.active {
438
+ .toggle-outlined:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
439
+ .toggle-outlined.active:not([aria-pressed]) {
274
440
  background-color: color-mix(in oklch, var(--color-primary) 10%, transparent);
275
441
  }
276
442
 
@@ -280,8 +446,8 @@
280
446
  border: none;
281
447
  }
282
448
 
283
- .toggle-filled.toggle-btn-active,
284
- .toggle-filled.active {
449
+ .toggle-filled:is(.toggle-btn-active:not([aria-pressed]), .toggle-btn[aria-pressed="true"]),
450
+ .toggle-filled.active:not([aria-pressed]) {
285
451
  background-color: var(--color-primary);
286
452
  color: var(--color-primary-content);
287
453
  }