@devslab/dds-css 0.25.1 → 0.27.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.
package/README.ko.md CHANGED
@@ -20,6 +20,9 @@ EmptyState** (BottomSheet은 Dialog의 네이티브 짝 — Phase 3).
20
20
  - 다크 모드는 공짜: `<html>` 또는 임의 서브트리에 `data-theme="dark"`
21
21
  (`data-theme="light"`로 되고정). 어떤 컴포넌트에도 다크 분기가 없습니다.
22
22
  - 개별 임포트: `@devslab/dds-css/components/button.css` 등.
23
+ - 진행 중인 버튼(`aria-busy="true"` + `.dds-spinner` 자식)은 크기와 이름을
24
+ 그대로 유지합니다. 라벨은 지우지 않고 숨기고, 스피너를 그 위에 겹쳐
25
+ 그립니다(D-043).
23
26
  - 컴포넌트별 마크업 계약·상태·접근성 체크리스트·do/don't:
24
27
  [`docs/components.ko.md`](../../docs/components.ko.md).
25
28
  라이브 레퍼런스: `preview/components.html` (먼저 `pnpm build`).
package/README.md CHANGED
@@ -21,6 +21,9 @@ the native counterpart of Dialog — Phase 3).
21
21
  - Dark mode is free: `data-theme="dark"` on `<html>` or any subtree
22
22
  (`data-theme="light"` pins back). No component has a dark branch.
23
23
  - Per-component imports: `@devslab/dds-css/components/button.css` etc.
24
+ - A loading button (`aria-busy="true"` + a `.dds-spinner` child) keeps its
25
+ size and name: the label is hidden, not removed, and the spinner is drawn
26
+ over it (D-043).
24
27
  - Markup contracts, states, accessibility checklists and do/don't per
25
28
  component: [`docs/components.md`](../../docs/components.md).
26
29
  Live reference: `preview/components.html` (run `pnpm build` first).
@@ -2,7 +2,7 @@
2
2
  * Variants: primary / secondary / ghost / danger. Sizes: sm / md(default) / lg.
3
3
  * Control heights come from the spacing scale (32/40/48); type from the typo
4
4
  * scale (label for sm/md, body-1 size for lg). Loading = aria-busy="true" with
5
- * a .dds-spinner child. */
5
+ * a .dds-spinner child; the button keeps its size while loading (D-043). */
6
6
 
7
7
  .dds-btn {
8
8
  appearance: none;
@@ -29,6 +29,10 @@
29
29
  -webkit-tap-highlight-color: transparent;
30
30
  white-space: normal;
31
31
  overflow-wrap: anywhere;
32
+ /* Containing block for the loading spinner, which is centred over the
33
+ * label rather than placed beside it. Set here, not only while loading,
34
+ * so a consumer's own position (a fixed action button) still wins. */
35
+ position: relative;
32
36
  }
33
37
 
34
38
  .dds-btn:focus-visible {
@@ -37,13 +41,36 @@
37
41
  }
38
42
 
39
43
  /* opacity is allowed for disabled only (spec §4.2) */
40
- .dds-btn[disabled],
41
- .dds-btn[aria-busy="true"] {
44
+ .dds-btn[disabled] {
42
45
  opacity: 0.45;
43
46
  cursor: not-allowed;
44
47
  }
48
+
49
+ /* Loading (D-043). The button keeps the exact box its label gives it, so the
50
+ * controls beside it do not move and a wrapped label does not wrap again
51
+ * when progress starts. The label stays in the layout and in the
52
+ * accessibility tree (its accessible name does not change) but is not
53
+ * painted: the text fill is transparent and element children (icons) are
54
+ * fully transparent. The spinner is taken out of the flow and centred over
55
+ * the label; it keeps the button's text color (currentColor).
56
+ *
57
+ * Not dimmed: the spinner is the only thing painted, and at the disabled
58
+ * 0.45 opacity it drops to about 2:1 against the button (WCAG 1.4.11 asks
59
+ * 3:1). The framework Buttons also set [disabled], so this rule comes after
60
+ * the one above to put the opacity back. */
45
61
  .dds-btn[aria-busy="true"] {
62
+ opacity: 1;
63
+ cursor: not-allowed;
46
64
  pointer-events: none;
65
+ -webkit-text-fill-color: transparent;
66
+ }
67
+ .dds-btn[aria-busy="true"] > :not(.dds-spinner) {
68
+ opacity: 0;
69
+ }
70
+ .dds-btn[aria-busy="true"] > .dds-spinner {
71
+ position: absolute;
72
+ inset: 0;
73
+ margin: auto;
47
74
  }
48
75
 
49
76
  /* variants ---------------------------------------------------------------- */
@@ -115,3 +142,22 @@
115
142
  min-block-size: 44px;
116
143
  }
