@scalewing/react 1.11.0 → 1.12.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 (53) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +28 -6
  3. package/dist/anchored-position.d.ts +14 -6
  4. package/dist/anchored-position.js +10 -9
  5. package/dist/components/ActionMenu.d.ts +9 -1
  6. package/dist/components/ActionMenu.js +2 -2
  7. package/dist/components/Checkbox.js +2 -1
  8. package/dist/components/DateField.js +4 -3
  9. package/dist/components/DenominationGrid.d.ts +1 -1
  10. package/dist/components/DenominationGrid.js +4 -2
  11. package/dist/components/Field.js +6 -3
  12. package/dist/components/FieldErrorRegion.d.ts +14 -0
  13. package/dist/components/FieldErrorRegion.js +13 -0
  14. package/dist/components/RadioGroup.js +2 -1
  15. package/dist/components/Select.d.ts +10 -1
  16. package/dist/components/Select.js +17 -86
  17. package/dist/components/Toast.d.ts +15 -1
  18. package/dist/components/Toast.js +15 -5
  19. package/dist/components/action-menu/ActionMenuList.d.ts +6 -4
  20. package/dist/components/action-menu/ActionMenuList.js +5 -4
  21. package/dist/components/denomination-grid/DenominationStrip.d.ts +2 -2
  22. package/dist/components/denomination-grid/DenominationStrip.js +13 -3
  23. package/dist/components/denomination-grid/DenominationTiles.js +8 -1
  24. package/dist/components/denomination-grid/types.d.ts +5 -0
  25. package/dist/components/select/SelectListbox.d.ts +20 -0
  26. package/dist/components/select/SelectListbox.js +16 -0
  27. package/dist/components/select/SelectValue.d.ts +15 -0
  28. package/dist/components/select/SelectValue.js +16 -0
  29. package/dist/components/select/use-select-list.d.ts +26 -0
  30. package/dist/components/select/use-select-list.js +85 -0
  31. package/dist/components/use-anchored-popover.d.ts +5 -2
  32. package/dist/components/use-anchored-popover.js +6 -5
  33. package/dist/css/chevron.d.ts +5 -0
  34. package/dist/css/chevron.js +9 -0
  35. package/dist/css/css-accordion.js +2 -5
  36. package/dist/css/css-button.d.ts +1 -0
  37. package/dist/css/css-button.js +19 -4
  38. package/dist/css/css-checkbox.js +1 -0
  39. package/dist/css/css-data.js +18 -1
  40. package/dist/css/css-date-field.js +2 -1
  41. package/dist/css/css-denomination-grid.js +11 -1
  42. package/dist/css/css-field.js +3 -0
  43. package/dist/css/css-filter-chips.js +8 -2
  44. package/dist/css/css-radio-group.js +3 -2
  45. package/dist/css/css-select.js +57 -19
  46. package/dist/css/css-toast.d.ts +2 -0
  47. package/dist/css/css-toast.js +42 -1
  48. package/dist/css/css-utilities.d.ts +2 -0
  49. package/dist/css/css-utilities.js +3 -1
  50. package/dist/index.d.ts +3 -3
  51. package/dist/index.js +1 -1
  52. package/dist/styles.css +113 -29
  53. package/package.json +1 -1
@@ -70,8 +70,15 @@ export function cssFilterChipsClasses() {
70
70
  line-height: ${data.lineHeight}px;
71
71
  }
72
72
 
73
+ /*
74
+ * A zero-count chip is quiet without fading: it drops the glass fill and
75
+ * sets its label in the muted color, which is 4.5:1 on the canvas, surface
76
+ * and subtle fill in every palette. It can still be chosen, so it never
77
+ * reads as disabled.
78
+ */
73
79
  .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face {
74
- opacity: var(--sw-quiet-opacity);
80
+ background: transparent;
81
+ color: var(--sw-color-muted);
75
82
  }
76
83
 
