@ashley-shrok/viewmodel-shell 2.0.0 → 3.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 +14 -10
- package/dist/index.d.ts +32 -11
- package/dist/tui.js +23 -13
- package/package.json +1 -1
- package/styles/default.css +80 -34
package/dist/browser.js
CHANGED
|
@@ -353,7 +353,7 @@ export class BrowserAdapter {
|
|
|
353
353
|
this.sectionKeyCounter.set(baseKey, ordinal + 1);
|
|
354
354
|
const finalKey = `${baseKey}:${ordinal}`;
|
|
355
355
|
const el = document.createElement("details");
|
|
356
|
-
el.className = `vms-section vms-section--collapsible${n.variant === "card" ? " vms-section--card" : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}${n.arrange ? ` vms-arrange--${n.arrange}` : ""}${n.align ? ` vms-align--${n.align}` : ""}${n.threshold ? ` vms-switch--${n.threshold}` : ""}${n.limit ? ` vms-switch-limit--${n.limit}` : ""}${n.minItem ? ` vms-cards-min--${n.minItem}` : ""}`;
|
|
356
|
+
el.className = `vms-section vms-section--collapsible${n.variant === "card" ? " vms-section--card" : ""}${n.tone ? ` vms-section--${n.tone}` : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}${n.arrange ? ` vms-arrange--${n.arrange}` : ""}${n.align ? ` vms-align--${n.align}` : ""}${n.threshold ? ` vms-switch--${n.threshold}` : ""}${n.limit ? ` vms-switch-limit--${n.limit}` : ""}${n.minItem ? ` vms-cards-min--${n.minItem}` : ""}`;
|
|
357
357
|
el.dataset.sectionKey = finalKey;
|
|
358
358
|
// Initial render is always closed — the post-render restore loop in
|
|
359
359
|
// render() re-applies `open=true` for keys the user had open before.
|
|
@@ -377,7 +377,7 @@ export class BrowserAdapter {
|
|
|
377
377
|
// action — see validateSectionAction in server.ts.
|
|
378
378
|
if (n.link) {
|
|
379
379
|
const a = document.createElement("a");
|
|
380
|
-
a.className = `vms-section vms-section--linked${n.variant === "card" ? " vms-section--card" : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}${n.arrange ? ` vms-arrange--${n.arrange}` : ""}${n.align ? ` vms-align--${n.align}` : ""}${n.threshold ? ` vms-switch--${n.threshold}` : ""}${n.limit ? ` vms-switch-limit--${n.limit}` : ""}${n.minItem ? ` vms-cards-min--${n.minItem}` : ""}`;
|
|
380
|
+
a.className = `vms-section vms-section--linked${n.variant === "card" ? " vms-section--card" : ""}${n.tone ? ` vms-section--${n.tone}` : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}${n.arrange ? ` vms-arrange--${n.arrange}` : ""}${n.align ? ` vms-align--${n.align}` : ""}${n.threshold ? ` vms-switch--${n.threshold}` : ""}${n.limit ? ` vms-switch-limit--${n.limit}` : ""}${n.minItem ? ` vms-cards-min--${n.minItem}` : ""}`;
|
|
381
381
|
a.href = n.link.url;
|
|
382
382
|
// Mirror LinkNode's external-attribute pattern (browser.ts ~line 666)
|
|
383
383
|
// byte-for-byte: target=_blank + rel=noopener noreferrer when external.
|
|
@@ -421,7 +421,7 @@ export class BrowserAdapter {
|
|
|
421
421
|
return;
|
|
422
422
|
}
|
|
423
423
|
const el = document.createElement("section");
|
|
424
|
-
el.className = `vms-section${n.variant === "card" ? " vms-section--card" : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}${n.arrange ? ` vms-arrange--${n.arrange}` : ""}${n.align ? ` vms-align--${n.align}` : ""}${n.threshold ? ` vms-switch--${n.threshold}` : ""}${n.limit ? ` vms-switch-limit--${n.limit}` : ""}${n.minItem ? ` vms-cards-min--${n.minItem}` : ""}${n.action ? " vms-section--clickable" : ""}`;
|
|
424
|
+
el.className = `vms-section${n.variant === "card" ? " vms-section--card" : ""}${n.tone ? ` vms-section--${n.tone}` : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}${n.arrange ? ` vms-arrange--${n.arrange}` : ""}${n.align ? ` vms-align--${n.align}` : ""}${n.threshold ? ` vms-switch--${n.threshold}` : ""}${n.limit ? ` vms-switch-limit--${n.limit}` : ""}${n.minItem ? ` vms-cards-min--${n.minItem}` : ""}${n.action ? " vms-section--clickable" : ""}`;
|
|
425
425
|
if (n.heading) {
|
|
426
426
|
const h = document.createElement("h2");
|
|
427
427
|
h.className = "vms-section__heading";
|
|
@@ -480,7 +480,7 @@ export class BrowserAdapter {
|
|
|
480
480
|
}
|
|
481
481
|
listItem(n, parent, on) {
|
|
482
482
|
const li = document.createElement("li");
|
|
483
|
-
li.className = `vms-list-item${n.
|
|
483
|
+
li.className = `vms-list-item${n.state ? ` vms-list-item--${n.state}` : ""}${n.tone ? ` vms-list-item--${n.tone}` : ""}`;
|
|
484
484
|
if (n.id)
|
|
485
485
|
li.dataset.id = n.id;
|
|
486
486
|
this.kids(n.children, li, on);
|
|
@@ -769,7 +769,7 @@ export class BrowserAdapter {
|
|
|
769
769
|
button(n, parent, on) {
|
|
770
770
|
const btn = document.createElement("button");
|
|
771
771
|
btn.type = "button";
|
|
772
|
-
btn.className = `vms-button${n.
|
|
772
|
+
btn.className = `vms-button${n.emphasis ? ` vms-button--${n.emphasis}` : ""}${n.tone ? ` vms-button--${n.tone}` : ""}${n.size ? ` vms-button--${n.size}` : ""}`;
|
|
773
773
|
btn.textContent = n.label;
|
|
774
774
|
btn.addEventListener("click", () => {
|
|
775
775
|
// pendingLabel: instant client-side feedback. Swap text + add
|
|
@@ -786,15 +786,17 @@ export class BrowserAdapter {
|
|
|
786
786
|
}
|
|
787
787
|
text(n, parent) {
|
|
788
788
|
const el = document.createElement(n.style === "pre" ? "pre" : "span");
|
|
789
|
-
el.className = `vms-text${n.style ? ` vms-text--${n.style}` : ""}`;
|
|
789
|
+
el.className = `vms-text${n.style ? ` vms-text--${n.style}` : ""}${n.tone ? ` vms-text--${n.tone}` : ""}`;
|
|
790
790
|
el.textContent = n.value;
|
|
791
791
|
parent.appendChild(el);
|
|
792
792
|
}
|
|
793
793
|
link(n, parent) {
|
|
794
794
|
const a = document.createElement("a");
|
|
795
|
-
a.className = "vms-link";
|
|
795
|
+
a.className = n.active ? "vms-link vms-link--active" : "vms-link";
|
|
796
796
|
a.href = n.href;
|
|
797
797
|
a.textContent = n.label;
|
|
798
|
+
if (n.active)
|
|
799
|
+
a.setAttribute("aria-current", "page");
|
|
798
800
|
if (n.external) {
|
|
799
801
|
a.target = "_blank";
|
|
800
802
|
a.rel = "noopener noreferrer";
|
|
@@ -1006,8 +1008,10 @@ export class BrowserAdapter {
|
|
|
1006
1008
|
n.rows.forEach(row => {
|
|
1007
1009
|
const tr = document.createElement("tr");
|
|
1008
1010
|
let rowClass = "vms-table__row";
|
|
1009
|
-
if (row.
|
|
1010
|
-
rowClass += ` vms-table__row--${row.
|
|
1011
|
+
if (row.state)
|
|
1012
|
+
rowClass += ` vms-table__row--${row.state}`;
|
|
1013
|
+
if (row.tone)
|
|
1014
|
+
rowClass += ` vms-table__row--${row.tone}`;
|
|
1011
1015
|
if (row.action)
|
|
1012
1016
|
rowClass += " vms-table__row--clickable";
|
|
1013
1017
|
tr.className = rowClass;
|
|
@@ -1133,7 +1137,7 @@ export class BrowserAdapter {
|
|
|
1133
1137
|
copyButton(n, parent) {
|
|
1134
1138
|
const btn = document.createElement("button");
|
|
1135
1139
|
btn.type = "button";
|
|
1136
|
-
btn.className = `vms-button${n.
|
|
1140
|
+
btn.className = `vms-button${n.emphasis ? ` vms-button--${n.emphasis}` : ""}${n.tone ? ` vms-button--${n.tone}` : ""}${n.size ? ` vms-button--${n.size}` : ""}`;
|
|
1137
1141
|
btn.textContent = n.label ?? "Copy";
|
|
1138
1142
|
btn.addEventListener("click", () => {
|
|
1139
1143
|
const write = navigator.clipboard?.writeText(n.text);
|
package/dist/index.d.ts
CHANGED
|
@@ -88,8 +88,10 @@ export interface PageNode {
|
|
|
88
88
|
export interface SectionNode {
|
|
89
89
|
type: "section";
|
|
90
90
|
heading?: string;
|
|
91
|
-
/** Section surface variant. Omitted = current behavior (no modifier class). "card" emits .vms-section--card. Closed union (D-03). */
|
|
91
|
+
/** Section surface variant — the structural KIND of the section's surface (the single meaning of "variant" framework-wide). Omitted = current behavior (no modifier class). "card" emits .vms-section--card. Closed union (D-03). */
|
|
92
92
|
variant?: "card";
|
|
93
|
+
/** Semantic intent/severity tone — the universal status color axis (orthogonal to `variant`; a section can be a `card` AND `tone:"warning"`). Emits .vms-section--{tone} (subtle tinted surface + colored border, reusing the --vms-error/-warning/-success/-info tokens). Omitted = neutral. Closed union. */
|
|
94
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
93
95
|
/** Layout preset arranging direct children. Omitted or "stack" = current vertical flow (no modifier class). "split" (equal 2-up), "cards" (uniform grid), "sidebar" (thin + wide app shell), "row" (left-aligned wrapping horizontal row; items hug content), "switcher" (N equal items flipping all-row ↔ all-stack atomically at a content-width `threshold` — the negative-flex-basis primitive a grid cannot express; distinct from `cards` auto-fit which passes through intermediate column counts) emit .vms-section--{value}. Closed union (D-01/D-02; sidebar D-28; row D-30; switcher SWITCH-01). */
|
|
94
96
|
layout?: "stack" | "split" | "cards" | "sidebar" | "row" | "switcher";
|
|
95
97
|
/** Main-axis arrangement for `layout:"row"` (the cluster primitive) — maps to `justify-content`. Omitted = no class → the row default (`flex-start`, left-pack) holds = byte-identical to today. Closed union copied verbatim from Jetpack Compose `Arrangement` ∩ Flutter `MainAxisAlignment` (ALIGN-01). Emits .vms-arrange--{value}. */
|
|
@@ -186,8 +188,10 @@ export interface ListNode {
|
|
|
186
188
|
export interface ListItemNode {
|
|
187
189
|
type: "list-item";
|
|
188
190
|
id?: string;
|
|
189
|
-
/** Appended as a BEM modifier: vms-list-item--{
|
|
190
|
-
|
|
191
|
+
/** Row lifecycle/selection STATE (NOT severity — that's `tone`). A freeform, app-extensible token; the framework ships styling for `active` (selected), `done`, `disabled`, `high` (priority), `running`, `moving`. Appended as a BEM modifier: vms-list-item--{state}. Orthogonal to `tone` (a row can be `state:"active"` AND `tone:"danger"`). */
|
|
192
|
+
state?: string;
|
|
193
|
+
/** Semantic intent/severity — the universal status tone axis (closed). Emits .vms-list-item--{tone} (colored accent border, reusing the shared tokens). Omitted = neutral. */
|
|
194
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
191
195
|
children: ViewNode[];
|
|
192
196
|
}
|
|
193
197
|
export interface FormNode {
|
|
@@ -251,7 +255,12 @@ export interface ButtonNode {
|
|
|
251
255
|
type: "button";
|
|
252
256
|
label: string;
|
|
253
257
|
action: ActionEvent;
|
|
254
|
-
|
|
258
|
+
/** Visual emphasis (how loud) — `primary` = filled, `secondary` = outline. Orthogonal to `tone` and `size`. Emits .vms-button--{emphasis}. Omitted = the neutral default button. Closed union. */
|
|
259
|
+
emphasis?: "primary" | "secondary";
|
|
260
|
+
/** Semantic intent/severity (what it means) — the universal status color axis, orthogonal to `emphasis`. A destructive primary button is `emphasis:"primary"` + `tone:"danger"`. Emits .vms-button--{tone}. Omitted = neutral. Closed union. */
|
|
261
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
262
|
+
/** Box geometry (padding + font), orthogonal to color/emphasis — the one axis no design system bakes into variant. Emits .vms-button--{size}. Omitted = the default (md) size. Closed union. */
|
|
263
|
+
size?: "sm" | "lg";
|
|
255
264
|
/** Transient label shown from click until the dispatch resolves (response
|
|
256
265
|
* arrives or dispatch errors). Mirrors `CopyButtonNode.copiedLabel`'s
|
|
257
266
|
* lifecycle pattern at a different beat: shown DURING the round-trip
|
|
@@ -263,7 +272,10 @@ export interface ButtonNode {
|
|
|
263
272
|
export interface TextNode {
|
|
264
273
|
type: "text";
|
|
265
274
|
value: string;
|
|
266
|
-
|
|
275
|
+
/** Typography role only (NOT color) — emits .vms-text--{style}. Semantic color moved to `tone` (the old `error`/`warning` style values are now `tone:"danger"`/`tone:"warning"`). Closed union. */
|
|
276
|
+
style?: "heading" | "subheading" | "body" | "muted" | "strikethrough" | "pre";
|
|
277
|
+
/** Semantic intent/severity color — the universal status tone axis, orthogonal to `style` (a heading can be `tone:"danger"`). Emits .vms-text--{tone}; the tone color wins over a `style` color via source order. Omitted = default text color. Closed union. */
|
|
278
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
267
279
|
}
|
|
268
280
|
export interface LinkNode {
|
|
269
281
|
type: "link";
|
|
@@ -271,6 +283,11 @@ export interface LinkNode {
|
|
|
271
283
|
href: string;
|
|
272
284
|
/** true = open outside current app context (browser: new tab + noopener) */
|
|
273
285
|
external?: boolean;
|
|
286
|
+
/** true = this link points at the current location (nav "you are here").
|
|
287
|
+
* Emits `.vms-link--active` + `aria-current="page"`. Server-owned: the
|
|
288
|
+
* backend decides which nav item is active from its route/state, exactly
|
|
289
|
+
* like every other view decision — there is no client-side route matching. */
|
|
290
|
+
active?: boolean;
|
|
274
291
|
}
|
|
275
292
|
export interface ImageNode {
|
|
276
293
|
type: "image";
|
|
@@ -352,7 +369,10 @@ export interface TableRow {
|
|
|
352
369
|
* cell (right). A previous version typed this as `ButtonNode[]` and called
|
|
353
370
|
* the button renderer blindly, silently dropping non-button entries. */
|
|
354
371
|
actions?: (ButtonNode | CheckboxNode)[];
|
|
355
|
-
|
|
372
|
+
/** Row lifecycle STATE (NOT severity — that's `tone`). A freeform, app-extensible token; the framework ships styling for `done`, `disabled`, `running`. Emits .vms-table__row--{state}. Orthogonal to `tone`. */
|
|
373
|
+
state?: string;
|
|
374
|
+
/** Semantic intent/severity — the universal status tone axis (closed). Emits .vms-table__row--{tone} (subtle tinted row background, reusing the shared tokens). Omitted = neutral. */
|
|
375
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
356
376
|
}
|
|
357
377
|
export interface TablePagination {
|
|
358
378
|
/** 1-based current page. */
|
|
@@ -405,11 +425,12 @@ export interface CopyButtonNode {
|
|
|
405
425
|
label?: string;
|
|
406
426
|
/** Ephemeral label shown after a successful copy, reverts after ~1.5 s. Adapter default: "Copied!". */
|
|
407
427
|
copiedLabel?: string;
|
|
408
|
-
/** Visual
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
428
|
+
/** Visual emphasis — mirrors ButtonNode.emphasis. `primary` = filled, `secondary` = outline. Emits .vms-button--{emphasis}. Closed union. */
|
|
429
|
+
emphasis?: "primary" | "secondary";
|
|
430
|
+
/** Semantic intent/severity — mirrors ButtonNode.tone. Emits .vms-button--{tone}. Closed union. */
|
|
431
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
432
|
+
/** Box geometry — mirrors ButtonNode.size. Emits .vms-button--{size}. Omitted = md. Closed union. */
|
|
433
|
+
size?: "sm" | "lg";
|
|
413
434
|
}
|
|
414
435
|
/**
|
|
415
436
|
* The SwiftUI `ViewThatFits` port. The renderer picks the FIRST child whose
|
package/dist/tui.js
CHANGED
|
@@ -855,14 +855,21 @@ const STYLE_ATTRS = {
|
|
|
855
855
|
body: {},
|
|
856
856
|
muted: { fg: "#888888" },
|
|
857
857
|
strikethrough: { attributes: 16 /* STRIKETHROUGH */, fg: "#888888" },
|
|
858
|
-
error: { fg: "#ff5555" },
|
|
859
|
-
warning: { fg: "#e0a823" },
|
|
860
858
|
pre: { fg: "#cccccc" },
|
|
861
859
|
};
|
|
860
|
+
// Universal semantic-tone → foreground color, shared by text / buttons / list items.
|
|
861
|
+
const TONE_FG = {
|
|
862
|
+
danger: "#ff5555",
|
|
863
|
+
warning: "#e0a823",
|
|
864
|
+
success: "#5fd75f",
|
|
865
|
+
info: "#5fafff",
|
|
866
|
+
};
|
|
862
867
|
function TextView({ node }) {
|
|
863
868
|
const style = node.style ?? "body";
|
|
864
869
|
const attrs = STYLE_ATTRS[style];
|
|
865
|
-
|
|
870
|
+
// tone color wins over the style's fg (mirrors the browser source-order rule).
|
|
871
|
+
const fg = node.tone ? TONE_FG[node.tone] : attrs.fg;
|
|
872
|
+
return (_jsx("text", { ...(attrs.attributes != null ? { attributes: attrs.attributes } : {}), ...(fg != null ? { fg } : {}), children: node.value }));
|
|
866
873
|
}
|
|
867
874
|
// ── link ──────────────────────────────────────────────────────────────────
|
|
868
875
|
// External links emit a real OSC 8 hyperlink (\x1b]8;;url\x07label\x1b]8;;\x07);
|
|
@@ -896,13 +903,16 @@ function ImageView({ node }) {
|
|
|
896
903
|
}
|
|
897
904
|
// ── list / list-item ──────────────────────────────────────────────────────
|
|
898
905
|
// A top-level `list` (direct child of page) is a pane on its own; nested
|
|
899
|
-
// lists scroll as part of their containing section. list-item
|
|
900
|
-
// ("done", "active", …)
|
|
906
|
+
// lists scroll as part of their containing section. list-item `state`
|
|
907
|
+
// ("done", "active", …) maps to a text color; semantic `tone` (danger/…)
|
|
908
|
+
// uses the shared TONE_FG and wins over state.
|
|
901
909
|
const LIST_ITEM_FG = {
|
|
902
910
|
done: "#88cc88",
|
|
903
911
|
active: "#88aaff",
|
|
904
|
-
|
|
905
|
-
|
|
912
|
+
running: "#88aaff",
|
|
913
|
+
moving: "#88aaff",
|
|
914
|
+
disabled: "#888888",
|
|
915
|
+
high: "#d76410",
|
|
906
916
|
};
|
|
907
917
|
function ListView({ node, ctx }) {
|
|
908
918
|
const isPane = ctx.isTopLevel;
|
|
@@ -924,9 +934,9 @@ function ListView({ node, ctx }) {
|
|
|
924
934
|
return (_jsx("scrollbox", { focused: focused, focusable: isPaneFocusable, borderColor: focused ? "#88aaff" : "#555555", focusedBorderColor: "#88aaff", flexGrow: 1, flexShrink: 1, children: _jsx("box", { flexDirection: "column", gap: 0, children: node.children.map((child, i) => renderNode(child, childCtx, i)) }) }));
|
|
925
935
|
}
|
|
926
936
|
function ListItemView({ node, ctx }) {
|
|
927
|
-
const fg = node.
|
|
937
|
+
const fg = node.tone ? TONE_FG[node.tone] : node.state ? LIST_ITEM_FG[node.state] : undefined;
|
|
928
938
|
const childCtx = { ...ctx, isTopLevel: false };
|
|
929
|
-
// The
|
|
939
|
+
// The tint applies to text children inside this item via inherited
|
|
930
940
|
// color (OpenTUI <text fg> wins per-element, so this only affects items
|
|
931
941
|
// whose children don't override). For B2 we just thread the children
|
|
932
942
|
// through unchanged; full per-variant styling lands in B3 (with the focus +
|
|
@@ -1022,8 +1032,8 @@ function TableView({ node, ctx }) {
|
|
|
1022
1032
|
// ── minimum-viable text surface for the rest of the node set ───────────────
|
|
1023
1033
|
// Same as B1 — text only, no interactivity. Full widgets land in B3/B4.
|
|
1024
1034
|
function ButtonView({ node, ctx }) {
|
|
1025
|
-
const fg = node.
|
|
1026
|
-
: node.
|
|
1035
|
+
const fg = node.tone ? TONE_FG[node.tone]
|
|
1036
|
+
: node.emphasis === "primary" ? "#88aaff"
|
|
1027
1037
|
: undefined;
|
|
1028
1038
|
// 0.8.0 (#11) — pendingLabel: when set + this button's key matches the
|
|
1029
1039
|
// adapter's pendingButtonKey, render the pending label instead of the
|
|
@@ -1128,8 +1138,8 @@ function CopyButtonView({ node, ctx }) {
|
|
|
1128
1138
|
// 0.9.0 (#14): variant coloring — mirrors ButtonView's fg derivation
|
|
1129
1139
|
// verbatim. Adapter-medium-adaptation parity: the browser uses CSS
|
|
1130
1140
|
// classes; the TUI uses ANSI fg colors. Same semantic mapping.
|
|
1131
|
-
const fg = node.
|
|
1132
|
-
: node.
|
|
1141
|
+
const fg = node.tone ? TONE_FG[node.tone]
|
|
1142
|
+
: node.emphasis === "primary" ? "#88aaff"
|
|
1133
1143
|
: undefined;
|
|
1134
1144
|
return (_jsxs("text", { ...(fg != null ? { fg } : {}), onMouseDown: () => ctx.copy(node.text), children: ["[ ", label, " ]"] }));
|
|
1135
1145
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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 — a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/styles/default.css
CHANGED
|
@@ -235,6 +235,11 @@ body {
|
|
|
235
235
|
flex-direction: row;
|
|
236
236
|
flex-wrap: wrap;
|
|
237
237
|
align-items: center;
|
|
238
|
+
/* Horizontal clusters (navbars, toolbars, header rows) need more breathing
|
|
239
|
+
room between siblings than the tight vertical --vms-space-sm rhythm. This
|
|
240
|
+
is the one place the row preset DOES redeclare gap (D-30 left it inherited;
|
|
241
|
+
nav links at 0.75rem read cramped). */
|
|
242
|
+
gap: var(--vms-space-lg);
|
|
238
243
|
}
|
|
239
244
|
.vms-page--row > .vms-page__title,
|
|
240
245
|
.vms-section--row > .vms-section__heading { flex: 0 0 100%; }
|
|
@@ -323,6 +328,19 @@ body {
|
|
|
323
328
|
padding: var(--vms-space-md);
|
|
324
329
|
}
|
|
325
330
|
|
|
331
|
+
/* ── Section tone — universal status axis (orthogonal to variant:card): a tinted
|
|
332
|
+
surface + solid tone-colored border, reusing the shared semantic tokens.
|
|
333
|
+
The tint is mixed into the OPAQUE surface (not transparent) so it reads on
|
|
334
|
+
dark themes too — a translucent layer over a dark surface is invisible, which
|
|
335
|
+
is why this must mix with var(--vms-surface). Composes with --card (a card
|
|
336
|
+
reads as warning/danger/etc; border-radius + padding come from --card) and
|
|
337
|
+
also tints a bare section. The solid tone border is the always-visible signal
|
|
338
|
+
on any background. ── */
|
|
339
|
+
.vms-section--danger { background: color-mix(in srgb, var(--vms-error) 14%, var(--vms-surface)); border: 1px solid var(--vms-error); }
|
|
340
|
+
.vms-section--warning { background: color-mix(in srgb, var(--vms-warning) 14%, var(--vms-surface)); border: 1px solid var(--vms-warning); }
|
|
341
|
+
.vms-section--success { background: color-mix(in srgb, var(--vms-success) 14%, var(--vms-surface)); border: 1px solid var(--vms-success); }
|
|
342
|
+
.vms-section--info { background: color-mix(in srgb, var(--vms-info) 14%, var(--vms-surface)); border: 1px solid var(--vms-info); }
|
|
343
|
+
|
|
326
344
|
/* ── Section: clickable (1.4.0 — SectionNode.action click-anywhere primitive) ──
|
|
327
345
|
Mirrors the 1.1.0 .vms-table__row--clickable idiom one level up the tree.
|
|
328
346
|
Hover uses a 1px accent-dim ring (rather than a background swap — cards
|
|
@@ -492,8 +510,15 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
492
510
|
}
|
|
493
511
|
.vms-checkbox__label { margin-left: var(--vms-space-xs); font-size: var(--vms-text-base); }
|
|
494
512
|
|
|
495
|
-
/* ── Buttons
|
|
513
|
+
/* ── Buttons — three orthogonal axes: emphasis (fill) × tone (color) × size
|
|
514
|
+
(geometry). `--_btn-tone` carries the semantic color: it defaults to accent
|
|
515
|
+
and the tone classes override it, so an emphasis fills/outlines in whatever
|
|
516
|
+
tone is set (emphasis:primary + tone:danger = a filled RED button). Box
|
|
517
|
+
metrics (padding/font) live ONLY on the base (= md) and the size classes —
|
|
518
|
+
never on emphasis or tone — so a primary and a danger button are the SAME
|
|
519
|
+
size. ── */
|
|
496
520
|
.vms-button {
|
|
521
|
+
--_btn-tone: var(--vms-accent);
|
|
497
522
|
background: var(--vms-surface-2);
|
|
498
523
|
border: 1px solid var(--vms-border);
|
|
499
524
|
border-radius: var(--vms-radius);
|
|
@@ -502,32 +527,37 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
502
527
|
font-family: var(--vms-font-body);
|
|
503
528
|
font-size: var(--vms-text-base);
|
|
504
529
|
padding: var(--vms-space-sm) var(--vms-space-md);
|
|
530
|
+
align-self: flex-start;
|
|
505
531
|
transition: background var(--vms-t), border-color var(--vms-t), transform var(--vms-t);
|
|
506
532
|
}
|
|
507
533
|
.vms-button:hover { border-color: var(--vms-accent-dim); }
|
|
508
534
|
.vms-button:active { transform: scale(0.98); }
|
|
535
|
+
|
|
536
|
+
/* tone — semantic color. Standalone (no emphasis) = colored text + border on the
|
|
537
|
+
neutral surface; combined with an emphasis it recolors the fill/outline. */
|
|
538
|
+
.vms-button--danger { --_btn-tone: var(--vms-error); color: var(--vms-error); border-color: var(--vms-error); }
|
|
539
|
+
.vms-button--warning { --_btn-tone: var(--vms-warning); color: var(--vms-warning); border-color: var(--vms-warning); }
|
|
540
|
+
.vms-button--success { --_btn-tone: var(--vms-success); color: var(--vms-success); border-color: var(--vms-success); }
|
|
541
|
+
.vms-button--info { --_btn-tone: var(--vms-info); color: var(--vms-info); border-color: var(--vms-info); }
|
|
542
|
+
|
|
543
|
+
/* emphasis — fill treatment, colored by --_btn-tone (declared AFTER tone so the
|
|
544
|
+
filled text color / outline color wins on combination). */
|
|
509
545
|
.vms-button--primary {
|
|
510
|
-
background: var(--
|
|
511
|
-
border-color: var(--
|
|
546
|
+
background: var(--_btn-tone);
|
|
547
|
+
border-color: var(--_btn-tone);
|
|
512
548
|
color: #fff;
|
|
513
|
-
padding: var(--vms-space-sm) var(--vms-space-lg);
|
|
514
|
-
align-self: flex-start;
|
|
515
549
|
}
|
|
516
550
|
.vms-button--primary:hover { filter: brightness(1.1); }
|
|
517
551
|
.vms-button--secondary {
|
|
518
552
|
background: transparent;
|
|
519
|
-
border-color: var(--
|
|
520
|
-
color: var(--
|
|
521
|
-
font-size: var(--vms-text-sm);
|
|
522
|
-
padding: var(--vms-space-2xs) var(--vms-space-sm);
|
|
553
|
+
border-color: var(--_btn-tone);
|
|
554
|
+
color: var(--_btn-tone);
|
|
523
555
|
}
|
|
524
|
-
.vms-button--secondary:hover {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
.vms-button--danger:hover { background: var(--vms-error-glow); border-color: var(--vms-error); }
|
|
556
|
+
.vms-button--secondary:hover { background: var(--vms-accent-glow); }
|
|
557
|
+
|
|
558
|
+
/* size — the ONLY axis that touches box metrics (md = base, no class). */
|
|
559
|
+
.vms-button--sm { padding: var(--vms-space-2xs) var(--vms-space-sm); font-size: var(--vms-text-sm); }
|
|
560
|
+
.vms-button--lg { padding: var(--vms-space-sm) var(--vms-space-lg); font-size: var(--vms-text-lg); }
|
|
531
561
|
|
|
532
562
|
/* ── Pending state (0.8.0 / issue #11) ──
|
|
533
563
|
Applied by BrowserAdapter when a ButtonNode with `pendingLabel` is clicked.
|
|
@@ -594,20 +624,23 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
594
624
|
}
|
|
595
625
|
.vms-list-item:hover { border-color: var(--vms-accent-dim); }
|
|
596
626
|
|
|
597
|
-
/*
|
|
598
|
-
|
|
627
|
+
/* State (lifecycle) — done/high are framework-styled; apps can add more
|
|
628
|
+
.vms-list-item--{state} rules. The `high` priority accent goes on border-left. */
|
|
599
629
|
.vms-list-item--done { background: var(--vms-done-bg); border-color: transparent; opacity: 0.75; }
|
|
600
|
-
.vms-list-item--
|
|
630
|
+
.vms-list-item--disabled { opacity: 0.55; }
|
|
601
631
|
.vms-list-item--high { border-left: 3px solid var(--vms-priority-high); }
|
|
632
|
+
.vms-list-item--high:hover { border-left-color: var(--vms-priority-high); }
|
|
633
|
+
|
|
634
|
+
/* Tone (severity) — the universal status axis (was the old critical/warning/...
|
|
635
|
+
variants; critical → danger). Colored left accent, preserved through hover. */
|
|
636
|
+
.vms-list-item--danger { border-left: 3px solid var(--vms-error); background: color-mix(in srgb, var(--vms-error) 4%, transparent); }
|
|
602
637
|
.vms-list-item--warning { border-left: 3px solid var(--vms-warning); }
|
|
603
638
|
.vms-list-item--success { border-left: 3px solid var(--vms-success); }
|
|
604
639
|
.vms-list-item--info { border-left: 3px solid var(--vms-info); }
|
|
605
|
-
|
|
606
|
-
.vms-list-item--
|
|
607
|
-
.vms-list-item--
|
|
608
|
-
.vms-list-item--
|
|
609
|
-
.vms-list-item--success:hover { border-left-color: var(--vms-success); }
|
|
610
|
-
.vms-list-item--info:hover { border-left-color: var(--vms-info); }
|
|
640
|
+
.vms-list-item--danger:hover { border-left-color: var(--vms-error); }
|
|
641
|
+
.vms-list-item--warning:hover { border-left-color: var(--vms-warning); }
|
|
642
|
+
.vms-list-item--success:hover { border-left-color: var(--vms-success); }
|
|
643
|
+
.vms-list-item--info:hover { border-left-color: var(--vms-info); }
|
|
611
644
|
|
|
612
645
|
/* Selection state for master-detail / list-detail patterns (D-27). The
|
|
613
646
|
currently-selected row. Stronger than the semantic hints (selection is
|
|
@@ -626,9 +659,13 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
626
659
|
.vms-text--body { line-height: 1.6; }
|
|
627
660
|
.vms-text--muted { color: var(--vms-text-muted); font-size: var(--vms-text-sm); }
|
|
628
661
|
.vms-text--strikethrough { color: var(--vms-done-text); text-decoration: line-through; }
|
|
629
|
-
.vms-text--error { color: var(--vms-error); font-size: var(--vms-text-base); }
|
|
630
|
-
.vms-text--warning { color: var(--vms-warning); font-size: var(--vms-text-base); }
|
|
631
662
|
.vms-text--pre { font-family: var(--vms-font-mono); white-space: pre; }
|
|
663
|
+
/* tone — semantic color axis (was the old style:"error"/"warning"). Placed after
|
|
664
|
+
the typography classes so a tone color wins over a style color (e.g. muted). */
|
|
665
|
+
.vms-text--danger { color: var(--vms-error); }
|
|
666
|
+
.vms-text--warning { color: var(--vms-warning); }
|
|
667
|
+
.vms-text--success { color: var(--vms-success); }
|
|
668
|
+
.vms-text--info { color: var(--vms-info); }
|
|
632
669
|
|
|
633
670
|
/* ── Link ── */
|
|
634
671
|
.vms-link {
|
|
@@ -642,6 +679,13 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
642
679
|
align-self: flex-start;
|
|
643
680
|
}
|
|
644
681
|
.vms-link:hover { border-bottom-color: var(--vms-accent); }
|
|
682
|
+
/* Active / current nav item ("you are here"). Solid underline + stronger
|
|
683
|
+
weight so the current location reads at a glance; server-owned via
|
|
684
|
+
LinkNode.active (emits aria-current="page" too). */
|
|
685
|
+
.vms-link--active {
|
|
686
|
+
border-bottom-color: var(--vms-accent);
|
|
687
|
+
font-weight: 600;
|
|
688
|
+
}
|
|
645
689
|
|
|
646
690
|
/* ── Image ── */
|
|
647
691
|
.vms-image { max-width: 100%; height: auto; display: block; }
|
|
@@ -719,9 +763,9 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
719
763
|
padding: var(--vms-space-sm) var(--vms-space-lg);
|
|
720
764
|
border-top: 1px solid var(--vms-border);
|
|
721
765
|
}
|
|
722
|
-
/* Buttons inside the footer ignore the
|
|
723
|
-
|
|
724
|
-
.vms-modal__footer .vms-button
|
|
766
|
+
/* Buttons inside the footer ignore the base align-self:flex-start so they sit
|
|
767
|
+
naturally on the right-aligned footer row (all emphasis/tone variants). */
|
|
768
|
+
.vms-modal__footer .vms-button { align-self: auto; }
|
|
725
769
|
|
|
726
770
|
/* ── Table ── */
|
|
727
771
|
.vms-table-wrapper {
|
|
@@ -768,16 +812,18 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
768
812
|
outline: 2px solid var(--vms-accent);
|
|
769
813
|
outline-offset: -2px;
|
|
770
814
|
}
|
|
771
|
-
/* Row
|
|
772
|
-
|
|
815
|
+
/* Row STATE (lifecycle) — done/disabled/running framework-styled; apps can add
|
|
816
|
+
more .vms-table__row--{state} rules. */
|
|
773
817
|
.vms-table__row--done { opacity: 0.6; }
|
|
774
818
|
.vms-table__row--disabled { opacity: 0.55; color: var(--vms-text-muted); }
|
|
775
819
|
.vms-table__row--disabled.vms-table__row--clickable { cursor: default; }
|
|
776
820
|
.vms-table__row--disabled.vms-table__row--clickable:hover { background: transparent; }
|
|
777
|
-
.vms-table__row--success { background: color-mix(in srgb, var(--vms-success) 8%, transparent); }
|
|
778
821
|
.vms-table__row--running { background: color-mix(in srgb, var(--vms-accent) 8%, transparent); }
|
|
822
|
+
/* Row TONE (severity) — the universal status axis (was the old critical/danger;
|
|
823
|
+
critical → danger). Tints derive from theme vars (color-mix), so a custom
|
|
824
|
+
:root theme recolors them automatically; no app CSS, no literals. */
|
|
825
|
+
.vms-table__row--success { background: color-mix(in srgb, var(--vms-success) 8%, transparent); }
|
|
779
826
|
.vms-table__row--warning { background: color-mix(in srgb, var(--vms-warning) 9%, transparent); }
|
|
780
|
-
.vms-table__row--critical,
|
|
781
827
|
.vms-table__row--danger { background: color-mix(in srgb, var(--vms-error) 9%, transparent); }
|
|
782
828
|
.vms-table__td { padding: var(--vms-space-sm) var(--vms-space-md); vertical-align: middle; }
|
|
783
829
|
.vms-table__link { color: var(--vms-accent); text-decoration: none; }
|