@scalewing/react 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +70 -3
  3. package/dist/anchored-position.d.ts +28 -0
  4. package/dist/anchored-position.js +17 -0
  5. package/dist/calendar-keys.d.ts +19 -0
  6. package/dist/calendar-keys.js +39 -0
  7. package/dist/calendar-labels.d.ts +12 -0
  8. package/dist/calendar-labels.js +32 -0
  9. package/dist/calendar-month.d.ts +36 -0
  10. package/dist/calendar-month.js +111 -0
  11. package/dist/calendar-years.d.ts +14 -0
  12. package/dist/calendar-years.js +20 -0
  13. package/dist/components/Accordion.d.ts +6 -1
  14. package/dist/components/Accordion.js +12 -3
  15. package/dist/components/ActionBar.d.ts +30 -0
  16. package/dist/components/ActionBar.js +13 -0
  17. package/dist/components/Box.d.ts +7 -3
  18. package/dist/components/Box.js +2 -2
  19. package/dist/components/DateField.d.ts +15 -1
  20. package/dist/components/DateField.js +58 -18
  21. package/dist/components/Field.d.ts +5 -1
  22. package/dist/components/Field.js +16 -4
  23. package/dist/components/FieldAdornment.d.ts +20 -0
  24. package/dist/components/FieldAdornment.js +34 -0
  25. package/dist/components/Grid.d.ts +4 -1
  26. package/dist/components/Grid.js +2 -2
  27. package/dist/components/Progress.d.ts +3 -1
  28. package/dist/components/Progress.js +2 -2
  29. package/dist/components/ScrollRegion.d.ts +15 -0
  30. package/dist/components/ScrollRegion.js +10 -0
  31. package/dist/components/Table.d.ts +1 -5
  32. package/dist/components/Table.js +3 -6
  33. package/dist/components/date-field/CalendarDialog.d.ts +26 -0
  34. package/dist/components/date-field/CalendarDialog.js +93 -0
  35. package/dist/components/date-field/CalendarGrid.d.ts +23 -0
  36. package/dist/components/date-field/CalendarGrid.js +36 -0
  37. package/dist/components/date-field/CalendarHeader.d.ts +18 -0
  38. package/dist/components/date-field/CalendarHeader.js +33 -0
  39. package/dist/components/date-field/DateFieldGlyph.d.ts +9 -0
  40. package/dist/components/date-field/DateFieldGlyph.js +15 -0
  41. package/dist/components/date-field/use-anchored-popover.d.ts +8 -0
  42. package/dist/components/date-field/use-anchored-popover.js +61 -0
  43. package/dist/components/date-field/use-date-entry.d.ts +20 -0
  44. package/dist/components/date-field/use-date-entry.js +61 -0
  45. package/dist/components/date-field/use-lang-locale.d.ts +7 -0
  46. package/dist/components/date-field/use-lang-locale.js +17 -0
  47. package/dist/components/denomination-grid/DenominationStrip.js +5 -4
  48. package/dist/css/css-accordion.js +55 -8
  49. package/dist/css/css-action-bar.d.ts +4 -0
  50. package/dist/css/css-action-bar.js +99 -0
  51. package/dist/css/css-action-menu.js +2 -1
  52. package/dist/css/css-box-border.d.ts +5 -0
  53. package/dist/css/css-box-border.js +24 -0
  54. package/dist/css/css-chrome.js +2 -1
  55. package/dist/css/css-components.js +39 -26
  56. package/dist/css/css-data.js +4 -10
  57. package/dist/css/css-date-field.js +241 -11
  58. package/dist/css/css-denomination-grid.js +49 -5
  59. package/dist/css/css-document.d.ts +2 -0
  60. package/dist/css/css-document.js +2 -1
  61. package/dist/css/css-field.js +61 -1
  62. package/dist/css/css-scroll-region.d.ts +8 -0
  63. package/dist/css/css-scroll-region.js +18 -0
  64. package/dist/css/css-select.js +2 -1
  65. package/dist/css/css-tooltip.js +2 -1
  66. package/dist/css/stacking.d.ts +10 -0
  67. package/dist/css/stacking.js +47 -0
  68. package/dist/css/stylesheet.js +4 -0
  69. package/dist/date-entry-validity.d.ts +10 -0
  70. package/dist/date-entry-validity.js +16 -0
  71. package/dist/date-entry.d.ts +32 -0
  72. package/dist/date-entry.js +106 -0
  73. package/dist/date-field-labels.d.ts +38 -0
  74. package/dist/date-field-labels.js +22 -0
  75. package/dist/date-field-locale.d.ts +8 -0
  76. package/dist/date-field-locale.js +27 -0
  77. package/dist/date-formatters.d.ts +13 -0
  78. package/dist/date-formatters.js +28 -0
  79. package/dist/date-only.d.ts +23 -0
  80. package/dist/date-only.js +63 -9
  81. package/dist/index.d.ts +5 -3
  82. package/dist/index.js +3 -2
  83. package/dist/styles.css +461 -45
  84. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { compactTypographyVariants, typographyVariants, } from '@scalewing/tokens';
