@duskmoon-dev/core 1.19.3 → 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 (101) 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/form-group.css +95 -204
  18. package/dist/components/form.css +1551 -421
  19. package/dist/components/index.css +5991 -6060
  20. package/dist/components/input.css +114 -31
  21. package/dist/components/kbd.css +28 -0
  22. package/dist/components/list.css +22 -3
  23. package/dist/components/otp-input.css +124 -6
  24. package/dist/components/radio.css +70 -0
  25. package/dist/components/range.css +41 -0
  26. package/dist/components/rating.css +39 -12
  27. package/dist/components/select.css +74 -0
  28. package/dist/components/stat.css +40 -0
  29. package/dist/components/switch.css +70 -0
  30. package/dist/components/table.css +8 -0
  31. package/dist/components/textarea.css +74 -0
  32. package/dist/components/timeline.css +40 -25
  33. package/dist/components/toggle-switch.css +165 -0
  34. package/dist/components/toggle.css +198 -32
  35. package/dist/components/validator.css +66 -0
  36. package/dist/effects/aura.css +22 -0
  37. package/dist/effects/hover-3d.css +8 -0
  38. package/dist/effects/hover-gallery.css +14 -0
  39. package/dist/effects/index.css +65 -0
  40. package/dist/effects/text-rotate.css +16 -0
  41. package/dist/esm/components/accordion.js +33 -9
  42. package/dist/esm/components/autocomplete.js +67 -13
  43. package/dist/esm/components/avatar.js +39 -15
  44. package/dist/esm/components/badge.js +15 -3
  45. package/dist/esm/components/card.js +12 -0
  46. package/dist/esm/components/carousel.d.ts +5 -0
  47. package/dist/esm/components/carousel.js +37 -0
  48. package/dist/esm/components/chat.js +15 -11
  49. package/dist/esm/components/checkbox.js +84 -14
  50. package/dist/esm/components/collapse.js +67 -16
  51. package/dist/esm/components/countdown.d.ts +5 -0
  52. package/dist/esm/components/countdown.js +31 -0
  53. package/dist/esm/components/datepicker.js +77 -1
  54. package/dist/esm/components/diff.d.ts +5 -0
  55. package/dist/esm/components/diff.js +42 -0
  56. package/dist/esm/components/file-input.d.ts +5 -0
  57. package/dist/esm/components/file-input.js +116 -0
  58. package/dist/esm/components/filter-group.d.ts +5 -0
  59. package/dist/esm/components/filter-group.js +461 -0
  60. package/dist/esm/components/form-group.js +96 -205
  61. package/dist/esm/components/form.js +1539 -409
  62. package/dist/esm/components/input.js +115 -32
  63. package/dist/esm/components/kbd.d.ts +5 -0
  64. package/dist/esm/components/kbd.js +37 -0
  65. package/dist/esm/components/list.js +22 -3
  66. package/dist/esm/components/otp-input.js +125 -7
  67. package/dist/esm/components/radio.js +71 -1
  68. package/dist/esm/components/range.d.ts +5 -0
  69. package/dist/esm/components/range.js +50 -0
  70. package/dist/esm/components/rating.js +39 -12
  71. package/dist/esm/components/select.js +75 -1
  72. package/dist/esm/components/stat.d.ts +5 -0
  73. package/dist/esm/components/stat.js +49 -0
  74. package/dist/esm/components/switch.js +71 -1
  75. package/dist/esm/components/table.js +8 -0
  76. package/dist/esm/components/textarea.js +75 -1
  77. package/dist/esm/components/timeline.js +40 -25
  78. package/dist/esm/components/toggle-switch.d.ts +5 -0
  79. package/dist/esm/components/toggle-switch.js +174 -0
  80. package/dist/esm/components/toggle.js +198 -32
  81. package/dist/esm/components/validator.d.ts +5 -0
  82. package/dist/esm/components/validator.js +75 -0
  83. package/dist/esm/effects/aura.d.ts +5 -0
  84. package/dist/esm/effects/aura.js +31 -0
  85. package/dist/esm/effects/hover-3d.d.ts +5 -0
  86. package/dist/esm/effects/hover-3d.js +17 -0
  87. package/dist/esm/effects/hover-gallery.d.ts +5 -0
  88. package/dist/esm/effects/hover-gallery.js +23 -0
  89. package/dist/esm/effects/index.d.ts +5 -0
  90. package/dist/esm/effects/index.js +74 -0
  91. package/dist/esm/effects/text-rotate.d.ts +5 -0
  92. package/dist/esm/effects/text-rotate.js +25 -0
  93. package/dist/index.css +5977 -6047
  94. package/dist/index.min.css +1 -1
  95. package/dist/standalone/duskmoonui-themes.css +1 -1
  96. package/dist/standalone/duskmoonui.css +6967 -7028
  97. package/dist/standalone/duskmoonui.js +11 -12
  98. package/dist/standalone/duskmoonui.mjs +11 -12
  99. package/dist/types/types/plugin.d.ts +1 -1
  100. package/dist/types/types/plugin.d.ts.map +1 -1
  101. package/package.json +98 -1