117
144
  }
145
+
146
+ /* Forced colors repaint every text color, transparent included, so the
147
+ * hidden label would come back on top of the spinner. A loading button opts
148
+ * out and paints itself with system colors: the face of a button, the
149
+ * disabled border, and the spinner in button text. No transition: opted out,
150
+ * the background would fade from the brand color it no longer forces. */
151
+ @media (forced-colors: active) {
152
+ .dds-btn[aria-busy="true"] {
153
+ forced-color-adjust: none;
154
+ background-color: ButtonFace;
155
+ border-color: GrayText;
156
+ color: ButtonText;
157
+ transition: none;
158
+ }
159
+ .dds-btn[aria-busy="true"] > .dds-spinner {
160
+ forced-color-adjust: none;
161
+ color: ButtonText;
162
+ }
163
+ }
@@ -0,0 +1,56 @@
1
+ /* Ark DatePicker interaction with DDS visual tokens. */
2
+ .dds-date-picker-control {display:flex;align-items:stretch;gap:var(--dds-space-8);}
3
+ .dds-date-picker-control input {min-width:0;}
4
+ .dds-date-picker-control .dds-btn {flex-shrink:0;min-width:calc(var(--dds-space-40) + var(--dds-space-4));padding-inline:calc(var(--dds-space-8) + var(--dds-space-2));}
5
+ .dds-date-picker-positioner {z-index:var(--dds-overlay-z-index,1001);}
6
+ .dds-date-picker-content {width:calc(var(--dds-space-40) * 8);max-width:calc(100vw - var(--dds-space-32));max-height:min(calc(100dvh - var(--dds-space-32)),var(--available-height,100dvh));overflow:auto;box-sizing:border-box;padding:var(--dds-space-16);background:var(--dds-color-bg-default);color:var(--dds-color-text-primary);border:calc(var(--dds-space-2) / 2) solid var(--dds-color-border-default);border-radius:var(--dds-radius-md);font-family:var(--dds-font-family-sans);font-size:var(--dds-typo-body-2-font-size);}
7
+ .dds-date-picker-header {display:flex;align-items:center;justify-content:space-between;gap:var(--dds-space-12);font-weight:600;margin-bottom:var(--dds-space-12);}
8
+ .dds-date-picker-table {width:100%;border-collapse:collapse;table-layout:fixed;}
9
+ .dds-date-picker-table th {font-size:var(--dds-space-12);font-weight:500;color:var(--dds-color-text-secondary);height:var(--dds-space-32);}
10
+ .dds-date-picker-table td {padding:0;text-align:center;}
11
+ .dds-date-picker-table [data-part="table-cell-trigger"] {display:flex;align-items:center;justify-content:center;width:100%;height:var(--dds-space-40);border:0;border-radius:var(--dds-radius-sm);font:inherit;color:inherit;background:transparent;cursor:pointer;}
12
+ .dds-date-picker-table [data-part="table-cell-trigger"]:hover {background:var(--dds-color-bg-brand-subtle);}
13
+ .dds-date-picker-table [data-outside-range] {color:var(--dds-color-text-secondary);}
14
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-selected] {background:var(--dds-color-bg-brand);color:var(--dds-color-text-on-brand);}
15
+ .dds-date-picker-table [data-today]:not([data-selected]) {text-decoration:underline;text-underline-offset:var(--dds-space-4);}
16
+ .dds-date-picker-content :focus-visible {outline:var(--dds-space-2) solid var(--dds-color-text-brand);outline-offset:var(--dds-space-2);}
17
+ .dds-date-picker-footer {display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--dds-space-12);margin-top:var(--dds-space-12);padding-top:var(--dds-space-12);border-top:calc(var(--dds-space-2) / 2) solid var(--dds-color-border-default);}
18
+
19
+ /* Text box shared by DatePicker, MonthPicker and the DateRangePicker pair: same var(--dds-space-40) DDS input look. */
20
+ .dds-date-picker-control > input,.dds-date-range-box {box-sizing:border-box;flex:1;min-inline-size:0;block-size:var(--dds-space-40);border:calc(var(--dds-space-2) / 2) solid var(--dds-color-border-strong);border-radius:var(--dds-radius-md);background:var(--dds-color-bg-default);color:var(--dds-color-text-primary);font-family:inherit;font-size:var(--dds-typo-body-2-font-size);font-variant-numeric:tabular-nums;}
21
+ .dds-date-picker-control > input {padding-inline:var(--dds-space-12);}
22
+ .dds-date-picker-control > input:focus-visible,.dds-date-range-box:focus-within {outline:var(--dds-space-2) solid var(--dds-color-border-focus);outline-offset:0;}
23
+ .dds-date-picker-control > input[aria-invalid="true"],.dds-date-range-box[data-invalid] {border-color:var(--dds-color-status-danger);}
24
+ .dds-date-picker-control > input:disabled,.dds-date-range-box[data-disabled] {opacity:.45;cursor:not-allowed;background:var(--dds-color-bg-subtle);}
25
+ .dds-date-picker-control > input::placeholder,.dds-date-range-input::placeholder {color:var(--dds-color-text-muted);}
26
+ .dds-date-range-box {display:flex;align-items:center;gap:var(--dds-space-8);padding-inline:var(--dds-space-12);}
27
+ .dds-date-range-box[hidden] {display:none;}
28
+ .dds-date-range-input {flex:1;inline-size:100%;min-inline-size:0;block-size:100%;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-variant-numeric:tabular-nums;outline:none;}
29
+ .dds-date-range-input:disabled {cursor:not-allowed;}
30
+ .dds-date-range-separator {flex:none;color:var(--dds-color-text-secondary);}
31
+ .dds-field__error {font-size:var(--dds-typo-caption-font-size);line-height:var(--dds-typo-caption-line-height);color:var(--dds-color-status-danger);}
32
+ @media (pointer:coarse) {.dds-date-picker-control > input,.dds-date-range-box {min-block-size:calc(var(--dds-space-40) + var(--dds-space-4));}}
33
+
34
+ /* Range selection: ends filled, days between tinted, hover previews the end being chosen. */
35
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-in-range] {background:var(--dds-color-bg-brand-subtle);color:var(--dds-color-text-primary);border-radius:0;}
36
+ .dds-date-picker-table [data-part="table-cell-trigger"]:is([data-range-start],[data-range-end]) {background:var(--dds-color-bg-brand);color:var(--dds-color-text-on-brand);border-radius:var(--dds-radius-sm);}
37
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-hover-range-end]:not([data-range-start]) {box-shadow:inset 0 0 0 var(--dds-space-2) var(--dds-color-bg-brand);border-radius:var(--dds-radius-sm);}
38
+ .dds-date-picker-presets {display:flex;flex-wrap:wrap;gap:var(--dds-space-8);flex:1 1 100%;}
39
+ .dds-date-picker-presets[hidden] {display:none;}
40
+ .dds-date-picker-footer > [data-part="clear-trigger"] {margin-inline-start:auto;}
41
+ /* Days and months outside min/max read as unavailable, not as ordinary choices. */
42
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-disabled] {opacity:.45;cursor:not-allowed;background:transparent;}
43
+
44
+ /* Month grid (MonthPicker): 3 columns × 4 rows, year paged in the header. */
45
+ .dds-date-picker-year {font-variant-numeric:tabular-nums;}
46
+ .dds-date-picker-table--months td {padding:var(--dds-space-4);}
47
+ .dds-date-picker-table--months [data-part="table-cell-trigger"] {height:var(--dds-space-40);}
48
+
49
+ /* Standalone Calendar shares the DatePicker view without popup positioning. */
50
+ .dds-calendar {display:block;}
51
+ .dds-date-picker--hidden > .dds-field__label {position:absolute;width:calc(var(--dds-space-2) / 2);height:calc(var(--dds-space-2) / 2);overflow:hidden;clip-path:inset(50%);}
52
+ .dds-date-picker--inside {position:relative;}
53
+ .dds-date-picker--inside > .dds-field__label {position:absolute;inset-inline-start:var(--dds-space-12);inset-block-start:calc(var(--dds-space-16) + var(--dds-space-2));z-index:1;pointer-events:none;}
54
+ .dds-date-picker--inside:focus-within > .dds-field__label,.dds-date-picker--inside[data-filled] > .dds-field__label {inset-block-start:var(--dds-space-2);font-size:var(--dds-typo-caption-font-size);}
55
+ .dds-date-picker--inside .dds-date-picker-control input {height:calc(var(--dds-space-40) + var(--dds-space-16));padding-block:var(--dds-space-20) var(--dds-space-4);}
56
+ .dds-date-picker--inside:not(:focus-within):not([data-filled]) input::placeholder {color:transparent;}
@@ -38,6 +38,15 @@
38
38
  cursor: not-allowed;
