@echelon-foundry/design-system 0.12.0 → 0.13.0-main.263.1
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 +49 -0
- package/contracts/color-contract.json +1 -1
- package/dist/all.css +59 -1
- package/dist/build-metrics.json +3 -3
- package/dist/components.css +12 -1
- package/dist/foundations.css +47 -0
- package/dist/icons/registry.json +1 -1
- package/dist/marketing/forma-all.css +60 -2
- package/dist/marketing/forma-echelon-marketing.css +48 -1
- package/dist/marketing/forma-marketing-theme-echelon.css +1 -1
- package/dist/marketing/forma-marketing.css +48 -1
- package/dist/marketing/forma-marketing.manifest.json +9 -9
- package/dist/marketing/forma-marketing.sha256 +4 -4
- package/dist/runtime.js +1 -1
- package/package.json +7 -4
- package/tools/TokenValidator/Css.fs +171 -0
- package/tools/TokenValidator/Program.fs +104 -0
- package/tools/TokenValidator/TokenValidator.fsproj +12 -0
- package/tools/TokenValidator/Validation.fs +395 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,55 @@ published as GitHub release tarballs
|
|
|
7
7
|
and to npm. Earlier releases are described in `docs/CONSUMING-FORMA.md` and
|
|
8
8
|
`HANDOFF.md`.
|
|
9
9
|
|
|
10
|
+
## 0.13.0
|
|
11
|
+
|
|
12
|
+
Minor release: layout reliability (LRP-001). The source document is `.praxis/inbox/documents/2026-10-30-LRP-001-layout-reliability.md`; the baseline and the Indy Init report are in `docs/reliability/`. `@echelon-foundry/forma-conformance` 0.3.0 ships the new geometry checks.
|
|
13
|
+
|
|
14
|
+
**Behaviour change: a token too long for its line now breaks inside Forma containers.** Before this release, a token without spaces kept its full width and escaped the card, dialog or field around it. This affected URLs, SHAs, paths and identifiers in a `span`, `strong`, `em`, `div` or `li`. Such a token now breaks at the container's edge. Words that fit are never split: containers pass on `overflow-wrap: break-word`, which leaves a word's minimum width alone, not `anywhere`. Content with an explicit `white-space` (`nowrap`, `pre`) keeps its wrapping, as do code blocks, truncating selects, character-grid runs and buttons. No class, token, element or attribute was removed or renamed (QD-004).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Text containment.** `overflow-wrap: break-word` is now inherited from these Forma containers, so arbitrary application text inside them breaks only when it would overflow:
|
|
18
|
+
- `.ef-surface`, `.ef-stack`, `.ef-cluster`, `.ef-actions`, `.ef-grid`, `.ef-responsive-grid`, `.ef-rail`, `.ef-mosaic`, `.ef-priority-stack`, `.ef-focus-stage`, `.ef-sidebar`, `.ef-switcher`, `.ef-split-pane`;
|
|
19
|
+
- `.ef-dialog`, `.ef-popover`, `.ef-menu`, `.ef-form`, `.ef-field`, `.ef-validation-summary`, `.ef-record-header`.
|
|
20
|
+
|
|
21
|
+
At 320px on 0.12.0, the fixtures measured escapes of up to 716px.
|
|
22
|
+
- **Flex rows.** Children of `.ef-cluster` and `.ef-actions` are capped at the row's width (`max-inline-size: 100%`). This also caps their automatic flex minimum, so a long label no longer pushes the row past its container; it breaks inside the row. Items that fit keep their content size, and no control is shrunk below its label. A `dialog` or `[popover]` authored inside the row is exempt. A full-bleed child that uses negative margins is now capped at the row's width.
|
|
23
|
+
- **Record header.** The type label can break when it is wider than a narrow header (320px at 200% text).
|
|
24
|
+
- **Command palette.** A command's label is capped at its button's width, so a long label no longer overflows the palette.
|
|
25
|
+
- **WebKit horizontal scrolling in forms.** A `select` with a long option inside `.ef-form-row` added the option's width to the page's horizontal scroll in WebKit/Safari. It now gets layout containment, which keeps that width inside the select; the subgrid alignment is kept.
|
|
26
|
+
- **Focus on date and time pickers.** In Chromium, keyboard focus on the picker button of a date, time, datetime-local, month or week input painted no focus indicator. It now shows the Forma focus ring. The rule applies only to that case (`:focus-within:not(:focus)`), so pointer focus on the input itself keeps the native behaviour.
|
|
27
|
+
- **Focus scrolling.** Focus-visible elements have `scroll-margin: 0.5rem`, so tabbing to an element at the viewport edge brings its whole focus ring into view. Chromium and WebKit honour this; Firefox does not.
|
|
28
|
+
- Side effect: in WebKit, tabbing into a mandatory scroll-snap container offsets the snap position by that margin.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **Design-token validator (`tools/TokenValidator`).** It is written in F#, has no dependencies, and ships in the package. It validates `var(--ef-*)` references and `--ef-*` declarations against the Forma installation itself. That installation's token sources, built CSS and color contract are the source of truth. It reports:
|
|
32
|
+
- `unknown-token`, even when the reference has a fallback;
|
|
33
|
+
- `deprecated-token` (DTCG `$deprecated`);
|
|
34
|
+
- `namespace`: `--forma-*`, `--ef_*`, a dropped segment such as `--ef-spacing-4`, or an application minting a new `--ef-*` name;
|
|
35
|
+
- `internal-variable`;
|
|
36
|
+
- custom-property `cycle`, judged per rule and at-rule context.
|
|
37
|
+
|
|
38
|
+
Strings and comments are inert. With `--self` it also checks Forma's own `invalid-alias`, alias `cycle`, `theme-missing` (token themes and brand manifests) and `missing-definition`. Application-owned custom properties are never reported.
|
|
39
|
+
- Forma itself: `npm run tokens:validate`, which is now part of `check` and `release:check`. These scripts therefore need the .NET SDK.
|
|
40
|
+
- Applications: `dotnet run --project node_modules/@echelon-foundry/design-system/tools/TokenValidator/TokenValidator.fsproj -- --forma node_modules/@echelon-foundry/design-system src/`. See `docs/CONSUMING-FORMA.md`, "Design-token validation".
|
|
41
|
+
- **`@echelon-foundry/forma-conformance` 0.3.0**, which also publishes the 0.11.0 corner-containment helpers:
|
|
42
|
+
- `measureContainment`, `expectContained` and `evaluateContainment` (ULI-L05 `contain.element`) check that every descendant box and text line stays inside a component's content box.
|
|
43
|
+
- `measureFocusVisibility`, `expectFocusVisible` and `evaluateFocusVisibility` (WCAG 2.4.7/2.4.11 `focus.unclipped`) check that a visible outline or outward box-shadow is painted and that no hard clip cuts it.
|
|
44
|
+
- **Browser geometry suite.**
|
|
45
|
+
- `tests/browser/layout-reliability.spec.mjs` covers 11 adversarial fixtures at 320, 375, 768 and 1280px and at 200% text. It also runs dialog, dropdown, focus, media, theme, skin and axe checks. On 0.12.0 it failed 48 of 78 checks across Chromium, Firefox and WebKit.
|
|
46
|
+
- `tests/browser/word-integrity.spec.mjs` checks that no canonical pattern or fixture splits a word that fits its box.
|
|
47
|
+
- `tests/browser/causa-representative.spec.mjs` covers Causa-shaped regions at phone, iPad portrait, iPad landscape and desktop sizes.
|
|
48
|
+
|
|
49
|
+
### Known limitations
|
|
50
|
+
- `.ef-dashboard-grid`, `.ef-split-pane` and `.ef-operation-status` reflow only at a viewport media query. Under page-level 200% text at iPad portrait width they stay wider than their region. This is the same class as the alert's viewport-based stacking. The Causa spec asserts these still fail, so a fix is noticed.
|
|
51
|
+
- A narrowed `<svg>` with `width`/`height` attributes keeps its attribute height, so its box is taller than the drawing; the drawing itself is letterboxed, not distorted.
|
|
52
|
+
- No single declaration fixes this without a regression in some engine. `block-size: auto` makes icons stretch in flex rows in every engine. The intrinsic keywords avoid that stretch, but WebKit does not resize an SVG with them.
|
|
53
|
+
- Workaround: give such charts `block-size: auto` yourself, or omit the `height` attribute.
|
|
54
|
+
|
|
55
|
+
## @echelon-foundry/forma-workflow 1.2.0
|
|
56
|
+
|
|
57
|
+
Minor release of the workflow package (and `EchelonFoundry.Forma.Workflow` 1.2.0): it ships the Forma 0.12.0 engine. **Behaviour change: validation is stricter.** Documents that validated under 1.1.0 can now fail with `A11Y-NONTEXT-CONTRAST` (WCAG 1.4.11, 3:1), and `render` refuses documents that fail validation. Added: `token:status-warning|danger|success|info` diagram colours, `ValidationOptions.Theme` and `--theme` on `validate`/`render`. No element, class, attribute or token was removed or renamed (QD-004), so this is a minor release, as Forma 0.12.0 was. See 0.12.0 below for the migration.
|
|
58
|
+
|
|
10
59
|
## 0.12.0
|
|
11
60
|
|
|
12
61
|
Minor release: diagram non-text contrast and status tokens.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "forma.color-contract/1.0.0",
|
|
3
|
-
"formaVersion": "0.
|
|
3
|
+
"formaVersion": "0.13.0",
|
|
4
4
|
"validationScope": "CSS var() references to --ef-color-* plus reserved internal color-related prefixes; other Forma APIs are governed by their own contracts.",
|
|
5
5
|
"compatibility": {
|
|
6
6
|
"policy": "Validate with the contract shipped by the pinned Forma package version.",
|
package/dist/all.css
CHANGED
|
@@ -794,6 +794,20 @@
|
|
|
794
794
|
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
795
795
|
outline-offset: 2px;
|
|
796
796
|
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
797
|
+
/* Focus scrolling brings the whole indicator into view, not only the
|
|
798
|
+
element's border box (LRP-001). Changes scroll position only. */
|
|
799
|
+
scroll-margin: 0.5rem;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/* Keyboard focus on a date or time control's own picker button leaves the
|
|
803
|
+
host input :focus-within but neither :focus nor :focus-visible
|
|
804
|
+
(Chromium), which painted no indicator at all (LRP-001). :not(:focus)
|
|
805
|
+
limits this to that case: pointer focus on the input itself (WebKit)
|
|
806
|
+
keeps the native behaviour. */
|
|
807
|
+
:where(input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"]):focus-within:not(:focus) {
|
|
808
|
+
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
809
|
+
outline-offset: 2px;
|
|
810
|
+
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
797
811
|
}
|
|
798
812
|
|
|
799
813
|
.ef-stack { display: flex; flex-direction: column; gap: var(--ef-stack-gap, var(--ef-primitive-spacing-4, 1rem)); }
|
|
@@ -811,6 +825,39 @@
|
|
|
811
825
|
overflow-wrap: anywhere;
|
|
812
826
|
}
|
|
813
827
|
|
|
828
|
+
/*
|
|
829
|
+
* Layout reliability (LRP-001, docs/reliability/layout-baseline.md).
|
|
830
|
+
* Forma containers that hold arbitrary application content pass
|
|
831
|
+
* overflow-wrap: break-word to that content by inheritance, so a long token
|
|
832
|
+
* in a span, strong, em, div or li breaks instead of escaping the card,
|
|
833
|
+
* dialog or field. break-word, unlike anywhere, leaves min-content sizes
|
|
834
|
+
* alone: a word that fits is never split, and auto-sized flex and grid
|
|
835
|
+
* items keep whole words. Inheritance never overrides a declared value:
|
|
836
|
+
* text elements above keep anywhere, character-grid runs keep normal, and
|
|
837
|
+
* white-space: pre or nowrap content (code blocks, truncating selects,
|
|
838
|
+
* scrollers) does not wrap.
|
|
839
|
+
*/
|
|
840
|
+
:where(
|
|
841
|
+
.ef-surface, .ef-stack, .ef-cluster, .ef-actions, .ef-grid, .ef-responsive-grid,
|
|
842
|
+
.ef-rail, .ef-mosaic, .ef-priority-stack, .ef-focus-stage, .ef-sidebar, .ef-switcher, .ef-split-pane,
|
|
843
|
+
.ef-dialog, .ef-popover, .ef-menu, .ef-form, .ef-field, .ef-validation-summary, .ef-record-header
|
|
844
|
+
) {
|
|
845
|
+
overflow-wrap: break-word;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/*
|
|
849
|
+
* A wrapping row's item can be no wider than the row (LRP-001), which also
|
|
850
|
+
* caps its automatic flex minimum, so a long label or an inline-block item
|
|
851
|
+
* cannot push the row past its container; the item's text then breaks
|
|
852
|
+
* inside it. Items keep their content size whenever it fits: nothing here
|
|
853
|
+
* shrinks a control below its label. Top-layer children (a dialog or
|
|
854
|
+
* popover authored inside the row) keep their own sizing. A full-bleed
|
|
855
|
+
* child with negative margins is capped at the row's width.
|
|
856
|
+
*/
|
|
857
|
+
:where(.ef-cluster, .ef-actions) > :not(dialog, [popover]) {
|
|
858
|
+
max-inline-size: 100%;
|
|
859
|
+
}
|
|
860
|
+
|
|
814
861
|
/* Action labels never split mid-word because their container allows it
|
|
815
862
|
(a link button inherits the rule above; a button inherits it from a
|
|
816
863
|
table cell). A single word wider than its container may still break
|
|
@@ -1075,6 +1122,12 @@
|
|
|
1075
1122
|
.ef-form-row > .ef-field > .ef-field__description { grid-row: 2; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
1076
1123
|
.ef-form-row > .ef-field > :not(.ef-field__label, .ef-field__description, .ef-validation-message) { grid-row: 3; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
1077
1124
|
.ef-form-row > .ef-field > .ef-validation-message { grid-row: 4; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
1125
|
+
/* WebKit lets a select's longest option add scrollable overflow through
|
|
1126
|
+
the subgrid, so a 320px form scrolled sideways by the option's width
|
|
1127
|
+
(LRP-001; Safari is the primary iPad target). Layout containment keeps
|
|
1128
|
+
it inside the select. Rows align at start, so the lost baseline is
|
|
1129
|
+
unused here. */
|
|
1130
|
+
.ef-form-row > .ef-field > select { contain: layout; }
|
|
1078
1131
|
|
|
1079
1132
|
/* Form contract ------------------------------------------------------- */
|
|
1080
1133
|
.ef-form {
|
|
@@ -3044,6 +3097,9 @@
|
|
|
3044
3097
|
border-inline: 0;
|
|
3045
3098
|
border-block-end: 0;
|
|
3046
3099
|
}
|
|
3100
|
+
/* A command's label is no wider than its button, so a long label breaks
|
|
3101
|
+
inside the button instead of overflowing the palette (LRP-001). */
|
|
3102
|
+
.ef-command-palette__group button > * { max-inline-size: 100%; }
|
|
3047
3103
|
.ef-command-palette__group button:last-child { border-block-end: 1px solid var(--ef-color-border-functional, #3a403c); }
|
|
3048
3104
|
.ef-command-palette kbd {
|
|
3049
3105
|
padding: 0.1rem 0.35rem;
|
|
@@ -3178,7 +3234,9 @@
|
|
|
3178
3234
|
row-gap: 0;
|
|
3179
3235
|
min-inline-size: 0;
|
|
3180
3236
|
}
|
|
3181
|
-
|
|
3237
|
+
/* A type label wider than a narrow header (320px at 200% text) breaks
|
|
3238
|
+
rather than setting the column's minimum and overflowing (LRP-001). */
|
|
3239
|
+
.ef-record-header__title > :first-child > .ef-record-header__type { grid-row: 1; align-self: end; overflow-wrap: anywhere; }
|
|
3182
3240
|
.ef-record-header__title > :first-child > :is(h1, h2, h3, h4) { grid-row: 2; }
|
|
3183
3241
|
.ef-record-header__title > :not(:first-child) { grid-row: 2; }
|
|
3184
3242
|
@container (max-width: 24em) {
|
package/dist/build-metrics.json
CHANGED
|
@@ -342,13 +342,13 @@
|
|
|
342
342
|
"tokens.css"
|
|
343
343
|
],
|
|
344
344
|
"bytes": {
|
|
345
|
-
"all.css":
|
|
345
|
+
"all.css": 344943,
|
|
346
346
|
"assessment.css": 36343,
|
|
347
347
|
"brands/echelon.css": 4213,
|
|
348
348
|
"brands/example-harbor.css": 4229,
|
|
349
349
|
"brands/index.json": 335,
|
|
350
|
-
"components.css":
|
|
351
|
-
"foundations.css":
|
|
350
|
+
"components.css": 214119,
|
|
351
|
+
"foundations.css": 22874,
|
|
352
352
|
"icons/add.svg": 216,
|
|
353
353
|
"icons/agent.svg": 354,
|
|
354
354
|
"icons/archive.svg": 248,
|
package/dist/components.css
CHANGED
|
@@ -63,6 +63,12 @@
|
|
|
63
63
|
.ef-form-row > .ef-field > .ef-field__description { grid-row: 2; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
64
64
|
.ef-form-row > .ef-field > :not(.ef-field__label, .ef-field__description, .ef-validation-message) { grid-row: 3; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
65
65
|
.ef-form-row > .ef-field > .ef-validation-message { grid-row: 4; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
66
|
+
/* WebKit lets a select's longest option add scrollable overflow through
|
|
67
|
+
the subgrid, so a 320px form scrolled sideways by the option's width
|
|
68
|
+
(LRP-001; Safari is the primary iPad target). Layout containment keeps
|
|
69
|
+
it inside the select. Rows align at start, so the lost baseline is
|
|
70
|
+
unused here. */
|
|
71
|
+
.ef-form-row > .ef-field > select { contain: layout; }
|
|
66
72
|
|
|
67
73
|
/* Form contract ------------------------------------------------------- */
|
|
68
74
|
.ef-form {
|
|
@@ -2032,6 +2038,9 @@
|
|
|
2032
2038
|
border-inline: 0;
|
|
2033
2039
|
border-block-end: 0;
|
|
2034
2040
|
}
|
|
2041
|
+
/* A command's label is no wider than its button, so a long label breaks
|
|
2042
|
+
inside the button instead of overflowing the palette (LRP-001). */
|
|
2043
|
+
.ef-command-palette__group button > * { max-inline-size: 100%; }
|
|
2035
2044
|
.ef-command-palette__group button:last-child { border-block-end: 1px solid var(--ef-color-border-functional, #3a403c); }
|
|
2036
2045
|
.ef-command-palette kbd {
|
|
2037
2046
|
padding: 0.1rem 0.35rem;
|
|
@@ -2166,7 +2175,9 @@
|
|
|
2166
2175
|
row-gap: 0;
|
|
2167
2176
|
min-inline-size: 0;
|
|
2168
2177
|
}
|
|
2169
|
-
|
|
2178
|
+
/* A type label wider than a narrow header (320px at 200% text) breaks
|
|
2179
|
+
rather than setting the column's minimum and overflowing (LRP-001). */
|
|
2180
|
+
.ef-record-header__title > :first-child > .ef-record-header__type { grid-row: 1; align-self: end; overflow-wrap: anywhere; }
|
|
2170
2181
|
.ef-record-header__title > :first-child > :is(h1, h2, h3, h4) { grid-row: 2; }
|
|
2171
2182
|
.ef-record-header__title > :not(:first-child) { grid-row: 2; }
|
|
2172
2183
|
@container (max-width: 24em) {
|
package/dist/foundations.css
CHANGED
|
@@ -183,6 +183,20 @@
|
|
|
183
183
|
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
184
184
|
outline-offset: 2px;
|
|
185
185
|
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
186
|
+
/* Focus scrolling brings the whole indicator into view, not only the
|
|
187
|
+
element's border box (LRP-001). Changes scroll position only. */
|
|
188
|
+
scroll-margin: 0.5rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Keyboard focus on a date or time control's own picker button leaves the
|
|
192
|
+
host input :focus-within but neither :focus nor :focus-visible
|
|
193
|
+
(Chromium), which painted no indicator at all (LRP-001). :not(:focus)
|
|
194
|
+
limits this to that case: pointer focus on the input itself (WebKit)
|
|
195
|
+
keeps the native behaviour. */
|
|
196
|
+
:where(input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"]):focus-within:not(:focus) {
|
|
197
|
+
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
198
|
+
outline-offset: 2px;
|
|
199
|
+
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
.ef-stack { display: flex; flex-direction: column; gap: var(--ef-stack-gap, var(--ef-primitive-spacing-4, 1rem)); }
|
|
@@ -200,6 +214,39 @@
|
|
|
200
214
|
overflow-wrap: anywhere;
|
|
201
215
|
}
|
|
202
216
|
|
|
217
|
+
/*
|
|
218
|
+
* Layout reliability (LRP-001, docs/reliability/layout-baseline.md).
|
|
219
|
+
* Forma containers that hold arbitrary application content pass
|
|
220
|
+
* overflow-wrap: break-word to that content by inheritance, so a long token
|
|
221
|
+
* in a span, strong, em, div or li breaks instead of escaping the card,
|
|
222
|
+
* dialog or field. break-word, unlike anywhere, leaves min-content sizes
|
|
223
|
+
* alone: a word that fits is never split, and auto-sized flex and grid
|
|
224
|
+
* items keep whole words. Inheritance never overrides a declared value:
|
|
225
|
+
* text elements above keep anywhere, character-grid runs keep normal, and
|
|
226
|
+
* white-space: pre or nowrap content (code blocks, truncating selects,
|
|
227
|
+
* scrollers) does not wrap.
|
|
228
|
+
*/
|
|
229
|
+
:where(
|
|
230
|
+
.ef-surface, .ef-stack, .ef-cluster, .ef-actions, .ef-grid, .ef-responsive-grid,
|
|
231
|
+
.ef-rail, .ef-mosaic, .ef-priority-stack, .ef-focus-stage, .ef-sidebar, .ef-switcher, .ef-split-pane,
|
|
232
|
+
.ef-dialog, .ef-popover, .ef-menu, .ef-form, .ef-field, .ef-validation-summary, .ef-record-header
|
|
233
|
+
) {
|
|
234
|
+
overflow-wrap: break-word;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* A wrapping row's item can be no wider than the row (LRP-001), which also
|
|
239
|
+
* caps its automatic flex minimum, so a long label or an inline-block item
|
|
240
|
+
* cannot push the row past its container; the item's text then breaks
|
|
241
|
+
* inside it. Items keep their content size whenever it fits: nothing here
|
|
242
|
+
* shrinks a control below its label. Top-layer children (a dialog or
|
|
243
|
+
* popover authored inside the row) keep their own sizing. A full-bleed
|
|
244
|
+
* child with negative margins is capped at the row's width.
|
|
245
|
+
*/
|
|
246
|
+
:where(.ef-cluster, .ef-actions) > :not(dialog, [popover]) {
|
|
247
|
+
max-inline-size: 100%;
|
|
248
|
+
}
|
|
249
|
+
|
|
203
250
|
/* Action labels never split mid-word because their container allows it
|
|
204
251
|
(a link button inherits the rule above; a button inherits it from a
|
|
205
252
|
table cell). A single word wider than its container may still break
|
package/dist/icons/registry.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Forma 0.
|
|
1
|
+
/*! Forma 0.13.0 | forma-all.css | bundle echelon-marketing
|
|
2
2
|
Complete Forma surface (application components, assessment, and marketing layers) for sites that also present Forma application patterns such as data grids or status lozenges.
|
|
3
3
|
Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
|
|
4
4
|
|
|
@@ -799,6 +799,20 @@
|
|
|
799
799
|
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
800
800
|
outline-offset: 2px;
|
|
801
801
|
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
802
|
+
/* Focus scrolling brings the whole indicator into view, not only the
|
|
803
|
+
element's border box (LRP-001). Changes scroll position only. */
|
|
804
|
+
scroll-margin: 0.5rem;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* Keyboard focus on a date or time control's own picker button leaves the
|
|
808
|
+
host input :focus-within but neither :focus nor :focus-visible
|
|
809
|
+
(Chromium), which painted no indicator at all (LRP-001). :not(:focus)
|
|
810
|
+
limits this to that case: pointer focus on the input itself (WebKit)
|
|
811
|
+
keeps the native behaviour. */
|
|
812
|
+
:where(input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"]):focus-within:not(:focus) {
|
|
813
|
+
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
814
|
+
outline-offset: 2px;
|
|
815
|
+
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
802
816
|
}
|
|
803
817
|
|
|
804
818
|
.ef-stack { display: flex; flex-direction: column; gap: var(--ef-stack-gap, var(--ef-primitive-spacing-4, 1rem)); }
|
|
@@ -816,6 +830,39 @@
|
|
|
816
830
|
overflow-wrap: anywhere;
|
|
817
831
|
}
|
|
818
832
|
|
|
833
|
+
/*
|
|
834
|
+
* Layout reliability (LRP-001, docs/reliability/layout-baseline.md).
|
|
835
|
+
* Forma containers that hold arbitrary application content pass
|
|
836
|
+
* overflow-wrap: break-word to that content by inheritance, so a long token
|
|
837
|
+
* in a span, strong, em, div or li breaks instead of escaping the card,
|
|
838
|
+
* dialog or field. break-word, unlike anywhere, leaves min-content sizes
|
|
839
|
+
* alone: a word that fits is never split, and auto-sized flex and grid
|
|
840
|
+
* items keep whole words. Inheritance never overrides a declared value:
|
|
841
|
+
* text elements above keep anywhere, character-grid runs keep normal, and
|
|
842
|
+
* white-space: pre or nowrap content (code blocks, truncating selects,
|
|
843
|
+
* scrollers) does not wrap.
|
|
844
|
+
*/
|
|
845
|
+
:where(
|
|
846
|
+
.ef-surface, .ef-stack, .ef-cluster, .ef-actions, .ef-grid, .ef-responsive-grid,
|
|
847
|
+
.ef-rail, .ef-mosaic, .ef-priority-stack, .ef-focus-stage, .ef-sidebar, .ef-switcher, .ef-split-pane,
|
|
848
|
+
.ef-dialog, .ef-popover, .ef-menu, .ef-form, .ef-field, .ef-validation-summary, .ef-record-header
|
|
849
|
+
) {
|
|
850
|
+
overflow-wrap: break-word;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/*
|
|
854
|
+
* A wrapping row's item can be no wider than the row (LRP-001), which also
|
|
855
|
+
* caps its automatic flex minimum, so a long label or an inline-block item
|
|
856
|
+
* cannot push the row past its container; the item's text then breaks
|
|
857
|
+
* inside it. Items keep their content size whenever it fits: nothing here
|
|
858
|
+
* shrinks a control below its label. Top-layer children (a dialog or
|
|
859
|
+
* popover authored inside the row) keep their own sizing. A full-bleed
|
|
860
|
+
* child with negative margins is capped at the row's width.
|
|
861
|
+
*/
|
|
862
|
+
:where(.ef-cluster, .ef-actions) > :not(dialog, [popover]) {
|
|
863
|
+
max-inline-size: 100%;
|
|
864
|
+
}
|
|
865
|
+
|
|
819
866
|
/* Action labels never split mid-word because their container allows it
|
|
820
867
|
(a link button inherits the rule above; a button inherits it from a
|
|
821
868
|
table cell). A single word wider than its container may still break
|
|
@@ -1080,6 +1127,12 @@
|
|
|
1080
1127
|
.ef-form-row > .ef-field > .ef-field__description { grid-row: 2; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
1081
1128
|
.ef-form-row > .ef-field > :not(.ef-field__label, .ef-field__description, .ef-validation-message) { grid-row: 3; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
1082
1129
|
.ef-form-row > .ef-field > .ef-validation-message { grid-row: 4; margin-block-start: var(--ef-primitive-spacing-2, 0.5rem); }
|
|
1130
|
+
/* WebKit lets a select's longest option add scrollable overflow through
|
|
1131
|
+
the subgrid, so a 320px form scrolled sideways by the option's width
|
|
1132
|
+
(LRP-001; Safari is the primary iPad target). Layout containment keeps
|
|
1133
|
+
it inside the select. Rows align at start, so the lost baseline is
|
|
1134
|
+
unused here. */
|
|
1135
|
+
.ef-form-row > .ef-field > select { contain: layout; }
|
|
1083
1136
|
|
|
1084
1137
|
/* Form contract ------------------------------------------------------- */
|
|
1085
1138
|
.ef-form {
|
|
@@ -3049,6 +3102,9 @@
|
|
|
3049
3102
|
border-inline: 0;
|
|
3050
3103
|
border-block-end: 0;
|
|
3051
3104
|
}
|
|
3105
|
+
/* A command's label is no wider than its button, so a long label breaks
|
|
3106
|
+
inside the button instead of overflowing the palette (LRP-001). */
|
|
3107
|
+
.ef-command-palette__group button > * { max-inline-size: 100%; }
|
|
3052
3108
|
.ef-command-palette__group button:last-child { border-block-end: 1px solid var(--ef-color-border-functional, #3a403c); }
|
|
3053
3109
|
.ef-command-palette kbd {
|
|
3054
3110
|
padding: 0.1rem 0.35rem;
|
|
@@ -3183,7 +3239,9 @@
|
|
|
3183
3239
|
row-gap: 0;
|
|
3184
3240
|
min-inline-size: 0;
|
|
3185
3241
|
}
|
|
3186
|
-
|
|
3242
|
+
/* A type label wider than a narrow header (320px at 200% text) breaks
|
|
3243
|
+
rather than setting the column's minimum and overflowing (LRP-001). */
|
|
3244
|
+
.ef-record-header__title > :first-child > .ef-record-header__type { grid-row: 1; align-self: end; overflow-wrap: anywhere; }
|
|
3187
3245
|
.ef-record-header__title > :first-child > :is(h1, h2, h3, h4) { grid-row: 2; }
|
|
3188
3246
|
.ef-record-header__title > :not(:first-child) { grid-row: 2; }
|
|
3189
3247
|
@container (max-width: 24em) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Forma 0.
|
|
1
|
+
/*! Forma 0.13.0 | forma-echelon-marketing.css | bundle echelon-marketing
|
|
2
2
|
Single-file Echelon marketing presentation: forma-marketing.css followed by the Echelon theme. Most sites load only this file.
|
|
3
3
|
Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
|
|
4
4
|
|
|
@@ -903,6 +903,20 @@
|
|
|
903
903
|
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
904
904
|
outline-offset: 2px;
|
|
905
905
|
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
906
|
+
/* Focus scrolling brings the whole indicator into view, not only the
|
|
907
|
+
element's border box (LRP-001). Changes scroll position only. */
|
|
908
|
+
scroll-margin: 0.5rem;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/* Keyboard focus on a date or time control's own picker button leaves the
|
|
912
|
+
host input :focus-within but neither :focus nor :focus-visible
|
|
913
|
+
(Chromium), which painted no indicator at all (LRP-001). :not(:focus)
|
|
914
|
+
limits this to that case: pointer focus on the input itself (WebKit)
|
|
915
|
+
keeps the native behaviour. */
|
|
916
|
+
:where(input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"]):focus-within:not(:focus) {
|
|
917
|
+
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
918
|
+
outline-offset: 2px;
|
|
919
|
+
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
906
920
|
}
|
|
907
921
|
|
|
908
922
|
.ef-stack { display: flex; flex-direction: column; gap: var(--ef-stack-gap, var(--ef-primitive-spacing-4, 1rem)); }
|
|
@@ -920,6 +934,39 @@
|
|
|
920
934
|
overflow-wrap: anywhere;
|
|
921
935
|
}
|
|
922
936
|
|
|
937
|
+
/*
|
|
938
|
+
* Layout reliability (LRP-001, docs/reliability/layout-baseline.md).
|
|
939
|
+
* Forma containers that hold arbitrary application content pass
|
|
940
|
+
* overflow-wrap: break-word to that content by inheritance, so a long token
|
|
941
|
+
* in a span, strong, em, div or li breaks instead of escaping the card,
|
|
942
|
+
* dialog or field. break-word, unlike anywhere, leaves min-content sizes
|
|
943
|
+
* alone: a word that fits is never split, and auto-sized flex and grid
|
|
944
|
+
* items keep whole words. Inheritance never overrides a declared value:
|
|
945
|
+
* text elements above keep anywhere, character-grid runs keep normal, and
|
|
946
|
+
* white-space: pre or nowrap content (code blocks, truncating selects,
|
|
947
|
+
* scrollers) does not wrap.
|
|
948
|
+
*/
|
|
949
|
+
:where(
|
|
950
|
+
.ef-surface, .ef-stack, .ef-cluster, .ef-actions, .ef-grid, .ef-responsive-grid,
|
|
951
|
+
.ef-rail, .ef-mosaic, .ef-priority-stack, .ef-focus-stage, .ef-sidebar, .ef-switcher, .ef-split-pane,
|
|
952
|
+
.ef-dialog, .ef-popover, .ef-menu, .ef-form, .ef-field, .ef-validation-summary, .ef-record-header
|
|
953
|
+
) {
|
|
954
|
+
overflow-wrap: break-word;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/*
|
|
958
|
+
* A wrapping row's item can be no wider than the row (LRP-001), which also
|
|
959
|
+
* caps its automatic flex minimum, so a long label or an inline-block item
|
|
960
|
+
* cannot push the row past its container; the item's text then breaks
|
|
961
|
+
* inside it. Items keep their content size whenever it fits: nothing here
|
|
962
|
+
* shrinks a control below its label. Top-layer children (a dialog or
|
|
963
|
+
* popover authored inside the row) keep their own sizing. A full-bleed
|
|
964
|
+
* child with negative margins is capped at the row's width.
|
|
965
|
+
*/
|
|
966
|
+
:where(.ef-cluster, .ef-actions) > :not(dialog, [popover]) {
|
|
967
|
+
max-inline-size: 100%;
|
|
968
|
+
}
|
|
969
|
+
|
|
923
970
|
/* Action labels never split mid-word because their container allows it
|
|
924
971
|
(a link button inherits the rule above; a button inherits it from a
|
|
925
972
|
table cell). A single word wider than its container may still break
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Forma 0.
|
|
1
|
+
/*! Forma 0.13.0 | forma-marketing-theme-echelon.css | bundle echelon-marketing
|
|
2
2
|
Echelon Marketing Theme: compiled theme tokens and theme hooks. Load after forma-marketing.css.
|
|
3
3
|
Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Forma 0.
|
|
1
|
+
/*! Forma 0.13.0 | forma-marketing.css | bundle echelon-marketing
|
|
2
2
|
Brand-neutral marketing layer: core tokens, role defaults, Forma foundations, marketing foundations, components, and layouts.
|
|
3
3
|
Generated by tools/PresentationBundler. Do not edit; pin by version and sha256. */
|
|
4
4
|
|
|
@@ -902,6 +902,20 @@
|
|
|
902
902
|
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
903
903
|
outline-offset: 2px;
|
|
904
904
|
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
905
|
+
/* Focus scrolling brings the whole indicator into view, not only the
|
|
906
|
+
element's border box (LRP-001). Changes scroll position only. */
|
|
907
|
+
scroll-margin: 0.5rem;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/* Keyboard focus on a date or time control's own picker button leaves the
|
|
911
|
+
host input :focus-within but neither :focus nor :focus-visible
|
|
912
|
+
(Chromium), which painted no indicator at all (LRP-001). :not(:focus)
|
|
913
|
+
limits this to that case: pointer focus on the input itself (WebKit)
|
|
914
|
+
keeps the native behaviour. */
|
|
915
|
+
:where(input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"], input[type="week"]):focus-within:not(:focus) {
|
|
916
|
+
outline: 3px solid var(--ef-color-focus-gap, #f2efe7);
|
|
917
|
+
outline-offset: 2px;
|
|
918
|
+
box-shadow: 0 0 0 5px var(--ef-color-focus-ring, #171a18);
|
|
905
919
|
}
|
|
906
920
|
|
|
907
921
|
.ef-stack { display: flex; flex-direction: column; gap: var(--ef-stack-gap, var(--ef-primitive-spacing-4, 1rem)); }
|
|
@@ -919,6 +933,39 @@
|
|
|
919
933
|
overflow-wrap: anywhere;
|
|
920
934
|
}
|
|
921
935
|
|
|
936
|
+
/*
|
|
937
|
+
* Layout reliability (LRP-001, docs/reliability/layout-baseline.md).
|
|
938
|
+
* Forma containers that hold arbitrary application content pass
|
|
939
|
+
* overflow-wrap: break-word to that content by inheritance, so a long token
|
|
940
|
+
* in a span, strong, em, div or li breaks instead of escaping the card,
|
|
941
|
+
* dialog or field. break-word, unlike anywhere, leaves min-content sizes
|
|
942
|
+
* alone: a word that fits is never split, and auto-sized flex and grid
|
|
943
|
+
* items keep whole words. Inheritance never overrides a declared value:
|
|
944
|
+
* text elements above keep anywhere, character-grid runs keep normal, and
|
|
945
|
+
* white-space: pre or nowrap content (code blocks, truncating selects,
|
|
946
|
+
* scrollers) does not wrap.
|
|
947
|
+
*/
|
|
948
|
+
:where(
|
|
949
|
+
.ef-surface, .ef-stack, .ef-cluster, .ef-actions, .ef-grid, .ef-responsive-grid,
|
|
950
|
+
.ef-rail, .ef-mosaic, .ef-priority-stack, .ef-focus-stage, .ef-sidebar, .ef-switcher, .ef-split-pane,
|
|
951
|
+
.ef-dialog, .ef-popover, .ef-menu, .ef-form, .ef-field, .ef-validation-summary, .ef-record-header
|
|
952
|
+
) {
|
|
953
|
+
overflow-wrap: break-word;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/*
|
|
957
|
+
* A wrapping row's item can be no wider than the row (LRP-001), which also
|
|
958
|
+
* caps its automatic flex minimum, so a long label or an inline-block item
|
|
959
|
+
* cannot push the row past its container; the item's text then breaks
|
|
960
|
+
* inside it. Items keep their content size whenever it fits: nothing here
|
|
961
|
+
* shrinks a control below its label. Top-layer children (a dialog or
|
|
962
|
+
* popover authored inside the row) keep their own sizing. A full-bleed
|
|
963
|
+
* child with negative margins is capped at the row's width.
|
|
964
|
+
*/
|
|
965
|
+
:where(.ef-cluster, .ef-actions) > :not(dialog, [popover]) {
|
|
966
|
+
max-inline-size: 100%;
|
|
967
|
+
}
|
|
968
|
+
|
|
922
969
|
/* Action labels never split mid-word because their container allows it
|
|
923
970
|
(a link button inherits the rule above; a button inherits it from a
|
|
924
971
|
table cell). A single word wider than its container may still break
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"forma": "0.
|
|
3
|
+
"forma": "0.13.0",
|
|
4
4
|
"bundle": "echelon-marketing",
|
|
5
5
|
"description": "Forma presentation artifacts for Echelon marketing sites. Every artifact is a single self-contained CSS file with no imports or relative URLs.",
|
|
6
|
-
"releaseTag": "v0.
|
|
6
|
+
"releaseTag": "v0.13.0",
|
|
7
7
|
"artifacts": [
|
|
8
8
|
{
|
|
9
9
|
"file": "forma-marketing.css",
|
|
10
10
|
"description": "Brand-neutral marketing layer: core tokens, role defaults, Forma foundations, marketing foundations, components, and layouts.",
|
|
11
|
-
"bytes":
|
|
12
|
-
"sha256": "
|
|
11
|
+
"bytes": 95041,
|
|
12
|
+
"sha256": "a2248c09b9c7731178f3046a245541fb240715c12b21638300d56a4fdd61819d",
|
|
13
13
|
"sources": [
|
|
14
14
|
"dist/tokens.css",
|
|
15
15
|
"src/marketing/roles.css",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"file": "forma-marketing-theme-echelon.css",
|
|
24
24
|
"description": "Echelon Marketing Theme: compiled theme tokens and theme hooks. Load after forma-marketing.css.",
|
|
25
25
|
"bytes": 6296,
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "0b803cade4de294a2e15ac8a8482cb2448617a2558d70fc6542a4b3b9a357bf2",
|
|
27
27
|
"sources": [
|
|
28
28
|
"build/marketing/echelon-marketing.tokens.css",
|
|
29
29
|
"themes/echelon/marketing.css"
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
{
|
|
33
33
|
"file": "forma-echelon-marketing.css",
|
|
34
34
|
"description": "Single-file Echelon marketing presentation: forma-marketing.css followed by the Echelon theme. Most sites load only this file.",
|
|
35
|
-
"bytes":
|
|
36
|
-
"sha256": "
|
|
35
|
+
"bytes": 101181,
|
|
36
|
+
"sha256": "cdf0ebfa0b60219e72d1c32dc0ac068d73255654748b55283bbb1c7a9db78a7f",
|
|
37
37
|
"sources": [
|
|
38
38
|
"artifact:forma-marketing.css",
|
|
39
39
|
"artifact:forma-marketing-theme-echelon.css"
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
{
|
|
43
43
|
"file": "forma-all.css",
|
|
44
44
|
"description": "Complete Forma surface (application components, assessment, and marketing layers) for sites that also present Forma application patterns such as data grids or status lozenges.",
|
|
45
|
-
"bytes":
|
|
46
|
-
"sha256": "
|
|
45
|
+
"bytes": 345306,
|
|
46
|
+
"sha256": "403d708505fc3fe2ac7bd2c78220e98ad9236a5c82f76d1b88d7a9c4b01ee213",
|
|
47
47
|
"sources": [
|
|
48
48
|
"dist/all.css"
|
|
49
49
|
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
a2248c09b9c7731178f3046a245541fb240715c12b21638300d56a4fdd61819d forma-marketing.css
|
|
2
|
+
0b803cade4de294a2e15ac8a8482cb2448617a2558d70fc6542a4b3b9a357bf2 forma-marketing-theme-echelon.css
|
|
3
|
+
cdf0ebfa0b60219e72d1c32dc0ac068d73255654748b55283bbb1c7a9db78a7f forma-echelon-marketing.css
|
|
4
|
+
403d708505fc3fe2ac7bd2c78220e98ad9236a5c82f76d1b88d7a9c4b01ee213 forma-all.css
|
package/dist/runtime.js
CHANGED
|
@@ -52,6 +52,6 @@ export function registerEchelonRuntime(name, metadata, target = globalThis) {
|
|
|
52
52
|
return installEchelonRuntimeRegistrar(target)?.(name, metadata);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export const FORMA_VERSION = "0.
|
|
55
|
+
export const FORMA_VERSION = "0.13.0";
|
|
56
56
|
|
|
57
57
|
if (typeof window !== "undefined") registerEchelonRuntime("forma", { version: FORMA_VERSION }, window);
|