@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +41 -3
  3. package/dist/accessible-name.d.ts +13 -0
  4. package/dist/accessible-name.js +1 -0
  5. package/dist/components/DataBar.d.ts +48 -0
  6. package/dist/components/DataBar.js +28 -0
  7. package/dist/components/DenominationGrid.d.ts +1 -1
  8. package/dist/components/DenominationGrid.js +2 -0
  9. package/dist/components/Pagination.d.ts +34 -0
  10. package/dist/components/Pagination.js +27 -0
  11. package/dist/components/ScrollRegion.d.ts +9 -5
  12. package/dist/components/ScrollRegion.js +8 -6
  13. package/dist/components/SectionNav.d.ts +2 -8
  14. package/dist/components/SegmentedControl.d.ts +2 -8
  15. package/dist/components/Stepper.d.ts +5 -0
  16. package/dist/components/Stepper.js +4 -2
  17. package/dist/components/Table.d.ts +48 -4
  18. package/dist/components/Table.js +9 -3
  19. package/dist/components/Tabs.d.ts +2 -9
  20. package/dist/components/denomination-grid/DenominationStrip.js +3 -1
  21. package/dist/components/denomination-grid/EntryCell.d.ts +3 -1
  22. package/dist/components/denomination-grid/EntryCell.js +2 -2
  23. package/dist/components/denomination-grid/StripRow.d.ts +8 -2
  24. package/dist/components/denomination-grid/StripRow.js +14 -5
  25. package/dist/components/denomination-grid/StripTotal.d.ts +11 -0
  26. package/dist/components/denomination-grid/StripTotal.js +12 -0
  27. package/dist/components/denomination-grid/StripValueRow.d.ts +25 -0
  28. package/dist/components/denomination-grid/StripValueRow.js +27 -0
  29. package/dist/components/denomination-grid/types.d.ts +19 -0
  30. package/dist/components/denomination-grid/validate-value-row.d.ts +6 -0
  31. package/dist/components/denomination-grid/validate-value-row.js +30 -0
  32. package/dist/components/table/TableRow.d.ts +8 -2
  33. package/dist/components/table/TableRow.js +1 -1
  34. package/dist/components/table/table-max-height.d.ts +20 -0
  35. package/dist/components/table/table-max-height.js +23 -0
  36. package/dist/components/use-scroll-overflow.d.ts +10 -8
  37. package/dist/components/use-scroll-overflow.js +23 -10
  38. package/dist/css/chevron.d.ts +6 -3
  39. package/dist/css/chevron.js +9 -4
  40. package/dist/css/css-components.js +6 -0
  41. package/dist/css/css-data-bar.d.ts +16 -0
  42. package/dist/css/css-data-bar.js +107 -0
  43. package/dist/css/css-denomination-grid.js +4 -0
  44. package/dist/css/css-denomination-value-row.d.ts +3 -0
  45. package/dist/css/css-denomination-value-row.js +54 -0
  46. package/dist/css/css-pagination.d.ts +2 -0
  47. package/dist/css/css-pagination.js +63 -0
  48. package/dist/css/css-scroll-region.d.ts +1 -0
  49. package/dist/css/css-scroll-region.js +10 -5
  50. package/dist/css/css-table-bounded-edges.d.ts +1 -0
  51. package/dist/css/css-table-bounded-edges.js +132 -0
  52. package/dist/css/css-table-bounded.d.ts +2 -0
  53. package/dist/css/css-table-bounded.js +73 -0
  54. package/dist/css/css-table-ledger.d.ts +5 -2
  55. package/dist/css/css-table-ledger.js +5 -2
  56. package/dist/css/css-table-metrics.d.ts +14 -0
  57. package/dist/css/css-table-metrics.js +13 -0
  58. package/dist/css/css-table-sort.js +2 -1
  59. package/dist/css/css-table-total-row.d.ts +2 -0
  60. package/dist/css/css-table-total-row.js +29 -0
  61. package/dist/css/css-table.js +14 -3
  62. package/dist/css/stylesheet.js +4 -0
  63. package/dist/data-bar-geometry.d.ts +24 -0
  64. package/dist/data-bar-geometry.js +41 -0
  65. package/dist/index.d.ts +4 -2
  66. package/dist/index.js +2 -0
  67. package/dist/pagination-pages.d.ts +13 -0
  68. package/dist/pagination-pages.js +22 -0
  69. package/dist/scroll-overflow.d.ts +12 -0
  70. package/dist/scroll-overflow.js +31 -7
  71. package/dist/styles.css +258 -0
  72. 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,3 @@
1
+ export declare const denominationValueRowClass = "sw-denomination-value-row";
2
+ export declare function cssDenominationValueRowClasses(): string;
3
+ export declare function denominationValueRowClassCatalog(): string[];
@@ -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,2 @@
1
+ export declare function cssPaginationClasses(): string;
2
+ export declare function paginationClassCatalog(): string[];
@@ -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
+ }
@@ -1,3 +1,4 @@
1
+ export declare const scrollShadeColor = "color-mix(in srgb, var(--sw-color-text) 28%, transparent)";
1
2
  /**
2
3
  * The shade each inline edge of `selector` draws while `useScrollOverflow`
3
4
  * marks it (`sw-scroll-more-start`, `sw-scroll-more-end`). The start edge is
@@ -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 shadeColor = 'color-mix(in srgb, var(--sw-color-text) 28%, transparent)';
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)) ${shadeColor}`;
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 ['sw-scroll-more-start', 'sw-scroll-more-end'];
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, ${shadeColor}, transparent);
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, ${shadeColor}, transparent);
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,2 @@
1
+ export declare function cssTableBoundedClasses(): string;
2
+ export declare function tableBoundedClassCatalog(): string[];
@@ -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
- /** A row's place in the table: a row of its own, or one under the row above. */
5
- export declare const tableRowVariants: readonly ["default", "sub"];
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
- /** A row's place in the table: a row of its own, or one under the row above. */
4
- export const tableRowVariants = ['default', 'sub'];
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: var(--sw-control-xs-min-height);
67
+ min-height: ${tableSortMinHeight};
67
68
  padding: 0 var(--sw-space-1);
68
69
  text-align: inherit;
69
70
  }
@@ -0,0 +1,2 @@
1
+ export declare function cssTableTotalRowClasses(): string;
2
+ export declare function tableTotalRowClassCatalog(): string[];
@@ -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
+ }