39
39
  }
40
40
 
41
+ /* Loading: the framework IconButtons swap the icon for a .dds-spinner, which
42
+ * the fixed box already centres. Same rule as .dds-btn (D-043): not dimmed,
43
+ * because a 0.45 spinner falls below 3:1 (danger is about 2.3:1). */
44
+ .dds-iconbtn[aria-busy="true"] {
45
+ opacity: 1;
46
+ cursor: not-allowed;
47
+ pointer-events: none;
48
+ }
49
+
41
50
  /* variants ---------------------------------------------------------------- */
42
51
 
43
52
  .dds-iconbtn--secondary {
@@ -27,8 +27,22 @@
27
27
  }
28
28
  }
29
29
 
30
+ /* base.css stops every dds- animation after one 0.01ms run under reduced
31
+ * motion. The :root prefix ties its specificity and this file loads after
32
+ * base.css, so the spinner keeps turning, at half speed, instead of freezing. */
30
33
  @media (prefers-reduced-motion: reduce) {
31
- .dds-spinner {
34
+ :root .dds-spinner {
32
35
  animation-duration: 1600ms;
36
+ animation-iteration-count: infinite;
37
+ }
38
+ }
39
+
40
+ /* Forced colors repaint the transparent top border too, which closes the gap
41
+ * and leaves a ring that looks still. Opt out and paint it in the system text
42
+ * color (a loading button sets ButtonText instead, button.css). */
43
+ @media (forced-colors: active) {
44
+ .dds-spinner {
45
+ forced-color-adjust: none;
46
+ color: CanvasText;
33
47
  }
34
48
  }
package/dist/dds.css CHANGED
@@ -52,7 +52,7 @@
52
52
  * Variants: primary / secondary / ghost / danger. Sizes: sm / md(default) / lg.
53
53
  * Control heights come from the spacing scale (32/40/48); type from the typo
54
54
  * scale (label for sm/md, body-1 size for lg). Loading = aria-busy="true" with
55
- * a .dds-spinner child. */
55
+ * a .dds-spinner child; the button keeps its size while loading (D-043). */
56
56
 
57
57
  .dds-btn {
58
58
  appearance: none;
@@ -79,6 +79,10 @@
79
79
  -webkit-tap-highlight-color: transparent;
80
80
  white-space: normal;
81
81
  overflow-wrap: anywhere;
82
+ /* Containing block for the loading spinner, which is centred over the
83
+ * label rather than placed beside it. Set here, not only while loading,
84
+ * so a consumer's own position (a fixed action button) still wins. */
85
+ position: relative;
82
86
  }
83
87
 
84
88
  .dds-btn:focus-visible {
@@ -87,13 +91,36 @@
87
91
  }
88
92
 
89
93
  /* opacity is allowed for disabled only (spec §4.2) */
90
- .dds-btn[disabled],
91
- .dds-btn[aria-busy="true"] {
94
+ .dds-btn[disabled] {
92
95
  opacity: 0.45;
93
96
  cursor: not-allowed;
94
97
  }
98
+
99
+ /* Loading (D-043). The button keeps the exact box its label gives it, so the
100
+ * controls beside it do not move and a wrapped label does not wrap again
101
+ * when progress starts. The label stays in the layout and in the
102
+ * accessibility tree (its accessible name does not change) but is not
103
+ * painted: the text fill is transparent and element children (icons) are
104
+ * fully transparent. The spinner is taken out of the flow and centred over
105
+ * the label; it keeps the button's text color (currentColor).
106
+ *
107
+ * Not dimmed: the spinner is the only thing painted, and at the disabled
108
+ * 0.45 opacity it drops to about 2:1 against the button (WCAG 1.4.11 asks
109
+ * 3:1). The framework Buttons also set [disabled], so this rule comes after
110
+ * the one above to put the opacity back. */
95
111
  .dds-btn[aria-busy="true"] {
112
+ opacity: 1;
113
+ cursor: not-allowed;
96
114
  pointer-events: none;
115
+ -webkit-text-fill-color: transparent;
116
+ }
117
+ .dds-btn[aria-busy="true"] > :not(.dds-spinner) {
118
+ opacity: 0;
119
+ }
120
+ .dds-btn[aria-busy="true"] > .dds-spinner {
121
+ position: absolute;
122
+ inset: 0;
123
+ margin: auto;
97
124
  }
98
125
 
99
126
  /* variants ---------------------------------------------------------------- */
@@ -166,6 +193,25 @@
166
193
  }
167
194
  }