2
2
  import { breakpointQuery } from './breakpoints.js';
3
+ import { cssActionBarClasses } from './css-action-bar.js';
3
4
  import { cssActionMenuClasses } from './css-action-menu.js';
4
5
  import { cssAccordionClasses } from './css-accordion.js';
5
6
  import { cssButtonClasses } from './css-button.js';
@@ -26,6 +27,36 @@ import { cssGridSpanClasses } from './css-grid-span.js';
26
27
  import { cssDenominationGridClasses } from './css-denomination-grid.js';
27
28
  import { cssFieldClasses } from './css-field.js';
28
29
  import { cssToastClasses } from './css-toast.js';
30
+ import { cssBoxBorderClasses } from './css-box-border.js';
31
+ import { cssPopupHostRules } from './stacking.js';
32
+ /*
33
+ * Reduce Transparency swaps every glass surface for the solid surface. It is
34
+ * emitted after every component rule: at equal specificity the later rule wins,
35
+ * so a glass rule emitted after this block would keep its blur.
36
+ */
37
+ function reducedTransparencyRules() {
38
+ return `@media (prefers-reduced-transparency: reduce) {
39
+ .sw-card-glass,
40
+ .sw-app-header,
41
+ .sw-action-bar,
42
+ .sw-badge-neutral,
43
+ .sw-segmented,
44
+ .sw-table-sticky thead th,
45
+ .sw-denomination-strip .sw-denomination-label,
46
+ .sw-denomination-strip thead .sw-denomination-corner:first-child,
47
+ .sw-bar-chart-track,
48
+ .sw-dialog,
49
+ .sw-accordion,
50
+ .sw-select-list,
51
+ .sw-action-menu-list,
52
+ .sw-date-field-calendar,
53
+ .sw-toast {
54
+ background: var(--sw-color-surface);
55
+ backdrop-filter: none;
56
+ -webkit-backdrop-filter: none;
57
+ }
58
+ }`;
59
+ }
29
60
  function textVariantRules() {
30
61
  return Object.entries(typographyVariants)
31
62
  .map(([name, variant]) => {
@@ -56,12 +87,7 @@ export function cssComponentClasses() {
56
87
  -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
57
88
  }
58
89
 
59
- /* Backdrop blur makes a glass surface its own stacking context, so an open popup inside it would paint under the next surface. */
60
- .sw-card-glass:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])),
61
- .sw-accordion:has(.sw-select-list, .sw-action-menu-list:not([hidden]), .sw-tooltip:not([hidden])) {
62
- position: relative;
63
- z-index: 1;
64
- }
90
+ ${cssPopupHostRules()}
65
91
 