@@ -1,3 +1,113 @@
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
+
70
+ @layer components {
71
+ .file-input {
72
+ display: block;
73
+ box-sizing: border-box;
74
+ width: 100%;
75
+ min-width: 0;
76
+ padding: 0;
77
+ font: inherit;
78
+ color: var(--color-on-surface);
79
+ background-color: var(--color-surface);
80
+ border: 1px solid currentColor;
81
+ border-radius: var(--radius-sm);
82
+ cursor: pointer;
83
+ }
84
+ .file-input::file-selector-button {
85
+ padding: 0.75rem 1rem;
86
+ margin-inline-end: 0.75rem;
87
+ font: inherit;
88
+ color: inherit;
89
+ background-color: var(--color-surface-container);
90
+ border: none;
91
+ border-inline-end: 1px solid currentColor;
92
+ cursor: inherit;
93
+ }
94
+ .file-input:hover:not(:disabled)::file-selector-button { background-color: var(--color-surface-container-high); }
95
+ .file-input:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 20%, transparent); }
96
+ .file-input:disabled { opacity: 0.5; cursor: not-allowed; }
97
+ .file-input-xs::file-selector-button { padding: 0.375rem 0.625rem; }
98
+ .file-input-sm::file-selector-button { padding: 0.5rem 0.75rem; }
99
+ .file-input-lg::file-selector-button { padding: 1rem 1.25rem; }
100
+ .file-input-primary { color: var(--color-primary); }
101
+ .file-input-secondary { color: var(--color-secondary); }
102
+ .file-input-tertiary { color: var(--color-tertiary); }
103
+ .file-input-info { color: var(--color-info); }
104
+ .file-input-success { color: var(--color-success); }
105
+ .file-input-warning { color: var(--color-warning); }
106
+ .file-input-error { color: var(--color-error); }
107
+ .file-input-ghost { border-color: transparent; }
108
+ }
109
+
110
+
1
111
  /**
2
112
  * Input Component Styles
3
113
  * DuskMoonUI - Material Design 3 inspired input system
@@ -6,6 +116,8 @@
6
116
  @layer components {
7
117
  /* Base Input */
8
118
  .input {
119
+ box-sizing: border-box;
120
+ min-width: 0;
9
121
  display: flex;
10
122
  width: 100%;
11
123
  padding: 0.75rem 1rem;
@@ -39,6 +151,7 @@
39
151
  }
40
152
 
41
153
  /* Bordered Input */
154
+ .input-outlined { background-color: transparent; border-color: var(--color-outline); }
42
155
  .input-bordered {
43
156
  border-color: var(--color-outline);
44
157
  background-color: var(--color-surface);
@@ -89,6 +202,7 @@
89
202
  }
90
203
 
91
204
  /* Ghost Input */
205
+ .input-md { height: 3rem; }
92
206
  .input-ghost {
93
207
  background-color: transparent;
94
208
  border-color: transparent;
@@ -167,37 +281,6 @@
167
281
  border-left: none;
168
282
  }
169
283
 