168
195
 
196
+ /* Forced colors repaint every text color, transparent included, so the
197
+ * hidden label would come back on top of the spinner. A loading button opts
198
+ * out and paints itself with system colors: the face of a button, the
199
+ * disabled border, and the spinner in button text. No transition: opted out,
200
+ * the background would fade from the brand color it no longer forces. */
201
+ @media (forced-colors: active) {
202
+ .dds-btn[aria-busy="true"] {
203
+ forced-color-adjust: none;
204
+ background-color: ButtonFace;
205
+ border-color: GrayText;
206
+ color: ButtonText;
207
+ transition: none;
208
+ }
209
+ .dds-btn[aria-busy="true"] > .dds-spinner {
210
+ forced-color-adjust: none;
211
+ color: ButtonText;
212
+ }
213
+ }
214
+
169
215
  /* IconButton — a Button whose only content is an icon (spec §4.3 v1 inventory).
170
216
  * Same state set and sizes as .dds-btn (32/40/48 from the spacing scale) so an
171
217
  * icon button never lands a few px off the text button next to it — the row
@@ -206,6 +252,15 @@
206
252
  cursor: not-allowed;
207
253
  }
208
254
 
255
+ /* Loading: the framework IconButtons swap the icon for a .dds-spinner, which
256
+ * the fixed box already centres. Same rule as .dds-btn (D-043): not dimmed,
257
+ * because a 0.45 spinner falls below 3:1 (danger is about 2.3:1). */
258
+ .dds-iconbtn[aria-busy="true"] {
259
+ opacity: 1;
260
+ cursor: not-allowed;
261
+ pointer-events: none;
262
+ }
263
+
209
264
  /* variants ---------------------------------------------------------------- */