66
92
  .sw-card-outlined {
67
93
  background: var(--sw-color-surface);
@@ -79,25 +105,6 @@ export function cssComponentClasses() {
79
105
  background: var(--sw-color-subtle);
80
106
  }
81
107
 
82
- @media (prefers-reduced-transparency: reduce) {
83
- .sw-card-glass,
84
- .sw-app-header,
85
- .sw-button-secondary,
86
- .sw-badge-neutral,
87
- .sw-segmented,
88
- .sw-table-sticky thead th,
89
- .sw-bar-chart-track,
90
- .sw-dialog,
91
- .sw-accordion,
92
- .sw-select-list,
93
- .sw-action-menu-list,
94
- .sw-toast {
95
- background: var(--sw-color-surface);
96
- backdrop-filter: none;
97
- -webkit-backdrop-filter: none;
98
- }
99
- }
100
-
101
108
  .sw-text-align-start { text-align: start; }
102
109
  .sw-text-align-center { text-align: center; }
103
110
  .sw-text-align-end { text-align: end; }
@@ -138,6 +145,8 @@ ${cssSelectClasses()}
138
145
 
139
146
  ${cssActionMenuClasses()}
140
147
 
148
+ ${cssActionBarClasses()}
149
+
141
150
  ${cssSwitchClasses()}
142
151
 
143
152
  ${cssSpinnerClasses()}
@@ -164,5 +173,9 @@ ${cssSplitClasses()}
164
173
 
165
174
  ${cssToastClasses()}
166
175
 
167
- ${cssResponsiveClasses()}`;
176
+ ${cssResponsiveClasses()}
177
+
178
+ ${cssBoxBorderClasses()}
179
+
180
+ ${reducedTransparencyRules()}`;
168
181
  }
@@ -1,4 +1,6 @@
1
1
  import { trackInset, typographyVariants } from '@scalewing/tokens';
