@scalewing/react 1.36.0 → 1.37.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @scalewing/react
2
2
 
3
+ ## 1.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a9e2447: `DenominationGrid` row `valueRow` (opt-in, strip layout): a line right under a row that gives each column's value, such as the money its count adds up to. It is its own table row on the subtle fill, named in the label column (`label`), each consumer-formatted value centred under its count in tabular figures (`null` or `''` blank), with the row's `total` moved onto it; each typed count is described by the value under it, which is no tab stop. `Stepper` gains `aria-describedby` (ids read before its error). The grid still does no arithmetic or currency formatting. New classes `sw-denomination-value-row` and `sw-denomination-value`; no new tokens, no new dependency. Request: `docs/requests/teisoro-denomination-value-row.md`.
8
+
3
9
  ## 1.36.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -113,7 +113,7 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
113
113
  </Field>
114
114
  ```
115
115
 
116
- `Stepper` is a whole-number count changed with − and + or typed. The count is a native text input with `role="spinbutton"` and `inputmode="numeric"`, named by `label` (visible above it, or `labelVisuallyHidden` where table headers say it) and the one tab stop: ArrowUp and ArrowDown step it by `step` (default 1), Home goes to `min` and End to a finite `max` (a range below zero takes `inputmode="text"`, since a phone's numeric pad has no minus key). The − and + buttons are pointer targets outside the tab order, named by `decrementLabel` and `incrementLabel` (your copy), and a press on one leaves focus where it was; the count and both buttons sit in a `group` named by the label. − is disabled at `min` (default 0) and + at `max` (no upper bound when left out). `value` is a count or `null` for an empty box, which the buttons treat as `min` and `placeholder` can fill. Typed text is refused unless it is digits (with a leading minus when `min` is below zero), so `onChange` only receives a whole number or `null`; a typed count is reported as typed even outside the bounds, so say why in `error` (a polite message, `aria-invalid` and the danger border, as `Field`'s), and the buttons then move it back inside them. `changed` takes `Field`'s changed look; `readOnly` keeps the count focusable and hides the buttons in their own room; `disabled` stops all three. `limitAppearance="plain"` (default `"faded"`) keeps − at `min` and + at `max` in their usual look instead of faded, for a count sheet where a faded − reads as broken; they are still disabled, a disabled or read-only Stepper is unchanged, forced colors still grey them, and an unknown value throws a `RangeError`. The count is a few digits wide and keeps that width (`width="fixed"`), or fills its container (`width="full"`, for a table cell). Bounds that are not whole numbers, `min` above `max`, or a `step` that is not above zero throw a `RangeError`.
116
+ `Stepper` is a whole-number count changed with − and + or typed. The count is a native text input with `role="spinbutton"` and `inputmode="numeric"`, named by `label` (visible above it, or `labelVisuallyHidden` where table headers say it) and the one tab stop: ArrowUp and ArrowDown step it by `step` (default 1), Home goes to `min` and End to a finite `max` (a range below zero takes `inputmode="text"`, since a phone's numeric pad has no minus key). The − and + buttons are pointer targets outside the tab order, named by `decrementLabel` and `incrementLabel` (your copy), and a press on one leaves focus where it was; the count and both buttons sit in a `group` named by the label. − is disabled at `min` (default 0) and + at `max` (no upper bound when left out). `value` is a count or `null` for an empty box, which the buttons treat as `min` and `placeholder` can fill. Typed text is refused unless it is digits (with a leading minus when `min` is below zero), so `onChange` only receives a whole number or `null`; a typed count is reported as typed even outside the bounds, so say why in `error` (a polite message, `aria-invalid` and the danger border, as `Field`'s), and the buttons then move it back inside them. `aria-describedby` takes ids of elements that describe the count, such as the value it adds up to, read before an `error`'s message. `changed` takes `Field`'s changed look; `readOnly` keeps the count focusable and hides the buttons in their own room; `disabled` stops all three. `limitAppearance="plain"` (default `"faded"`) keeps − at `min` and + at `max` in their usual look instead of faded, for a count sheet where a faded − reads as broken; they are still disabled, a disabled or read-only Stepper is unchanged, forced colors still grey them, and an unknown value throws a `RangeError`. The count is a few digits wide and keeps that width (`width="fixed"`), or fills its container (`width="full"`, for a table cell). Bounds that are not whole numbers, `min` above `max`, or a `step` that is not above zero throw a `RangeError`.
117
117
 
118
118
  ```tsx
119
119
  <Stepper
@@ -564,7 +564,7 @@ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span
564
564
  </Grid>
