@ashley-shrok/viewmodel-shell 5.2.0 → 6.1.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/dist/browser.js +9 -5
- package/dist/index.d.ts +33 -1
- package/package.json +3 -1
- package/styles/default.css +125 -1
package/dist/browser.js
CHANGED
|
@@ -954,8 +954,8 @@ export class BrowserAdapter {
|
|
|
954
954
|
parent.appendChild(el);
|
|
955
955
|
}
|
|
956
956
|
list(n, parent, on) {
|
|
957
|
-
const ul = document.createElement("ul");
|
|
958
|
-
ul.className = "vms-list"
|
|
957
|
+
const ul = document.createElement(n.ordered ? "ol" : "ul");
|
|
958
|
+
ul.className = `vms-list${n.ordered ? " vms-list--ordered" : ""}`;
|
|
959
959
|
if (n.id)
|
|
960
960
|
ul.id = n.id;
|
|
961
961
|
this.kids(n.children, ul, on);
|
|
@@ -2880,10 +2880,12 @@ export class BrowserAdapter {
|
|
|
2880
2880
|
bar.className = "vms-stat-bar";
|
|
2881
2881
|
n.stats.forEach(stat => {
|
|
2882
2882
|
const item = document.createElement("div");
|
|
2883
|
-
item.className =
|
|
2883
|
+
item.className = stat.tone
|
|
2884
|
+
? `vms-stat-bar__item vms-stat-bar__item--toned vms-stat-bar__item--tone-${stat.tone}`
|
|
2885
|
+
: "vms-stat-bar__item";
|
|
2884
2886
|
const val = document.createElement("span");
|
|
2885
2887
|
val.className = "vms-stat-bar__value";
|
|
2886
|
-
val.textContent =
|
|
2888
|
+
val.textContent = stat.value;
|
|
2887
2889
|
const lbl = document.createElement("span");
|
|
2888
2890
|
lbl.className = "vms-stat-bar__label";
|
|
2889
2891
|
lbl.textContent = stat.label;
|
|
@@ -3405,7 +3407,9 @@ export class BrowserAdapter {
|
|
|
3405
3407
|
n.steps.forEach((step, i) => {
|
|
3406
3408
|
const state = i < n.current ? "done" : i === n.current ? "current" : "upcoming";
|
|
3407
3409
|
const li = document.createElement("li");
|
|
3408
|
-
li.className =
|
|
3410
|
+
li.className = step.tone
|
|
3411
|
+
? `vms-steps__step vms-steps__step--${state} vms-steps__step--toned vms-steps__step--tone-${step.tone}`
|
|
3412
|
+
: `vms-steps__step vms-steps__step--${state}`;
|
|
3409
3413
|
if (state === "current")
|
|
3410
3414
|
li.setAttribute("aria-current", "step");
|
|
3411
3415
|
// Connector — CSS-drawn line marker-center to marker-center, behind the
|
package/dist/index.d.ts
CHANGED
|
@@ -233,6 +233,14 @@ export interface SectionNode {
|
|
|
233
233
|
export interface ListNode {
|
|
234
234
|
type: "list";
|
|
235
235
|
id?: string;
|
|
236
|
+
/** When true, this is an ORDERED list — renders `<ol>` (numbered) instead of
|
|
237
|
+
* `<ul>`. The semantic element gives real ordinal meaning (a11y: screen
|
|
238
|
+
* readers announce position/count; agent-legibility: the order is structural,
|
|
239
|
+
* not baked into item text). The visible "1." / "2." markers come from a CSS
|
|
240
|
+
* counter (`.vms-list--ordered`), not native list markers, because `.vms-list`
|
|
241
|
+
* is a flex column with `list-style:none` — so numbering survives the styled
|
|
242
|
+
* list-item layout. Omitted/false = `<ul>` (byte-identical to today). */
|
|
243
|
+
ordered?: boolean;
|
|
236
244
|
children: ViewNode[];
|
|
237
245
|
}
|
|
238
246
|
export interface ListItemNode {
|
|
@@ -722,7 +730,21 @@ export interface StatBarNode {
|
|
|
722
730
|
type: "stat-bar";
|
|
723
731
|
stats: Array<{
|
|
724
732
|
label: string;
|
|
725
|
-
value
|
|
733
|
+
/** The stat's value. Typed `string` (not `string | number`) so the TS and
|
|
734
|
+
* .NET backends emit BYTE-IDENTICAL wire — a bare number would serialize as
|
|
735
|
+
* JSON `12` in TS but the .NET twin's `string Value` can only emit `"12"`,
|
|
736
|
+
* a real cross-backend drift the parity suite never exercised. Format the
|
|
737
|
+
* number server-side (`String(n)`, `n.toFixed(2)`, `$${n}`) — the value is
|
|
738
|
+
* display text, and any formatting an app wants is richer than a bare
|
|
739
|
+
* number anyway. (Narrowed in 6.0.0 — see CHANGELOG; the field was unused.) */
|
|
740
|
+
value: string;
|
|
741
|
+
/** Optional semantic status tone for this tile — the universal status color
|
|
742
|
+
* axis (same closed set as Section/Button/ListItem/TableRow). Renders the
|
|
743
|
+
* tile as a subtly tinted chip (surface tint + colored border, reusing the
|
|
744
|
+
* --vms-error/-warning/-success/-info tokens), so an unhealthy stat reads at
|
|
745
|
+
* a glance rather than via one small line of text. Omitted = today's neutral
|
|
746
|
+
* inline stat. Closed union. */
|
|
747
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
726
748
|
}>;
|
|
727
749
|
}
|
|
728
750
|
export interface TabsNode {
|
|
@@ -993,6 +1015,16 @@ export interface StepItem {
|
|
|
993
1015
|
label: string;
|
|
994
1016
|
/** Optional one-line supporting text shown beside/under the label. */
|
|
995
1017
|
description?: string;
|
|
1018
|
+
/** Optional semantic status tone for this step — the universal status color
|
|
1019
|
+
* axis (same closed set as Section/Button/ListItem/TableRow). ORTHOGONAL to
|
|
1020
|
+
* the done/current/upcoming state the framework DERIVES from `current`: tone
|
|
1021
|
+
* overlays a semantic color onto the marker (e.g. a failed/blocked stage read
|
|
1022
|
+
* as `danger`, a stage needing attention as `warning`) regardless of its
|
|
1023
|
+
* position in the sequence. Omitted = the derived state's default appearance
|
|
1024
|
+
* (accent for done/current, muted for upcoming). Because tone is app-authored
|
|
1025
|
+
* status reinforced by the step's own label text, it is NOT color-only (same
|
|
1026
|
+
* posture as Section tone). Closed union. */
|
|
1027
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
996
1028
|
}
|
|
997
1029
|
/** A discrete step / stepper / wizard progress indicator — an ordered list of
|
|
998
1030
|
* stages with a single 0-based `current` index. Per-step status DERIVES from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic \u2014 a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsc -b tsconfig.tui.json",
|
|
33
33
|
"check:core-globals": "node scripts/check-core-platform-globals.mjs",
|
|
34
|
+
"check:test-types": "tsc -p tsconfig.test.json --noEmit",
|
|
35
|
+
"check:demo-types": "node scripts/check-demo-types.mjs",
|
|
34
36
|
"check:aa-contrast": "node scripts/check-aa-contrast.mjs",
|
|
35
37
|
"check:no-demo-style": "node scripts/check-no-demo-style.mjs",
|
|
36
38
|
"check:theme-byte-identity": "node scripts/check-theme-byte-identity.mjs",
|
package/styles/default.css
CHANGED
|
@@ -29,6 +29,21 @@
|
|
|
29
29
|
--vms-error: #c2453d;
|
|
30
30
|
--vms-error-glow: rgba(194, 69, 61, 0.10);
|
|
31
31
|
--vms-warning: #8a630d;
|
|
32
|
+
/* Warning is the ONE tone with a polarity split. --vms-warning is deliberately
|
|
33
|
+
DARK on light themes because it doubles as a TEXT/border color (.vms-text--warning,
|
|
34
|
+
button/badge outline) and warning-colored TEXT on a light surface must be dark to
|
|
35
|
+
clear WCAG-AA — a bright yellow text on white is unreadable. But a warning FILL has
|
|
36
|
+
no such constraint: the standard design-system pattern (Bootstrap/Material) is a
|
|
37
|
+
BRIGHT yellow fill with a DARK foreground. --vms-warning-fill + --vms-on-warning-fill
|
|
38
|
+
are that pair — used on every SOLID warning fill (toast, primary badge/button, the
|
|
39
|
+
toned steps marker) so warning actually reads as a yellow that grabs attention on
|
|
40
|
+
light themes, while staying AA (dark #2a2205 on #e0a823 = 7.37:1). Theme-independent
|
|
41
|
+
(a solid amber fill reads the same on any page surface); dark themes need no override
|
|
42
|
+
— their --vms-warning is already #e0a823, so this ALSO fixes the white-on-bright-amber
|
|
43
|
+
fill that failed AA on dark themes (2.14:1). Tints (section/table/chip) keep the base
|
|
44
|
+
--vms-warning as their own consistent family. */
|
|
45
|
+
--vms-warning-fill: #e0a823;
|
|
46
|
+
--vms-on-warning-fill: #2a2205;
|
|
32
47
|
--vms-priority-high: #d76410;
|
|
33
48
|
--vms-success: #2da359;
|
|
34
49
|
--vms-info: #2277dd;
|
|
@@ -473,6 +488,32 @@ body:has(.vms-page--fill) { margin: 0; }
|
|
|
473
488
|
text-transform: uppercase;
|
|
474
489
|
letter-spacing: 0.05em;
|
|
475
490
|
}
|
|
491
|
+
/* Per-tile tone (StatItem.tone) — a toned stat becomes a subtly tinted chip
|
|
492
|
+
(surface tint + colored border, the SAME color-mix formula as Section tone, so
|
|
493
|
+
it inherits that surface's already-AA-cleared text contrast) so an unhealthy
|
|
494
|
+
stat reads at a glance. Only toned items get the chip surface; neutral items
|
|
495
|
+
stay byte-identical to today's inline stat. --_tone resolves per tone below. */
|
|
496
|
+
.vms-stat-bar__item--tone-danger { --_tone: var(--vms-error); }
|
|
497
|
+
.vms-stat-bar__item--tone-warning { --_tone: var(--vms-warning); }
|
|
498
|
+
.vms-stat-bar__item--tone-success { --_tone: var(--vms-success); }
|
|
499
|
+
.vms-stat-bar__item--tone-info { --_tone: var(--vms-info); }
|
|
500
|
+
.vms-stat-bar__item--toned {
|
|
501
|
+
align-items: center;
|
|
502
|
+
gap: var(--vms-space-2xs);
|
|
503
|
+
padding: var(--vms-space-2xs) var(--vms-space-sm);
|
|
504
|
+
border: 1px solid var(--_tone);
|
|
505
|
+
border-radius: var(--vms-radius-md);
|
|
506
|
+
background: color-mix(in srgb, var(--_tone) 14%, var(--vms-surface));
|
|
507
|
+
}
|
|
508
|
+
/* The tint + border carry the status (the Section-tone model); the value + label
|
|
509
|
+
stay NEUTRAL and readable rather than tone-colored — tone text on its own 14%
|
|
510
|
+
tint fails WCAG-AA (success bottomed at 2.79:1 across the light themes), while
|
|
511
|
+
neutral text on the tint clears it exactly as Section tone's neutral text does. */
|
|
512
|
+
.vms-stat-bar__item--toned .vms-stat-bar__value { color: var(--vms-text); }
|
|
513
|
+
/* Label strengthens from muted to full text on a toned chip: muted on the 14%
|
|
514
|
+
tint bottoms at 4.21:1 (just under the 4.5 AA-text bar), and an emphasized chip
|
|
515
|
+
reads better with a solid label anyway. Full text on the tint is 11.14:1. */
|
|
516
|
+
.vms-stat-bar__item--toned .vms-stat-bar__label { color: var(--vms-text); }
|
|
476
517
|
|
|
477
518
|
/* ── Form ── */
|
|
478
519
|
.vms-form { display: flex; flex-direction: column; gap: var(--vms-space-sm); align-items: stretch; }
|
|
@@ -875,6 +916,16 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
875
916
|
border-color: var(--_btn-tone);
|
|
876
917
|
color: #fff;
|
|
877
918
|
}
|
|
919
|
+
/* Warning is the polarity exception: a filled warning button uses the bright fill +
|
|
920
|
+
dark label (white on warning fails AA — 3.2/2.14:1 light/dark; see --vms-warning-fill).
|
|
921
|
+
Border matches the fill (no rim) — same as every other primary tone, which is the
|
|
922
|
+
conventional filled-warning look (cf. Bootstrap .btn-warning). The dark label + filled
|
|
923
|
+
shape identify the control; a dark rim read as out-of-place next to danger/success/info. */
|
|
924
|
+
.vms-button--warning.vms-button--primary {
|
|
925
|
+
background: var(--vms-warning-fill);
|
|
926
|
+
border-color: var(--vms-warning-fill);
|
|
927
|
+
color: var(--vms-on-warning-fill);
|
|
928
|
+
}
|
|
878
929
|
.vms-button--primary:hover { filter: brightness(1.1); }
|
|
879
930
|
.vms-button--secondary {
|
|
880
931
|
background: transparent;
|
|
@@ -992,6 +1043,22 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
992
1043
|
.vms-list-item--success:hover { border-left-color: var(--vms-success); }
|
|
993
1044
|
.vms-list-item--info:hover { border-left-color: var(--vms-info); }
|
|
994
1045
|
|
|
1046
|
+
/* Ordered list — <ol>. `.vms-list` sets list-style:none for the styled
|
|
1047
|
+
flex-column layout, so native markers never show; a CSS counter supplies the
|
|
1048
|
+
number as the first flex child of each item (spaced by the item's own gap).
|
|
1049
|
+
tabular-nums + right-aligned min-width keeps multi-digit numbers aligned. */
|
|
1050
|
+
.vms-list--ordered { counter-reset: vms-li; }
|
|
1051
|
+
.vms-list--ordered > .vms-list-item { counter-increment: vms-li; }
|
|
1052
|
+
.vms-list--ordered > .vms-list-item::before {
|
|
1053
|
+
content: counter(vms-li) ".";
|
|
1054
|
+
flex: 0 0 auto;
|
|
1055
|
+
min-width: 1.5em;
|
|
1056
|
+
text-align: right;
|
|
1057
|
+
font-variant-numeric: tabular-nums;
|
|
1058
|
+
font-weight: 600;
|
|
1059
|
+
color: var(--vms-text-muted);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
995
1062
|
/* Selection state for master-detail / list-detail patterns (D-27). The
|
|
996
1063
|
currently-selected row. Stronger than the semantic hints (selection is
|
|
997
1064
|
the primary state) and stable through hover. Themable: uses only seam
|
|
@@ -1328,7 +1395,10 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
1328
1395
|
success/info are deepened (color-mix w/ black) so white at 14px also clears
|
|
1329
1396
|
4.5:1 (their pure tokens sit at 3.2 / 4.4:1). */
|
|
1330
1397
|
.vms-toast--danger { background: var(--vms-error); color: #fff; }
|
|
1331
|
-
|
|
1398
|
+
/* Warning uses the bright fill + dark foreground (see --vms-warning-fill): white on
|
|
1399
|
+
the dark #8a630d was muddy on light and white on the bright #e0a823 fails AA on dark
|
|
1400
|
+
(2.14:1). Dark text on the bright fill is 7.37:1 and reads as an attention-grabbing yellow. */
|
|
1401
|
+
.vms-toast--warning { background: var(--vms-warning-fill); color: var(--vms-on-warning-fill); }
|
|
1332
1402
|
.vms-toast--success { background: color-mix(in srgb, var(--vms-success) 80%, #000); color: #fff; }
|
|
1333
1403
|
.vms-toast--info { background: color-mix(in srgb, var(--vms-info) 88%, #000); color: #fff; }
|
|
1334
1404
|
/* fade-out applied just before removal. */
|
|
@@ -1403,6 +1473,14 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
1403
1473
|
border-color: var(--_badge-tone);
|
|
1404
1474
|
color: #fff;
|
|
1405
1475
|
}
|
|
1476
|
+
/* Warning polarity exception: a filled warning badge uses the bright fill + dark text
|
|
1477
|
+
(white on warning fails AA; see --vms-warning-fill). Border matches the fill (no rim),
|
|
1478
|
+
like every other primary tone. */
|
|
1479
|
+
.vms-badge--warning.vms-badge--primary {
|
|
1480
|
+
background: var(--vms-warning-fill);
|
|
1481
|
+
border-color: var(--vms-warning-fill);
|
|
1482
|
+
color: var(--vms-on-warning-fill);
|
|
1483
|
+
}
|
|
1406
1484
|
.vms-badge--secondary {
|
|
1407
1485
|
background: transparent;
|
|
1408
1486
|
border-color: var(--_badge-tone);
|
|
@@ -1530,6 +1608,52 @@ button.vms-breadcrumb__link {
|
|
|
1530
1608
|
}
|
|
1531
1609
|
.vms-steps__label { color: var(--vms-text); font-weight: 600; }
|
|
1532
1610
|
.vms-steps__description { color: var(--vms-text-muted); font-size: var(--vms-text-xs); }
|
|
1611
|
+
/* Per-step tone (StepItem.tone) — overlays the universal status color onto the
|
|
1612
|
+
marker, ORTHOGONAL to the derived done/current/upcoming treatment: the tone
|
|
1613
|
+
recolors whatever the state already does (filled states fill in the tone;
|
|
1614
|
+
upcoming stays outlined but in the tone), so a failed/blocked stage reads red
|
|
1615
|
+
without losing the reached/not-reached distinction. Two-class selectors
|
|
1616
|
+
outspecify the base state rules. The knockout glyph stays --vms-surface
|
|
1617
|
+
(polarity-adaptive, >=3:1 on the filled tone tokens across all themes — a
|
|
1618
|
+
graphical indicator whose state also rides aria-label + glyph + label text). */
|
|
1619
|
+
.vms-steps__step--tone-danger { --_tone: var(--vms-error); }
|
|
1620
|
+
.vms-steps__step--tone-warning { --_tone: var(--vms-warning); }
|
|
1621
|
+
.vms-steps__step--tone-success { --_tone: var(--vms-success); }
|
|
1622
|
+
.vms-steps__step--tone-info { --_tone: var(--vms-info); }
|
|
1623
|
+
/* Tone recolors the DERIVED treatment: filled states (done/current) fill in the tone;
|
|
1624
|
+
upcoming stays outlined but in the tone — preserving the reached/not-reached read while
|
|
1625
|
+
overlaying status color. Two-class selectors outspecify the base state rules. */
|
|
1626
|
+
.vms-steps__step--toned.vms-steps__step--done .vms-steps__marker,
|
|
1627
|
+
.vms-steps__step--toned.vms-steps__step--current .vms-steps__marker {
|
|
1628
|
+
background: var(--_tone);
|
|
1629
|
+
border-color: var(--_tone);
|
|
1630
|
+
color: var(--vms-surface);
|
|
1631
|
+
}
|
|
1632
|
+
.vms-steps__step--toned.vms-steps__step--upcoming .vms-steps__marker {
|
|
1633
|
+
border-color: var(--_tone);
|
|
1634
|
+
color: var(--_tone);
|
|
1635
|
+
}
|
|
1636
|
+
.vms-steps__step--toned.vms-steps__step--current .vms-steps__marker {
|
|
1637
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--_tone) 30%, transparent);
|
|
1638
|
+
}
|
|
1639
|
+
/* Warning FILLED (done/current): bright fill + dark glyph, no rim (see --vms-warning-fill). */
|
|
1640
|
+
.vms-steps__step--tone-warning.vms-steps__step--done .vms-steps__marker,
|
|
1641
|
+
.vms-steps__step--tone-warning.vms-steps__step--current .vms-steps__marker {
|
|
1642
|
+
background: var(--vms-warning-fill);
|
|
1643
|
+
border-color: var(--vms-warning-fill);
|
|
1644
|
+
color: var(--vms-on-warning-fill);
|
|
1645
|
+
}
|
|
1646
|
+
/* Warning UPCOMING (outline): try the BRIGHT amber for the ring + number rather than the
|
|
1647
|
+
dark --vms-warning. Bright amber as a line/digit on white is ~2.14:1 (below the strict
|
|
1648
|
+
bar), so this is a judgment call on visual legibility — the step's state is multi-channel
|
|
1649
|
+
(aria-label + number + position), so color is not its sole carrier. */
|
|
1650
|
+
.vms-steps__step--tone-warning.vms-steps__step--upcoming .vms-steps__marker {
|
|
1651
|
+
border-color: var(--vms-warning-fill);
|
|
1652
|
+
color: var(--vms-warning-fill);
|
|
1653
|
+
}
|
|
1654
|
+
.vms-steps__step--tone-warning.vms-steps__step--current .vms-steps__marker {
|
|
1655
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vms-warning-fill) 35%, transparent);
|
|
1656
|
+
}
|
|
1533
1657
|
|
|
1534
1658
|
/* Connector — the OUTGOING segment from this marker toward the next (hidden on
|
|
1535
1659
|
the last step). Colored by the step's own state: accent once the step is done,
|