77
84
  .sw-filter-chip-input:checked + .sw-filter-chip-face {
@@ -99,7 +106,6 @@ export function cssFilterChipsClasses() {
99
106
  .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
100
107
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
101
108
  .sw-filter-chip-count { color: inherit; }
102
- .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face { opacity: 1; color: GrayText; border-color: GrayText; }
103
109
  }`;
104
110
  }
105
111
  export function filterChipsClassCatalog() {
@@ -92,7 +92,7 @@ export function cssRadioGroupClasses() {
92
92
  outline-offset: var(--sw-focus-ring-offset);
93
93
  }
94
94
 
95
- .sw-radio-group:has(.sw-radio-group-error) .sw-radio-group-mark {
95
+ .sw-radio-group[aria-invalid='true'] .sw-radio-group-mark {
96
96
  border-color: var(--sw-color-danger);
97
97
  }
98
98
 
@@ -119,6 +119,7 @@ export function cssRadioGroupClasses() {
119
119
 
120
120
  .sw-radio-group-description { color: var(--sw-color-muted); }
121
121
  .sw-radio-group-error { color: var(--sw-color-danger); }
122
+ .sw-radio-group-error:empty { position: absolute; }
122
123
 
123
124
  @media (prefers-reduced-transparency: reduce) {
124
125
  .sw-radio-group-mark { background: var(--sw-color-surface); }
@@ -129,7 +130,7 @@ export function cssRadioGroupClasses() {
129
130
  .sw-radio-group-mark { background: Canvas; border-color: CanvasText; }
130
131
  .sw-radio-group-input:checked + .sw-radio-group-mark { background: Highlight; border-color: Highlight; }
131
132
  .sw-radio-group-input:checked + .sw-radio-group-mark::after { background: HighlightText; }
132
- .sw-radio-group:has(.sw-radio-group-error) .sw-radio-group-mark { border-color: CanvasText; }
133
+ .sw-radio-group[aria-invalid='true'] .sw-radio-group-mark { border-color: CanvasText; }
133
134
  }`;
134
135
  }