170
- /* File Input */
171
- .file-input {
172
- display: flex;
173
- width: 100%;
174
- padding: 0;
175
- background-color: var(--color-surface);
176
- border: 1px solid var(--color-outline);
177
- border-radius: var(--radius-sm);
178
- overflow: hidden;
179
- cursor: pointer;
180
- }
181
-
182
- .file-input::file-selector-button {
183
- padding: 0.75rem 1rem;
184
- background-color: var(--color-surface-container);
185
- color: var(--color-on-surface);
186
- border: none;
187
- border-right: 1px solid var(--color-outline);
188
- cursor: pointer;
189
- transition: background-color 150ms ease-in-out;
190
- }
191
-
192
- .file-input::file-selector-button:hover {
193
- background-color: var(--color-surface-container-high);
194
- }
195
-
196
- .file-input:focus-visible {
197
- box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 20%, transparent);
198
- outline: none;
199
- }
200
-
201
284
  /* Reduce Motion */
202
285
  @media (prefers-reduced-motion: reduce) {
203
286
  .input,
@@ -0,0 +1,28 @@
1
+ @layer components {
2
+ .kbd {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ min-inline-size: 1.5rem;
7
+ padding: 0.125rem 0.375rem;
8
+ font: 0.875rem ui-monospace, monospace;
9
+ line-height: 1.5;
10
+ color: var(--color-on-surface);
11
+ background: var(--color-surface-container);
12
+ border: 1px solid currentColor;
13
+ border-block-end-width: 2px;
14
+ border-radius: var(--radius-sm);
15
+ vertical-align: middle;
16
+ }
17
+ .kbd-xs { font-size: 0.625rem; }
18
+ .kbd-sm { font-size: 0.75rem; }
19
+ .kbd-lg { font-size: 1rem; padding: 0.25rem 0.5rem; }
20
+ .kbd-primary { color: var(--color-primary); }
21
+ .kbd-secondary { color: var(--color-secondary); }
22
+ .kbd-tertiary { color: var(--color-tertiary); }
23
+ .kbd-info { color: var(--color-info); }
24
+ .kbd-success { color: var(--color-success); }
25
+ .kbd-warning { color: var(--color-warning); }
26
+ .kbd-error { color: var(--color-error); }
27
+ .kbd-ghost { border-color: transparent; }
28
+ }
@@ -63,14 +63,18 @@
63
63
  min-width: 0;
64
64
  }
65
65
 