565
565
  ```
566
566
 
567
- `DenominationGrid` shows integer counts per unit across a fixed set of columns. The `strip` layout is a captioned table: each row has a toned label with an icon slot, muted zero cells, optional signed deltas toned by sign, and an optional consumer-formatted `total` that shows under the label on a phone. Pass `totalLabel` (for example "Total") to name the total column: it renders a `th scope="col"` shown over the totals from `md` up and visually hidden below it, and without it the corner stays an empty cell as before (either way the column takes no width on a phone). `labelWidth` (`xs` to `xl`, the `Table` column sizes) lines a strip's columns up with every other strip of the same columns in a container of the same width, such as the cards of a feed: the row labels take that width and the count columns share the rest of the region equally, so no content moves a column. `labelAppearance="plain"` (default `"tone"`) sets every row label in the text color at label weight with no tone stripe, in both layouts, for a grid whose rows need no color to tell apart; a row's `tone` still colors its total, and an unknown value throws. Three more grid-level options match a framed count sheet, each off by default: `frame="table"` (default `"none"`) draws a strip as a framed table, its scroll region a surface inside a border hairline at the `sm` radius, the heading row on the subtle fill, a rule between columns, the counts at label size centred under their heads and the cells padded a space-3 step across (the tiles layout has no frame and ignores it); `totalEmphasis="strong"` (default `"default"`) sets the row totals in bold label type, a neutral row's in the text color and a toned row's in its tone, under the label on a phone too; and `cellToneAppearance="pill"` (default `"text"`) sets a `cellTones` count of a strip's read-only row on a rounded tint of its tone in the tone's ink (the `Notice` fill and words, 4.5:1 on the tint except where a palette's own `danger` is already under 4.5:1, harvest and mocha dark), outlined in forced colors; entry counts and tiles keep their tone border. With `zeroLabel=""` a toned zero or empty count shows nothing in either appearance, so tone only counts that are there. An unknown value of any of them throws a `RangeError`. `zeroLabel` (default `"—"`) is what a zero or empty read-only count shows; an empty string leaves those counts blank, so only the counts that are there stand out (it suits the strip, where the column header names each blank cell, better than tiles, where a blank tile is only its column's name), and a `zeroLabel` that is not a string throws. On a phone the total cell stays in the table, visually hidden, so a screen reader still reads each total in its row; the copy under the label is `aria-hidden`. A strip wider than its container scrolls sideways inside its own keyboard-focusable region, named after `label`, with the row labels pinned at the start and the same edge shade as `Table` while counts are scrolled out of view (the start shade is cast from the pinned labels' edge, since they cover the region's own); the page never scrolls sideways. The `tiles` layout stacks the column label, the count, and an optional `subtotal` string per column. Each tiles row shows its label line (icon, label and `total`) when the grid has several rows, or when a lone row has an icon or a `total`; a lone plain row is named by the grid's `label` alone. Every row is a region named by its `label`, except a lone plain row whose `label` is the grid's own: it has no name of its own, so assistive technology hears the grid's group once rather than a region repeating it. `rowRole="group"` (default `"region"`) keeps every named row's name but makes it a `group` instead of a landmark region, for a page with many grids (one per order card, say) whose rows would otherwise fill the landmark list; the strip ignores it, since its rows are table rows, and an unknown `rowRole` throws a `RangeError` in either layout. A row's icon and its label words stay on one line in both layouts (`sw-denomination-label-line`): a long label wraps its words beside the icon, never under it, and on a phone the strip's total goes on its own line under them. A row's `cellTones` (one Badge tone or `null` per column) tones a single count in place: the count is set in the tone over its signed or zero look, and in the tiles layout its tile's border takes the tone too, so a short bill turns red without leaving its row; `neutral` or `null` leaves a count as it is, a row's `tone` still colors only its label, and a `cellTones` that is not one known tone or `null` per column throws. The tone never says why on its own; put that in words beside the grid. Every negative count, signed or not and in either layout, is written with the typographic minus ("−1"), never a hyphen. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
567
+ `DenominationGrid` shows integer counts per unit across a fixed set of columns. The `strip` layout is a captioned table: each row has a toned label with an icon slot, muted zero cells, optional signed deltas toned by sign, and an optional consumer-formatted `total` that shows under the label on a phone. Pass `totalLabel` (for example "Total") to name the total column: it renders a `th scope="col"` shown over the totals from `md` up and visually hidden below it, and without it the corner stays an empty cell as before (either way the column takes no width on a phone). `labelWidth` (`xs` to `xl`, the `Table` column sizes) lines a strip's columns up with every other strip of the same columns in a container of the same width, such as the cards of a feed: the row labels take that width and the count columns share the rest of the region equally, so no content moves a column. `labelAppearance="plain"` (default `"tone"`) sets every row label in the text color at label weight with no tone stripe, in both layouts, for a grid whose rows need no color to tell apart; a row's `tone` still colors its total, and an unknown value throws. Three more grid-level options match a framed count sheet, each off by default: `frame="table"` (default `"none"`) draws a strip as a framed table, its scroll region a surface inside a border hairline at the `sm` radius, the heading row on the subtle fill, a rule between columns, the counts at label size centred under their heads and the cells padded a space-3 step across (the tiles layout has no frame and ignores it); `totalEmphasis="strong"` (default `"default"`) sets the row totals in bold label type, a neutral row's in the text color and a toned row's in its tone, under the label on a phone too; and `cellToneAppearance="pill"` (default `"text"`) sets a `cellTones` count of a strip's read-only row on a rounded tint of its tone in the tone's ink (the `Notice` fill and words, 4.5:1 on the tint except where a palette's own `danger` is already under 4.5:1, harvest and mocha dark), outlined in forced colors; entry counts and tiles keep their tone border. A strip row's `valueRow` (`{ label, cells }`, opt-in) adds a line right under it that gives each column's value, such as the money its count adds up to: its own table row on the subtle fill, `label` (such as "Value") in the label column (named with the row's label, "Counted Value", for table navigation), each of `cells` (one consumer-formatted string or `null` per column; `null` or `''` leaves it blank) centred under its count at label size in tabular figures, and the row's `total` moved onto it (bold with `totalEmphasis="strong"`, under the line's name on a phone); the counts' own total cell stays empty. It keeps the row's tone and `disabled` (a disabled line fades its values). Each entry count with a value is described by it (`aria-describedby`, before an error), and the value is no tab stop. The grid still does no arithmetic. A `valueRow` in the tiles layout, a blank label, or cells that are not one string or `null` per column throw a `RangeError`. With `zeroLabel=""` a toned zero or empty count shows nothing in either appearance, so tone only counts that are there. An unknown value of any of them throws a `RangeError`. `zeroLabel` (default `"—"`) is what a zero or empty read-only count shows; an empty string leaves those counts blank, so only the counts that are there stand out (it suits the strip, where the column header names each blank cell, better than tiles, where a blank tile is only its column's name), and a `zeroLabel` that is not a string throws. On a phone the total cell stays in the table, visually hidden, so a screen reader still reads each total in its row; the copy under the label is `aria-hidden`. A strip wider than its container scrolls sideways inside its own keyboard-focusable region, named after `label`, with the row labels pinned at the start and the same edge shade as `Table` while counts are scrolled out of view (the start shade is cast from the pinned labels' edge, since they cover the region's own); the page never scrolls sideways. The `tiles` layout stacks the column label, the count, and an optional `subtotal` string per column. Each tiles row shows its label line (icon, label and `total`) when the grid has several rows, or when a lone row has an icon or a `total`; a lone plain row is named by the grid's `label` alone. Every row is a region named by its `label`, except a lone plain row whose `label` is the grid's own: it has no name of its own, so assistive technology hears the grid's group once rather than a region repeating it. `rowRole="group"` (default `"region"`) keeps every named row's name but makes it a `group` instead of a landmark region, for a page with many grids (one per order card, say) whose rows would otherwise fill the landmark list; the strip ignores it, since its rows are table rows, and an unknown `rowRole` throws a `RangeError` in either layout. A row's icon and its label words stay on one line in both layouts (`sw-denomination-label-line`): a long label wraps its words beside the icon, never under it, and on a phone the strip's total goes on its own line under them. A row's `cellTones` (one Badge tone or `null` per column) tones a single count in place: the count is set in the tone over its signed or zero look, and in the tiles layout its tile's border takes the tone too, so a short bill turns red without leaving its row; `neutral` or `null` leaves a count as it is, a row's `tone` still colors only its label, and a `cellTones` that is not one known tone or `null` per column throws. The tone never says why on its own; put that in words beside the grid. Every negative count, signed or not and in either layout, is written with the typographic minus ("−1"), never a hyphen. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
568
568
 
569
569
  A strip row with `entry` makes its counts editable: each column holds a `Stepper` (`width="full"`, its label hidden and named by the row label and the column's label and caption as its header reads, such as "Received $20" or "Counted $1 × 100") whose count is the row's `cells` entry (`null` is an empty box). `entry.onChange(columnKey, value)` reports a change; `entry.decrementLabel` and `entry.incrementLabel` name the buttons; `entry.max` caps each column (`null` for none), `entry.errors` puts a message under a column's count (`null` for none), and `entry.placeholder` fills an empty box. `entry.limitAppearance` is the Steppers' `limitAppearance`: `"plain"` keeps − at 0 (and + at a cap) unfaded. The minimum is 0. The grid still does no arithmetic, so recompute the row's `total` in `onChange`. A strip with an entry row centres its heads and read-only counts over the steppers, and each count keeps spacing step 8 of room, so a narrow strip scrolls in its region instead of crushing it. A `cellTones` tone on an entry count draws its border in the tone (danger for a difference); an `accent` tone, for a suggestion or a change, is the `Stepper`'s `changed` look (border and tint); a message in `entry.errors` wins. A row `caption` is a muted line under its label ("from the customer"), and a column `caption` a muted note after its label ("× 100"). Captions show in both layouts. In the strip, `emphasis="quiet"` sets a row on the subtle fill, for a row to compare against, and `disabled` fades a row that does not apply, disables its entry and marks it `aria-disabled`. Entry rows, quiet rows and disabled rows need the `strip` layout. An entry row's counts are zero or more and it is not `signed`; its label is its own among the entry rows, as each column's label and caption are, so every count has its own name. Breaking any of these, a cap or message list that is not one per column, a cap that is not a whole count of zero or more, blank button names or captions, an unknown `emphasis`, or a `disabled` that is not a boolean throws a `RangeError`.
570
570
 
@@ -2,7 +2,7 @@ import { denominationGridFrames, denominationTotalEmphases } from '../css/css-de
2
2
  import { denominationCellToneAppearances } from '../css/css-denomination-cell-tones.js';
3
3
  import { denominationLabelAppearances } from '../css/css-denomination-label.js';
4
4
  import { type DenominationGridProps } from './denomination-grid/types.js';
5
- export type { DenominationCellToneAppearance, DenominationGridColumn, DenominationGridEntry, DenominationGridFrame, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationLabelWidth, DenominationTotalEmphasis, } from './denomination-grid/types.js';
5
+ export type { DenominationCellToneAppearance, DenominationGridColumn, DenominationGridEntry, DenominationGridFrame, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationGridValueRow, DenominationLabelWidth, DenominationTotalEmphasis, } from './denomination-grid/types.js';
6
6
  export type { DenominationLabelAppearance } from '../css/css-denomination-label.js';
7
7
  export { denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationTotalEmphases, };
8
8
  export declare function DenominationGrid({ label, columns, rows, layout, subtotal, zeroLabel, totalLabel, rowRole, labelWidth, labelAppearance, frame, totalEmphasis, cellToneAppearance, }: DenominationGridProps): import("react").JSX.Element;
@@ -7,6 +7,7 @@ import { denominationLabelAppearances, denominationLabelsPlainClass, } from '../
7
7
  import { DenominationStrip } from './denomination-grid/DenominationStrip.js';
8
8
  import { DenominationTiles } from './denomination-grid/DenominationTiles.js';
9
9
  import { assertDenominationGrid } from './denomination-grid/validate.js';
10
+ import { assertDenominationValueRows } from './denomination-grid/validate-value-row.js';
10
11
  import { assertDenominationEntries, assertDenominationExtras, } from './denomination-grid/validate-entry.js';
11
12
  export { denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationTotalEmphases, };
12
13
  /** Fails closed on a value outside a closed set, naming the prop. */
@@ -18,6 +19,7 @@ export function DenominationGrid({ label, columns, rows, layout = 'strip', subto
18
19
  assertDenominationGrid(label, columns, rows);
19
20
  assertDenominationExtras(layout, columns, rows);
20
21
  assertDenominationEntries(layout, columns, rows);
22
+ assertDenominationValueRows(layout, columns, rows);
21
23
  if (typeof zeroLabel !== 'string')
22
24
  throw new RangeError('zeroLabel must be a string');
23
25
  if (totalLabel !== undefined && !totalLabel.trim())
@@ -38,6 +38,11 @@ export type StepperProps = {
38
38
  readOnly?: boolean;
39
39
  /** Says the count in words for assistive technology (`aria-valuetext`), such as "12 straps". */
40
40
  valueText?: string;
41
+ /**
42
+ * Ids of elements that describe the count, such as the value it adds up
43
+ * to; read before an `error`'s message.
44
+ */
45
+ 'aria-describedby'?: string;
41
46
  /** Shown in an empty box, such as "0". */
42
47
  placeholder?: string;
43
48
  name?: string;
@@ -15,7 +15,7 @@ function keepFocus(event) {
15
15
  * tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
16
16
  * the buttons as pointer targets beside it. − stops at `min`, + at `max`.
17
17
  */
18
- export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden = false, value, onChange, min = 0, max = Number.POSITIVE_INFINITY, step = 1, decrementLabel, incrementLabel, error, changed = false, disabled = false, readOnly = false, valueText, placeholder, name, width = 'fixed', limitAppearance = 'faded', }, ref) {
18
+ export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden = false, value, onChange, min = 0, max = Number.POSITIVE_INFINITY, step = 1, decrementLabel, incrementLabel, error, changed = false, disabled = false, readOnly = false, valueText, 'aria-describedby': describedBy, placeholder, name, width = 'fixed', limitAppearance = 'faded', }, ref) {
19
19
  const bounds = { min, max, step };
20
20
  assertStepperBounds(bounds);
21
21
  if (!stepperLimitAppearances.includes(limitAppearance))
@@ -76,7 +76,9 @@ export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden
76
76
  return (_jsxs("div", { className: cx('sw-stepper', width === 'full' && 'sw-stepper-full', invalid && 'sw-stepper-invalid', changed && !invalid && 'sw-stepper-changed', readOnly && 'sw-stepper-readonly', limitAppearance === 'plain' &&
77
77
  !disabled &&
78
78
  !readOnly &&
79
- 'sw-stepper-limit-plain'), children: [_jsx("label", { className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label }) }), _jsxs("div", { "aria-labelledby": labelId, className: "sw-stepper-track", role: "group", children: [_jsx("button", { "aria-controls": inputId, "aria-label": decrementLabel, className: "sw-stepper-button", disabled: moves.down === null, onClick: () => move(moves.down), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: '−' }) }), _jsx("input", { ref: ref, "aria-describedby": invalid ? errorId : undefined, "aria-invalid": invalid || undefined, "aria-valuemax": Number.isFinite(max) ? max : undefined, "aria-valuemin": min, "aria-valuenow": value ?? undefined, "aria-valuetext": valueText, autoComplete: "off", className: "sw-stepper-input", disabled: disabled, id: inputId,
79
+ 'sw-stepper-limit-plain'), children: [_jsx("label", { className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label }) }), _jsxs("div", { "aria-labelledby": labelId, className: "sw-stepper-track", role: "group", children: [_jsx("button", { "aria-controls": inputId, "aria-label": decrementLabel, className: "sw-stepper-button", disabled: moves.down === null, onClick: () => move(moves.down), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: '−' }) }), _jsx("input", { ref: ref, "aria-describedby": [describedBy?.trim() || null, invalid ? errorId : null]
80
+ .filter((id) => id !== null)
81
+ .join(' ') || undefined, "aria-invalid": invalid || undefined, "aria-valuemax": Number.isFinite(max) ? max : undefined, "aria-valuemin": min, "aria-valuenow": value ?? undefined, "aria-valuetext": valueText, autoComplete: "off", className: "sw-stepper-input", disabled: disabled, id: inputId,
80
82
  // iOS's numeric pad has no minus key, so a range below zero takes
81
83
  // the text keyboard.
82
84
  inputMode: allowsNegative ? 'text' : 'numeric', name: name, onBlur: () => setDraft(null), onChange: onType, onKeyDown: onKeyDown, placeholder: placeholder, readOnly: readOnly, role: "spinbutton", type: "text", value: text }), _jsx("button", { "aria-controls": inputId, "aria-label": incrementLabel, className: "sw-stepper-button", disabled: moves.up === null, onClick: () => move(moves.up), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "+" }) })] }), _jsx(FieldErrorRegion, { className: "sw-field-error", id: errorId, message: error })] }));
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId } from 'react';
2
3
  import { cx } from '../../class-names.js';
3
4
  import { denominationLabelWidthClass } from '../../css/css-denomination-aligned.js';
4
5
  import { denominationFramedClass, denominationScrollFramedClass, } from '../../css/css-denomination-appearance.js';
@@ -18,6 +19,7 @@ function TotalHead({ totalLabel }) {
18
19
  return (_jsx("th", { className: "sw-denomination-head sw-denomination-total-head", scope: "col", children: _jsx("span", { className: "sw-denomination-total-label", children: totalLabel }) }));
19
20
  }
20
21
  export function DenominationStrip({ className, framed, label, columns, rows, zeroLabel, totalLabel, labelWidth, }) {
22
+ const idPrefix = useId();
21
23
  const hasTotals = rows.some((row) => row.total !== undefined);
22
24
  // An aligned strip sizes its columns from the region's width; it needs
23
25
  // the column count and whether a total column takes a share.
@@ -27,5 +29,5 @@ export function DenominationStrip({ className, framed, label, columns, rows, zer
27
29
  '--sw-denomination-columns': columns.length,
28
30
  '--sw-denomination-total': hasTotals ? 1 : 0,
29
31
  };
30
- return (_jsx(ScrollRegion, { "aria-label": label, className: cx('sw-denomination-scroll', aligned && 'sw-denomination-scroll-aligned', framed && denominationScrollFramedClass), children: _jsxs("table", { className: cx('sw-denomination-grid sw-denomination-strip', labelWidth && 'sw-denomination-strip-aligned', rows.some((row) => row.entry) && 'sw-denomination-strip-entry', labelWidth && denominationLabelWidthClass(labelWidth), framed && denominationFramedClass, className), style: aligned, children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: _jsx(ColumnHeadText, { column: column }) }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(StripRow, { columns: columns, hasTotals: hasTotals, row: row, zeroLabel: zeroLabel }, row.id))) })] }) }));
32
+ return (_jsx(ScrollRegion, { "aria-label": label, className: cx('sw-denomination-scroll', aligned && 'sw-denomination-scroll-aligned', framed && denominationScrollFramedClass), children: _jsxs("table", { className: cx('sw-denomination-grid sw-denomination-strip', labelWidth && 'sw-denomination-strip-aligned', rows.some((row) => row.entry) && 'sw-denomination-strip-entry', labelWidth && denominationLabelWidthClass(labelWidth), framed && denominationFramedClass, className), style: aligned, children: [_jsx("caption", { className: "sw-sr-only", children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", { className: "sw-denomination-corner" }), columns.map((column) => (_jsx("th", { className: "sw-denomination-head", scope: "col", children: _jsx(ColumnHeadText, { column: column }) }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row, rowIndex) => (_jsx(StripRow, { columns: columns, hasTotals: hasTotals, idPrefix: idPrefix, row: row, rowIndex: rowIndex, zeroLabel: zeroLabel }, row.id))) })] }) }));
31
33
  }
@@ -4,6 +4,8 @@ type EntryCellProps = {
4
4
  entry: DenominationGridEntry;
5
5
  column: DenominationGridColumn;
6
6
  index: number;
7
+ /** The value cell under this count, which describes it, if any. */
8
+ describedBy?: string;
7
9
  };
8
10
  /**
9
11
  * One editable count of a strip row: a full-width `Stepper` named by the
@@ -11,5 +13,5 @@ type EntryCellProps = {
11
13
  * message from the entry, and the cell's tone on its border (an accent one
12
14
  * is the Stepper's changed look).
13
15
  */
14
- export declare function EntryCell({ row, entry, column, index }: EntryCellProps): import("react").JSX.Element;
16
+ export declare function EntryCell({ row, entry, column, index, describedBy, }: EntryCellProps): import("react").JSX.Element;
15
17
  export {};
@@ -9,8 +9,8 @@ import { columnName } from './ColumnHeadText.js';
9
9
  * message from the entry, and the cell's tone on its border (an accent one
10
10
  * is the Stepper's changed look).
11
11
  */
12
- export function EntryCell({ row, entry, column, index }) {
12
+ export function EntryCell({ row, entry, column, index, describedBy, }) {
13
13
  const max = entry.max?.[index] ?? undefined;
14
14
  const tone = row.cellTones?.[index];
15
- return (_jsx("td", { className: cx('sw-denomination-entry', ...denominationCellToneClassNames(tone)), children: _jsx(Stepper, { changed: tone === 'accent', decrementLabel: entry.decrementLabel, disabled: row.disabled, error: entry.errors?.[index] ?? undefined, incrementLabel: entry.incrementLabel, label: `${row.label} ${columnName(column)}`, labelVisuallyHidden: true, limitAppearance: entry.limitAppearance, max: max, onChange: (value) => entry.onChange(column.key, value), placeholder: entry.placeholder, value: row.cells[index] ?? null, width: "full" }) }));
15
+ return (_jsx("td", { className: cx('sw-denomination-entry', ...denominationCellToneClassNames(tone)), children: _jsx(Stepper, { "aria-describedby": describedBy, changed: tone === 'accent', decrementLabel: entry.decrementLabel, disabled: row.disabled, error: entry.errors?.[index] ?? undefined, incrementLabel: entry.incrementLabel, label: `${row.label} ${columnName(column)}`, labelVisuallyHidden: true, limitAppearance: entry.limitAppearance, max: max, onChange: (value) => entry.onChange(column.key, value), placeholder: entry.placeholder, value: row.cells[index] ?? null, width: "full" }) }));
16
16
  }
@@ -4,10 +4,16 @@ type StripRowProps = {
4
4
  columns: readonly DenominationGridColumn[];
5
5
  zeroLabel: string;
6
6
  hasTotals: boolean;
7
+ /** A per-grid prefix that keeps the value cells' ids unique on the page. */
8
+ idPrefix: string;
9
+ /** The row's place in the grid, for its value cells' ids. */
10
+ rowIndex: number;
7
11
  };
8
12
  /**
9
13
  * One strip row: its pinned label (with the phone total under it), a count
10
- * per column, read-only or entered, and its total.
14
+ * per column, read-only or entered, and its total. With a `valueRow`, the
15
+ * value line follows it and takes the total; each entry count is described
16
+ * by the value under it.
11
17
  */
12
- export declare function StripRow({ row, columns, zeroLabel, hasTotals, }: StripRowProps): import("react").JSX.Element;
18
+ export declare function StripRow({ row, columns, zeroLabel, hasTotals, idPrefix, rowIndex, }: StripRowProps): import("react").JSX.Element;
13
19
  export {};
@@ -1,9 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { cx } from '../../class-names.js';
3
3
  import { denominationCellToneClassNames } from '../../css/css-denomination-cell-tones.js';
4
4
  import { denominationCellView } from '../../denomination-cells.js';
5
5
  import { EntryCell } from './EntryCell.js';
6
6
  import { RowLabelLine } from './RowLabelLine.js';
7
+ import { StripTotalCell, StripTotalInline } from './StripTotal.js';
8
+ import { hasValue, StripValueRow, valueCellId } from './StripValueRow.js';
7
9
  /**
8
10
  * One count, read-only: the zero label, a signed delta, and the cell's tone.
9
11
  * The value has its own box, which the `pill` tone appearance fills.
@@ -14,9 +16,16 @@ function CountCell({ row, index, zeroLabel, }) {
14
16
  }
15
17
  /**
16
18
  * One strip row: its pinned label (with the phone total under it), a count
17
- * per column, read-only or entered, and its total.
19
+ * per column, read-only or entered, and its total. With a `valueRow`, the
20
+ * value line follows it and takes the total; each entry count is described
21
+ * by the value under it.
18
22
  */
19
- export function StripRow({ row, columns, zeroLabel, hasTotals, }) {
20
- const { entry } = row;
21
- return (_jsxs("tr", { "aria-disabled": row.disabled || undefined, className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`, row.signed && 'sw-denomination-row-signed', row.emphasis === 'quiet' && 'sw-denomination-row-quiet', row.disabled && 'sw-denomination-row-disabled'), children: [_jsx("th", { className: "sw-denomination-label", scope: "row", children: _jsxs("span", { className: "sw-denomination-label-body", children: [_jsx(RowLabelLine, { row: row }), row.total !== undefined ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-total-inline", children: row.total })) : null] }) }), columns.map((column, index) => entry ? (_jsx(EntryCell, { column: column, entry: entry, index: index, row: row }, column.key)) : (_jsx(CountCell, { index: index, row: row, zeroLabel: zeroLabel }, column.key))), hasTotals ? (_jsx("td", { className: "sw-denomination-total", children: _jsx("span", { className: "sw-denomination-total-value", children: row.total ?? '' }) })) : null] }));
23
+ export function StripRow({ row, columns, zeroLabel, hasTotals, idPrefix, rowIndex, }) {
24
+ const { entry, valueRow } = row;
25
+ // With a value line, the total is on it, not on the counts.
26
+ const total = valueRow ? undefined : row.total;
27
+ const describedBy = (index) => hasValue(valueRow?.cells[index])
28
+ ? valueCellId(idPrefix, rowIndex, index)
29
+ : undefined;
30
+ return (_jsxs(_Fragment, { children: [_jsxs("tr", { "aria-disabled": row.disabled || undefined, className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`, row.signed && 'sw-denomination-row-signed', row.emphasis === 'quiet' && 'sw-denomination-row-quiet', row.disabled && 'sw-denomination-row-disabled'), children: [_jsx("th", { className: "sw-denomination-label", scope: "row", children: _jsxs("span", { className: "sw-denomination-label-body", children: [_jsx(RowLabelLine, { row: row }), _jsx(StripTotalInline, { total: total })] }) }), columns.map((column, index) => entry ? (_jsx(EntryCell, { column: column, describedBy: describedBy(index), entry: entry, index: index, row: row }, column.key)) : (_jsx(CountCell, { index: index, row: row, zeroLabel: zeroLabel }, column.key))), hasTotals ? _jsx(StripTotalCell, { total: total }) : null] }), valueRow ? (_jsx(StripValueRow, { columns: columns, hasTotals: hasTotals, idPrefix: idPrefix, row: row, rowIndex: rowIndex, valueRow: valueRow })) : null] }));
22
31
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A strip row's total cell: the consumer's total, or empty. Below md it is
3
+ * visually hidden, and `StripTotalInline` shows it under the row's name.
4
+ */
5
+ export declare function StripTotalCell({ total }: {
6
+ total: string | undefined;
7
+ }): import("react").JSX.Element;
8
+ /** The total under a row's name on a phone; the cell reads it to everyone. */
9
+ export declare function StripTotalInline({ total }: {
10
+ total: string | undefined;
11
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * A strip row's total cell: the consumer's total, or empty. Below md it is
4
+ * visually hidden, and `StripTotalInline` shows it under the row's name.
5
+ */
6
+ export function StripTotalCell({ total }) {
7
+ return (_jsx("td", { className: "sw-denomination-total", children: _jsx("span", { className: "sw-denomination-total-value", children: total ?? '' }) }));
8
+ }
9
+ /** The total under a row's name on a phone; the cell reads it to everyone. */
10
+ export function StripTotalInline({ total }) {
11
+ return total !== undefined ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-total-inline", children: total })) : null;
12
+ }
@@ -0,0 +1,25 @@
1
+ import { type DenominationGridColumn, type DenominationGridRow, type DenominationGridValueRow } from './types.js';
2
+ /**
3
+ * The id of a value cell, which the count above it is described by: the
4
+ * grid's prefix and the cell's place, never consumer text, so it is always
5
+ * one valid token of an id list.
6
+ */
7
+ export declare function valueCellId(idPrefix: string, rowIndex: number, columnIndex: number): string;
8
+ /** Whether a value cell shows something: not `null` and not blank. */
9
+ export declare function hasValue(cell: string | null | undefined): cell is string;
10
+ type StripValueRowProps = {
11
+ row: DenominationGridRow;
12
+ valueRow: DenominationGridValueRow;
13
+ columns: readonly DenominationGridColumn[];
14
+ hasTotals: boolean;
15
+ /** A per-grid prefix that keeps the value cells' ids unique on the page. */
16
+ idPrefix: string;
17
+ rowIndex: number;
18
+ };
19
+ /**
20
+ * The value line under a strip row: its name in the label column (with the
21
+ * row's total under it on a phone), a centred value per column, and the
22
+ * row's total. It keeps the row's tone, so a toned total stays toned.
23
+ */
24
+ export declare function StripValueRow({ row, valueRow, columns, hasTotals, idPrefix, rowIndex, }: StripValueRowProps): import("react").JSX.Element;
25
+ export {};
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../../class-names.js';
3
+ import { StripTotalCell, StripTotalInline } from './StripTotal.js';
4
+ import { denominationValueRowClass } from '../../css/css-denomination-value-row.js';
5
+ /**
6
+ * The id of a value cell, which the count above it is described by: the
7
+ * grid's prefix and the cell's place, never consumer text, so it is always
8
+ * one valid token of an id list.
9
+ */
10
+ export function valueCellId(idPrefix, rowIndex, columnIndex) {
11
+ return `${idPrefix}value-${rowIndex}-${columnIndex}`;
12
+ }
13
+ /** Whether a value cell shows something: not `null` and not blank. */
14
+ export function hasValue(cell) {
15
+ return typeof cell === 'string' && cell.trim() !== '';
16
+ }
17
+ /**
18
+ * The value line under a strip row: its name in the label column (with the
19
+ * row's total under it on a phone), a centred value per column, and the
20
+ * row's total. It keeps the row's tone, so a toned total stays toned.
21
+ */
22
+ export function StripValueRow({ row, valueRow, columns, hasTotals, idPrefix, rowIndex, }) {
23
+ return (_jsxs("tr", { "aria-disabled": row.disabled || undefined, className: cx('sw-denomination-row', denominationValueRowClass, `sw-denomination-row-${row.tone ?? 'neutral'}`, row.disabled && 'sw-denomination-row-disabled'), children: [_jsx("th", { "aria-label": `${row.label} ${valueRow.label}`, className: "sw-denomination-label", scope: "row", children: _jsxs("span", { className: "sw-denomination-label-body", children: [_jsx("span", { className: "sw-denomination-label-line", children: _jsx("span", { className: "sw-denomination-label-text", children: valueRow.label }) }), _jsx(StripTotalInline, { total: row.total })] }) }), columns.map((column, index) => {
24
+ const cell = valueRow.cells[index];
25
+ return (_jsx("td", { className: "sw-denomination-value", id: valueCellId(idPrefix, rowIndex, index), children: hasValue(cell) ? cell : '' }, column.key));
26
+ }), hasTotals ? _jsx(StripTotalCell, { total: row.total }) : null] }));
27
+ }
@@ -45,6 +45,20 @@ export type DenominationGridEntry = {
45
45
  */
46
46
  limitAppearance?: StepperLimitAppearance;
47
47
  };
48
+ /**
49
+ * A line under a strip row that gives each column's value, such as the
50
+ * money its count adds up to: its own table row on the subtle fill, named
51
+ * in the label column, one consumer-formatted value per column centred
52
+ * under its count (`null` or `''` leaves a cell blank, such as for a zero
53
+ * count), and the row's `total` in the total column. The grid does no
54
+ * arithmetic and no currency formatting.
55
+ */
56
+ export type DenominationGridValueRow = {
57
+ /** The line's name in the label column, such as "Value". */
58
+ label: string;
59
+ /** One formatted value, or `null` for none, per column. */
60
+ cells: readonly (string | null)[];
61
+ };
48
62
  export type DenominationGridRow = {
49
63
  id: string;
50
64
  label: string;
@@ -80,6 +94,11 @@ export type DenominationGridRow = {
80
94
  * `entry.errors` wins.
81
95
  */
82
96
  entry?: DenominationGridEntry;
97
+ /**
98
+ * A value line under this row (the `strip` layout only); the row's
99
+ * `total` moves to it, and each entry count is described by its value.
100
+ */
101
+ valueRow?: DenominationGridValueRow;
83
102
  };
84
103
  export type DenominationGridProps = {
85
104
  label: string;
@@ -0,0 +1,6 @@
1
+ import { type DenominationGridColumn, type DenominationGridLayout, type DenominationGridRow } from './types.js';
2
+ /**
3
+ * Value rows: the strip layout only, a name that is not blank, and one
4
+ * formatted value or null per column.
5
+ */
6
+ export declare function assertDenominationValueRows(layout: DenominationGridLayout, columns: readonly DenominationGridColumn[], rows: readonly DenominationGridRow[]): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Value rows: the strip layout only, a name that is not blank, and one
3
+ * formatted value or null per column.
4
+ */
5
+ export function assertDenominationValueRows(layout, columns, rows) {
6
+ const valueRows = rows.flatMap((row) => row.valueRow == null ? [] : [row.valueRow]);
7
+ if (valueRows.length === 0)
8
+ return;
9
+ if (layout !== 'strip')
10
+ throw new RangeError('value rows need the strip layout');
11
+ for (const valueRow of valueRows) {
12
+ if (typeof valueRow !== 'object')
13
+ throw new RangeError('a value row is an object of label and cells');
14
+ if (typeof valueRow.label !== 'string' || !valueRow.label.trim())
15
+ throw new RangeError('a value row needs a label');
16
+ if (!fitsColumns(valueRow.cells, columns.length))
17
+ throw new RangeError('a value row needs one string or null per column');
18
+ }
19
+ }
20
+ function fitsColumns(cells, columnCount) {
21
+ if (!Array.isArray(cells) || cells.length !== columnCount)
22
+ return false;
23
+ // An index loop, not every(): every() skips the holes of a sparse array.
24
+ for (let index = 0; index < columnCount; index += 1) {
25
+ const cell = cells[index];
26
+ if (cell !== null && typeof cell !== 'string')
27
+ return false;
28
+ }
29
+ return true;
30
+ }
@@ -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
+ }
package/dist/index.d.ts CHANGED
@@ -58,6 +58,6 @@ export type { GridColumns, GridProps } from './components/Grid.js';
58
58
  export type { GridColumnSpan } from './css/css-grid-span.js';
59
59
  export type { ColumnSpanProps } from './column-span-classes.js';
60
60
  export { DenominationGrid, denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationTotalEmphases, } from './components/DenominationGrid.js';
61
- export type { DenominationCellToneAppearance, DenominationGridColumn, DenominationGridEntry, DenominationGridFrame, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationLabelAppearance, DenominationLabelWidth, DenominationTotalEmphasis, } from './components/DenominationGrid.js';
61
+ export type { DenominationCellToneAppearance, DenominationGridColumn, DenominationGridEntry, DenominationGridFrame, DenominationGridLayout, DenominationGridProps, DenominationGridRow, DenominationGridRowEmphasis, DenominationGridRowRole, DenominationGridTone, DenominationGridValueRow, DenominationLabelAppearance, DenominationLabelWidth, DenominationTotalEmphasis, } from './components/DenominationGrid.js';
62
62
  export { FilterChips } from './components/FilterChips.js';
63
63
  export type { FilterChipOption, FilterChipsProps, } from './components/FilterChips.js';
package/dist/styles.css CHANGED
@@ -4907,6 +4907,41 @@ body {
4907
4907
  color: var(--sw-color-text);
4908
4908
  }
4909
4909
 
4910
+ .sw-denomination-strip .sw-denomination-value-row,
4911
+ .sw-denomination-strip .sw-denomination-value-row .sw-denomination-label {
4912
+ background: var(--sw-color-subtle);
4913
+ }
4914
+
4915
+ .sw-denomination-strip .sw-denomination-value-row :is(th, td) {
4916
+ padding-block: var(--sw-space-2);
4917
+ }
4918
+
4919
+ .sw-denomination-strip .sw-denomination-value-row .sw-denomination-value {
4920
+ color: var(--sw-color-text);
4921
+ font-size: 15px;
4922
+ font-variant-numeric: tabular-nums;
4923
+ font-weight: 500;
4924
+ letter-spacing: -0.2px;
4925
+ line-height: 20px;
4926
+ text-align: center;
4927
+ white-space: nowrap;
4928
+ }
4929
+
4930
+ .sw-denomination-strip .sw-denomination-value-row.sw-denomination-row-disabled .sw-denomination-value {
4931
+ opacity: var(--sw-disabled-opacity);
4932
+ }
4933
+
4934
+ @media not all and (min-width: 48rem) {
4935
+ .sw-denomination-strip .sw-denomination-value-row :is(th, td) { padding-block: var(--sw-space-1); }
4936
+ .sw-denomination-strip .sw-denomination-value-row .sw-denomination-total { padding: 0; }
4937
+ }
4938
+
4939
+ @media (forced-colors: active) {
4940
+ .sw-denomination-strip .sw-denomination-value-row,
4941
+ .sw-denomination-strip .sw-denomination-value-row .sw-denomination-label { background: Canvas; }
4942
+ .sw-denomination-strip .sw-denomination-value-row .sw-denomination-value { color: CanvasText; }
4943
+ }
4944
+
4910
4945
  @media (forced-colors: active) {
4911
4946
  .sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
4912
4947
  .sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalewing/react",
3
- "version": "1.36.0",
3
+ "version": "1.37.0",
4
4
  "description": "Scalewing DOM components and package-owned CSS utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",