135
136
  export function radioGroupClassCatalog() {
@@ -1,13 +1,8 @@
1
1
  import { typographyVariants } from '@scalewing/tokens';
2
+ import { chevronStroke } from './chevron.js';
2
3
  import { zIndex } from './stacking.js';
3
4
  const caption = typographyVariants.caption;
4
5
  const label = typographyVariants.label;
5
- const chevronImage = `background-image:
6
- linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),
7
- linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%);
8
- background-repeat: no-repeat;
9
- background-size: var(--sw-space-1) var(--sw-space-1),
10
- var(--sw-space-1) var(--sw-space-1);`;
11
6
  export function cssSelectClasses() {
12
7
  return `.sw-select {
13
8
  max-width: 100%;
@@ -19,13 +14,10 @@ export function cssSelectClasses() {
19
14
  }
20
15
 
21
16
  .sw-select-trigger {
17
+ align-items: center;
22
18
  appearance: none;
23
19
  -webkit-appearance: none;
24
20
  background-color: var(--sw-glass-fill);
25
- ${chevronImage}
26
- background-position:
27
- calc(100% - var(--sw-space-4)) calc(50% - 1px),
28
- calc(100% - calc(var(--sw-space-4) - var(--sw-space-1))) calc(50% - 1px);
29
21
  border: 1px solid var(--sw-color-border);
30
22
  border-radius: var(--sw-radius-sm);
31
23
  box-sizing: border-box;
@@ -34,19 +26,60 @@ export function cssSelectClasses() {
34
26
  display: inline-flex;
35
27
  font-family: inherit;
36
28
  font-size: inherit;
29
+ gap: var(--sw-space-3);
37
30
  letter-spacing: inherit;
38
31
  line-height: inherit;
39
32
  margin: 0;
33
+ max-width: 100%;
40
34
  min-height: var(--sw-control-md-min-height);
41
35
  padding-block: 0;
42
36
  padding-inline: var(--sw-control-md-padding-inline);
43
- padding-inline-end: calc(
44
- var(--sw-control-md-padding-inline) + var(--sw-space-5)
45
- );
46
37
  text-align: start;
38
+ }
39
+
40
+ /* The chevron Accordion uses, pointing down, centred on the text. */
41
+ .sw-select-trigger::after {
42
+ ${chevronStroke}
43
+ content: '';
44
+ flex: none;
45
+ transform: translateY(-25%) rotate(45deg);
46
+ }
47
+
48
+ /*
49
+ * The current label and a hidden copy of every option's label share one grid
50
+ * cell, so the trigger is as wide as the longest option and keeps that width
51
+ * when the value changes; past the available width the label ellipsizes.
52
+ */
53
+ .sw-select-value {
54
+ display: grid;
55
+ flex: 1 1 auto;
56
+ min-width: 0;
57
+ }
58
+
59
+ .sw-select-value > * {
60
+ grid-area: 1 / 1;
61
+ min-width: 0;
62
+ overflow: hidden;
63
+ text-overflow: ellipsis;
47
64
  white-space: nowrap;
48
65
  }
49
66
 
67
+ .sw-select-value-sizer {
68
+ visibility: hidden;
69
+ }
70
+
71
+ .sw-select-placeholder {
72
+ color: var(--sw-color-muted);
73
+ }
74
+
75
+ .sw-select-invalid .sw-select-trigger {
76
+ border-color: var(--sw-color-danger);
77
+ }
78
+
79
+ .sw-select-value-sizer::before {
80
+ content: attr(data-label);
81
+ }
82
+
50
83
  .sw-select-trigger:focus {
51
84
  box-shadow: none;
52
85
  outline: none;
@@ -58,17 +91,17 @@ export function cssSelectClasses() {
58
91
  }
59
92
 
60
93
  .sw-select-xs .sw-select-trigger {
61
- background-position:
62
- calc(100% - var(--sw-space-3)) calc(50% - 1px),
63
- calc(100% - calc(var(--sw-space-3) - var(--sw-space-1))) calc(50% - 1px);
64
94
  font-size: ${caption.fontSize}px;
95
+ gap: var(--sw-space-2);
65
96
  letter-spacing: ${caption.letterSpacing}px;
66
97
  line-height: ${caption.lineHeight}px;
67
98
  min-height: var(--sw-control-xs-min-height);
68
99
  padding-inline: var(--sw-control-xs-padding-inline);
69
- padding-inline-end: calc(
70
- var(--sw-control-xs-padding-inline) + var(--sw-space-5)
71
- );
100
+ }
101
+
102
+ @media (forced-colors: active) {
103
+ .sw-select-trigger::after { border-color: CanvasText; }
104
+ .sw-select-invalid .sw-select-trigger { border-color: Mark; }
72
105
  }
73
106
 
74
107
  .sw-select-list {
@@ -128,6 +161,11 @@ export function selectClassCatalog() {
128
161
  'sw-select-xs',
129
162
  'sw-select-control',
130
163
  'sw-select-trigger',
164
+ 'sw-select-value',
165
+ 'sw-select-value-text',
166
+ 'sw-select-value-sizer',
167
+ 'sw-select-placeholder',
168
+ 'sw-select-invalid',
131
169
  'sw-select-list',
132
170
  'sw-select-option',
133
171
  'sw-select-action',
@@ -1,2 +1,4 @@
1
+ export declare const toastTones: readonly ["neutral", "success", "warning", "danger"];
2
+ export type ToastTone = (typeof toastTones)[number];
1
3
  export declare function cssToastClasses(): string;
2
4
  export declare function toastClassCatalog(): string[];
@@ -1,3 +1,14 @@
1
+ export const toastTones = ['neutral', 'success', 'warning', 'danger'];
2
+ const tintedTones = toastTones.filter((tone) => tone !== 'neutral');
3
+ /*
4
+ * A tone tints the toast's border and its icon; the message keeps the text
5
+ * color, so it reads the same in every tone and the word carries the meaning.
6
+ */
7
+ function toneRules() {
8
+ return tintedTones
9
+ .map((tone) => `.sw-toast-${tone} { --sw-toast-tone: var(--sw-color-${tone}); border-color: var(--sw-toast-tone); }`)
10
+ .join('\n');
11
+ }
1
12
  export function cssToastClasses() {
2
13
  return `.sw-toast {
3
14
  background: var(--sw-glass-fill);
@@ -19,6 +30,29 @@ export function cssToastClasses() {
19
30
  width: max-content;
20
31
  }
21
32
 
33
+ ${toneRules()}
34
+
35
+ .sw-toast-row {
36
+ align-items: center;
37
+ display: flex;
38
+ gap: var(--sw-space-2);
39
+ }
40
+
41
+ .sw-toast-icon {
42
+ color: var(--sw-toast-tone, currentColor);
43
+ display: inline-flex;
44
+ flex: none;
45
+ }
46
+
47
+ .sw-toast-body {
48
+ min-width: 0;
49
+ }
50
+
51
+ @media (forced-colors: active) {
52
+ .sw-toast { border-color: CanvasText; }
53
+ .sw-toast-icon { color: CanvasText; }
54
+ }
55
+
22
56
  .sw-toast:popover-open {
23
57
  animation: sw-toast-float var(--sw-motion-default) var(--sw-motion-easing);
24
58
  }
@@ -88,5 +122,12 @@ export function cssToastClasses() {
88
122
  }`;
89
123
  }
90
124
  export function toastClassCatalog() {
91
- return ['sw-toast', 'sw-toast-travel'];
125
+ return [
126
+ 'sw-toast',
127
+ ...tintedTones.map((tone) => `sw-toast-${tone}`),
128
+ 'sw-toast-row',
129
+ 'sw-toast-icon',
130
+ 'sw-toast-body',
131
+ 'sw-toast-travel',
132
+ ];
92
133
  }
@@ -1 +1,3 @@
1
+ /** The `sw-sr-only` declarations: kept for assistive technology, not drawn. */
2
+ export declare const visuallyHiddenDeclarations = "position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;";
1
3
  export declare function cssUtilities(): string;
@@ -1,5 +1,7 @@
1
1
  import { spacingSteps } from '@scalewing/tokens';
2
2
  import { gapAxes, layoutClassNames, paddingAxes, spacingClass, } from './spacing-classes.js';
3
+ /** The `sw-sr-only` declarations: kept for assistive technology, not drawn. */
4
+ export const visuallyHiddenDeclarations = 'position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;';
3
5
  function paddingRule(axis, step) {
4
6
  const className = spacingClass('padding', axis, step);
5
7
  const value = `var(--sw-space-${step})`;
@@ -39,7 +41,7 @@ const layoutRules = {
39
41
  'sw-grow': '.sw-grow { flex-grow: 1; }',
40
42
  'sw-full-width': '.sw-full-width { width: 100%; }',
41
43
  'sw-container': '.sw-container { width: 100%; max-width: var(--sw-container-max); margin-inline: auto; }',
42
- 'sw-sr-only': '.sw-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }',
44
+ 'sw-sr-only': `.sw-sr-only { ${visuallyHiddenDeclarations} }`,
43
45
  'sw-align-start': '.sw-align-start { align-items: flex-start; }',
44
46
  'sw-align-center': '.sw-align-center { align-items: center; }',
45
47
  'sw-align-end': '.sw-align-end { align-items: flex-end; }',
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ActionMenu, type ActionMenuItem, type ActionMenuProps, } from './components/ActionMenu.js';
1
+ export { ActionMenu, type ActionMenuAlign, type ActionMenuItem, type ActionMenuProps, } from './components/ActionMenu.js';
2
2
  export { ActionBar, type ActionBarProps } from './components/ActionBar.js';
3
3
  export { Accordion } from './components/Accordion.js';
4
4
  export type { AccordionProps, AccordionSize } from './components/Accordion.js';
@@ -20,8 +20,8 @@ export { Card, type CardProps, type CardVariant } from './components/Card.js';
20
20
  export { Dialog, type DialogProps, type DialogSize, } from './components/Dialog.js';
21
21
  export { StatTile, type StatTileEmphasis, type StatTileProps, type StatTileTone, statTileTones, } from './components/StatTile.js';
22
22
  export { TabPanel, type TabItem, type TabPanelProps, Tabs, type TabsProps, tabId, tabPanelId, } from './components/Tabs.js';
23
- export { Toast } from './components/Toast.js';
24
- export type { ToastProps } from './components/Toast.js';
23
+ export { Toast, toastTones } from './components/Toast.js';
24
+ export type { ToastProps, ToastTone } from './components/Toast.js';
25
25
  export { Field, type FieldProps, type FieldSize } from './components/Field.js';
26
26
  export { Inline, type InlineProps } from './components/Inline.js';
27
27
  export { Split } from './components/Split.js';
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ export { Card } from './components/Card.js';
19
19
  export { Dialog, } from './components/Dialog.js';
20
20
  export { StatTile, statTileTones, } from './components/StatTile.js';
21
21
  export { TabPanel, Tabs, tabId, tabPanelId, } from './components/Tabs.js';
22
- export { Toast } from './components/Toast.js';
22
+ export { Toast, toastTones } from './components/Toast.js';
23
23
  export { Field } from './components/Field.js';
24
24
  export { Inline } from './components/Inline.js';
25
25
  export { Split } from './components/Split.js';
package/dist/styles.css CHANGED
@@ -849,15 +849,16 @@ body {
849
849
  opacity: var(--sw-disabled-opacity);
850
850
  }
851
851
 
852
- .sw-button[aria-pressed='false'] {
853
- opacity: var(--sw-quiet-opacity);
854
- }
855
-
856
852
  .sw-button-primary { background: var(--sw-color-accent); border-color: transparent; color: var(--sw-color-onAccent); }
857
853
  .sw-button-secondary { background: var(--sw-color-secondary); border-color: var(--sw-button-secondary-border); color: var(--sw-color-onSecondary); }
858
854
  .sw-button-tertiary { background: var(--sw-color-tertiary); border-color: transparent; color: var(--sw-color-onTertiary); }
859
855
  .sw-button-ghost { background: transparent; border-color: transparent; color: var(--sw-color-accent); }
860
856
  .sw-button-danger { background: var(--sw-color-danger); border-color: transparent; color: var(--sw-color-onDanger); }
857
+ .sw-button[aria-pressed='true'] { box-shadow: 0 0 0 2px var(--sw-color-background), 0 0 0 4px var(--sw-color-accent); }
858
+ .sw-button[aria-pressed='true']:focus-visible { outline-offset: calc(var(--sw-focus-ring-offset) + 4px); }
859
+ @media (forced-colors: active) {
860
+ .sw-button[aria-pressed='true'] { background: Highlight; border-color: Highlight; color: HighlightText; }
861
+ }
861
862
  .sw-button-xs { min-height: 28px; padding-inline: 10px; }
862
863
  .sw-button-sm { min-height: 32px; padding-inline: 12px; }
863
864
  .sw-button-md { min-height: 44px; padding-inline: 16px; }
@@ -968,6 +969,7 @@ body {
968
969
 
969
970
  .sw-checkbox-description { color: var(--sw-color-muted); }
970
971
  .sw-checkbox-error { color: var(--sw-color-danger); }
972
+ .sw-checkbox-error:empty { position: absolute; }
971
973
 
972
974
  @media (prefers-reduced-transparency: reduce) {
973
975
  .sw-checkbox-mark { background: var(--sw-color-surface); }
@@ -1071,7 +1073,7 @@ body {
1071
1073
  outline-offset: var(--sw-focus-ring-offset);
1072
1074
  }
1073
1075
 
1074
- .sw-radio-group:has(.sw-radio-group-error) .sw-radio-group-mark {
1076
+ .sw-radio-group[aria-invalid='true'] .sw-radio-group-mark {
1075
1077
  border-color: var(--sw-color-danger);
1076
1078
  }
1077
1079
 
@@ -1098,6 +1100,7 @@ body {
1098
1100
 
1099
1101
  .sw-radio-group-description { color: var(--sw-color-muted); }
1100
1102
  .sw-radio-group-error { color: var(--sw-color-danger); }
1103
+ .sw-radio-group-error:empty { position: absolute; }
1101
1104
 
1102
1105
  @media (prefers-reduced-transparency: reduce) {
1103
1106
  .sw-radio-group-mark { background: var(--sw-color-surface); }
@@ -1108,7 +1111,7 @@ body {
1108
1111
  .sw-radio-group-mark { background: Canvas; border-color: CanvasText; }
1109
1112
  .sw-radio-group-input:checked + .sw-radio-group-mark { background: Highlight; border-color: Highlight; }
1110
1113
  .sw-radio-group-input:checked + .sw-radio-group-mark::after { background: HighlightText; }
1111
- .sw-radio-group:has(.sw-radio-group-error) .sw-radio-group-mark { border-color: CanvasText; }
1114
+ .sw-radio-group[aria-invalid='true'] .sw-radio-group-mark { border-color: CanvasText; }
1112
1115
  }
1113
1116
 
1114
1117
  .sw-badge {
@@ -1163,6 +1166,8 @@ body {
1163
1166
  color: var(--sw-color-warning);
1164
1167
  }
1165
1168
 
1169
+ :is(.sw-button-primary, .sw-button-secondary, .sw-button-tertiary, .sw-button-danger) .sw-badge { background: var(--sw-color-surface); color: var(--sw-color-text); }
1170
+
1166
1171
  .sw-segmented {
1167
1172
  background: var(--sw-glass-fill);
1168
1173
  border: 1px solid var(--sw-glass-border);
@@ -1431,6 +1436,7 @@ body {
1431
1436
 
1432
1437
  .sw-date-field-description { color: var(--sw-color-muted); }
1433
1438
  .sw-date-field-error { color: var(--sw-color-danger); }
1439
+ .sw-date-field-error:empty { position: absolute; }
1434
1440
 
1435
1441
  .sw-date-field-calendar {
1436
1442
  background: var(--sw-glass-fill);
@@ -1811,12 +1817,12 @@ body {
1811
1817
  border-bottom: 2px solid var(--sw-color-muted);
1812
1818
  border-right: 2px solid var(--sw-color-muted);
1813
1819
  box-sizing: border-box;
1814
- flex: none;
1815
1820
  height: var(--sw-space-2);
1821
+ width: var(--sw-space-2);
1822
+ flex: none;
1816
1823
  margin-inline-end: var(--sw-space-1);
1817
1824
  transform: rotate(-45deg);
1818
1825
  transition: transform var(--sw-motion-default) var(--sw-motion-easing);
1819
- width: var(--sw-space-2);
1820
1826
  }
1821
1827
 
1822
1828
  .sw-accordion-marker:dir(rtl) { transform: rotate(135deg); }
@@ -1843,18 +1849,10 @@ body {
1843
1849
  }
1844
1850
 
1845
1851
  .sw-select-trigger {
1852
+ align-items: center;
1846
1853
  appearance: none;
1847
1854
  -webkit-appearance: none;
1848
1855
  background-color: var(--sw-glass-fill);
1849
- background-image:
1850
- linear-gradient(45deg, transparent 50%, var(--sw-color-muted) 50%),
1851
- linear-gradient(135deg, var(--sw-color-muted) 50%, transparent 50%);
1852
- background-repeat: no-repeat;
1853
- background-size: var(--sw-space-1) var(--sw-space-1),
1854
- var(--sw-space-1) var(--sw-space-1);
1855
- background-position:
1856
- calc(100% - var(--sw-space-4)) calc(50% - 1px),
1857
- calc(100% - calc(var(--sw-space-4) - var(--sw-space-1))) calc(50% - 1px);
1858
1856
  border: 1px solid var(--sw-color-border);
1859
1857
  border-radius: var(--sw-radius-sm);
1860
1858
  box-sizing: border-box;
@@ -1863,19 +1861,64 @@ body {
1863
1861
  display: inline-flex;
1864
1862
  font-family: inherit;
1865
1863
  font-size: inherit;
1864
+ gap: var(--sw-space-3);
1866
1865
  letter-spacing: inherit;
1867
1866
  line-height: inherit;
1868
1867
  margin: 0;
1868
+ max-width: 100%;
1869
1869
  min-height: var(--sw-control-md-min-height);
1870
1870
  padding-block: 0;
1871
1871
  padding-inline: var(--sw-control-md-padding-inline);
1872
- padding-inline-end: calc(
1873
- var(--sw-control-md-padding-inline) + var(--sw-space-5)
1874
- );
1875
1872
  text-align: start;
1873
+ }
1874
+
1875
+ /* The chevron Accordion uses, pointing down, centred on the text. */
1876
+ .sw-select-trigger::after {
1877
+ border-bottom: 2px solid var(--sw-color-muted);
1878
+ border-right: 2px solid var(--sw-color-muted);
1879
+ box-sizing: border-box;
1880
+ height: var(--sw-space-2);
1881
+ width: var(--sw-space-2);
1882
+ content: '';
1883
+ flex: none;
1884
+ transform: translateY(-25%) rotate(45deg);
1885
+ }
1886
+
1887
+ /*
1888
+ * The current label and a hidden copy of every option's label share one grid
1889
+ * cell, so the trigger is as wide as the longest option and keeps that width
1890
+ * when the value changes; past the available width the label ellipsizes.
1891
+ */
1892
+ .sw-select-value {
1893
+ display: grid;
1894
+ flex: 1 1 auto;
1895
+ min-width: 0;
1896
+ }
1897
+
1898
+ .sw-select-value > * {
1899
+ grid-area: 1 / 1;
1900
+ min-width: 0;
1901
+ overflow: hidden;
1902
+ text-overflow: ellipsis;
1876
1903
  white-space: nowrap;
1877
1904
  }
1878
1905
 
1906
+ .sw-select-value-sizer {
1907
+ visibility: hidden;
1908
+ }
1909
+
1910
+ .sw-select-placeholder {
1911
+ color: var(--sw-color-muted);
1912
+ }
1913
+
1914
+ .sw-select-invalid .sw-select-trigger {
1915
+ border-color: var(--sw-color-danger);
1916
+ }
1917
+
1918
+ .sw-select-value-sizer::before {
1919
+ content: attr(data-label);
1920
+ }
1921
+
1879
1922
  .sw-select-trigger:focus {
1880
1923
  box-shadow: none;
1881
1924
  outline: none;
@@ -1887,17 +1930,17 @@ body {
1887
1930
  }
1888
1931
 
1889
1932
  .sw-select-xs .sw-select-trigger {
1890
- background-position:
1891
- calc(100% - var(--sw-space-3)) calc(50% - 1px),
1892
- calc(100% - calc(var(--sw-space-3) - var(--sw-space-1))) calc(50% - 1px);
1893
1933
  font-size: 13px;
1934
+ gap: var(--sw-space-2);
1894
1935
  letter-spacing: -0.08px;
1895
1936
  line-height: 18px;
1896
1937
  min-height: var(--sw-control-xs-min-height);
1897
1938
  padding-inline: var(--sw-control-xs-padding-inline);
1898
- padding-inline-end: calc(
1899
- var(--sw-control-xs-padding-inline) + var(--sw-space-5)
1900
- );
1939
+ }
1940
+
1941
+ @media (forced-colors: active) {
1942
+ .sw-select-trigger::after { border-color: CanvasText; }
1943
+ .sw-select-invalid .sw-select-trigger { border-color: Mark; }
1901
1944
  }
1902
1945
 
1903
1946
  .sw-select-list {
@@ -2544,8 +2587,15 @@ body {
2544
2587
  line-height: 18px;
2545
2588
  }
2546
2589
 
2590
+ /*
2591
+ * A zero-count chip is quiet without fading: it drops the glass fill and
2592
+ * sets its label in the muted color, which is 4.5:1 on the canvas, surface
2593
+ * and subtle fill in every palette. It can still be chosen, so it never
2594
+ * reads as disabled.
2595
+ */
2547
2596
  .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face {
2548
- opacity: var(--sw-quiet-opacity);
2597
+ background: transparent;
2598
+ color: var(--sw-color-muted);
2549
2599
  }
2550
2600
 
2551
2601
  .sw-filter-chip-input:checked + .sw-filter-chip-face {
@@ -2573,7 +2623,6 @@ body {
2573
2623
  .sw-filter-chip-input:checked + .sw-filter-chip-face { background: Highlight; border-color: Highlight; color: HighlightText; }
2574
2624
  .sw-filter-chip-input:focus-visible + .sw-filter-chip-face { outline-color: Highlight; }
2575
2625
  .sw-filter-chip-count { color: inherit; }
2576
- .sw-filter-chip-quiet:not(:has(.sw-filter-chip-input:checked)) .sw-filter-chip-face { opacity: 1; color: GrayText; border-color: GrayText; }
2577
2626
  }
2578
2627
 
2579
2628
  .sw-grid {
@@ -2837,8 +2886,15 @@ body {
2837
2886
  text-align: center;
2838
2887
  }
2839
2888
 
2889
+ /*
2890
+ * Below md the aria-hidden copy under each row label is what shows; the
2891
+ * total cell stays in the table, visually hidden, so a screen reader still
2892
+ * reads each total in its row and under its column header.
2893
+ */
2840
2894
  @media not all and (min-width: 48rem) {
2841
- .sw-denomination-strip .sw-denomination-total { display: none; }
2895
+ .sw-denomination-strip .sw-denomination-total,
2896
+ .sw-denomination-strip .sw-denomination-total-head { padding: 0; position: relative; }
2897
+ .sw-denomination-total-value { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
2842
2898
  .sw-denomination-total-inline { display: block; }
2843
2899
  .sw-denomination-label-body { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
2844
2900
  .sw-denomination-strip th,
@@ -2869,6 +2925,9 @@ body {
2869
2925
  .sw-field-description { color: var(--sw-color-muted); }
2870
2926
  .sw-field-error { color: var(--sw-color-danger); }
2871
2927
 
2928
+ /* An error region is always rendered as a live region; empty, it leaves the layout (and its gap) but stays in the accessibility tree. */
2929
+ .sw-field-error:empty { position: absolute; }
2930
+
2872
2931
  [data-theme] .sw-field-invalid :is(input, select, textarea) {
2873
2932
  border-color: var(--sw-color-danger);
2874
2933
  }
@@ -3015,6 +3074,31 @@ body {
3015
3074
  width: max-content;
3016
3075
  }
3017
3076
 
3077
+ .sw-toast-success { --sw-toast-tone: var(--sw-color-success); border-color: var(--sw-toast-tone); }
3078
+ .sw-toast-warning { --sw-toast-tone: var(--sw-color-warning); border-color: var(--sw-toast-tone); }
3079
+ .sw-toast-danger { --sw-toast-tone: var(--sw-color-danger); border-color: var(--sw-toast-tone); }
3080
+
3081
+ .sw-toast-row {
3082
+ align-items: center;
3083
+ display: flex;
3084
+ gap: var(--sw-space-2);
3085
+ }
3086
+
3087
+ .sw-toast-icon {
3088
+ color: var(--sw-toast-tone, currentColor);
3089
+ display: inline-flex;
3090
+ flex: none;
3091
+ }
3092
+
3093
+ .sw-toast-body {
3094
+ min-width: 0;
3095
+ }
3096
+
3097
+ @media (forced-colors: active) {
3098
+ .sw-toast { border-color: CanvasText; }
3099
+ .sw-toast-icon { color: CanvasText; }
3100
+ }
3101
+
3018
3102
  .sw-toast:popover-open {
3019
3103
  animation: sw-toast-float var(--sw-motion-default) var(--sw-motion-easing);
3020
3104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalewing/react",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Scalewing DOM components and package-owned CSS utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",