@ashley-shrok/viewmodel-shell 5.2.0 → 6.0.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 +7 -3
- package/dist/index.d.ts +25 -1
- package/package.json +3 -1
- package/styles/default.css +109 -1
package/dist/browser.js
CHANGED
|
@@ -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
|
@@ -722,7 +722,21 @@ export interface StatBarNode {
|
|
|
722
722
|
type: "stat-bar";
|
|
723
723
|
stats: Array<{
|
|
724
724
|
label: string;
|
|
725
|
-
value
|
|
725
|
+
/** The stat's value. Typed `string` (not `string | number`) so the TS and
|
|
726
|
+
* .NET backends emit BYTE-IDENTICAL wire — a bare number would serialize as
|
|
727
|
+
* JSON `12` in TS but the .NET twin's `string Value` can only emit `"12"`,
|
|
728
|
+
* a real cross-backend drift the parity suite never exercised. Format the
|
|
729
|
+
* number server-side (`String(n)`, `n.toFixed(2)`, `$${n}`) — the value is
|
|
730
|
+
* display text, and any formatting an app wants is richer than a bare
|
|
731
|
+
* number anyway. (Narrowed in 6.0.0 — see CHANGELOG; the field was unused.) */
|
|
732
|
+
value: string;
|
|
733
|
+
/** Optional semantic status tone for this tile — the universal status color
|
|
734
|
+
* axis (same closed set as Section/Button/ListItem/TableRow). Renders the
|
|
735
|
+
* tile as a subtly tinted chip (surface tint + colored border, reusing the
|
|
736
|
+
* --vms-error/-warning/-success/-info tokens), so an unhealthy stat reads at
|
|
737
|
+
* a glance rather than via one small line of text. Omitted = today's neutral
|
|
738
|
+
* inline stat. Closed union. */
|
|
739
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
726
740
|
}>;
|
|
727
741
|
}
|
|
728
742
|
export interface TabsNode {
|
|
@@ -993,6 +1007,16 @@ export interface StepItem {
|
|
|
993
1007
|
label: string;
|
|
994
1008
|
/** Optional one-line supporting text shown beside/under the label. */
|
|
995
1009
|
description?: string;
|
|
1010
|
+
/** Optional semantic status tone for this step — the universal status color
|
|
1011
|
+
* axis (same closed set as Section/Button/ListItem/TableRow). ORTHOGONAL to
|
|
1012
|
+
* the done/current/upcoming state the framework DERIVES from `current`: tone
|
|
1013
|
+
* overlays a semantic color onto the marker (e.g. a failed/blocked stage read
|
|
1014
|
+
* as `danger`, a stage needing attention as `warning`) regardless of its
|
|
1015
|
+
* position in the sequence. Omitted = the derived state's default appearance
|
|
1016
|
+
* (accent for done/current, muted for upcoming). Because tone is app-authored
|
|
1017
|
+
* status reinforced by the step's own label text, it is NOT color-only (same
|
|
1018
|
+
* posture as Section tone). Closed union. */
|
|
1019
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
996
1020
|
}
|
|
997
1021
|
/** A discrete step / stepper / wizard progress indicator — an ordered list of
|
|
998
1022
|
* 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.0.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;
|
|
@@ -1328,7 +1379,10 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
1328
1379
|
success/info are deepened (color-mix w/ black) so white at 14px also clears
|
|
1329
1380
|
4.5:1 (their pure tokens sit at 3.2 / 4.4:1). */
|
|
1330
1381
|
.vms-toast--danger { background: var(--vms-error); color: #fff; }
|
|
1331
|
-
|
|
1382
|
+
/* Warning uses the bright fill + dark foreground (see --vms-warning-fill): white on
|
|
1383
|
+
the dark #8a630d was muddy on light and white on the bright #e0a823 fails AA on dark
|
|
1384
|
+
(2.14:1). Dark text on the bright fill is 7.37:1 and reads as an attention-grabbing yellow. */
|
|
1385
|
+
.vms-toast--warning { background: var(--vms-warning-fill); color: var(--vms-on-warning-fill); }
|
|
1332
1386
|
.vms-toast--success { background: color-mix(in srgb, var(--vms-success) 80%, #000); color: #fff; }
|
|
1333
1387
|
.vms-toast--info { background: color-mix(in srgb, var(--vms-info) 88%, #000); color: #fff; }
|
|
1334
1388
|
/* fade-out applied just before removal. */
|
|
@@ -1403,6 +1457,14 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
1403
1457
|
border-color: var(--_badge-tone);
|
|
1404
1458
|
color: #fff;
|
|
1405
1459
|
}
|
|
1460
|
+
/* Warning polarity exception: a filled warning badge uses the bright fill + dark text
|
|
1461
|
+
(white on warning fails AA; see --vms-warning-fill). Border matches the fill (no rim),
|
|
1462
|
+
like every other primary tone. */
|
|
1463
|
+
.vms-badge--warning.vms-badge--primary {
|
|
1464
|
+
background: var(--vms-warning-fill);
|
|
1465
|
+
border-color: var(--vms-warning-fill);
|
|
1466
|
+
color: var(--vms-on-warning-fill);
|
|
1467
|
+
}
|
|
1406
1468
|
.vms-badge--secondary {
|
|
1407
1469
|
background: transparent;
|
|
1408
1470
|
border-color: var(--_badge-tone);
|
|
@@ -1530,6 +1592,52 @@ button.vms-breadcrumb__link {
|
|
|
1530
1592
|
}
|
|
1531
1593
|
.vms-steps__label { color: var(--vms-text); font-weight: 600; }
|
|
1532
1594
|
.vms-steps__description { color: var(--vms-text-muted); font-size: var(--vms-text-xs); }
|
|
1595
|
+
/* Per-step tone (StepItem.tone) — overlays the universal status color onto the
|
|
1596
|
+
marker, ORTHOGONAL to the derived done/current/upcoming treatment: the tone
|
|
1597
|
+
recolors whatever the state already does (filled states fill in the tone;
|
|
1598
|
+
upcoming stays outlined but in the tone), so a failed/blocked stage reads red
|
|
1599
|
+
without losing the reached/not-reached distinction. Two-class selectors
|
|
1600
|
+
outspecify the base state rules. The knockout glyph stays --vms-surface
|
|
1601
|
+
(polarity-adaptive, >=3:1 on the filled tone tokens across all themes — a
|
|
1602
|
+
graphical indicator whose state also rides aria-label + glyph + label text). */
|
|
1603
|
+
.vms-steps__step--tone-danger { --_tone: var(--vms-error); }
|
|
1604
|
+
.vms-steps__step--tone-warning { --_tone: var(--vms-warning); }
|
|
1605
|
+
.vms-steps__step--tone-success { --_tone: var(--vms-success); }
|
|
1606
|
+
.vms-steps__step--tone-info { --_tone: var(--vms-info); }
|
|
1607
|
+
/* Tone recolors the DERIVED treatment: filled states (done/current) fill in the tone;
|
|
1608
|
+
upcoming stays outlined but in the tone — preserving the reached/not-reached read while
|
|
1609
|
+
overlaying status color. Two-class selectors outspecify the base state rules. */
|
|
1610
|
+
.vms-steps__step--toned.vms-steps__step--done .vms-steps__marker,
|
|
1611
|
+
.vms-steps__step--toned.vms-steps__step--current .vms-steps__marker {
|
|
1612
|
+
background: var(--_tone);
|
|
1613
|
+
border-color: var(--_tone);
|
|
1614
|
+
color: var(--vms-surface);
|
|
1615
|
+
}
|
|
1616
|
+
.vms-steps__step--toned.vms-steps__step--upcoming .vms-steps__marker {
|
|
1617
|
+
border-color: var(--_tone);
|
|
1618
|
+
color: var(--_tone);
|
|
1619
|
+
}
|
|
1620
|
+
.vms-steps__step--toned.vms-steps__step--current .vms-steps__marker {
|
|
1621
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--_tone) 30%, transparent);
|
|
1622
|
+
}
|
|
1623
|
+
/* Warning FILLED (done/current): bright fill + dark glyph, no rim (see --vms-warning-fill). */
|
|
1624
|
+
.vms-steps__step--tone-warning.vms-steps__step--done .vms-steps__marker,
|
|
1625
|
+
.vms-steps__step--tone-warning.vms-steps__step--current .vms-steps__marker {
|
|
1626
|
+
background: var(--vms-warning-fill);
|
|
1627
|
+
border-color: var(--vms-warning-fill);
|
|
1628
|
+
color: var(--vms-on-warning-fill);
|
|
1629
|
+
}
|
|
1630
|
+
/* Warning UPCOMING (outline): try the BRIGHT amber for the ring + number rather than the
|
|
1631
|
+
dark --vms-warning. Bright amber as a line/digit on white is ~2.14:1 (below the strict
|
|
1632
|
+
bar), so this is a judgment call on visual legibility — the step's state is multi-channel
|
|
1633
|
+
(aria-label + number + position), so color is not its sole carrier. */
|
|
1634
|
+
.vms-steps__step--tone-warning.vms-steps__step--upcoming .vms-steps__marker {
|
|
1635
|
+
border-color: var(--vms-warning-fill);
|
|
1636
|
+
color: var(--vms-warning-fill);
|
|
1637
|
+
}
|
|
1638
|
+
.vms-steps__step--tone-warning.vms-steps__step--current .vms-steps__marker {
|
|
1639
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--vms-warning-fill) 35%, transparent);
|
|
1640
|
+
}
|
|
1533
1641
|
|
|
1534
1642
|
/* Connector — the OUTGOING segment from this marker toward the next (hidden on
|
|
1535
1643
|
the last step). Colored by the step's own state: accent once the step is done,
|