@scalewing/react 1.15.0 → 1.16.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 +13 -0
- package/README.md +28 -3
- package/dist/components/Field.d.ts +10 -1
- package/dist/components/Field.js +17 -4
- package/dist/components/denomination-grid/DenominationStrip.js +2 -1
- package/dist/components/denomination-grid/DenominationTiles.js +2 -1
- package/dist/components/denomination-grid/RowLabelLine.d.ts +9 -0
- package/dist/components/denomination-grid/RowLabelLine.js +9 -0
- package/dist/css/css-button.d.ts +1 -0
- package/dist/css/css-button.js +9 -0
- package/dist/css/css-denomination-grid.js +13 -0
- package/dist/css/css-document.d.ts +2 -0
- package/dist/css/css-document.js +16 -0
- package/dist/css/css-toast.d.ts +1 -0
- package/dist/css/css-toast.js +9 -0
- package/dist/styles.css +29 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b64a805: `Field` takes `invalid` (`docs/requests/teisoro-field-validation.md`, 2026-09-30 follow-up, Teisoro F-007-S05 task 1375, DRW-18). `invalid?: boolean` marks the native control invalid, `aria-invalid="true"` and the same danger border as `error` (on a `prefix`/`suffix` frame too, `Mark` in forced colors), without a message of its own, for fields whose one error is shown elsewhere, such as under a group of count fields; the consumer points the control's `aria-describedby` at that message, and it is kept. The field's polite region stays empty and its `description` stays. `error` implies it. Passing `invalid`, even `invalid={false}` but not `invalid={undefined}` (so a wrapper forwarding an optional prop is safe), requires one native control child from the first render (a `TypeError` otherwise), so a form that marks a composed child learns it while its fields are valid, not on its first failed submit. `error` is unchanged: it requires the native control only while it holds a message, so `error={undefined}` beside a composed child still renders. Without it nothing changes. No new dependencies.
|
|
8
|
+
- 68beae9: A programmatic focus target takes the accent focus ring (`docs/requests/teisoro-focus-target.md`, Teisoro F-007-S05 task 1375, NSF-34, ENT-29, DRW-30). The generated document canvas gains `:where([data-theme] [tabindex='-1']:focus-visible)` with the same ring as links and native controls (`--sw-focus-ring-width` solid `--sw-color-accent`, `--sw-focus-ring-offset` out), so a notice or card a script focuses after a save, such as a `Box tabIndex={-1} radius="lg"` round a `Card`, no longer shows the browser's outline. The ring shows only when focus is visible, as the browser's does, and the rule has zero specificity, so a component's own ring always wins. No new prop or class and no new dependencies.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 849ce60: `Button` sets a glyph one token gap from its label (`docs/requests/teisoro-button.md`, 2026-09-30 follow-up, Teisoro F-007-S05 task 1375). `.sw-button` gains `gap: var(--sw-space-2)`, so a glyph and a label passed as the button's own children sit 8 px apart at every size instead of touching. An icon-only button, a visually hidden name (`CalendarButton`) and a label already wrapped in one element with its own gap (`<Inline as="span" gap={2}>`) are unchanged: each is a single flex item, so there is never a second gap, and the wrapper can be dropped. A phrase split across elements (`Save <strong>draft</strong> now`) already drew its parts touching, because each is a flex item whose edge spaces collapse; they are now 8 px apart. Put such a phrase in one element, where its word spaces are kept. No API change and no new dependencies.
|
|
13
|
+
- 9a84726: `DenominationGrid` keeps a row's icon beside its label (`docs/requests/teisoro-denomination-grid.md`, 2026-09-30 follow-up, Teisoro F-007-S05 task 1375, DRW-28). A row's icon and words now sit in `.sw-denomination-label-line`, a flex row that does not wrap, in both layouts, so a long label wraps its words beside the icon instead of dropping them under it. Below `md` the strip no longer puts the icon on its own line over the label: the line stays a row (its gap narrows to spacing step 1 in the strip only; a tiles row keeps step 2), and the phone total keeps its own line under it. New generated class `sw-denomination-label-line`; no API change and no new dependencies.
|
|
14
|
+
- b7beef1: `Toast` keeps the page gutter on a phone (`docs/requests/teisoro-toast.md`, 2026-09-30 follow-up, Teisoro F-007-S05 task 1375, DRW-29). `.sw-toast` is capped at `calc(100% - var(--sw-space-4) - var(--sw-space-4))` of the top layer and wraps a long word (`overflow-wrap: break-word`), so a message wider than the screen wraps inside a 16 px gutter on each side instead of running to the edges. A toast is still as wide as its message up to that cap and centred, so a short toast is unchanged. No API change and no new dependencies.
|
|
15
|
+
|
|
3
16
|
## 1.15.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -50,7 +50,23 @@ Import the CSS once at the application entry. Do not copy it into your source tr
|
|
|
50
50
|
|
|
51
51
|
`Box hideBelow="md"` hides a region under 48rem and `hideFrom="md"` hides it at 48rem and wider, so a product can swap a wide layout for a narrow one without its own stylesheet. Hidden regions leave the accessibility tree; do not use this to protect data. `Box as="a"` is a layout link. Use `Button` for press actions. `Field` associates a native `<input>` or `<select>` with a label and token gap; native text controls inherit the generated document canvas. `Select` is a labeled listbox menu when the open list must match the canvas.
|
|
52
52
|
|
|
53
|
-
`Field` supports optional `description`, `error`, and `required` on one native input, select, or textarea child. It preserves an existing `aria-describedby` and appends a stable ID for the hint, or for the error, which replaces the hint on screen while it is supplied. `error` sets `aria-invalid` and token-owned invalid styling. The error sits in a polite live region (`aria-live="polite"`) that is always rendered, empty while there is no error, so an error that appears while the person types, or after a submit, is announced once, politely, when its text is swapped in; it is never `role="alert"`, so a submit that finds several errors does not fire several assertive alerts at once. Empty, the region takes no room in the layout. The consumer owns validation and localized messages, and after a refused submit it moves focus to the first invalid control (or to one form-level summary). `Checkbox`, `RadioGroup` and `DateField` expose their `error` the same way.
|
|
53
|
+
`Field` supports optional `description`, `error`, and `required` on one native input, select, or textarea child. It preserves an existing `aria-describedby` and appends a stable ID for the hint, or for the error, which replaces the hint on screen while it is supplied. `error` sets `aria-invalid` and token-owned invalid styling. The error sits in a polite live region (`aria-live="polite"`) that is always rendered, empty while there is no error, so an error that appears while the person types, or after a submit, is announced once, politely, when its text is swapped in; it is never `role="alert"`, so a submit that finds several errors does not fire several assertive alerts at once. Empty, the region takes no room in the layout. The consumer owns validation and localized messages, and after a refused submit it moves focus to the first invalid control (or to one form-level summary). `Checkbox`, `RadioGroup` and `DateField` expose their `error` the same way. `invalid` marks the control invalid the same way (`aria-invalid` and the danger border, `Mark` in forced colors) without a message of its own, for fields whose one error is shown elsewhere, such as a count that adds up to nothing under a group of count fields: point each control's `aria-describedby` at that message so it is read with the field. The field's own polite region stays empty and its `description` stays on screen; `error` implies `invalid`. Passing `invalid`, even `invalid={false}` but not `invalid={undefined}`, needs one native control child and throws a `TypeError` on the first render otherwise, so a form learns it while the fields are valid, not on its first failed submit. `error` keeps its rule: it needs the native control only while it holds a message. `invalid` is `Field`'s only: `DateField`, `Select` and the other controls with `error` do not have it yet.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Field invalid={countIsZero} label="$1 bills">
|
|
57
|
+
<input aria-describedby={countIsZero ? countErrorId : undefined} />
|
|
58
|
+
</Field>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
An element a script focuses, such as a notice or a card announced after a save, takes the same accent focus ring as the canvas's links and native controls, 2 px past its edge (`--sw-focus-ring-width` and `--sw-focus-ring-offset`), instead of the browser's outline. Give it `tabIndex={-1}`, so it takes focus from a script but is no tab stop, and put the focus on the whole card rather than its text, so the ring goes round it clear of the words; a `Box` round the card with the card's `radius` makes the ring follow its corners. As with the browser's own outline, the ring shows only when focus is visible (`:focus-visible`): after a keyboard press, not after a mouse press. The rule has zero specificity, so a component's own ring always wins.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<Box ref={notice} tabIndex={-1} radius="lg" role="status">
|
|
65
|
+
<Card padding={4} variant="outlined">
|
|
66
|
+
<Text as="p">Survey saved.</Text>
|
|
67
|
+
</Card>
|
|
68
|
+
</Box>
|
|
69
|
+
```
|
|
54
70
|
|
|
55
71
|
A disabled native text control (`<input>`, `<select>` or `<textarea>` on the canvas, a `Field` frame with a `prefix` or `suffix`, or a `DateField` entry) takes the quiet subtle fill, a dashed border and a not-allowed cursor. Its value stays in the text color at full strength, so a locked value is still readable; forced colors keep the dashed border in the system's disabled color. A typed input's placeholder, enabled or disabled, is drawn in the muted color, which keeps 4.5:1 on the field in every palette.
|
|
56
72
|
|
|
@@ -124,6 +140,15 @@ A disabled native text control (`<input>`, `<select>` or `<textarea>` on the can
|
|
|
124
140
|
</Stack>
|
|
125
141
|
```
|
|
126
142
|
|
|
143
|
+
A `Button`'s own children are laid out in a row one token gap apart (spacing step 2, 8 px), so a glyph passed beside the label never touches it. Pass the glyph and the label as direct children. An icon-only button has one child and is unchanged, and a visually hidden name (`sw-sr-only`) is out of the flow, so it adds no gap. A label already wrapped in one element with its own gap, such as `<Inline as="span" gap={2}>`, is a single child and gets no second gap; it can drop the wrapper. A phrase split across elements, such as `Save <strong>draft</strong> now`, is several flex items: the spaces at their edges collapse, so its parts touched before and are a gap apart now. Put such a phrase in one element (`<span>Save <strong>draft</strong> now</span>`), where its word spaces are kept.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<Button onPress={logSighting}>
|
|
147
|
+
<Plus aria-hidden />
|
|
148
|
+
Log sighting
|
|
149
|
+
</Button>
|
|
150
|
+
```
|
|
151
|
+
|
|
127
152
|
A `Button` that toggles something passes `aria-pressed`. The pressed button gets a 2 px accent ring outside its fill, past a 2 px gap in `--sw-color-background`, so the ring meets only the page and keeps the accent's contrast on the canvas (4.5:1 or more in every palette, at least the 3:1 a state indicator needs) whatever its `variant` or fill. A focused pressed button moves its focus outline out past the ring. In forced colors, which drop shadows, the pressed button keeps the forced button colors, so its label and any glyph or badge inside it stay readable, and draws the same ring and its own border in the system highlight (`Highlight`); nothing opts out of forced colors. An unpressed button is drawn at full strength, so its label keeps its contrast. To make the choice stand out further, give the pressed button `primary` and the others `secondary`.
|
|
128
153
|
|
|
129
154
|
`Badge` is a non-interactive chicklet with a `tone` (`neutral`, `accent`, `success`, `danger`, `warning`) and a `size` (`md`, or `sm` for a table cell). A badge inside a filled `Button` (every variant but `ghost`) sits on `--sw-color-surface` with its words in the text color and its tone on its border, so it reads at 4.5:1 in every palette whatever the button's fill.
|
|
@@ -132,7 +157,7 @@ A `Button` that toggles something passes `aria-pressed`. The pressed button gets
|
|
|
132
157
|
|
|
133
158
|
`Dialog` is a modal `<dialog>` controlled by `open`. Escape, a backdrop press, a platform close request, and a `<form method="dialog">` submit (or a submitter with `formmethod="dialog"`) are each prevented and call `onClose`; none of them closes the dialog itself. A consumer `onKeyDown`, `onCancel`, `onSubmit` or `onPointerDown` that prevents the event vetoes that request. Escape in a search field that holds text clears the field first, and a descendant's own `cancel` (a dismissed file picker) is not a close request. Set `open` to false to close it, or keep it true (for example while a form is saving) and it stays shown. `onClose` is not called when `open` turns false, and it is required: a dialog that must not be dismissed passes a callback that keeps `open` true.
|
|
134
159
|
|
|
135
|
-
`Toast` is an auto-dismiss confirmation on the popover layer, controlled by `open` and `onOpenChange`; it does not trap focus and closes after `timeoutMs` (default 800, or 6000 for a `warning` or `danger` toast, which must be read). `anchor` and `target` send it from a press to a destination. `tone` (`neutral` by default, `success`, `warning`, `danger`) tints its border and an optional consumer `icon` (a Lucide glyph, hidden from assistive technology); the message keeps the text color, so its words carry the meaning. A `danger` toast is announced as `role="alert"`, the others as `role="status"`, so do not wrap the message in another live region. A toast still dismisses itself: an error the person must act on belongs in the page.
|
|
160
|
+
`Toast` is an auto-dismiss confirmation on the popover layer, controlled by `open` and `onOpenChange`; it does not trap focus and closes after `timeoutMs` (default 800, or 6000 for a `warning` or `danger` toast, which must be read). `anchor` and `target` send it from a press to a destination. `tone` (`neutral` by default, `success`, `warning`, `danger`) tints its border and an optional consumer `icon` (a Lucide glyph, hidden from assistive technology); the message keeps the text color, so its words carry the meaning. A `danger` toast is announced as `role="alert"`, the others as `role="status"`, so do not wrap the message in another live region. A toast still dismisses itself: an error the person must act on belongs in the page. A toast is as wide as its message, up to the page's gutter (spacing step 4) on each side, so on a phone a long message wraps inside the gutter instead of running to the screen's edges.
|
|
136
161
|
|
|
137
162
|
`Switch` is a controlled on/off input. It keeps native checkbox keyboard behavior and exposes switch semantics. Provide the current `checked` value and update it in `onCheckedChange`.
|
|
138
163
|
|
|
@@ -237,7 +262,7 @@ Values outside the catalog throw a `RangeError`. There is no per-breakpoint span
|
|
|
237
262
|
</Grid>
|
|
238
263
|
```
|
|
239
264
|
|
|
240
|
-
`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 for a screen reader: it renders a visually hidden `th scope="col"`, and without it the corner stays an empty cell as before (either way the column takes no width on a phone). 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; 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. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
|
|
265
|
+
`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 for a screen reader: it renders a visually hidden `th scope="col"`, and without it the corner stays an empty cell as before (either way the column takes no width on a phone). 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; 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. 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. The primitive does no arithmetic and no currency formatting; tones reuse the Badge vocabulary. Invalid columns, rows, or cells throw.
|
|
241
266
|
|
|
242
267
|
`FilterChips` presents a long, wrapping single-choice filter set. Provide a localized group `label`, controlled `value`, `onChange`, and options with stable values and localized labels. An optional non-negative integer `count` renders as a tabular chicklet after the label and is part of the option's accessible name; a zero count quiets the chip until it is selected: it drops its glass fill and sets its label in the muted color (4.5:1 in every palette), without fading, since it can still be chosen. Native radios provide arrow and Space navigation; disabled options are skipped.
|
|
243
268
|
|
|
@@ -9,10 +9,19 @@ export type FieldProps = {
|
|
|
9
9
|
size?: FieldSize;
|
|
10
10
|
description?: string;
|
|
11
11
|
error?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Marks the control invalid (`aria-invalid` and the danger border) without
|
|
14
|
+
* a message of its own, for a field whose error is shown elsewhere, such
|
|
15
|
+
* as one message under a group of fields. Point the control's
|
|
16
|
+
* `aria-describedby` at that message. `error` implies it. Passing
|
|
17
|
+
* `invalid`, even `false` but not `undefined`, requires one native control
|
|
18
|
+
* child from the first render.
|
|
19
|
+
*/
|
|
20
|
+
invalid?: boolean;
|
|
12
21
|
required?: boolean;
|
|
13
22
|
/** Short text inside the control frame before the value, such as a currency sign. Not part of the value. */
|
|
14
23
|
prefix?: string;
|
|
15
24
|
/** Short text inside the control frame after the value, such as a unit. Not part of the value. */
|
|
16
25
|
suffix?: string;
|
|
17
26
|
};
|
|
18
|
-
export declare function Field(
|
|
27
|
+
export declare function Field(props: FieldProps): import("react").JSX.Element;
|
package/dist/components/Field.js
CHANGED
|
@@ -5,7 +5,19 @@ import { adornedControlAria, FieldAdornment, } from './FieldAdornment.js';
|
|
|
5
5
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
6
6
|
import { FieldLabelText } from './FieldLabelText.js';
|
|
7
7
|
import { Stack } from './Stack.js';
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* `invalid` was passed, even as `false`, but not as `undefined` (a wrapper
|
|
10
|
+
* forwarding an optional prop passes nothing): such a field needs one native
|
|
11
|
+
* control from its first render, so a form learns it on the happy path
|
|
12
|
+
* instead of on its first failed submit. `error` keeps its older rule (it
|
|
13
|
+
* requires the control only while it holds a message), so a composed
|
|
14
|
+
* child beside `error={undefined}` still renders.
|
|
15
|
+
*/
|
|
16
|
+
function passesInvalid(props) {
|
|
17
|
+
return props.invalid !== undefined;
|
|
18
|
+
}
|
|
19
|
+
export function Field(props) {
|
|
20
|
+
const { children, gap = 1, label, labelVisuallyHidden = false, size = 'md', description, error, invalid = false, required = false, prefix, suffix, } = props;
|
|
9
21
|
const controlId = useId();
|
|
10
22
|
const descriptionId = useId();
|
|
11
23
|
const errorId = useId();
|
|
@@ -13,12 +25,13 @@ export function Field({ children, gap = 1, label, labelVisuallyHidden = false, s
|
|
|
13
25
|
const prefixId = useId();
|
|
14
26
|
const suffixId = useId();
|
|
15
27
|
const message = error || description;
|
|
28
|
+
const marksInvalid = Boolean(error) || invalid;
|
|
16
29
|
const adorned = Boolean(prefix || suffix);
|
|
17
|
-
const needsControl = Boolean(message || required || adorned);
|
|
30
|
+
const needsControl = Boolean(message || marksInvalid || passesInvalid(props) || required || adorned);
|
|
18
31
|
const className = [
|
|
19
32
|
'sw-field',
|
|
20
33
|
size === 'xs' && 'sw-field-xs',
|
|
21
|
-
|
|
34
|
+
marksInvalid && 'sw-field-invalid',
|
|
22
35
|
]
|
|
23
36
|
.filter(Boolean)
|
|
24
37
|
.join(' ');
|
|
@@ -46,7 +59,7 @@ export function Field({ children, gap = 1, label, labelVisuallyHidden = false, s
|
|
|
46
59
|
const control = cloneElement(children, {
|
|
47
60
|
id: children.props.id || controlId,
|
|
48
61
|
'aria-describedby': describedBy || undefined,
|
|
49
|
-
'aria-invalid':
|
|
62
|
+
'aria-invalid': marksInvalid ? true : children.props['aria-invalid'],
|
|
50
63
|
'aria-labelledby': adornment.labelledBy,
|
|
51
64
|
required: required || children.props.required,
|
|
52
65
|
});
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { cx } from '../../class-names.js';
|
|
3
3
|
import { denominationCellView } from '../../denomination-cells.js';
|
|
4
4
|
import { ScrollRegion } from '../ScrollRegion.js';
|
|
5
|
+
import { RowLabelLine } from './RowLabelLine.js';
|
|
5
6
|
/**
|
|
6
7
|
* The total column's header: a visually hidden `th` when the consumer names
|
|
7
8
|
* the column, otherwise the empty corner it has always been. Both carry
|
|
@@ -14,7 +15,7 @@ function TotalHead({ totalLabel }) {
|
|
|
14
15
|
}
|
|
15
16
|
export function DenominationStrip({ label, columns, rows, zeroLabel, totalLabel, }) {
|
|
16
17
|
const hasTotals = rows.some((row) => row.total !== undefined);
|
|
17
|
-
return (_jsx(ScrollRegion, { "aria-label": label, className: "sw-denomination-scroll", children: _jsxs("table", { className: "sw-denomination-grid sw-denomination-strip", 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: column.label }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`, row.signed && 'sw-denomination-row-signed'), children: [_jsx("th", { className: "sw-denomination-label", scope: "row", children: _jsxs("span", { className: "sw-denomination-label-body", children: [
|
|
18
|
+
return (_jsx(ScrollRegion, { "aria-label": label, className: "sw-denomination-scroll", children: _jsxs("table", { className: "sw-denomination-grid sw-denomination-strip", 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: column.label }, column.key))), hasTotals ? _jsx(TotalHead, { totalLabel: totalLabel }) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`, row.signed && 'sw-denomination-row-signed'), 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) => {
|
|
18
19
|
const view = denominationCellView(row.cells[index] ?? null, row.signed ?? false, zeroLabel);
|
|
19
20
|
return (_jsx("td", { className: `sw-denomination-cell sw-denomination-cell-${view.state}`, children: view.text }, column.key));
|
|
20
21
|
}), hasTotals ? (_jsx("td", { className: "sw-denomination-total", children: _jsx("span", { className: "sw-denomination-total-value", children: row.total ?? '' }) })) : null] }, row.id))) })] }) }));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../../class-names.js';
|
|
3
3
|
import { denominationCellView } from '../../denomination-cells.js';
|
|
4
|
+
import { RowLabelLine } from './RowLabelLine.js';
|
|
4
5
|
/**
|
|
5
6
|
* A lone row is named by the grid's own label, so its label line shows only
|
|
6
7
|
* when it adds something: an icon or a total. Several rows always show it.
|
|
@@ -20,7 +21,7 @@ function namesRow(rowCount, row, gridLabel) {
|
|
|
20
21
|
export function DenominationTiles({ label, columns, rows, subtotal, zeroLabel, }) {
|
|
21
22
|
return (_jsx("div", { "aria-label": label, className: "sw-denomination-grid sw-denomination-tiles", role: "group", children: rows.map((row) => {
|
|
22
23
|
const labelled = showsRowLabel(rows.length, row);
|
|
23
|
-
return (_jsxs("section", { "aria-label": namesRow(rows.length, row, label) ? row.label : undefined, className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`), children: [labelled ? (_jsxs("span", { className: "sw-denomination-label", children: [
|
|
24
|
+
return (_jsxs("section", { "aria-label": namesRow(rows.length, row, label) ? row.label : undefined, className: cx('sw-denomination-row', `sw-denomination-row-${row.tone ?? 'neutral'}`), children: [labelled ? (_jsxs("span", { className: "sw-denomination-label", children: [_jsx(RowLabelLine, { row: row }), row.total !== undefined ? (_jsx("span", { className: "sw-denomination-total", children: row.total })) : null] })) : null, _jsx("ul", { className: "sw-denomination-tile-list", children: columns.map((column, index) => {
|
|
24
25
|
const cell = row.cells[index] ?? null;
|
|
25
26
|
const view = denominationCellView(cell, row.signed ?? false, zeroLabel);
|
|
26
27
|
const amount = subtotal && cell !== null && cell !== 0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DenominationGridRow } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A row label's glyph and words, kept on one line: when the words wrap,
|
|
4
|
+
* they wrap beside the glyph, never under it (Teisoro DRW-28). Both the
|
|
5
|
+
* strip and the tiles layouts draw a row label with it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function RowLabelLine({ row }: {
|
|
8
|
+
row: DenominationGridRow;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A row label's glyph and words, kept on one line: when the words wrap,
|
|
4
|
+
* they wrap beside the glyph, never under it (Teisoro DRW-28). Both the
|
|
5
|
+
* strip and the tiles layouts draw a row label with it.
|
|
6
|
+
*/
|
|
7
|
+
export function RowLabelLine({ row }) {
|
|
8
|
+
return (_jsxs("span", { className: "sw-denomination-label-line", children: [row.icon ? (_jsx("span", { "aria-hidden": "true", className: "sw-denomination-icon", children: row.icon })) : null, _jsx("span", { className: "sw-denomination-label-text", children: row.label })] }));
|
|
9
|
+
}
|
package/dist/css/css-button.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare function buttonClassNames(options: {
|
|
|
5
5
|
}): string[];
|
|
6
6
|
export declare const pressedRingGap = 2;
|
|
7
7
|
export declare const pressedRingWidth = 4;
|
|
8
|
+
export declare const buttonContentGap = "var(--sw-space-2)";
|
|
8
9
|
export declare function cssButtonClasses(): string;
|
|
9
10
|
export declare const buttonGroupJustifies: readonly ["start", "end", "between"];
|
|
10
11
|
export declare function buttonClassCatalog(): string[];
|
package/dist/css/css-button.js
CHANGED
|
@@ -52,6 +52,14 @@ function sizeRules() {
|
|
|
52
52
|
})
|
|
53
53
|
.join('\n');
|
|
54
54
|
}
|
|
55
|
+
/*
|
|
56
|
+
* The button's own children are its flex items, so a glyph and its label
|
|
57
|
+
* sit one token gap apart. An icon-only button has one item, and a consumer
|
|
58
|
+
* that wraps its glyph and label in one element (an Inline span) has one
|
|
59
|
+
* item too, so neither gets a second gap. A visually hidden name is
|
|
60
|
+
* absolutely positioned, out of the flex flow, so it adds none either.
|
|
61
|
+
*/
|
|
62
|
+
export const buttonContentGap = 'var(--sw-space-2)';
|
|
55
63
|
export function cssButtonClasses() {
|
|
56
64
|
return `.sw-button {
|
|
57
65
|
appearance: none;
|
|
@@ -65,6 +73,7 @@ export function cssButtonClasses() {
|
|
|
65
73
|
font-family: var(--sw-font-sans);
|
|
66
74
|
font-size: ${label.fontSize}px;
|
|
67
75
|
font-weight: ${label.fontWeight};
|
|
76
|
+
gap: ${buttonContentGap};
|
|
68
77
|
justify-content: center;
|
|
69
78
|
letter-spacing: ${label.letterSpacing}px;
|
|
70
79
|
line-height: ${label.lineHeight}px;
|
|
@@ -72,6 +72,17 @@ ${scrollRegionRules('.sw-denomination-scroll')}
|
|
|
72
72
|
gap: var(--sw-space-2);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/*
|
|
76
|
+
* A row label's glyph and words are one line: the line does not wrap, so its
|
|
77
|
+
* narrowest width is the glyph, the gap and the longest word, and a long
|
|
78
|
+
* label wraps its words beside the glyph instead of dropping them under it.
|
|
79
|
+
*/
|
|
80
|
+
.sw-denomination-label-line {
|
|
81
|
+
align-items: center;
|
|
82
|
+
display: flex;
|
|
83
|
+
gap: var(--sw-space-2);
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
/*
|
|
76
87
|
* The row labels and the header corner above them stay in view while a wide
|
|
77
88
|
* strip scrolls under them, so each column head stays over its counts.
|
|
@@ -221,6 +232,7 @@ ${scrollRegionRules('.sw-denomination-scroll')}
|
|
|
221
232
|
.sw-denomination-total-value { ${visuallyHiddenDeclarations} }
|
|
222
233
|
.sw-denomination-total-inline { display: block; }
|
|
223
234
|
.sw-denomination-label-body { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
|
|
235
|
+
.sw-denomination-strip .sw-denomination-label-line { gap: var(--sw-space-1); }
|
|
224
236
|
.sw-denomination-strip th,
|
|
225
237
|
.sw-denomination-strip td { padding: var(--sw-space-1); }
|
|
226
238
|
.sw-denomination-strip .sw-denomination-label { padding-inline-start: calc(var(--sw-space-1) * 2); }
|
|
@@ -249,6 +261,7 @@ export function denominationGridClassCatalog() {
|
|
|
249
261
|
'sw-denomination-row-signed',
|
|
250
262
|
'sw-denomination-label',
|
|
251
263
|
'sw-denomination-label-body',
|
|
264
|
+
'sw-denomination-label-line',
|
|
252
265
|
'sw-denomination-label-text',
|
|
253
266
|
'sw-denomination-icon',
|
|
254
267
|
'sw-denomination-cell',
|
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
export declare const controlSurface = "background-color: var(--sw-glass-fill);\n border: 1px solid var(--sw-color-border);\n border-radius: var(--sw-radius-sm);\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n padding-inline: var(--sw-control-md-padding-inline);";
|
|
3
3
|
export declare const disabledControlSurface = "background-color: var(--sw-color-subtle);\n border-style: dashed;\n cursor: not-allowed;\n opacity: 1;\n -webkit-text-fill-color: currentColor;";
|
|
4
4
|
export declare const disabledControlForcedColors = "border-color: GrayText;\n color: GrayText;\n -webkit-text-fill-color: GrayText;";
|
|
5
|
+
/** A script-only focus target in the canvas, at zero specificity. */
|
|
6
|
+
export declare const focusTargetSelector = ":where([data-theme] [tabindex='-1']:focus-visible)";
|
|
5
7
|
export declare function cssDocumentCanvas(): string;
|
package/dist/css/css-document.js
CHANGED
|
@@ -34,6 +34,8 @@ export const disabledControlSurface = `background-color: var(--sw-color-subtle);
|
|
|
34
34
|
export const disabledControlForcedColors = `border-color: GrayText;
|
|
35
35
|
color: GrayText;
|
|
36
36
|
-webkit-text-fill-color: GrayText;`;
|
|
37
|
+
/** A script-only focus target in the canvas, at zero specificity. */
|
|
38
|
+
export const focusTargetSelector = ":where([data-theme] [tabindex='-1']:focus-visible)";
|
|
37
39
|
export function cssDocumentCanvas() {
|
|
38
40
|
return `html,
|
|
39
41
|
body {
|
|
@@ -79,6 +81,20 @@ body {
|
|
|
79
81
|
outline-offset: var(--sw-focus-ring-offset);
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/*
|
|
85
|
+
* A programmatic focus target (tabindex="-1": a notice, a card or a heading
|
|
86
|
+
* that a script focuses after a save) takes the accent ring past the ring
|
|
87
|
+
* offset, as links and native controls do, instead of the browser's outline.
|
|
88
|
+
* It shows when the browser would show its own (:focus-visible), so a target
|
|
89
|
+
* focused after a mouse press stays quiet. :where() keeps the rule at zero
|
|
90
|
+
* specificity: it still beats the user-agent outline, and every component's
|
|
91
|
+
* own ring (a roving tabindex="-1" item's included) wins over it.
|
|
92
|
+
*/
|
|
93
|
+
${focusTargetSelector} {
|
|
94
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
95
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
[data-theme] :is(${typedInputs}) {
|
|
83
99
|
${controlSurface}
|
|
84
100
|
box-sizing: border-box;
|
package/dist/css/css-toast.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const toastTones: readonly ["neutral", "success", "warning", "danger"];
|
|
2
2
|
export type ToastTone = (typeof toastTones)[number];
|
|
3
|
+
export declare const toastMaxWidth = "calc(100% - var(--sw-space-4) - var(--sw-space-4))";
|
|
3
4
|
export declare function cssToastClasses(): string;
|
|
4
5
|
export declare function toastClassCatalog(): string[];
|
package/dist/css/css-toast.js
CHANGED
|
@@ -9,6 +9,13 @@ function toneRules() {
|
|
|
9
9
|
.map((tone) => `.sw-toast-${tone} { --sw-toast-tone: var(--sw-color-${tone}); border-color: var(--sw-toast-tone); }`)
|
|
10
10
|
.join('\n');
|
|
11
11
|
}
|
|
12
|
+
/*
|
|
13
|
+
* The toast keeps the page's side gutter (spacing step 4 on each side): a
|
|
14
|
+
* long message wraps inside it instead of running to the screen's edges.
|
|
15
|
+
* 100% of the top layer's containing block leaves out a classic scrollbar;
|
|
16
|
+
* 100vw would not.
|
|
17
|
+
*/
|
|
18
|
+
export const toastMaxWidth = 'calc(100% - var(--sw-space-4) - var(--sw-space-4))';
|
|
12
19
|
export function cssToastClasses() {
|
|
13
20
|
return `.sw-toast {
|
|
14
21
|
background: var(--sw-glass-fill);
|
|
@@ -23,7 +30,9 @@ export function cssToastClasses() {
|
|
|
23
30
|
bottom: var(--sw-space-6);
|
|
24
31
|
left: 50%;
|
|
25
32
|
margin: 0;
|
|
33
|
+
max-width: ${toastMaxWidth};
|
|
26
34
|
overflow: visible;
|
|
35
|
+
overflow-wrap: break-word;
|
|
27
36
|
pointer-events: none;
|
|
28
37
|
position: fixed;
|
|
29
38
|
translate: -50% 0;
|
package/dist/styles.css
CHANGED
|
@@ -610,6 +610,20 @@ body {
|
|
|
610
610
|
outline-offset: var(--sw-focus-ring-offset);
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
/*
|
|
614
|
+
* A programmatic focus target (tabindex="-1": a notice, a card or a heading
|
|
615
|
+
* that a script focuses after a save) takes the accent ring past the ring
|
|
616
|
+
* offset, as links and native controls do, instead of the browser's outline.
|
|
617
|
+
* It shows when the browser would show its own (:focus-visible), so a target
|
|
618
|
+
* focused after a mouse press stays quiet. :where() keeps the rule at zero
|
|
619
|
+
* specificity: it still beats the user-agent outline, and every component's
|
|
620
|
+
* own ring (a roving tabindex="-1" item's included) wins over it.
|
|
621
|
+
*/
|
|
622
|
+
:where([data-theme] [tabindex='-1']:focus-visible) {
|
|
623
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
624
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
625
|
+
}
|
|
626
|
+
|
|
613
627
|
[data-theme] :is(input[type='text'], input[type='email'], input[type='number'], input[type='search'], input[type='url'], input[type='password'], input:not([type]), textarea) {
|
|
614
628
|
background-color: var(--sw-glass-fill);
|
|
615
629
|
border: 1px solid var(--sw-color-border);
|
|
@@ -858,6 +872,7 @@ body {
|
|
|
858
872
|
font-family: var(--sw-font-sans);
|
|
859
873
|
font-size: 15px;
|
|
860
874
|
font-weight: 600;
|
|
875
|
+
gap: var(--sw-space-2);
|
|
861
876
|
justify-content: center;
|
|
862
877
|
letter-spacing: -0.2px;
|
|
863
878
|
line-height: 20px;
|
|
@@ -2886,6 +2901,17 @@ body {
|
|
|
2886
2901
|
gap: var(--sw-space-2);
|
|
2887
2902
|
}
|
|
2888
2903
|
|
|
2904
|
+
/*
|
|
2905
|
+
* A row label's glyph and words are one line: the line does not wrap, so its
|
|
2906
|
+
* narrowest width is the glyph, the gap and the longest word, and a long
|
|
2907
|
+
* label wraps its words beside the glyph instead of dropping them under it.
|
|
2908
|
+
*/
|
|
2909
|
+
.sw-denomination-label-line {
|
|
2910
|
+
align-items: center;
|
|
2911
|
+
display: flex;
|
|
2912
|
+
gap: var(--sw-space-2);
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2889
2915
|
/*
|
|
2890
2916
|
* The row labels and the header corner above them stay in view while a wide
|
|
2891
2917
|
* strip scrolls under them, so each column head stays over its counts.
|
|
@@ -3035,6 +3061,7 @@ body {
|
|
|
3035
3061
|
.sw-denomination-total-value { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
3036
3062
|
.sw-denomination-total-inline { display: block; }
|
|
3037
3063
|
.sw-denomination-label-body { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
|
|
3064
|
+
.sw-denomination-strip .sw-denomination-label-line { gap: var(--sw-space-1); }
|
|
3038
3065
|
.sw-denomination-strip th,
|
|
3039
3066
|
.sw-denomination-strip td { padding: var(--sw-space-1); }
|
|
3040
3067
|
.sw-denomination-strip .sw-denomination-label { padding-inline-start: calc(var(--sw-space-1) * 2); }
|
|
@@ -3221,7 +3248,9 @@ body {
|
|
|
3221
3248
|
bottom: var(--sw-space-6);
|
|
3222
3249
|
left: 50%;
|
|
3223
3250
|
margin: 0;
|
|
3251
|
+
max-width: calc(100% - var(--sw-space-4) - var(--sw-space-4));
|
|
3224
3252
|
overflow: visible;
|
|
3253
|
+
overflow-wrap: break-word;
|
|
3225
3254
|
pointer-events: none;
|
|
3226
3255
|
position: fixed;
|
|
3227
3256
|
translate: -50% 0;
|