66
- .list-item-title {
66
+ .list-item-title,
67
+ .list-item-text {
68
+ overflow-wrap: anywhere;
67
69
  font-size: 1rem;
68
70
  font-weight: 500;
69
71
  line-height: 1.5rem;
70
72
  color: var(--color-on-surface);
71
73
  }
72
74
 
73
- .list-item-subtitle {
75
+ .list-item-subtitle,
76
+ .list-item-secondary {
77
+ overflow-wrap: anywhere;
74
78
  font-size: 0.875rem;
75
79
  line-height: 1.25rem;
76
80
  color: var(--color-on-surface-variant);
@@ -84,6 +88,21 @@
84
88
  color: var(--color-on-surface-variant);
85
89
  }
86
90
 
91
+ .list-item-leading, .list-item-trailing { display: inline-flex; align-items: center; gap: 0.5rem; flex: none; }
92
+ .list-item-trailing { margin-inline-start: auto; }
93
+ .list-item-two-line, .list-item-three-line { align-items: flex-start; }
94
+ .list-item-interactive { cursor: pointer; }
95
+ .list-item-interactive:hover { background: var(--color-surface-container); }
96
+ .list-item-interactive:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
97
+ .list-item-active-primary { background: var(--color-primary-container); color: var(--color-on-primary-container); }
98
+ .list-item-active-secondary { background: var(--color-secondary-container); color: var(--color-on-secondary-container); }
99
+ .list-item-active-tertiary { background: var(--color-tertiary-container); color: var(--color-on-tertiary-container); }
100
+ .list-comfortable > .list-item { padding-block: 1rem; }
101
+ .list-surface-container { background: var(--color-surface-container); }
102
+ .list-surface-container-low { background: var(--color-surface-container-low); }
103
+ .list-surface-container-high { background: var(--color-surface-container-high); }
104
+ .list-nested { margin-inline-start: 1rem; }
105
+
87
106
  /* List Variants */
88
107
  .list-bordered {
89
108
  border: 1px solid var(--color-outline);
@@ -120,7 +139,7 @@
120
139
  }
121
140
 
122
141
  .list-divider-inset {
123
- margin-left: 3.5rem;
142
+ margin-inline-start: 3.5rem;
124
143
  }
125
144
 
126
145
  /* List Subheader */
@@ -1,11 +1,82 @@
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
+
1
69
  /**
2
70
  * OTP Input Component Styles
3
71
  * DuskMoonUI - One-Time Password input for verification codes
4
72
  */
5
73
 
6
74
  @layer components {
75
+ :where(.otp-input, .otp-input-field) { box-sizing: border-box; }
76
+
7
77
  /* Base OTP Container */
8
78
  .otp-input {
79
+ flex-wrap: wrap;
9
80
  display: inline-flex;
10
81
  align-items: center;
11
82
  gap: 0.5rem;
@@ -54,7 +125,8 @@
54
125
  }
55
126
 
56
127
  /* Separator */
57
- .otp-separator {
128
+ .otp-separator,
129
+ .otp-input-separator {
58
130
  display: flex;
59
131
  align-items: center;
60
132
  justify-content: center;
@@ -108,14 +180,16 @@
108
180
  }
109
181
 
110
182
  /* Underline Variant */
111
- .otp-input-underline .otp-input-field {
183
+ .otp-input-underline .otp-input-field,
184
+ .otp-input-underlined .otp-input-field {
112
185
  background-color: transparent;
113
186
  border: none;
114
187
  border-bottom: 2px solid var(--color-outline);
115
188
  border-radius: 0;
116
189
  }
117
190
 
118
- .otp-input-underline .otp-input-field:focus {
191
+ .otp-input-underline .otp-input-field:focus,
192
+ .otp-input-underlined .otp-input-field:focus {
119
193
  border-bottom-color: var(--color-primary);
120
194
  box-shadow: none;
121
195
  }
@@ -232,17 +306,61 @@
232
306
  color: var(--color-on-surface);
233
307
  }
234
308
 
235
- .otp-helper {
309
+ .otp-helper,
310
+ .otp-input-helper {
236
311
  font-size: 0.75rem;
237
312
  color: var(--color-on-surface-variant);
238
313
  }
239
314
 
240
- .otp-error-message {
315
+ .otp-error-message,
316
+ .otp-input-helper-error {
241
317
  font-size: 0.75rem;
242
318
  color: var(--color-error);
243
319
  }
244
320
 
245
- /* Reduce Motion */
321
+ /* One complete native code. Length guides never create separate fields. */
322
+ .otp-code {
323
+ --otp-length: 6;
324
+ display: block;
325
+ box-sizing: border-box;
326
+ /* The extra two content pixels keep the trailing native caret in view. */
327
+ inline-size: calc(var(--otp-length) * 1ch + 1.5rem + 4px);
328
+ max-inline-size: 100%;
329
+ min-inline-size: 0;
330
+ padding: 0.75rem;
331
+ font: 1.5rem / 1.5 ui-monospace, monospace;
332
+ font-variant-numeric: tabular-nums;
333
+ direction: ltr;
334
+ text-align: start;
335
+ color: var(--color-on-surface);
336
+ caret-color: currentColor;
337
+ background-color: var(--color-surface);
338
+ border: 1px solid currentColor;
339
+ border-radius: var(--radius-sm);
340
+ }
341
+ .otp-code-4 { --otp-length: 4; }
342
+ .otp-code:focus { outline: 2px solid currentColor; outline-offset: 3px; }
343
+ .otp-code:disabled { opacity: 0.38; cursor: not-allowed; }
344
+ .otp-code-primary { color: var(--color-primary); }
345
+ .otp-code-secondary { color: var(--color-secondary); }
346
+ .otp-code-tertiary { color: var(--color-tertiary); }
347
+ .otp-code-info { color: var(--color-info); }
348
+ .otp-code-success { color: var(--color-success); }
349
+ .otp-code-warning { color: var(--color-warning); }
350
+ .otp-code-error { color: var(--color-error); }
351
+ .otp-input-helper-success { font-size: 0.75rem; color: var(--color-success); }
352
+
353
+ @supports (background-image: repeating-linear-gradient(to right, transparent 0 1ch)) {
354
+ .otp-code {
355
+ background-image: repeating-linear-gradient(to right, transparent 0 0.125ch, var(--color-outline-variant) 0.125ch 0.875ch, transparent 0.875ch 1ch);
356
+ background-size: calc(var(--otp-length) * 1ch) 3px;
357
+ background-repeat: no-repeat;
358
+ background-origin: content-box;
359
+ background-position: left bottom;
360
+ }
361
+ }
362
+ @media (forced-colors: active) { .otp-code { background-image: none; } }
363
+
246
364
  @media (prefers-reduced-motion: reduce) {
247
365
  .otp-input-field {
248
366
  transition: none;
@@ -1,3 +1,71 @@
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
+
1
69
  /**
2
70
  * Radio Component Styles
3
71
  * DuskMoonUI - Material Design 3 inspired radio button
@@ -7,6 +75,8 @@
7
75
  */
8
76
 
9
77
  @layer components {
78
+ :where(.radio) { box-sizing: border-box; }
79
+
10
80
  /* Base Radio - applied directly to input[type="radio"] */
11
81
  .radio {
12
82
  --radio-size: 1.25rem;
@@ -0,0 +1,41 @@
1
+ @layer components {
2
+ .range {
3
+ --range-thumb-size: 1.25rem;
4
+ appearance: none;
5
+ display: block;
6
+ box-sizing: border-box;
7
+ width: 100%;
8
+ min-width: 0;
9
+ height: var(--range-thumb-size);
10
+ margin: 0;
11
+ color: var(--color-on-surface);
12
+ accent-color: currentColor;
13
+ background: transparent;
14
+ cursor: pointer;
15
+ }
16
+ .range::-webkit-slider-runnable-track { height: 0.5rem; background: var(--color-surface-container-highest); border-radius: var(--radius-xs); }
17
+ .range::-moz-range-track { height: 0.5rem; background: var(--color-surface-container-highest); border-radius: var(--radius-xs); }
18
+ .range::-webkit-slider-thumb {
19
+ appearance: none;
20
+ width: var(--range-thumb-size);
21
+ height: var(--range-thumb-size);
22
+ margin-top: calc((0.5rem - var(--range-thumb-size)) / 2);
23
+ background: currentColor;
24
+ border: 1px solid currentColor;
25
+ border-radius: var(--radius-full);
26
+ }
27
+ .range::-moz-range-thumb { width: var(--range-thumb-size); height: var(--range-thumb-size); background: currentColor; border: 1px solid currentColor; border-radius: var(--radius-full); }
28
+ .range:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
29
+ .range:disabled { opacity: 0.5; cursor: not-allowed; }
30
+ .range-xs { --range-thumb-size: 0.875rem; }
31
+ .range-sm { --range-thumb-size: 1rem; }
32
+ .range-lg { --range-thumb-size: 1.5rem; }
33
+ .range-primary { color: var(--color-primary); }
34
+ .range-secondary { color: var(--color-secondary); }
35
+ .range-tertiary { color: var(--color-tertiary); }
36
+ .range-info { color: var(--color-info); }
37
+ .range-success { color: var(--color-success); }
38
+ .range-warning { color: var(--color-warning); }
39
+ .range-error { color: var(--color-error); }
40
+ @media (forced-colors: active) { .range { appearance: auto; } .range:focus-visible { outline-color: Highlight; } }
41
+ }
@@ -175,23 +175,23 @@
175
175
  }
176
176
 
177
177
  /* Hover Preview */
178
- .rating:not(.rating-readonly) .rating-item:hover ~ .rating-item {
178
+ .rating:not(.rating-readonly):not(.rating-native) .rating-item:hover ~ .rating-item {
179
179
  color: var(--color-outline);
180
180
  }
181
181
 
182
- .rating:not(.rating-readonly):hover .rating-item {
182
+ .rating:not(.rating-readonly):not(.rating-native):hover .rating-item {
183
183
  color: var(--color-warning);
184
184
  }
185
185
 
186
- .rating-primary:not(.rating-readonly):hover .rating-item { color: var(--color-primary); }
187
- .rating-secondary:not(.rating-readonly):hover .rating-item { color: var(--color-secondary); }
188
- .rating-tertiary:not(.rating-readonly):hover .rating-item { color: var(--color-tertiary); }
189
- .rating-info:not(.rating-readonly):hover .rating-item { color: var(--color-info); }
190
- .rating-success:not(.rating-readonly):hover .rating-item { color: var(--color-success); }
191
- .rating-error:not(.rating-readonly):hover .rating-item { color: var(--color-error); }
192
- .rating-accent:not(.rating-readonly):hover .rating-item { color: var(--color-accent); }
193
- .rating-neutral:not(.rating-readonly):hover .rating-item { color: var(--color-neutral); }
194
- .rating-base:not(.rating-readonly):hover .rating-item { color: var(--color-base-content); }
186
+ .rating-primary:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-primary); }
187
+ .rating-secondary:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-secondary); }
188
+ .rating-tertiary:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-tertiary); }
189
+ .rating-info:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-info); }
190
+ .rating-success:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-success); }
191
+ .rating-error:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-error); }
192
+ .rating-accent:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-accent); }
193
+ .rating-neutral:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-neutral); }
194
+ .rating-base:not(.rating-readonly):not(.rating-native):hover .rating-item { color: var(--color-base-content); }
195
195
 