2
+ import { scrollRegionRules } from './css-scroll-region.js';
3
+ import { zIndex } from './stacking.js';
2
4
  export const badgeTones = [
3
5
  'neutral',
4
6
  'accent',
@@ -134,15 +136,7 @@ export function cssDataClasses() {
134
136
  cursor: not-allowed;
135
137
  }
136
138
 
137
- .sw-table-wrap {
138
- overflow: auto;
139
- width: 100%;
140
- }
141
-
142
- .sw-table-wrap:focus-visible {
143
- outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
144
- outline-offset: calc(-1 * var(--sw-focus-ring-width));
145
- }
139
+ ${scrollRegionRules('.sw-table-wrap')}
146
140
 
147
141
  .sw-table {
148
142
  border-collapse: collapse;
@@ -187,7 +181,7 @@ export function cssDataClasses() {
187
181
  -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
188
182
  position: sticky;
189
183
  top: 0;
190
- z-index: 1;
184
+ ${zIndex('stickyCell')}
191
185
  }
192
186
 
193
187
  .sw-table-compact th,
@@ -1,8 +1,15 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { zIndex } from './stacking.js';
2
3
  const body = typographyVariants.body;
3
4
  const label = typographyVariants.label;
4
5
  const caption = typographyVariants.caption;
5
- export function cssDateFieldClasses() {
6
+ /* The calendar button, month steps, and days are one square control wide. */
7
+ const square = 'var(--sw-control-md-min-height)';
8
+ /* Wide enough for "MM/DD/YYYY" in the system sans. */
9
+ const entryWidth = '12ch';
10
+ /* Outranks the canvas rule for text inputs so the button keeps its room. */
11
+ const input = '.sw-date-field .sw-date-field-control > .sw-date-field-input';
12
+ function fieldRules() {
6
13
  return `.sw-date-field {
7
14
  display: flex;
8
15
  flex-direction: column;
@@ -20,7 +27,15 @@ export function cssDateFieldClasses() {
20
27
  line-height: ${label.lineHeight}px;
21
28
  }
22
29
 
23
- .sw-date-field-input {
30
+ .sw-date-field-control {
31
+ align-self: flex-start;
32
+ display: flex;
33
+ max-width: 100%;
34
+ min-width: 0;
35
+ position: relative;
36
+ }
37
+
38
+ ${input} {
24
39
  background: var(--sw-glass-fill);
25
40
  border: 1px solid var(--sw-color-border);
26
41
  border-radius: var(--sw-radius-sm);
@@ -28,26 +43,78 @@ export function cssDateFieldClasses() {
28
43
  color: var(--sw-color-text);
29
44
  font-family: var(--sw-font-sans);
30
45
  font-size: ${body.fontSize}px;
46
+ font-variant-numeric: tabular-nums;
47
+ margin: 0;
48
+ max-width: 100%;
31
49
  min-height: var(--sw-control-md-min-height);
32
50
  min-width: 0;
33
- padding-inline: var(--sw-control-md-padding-inline);
34
- width: 100%;
51
+ padding-inline: var(--sw-control-md-padding-inline) ${square};
52
+ width: calc(${entryWidth} + var(--sw-control-md-padding-inline) + ${square});
53
+ }
54
+
55
+ ${input}::placeholder {
56
+ color: var(--sw-color-muted);
57
+ opacity: 1;
58
+ }
59
+
60
+ ${input}:focus {
61
+ box-shadow: none;
62
+ outline: none;
35
63
  }
36
64
 
37
- .sw-date-field-input:focus-visible {
65
+ ${input}:focus-visible {
38
66
  outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
39
67
  outline-offset: var(--sw-focus-ring-offset);
40
68
  }
41
69
 
42
- .sw-date-field-input[aria-invalid='true'] {
70
+ ${input}[aria-invalid='true'] {
43
71
  border-color: var(--sw-color-danger);
44
72
  }
45
73
 
46
- .sw-date-field-input:disabled {
74
+ ${input}:disabled,
75
+ .sw-date-field-button:disabled {
47
76
  cursor: not-allowed;
48
77
  opacity: var(--sw-disabled-opacity);
49
78
  }
50
79
 
80
+ .sw-date-field-button {
81
+ align-items: center;
82
+ appearance: none;
83
+ background: transparent;
84
+ border: 0;
85
+ border-radius: var(--sw-radius-sm);
86
+ color: var(--sw-color-muted);
87
+ cursor: pointer;
88
+ display: flex;
89
+ inset-block: 0;
90
+ inset-inline-end: 0;
91
+ justify-content: center;
92
+ margin: 0;
93
+ padding: 0;
94
+ position: absolute;
95
+ width: ${square};
96
+ }
97
+
98
+ .sw-date-field-button:hover:not(:disabled),
99
+ .sw-date-field-button[aria-expanded='true'] {
100
+ color: var(--sw-color-text);
101
+ }
102
+
103
+ .sw-date-field-glyph {
104
+ fill: none;
105
+ flex: none;
106
+ height: var(--sw-space-5);
107
+ stroke: currentColor;
108
+ stroke-linecap: round;
109
+ stroke-linejoin: round;
110
+ stroke-width: 1.75;
111
+ width: var(--sw-space-5);
112
+ }
113
+
114
+ [dir='rtl'] .sw-date-field-glyph-directional {
115
+ transform: scaleX(-1);
116
+ }
117
+
51
118
  .sw-date-field-description,
52
119
  .sw-date-field-error {
53
120
  font-family: var(--sw-font-sans);
@@ -56,22 +123,185 @@ export function cssDateFieldClasses() {
56
123
  }
57
124
 
58
125
  .sw-date-field-description { color: var(--sw-color-muted); }
59
- .sw-date-field-error { color: var(--sw-color-danger); }
126
+ .sw-date-field-error { color: var(--sw-color-danger); }`;
127
+ }
128
+ function calendarRules() {
129
+ return `.sw-date-field-calendar {
130
+ background: var(--sw-glass-fill);
131
+ border: 1px solid var(--sw-glass-border);
132
+ border-radius: var(--sw-radius-md);
133
+ box-shadow: var(--sw-elevation-md), inset 0 1px 0 var(--sw-glass-specular);
134
+ backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
135
+ -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
136
+ box-sizing: border-box;
137
+ color: var(--sw-color-text);
138
+ display: flex;
139
+ flex-direction: column;
140
+ font-family: var(--sw-font-sans);
141
+ gap: var(--sw-space-2);
142
+ height: auto;
143
+ inset: auto;
144
+ margin: 0;
145
+ max-width: calc(100vw - var(--sw-space-2) - var(--sw-space-2));
146
+ overflow: visible;
147
+ padding: var(--sw-space-3);
148
+ position: fixed;
149
+ width: max-content;
150
+ ${zIndex('popup')}
151
+ }
152
+
153
+ .sw-date-field-header {
154
+ align-items: center;
155
+ display: flex;
156
+ gap: var(--sw-space-1);
157
+ justify-content: space-between;
158
+ }
159
+
160
+ .sw-date-field-jump {
161
+ display: flex;
162
+ gap: var(--sw-space-1);
163
+ min-width: 0;
164
+ }
165
+
166
+ .sw-date-field-step {
167
+ align-items: center;
168
+ appearance: none;
169
+ background: transparent;
170
+ border: 0;
171
+ border-radius: var(--sw-radius-pill);
172
+ color: var(--sw-color-text);
173
+ cursor: pointer;
174
+ display: flex;
175
+ flex: none;
176
+ height: ${square};
177
+ justify-content: center;
178
+ margin: 0;
179
+ padding: 0;
180
+ width: ${square};
181
+ }
182
+
183
+ .sw-date-field-step[aria-disabled='true'] {
184
+ cursor: not-allowed;
185
+ opacity: var(--sw-disabled-opacity);
186
+ }
187
+
188
+ .sw-date-field-grid {
189
+ border-collapse: separate;
190
+ border-spacing: 0;
191
+ table-layout: fixed;
192
+ }
193
+
194
+ .sw-date-field-grid th {
195
+ color: var(--sw-color-muted);
196
+ font-size: ${caption.fontSize}px;
197
+ font-weight: normal;
198
+ line-height: ${caption.lineHeight}px;
199
+ padding: 0 0 var(--sw-space-1);
200
+ text-align: center;
201
+ }
60
202
 
61
- @media (prefers-reduced-transparency: reduce) {
62
- .sw-date-field-input { background: var(--sw-color-surface); }
203
+ .sw-date-field-day {
204
+ border: 1px solid transparent;
205
+ border-radius: var(--sw-radius-pill);
206
+ box-sizing: border-box;
207
+ cursor: pointer;
208
+ font-size: ${label.fontSize}px;
209
+ font-variant-numeric: tabular-nums;
210
+ height: ${square};
211
+ padding: 0;
212
+ text-align: center;
213
+ vertical-align: middle;
214
+ width: ${square};
215
+ }
216
+
217
+ .sw-date-field-day-outside {
218
+ color: var(--sw-color-muted);
219
+ }
220
+
221
+ .sw-date-field-step:hover:not([aria-disabled='true']),
222
+ .sw-date-field-day:hover:not([aria-disabled='true']) {
223
+ background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
224
+ }
225
+
226
+ .sw-date-field-day[aria-current='date'] {
227
+ border-color: var(--sw-color-accent);
228
+ color: var(--sw-color-accent);
229
+ font-weight: ${label.fontWeight};
230
+ }
231
+
232
+ .sw-date-field-day[aria-selected='true'],
233
+ .sw-date-field-day[aria-selected='true']:hover {
234
+ background: var(--sw-color-accent);
235
+ border-color: var(--sw-color-accent);
236
+ color: var(--sw-color-onAccent);
237
+ font-weight: ${label.fontWeight};
238
+ }
239
+
240
+ .sw-date-field-day[aria-disabled='true'] {
241
+ cursor: not-allowed;
242
+ opacity: var(--sw-disabled-opacity);
243
+ }
244
+
245
+ .sw-date-field-button:focus,
246
+ .sw-date-field-step:focus,
247
+ .sw-date-field-day:focus {
248
+ outline: none;
249
+ }
250
+
251
+ .sw-date-field-button:focus-visible,
252
+ .sw-date-field-step:focus-visible,
253
+ .sw-date-field-day:focus-visible {
254
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
255
+ outline-offset: var(--sw-focus-ring-offset);
256
+ }
257
+
258
+ .sw-date-field-footer {
259
+ display: flex;
260
+ gap: var(--sw-space-1);
261
+ justify-content: flex-end;
262
+ }`;
263
+ }
264
+ function adaptiveRules() {
265
+ return `@media (prefers-reduced-transparency: reduce) {
266
+ ${input} { background: var(--sw-color-surface); }
63
267
  }
64
268
 
65
269
  @media (forced-colors: active) {
66
- .sw-date-field-input[aria-invalid='true'] { border-color: CanvasText; }
270
+ ${input}[aria-invalid='true'] { border-color: CanvasText; }
271
+ .sw-date-field-calendar { border-color: CanvasText; }
272
+ .sw-date-field-day[aria-current='date'] { border-color: CanvasText; }
273
+ .sw-date-field-day[aria-selected='true'] {
274
+ background: Highlight;
275
+ color: HighlightText;
276
+ forced-color-adjust: none;
277
+ }
67
278
  }`;
68
279
  }
280
+ export function cssDateFieldClasses() {
281
+ return `${fieldRules()}
282
+
283
+ ${calendarRules()}
284
+
285
+ ${adaptiveRules()}`;
286
+ }
69
287
  export function dateFieldClassCatalog() {
70
288
  return [
71
289
  'sw-date-field',
72
290
  'sw-date-field-label',
291
+ 'sw-date-field-control',
73
292
  'sw-date-field-input',
293
+ 'sw-date-field-button',
294
+ 'sw-date-field-glyph',
295
+ 'sw-date-field-glyph-directional',
74
296
  'sw-date-field-description',
75
297
  'sw-date-field-error',
298
+ 'sw-date-field-calendar',
299
+ 'sw-date-field-header',
300
+ 'sw-date-field-jump',
301
+ 'sw-date-field-step',
302
+ 'sw-date-field-grid',
303
+ 'sw-date-field-day',
304
+ 'sw-date-field-day-outside',
305
+ 'sw-date-field-footer',
76
306
  ];
77
307
  }
@@ -1,6 +1,8 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
2
  import { breakpointQuery } from './breakpoints.js';
3
3
  import { badgeTones } from './css-data.js';
4
+ import { scrollRegionRules } from './css-scroll-region.js';
5
+ import { zIndex } from './stacking.js';
4
6
  const caption = typographyVariants.caption;
5
7
  const data = typographyVariants.data;
6
8
  const label = typographyVariants.label;
@@ -20,6 +22,8 @@ export function cssDenominationGridClasses() {
20
22
 
21
23
  ${toneRules()}
22
24
 
25
+ ${scrollRegionRules('.sw-denomination-scroll')}
26
+
23
27
  .sw-denomination-strip {
24
28
  border-collapse: collapse;
25
29
  width: 100%;
@@ -49,20 +53,56 @@ ${toneRules()}
49
53
  }
50
54
 
51
55
  .sw-denomination-label {
52
- align-items: center;
53
56
  border-inline-start: var(--sw-space-1) solid var(--sw-denomination-tone);
54
57
  color: var(--sw-denomination-tone);
55
- display: flex;
56
- flex-wrap: wrap;
57
58
  font-size: ${label.fontSize}px;
58
59
  font-weight: ${label.fontWeight};
59
- gap: var(--sw-space-2);
60
60
  letter-spacing: ${label.letterSpacing}px;
61
61
  line-height: ${label.lineHeight}px;
62
62
  min-width: 0;
63
63
  text-align: start;
64
64
  }
65
65
 
66
+ .sw-denomination-tiles .sw-denomination-label,
67
+ .sw-denomination-label-body {
68
+ align-items: center;
69
+ display: flex;
70
+ flex-wrap: wrap;
71
+ gap: var(--sw-space-2);
72
+ }
73
+
74
+ /*
75
+ * The row labels and the header corner above them stay in view while a wide
76
+ * strip scrolls under them, so each column head stays over its counts.
77
+ */
78
+ .sw-denomination-strip .sw-denomination-label,
79
+ .sw-denomination-strip thead .sw-denomination-corner:first-child {
80
+ background: var(--sw-glass-fill);
81
+ backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
82
+ -webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
83
+ inset-inline-start: 0;
84
+ position: sticky;
85
+ ${zIndex('stickyCell')}
86
+ }
87
+
88
+ /*
89
+ * A collapsed-table border does not travel with a sticky cell in every
90
+ * browser, so the pinned label draws its tone stripe as its own box.
91
+ */
92
+ .sw-denomination-strip .sw-denomination-label {
93
+ border-inline-start: 0;
94
+ padding-inline-start: calc(var(--sw-space-2) + var(--sw-space-1));
95
+ }
96
+
97
+ .sw-denomination-strip .sw-denomination-label::before {
98
+ background: var(--sw-denomination-tone);
99
+ content: '';
100
+ inset-block: 0;
101
+ inset-inline-start: 0;
102
+ position: absolute;
103
+ width: var(--sw-space-1);
104
+ }
105
+
66
106
  .sw-denomination-icon {
67
107
  display: inline-flex;
68
108
  flex: none;
@@ -172,14 +212,16 @@ ${toneRules()}
172
212
  @media ${breakpointQuery('below', 'md')} {
173
213
  .sw-denomination-strip .sw-denomination-total { display: none; }
174
214
  .sw-denomination-total-inline { display: block; }
175
- .sw-denomination-strip .sw-denomination-label { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
215
+ .sw-denomination-label-body { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
176
216
  .sw-denomination-strip th,
177
217
  .sw-denomination-strip td { padding: var(--sw-space-1); }
218
+ .sw-denomination-strip .sw-denomination-label { padding-inline-start: calc(var(--sw-space-1) * 2); }
178
219
  .sw-denomination-tile-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
179
220
  }
180
221
 
181
222
  @media (forced-colors: active) {
182
223
  .sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
224
+ .sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
183
225
  .sw-denomination-cell-zero { color: GrayText; opacity: 1; }
184
226
  .sw-denomination-cell-negative,
185
227
  .sw-denomination-row-signed .sw-denomination-cell-positive,
@@ -190,6 +232,7 @@ ${toneRules()}
190
232
  export function denominationGridClassCatalog() {
191
233
  return [
192
234
  'sw-denomination-grid',
235
+ 'sw-denomination-scroll',
193
236
  ...badgeTones.map((tone) => `sw-denomination-row-${tone}`),
194
237
  'sw-denomination-strip',
195
238
  'sw-denomination-head',
@@ -197,6 +240,7 @@ export function denominationGridClassCatalog() {
197
240
  'sw-denomination-row',
198
241
  'sw-denomination-row-signed',
199
242
  'sw-denomination-label',
243
+ 'sw-denomination-label-body',
200
244
  'sw-denomination-label-text',
201
245
  'sw-denomination-icon',
202
246
  'sw-denomination-cell',
@@ -1 +1,3 @@
1
+ /** The frame every native text control draws: fill, hairline border, radius. */
2
+ export declare const controlSurface = "background-color: var(--sw-glass-fill);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-radius-sm);\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n padding-inline: var(--sw-control-md-padding-inline);";
1
3
  export declare function cssDocumentCanvas(): string;
@@ -10,7 +10,8 @@ const typedInputs = [
10
10
  'input:not([type])',
11
11
  'textarea',
12
12
  ].join(', ');
13
- const controlSurface = `background-color: var(--sw-glass-fill);
13
+ /** The frame every native text control draws: fill, hairline border, radius. */
14
+ export const controlSurface = `background-color: var(--sw-glass-fill);
14
15
  border: 1px solid var(--sw-color-border);
15
16
  border-radius: var(--sw-radius-sm);
16
17
  color: inherit;
@@ -1,5 +1,58 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { controlSurface } from './css-document.js';
2
3
  const caption = typographyVariants.caption;
4
+ /*
5
+ * A prefix or suffix sits inside the control frame: the wrapper draws the
6
+ * canvas control surface and the focus ring, and the native input inside it
7
+ * drops its own frame so the text and the value read as one control.
8
+ */
9
+ function adornmentRules() {
10
+ return `.sw-field-adorned {
11
+ ${controlSurface}
12
+ align-items: center;
13
+ box-sizing: border-box;
14
+ cursor: text;
15
+ display: flex;
16
+ gap: var(--sw-space-1);
17
+ min-height: var(--sw-control-md-min-height);
18
+ }
19
+
20
+ [data-theme] .sw-field-adorned > input {
21
+ align-self: stretch;
22
+ background: transparent;
23
+ border: 0;
24
+ border-radius: 0;
25
+ flex: 1 1 auto;
26
+ min-height: 0;
27
+ min-width: 0;
28
+ padding-inline: 0;
29
+ }
30
+
31
+ [data-theme] .sw-field-adorned > input:focus-visible { outline: none; }
32
+
33
+ .sw-field-adorned:focus-within {
34
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
35
+ outline-offset: var(--sw-focus-ring-offset);
36
+ }
37
+
38
+ .sw-field-prefix,
39
+ .sw-field-suffix {
40
+ color: var(--sw-color-muted);
41
+ flex: none;
42
+ user-select: none;
43
+ white-space: nowrap;
44
+ }
45
+
46
+ .sw-field-xs .sw-field-adorned {
47
+ font-size: ${caption.fontSize}px;
48
+ letter-spacing: ${caption.letterSpacing}px;
49
+ line-height: ${caption.lineHeight}px;
50
+ min-height: var(--sw-control-xs-min-height);
51
+ padding-inline: var(--sw-control-xs-padding-inline);
52
+ }
53
+
54
+ .sw-field-invalid .sw-field-adorned { border-color: var(--sw-color-danger); }`;
55
+ }
3
56
  export function cssFieldClasses() {
4
57
  return `.sw-field-required { color: var(--sw-color-danger); }
5
58
 
@@ -17,8 +70,12 @@ export function cssFieldClasses() {
17
70
  border-color: var(--sw-color-danger);
18
71
  }
19
72
 
73
+ ${adornmentRules()}
74
+
20
75
  @media (forced-colors: active) {
21
- [data-theme] .sw-field-invalid :is(input, select, textarea) { border-color: Mark; }
76
+ [data-theme] .sw-field-invalid :is(input, select, textarea),
77
+ .sw-field-invalid .sw-field-adorned { border-color: Mark; }
78
+ .sw-field-prefix, .sw-field-suffix { color: CanvasText; }
22
79
  .sw-field-error, .sw-field-required { color: Mark; }
23
80
  }`;
24
81
  }
@@ -29,5 +86,8 @@ export function fieldClassCatalog() {
29
86
  'sw-field-description',
30
87
  'sw-field-error',
31
88
  'sw-field-invalid',
89
+ 'sw-field-adorned',
90
+ 'sw-field-prefix',
91
+ 'sw-field-suffix',
32
92
  ];
33
93
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A scroll region is a full-width box that scrolls its own content when the
3
+ * content is wider (or taller) than the box, so a wide table never widens the
4
+ * page. It is a keyboard stop, so its focus ring is drawn inside its edge
5
+ * where an ancestor's clipping cannot hide it. Table and the DenominationGrid
6
+ * strip share it.
7
+ */
8
+ export declare function scrollRegionRules(selector: string): string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A scroll region is a full-width box that scrolls its own content when the
3
+ * content is wider (or taller) than the box, so a wide table never widens the
4
+ * page. It is a keyboard stop, so its focus ring is drawn inside its edge
5
+ * where an ancestor's clipping cannot hide it. Table and the DenominationGrid
6
+ * strip share it.
7
+ */
8
+ export function scrollRegionRules(selector) {
9
+ return `${selector} {
10
+ overflow: auto;
11
+ width: 100%;
12
+ }
13
+
14
+ ${selector}:focus-visible {
15
+ outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
16
+ outline-offset: calc(-1 * var(--sw-focus-ring-width));
17
+ }`;
18
+ }
@@ -1,4 +1,5 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { zIndex } from './stacking.js';
2
3
  const caption = typographyVariants.caption;
3
4
  const label = typographyVariants.label;
4
5
  const chevronImage = `background-image:
@@ -88,7 +89,7 @@ export function cssSelectClasses() {
88
89
  padding: var(--sw-space-1);
89
90
  position: absolute;
90
91
  width: max-content;
91
- z-index: 3;
92
+ ${zIndex('popup')}
92
93
  }
93
94
 
94
95
  .sw-select-option {