@scalewing/react 1.36.0 → 1.38.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 +15 -0
- package/README.md +41 -3
- package/dist/accessible-name.d.ts +13 -0
- package/dist/accessible-name.js +1 -0
- package/dist/components/DataBar.d.ts +48 -0
- package/dist/components/DataBar.js +28 -0
- package/dist/components/DenominationGrid.d.ts +1 -1
- package/dist/components/DenominationGrid.js +2 -0
- package/dist/components/Pagination.d.ts +34 -0
- package/dist/components/Pagination.js +27 -0
- package/dist/components/ScrollRegion.d.ts +9 -5
- package/dist/components/ScrollRegion.js +8 -6
- package/dist/components/SectionNav.d.ts +2 -8
- package/dist/components/SegmentedControl.d.ts +2 -8
- package/dist/components/Stepper.d.ts +5 -0
- package/dist/components/Stepper.js +4 -2
- package/dist/components/Table.d.ts +48 -4
- package/dist/components/Table.js +9 -3
- package/dist/components/Tabs.d.ts +2 -9
- package/dist/components/denomination-grid/DenominationStrip.js +3 -1
- package/dist/components/denomination-grid/EntryCell.d.ts +3 -1
- package/dist/components/denomination-grid/EntryCell.js +2 -2
- package/dist/components/denomination-grid/StripRow.d.ts +8 -2
- package/dist/components/denomination-grid/StripRow.js +14 -5
- package/dist/components/denomination-grid/StripTotal.d.ts +11 -0
- package/dist/components/denomination-grid/StripTotal.js +12 -0
- package/dist/components/denomination-grid/StripValueRow.d.ts +25 -0
- package/dist/components/denomination-grid/StripValueRow.js +27 -0
- package/dist/components/denomination-grid/types.d.ts +19 -0
- package/dist/components/denomination-grid/validate-value-row.d.ts +6 -0
- package/dist/components/denomination-grid/validate-value-row.js +30 -0
- package/dist/components/table/TableRow.d.ts +8 -2
- package/dist/components/table/TableRow.js +1 -1
- package/dist/components/table/table-max-height.d.ts +20 -0
- package/dist/components/table/table-max-height.js +23 -0
- package/dist/components/use-scroll-overflow.d.ts +10 -8
- package/dist/components/use-scroll-overflow.js +23 -10
- package/dist/css/chevron.d.ts +6 -3
- package/dist/css/chevron.js +9 -4
- package/dist/css/css-components.js +6 -0
- package/dist/css/css-data-bar.d.ts +16 -0
- package/dist/css/css-data-bar.js +107 -0
- package/dist/css/css-denomination-grid.js +4 -0
- package/dist/css/css-denomination-value-row.d.ts +3 -0
- package/dist/css/css-denomination-value-row.js +54 -0
- package/dist/css/css-pagination.d.ts +2 -0
- package/dist/css/css-pagination.js +63 -0
- package/dist/css/css-scroll-region.d.ts +1 -0
- package/dist/css/css-scroll-region.js +10 -5
- package/dist/css/css-table-bounded-edges.d.ts +1 -0
- package/dist/css/css-table-bounded-edges.js +132 -0
- package/dist/css/css-table-bounded.d.ts +2 -0
- package/dist/css/css-table-bounded.js +73 -0
- package/dist/css/css-table-ledger.d.ts +5 -2
- package/dist/css/css-table-ledger.js +5 -2
- package/dist/css/css-table-metrics.d.ts +14 -0
- package/dist/css/css-table-metrics.js +13 -0
- package/dist/css/css-table-sort.js +2 -1
- package/dist/css/css-table-total-row.d.ts +2 -0
- package/dist/css/css-table-total-row.js +29 -0
- package/dist/css/css-table.js +14 -3
- package/dist/css/stylesheet.js +4 -0
- package/dist/data-bar-geometry.d.ts +24 -0
- package/dist/data-bar-geometry.js +41 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/pagination-pages.d.ts +13 -0
- package/dist/pagination-pages.js +22 -0
- package/dist/scroll-overflow.d.ts +12 -0
- package/dist/scroll-overflow.js +31 -7
- package/dist/styles.css +258 -0
- package/package.json +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a range bar's fill means: the start or a carried figure (`neutral`),
|
|
3
|
+
* money in (`success`), money out (`muted`, never danger: an outflow is not
|
|
4
|
+
* an error), a subtotal (`strong`) and what was counted or recorded
|
|
5
|
+
* (`accent`).
|
|
6
|
+
*/
|
|
7
|
+
export const dataBarTones = [
|
|
8
|
+
'neutral',
|
|
9
|
+
'success',
|
|
10
|
+
'muted',
|
|
11
|
+
'strong',
|
|
12
|
+
'accent',
|
|
13
|
+
];
|
|
14
|
+
/*
|
|
15
|
+
* Each tone is a semantic colour, mixed into the surface where the board
|
|
16
|
+
* draws a lighter fill: the bar is decorative (the figure is the cell's
|
|
17
|
+
* text), so the mixes keep the tones apart from one another rather than
|
|
18
|
+
* meeting a text contrast: every tone at 1.5:1 or more on the surface, and
|
|
19
|
+
* the three greys (out, start, subtotal) 1.5:1 or more from the next one,
|
|
20
|
+
* in every palette and scheme (`data-bar.test.tsx`). `accent` is the
|
|
21
|
+
* accent itself.
|
|
22
|
+
*/
|
|
23
|
+
export const dataBarToneMixes = {
|
|
24
|
+
neutral: { color: 'muted', share: 0.65 },
|
|
25
|
+
success: { color: 'success', share: 0.55 },
|
|
26
|
+
muted: { color: 'muted', share: 0.35 },
|
|
27
|
+
strong: { color: 'text', share: 0.85 },
|
|
28
|
+
accent: { color: 'accent', share: 1 },
|
|
29
|
+
};
|
|
30
|
+
/** A tone's fill: its colour, mixed into the surface by its share. */
|
|
31
|
+
export function dataBarToneFill(tone) {
|
|
32
|
+
const { color, share } = dataBarToneMixes[tone];
|
|
33
|
+
if (share === 1)
|
|
34
|
+
return `var(--sw-color-${color})`;
|
|
35
|
+
return `color-mix(in srgb, var(--sw-color-${color}) ${Math.round(share * 100)}%, var(--sw-color-surface))`;
|
|
36
|
+
}
|
|
37
|
+
function toneRules() {
|
|
38
|
+
return dataBarTones
|
|
39
|
+
.map((tone) => `.sw-data-bar-${tone} > .sw-data-bar-fill { background: ${dataBarToneFill(tone)}; }`)
|
|
40
|
+
.join('\n');
|
|
41
|
+
}
|
|
42
|
+
/*
|
|
43
|
+
* A range bar fills its container's width: a track with no fill of its own,
|
|
44
|
+
* a spacing step 4 tall, and the bar placed on it by two shares the
|
|
45
|
+
* component sets (`--sw-data-bar-start`, `--sw-data-bar-size`), with
|
|
46
|
+
* logical insets so a right-to-left table mirrors it. A diverging domain
|
|
47
|
+
* draws a hairline at zero (`--sw-data-bar-zero`) across the track, under
|
|
48
|
+
* the bar, and the bar's end at zero is square (`sw-data-bar-zero-start`
|
|
49
|
+
* or `-end`), so it meets the line as BarChart's diverging bars do.
|
|
50
|
+
* Forced colours draw the bar and the line in the system text colour;
|
|
51
|
+
* the figure beside it carries the meaning there, as always.
|
|
52
|
+
*/
|
|
53
|
+
export function cssDataBarClasses() {
|
|
54
|
+
return `.sw-data-bar {
|
|
55
|
+
display: block;
|
|
56
|
+
height: var(--sw-space-4);
|
|
57
|
+
min-width: var(--sw-space-6);
|
|
58
|
+
position: relative;
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.sw-data-bar-fill {
|
|
63
|
+
border-radius: calc(var(--sw-radius-sm) / 2);
|
|
64
|
+
inset-block: 0;
|
|
65
|
+
inset-inline-start: calc(var(--sw-data-bar-start, 0) * 100%);
|
|
66
|
+
position: absolute;
|
|
67
|
+
width: calc(var(--sw-data-bar-size, 0) * 100%);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
${toneRules()}
|
|
71
|
+
|
|
72
|
+
.sw-data-bar-diverging::before {
|
|
73
|
+
background: var(--sw-color-border);
|
|
74
|
+
content: '';
|
|
75
|
+
inset-block: calc(-1 * var(--sw-space-1));
|
|
76
|
+
inset-inline-start: calc(var(--sw-data-bar-zero, 0) * 100%);
|
|
77
|
+
/* Centred on zero, mirrored right to left. */
|
|
78
|
+
margin-inline-start: -0.5px;
|
|
79
|
+
position: absolute;
|
|
80
|
+
width: 1px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sw-data-bar-zero-start > .sw-data-bar-fill {
|
|
84
|
+
border-end-start-radius: 0;
|
|
85
|
+
border-start-start-radius: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sw-data-bar-zero-end > .sw-data-bar-fill {
|
|
89
|
+
border-end-end-radius: 0;
|
|
90
|
+
border-start-end-radius: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@media (forced-colors: active) {
|
|
94
|
+
.sw-data-bar > .sw-data-bar-fill,
|
|
95
|
+
.sw-data-bar-diverging::before { background: CanvasText; }
|
|
96
|
+
}`;
|
|
97
|
+
}
|
|
98
|
+
export function dataBarClassCatalog() {
|
|
99
|
+
return [
|
|
100
|
+
'sw-data-bar',
|
|
101
|
+
'sw-data-bar-fill',
|
|
102
|
+
'sw-data-bar-diverging',
|
|
103
|
+
'sw-data-bar-zero-start',
|
|
104
|
+
'sw-data-bar-zero-end',
|
|
105
|
+
...dataBarTones.map((tone) => `sw-data-bar-${tone}`),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
@@ -5,6 +5,7 @@ import { cssDenominationAppearanceClasses, denominationAppearanceClassCatalog, }
|
|
|
5
5
|
import { cssDenominationLabelClasses, denominationLabelClassCatalog, } from './css-denomination-label.js';
|
|
6
6
|
import { cssDenominationAlignedClasses, denominationAlignedClassCatalog, } from './css-denomination-aligned.js';
|
|
7
7
|
import { cssDenominationEntryClasses, denominationEntryClassCatalog, } from './css-denomination-entry.js';
|
|
8
|
+
import { cssDenominationValueRowClasses, denominationValueRowClassCatalog, } from './css-denomination-value-row.js';
|
|
8
9
|
import { cssDenominationCellToneClasses, denominationCellToneClassCatalog, } from './css-denomination-cell-tones.js';
|
|
9
10
|
import { pinnedStartShadeRules, scrollRegionRules, } from './css-scroll-region.js';
|
|
10
11
|
import { visuallyHiddenDeclarations } from './css-utilities.js';
|
|
@@ -260,6 +261,8 @@ ${cssDenominationLabelClasses()}
|
|
|
260
261
|
|
|
261
262
|
${cssDenominationAppearanceClasses()}
|
|
262
263
|
|
|
264
|
+
${cssDenominationValueRowClasses()}
|
|
265
|
+
|
|
263
266
|
@media (forced-colors: active) {
|
|
264
267
|
.sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
|
|
265
268
|
.sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
|
|
@@ -303,5 +306,6 @@ export function denominationGridClassCatalog() {
|
|
|
303
306
|
...denominationAlignedClassCatalog(),
|
|
304
307
|
...denominationLabelClassCatalog(),
|
|
305
308
|
...denominationAppearanceClassCatalog(),
|
|
309
|
+
...denominationValueRowClassCatalog(),
|
|
306
310
|
];
|
|
307
311
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
3
|
+
const data = typographyVariants.data;
|
|
4
|
+
const label = typographyVariants.label;
|
|
5
|
+
export const denominationValueRowClass = 'sw-denomination-value-row';
|
|
6
|
+
const valueRow = `.sw-denomination-strip .${denominationValueRowClass}`;
|
|
7
|
+
/*
|
|
8
|
+
* A value line under a strip row: the subtle fill across the row (its
|
|
9
|
+
* pinned label too), each value at label size in data weight and tabular
|
|
10
|
+
* figures (the framed counts' size), centred
|
|
11
|
+
* under its count in the text colour, never wrapped, padded a space-2 step
|
|
12
|
+
* down (space-1 below md): shorter than framed counts above it. A disabled
|
|
13
|
+
* row's line fades its values with the row. Emitted after
|
|
14
|
+
* the frame's rules and one class more specific, so it wins at every width.
|
|
15
|
+
*/
|
|
16
|
+
export function cssDenominationValueRowClasses() {
|
|
17
|
+
return `${valueRow},
|
|
18
|
+
${valueRow} .sw-denomination-label {
|
|
19
|
+
background: var(--sw-color-subtle);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
${valueRow} :is(th, td) {
|
|
23
|
+
padding-block: var(--sw-space-2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
${valueRow} .sw-denomination-value {
|
|
27
|
+
color: var(--sw-color-text);
|
|
28
|
+
font-size: ${label.fontSize}px;
|
|
29
|
+
font-variant-numeric: tabular-nums;
|
|
30
|
+
font-weight: ${data.fontWeight};
|
|
31
|
+
letter-spacing: ${label.letterSpacing}px;
|
|
32
|
+
line-height: ${label.lineHeight}px;
|
|
33
|
+
text-align: center;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
${valueRow}.sw-denomination-row-disabled .sw-denomination-value {
|
|
38
|
+
opacity: var(--sw-disabled-opacity);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
42
|
+
${valueRow} :is(th, td) { padding-block: var(--sw-space-1); }
|
|
43
|
+
${valueRow} .sw-denomination-total { padding: 0; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@media (forced-colors: active) {
|
|
47
|
+
${valueRow},
|
|
48
|
+
${valueRow} .sw-denomination-label { background: Canvas; }
|
|
49
|
+
${valueRow} .sw-denomination-value { color: CanvasText; }
|
|
50
|
+
}`;
|
|
51
|
+
}
|
|
52
|
+
export function denominationValueRowClassCatalog() {
|
|
53
|
+
return [denominationValueRowClass, 'sw-denomination-value'];
|
|
54
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { chevronStrokeIn } from './chevron.js';
|
|
3
|
+
import { iconButtonRules } from './css-icon-button.js';
|
|
4
|
+
const caption = typographyVariants.caption;
|
|
5
|
+
/*
|
|
6
|
+
* A pager: the consumer's range words at the inline start in the muted
|
|
7
|
+
* caption, the previous and next steps at the end, wrapping under the
|
|
8
|
+
* words on a narrow column. The steps are icon-only neutral Buttons,
|
|
9
|
+
* square at their size and the touch target on a coarse pointer
|
|
10
|
+
* (`iconButtonRules`).
|
|
11
|
+
*
|
|
12
|
+
* Their glyph is private control chrome: the shared stroked chevron
|
|
13
|
+
* (`chevron.ts`) in the button's colour, pointing toward the inline start
|
|
14
|
+
* for the previous page and the end for the next; right to left one flip
|
|
15
|
+
* of the turned chevron mirrors both. It is the chevron Accordion, Select
|
|
16
|
+
* and the menus draw, not DateField's month steps (an SVG at a larger
|
|
17
|
+
* size inside the calendar).
|
|
18
|
+
*/
|
|
19
|
+
export function cssPaginationClasses() {
|
|
20
|
+
return `.sw-pagination {
|
|
21
|
+
align-items: center;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
gap: var(--sw-space-2) var(--sw-space-3);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sw-pagination-range {
|
|
28
|
+
color: var(--sw-color-muted);
|
|
29
|
+
font-family: var(--sw-font-sans);
|
|
30
|
+
font-size: ${caption.fontSize}px;
|
|
31
|
+
font-variant-numeric: tabular-nums;
|
|
32
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
33
|
+
line-height: ${caption.lineHeight}px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sw-pagination-steps {
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: var(--sw-space-2);
|
|
39
|
+
margin-inline-start: auto;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
${iconButtonRules('sw-pagination-step')}
|
|
43
|
+
|
|
44
|
+
.sw-pagination-glyph {
|
|
45
|
+
${chevronStrokeIn('currentColor')}
|
|
46
|
+
display: block;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sw-pagination-glyph-previous { transform: translateX(25%) rotate(135deg); }
|
|
50
|
+
.sw-pagination-glyph-next { transform: translateX(-25%) rotate(-45deg); }
|
|
51
|
+
.sw-pagination-glyph:dir(rtl) { scale: -1 1; }`;
|
|
52
|
+
}
|
|
53
|
+
export function paginationClassCatalog() {
|
|
54
|
+
return [
|
|
55
|
+
'sw-pagination',
|
|
56
|
+
'sw-pagination-range',
|
|
57
|
+
'sw-pagination-steps',
|
|
58
|
+
'sw-pagination-step',
|
|
59
|
+
'sw-pagination-glyph',
|
|
60
|
+
'sw-pagination-glyph-previous',
|
|
61
|
+
'sw-pagination-glyph-next',
|
|
62
|
+
];
|
|
63
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* inherited variables, so a scroll region inside another never takes its
|
|
8
8
|
* ancestor's shade.
|
|
9
9
|
*/
|
|
10
|
-
const
|
|
10
|
+
export const scrollShadeColor = 'color-mix(in srgb, var(--sw-color-text) 28%, transparent)';
|
|
11
11
|
function edgeShade(side) {
|
|
12
12
|
const offset = side === 'left' ? 'var(--sw-space-6)' : 'calc(-1 * var(--sw-space-6))';
|
|
13
|
-
return `inset ${offset} 0 var(--sw-space-5) calc(-1 * var(--sw-space-5)) ${
|
|
13
|
+
return `inset ${offset} 0 var(--sw-space-5) calc(-1 * var(--sw-space-5)) ${scrollShadeColor}`;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* The shade each inline edge of `selector` draws while `useScrollOverflow`
|
|
@@ -61,7 +61,12 @@ ${selector}:focus-visible {
|
|
|
61
61
|
${scrollShadeRules(selector)}`;
|
|
62
62
|
}
|
|
63
63
|
export function scrollRegionClassCatalog() {
|
|
64
|
-
return [
|
|
64
|
+
return [
|
|
65
|
+
'sw-scroll-more-start',
|
|
66
|
+
'sw-scroll-more-end',
|
|
67
|
+
'sw-scroll-more-top',
|
|
68
|
+
'sw-scroll-more-bottom',
|
|
69
|
+
];
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* A region whose start column is pinned (sticky, on an opaque fill) hides
|
|
@@ -76,7 +81,7 @@ export function pinnedStartShadeRules(region, pinned) {
|
|
|
76
81
|
.map((cell) => `${region}.sw-scroll-more-start${direction} ${cell}::after`)
|
|
77
82
|
.join(',\n');
|
|
78
83
|
return `${after('')} {
|
|
79
|
-
background: linear-gradient(to right, ${
|
|
84
|
+
background: linear-gradient(to right, ${scrollShadeColor}, transparent);
|
|
80
85
|
content: '';
|
|
81
86
|
inset-block: 0;
|
|
82
87
|
inset-inline-start: 100%;
|
|
@@ -86,7 +91,7 @@ export function pinnedStartShadeRules(region, pinned) {
|
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
${after(':dir(rtl)')} {
|
|
89
|
-
background: linear-gradient(to left, ${
|
|
94
|
+
background: linear-gradient(to left, ${scrollShadeColor}, transparent);
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
@media (forced-colors: active) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cssTableBoundedEdgeClasses(): string;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { scrollShadeColor } from './css-scroll-region.js';
|
|
2
|
+
const region = '.sw-table-bounded';
|
|
3
|
+
/*
|
|
4
|
+
* While rows are scrolled out past the top or bottom of a bounded region
|
|
5
|
+
* (`sw-scroll-more-top`, `sw-scroll-more-bottom`), that edge draws the
|
|
6
|
+
* scroll shade, a spacing step 5 deep, in the colour of the inline edges'
|
|
7
|
+
* shade. A pinned row covers the region's own edge, so the shade is cast
|
|
8
|
+
* from the pinned row instead: the header's last row extends its rule's
|
|
9
|
+
* `::before` downward into a gradient, and a sticky footer's first row
|
|
10
|
+
* extends its 2 px rule's `::before` upward into one. Both are drawn
|
|
11
|
+
* inside their pinned group's layer, over the rows that scroll under
|
|
12
|
+
* them, and no cell moves. Only the header's last row casts it, so a
|
|
13
|
+
* header cell that spans rows (`rowSpan`) from an earlier row casts none
|
|
14
|
+
* under its column. An edge with nothing pinned on it, such as a bounded
|
|
15
|
+
* table without a sticky footer, shades the region's own edge with a
|
|
16
|
+
* background layer fixed to the box, under the rows as the inline shade
|
|
17
|
+
* is. Forced colours draw no shade; the system scrollbar is the cue.
|
|
18
|
+
*/
|
|
19
|
+
function shadeRules() {
|
|
20
|
+
const shade = (direction) => `linear-gradient(to ${direction}, ${scrollShadeColor}, transparent)`;
|
|
21
|
+
const headCells = `${region}.sw-scroll-more-top > .sw-table-sticky > thead > tr:last-child > :is(th, td)::before`;
|
|
22
|
+
const footCells = `${region}.sw-scroll-more-bottom > .sw-table > .sw-table-footer-sticky > tr:first-child > :is(th, td)::before`;
|
|
23
|
+
const noHead = ':not(:has(> .sw-table > thead))';
|
|
24
|
+
const noPinnedFoot = ':not(:has(> .sw-table > .sw-table-footer-sticky))';
|
|
25
|
+
const ownTop = `${region}.sw-scroll-more-top${noHead}`;
|
|
26
|
+
const ownBottom = `${region}.sw-scroll-more-bottom${noPinnedFoot}`;
|
|
27
|
+
const ownBoth = `${region}.sw-scroll-more-top.sw-scroll-more-bottom${noHead}${noPinnedFoot}`;
|
|
28
|
+
return `${headCells} {
|
|
29
|
+
background: ${shade('bottom')};
|
|
30
|
+
border-bottom: 0;
|
|
31
|
+
border-top: 1px solid var(--sw-color-border);
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
height: var(--sw-space-5);
|
|
34
|
+
inset-block-end: auto;
|
|
35
|
+
inset-block-start: calc(100% - 1px);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
${footCells} {
|
|
39
|
+
background: ${shade('top')};
|
|
40
|
+
border-bottom: 2px solid var(--sw-color-border);
|
|
41
|
+
border-top: 0;
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
height: var(--sw-space-5);
|
|
44
|
+
inset-block-end: calc(100% - 1px);
|
|
45
|
+
inset-block-start: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
${ownTop},
|
|
49
|
+
${ownBottom} {
|
|
50
|
+
background-repeat: no-repeat;
|
|
51
|
+
background-size: 100% var(--sw-space-5);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
${ownTop} {
|
|
55
|
+
background-image: ${shade('bottom')};
|
|
56
|
+
background-position: top;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
${ownBottom} {
|
|
60
|
+
background-image: ${shade('top')};
|
|
61
|
+
background-position: bottom;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
${ownBoth} {
|
|
65
|
+
background-image: ${shade('bottom')}, ${shade('top')};
|
|
66
|
+
background-position: top, bottom;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media (forced-colors: active) {
|
|
70
|
+
${headCells},
|
|
71
|
+
${footCells} { background: none; border-color: CanvasText; }
|
|
72
|
+
${ownTop},
|
|
73
|
+
${ownBottom},
|
|
74
|
+
${ownBoth} { background-image: none; }
|
|
75
|
+
}`;
|
|
76
|
+
}
|
|
77
|
+
/*
|
|
78
|
+
* The region's focus ring is its own outline, drawn inside its edge, and
|
|
79
|
+
* the pinned header and sticky footer paint over it. While the region has
|
|
80
|
+
* keyboard focus, those pinned cells carry the ring's part they cover: an
|
|
81
|
+
* inset line at the top of the header's first row and the bottom of the
|
|
82
|
+
* footer's last row, and at the outer side of each pinned row's first and
|
|
83
|
+
* last cell (mirrored right to left). The parts are custom properties set
|
|
84
|
+
* on the cell they belong to and joined in one shadow, so a corner cell
|
|
85
|
+
* carries two. The side parts sit on the first and last cells, so they
|
|
86
|
+
* show while the region is scrolled to that inline edge, and a header
|
|
87
|
+
* cell spanning rows (`rowSpan`) puts a later row's first cell off the
|
|
88
|
+
* edge, where its side part would land mid-header. Forced colours drop
|
|
89
|
+
* shadows; the outline shows beside the rows there.
|
|
90
|
+
*/
|
|
91
|
+
function focusRules() {
|
|
92
|
+
const focused = `${region}:focus-visible`;
|
|
93
|
+
const head = `${focused} > .sw-table-sticky > thead`;
|
|
94
|
+
const foot = `${focused} > .sw-table > .sw-table-footer-sticky`;
|
|
95
|
+
const cells = (group, row = 'tr', cell = ':is(th, td)') => `${group} > ${row} > ${cell}`;
|
|
96
|
+
const both = (row, cell) => `${cells(head, row, cell)},\n${cells(foot, row, cell)}`;
|
|
97
|
+
const ring = 'var(--sw-focus-ring-width)';
|
|
98
|
+
const line = (x, y) => `inset ${x} ${y} 0 0 var(--sw-color-accent)`;
|
|
99
|
+
const none = '0 0 transparent';
|
|
100
|
+
return `${both('tr', ':is(th, td)')} {
|
|
101
|
+
box-shadow: var(--sw-table-ring-block, ${none}), var(--sw-table-ring-start, ${none}), var(--sw-table-ring-end, ${none});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
${cells(head, 'tr:first-child')} {
|
|
105
|
+
--sw-table-ring-block: ${line('0', ring)};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
${cells(foot, 'tr:last-child')} {
|
|
109
|
+
--sw-table-ring-block: ${line('0', `calc(-1 * ${ring})`)};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
${both('tr', ':first-child')} {
|
|
113
|
+
--sw-table-ring-start: ${line(ring, '0')};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
${both('tr', ':last-child')} {
|
|
117
|
+
--sw-table-ring-end: ${line(`calc(-1 * ${ring})`, '0')};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
${both('tr', ':first-child:dir(rtl)')} {
|
|
121
|
+
--sw-table-ring-start: ${line(`calc(-1 * ${ring})`, '0')};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
${both('tr', ':last-child:dir(rtl)')} {
|
|
125
|
+
--sw-table-ring-end: ${line(ring, '0')};
|
|
126
|
+
}`;
|
|
127
|
+
}
|
|
128
|
+
export function cssTableBoundedEdgeClasses() {
|
|
129
|
+
return `${shadeRules()}
|
|
130
|
+
|
|
131
|
+
${focusRules()}`;
|
|
132
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { cssTableBoundedEdgeClasses } from './css-table-bounded-edges.js';
|
|
3
|
+
import { tableCellPadding, tableRuleWidth, tableSortMinHeight, } from './css-table-metrics.js';
|
|
4
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
5
|
+
const body = typographyVariants.body;
|
|
6
|
+
const caption = typographyVariants.caption;
|
|
7
|
+
/** A header row whose line is `line` tall: that line and its padding twice. */
|
|
8
|
+
function headBlock(line) {
|
|
9
|
+
return `calc(${line} + 2 * var(--sw-table-cell-pad))`;
|
|
10
|
+
}
|
|
11
|
+
/*
|
|
12
|
+
* A bounded table (`Table maxHeight`, `sw-table-bounded` on its region)
|
|
13
|
+
* is as tall as its row count (`--sw-table-max-rows`) of single-line body
|
|
14
|
+
* rows, plus one header row when it has a `thead` and one footer row when
|
|
15
|
+
* it has a `tfoot`, from the measures its cells are drawn with
|
|
16
|
+
* (`css-table-metrics.ts`): a body row is the body line, the cell padding
|
|
17
|
+
* twice and its rule; a header row is the caption line and the padding
|
|
18
|
+
* twice (a pinned header cell draws its rule inside itself), or the sort
|
|
19
|
+
* button's height where a header sorts (the touch target on a coarse
|
|
20
|
+
* pointer). Compact density swaps the padding. Rows of two lines or with
|
|
21
|
+
* controls, a second header or footer row, and a horizontal scrollbar
|
|
22
|
+
* that takes room (classic scrollbars) leave fewer rows showing.
|
|
23
|
+
*
|
|
24
|
+
* The region already scrolls (`overflow: auto`), so the rows past that
|
|
25
|
+
* height scroll inside it, and the pinned header group (`thead`, top 0)
|
|
26
|
+
* and a sticky footer (bottom 0) pin to the region's own edges: it is
|
|
27
|
+
* their nearest scroll box. A bounded region is never marked
|
|
28
|
+
* `sw-scroll-fits`, so it never stops being a scroll box for the page
|
|
29
|
+
* pinning in `css-table-page-pin.ts`. Printed, every row shows.
|
|
30
|
+
*/
|
|
31
|
+
function heightRules() {
|
|
32
|
+
const sorting = '.sw-table-bounded:has(> .sw-table > thead .sw-table-sort)';
|
|
33
|
+
return `.sw-table-bounded {
|
|
34
|
+
--sw-table-cell-pad: ${tableCellPadding.comfortable.block};
|
|
35
|
+
--sw-table-head-block: 0px;
|
|
36
|
+
--sw-table-foot-rows: 0;
|
|
37
|
+
max-height: calc((var(--sw-table-max-rows) + var(--sw-table-foot-rows)) * (${body.lineHeight}px + 2 * var(--sw-table-cell-pad) + ${tableRuleWidth}) + var(--sw-table-head-block));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sw-table-bounded:has(> .sw-table-compact) {
|
|
41
|
+
--sw-table-cell-pad: ${tableCellPadding.compact.block};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sw-table-bounded:has(> .sw-table > thead) {
|
|
45
|
+
--sw-table-head-block: ${headBlock(`${caption.lineHeight}px`)};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
${sorting} {
|
|
49
|
+
--sw-table-head-block: ${headBlock(`max(${caption.lineHeight}px, ${tableSortMinHeight})`)};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@media ${coarsePointerQuery} {
|
|
53
|
+
${sorting} {
|
|
54
|
+
--sw-table-head-block: ${headBlock(`max(${caption.lineHeight}px, ${touchTarget})`)};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sw-table-bounded:has(> .sw-table > tfoot) {
|
|
59
|
+
--sw-table-foot-rows: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media print {
|
|
63
|
+
.sw-table-bounded { max-height: none; }
|
|
64
|
+
}`;
|
|
65
|
+
}
|
|
66
|
+
export function cssTableBoundedClasses() {
|
|
67
|
+
return `${heightRules()}
|
|
68
|
+
|
|
69
|
+
${cssTableBoundedEdgeClasses()}`;
|
|
70
|
+
}
|
|
71
|
+
export function tableBoundedClassCatalog() {
|
|
72
|
+
return ['sw-table-bounded'];
|
|
73
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/** The colours a cell's text can take: a signed figure or a quiet value. */
|
|
2
2
|
export declare const tableCellTones: readonly ["success", "danger", "muted", "accent"];
|
|
3
3
|
export type TableCellTone = (typeof tableCellTones)[number];
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* A row's place in the table: a row of its own, one under the row above,
|
|
6
|
+
* or a total of the rows above it, mid-table.
|
|
7
|
+
*/
|
|
8
|
+
export declare const tableRowVariants: readonly ["default", "sub", "total"];
|
|
6
9
|
export type TableRowVariant = (typeof tableRowVariants)[number];
|
|
7
10
|
export declare function assertTableCellTone(tone: unknown): void;
|
|
8
11
|
export declare function assertTableRowVariant(variant: unknown): void;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/** The colours a cell's text can take: a signed figure or a quiet value. */
|
|
2
2
|
export const tableCellTones = ['success', 'danger', 'muted', 'accent'];
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* A row's place in the table: a row of its own, one under the row above,
|
|
5
|
+
* or a total of the rows above it, mid-table.
|
|
6
|
+
*/
|
|
7
|
+
export const tableRowVariants = ['default', 'sub', 'total'];
|
|
5
8
|
export function assertTableCellTone(tone) {
|
|
6
9
|
if (tone !== undefined && !tableCellTones.includes(tone))
|
|
7
10
|
throw new RangeError(`Unknown TableCell tone: ${String(tone)}`);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const tableCellPadding: {
|
|
2
|
+
readonly comfortable: {
|
|
3
|
+
readonly block: "var(--sw-space-2)";
|
|
4
|
+
readonly inline: "var(--sw-space-3)";
|
|
5
|
+
};
|
|
6
|
+
readonly compact: {
|
|
7
|
+
readonly block: "var(--sw-space-1)";
|
|
8
|
+
readonly inline: "var(--sw-space-2)";
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/** The rule under every row (a collapsed cell border). */
|
|
12
|
+
export declare const tableRuleWidth = "1px";
|
|
13
|
+
/** The least height of a sortable header's button on a fine pointer. */
|
|
14
|
+
export declare const tableSortMinHeight = "var(--sw-control-xs-min-height)";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The measures a table's cells are drawn with, shared by the cell rules
|
|
3
|
+
* (`css-table.ts`, `css-table-sort.ts`) and the bounded table's height
|
|
4
|
+
* (`css-table-bounded.ts`), so a change to one moves the other with it.
|
|
5
|
+
*/
|
|
6
|
+
export const tableCellPadding = {
|
|
7
|
+
comfortable: { block: 'var(--sw-space-2)', inline: 'var(--sw-space-3)' },
|
|
8
|
+
compact: { block: 'var(--sw-space-1)', inline: 'var(--sw-space-2)' },
|
|
9
|
+
};
|
|
10
|
+
/** The rule under every row (a collapsed cell border). */
|
|
11
|
+
export const tableRuleWidth = '1px';
|
|
12
|
+
/** The least height of a sortable header's button on a fine pointer. */
|
|
13
|
+
export const tableSortMinHeight = 'var(--sw-control-xs-min-height)';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { chevronStroke } from './chevron.js';
|
|
2
|
+
import { tableSortMinHeight } from './css-table-metrics.js';
|
|
2
3
|
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
3
4
|
/*
|
|
4
5
|
* A sortable header's button keeps the header's own text style (caption,
|
|
@@ -63,7 +64,7 @@ export function cssTableSortClasses() {
|
|
|
63
64
|
letter-spacing: inherit;
|
|
64
65
|
/* A step of room for the focus ring, taken back so the name stays on the cell's edge. */
|
|
65
66
|
margin-inline: calc(-1 * var(--sw-space-1));
|
|
66
|
-
min-height:
|
|
67
|
+
min-height: ${tableSortMinHeight};
|
|
67
68
|
padding: 0 var(--sw-space-1);
|
|
68
69
|
text-align: inherit;
|
|
69
70
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A total row mid-table (`TableRow variant="total"`), such as a subtotal
|
|
3
|
+
* the rows above add up to before the table goes on: its cells at weight
|
|
4
|
+
* 600 under the footer's heavier rule, 2 px in the border colour across
|
|
5
|
+
* its top, as a `TableFooter` total is drawn. The rule is the cells' own
|
|
6
|
+
* top border, which the collapsed-border model resolves over the row
|
|
7
|
+
* above's 1 px one, so forced colours keep it (in `CanvasText`) and no
|
|
8
|
+
* pseudo-element is taken from a selected row's bar or a sub row's guide.
|
|
9
|
+
* The row takes no fill: on the subtle fill, accent text (a link or an
|
|
10
|
+
* accent-toned figure) falls under 4.5:1 in several palettes, as the
|
|
11
|
+
* selected row's note in `css-table.ts` found. A total row belongs in the
|
|
12
|
+
* body: a pinned header cell draws its rules on its pseudo-elements
|
|
13
|
+
* instead, and a `TableFooter` already draws the total at the table's
|
|
14
|
+
* end. A cell's `tone` keeps its colour; a struck total row mutes and
|
|
15
|
+
* strikes it as any struck row.
|
|
16
|
+
*/
|
|
17
|
+
export function cssTableTotalRowClasses() {
|
|
18
|
+
return `.sw-table .sw-table-row-total > :is(th, td) {
|
|
19
|
+
border-top: 2px solid var(--sw-color-border);
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (forced-colors: active) {
|
|
24
|
+
.sw-table .sw-table-row-total > :is(th, td) { border-top-color: CanvasText; }
|
|
25
|
+
}`;
|
|
26
|
+
}
|
|
27
|
+
export function tableTotalRowClassCatalog() {
|
|
28
|
+
return ['sw-table-row-total'];
|
|
29
|
+
}
|