210
265
 
211
266
  .dds-iconbtn--secondary {
@@ -467,6 +522,63 @@
467
522
  .dds-choice[data-disabled] {opacity:.45;}
468
523
  @media(pointer:coarse){.dds-choice-input,.dds-choice-trigger,.dds-choice-clear {min-block-size:44px;min-inline-size:44px;}}
469
524
 
525
+ /* Ark DatePicker interaction with DDS visual tokens. */
526
+ .dds-date-picker-control {display:flex;align-items:stretch;gap:var(--dds-space-8);}
527
+ .dds-date-picker-control input {min-width:0;}
528
+ .dds-date-picker-control .dds-btn {flex-shrink:0;min-width:calc(var(--dds-space-40) + var(--dds-space-4));padding-inline:calc(var(--dds-space-8) + var(--dds-space-2));}
529
+ .dds-date-picker-positioner {z-index:var(--dds-overlay-z-index,1001);}
530
+ .dds-date-picker-content {width:calc(var(--dds-space-40) * 8);max-width:calc(100vw - var(--dds-space-32));max-height:min(calc(100dvh - var(--dds-space-32)),var(--available-height,100dvh));overflow:auto;box-sizing:border-box;padding:var(--dds-space-16);background:var(--dds-color-bg-default);color:var(--dds-color-text-primary);border:calc(var(--dds-space-2) / 2) solid var(--dds-color-border-default);border-radius:var(--dds-radius-md);font-family:var(--dds-font-family-sans);font-size:var(--dds-typo-body-2-font-size);}
531
+ .dds-date-picker-header {display:flex;align-items:center;justify-content:space-between;gap:var(--dds-space-12);font-weight:600;margin-bottom:var(--dds-space-12);}
532
+ .dds-date-picker-table {width:100%;border-collapse:collapse;table-layout:fixed;}
533
+ .dds-date-picker-table th {font-size:var(--dds-space-12);font-weight:500;color:var(--dds-color-text-secondary);height:var(--dds-space-32);}
534
+ .dds-date-picker-table td {padding:0;text-align:center;}
535
+ .dds-date-picker-table [data-part="table-cell-trigger"] {display:flex;align-items:center;justify-content:center;width:100%;height:var(--dds-space-40);border:0;border-radius:var(--dds-radius-sm);font:inherit;color:inherit;background:transparent;cursor:pointer;}
536
+ .dds-date-picker-table [data-part="table-cell-trigger"]:hover {background:var(--dds-color-bg-brand-subtle);}
537
+ .dds-date-picker-table [data-outside-range] {color:var(--dds-color-text-secondary);}
538
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-selected] {background:var(--dds-color-bg-brand);color:var(--dds-color-text-on-brand);}
539
+ .dds-date-picker-table [data-today]:not([data-selected]) {text-decoration:underline;text-underline-offset:var(--dds-space-4);}
540
+ .dds-date-picker-content :focus-visible {outline:var(--dds-space-2) solid var(--dds-color-text-brand);outline-offset:var(--dds-space-2);}
541
+ .dds-date-picker-footer {display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--dds-space-12);margin-top:var(--dds-space-12);padding-top:var(--dds-space-12);border-top:calc(var(--dds-space-2) / 2) solid var(--dds-color-border-default);}
542
+
543
+ /* Text box shared by DatePicker, MonthPicker and the DateRangePicker pair: same var(--dds-space-40) DDS input look. */
544
+ .dds-date-picker-control > input,.dds-date-range-box {box-sizing:border-box;flex:1;min-inline-size:0;block-size:var(--dds-space-40);border:calc(var(--dds-space-2) / 2) solid var(--dds-color-border-strong);border-radius:var(--dds-radius-md);background:var(--dds-color-bg-default);color:var(--dds-color-text-primary);font-family:inherit;font-size:var(--dds-typo-body-2-font-size);font-variant-numeric:tabular-nums;}
545
+ .dds-date-picker-control > input {padding-inline:var(--dds-space-12);}
546
+ .dds-date-picker-control > input:focus-visible,.dds-date-range-box:focus-within {outline:var(--dds-space-2) solid var(--dds-color-border-focus);outline-offset:0;}
547
+ .dds-date-picker-control > input[aria-invalid="true"],.dds-date-range-box[data-invalid] {border-color:var(--dds-color-status-danger);}
548
+ .dds-date-picker-control > input:disabled,.dds-date-range-box[data-disabled] {opacity:.45;cursor:not-allowed;background:var(--dds-color-bg-subtle);}
549
+ .dds-date-picker-control > input::placeholder,.dds-date-range-input::placeholder {color:var(--dds-color-text-muted);}
550
+ .dds-date-range-box {display:flex;align-items:center;gap:var(--dds-space-8);padding-inline:var(--dds-space-12);}
551
+ .dds-date-range-box[hidden] {display:none;}
552
+ .dds-date-range-input {flex:1;inline-size:100%;min-inline-size:0;block-size:100%;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-variant-numeric:tabular-nums;outline:none;}
553
+ .dds-date-range-input:disabled {cursor:not-allowed;}
554
+ .dds-date-range-separator {flex:none;color:var(--dds-color-text-secondary);}
555
+ .dds-field__error {font-size:var(--dds-typo-caption-font-size);line-height:var(--dds-typo-caption-line-height);color:var(--dds-color-status-danger);}
556
+ @media (pointer:coarse) {.dds-date-picker-control > input,.dds-date-range-box {min-block-size:calc(var(--dds-space-40) + var(--dds-space-4));}}
557
+
558
+ /* Range selection: ends filled, days between tinted, hover previews the end being chosen. */
559
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-in-range] {background:var(--dds-color-bg-brand-subtle);color:var(--dds-color-text-primary);border-radius:0;}
560
+ .dds-date-picker-table [data-part="table-cell-trigger"]:is([data-range-start],[data-range-end]) {background:var(--dds-color-bg-brand);color:var(--dds-color-text-on-brand);border-radius:var(--dds-radius-sm);}
561
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-hover-range-end]:not([data-range-start]) {box-shadow:inset 0 0 0 var(--dds-space-2) var(--dds-color-bg-brand);border-radius:var(--dds-radius-sm);}
562
+ .dds-date-picker-presets {display:flex;flex-wrap:wrap;gap:var(--dds-space-8);flex:1 1 100%;}
563
+ .dds-date-picker-presets[hidden] {display:none;}
564
+ .dds-date-picker-footer > [data-part="clear-trigger"] {margin-inline-start:auto;}
565
+ /* Days and months outside min/max read as unavailable, not as ordinary choices. */
566
+ .dds-date-picker-table [data-part="table-cell-trigger"][data-disabled] {opacity:.45;cursor:not-allowed;background:transparent;}
567
+
568
+ /* Month grid (MonthPicker): 3 columns × 4 rows, year paged in the header. */
569
+ .dds-date-picker-year {font-variant-numeric:tabular-nums;}
570
+ .dds-date-picker-table--months td {padding:var(--dds-space-4);}
571
+ .dds-date-picker-table--months [data-part="table-cell-trigger"] {height:var(--dds-space-40);}
572
+
573
+ /* Standalone Calendar shares the DatePicker view without popup positioning. */
574
+ .dds-calendar {display:block;}
575
+ .dds-date-picker--hidden > .dds-field__label {position:absolute;width:calc(var(--dds-space-2) / 2);height:calc(var(--dds-space-2) / 2);overflow:hidden;clip-path:inset(50%);}
576
+ .dds-date-picker--inside {position:relative;}
577
+ .dds-date-picker--inside > .dds-field__label {position:absolute;inset-inline-start:var(--dds-space-12);inset-block-start:calc(var(--dds-space-16) + var(--dds-space-2));z-index:1;pointer-events:none;}
578
+ .dds-date-picker--inside:focus-within > .dds-field__label,.dds-date-picker--inside[data-filled] > .dds-field__label {inset-block-start:var(--dds-space-2);font-size:var(--dds-typo-caption-font-size);}
579
+ .dds-date-picker--inside .dds-date-picker-control input {height:calc(var(--dds-space-40) + var(--dds-space-16));padding-block:var(--dds-space-20) var(--dds-space-4);}
580
+ .dds-date-picker--inside:not(:focus-within):not([data-filled]) input::placeholder {color:transparent;}
581
+
470
582
  /* TimePicker: the Combobox structure from choice.css (same 40px control, list
471
583
  * and focus), with fixed-width digits and a clock button instead of the caret. */
