@scalewing/react 1.11.0 → 1.13.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/CHANGELOG.md +100 -0
- package/README.md +62 -7
- package/dist/anchored-position.d.ts +14 -6
- package/dist/anchored-position.js +10 -9
- package/dist/components/ActionMenu.d.ts +9 -1
- package/dist/components/ActionMenu.js +2 -2
- package/dist/components/Checkbox.js +2 -1
- package/dist/components/DateField.js +4 -2
- package/dist/components/DenominationGrid.d.ts +1 -1
- package/dist/components/DenominationGrid.js +4 -2
- package/dist/components/Field.js +9 -6
- package/dist/components/FieldErrorRegion.d.ts +14 -0
- package/dist/components/FieldErrorRegion.js +13 -0
- package/dist/components/FieldLabelText.d.ts +13 -0
- package/dist/components/FieldLabelText.js +12 -0
- package/dist/components/RadioGroup.d.ts +7 -0
- package/dist/components/RadioGroup.js +2 -1
- package/dist/components/SegmentedControl.d.ts +8 -0
- package/dist/components/SegmentedControl.js +16 -11
- package/dist/components/Select.d.ts +10 -1
- package/dist/components/Select.js +17 -86
- package/dist/components/Toast.d.ts +15 -1
- package/dist/components/Toast.js +15 -5
- package/dist/components/action-menu/ActionMenuList.d.ts +6 -4
- package/dist/components/action-menu/ActionMenuList.js +5 -4
- package/dist/components/denomination-grid/DenominationStrip.d.ts +2 -2
- package/dist/components/denomination-grid/DenominationStrip.js +13 -3
- package/dist/components/denomination-grid/DenominationTiles.js +27 -8
- package/dist/components/denomination-grid/types.d.ts +5 -0
- package/dist/components/select/SelectListbox.d.ts +20 -0
- package/dist/components/select/SelectListbox.js +16 -0
- package/dist/components/select/SelectValue.d.ts +15 -0
- package/dist/components/select/SelectValue.js +16 -0
- package/dist/components/select/use-select-list.d.ts +26 -0
- package/dist/components/select/use-select-list.js +85 -0
- package/dist/components/use-anchored-popover.d.ts +5 -2
- package/dist/components/use-anchored-popover.js +6 -5
- package/dist/css/chevron.d.ts +5 -0
- package/dist/css/chevron.js +9 -0
- package/dist/css/css-accordion.js +2 -5
- package/dist/css/css-button.d.ts +2 -0
- package/dist/css/css-button.js +28 -4
- package/dist/css/css-checkbox.js +1 -0
- package/dist/css/css-components.js +6 -0
- package/dist/css/css-data.js +17 -163
- package/dist/css/css-date-field.js +14 -6
- package/dist/css/css-denomination-grid.js +11 -1
- package/dist/css/css-document.d.ts +2 -0
- package/dist/css/css-document.js +35 -0
- package/dist/css/css-field.js +15 -2
- package/dist/css/css-filter-chips.js +8 -2
- package/dist/css/css-radio-group.js +12 -2
- package/dist/css/css-segmented.d.ts +2 -0
- package/dist/css/css-segmented.js +127 -0
- package/dist/css/css-select.js +57 -19
- package/dist/css/css-table.d.ts +2 -0
- package/dist/css/css-table.js +99 -0
- package/dist/css/css-toast.d.ts +2 -0
- package/dist/css/css-toast.js +42 -1
- package/dist/css/css-utilities.d.ts +2 -0
- package/dist/css/css-utilities.js +3 -1
- package/dist/css/stylesheet.js +4 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/styles.css +216 -48
- package/package.json +1 -1
|
@@ -13,11 +13,12 @@ function layoutViewport() {
|
|
|
13
13
|
height: root.clientHeight || window.innerHeight,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function placePopover(node, anchor, { flipInline = false }) {
|
|
16
|
+
function placePopover(node, anchor, { align = 'start', flipInline = false }) {
|
|
17
17
|
const position = anchoredPosition(anchor.getBoundingClientRect(), { width: node.offsetWidth, height: node.offsetHeight }, layoutViewport(), {
|
|
18
18
|
gap: spacingScale[1],
|
|
19
19
|
inset: spacingScale[2],
|
|
20
20
|
rtl: getComputedStyle(anchor).direction === 'rtl',
|
|
21
|
+
align,
|
|
21
22
|
flipInline,
|
|
22
23
|
});
|
|
23
24
|
node.style.left = `${position.left}px`;
|
|
@@ -31,7 +32,7 @@ function placePopover(node, anchor, { flipInline = false }) {
|
|
|
31
32
|
* outside it and outside `ignoreRef` (the button that toggles it).
|
|
32
33
|
* Browsers without the popover API get the same fixed placement.
|
|
33
34
|
*/
|
|
34
|
-
export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss, { flipInline = false } = {}) {
|
|
35
|
+
export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss, { align = 'start', flipInline = false } = {}) {
|
|
35
36
|
const dismissRef = useRef(onDismiss);
|
|
36
37
|
useLayoutEffect(() => {
|
|
37
38
|
dismissRef.current = onDismiss;
|
|
@@ -49,7 +50,7 @@ export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss,
|
|
|
49
50
|
if (!node.matches(':popover-open'))
|
|
50
51
|
node.showPopover();
|
|
51
52
|
}
|
|
52
|
-
placePopover(node, anchor, { flipInline });
|
|
53
|
+
placePopover(node, anchor, { align, flipInline });
|
|
53
54
|
function onPointerDown(event) {
|
|
54
55
|
const target = event.target;
|
|
55
56
|
if (!(target instanceof Node))
|
|
@@ -60,7 +61,7 @@ export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss,
|
|
|
60
61
|
}
|
|
61
62
|
function onMove() {
|
|
62
63
|
if (node && anchor)
|
|
63
|
-
placePopover(node, anchor, { flipInline });
|
|
64
|
+
placePopover(node, anchor, { align, flipInline });
|
|
64
65
|
}
|
|
65
66
|
// A popover or anchor that changes size while open (new commands, a
|
|
66
67
|
// month with another row of weeks, a relabelled trigger) moves too.
|
|
@@ -79,5 +80,5 @@ export function useAnchoredPopover(popoverRef, anchorRef, ignoreRef, onDismiss,
|
|
|
79
80
|
if (supportsPopover && node.matches(':popover-open'))
|
|
80
81
|
node.hidePopover();
|
|
81
82
|
};
|
|
82
|
-
}, [anchorRef, flipInline, ignoreRef, popoverRef]);
|
|
83
|
+
}, [align, anchorRef, flipInline, ignoreRef, popoverRef]);
|
|
83
84
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stroked chevron Accordion and Select share: two muted sides of a
|
|
3
|
+
* `space-2` square, turned by each surface to point where it needs.
|
|
4
|
+
*/
|
|
5
|
+
export declare const chevronStroke = "border-bottom: 2px solid var(--sw-color-muted);\n border-right: 2px solid var(--sw-color-muted);\n box-sizing: border-box;\n height: var(--sw-space-2);\n width: var(--sw-space-2);";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stroked chevron Accordion and Select share: two muted sides of a
|
|
3
|
+
* `space-2` square, turned by each surface to point where it needs.
|
|
4
|
+
*/
|
|
5
|
+
export const chevronStroke = `border-bottom: 2px solid var(--sw-color-muted);
|
|
6
|
+
border-right: 2px solid var(--sw-color-muted);
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
height: var(--sw-space-2);
|
|
9
|
+
width: var(--sw-space-2);`;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
+
import { chevronStroke } from './chevron.js';
|
|
1
2
|
function markerRules() {
|
|
2
3
|
return `/* A token chevron replaces the user-agent triangle: it points to the inline end when closed and down when open. */
|
|
3
4
|
.sw-accordion-summary::-webkit-details-marker { display: none; }
|
|
4
5
|
|
|
5
6
|
.sw-accordion-marker {
|
|
6
|
-
|
|
7
|
-
border-right: 2px solid var(--sw-color-muted);
|
|
8
|
-
box-sizing: border-box;
|
|
7
|
+
${chevronStroke}
|
|
9
8
|
flex: none;
|
|
10
|
-
height: var(--sw-space-2);
|
|
11
9
|
margin-inline-end: var(--sw-space-1);
|
|
12
10
|
transform: rotate(-45deg);
|
|
13
11
|
transition: transform var(--sw-motion-default) var(--sw-motion-easing);
|
|
14
|
-
width: var(--sw-space-2);
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
.sw-accordion-marker:dir(rtl) { transform: rotate(135deg); }
|
package/dist/css/css-button.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export declare function buttonClassNames(options: {
|
|
|
3
3
|
size: ButtonSize;
|
|
4
4
|
variant: ButtonVariant;
|
|
5
5
|
}): string[];
|
|
6
|
+
export declare const pressedRingGap = 2;
|
|
7
|
+
export declare const pressedRingWidth = 4;
|
|
6
8
|
export declare function cssButtonClasses(): string;
|
|
7
9
|
export declare const buttonGroupJustifies: readonly ["start", "end", "between"];
|
|
8
10
|
export declare function buttonClassCatalog(): string[];
|
package/dist/css/css-button.js
CHANGED
|
@@ -17,6 +17,33 @@ function variantRules() {
|
|
|
17
17
|
`.sw-button-danger { background: var(--sw-color-danger); border-color: transparent; color: var(--sw-color-onDanger); }`,
|
|
18
18
|
].join('\n');
|
|
19
19
|
}
|
|
20
|
+
/*
|
|
21
|
+
* A toggle button's pressed state is an accent ring drawn outside the fill,
|
|
22
|
+
* past a 2 px gap in the page background: the ring meets the background on
|
|
23
|
+
* both sides, so it keeps the accent's contrast on the canvas (4.5:1 or more
|
|
24
|
+
* in every palette, above WCAG 1.4.11's 3:1) whatever the variant's fill.
|
|
25
|
+
* A focused pressed button moves its focus outline out past the ring, so the
|
|
26
|
+
* two stay apart. The unpressed button keeps its full label contrast: no
|
|
27
|
+
* opacity.
|
|
28
|
+
*
|
|
29
|
+
* Forced colors drop box shadows, so there the same ring is a Highlight
|
|
30
|
+
* border on an out-of-flow ::after, and the button's own border turns
|
|
31
|
+
* Highlight. The button keeps the forced ButtonFace and ButtonText of every
|
|
32
|
+
* other button: a Highlight fill cannot be used, because Chromium paints
|
|
33
|
+
* the forced backplate behind a button's text in Canvas, which erased a
|
|
34
|
+
* HighlightText label (1.00:1, Teisoro's pressed toggles in 1.12.0), and
|
|
35
|
+
* forced-color-adjust: none would let every descendant keep author colors.
|
|
36
|
+
*/
|
|
37
|
+
export const pressedRingGap = 2;
|
|
38
|
+
export const pressedRingWidth = 4;
|
|
39
|
+
function pressedRules() {
|
|
40
|
+
return `.sw-button[aria-pressed='true'] { box-shadow: 0 0 0 ${pressedRingGap}px var(--sw-color-background), 0 0 0 ${pressedRingWidth}px var(--sw-color-accent); }
|
|
41
|
+
.sw-button[aria-pressed='true']:focus-visible { outline-offset: calc(var(--sw-focus-ring-offset) + ${pressedRingWidth}px); }
|
|
42
|
+
@media (forced-colors: active) {
|
|
43
|
+
.sw-button[aria-pressed='true'] { border-color: Highlight; position: relative; }
|
|
44
|
+
.sw-button[aria-pressed='true']::after { border: ${pressedRingWidth - pressedRingGap}px solid Highlight; border-radius: inherit; content: ''; inset: calc(-1px - ${pressedRingWidth}px); pointer-events: none; position: absolute; }
|
|
45
|
+
}`;
|
|
46
|
+
}
|
|
20
47
|
function sizeRules() {
|
|
21
48
|
return buttonSizes
|
|
22
49
|
.map((size) => {
|
|
@@ -54,11 +81,8 @@ export function cssButtonClasses() {
|
|
|
54
81
|
opacity: var(--sw-disabled-opacity);
|
|
55
82
|
}
|
|
56
83
|
|
|
57
|
-
.sw-button[aria-pressed='false'] {
|
|
58
|
-
opacity: var(--sw-quiet-opacity);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
84
|
${variantRules()}
|
|
85
|
+
${pressedRules()}
|
|
62
86
|
${sizeRules()}
|
|
63
87
|
${groupRules()}`;
|
|
64
88
|
}
|
package/dist/css/css-checkbox.js
CHANGED
|
@@ -100,6 +100,7 @@ export function cssCheckboxClasses() {
|
|
|
100
100
|
|
|
101
101
|
.sw-checkbox-description { color: var(--sw-color-muted); }
|
|
102
102
|
.sw-checkbox-error { color: var(--sw-color-danger); }
|
|
103
|
+
.sw-checkbox-error:empty { position: absolute; }
|
|
103
104
|
|
|
104
105
|
@media (prefers-reduced-transparency: reduce) {
|
|
105
106
|
.sw-checkbox-mark { background: var(--sw-color-surface); }
|
|
@@ -8,6 +8,8 @@ import { cssCheckboxClasses } from './css-checkbox.js';
|
|
|
8
8
|
import { cssRadioGroupClasses } from './css-radio-group.js';
|
|
9
9
|
import { cssChromeClasses } from './css-chrome.js';
|
|
10
10
|
import { cssDataClasses } from './css-data.js';
|
|
11
|
+
import { cssSegmentedClasses } from './css-segmented.js';
|
|
12
|
+
import { cssTableClasses } from './css-table.js';
|
|
11
13
|
import { cssDateFieldClasses } from './css-date-field.js';
|
|
12
14
|
import { cssDialogClasses } from './css-dialog.js';
|
|
13
15
|
import { cssSelectClasses } from './css-select.js';
|
|
@@ -109,6 +111,10 @@ ${cssRadioGroupClasses()}
|
|
|
109
111
|
|
|
110
112
|
${cssDataClasses()}
|
|
111
113
|
|
|
114
|
+
${cssSegmentedClasses()}
|
|
115
|
+
|
|
116
|
+
${cssTableClasses()}
|
|
117
|
+
|
|
112
118
|
${cssDateFieldClasses()}
|
|
113
119
|
|
|
114
120
|
${cssCalendarButtonClasses()}
|
package/dist/css/css-data.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { scrollRegionRules } from './css-scroll-region.js';
|
|
3
|
-
import { zIndex } from './stacking.js';
|
|
1
|
+
import { buttonVariants, typographyVariants } from '@scalewing/tokens';
|
|
4
2
|
export const badgeTones = [
|
|
5
3
|
'neutral',
|
|
6
4
|
'accent',
|
|
@@ -10,11 +8,24 @@ export const badgeTones = [
|
|
|
10
8
|
];
|
|
11
9
|
export const badgeSizes = ['sm', 'md'];
|
|
12
10
|
const caption = typographyVariants.caption;
|
|
13
|
-
const data = typographyVariants.data;
|
|
14
|
-
const body = typographyVariants.body;
|
|
15
11
|
export function badgeClassNames(tone, size = 'md') {
|
|
16
12
|
return ['sw-badge', `sw-badge-${tone}`, `sw-badge-${size}`];
|
|
17
13
|
}
|
|
14
|
+
/*
|
|
15
|
+
* A badge inside a filled button would otherwise draw its tone on the
|
|
16
|
+
* button's fill (a warning badge on the accent is about 1.1:1), so it takes
|
|
17
|
+
* the surface and sets its words in the text color, which is 4.5:1 on the
|
|
18
|
+
* surface in every palette; its tone stays on its border (3:1 or more), as
|
|
19
|
+
* some palettes' tones are under 4.5:1 as text on the surface. A ghost
|
|
20
|
+
* button has no fill.
|
|
21
|
+
*/
|
|
22
|
+
function badgeOnFilledButtonRules() {
|
|
23
|
+
const filled = buttonVariants
|
|
24
|
+
.filter((variant) => variant !== 'ghost')
|
|
25
|
+
.map((variant) => `.sw-button-${variant}`)
|
|
26
|
+
.join(', ');
|
|
27
|
+
return `:is(${filled}) .sw-badge { background: var(--sw-color-surface); color: var(--sw-color-text); }`;
|
|
28
|
+
}
|
|
18
29
|
export function cssDataClasses() {
|
|
19
30
|
return `.sw-badge {
|
|
20
31
|
align-items: center;
|
|
@@ -68,169 +79,12 @@ export function cssDataClasses() {
|
|
|
68
79
|
color: var(--sw-color-warning);
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
background: var(--sw-glass-fill);
|
|
73
|
-
border: 1px solid var(--sw-glass-border);
|
|
74
|
-
border-radius: var(--sw-radius-pill);
|
|
75
|
-
box-sizing: border-box;
|
|
76
|
-
display: inline-flex;
|
|
77
|
-
gap: ${trackInset}px;
|
|
78
|
-
padding: ${trackInset}px;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.sw-segmented-item {
|
|
82
|
-
appearance: none;
|
|
83
|
-
background: transparent;
|
|
84
|
-
border: 0;
|
|
85
|
-
border-radius: var(--sw-radius-pill);
|
|
86
|
-
color: var(--sw-color-muted);
|
|
87
|
-
cursor: pointer;
|
|
88
|
-
font-family: var(--sw-font-sans);
|
|
89
|
-
font-size: ${data.fontSize}px;
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
letter-spacing: ${data.letterSpacing}px;
|
|
92
|
-
line-height: ${data.lineHeight}px;
|
|
93
|
-
min-height: var(--sw-control-xs-min-height);
|
|
94
|
-
padding-inline: var(--sw-control-xs-padding-inline);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.sw-segmented-item:focus-visible {
|
|
98
|
-
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
99
|
-
outline-offset: var(--sw-focus-ring-offset);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.sw-segmented-item-selected {
|
|
103
|
-
background: var(--sw-color-surface);
|
|
104
|
-
color: var(--sw-color-text);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.sw-segmented-filled {
|
|
108
|
-
display: grid;
|
|
109
|
-
grid-auto-columns: minmax(0, 1fr);
|
|
110
|
-
grid-auto-flow: column;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.sw-segmented-filled .sw-segmented-item {
|
|
114
|
-
align-items: center;
|
|
115
|
-
color: var(--sw-color-text);
|
|
116
|
-
display: inline-flex;
|
|
117
|
-
font-size: ${body.fontSize}px;
|
|
118
|
-
justify-content: center;
|
|
119
|
-
letter-spacing: ${body.letterSpacing}px;
|
|
120
|
-
line-height: ${body.lineHeight}px;
|
|
121
|
-
min-height: var(--sw-control-md-min-height);
|
|
122
|
-
min-width: 0;
|
|
123
|
-
padding-inline: var(--sw-control-md-padding-inline);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.sw-segmented-filled .sw-segmented-item-selected {
|
|
127
|
-
background: var(--sw-color-accent);
|
|
128
|
-
color: var(--sw-color-onAccent);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.sw-segmented-disabled {
|
|
132
|
-
opacity: var(--sw-disabled-opacity);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.sw-segmented-item:disabled {
|
|
136
|
-
cursor: not-allowed;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
${scrollRegionRules('.sw-table-wrap')}
|
|
140
|
-
|
|
141
|
-
.sw-table {
|
|
142
|
-
border-collapse: collapse;
|
|
143
|
-
width: 100%;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.sw-table th,
|
|
147
|
-
.sw-table td {
|
|
148
|
-
border-bottom: 1px solid var(--sw-color-border);
|
|
149
|
-
padding: var(--sw-space-2) var(--sw-space-3);
|
|
150
|
-
text-align: start;
|
|
151
|
-
vertical-align: middle;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.sw-table th {
|
|
155
|
-
color: var(--sw-color-muted);
|
|
156
|
-
font-family: var(--sw-font-sans);
|
|
157
|
-
font-size: ${caption.fontSize}px;
|
|
158
|
-
font-weight: 600;
|
|
159
|
-
letter-spacing: ${caption.letterSpacing}px;
|
|
160
|
-
line-height: ${caption.lineHeight}px;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/* Descendant form outranks .sw-table th / .sw-table td, which set text-align: start. */
|
|
164
|
-
.sw-table .sw-table-end {
|
|
165
|
-
text-align: end;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.sw-table .sw-table-numeric {
|
|
169
|
-
font-variant-numeric: tabular-nums;
|
|
170
|
-
text-align: end;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.sw-table-clip {
|
|
174
|
-
max-width: 0;
|
|
175
|
-
overflow: hidden;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.sw-table-sticky thead th {
|
|
179
|
-
background: var(--sw-glass-fill);
|
|
180
|
-
backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
181
|
-
-webkit-backdrop-filter: blur(var(--sw-glass-blur)) saturate(var(--sw-glass-saturate));
|
|
182
|
-
position: sticky;
|
|
183
|
-
top: 0;
|
|
184
|
-
${zIndex('stickyCell')}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.sw-table-compact th,
|
|
188
|
-
.sw-table-compact td {
|
|
189
|
-
padding: var(--sw-space-1) var(--sw-space-2);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.sw-table-row-selected > :first-child {
|
|
193
|
-
padding-inline-start: var(--sw-space-5);
|
|
194
|
-
position: relative;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.sw-table-compact .sw-table-row-selected > :first-child {
|
|
198
|
-
padding-inline-start: var(--sw-space-4);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.sw-table-row-selected > :first-child::before {
|
|
202
|
-
background: var(--sw-color-accent);
|
|
203
|
-
border-radius: var(--sw-radius-pill);
|
|
204
|
-
content: '';
|
|
205
|
-
height: var(--sw-space-2);
|
|
206
|
-
inset-block-start: 50%;
|
|
207
|
-
inset-inline-start: var(--sw-space-2);
|
|
208
|
-
position: absolute;
|
|
209
|
-
transform: translateY(-50%);
|
|
210
|
-
width: var(--sw-space-2);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.sw-table-compact .sw-table-row-selected > :first-child::before {
|
|
214
|
-
inset-inline-start: var(--sw-space-1);
|
|
215
|
-
}`;
|
|
82
|
+
${badgeOnFilledButtonRules()}`;
|
|
216
83
|
}
|
|
217
84
|
export function dataClassCatalog() {
|
|
218
85
|
return [
|
|
219
86
|
'sw-badge',
|
|
220
87
|
...badgeTones.map((tone) => `sw-badge-${tone}`),
|
|
221
88
|
...badgeSizes.map((size) => `sw-badge-${size}`),
|
|
222
|
-
'sw-segmented',
|
|
223
|
-
'sw-segmented-item',
|
|
224
|
-
'sw-segmented-item-selected',
|
|
225
|
-
'sw-segmented-filled',
|
|
226
|
-
'sw-segmented-disabled',
|
|
227
|
-
'sw-table-wrap',
|
|
228
|
-
'sw-table',
|
|
229
|
-
'sw-table-end',
|
|
230
|
-
'sw-table-numeric',
|
|
231
|
-
'sw-table-clip',
|
|
232
|
-
'sw-table-sticky',
|
|
233
|
-
'sw-table-compact',
|
|
234
|
-
'sw-table-row-selected',
|
|
235
89
|
];
|
|
236
90
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
|
|
2
3
|
import { zIndex } from './stacking.js';
|
|
3
4
|
const body = typographyVariants.body;
|
|
4
5
|
const label = typographyVariants.label;
|
|
@@ -19,12 +20,11 @@ function fieldRules() {
|
|
|
19
20
|
width: max-content;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/* The label inherits the canvas's body type, as Field's does; the label
|
|
24
|
+
words inside it are a Text label span. The same line box gives the same
|
|
25
|
+
gap to the entry as a Field beside it. */
|
|
22
26
|
.sw-date-field-label {
|
|
23
27
|
color: var(--sw-color-text);
|
|
24
|
-
font-family: var(--sw-font-sans);
|
|
25
|
-
font-size: ${label.fontSize}px;
|
|
26
|
-
font-weight: ${label.fontWeight};
|
|
27
|
-
line-height: ${label.lineHeight}px;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.sw-date-field-control {
|
|
@@ -71,7 +71,11 @@ ${input}[aria-invalid='true'] {
|
|
|
71
71
|
border-color: var(--sw-color-danger);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
/* The entry looks locked as every text control does; its button fades as buttons do. */
|
|
75
|
+
${input}:disabled {
|
|
76
|
+
${disabledControlSurface}
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
.sw-date-field-button:disabled {
|
|
76
80
|
cursor: not-allowed;
|
|
77
81
|
opacity: var(--sw-disabled-opacity);
|
|
@@ -123,7 +127,8 @@ ${input}:disabled,
|
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
.sw-date-field-description { color: var(--sw-color-muted); }
|
|
126
|
-
.sw-date-field-error { color: var(--sw-color-danger); }
|
|
130
|
+
.sw-date-field-error { color: var(--sw-color-danger); }
|
|
131
|
+
.sw-date-field-error:empty { position: absolute; }`;
|
|
127
132
|
}
|
|
128
133
|
function calendarRules() {
|
|
129
134
|
return `.sw-date-field-calendar {
|
|
@@ -269,6 +274,9 @@ function adaptiveRules() {
|
|
|
269
274
|
|
|
270
275
|
@media (forced-colors: active) {
|
|
271
276
|
${input}[aria-invalid='true'] { border-color: CanvasText; }
|
|
277
|
+
${input}:disabled {
|
|
278
|
+
${disabledControlForcedColors}
|
|
279
|
+
}
|
|
272
280
|
.sw-date-field-calendar { border-color: CanvasText; }
|
|
273
281
|
.sw-date-field-day[aria-current='date'] { border-color: CanvasText; }
|
|
274
282
|
.sw-date-field-day[aria-selected='true'] {
|
|
@@ -2,6 +2,7 @@ import { typographyVariants } from '@scalewing/tokens';
|
|
|
2
2
|
import { breakpointQuery } from './breakpoints.js';
|
|
3
3
|
import { badgeTones } from './css-data.js';
|
|
4
4
|
import { scrollRegionRules } from './css-scroll-region.js';
|
|
5
|
+
import { visuallyHiddenDeclarations } from './css-utilities.js';
|
|
5
6
|
import { zIndex } from './stacking.js';
|
|
6
7
|
const caption = typographyVariants.caption;
|
|
7
8
|
const data = typographyVariants.data;
|
|
@@ -209,8 +210,15 @@ ${scrollRegionRules('.sw-denomination-scroll')}
|
|
|
209
210
|
text-align: center;
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
/*
|
|
214
|
+
* Below md the aria-hidden copy under each row label is what shows; the
|
|
215
|
+
* total cell stays in the table, visually hidden, so a screen reader still
|
|
216
|
+
* reads each total in its row and under its column header.
|
|
217
|
+
*/
|
|
212
218
|
@media ${breakpointQuery('below', 'md')} {
|
|
213
|
-
.sw-denomination-strip .sw-denomination-total
|
|
219
|
+
.sw-denomination-strip .sw-denomination-total,
|
|
220
|
+
.sw-denomination-strip .sw-denomination-total-head { padding: 0; position: relative; }
|
|
221
|
+
.sw-denomination-total-value { ${visuallyHiddenDeclarations} }
|
|
214
222
|
.sw-denomination-total-inline { display: block; }
|
|
215
223
|
.sw-denomination-label-body { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
|
|
216
224
|
.sw-denomination-strip th,
|
|
@@ -249,6 +257,8 @@ export function denominationGridClassCatalog() {
|
|
|
249
257
|
'sw-denomination-cell-negative',
|
|
250
258
|
'sw-denomination-total',
|
|
251
259
|
'sw-denomination-total-inline',
|
|
260
|
+
'sw-denomination-total-head',
|
|
261
|
+
'sw-denomination-total-value',
|
|
252
262
|
'sw-denomination-tiles',
|
|
253
263
|
'sw-denomination-tile-list',
|
|
254
264
|
'sw-denomination-tile',
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/** The frame every native text control draws: fill, hairline border, radius. */
|
|
2
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);";
|
|
3
|
+
export declare const disabledControlSurface = "background-color: var(--sw-color-subtle);\n border-style: dashed;\n cursor: not-allowed;\n opacity: 1;\n -webkit-text-fill-color: currentColor;";
|
|
4
|
+
export declare const disabledControlForcedColors = "border-color: GrayText;\n color: GrayText;\n -webkit-text-fill-color: GrayText;";
|
|
3
5
|
export declare function cssDocumentCanvas(): string;
|
package/dist/css/css-document.js
CHANGED
|
@@ -18,6 +18,22 @@ export const controlSurface = `background-color: var(--sw-glass-fill);
|
|
|
18
18
|
font-family: inherit;
|
|
19
19
|
font-size: inherit;
|
|
20
20
|
padding-inline: var(--sw-control-md-padding-inline);`;
|
|
21
|
+
/*
|
|
22
|
+
* A disabled text control keeps its value in the text color, so a locked
|
|
23
|
+
* value stays readable (4.5:1 or more on the subtle fill in every palette),
|
|
24
|
+
* and says it is locked with the quiet subtle fill and a dashed hairline
|
|
25
|
+
* instead of the opacity buttons use. WebKit fades a disabled entry's text
|
|
26
|
+
* with its own fill color and opacity, so both are set back.
|
|
27
|
+
*/
|
|
28
|
+
export const disabledControlSurface = `background-color: var(--sw-color-subtle);
|
|
29
|
+
border-style: dashed;
|
|
30
|
+
cursor: not-allowed;
|
|
31
|
+
opacity: 1;
|
|
32
|
+
-webkit-text-fill-color: currentColor;`;
|
|
33
|
+
/* Forced colors keep the dashed border; the system's disabled color draws it. */
|
|
34
|
+
export const disabledControlForcedColors = `border-color: GrayText;
|
|
35
|
+
color: GrayText;
|
|
36
|
+
-webkit-text-fill-color: GrayText;`;
|
|
21
37
|
export function cssDocumentCanvas() {
|
|
22
38
|
return `html,
|
|
23
39
|
body {
|
|
@@ -99,5 +115,24 @@ body {
|
|
|
99
115
|
[data-theme] select:focus-visible {
|
|
100
116
|
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
101
117
|
outline-offset: var(--sw-focus-ring-offset);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* The browser's placeholder gray is one fixed #757575 (2.3:1 on a dark
|
|
121
|
+
field); muted is tuned per palette and scheme. */
|
|
122
|
+
[data-theme] :is(${typedInputs})::placeholder {
|
|
123
|
+
color: var(--sw-color-muted);
|
|
124
|
+
opacity: 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[data-theme] :is(${typedInputs}):disabled,
|
|
128
|
+
[data-theme] select:disabled {
|
|
129
|
+
${disabledControlSurface}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@media (forced-colors: active) {
|
|
133
|
+
[data-theme] :is(${typedInputs}):disabled,
|
|
134
|
+
[data-theme] select:disabled {
|
|
135
|
+
${disabledControlForcedColors}
|
|
136
|
+
}
|
|
102
137
|
}`;
|
|
103
138
|
}
|
package/dist/css/css-field.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
-
import { controlSurface } from './css-document.js';
|
|
2
|
+
import { controlSurface, disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
|
|
3
3
|
const caption = typographyVariants.caption;
|
|
4
4
|
/*
|
|
5
5
|
* A prefix or suffix sits inside the control frame: the wrapper draws the
|
|
@@ -51,7 +51,14 @@ function adornmentRules() {
|
|
|
51
51
|
padding-inline: var(--sw-control-xs-padding-inline);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.sw-field-invalid .sw-field-adorned { border-color: var(--sw-color-danger); }
|
|
54
|
+
.sw-field-invalid .sw-field-adorned { border-color: var(--sw-color-danger); }
|
|
55
|
+
|
|
56
|
+
/* The frame draws the surface, so it carries the disabled look for its input. */
|
|
57
|
+
.sw-field-adorned:has(> input:disabled) {
|
|
58
|
+
${disabledControlSurface}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[data-theme] .sw-field-adorned > input:disabled { background: transparent; }`;
|
|
55
62
|
}
|
|
56
63
|
export function cssFieldClasses() {
|
|
57
64
|
return `.sw-field-required { color: var(--sw-color-danger); }
|
|
@@ -66,6 +73,9 @@ export function cssFieldClasses() {
|
|
|
66
73
|
.sw-field-description { color: var(--sw-color-muted); }
|
|
67
74
|
.sw-field-error { color: var(--sw-color-danger); }
|
|
68
75
|
|
|
76
|
+
/* An error region is always rendered as a live region; empty, it leaves the layout (and its gap) but stays in the accessibility tree. */
|
|
77
|
+
.sw-field-error:empty { position: absolute; }
|
|
78
|
+
|
|
69
79
|
[data-theme] .sw-field-invalid :is(input, select, textarea) {
|
|
70
80
|
border-color: var(--sw-color-danger);
|
|
71
81
|
}
|
|
@@ -76,6 +86,9 @@ ${adornmentRules()}
|
|
|
76
86
|
[data-theme] .sw-field-invalid :is(input, select, textarea),
|
|
77
87
|
.sw-field-invalid .sw-field-adorned { border-color: Mark; }
|
|
78
88
|
.sw-field-prefix, .sw-field-suffix { color: CanvasText; }
|
|
89
|
+
.sw-field-adorned:has(> input:disabled) {
|
|
90
|
+
${disabledControlForcedColors}
|
|
91
|
+
}
|
|
79
92
|
.sw-field-error, .sw-field-required { color: Mark; }
|
|
80
93
|
}`;
|
|
81
94
|
}
|
|
@@ -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
|
-
|
|
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
|
|
95
|
+
.sw-radio-group[aria-invalid='true'] .sw-radio-group-mark {
|
|
96
96
|
border-color: var(--sw-color-danger);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -101,6 +101,14 @@ export function cssRadioGroupClasses() {
|
|
|
101
101
|
opacity: var(--sw-disabled-opacity);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* A consumer glyph beside the label, in the label's color and the option's gap. */
|
|
105
|
+
.sw-radio-group-icon {
|
|
106
|
+
align-items: center;
|
|
107
|
+
color: var(--sw-color-text);
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
flex: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
104
112
|
.sw-radio-group-text {
|
|
105
113
|
color: var(--sw-color-text);
|
|
106
114
|
font-family: var(--sw-font-sans);
|
|
@@ -119,6 +127,7 @@ export function cssRadioGroupClasses() {
|
|
|
119
127
|
|
|
120
128
|
.sw-radio-group-description { color: var(--sw-color-muted); }
|
|
121
129
|
.sw-radio-group-error { color: var(--sw-color-danger); }
|
|
130
|
+
.sw-radio-group-error:empty { position: absolute; }
|
|
122
131
|
|
|
123
132
|
@media (prefers-reduced-transparency: reduce) {
|
|
124
133
|
.sw-radio-group-mark { background: var(--sw-color-surface); }
|
|
@@ -129,7 +138,7 @@ export function cssRadioGroupClasses() {
|
|
|
129
138
|
.sw-radio-group-mark { background: Canvas; border-color: CanvasText; }
|
|
130
139
|
.sw-radio-group-input:checked + .sw-radio-group-mark { background: Highlight; border-color: Highlight; }
|
|
131
140
|
.sw-radio-group-input:checked + .sw-radio-group-mark::after { background: HighlightText; }
|
|
132
|
-
.sw-radio-group
|
|
141
|
+
.sw-radio-group[aria-invalid='true'] .sw-radio-group-mark { border-color: CanvasText; }
|
|
133
142
|
}`;
|
|
134
143
|
}
|
|
135
144
|
export function radioGroupClassCatalog() {
|
|
@@ -141,6 +150,7 @@ export function radioGroupClassCatalog() {
|
|
|
141
150
|
'sw-radio-group-control',
|
|
142
151
|
'sw-radio-group-input',
|
|
143
152
|
'sw-radio-group-mark',
|
|
153
|
+
'sw-radio-group-icon',
|
|
144
154
|
'sw-radio-group-text',
|
|
145
155
|
'sw-radio-group-description',
|
|
146
156
|
'sw-radio-group-error',
|