196
196
  /* Focus Visible — use warning (the default fill color) for the base ring;
197
197
  per-variant overrides below match the active fill color for each variant. */
@@ -266,7 +266,34 @@
266
266
  }
267
267
  }
268
268
 
269
- /* Reduce Motion */
269
+ /* Native radio pattern: selection is authoritative, not .filled/.active. */
270
+ .rating-native { --rating-color: var(--color-warning); flex-wrap: wrap; max-inline-size: 100%; }
271
+ .rating-native.rating-primary { --rating-color: var(--color-primary); }
272
+ .rating-native.rating-secondary { --rating-color: var(--color-secondary); }
273
+ .rating-native.rating-tertiary { --rating-color: var(--color-tertiary); }
274
+ .rating-native.rating-info { --rating-color: var(--color-info); }
275
+ .rating-native.rating-success { --rating-color: var(--color-success); }
276
+ .rating-native.rating-warning { --rating-color: var(--color-warning); }
277
+ .rating-native.rating-error { --rating-color: var(--color-error); }
278
+ .rating-native .rating-item { position: relative; padding: 0.25rem; }
279
+ .rating-native .rating-input {
280
+ inset: 0;
281
+ width: 100%;
282
+ height: 100%;
283
+ margin: 0;
284
+ cursor: inherit;
285
+ }
286
+ .rating-native .rating-item:is(.filled, .active, :hover),
287
+ .rating-native .rating-item { color: var(--color-outline); transform: none; }
288
+ .rating-native .rating-item:has(.rating-input:checked),
289
+ .rating-native .rating-item:not(.rating-none):has(~ .rating-item .rating-input:checked) { color: var(--rating-color); }
290
+ .rating-native .rating-item:has(.rating-input:focus-visible) { outline: 2px solid currentColor; outline-offset: 2px; border-radius: var(--radius-xs); }
291
+ .rating-native .rating-item:has(.rating-input:disabled) { opacity: 0.38; cursor: not-allowed; }
292
+ .rating-native .rating-none { color: var(--color-on-surface); font-size: 0.875rem; }
293
+ @media (forced-colors: active) {
294
+ .rating-native .rating-item:has(.rating-input:checked) { outline: 2px solid Highlight; }
295
+ }
296
+
270
297
  @media (prefers-reduced-motion: reduce) {
271
298
  .rating-item,
272
299
  .rating-animated .rating-item {