472
584
  .dds-time-picker .dds-choice-input,.dds-time-picker .dds-choice-item {font-variant-numeric:tabular-nums;}
@@ -1129,9 +1241,23 @@
1129
1241
  }
1130
1242
  }
1131
1243
 
1244
+ /* base.css stops every dds- animation after one 0.01ms run under reduced
1245
+ * motion. The :root prefix ties its specificity and this file loads after
1246
+ * base.css, so the spinner keeps turning, at half speed, instead of freezing. */
1132
1247
  @media (prefers-reduced-motion: reduce) {
1133
- .dds-spinner {
1248
+ :root .dds-spinner {
1134
1249
  animation-duration: 1600ms;
1250
+ animation-iteration-count: infinite;
1251
+ }
1252
+ }
1253
+
1254
+ /* Forced colors repaint the transparent top border too, which closes the gap
1255
+ * and leaves a ring that looks still. Opt out and paint it in the system text
1256
+ * color (a loading button sets ButtonText instead, button.css). */
1257
+ @media (forced-colors: active) {
1258
+ .dds-spinner {
1259
+ forced-color-adjust: none;
1260
+ color: CanvasText;
1135
1261
  }
1136
1262
  }
1137
1263
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devslab/dds-css",
3
- "version": "0.25.1",
3
+ "version": "0.27.0",
4
4
  "description": "DDS CSS component layer — class-based, framework-neutral, semantic-token-only (spec §4.3 v1 inventory, 20 components)",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",