@ashley-shrok/viewmodel-shell 4.1.0 → 5.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/agent-skill.md +15 -6
- package/dist/browser.d.ts +18 -16
- package/dist/browser.js +147 -40
- package/dist/index.d.ts +40 -19
- package/dist/server.js +3 -0
- package/dist/tui.js +38 -15
- package/package.json +1 -1
- package/styles/default.css +41 -1
- package/styles/themes/dark-amber.css +9 -0
- package/styles/themes/dark-blue.css +9 -0
- package/styles/themes/dark-green.css +9 -0
- package/styles/themes/dark-purple.css +11 -0
- package/styles/themes/dark-rose.css +9 -0
- package/styles/themes/dark-teal.css +9 -0
- package/styles/themes/light-amber.css +9 -0
- package/styles/themes/light-blue.css +9 -0
- package/styles/themes/light-green.css +9 -0
- package/styles/themes/light-purple.css +9 -0
- package/styles/themes/light-rose.css +9 -0
- package/styles/themes/light-teal.css +9 -0
package/agent-skill.md
CHANGED
|
@@ -156,15 +156,24 @@ File uploads use the multipart form above. One form entry per file input, keyed
|
|
|
156
156
|
A `ChartNode` in the `vm` tree carries bounded, agent-legible declared data — it is read-only structured data like any other node, with no dispatch-bearing fields of its own:
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
|
-
{ "type": "chart", "
|
|
160
|
-
"
|
|
161
|
-
"
|
|
159
|
+
{ "type": "chart", "title": "Weekly visits",
|
|
160
|
+
"labels": [ "Mon", "Tue", "Wed" ],
|
|
161
|
+
"series": [
|
|
162
|
+
{ "name": "Visits", "data": [ 12, 19, 7 ] },
|
|
163
|
+
{ "name": "Errors", "data": [ 1, 3, 2 ], "tone": "danger" }
|
|
164
|
+
],
|
|
165
|
+
"stacked": true }
|
|
162
166
|
```
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
(`kind` is omitted above, so this renders as the default `"bar"`, grouped→stacked via `stacked:true`.)
|
|
169
|
+
|
|
170
|
+
- `kind` — optional, a closed union: `bar | line | area | pie | donut`. Omitted means `"bar"`. Widened additively over time — key off the value you see, never assume a fixed set.
|
|
171
|
+
- `labels` — the shared category (x-)axis. `labels[i]` is the category every series' `data[i]` refers to.
|
|
172
|
+
- `series` — one or more `{ name, data, tone? }` entries over the shared `labels`. `data[i]` aligns by index to `labels[i]`. A single-series chart is just one entry in this array — there is no separate "simple" shape. `name` is the series label (rendered in a browser legend); a wire-driving agent reads it to identify which series is which.
|
|
173
|
+
- `series[].tone` — optional (`danger|warning|success|info`), set PER SERIES. When present, that series is drawn in the theme's semantic color instead of the next categorical palette slot (a loss/error series tagged `danger`, for example). Omitted → the browser client assigns the next palette slot; this only affects rendered COLOR. A wire-driving agent with no renderer can simply read `series[].data`/`name`/`labels`/`title` directly and ignore `tone`.
|
|
174
|
+
- `stacked` — optional boolean, applies to `bar`/`area` only (ignored for `line`/`pie`/`donut`). Omitted/`false` means series are grouped side-by-side; `true` stacks them.
|
|
166
175
|
- `title` — optional chart title.
|
|
167
|
-
-
|
|
176
|
+
- **pie/donut note:** these kinds are single-series — only `series[0]` is rendered (per-slice palette), any additional entries are ignored by the renderer. Not shown in the example above.
|
|
168
177
|
|
|
169
178
|
There is nothing else to do to "drive" a chart over the wire.
|
|
170
179
|
|
package/dist/browser.d.ts
CHANGED
|
@@ -72,30 +72,32 @@ export declare class BrowserAdapter implements Adapter {
|
|
|
72
72
|
*/
|
|
73
73
|
private fits;
|
|
74
74
|
/**
|
|
75
|
-
* ChartNode (
|
|
76
|
-
* loaded as a PRIVATE, LAZY, OPTIONAL adapter
|
|
77
|
-
* `import("chart.js")` in loadChart() is reached
|
|
78
|
-
* and it registers
|
|
79
|
-
* renders no chart loads zero chart.js bytes
|
|
80
|
-
* no dependency).
|
|
81
|
-
*
|
|
75
|
+
* ChartNode (CHARTBASE-02/03) — the multi-series base set (bar/line/area/
|
|
76
|
+
* pie/donut) drawn by Chart.js, loaded as a PRIVATE, LAZY, OPTIONAL adapter
|
|
77
|
+
* dependency: the dynamic `import("chart.js")` in loadChart() is reached
|
|
78
|
+
* ONLY when a ChartNode renders, and it registers the base-set pieces so an
|
|
79
|
+
* app that renders no chart loads zero chart.js bytes (the core + .NET/bun
|
|
80
|
+
* backends gain no dependency). Every color is read via getComputedStyle
|
|
81
|
+
* from the `--vms-chart-1..8` categorical palette (18-02) or, when a series
|
|
82
|
+
* carries a `tone`, from the theme's tone token — NO raw CSS crosses the wire.
|
|
82
83
|
*
|
|
83
84
|
* The canvas + Chart instance are keyed by a stable per-render ordinal and kept
|
|
84
85
|
* in `chartInstances` ACROSS renders, so a re-render with changed data reuses
|
|
85
86
|
* the SAME canvas (detached, not destroyed, by render()'s innerHTML wipe — its
|
|
86
|
-
* 2D context + bitmap survive) and redraws IN PLACE via `.update()`
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
87
|
+
* 2D context + bitmap survive) and redraws IN PLACE via `.update()` rather than
|
|
88
|
+
* reconstructing. render() mark-sweeps + destroy()s any instance the new tree
|
|
89
|
+
* dropped (leak prevention). getComputedStyle / canvas / Chart.js live ONLY
|
|
90
|
+
* here in browser.ts — the core (index.ts) stays platform-agnostic.
|
|
90
91
|
*/
|
|
91
92
|
private chart;
|
|
92
93
|
/**
|
|
93
94
|
* Lazily import chart.js and construct the Chart for `key`. The dynamic import
|
|
94
|
-
* is what keeps chart.js zero-bytes-when-absent; registering
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
95
|
+
* is what keeps chart.js zero-bytes-when-absent; registering the base-set
|
|
96
|
+
* controllers/elements/scales/plugins (bar, line+fill, pie/doughnut, plus the
|
|
97
|
+
* shared category/linear scales, tooltip, and legend) covers every kind the
|
|
98
|
+
* widened chart() config can construct. Fire-and-forget from chart()
|
|
99
|
+
* (`void this.loadChart(...)`), so a missing dependency is surfaced through the
|
|
100
|
+
* fail-loud seam (chartFailLoud), NEVER a floating unhandled rejection.
|
|
99
101
|
*/
|
|
100
102
|
private loadChart;
|
|
101
103
|
/**
|
package/dist/browser.js
CHANGED
|
@@ -481,21 +481,22 @@ export class BrowserAdapter {
|
|
|
481
481
|
this.fitsObservers.push(ro);
|
|
482
482
|
}
|
|
483
483
|
/**
|
|
484
|
-
* ChartNode (
|
|
485
|
-
* loaded as a PRIVATE, LAZY, OPTIONAL adapter
|
|
486
|
-
* `import("chart.js")` in loadChart() is reached
|
|
487
|
-
* and it registers
|
|
488
|
-
* renders no chart loads zero chart.js bytes
|
|
489
|
-
* no dependency).
|
|
490
|
-
*
|
|
484
|
+
* ChartNode (CHARTBASE-02/03) — the multi-series base set (bar/line/area/
|
|
485
|
+
* pie/donut) drawn by Chart.js, loaded as a PRIVATE, LAZY, OPTIONAL adapter
|
|
486
|
+
* dependency: the dynamic `import("chart.js")` in loadChart() is reached
|
|
487
|
+
* ONLY when a ChartNode renders, and it registers the base-set pieces so an
|
|
488
|
+
* app that renders no chart loads zero chart.js bytes (the core + .NET/bun
|
|
489
|
+
* backends gain no dependency). Every color is read via getComputedStyle
|
|
490
|
+
* from the `--vms-chart-1..8` categorical palette (18-02) or, when a series
|
|
491
|
+
* carries a `tone`, from the theme's tone token — NO raw CSS crosses the wire.
|
|
491
492
|
*
|
|
492
493
|
* The canvas + Chart instance are keyed by a stable per-render ordinal and kept
|
|
493
494
|
* in `chartInstances` ACROSS renders, so a re-render with changed data reuses
|
|
494
495
|
* the SAME canvas (detached, not destroyed, by render()'s innerHTML wipe — its
|
|
495
|
-
* 2D context + bitmap survive) and redraws IN PLACE via `.update()`
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
496
|
+
* 2D context + bitmap survive) and redraws IN PLACE via `.update()` rather than
|
|
497
|
+
* reconstructing. render() mark-sweeps + destroy()s any instance the new tree
|
|
498
|
+
* dropped (leak prevention). getComputedStyle / canvas / Chart.js live ONLY
|
|
499
|
+
* here in browser.ts — the core (index.ts) stays platform-agnostic.
|
|
499
500
|
*/
|
|
500
501
|
chart(n, parent) {
|
|
501
502
|
// Stable key: title-derived base disambiguated by a per-render ordinal so
|
|
@@ -510,7 +511,8 @@ export class BrowserAdapter {
|
|
|
510
511
|
wrapper.className = "vms-chart";
|
|
511
512
|
parent.appendChild(wrapper);
|
|
512
513
|
// tone → theme token, NOT `--vms-${tone}`: `danger` maps to `--vms-error`
|
|
513
|
-
// (matching .vms-section--danger)
|
|
514
|
+
// (matching .vms-section--danger). Used ONLY when a series declares a
|
|
515
|
+
// `tone` — otherwise a series/slice gets the next categorical palette slot.
|
|
514
516
|
const toneToken = {
|
|
515
517
|
danger: "--vms-error",
|
|
516
518
|
warning: "--vms-warning",
|
|
@@ -518,37 +520,90 @@ export class BrowserAdapter {
|
|
|
518
520
|
info: "--vms-info",
|
|
519
521
|
};
|
|
520
522
|
const cs = getComputedStyle(this.container);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
//
|
|
523
|
+
// Categorical palette slot i (0-based) → --vms-chart-1..8, cycling. Falls
|
|
524
|
+
// back to --vms-accent (the pre-reshape safety net) when a consumer's
|
|
525
|
+
// custom theme (built via the sanctioned --vms-* override seam) predates
|
|
526
|
+
// this phase and doesn't define the chart tokens — every SHIPPED theme
|
|
527
|
+
// does, so this only matters for external reskins.
|
|
528
|
+
const paletteColor = (i) => cs.getPropertyValue(`--vms-chart-${(i % 8) + 1}`).trim() || cs.getPropertyValue("--vms-accent").trim();
|
|
529
|
+
// A series' resolved color: its tone token if set, else the next palette slot.
|
|
530
|
+
const seriesColor = (i, tone) => (tone && toneToken[tone]) ? cs.getPropertyValue(toneToken[tone]).trim() : paletteColor(i);
|
|
531
|
+
// Grid/tick/axis/text colors track the theme so the chart reads consistently
|
|
532
|
+
// in light AND dark. Chart.js's defaults are FIXED near-black — its grid is
|
|
533
|
+
// rgba(0,0,0,0.1) and its text (legend labels + title) is #666 — visible on a
|
|
534
|
+
// light background but low-contrast/~invisible on a dark one. So wire the grid
|
|
535
|
+
// + axis border to `--vms-border` (subtle in every theme), the tick labels
|
|
536
|
+
// (secondary) to `--vms-text-muted`, and the legend labels + title (the text
|
|
537
|
+
// that NAMES the series/chart — primary information) to the full-contrast
|
|
538
|
+
// `--vms-text` so they read prominently, not washed out.
|
|
528
539
|
const gridColor = cs.getPropertyValue("--vms-border").trim();
|
|
529
540
|
const tickColor = cs.getPropertyValue("--vms-text-muted").trim();
|
|
541
|
+
const textColor = cs.getPropertyValue("--vms-text").trim();
|
|
530
542
|
const scaleOpts = {
|
|
531
543
|
grid: { color: gridColor },
|
|
532
544
|
border: { color: gridColor },
|
|
533
545
|
ticks: { color: tickColor },
|
|
534
546
|
};
|
|
547
|
+
const kind = n.kind ?? "bar";
|
|
548
|
+
const isPie = kind === "pie" || kind === "donut";
|
|
549
|
+
let type;
|
|
550
|
+
let datasets;
|
|
551
|
+
if (isPie) {
|
|
552
|
+
// pie/donut are single-series (LOCKED design): render series[0] only,
|
|
553
|
+
// colored PER SLICE from the palette (tone is a per-series concept and
|
|
554
|
+
// doesn't apply to a per-slice pie). Extra series are lenient — one dev
|
|
555
|
+
// warning, series[0] still renders — never a crash. Gated to the FIRST
|
|
556
|
+
// render of this chart key (chartInstances doesn't have it yet) so a
|
|
557
|
+
// mis-shaped pie/donut in a polling view warns once, not once per poll.
|
|
558
|
+
if (n.series.length > 1 && !this.chartInstances.has(key)) {
|
|
559
|
+
console.warn(`[ViewModelShell] ChartNode kind "${kind}" renders a single series; ` +
|
|
560
|
+
`${n.series.length - 1} extra series ignored.`);
|
|
561
|
+
}
|
|
562
|
+
const primary = n.series[0];
|
|
563
|
+
type = kind === "donut" ? "doughnut" : "pie";
|
|
564
|
+
datasets = [{
|
|
565
|
+
data: primary ? primary.data : [],
|
|
566
|
+
backgroundColor: n.labels.map((_, j) => paletteColor(j)),
|
|
567
|
+
}];
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
// bar/line/area — one dataset per series, sharing the `labels` x-axis.
|
|
571
|
+
type = (kind === "line" || kind === "area") ? "line" : "bar";
|
|
572
|
+
datasets = n.series.map((s, i) => {
|
|
573
|
+
const color = seriesColor(i, s.tone);
|
|
574
|
+
const dataset = {
|
|
575
|
+
label: s.name,
|
|
576
|
+
data: s.data,
|
|
577
|
+
backgroundColor: color,
|
|
578
|
+
borderColor: color,
|
|
579
|
+
};
|
|
580
|
+
if (kind === "line" || kind === "area") {
|
|
581
|
+
// area = line + fill; the fill is token-derived (same resolved color
|
|
582
|
+
// as the stroke) — no raw color literal introduced for the fill.
|
|
583
|
+
dataset.fill = kind === "area";
|
|
584
|
+
}
|
|
585
|
+
return dataset;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
// `stacked` applies to bar/area only (LOCKED design); ignored for line/pie/donut.
|
|
589
|
+
const stacked = (kind === "bar" || kind === "area") && !!n.stacked;
|
|
590
|
+
// Legend: multi-series always, OR pie/donut (always multi-slice).
|
|
591
|
+
const legendDisplay = n.series.length > 1 || isPie;
|
|
535
592
|
const config = {
|
|
536
|
-
type
|
|
537
|
-
data: {
|
|
538
|
-
labels: n.points.map(p => p.label),
|
|
539
|
-
datasets: [{
|
|
540
|
-
data: n.points.map(p => p.value),
|
|
541
|
-
backgroundColor: color,
|
|
542
|
-
borderColor: color,
|
|
543
|
-
}],
|
|
544
|
-
},
|
|
593
|
+
type,
|
|
594
|
+
data: { labels: n.labels, datasets },
|
|
545
595
|
options: {
|
|
546
596
|
responsive: true,
|
|
547
597
|
maintainAspectRatio: false,
|
|
548
|
-
|
|
598
|
+
...(isPie ? {} : {
|
|
599
|
+
scales: {
|
|
600
|
+
x: { ...scaleOpts, stacked },
|
|
601
|
+
y: { ...scaleOpts, stacked },
|
|
602
|
+
},
|
|
603
|
+
}),
|
|
549
604
|
plugins: {
|
|
550
|
-
title: n.title ? { display: true, text: n.title } : { display: false },
|
|
551
|
-
legend: { display:
|
|
605
|
+
title: n.title ? { display: true, text: n.title, color: textColor } : { display: false },
|
|
606
|
+
legend: { display: legendDisplay, labels: { color: textColor } },
|
|
552
607
|
},
|
|
553
608
|
},
|
|
554
609
|
};
|
|
@@ -558,7 +613,7 @@ export class BrowserAdapter {
|
|
|
558
613
|
// destroyed) — its 2D context + drawn bitmap survive.
|
|
559
614
|
wrapper.appendChild(existing.canvas);
|
|
560
615
|
if (existing.chart) {
|
|
561
|
-
// Redraw in place (
|
|
616
|
+
// Redraw in place (CHARTBASE-03).
|
|
562
617
|
existing.chart.data = config.data;
|
|
563
618
|
existing.chart.options = config.options;
|
|
564
619
|
existing.chart.update();
|
|
@@ -578,11 +633,12 @@ export class BrowserAdapter {
|
|
|
578
633
|
}
|
|
579
634
|
/**
|
|
580
635
|
* Lazily import chart.js and construct the Chart for `key`. The dynamic import
|
|
581
|
-
* is what keeps chart.js zero-bytes-when-absent; registering
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
636
|
+
* is what keeps chart.js zero-bytes-when-absent; registering the base-set
|
|
637
|
+
* controllers/elements/scales/plugins (bar, line+fill, pie/doughnut, plus the
|
|
638
|
+
* shared category/linear scales, tooltip, and legend) covers every kind the
|
|
639
|
+
* widened chart() config can construct. Fire-and-forget from chart()
|
|
640
|
+
* (`void this.loadChart(...)`), so a missing dependency is surfaced through the
|
|
641
|
+
* fail-loud seam (chartFailLoud), NEVER a floating unhandled rejection.
|
|
586
642
|
*/
|
|
587
643
|
async loadChart(key, config) {
|
|
588
644
|
let mod;
|
|
@@ -594,9 +650,10 @@ export class BrowserAdapter {
|
|
|
594
650
|
"installed. Run: npm install chart.js");
|
|
595
651
|
return;
|
|
596
652
|
}
|
|
597
|
-
const { Chart, BarController, BarElement, CategoryScale, LinearScale, Tooltip } = mod;
|
|
598
|
-
//
|
|
599
|
-
|
|
653
|
+
const { Chart, BarController, BarElement, LineController, LineElement, PointElement, Filler, PieController, DoughnutController, ArcElement, CategoryScale, LinearScale, Tooltip, Legend, } = mod;
|
|
654
|
+
// Base-set registration — bar, line/area (+ Filler for the area fill),
|
|
655
|
+
// pie/donut (+ Arc element), the shared scales, tooltip, and the legend.
|
|
656
|
+
Chart.register(BarController, BarElement, LineController, LineElement, PointElement, Filler, PieController, DoughnutController, ArcElement, CategoryScale, LinearScale, Tooltip, Legend);
|
|
600
657
|
const entry = this.chartInstances.get(key);
|
|
601
658
|
// A later render may have mark-swept this key before the import resolved.
|
|
602
659
|
if (!entry)
|
|
@@ -1225,6 +1282,13 @@ export class BrowserAdapter {
|
|
|
1225
1282
|
// attribute was cleared out-of-band.)
|
|
1226
1283
|
if (n.disabled)
|
|
1227
1284
|
return;
|
|
1285
|
+
// confirm: a destructive-action guard. Show the NATIVE browser confirm
|
|
1286
|
+
// BEFORE any pendingLabel swap or dispatch; Cancel suppresses everything
|
|
1287
|
+
// (no dispatch, no visual change). Native by design — zero app/framework
|
|
1288
|
+
// state, and it's a client-only human affordance (an agent never reaches
|
|
1289
|
+
// this click handler; it dispatches the action directly over the wire).
|
|
1290
|
+
if (n.confirm && !window.confirm(n.confirm))
|
|
1291
|
+
return;
|
|
1228
1292
|
// pendingLabel: instant client-side feedback. Swap text + add
|
|
1229
1293
|
// .vms-button--pending BEFORE handing off to the dispatcher. On
|
|
1230
1294
|
// success the next render replaces the button entirely. On dispatch
|
|
@@ -1614,6 +1678,49 @@ export class BrowserAdapter {
|
|
|
1614
1678
|
const prevDisabled = pg.page <= 1 || pg.prevAction == null;
|
|
1615
1679
|
const nextDisabled = pg.page >= totalPages || pg.nextAction == null;
|
|
1616
1680
|
footer.appendChild(mkBtn("‹ Prev", pg.page - 1, pg.prevAction, prevDisabled));
|
|
1681
|
+
if (pg.jumpAction) {
|
|
1682
|
+
const jumpAction = pg.jumpAction;
|
|
1683
|
+
const jump = document.createElement("span");
|
|
1684
|
+
jump.className = "vms-table__pagination-jump";
|
|
1685
|
+
const label = document.createElement("span");
|
|
1686
|
+
label.className = "vms-table__pagination-jump-label";
|
|
1687
|
+
label.textContent = "Page";
|
|
1688
|
+
jump.appendChild(label);
|
|
1689
|
+
const input = document.createElement("input");
|
|
1690
|
+
input.type = "number";
|
|
1691
|
+
input.className = "vms-table__pagination-jump-input";
|
|
1692
|
+
input.min = "1";
|
|
1693
|
+
input.max = String(totalPages);
|
|
1694
|
+
input.inputMode = "numeric";
|
|
1695
|
+
input.setAttribute("aria-label", "Page number");
|
|
1696
|
+
input.value = String(pg.page);
|
|
1697
|
+
jump.appendChild(input);
|
|
1698
|
+
const ofLabel = document.createElement("span");
|
|
1699
|
+
ofLabel.className = "vms-table__pagination-jump-label";
|
|
1700
|
+
ofLabel.textContent = `of ${totalPages}`;
|
|
1701
|
+
jump.appendChild(ofLabel);
|
|
1702
|
+
const submitJump = () => {
|
|
1703
|
+
const parsed = Number.parseInt(input.value.trim(), 10);
|
|
1704
|
+
if (!Number.isFinite(parsed))
|
|
1705
|
+
return;
|
|
1706
|
+
const clamped = Math.min(Math.max(parsed, 1), totalPages);
|
|
1707
|
+
input.value = String(clamped);
|
|
1708
|
+
if (paginationBind != null)
|
|
1709
|
+
this.sa.write(paginationBind, clamped);
|
|
1710
|
+
on(jumpAction);
|
|
1711
|
+
};
|
|
1712
|
+
const goBtn = document.createElement("button");
|
|
1713
|
+
goBtn.type = "button";
|
|
1714
|
+
goBtn.className = "vms-button vms-button--secondary vms-table__pagination-btn";
|
|
1715
|
+
goBtn.textContent = "Go";
|
|
1716
|
+
goBtn.addEventListener("click", submitJump);
|
|
1717
|
+
input.addEventListener("keydown", (e) => {
|
|
1718
|
+
if (e.key === "Enter")
|
|
1719
|
+
submitJump();
|
|
1720
|
+
});
|
|
1721
|
+
jump.appendChild(goBtn);
|
|
1722
|
+
footer.appendChild(jump);
|
|
1723
|
+
}
|
|
1617
1724
|
footer.appendChild(mkBtn("Next ›", pg.page + 1, pg.nextAction, nextDisabled));
|
|
1618
1725
|
wrapper.appendChild(footer);
|
|
1619
1726
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -384,6 +384,18 @@ export interface ButtonNode {
|
|
|
384
384
|
* while in this state so the button visibly disables (cursor + opacity).
|
|
385
385
|
* Omitted = no pending feedback (existing instant-click behavior). */
|
|
386
386
|
pendingLabel?: string;
|
|
387
|
+
/** Optional confirmation question for a destructive/irreversible action
|
|
388
|
+
* (delete, reset, archive). When set, the BrowserAdapter shows a NATIVE
|
|
389
|
+
* browser confirm dialog with this message on click; the action dispatches
|
|
390
|
+
* only if the user accepts, and Cancel suppresses it entirely (no dispatch,
|
|
391
|
+
* no pendingLabel swap). Deliberately NATIVE, not a framework-drawn dialog:
|
|
392
|
+
* it adds ZERO app or framework state (no modal in the tree, nothing to
|
|
393
|
+
* round-trip or tear down) and its OS-native, deliberately-jarring look
|
|
394
|
+
* reinforces "this one is serious — stop." It is a CLIENT-ONLY human
|
|
395
|
+
* affordance: an agent dispatches the action directly over the wire and is
|
|
396
|
+
* never gated by it (the confirm exists only at browser render time). The
|
|
397
|
+
* TUI has no confirm() and dispatches as normal. Omitted = instant dispatch. */
|
|
398
|
+
confirm?: string;
|
|
387
399
|
}
|
|
388
400
|
export interface TextNode {
|
|
389
401
|
type: "text";
|
|
@@ -504,6 +516,11 @@ export interface TablePagination {
|
|
|
504
516
|
/** Dispatched on the next page-control click. Carries an action name only —
|
|
505
517
|
* the renderer writes the target page to TableNode.paginationBind before dispatch. */
|
|
506
518
|
nextAction?: ActionEvent;
|
|
519
|
+
/** Dispatched when the user submits a typed target page via the jump-to-page
|
|
520
|
+
* control's Go button or Enter key. The renderer clamps the typed value into
|
|
521
|
+
* [1, totalPages] before writing it to TableNode.paginationBind and dispatching —
|
|
522
|
+
* same mechanism as prevAction/nextAction. Omitted = no jump control renders. */
|
|
523
|
+
jumpAction?: ActionEvent;
|
|
507
524
|
}
|
|
508
525
|
export interface TableNode {
|
|
509
526
|
type: "table";
|
|
@@ -649,30 +666,34 @@ export interface FitsNode {
|
|
|
649
666
|
* guaranteed-fits fallback rendered when none fit. */
|
|
650
667
|
children: ViewNode[];
|
|
651
668
|
}
|
|
652
|
-
export interface
|
|
653
|
-
/**
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
|
|
669
|
+
export interface ChartSeries {
|
|
670
|
+
/** Series name — rendered in the legend and read by agents to identify the series. */
|
|
671
|
+
name: string;
|
|
672
|
+
/** Values aligned by index to the chart's `labels`: data[i] is the value at labels[i]. */
|
|
673
|
+
data: number[];
|
|
674
|
+
/** OPTIONAL semantic tone from the existing closed tone axis. When set, this series is
|
|
675
|
+
* drawn in the theme's tone token (danger→--vms-error, etc.) instead of the next
|
|
676
|
+
* categorical-palette slot. For MEANING (a loss series → danger), not decoration.
|
|
677
|
+
* Omitted → framework assigns the next --vms-chart-N slot. NO raw color crosses the wire. */
|
|
678
|
+
tone?: "danger" | "warning" | "success" | "info";
|
|
657
679
|
}
|
|
658
680
|
export interface ChartNode {
|
|
659
681
|
type: "chart";
|
|
660
|
-
/** Chart type. CLOSED union; OMITTED = "bar".
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
*
|
|
668
|
-
|
|
682
|
+
/** Chart type. CLOSED union; OMITTED = "bar". Widened additively later (e.g. scatter) —
|
|
683
|
+
* consumers/agents key off `kind`, never assume a fixed set. The renderer treats an
|
|
684
|
+
* absent `kind` as "bar". */
|
|
685
|
+
kind?: "bar" | "line" | "area" | "pie" | "donut";
|
|
686
|
+
/** Shared category axis. labels[i] is the category for every series' data[i]. */
|
|
687
|
+
labels: string[];
|
|
688
|
+
/** One or more series over the shared `labels`. Single-series charts are just one entry.
|
|
689
|
+
* Multi-series charts share ONE x-axis (labels) — this is the honest encoding of that
|
|
690
|
+
* shared axis, and the shape every charting library uses. */
|
|
691
|
+
series: ChartSeries[];
|
|
692
|
+
/** bar/area only: stack series instead of grouping side-by-side. Omitted/false = grouped
|
|
693
|
+
* (ignored for line/pie/donut). */
|
|
694
|
+
stacked?: boolean;
|
|
669
695
|
/** Optional chart title rendered above the plot. */
|
|
670
696
|
title?: string;
|
|
671
|
-
/** Optional appearance tone from the existing tone axis, mapped to the theme's
|
|
672
|
-
* --vms-* tone tokens (danger→--vms-error, warning→--vms-warning,
|
|
673
|
-
* success→--vms-success, info→--vms-info; omitted → --vms-accent). NO raw
|
|
674
|
-
* color/CSS crosses the wire (D3) — only the closed tone token. */
|
|
675
|
-
tone?: "danger" | "warning" | "success" | "info";
|
|
676
697
|
}
|
|
677
698
|
export interface ShellOptions {
|
|
678
699
|
endpoint: string;
|
package/dist/server.js
CHANGED
|
@@ -153,6 +153,9 @@ function collectActions(node, enclosingForm, out) {
|
|
|
153
153
|
if (table.pagination?.nextAction) {
|
|
154
154
|
recordAction(table.pagination.nextAction, enclosingForm, out);
|
|
155
155
|
}
|
|
156
|
+
if (table.pagination?.jumpAction) {
|
|
157
|
+
recordAction(table.pagination.jumpAction, enclosingForm, out);
|
|
158
|
+
}
|
|
156
159
|
for (const row of table.rows) {
|
|
157
160
|
if (row.actions) {
|
|
158
161
|
// row.actions is ViewNode[] — it can include bind-only nodes (e.g. a
|
package/dist/tui.js
CHANGED
|
@@ -1102,24 +1102,47 @@ function StatBarView({ node }) {
|
|
|
1102
1102
|
return (_jsx("box", { flexDirection: "row", gap: 1, children: node.stats.map((s, i) => (_jsxs("box", { flexDirection: "row", gap: 1, children: [i > 0 ? _jsx("text", { fg: "#555555", children: "\u2502" }) : null, _jsx("text", { fg: "#888888", children: s.label }), _jsx("text", { children: String(s.value) })] }, i))) }));
|
|
1103
1103
|
}
|
|
1104
1104
|
// ── chart ─────────────────────────────────────────────────────────────────
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
//
|
|
1108
|
-
//
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1105
|
+
// CHARTBASE-05 — DELIBERATE degradation for the reshaped multi-series
|
|
1106
|
+
// ChartNode { kind?; labels: string[]; series: ChartSeries[]; stacked?;
|
|
1107
|
+
// title? }. A terminal has no canvas, but a ChartNode is STRUCTURED data, so
|
|
1108
|
+
// it prints legibly: the title (if any) on its own line, then GROUPED BY
|
|
1109
|
+
// SERIES — each series' name as a sub-header, followed by one row per label
|
|
1110
|
+
// `<label padded> <value> <bar>` where <bar> is a run of "█" scaled to
|
|
1111
|
+
// value / the GLOBAL max across ALL series (so multi-series bars are
|
|
1112
|
+
// comparable to each other, not just within their own series). pie/donut are
|
|
1113
|
+
// single-series by design (CHARTBASE-03) — they degrade to printing
|
|
1114
|
+
// series[0]'s label/value slices (no bars; a "share of whole" reads oddly as
|
|
1115
|
+
// a bar anyway). Empty series / empty labels / an all-zero or non-positive
|
|
1116
|
+
// max render names/labels/values with no bars — every division is guarded by
|
|
1117
|
+
// `maxValue > 0`, and the resulting length is additionally clamped to >= 0
|
|
1118
|
+
// before `.repeat()`, since an individual value can be negative even while
|
|
1119
|
+
// the GLOBAL max (across all series) is positive (e.g. a "Net" series with
|
|
1120
|
+
// mixed-sign entries) — never throws. ChartNode is a LEAF (no children) → no
|
|
1113
1121
|
// container-walk arm is needed (mirrors StatBarView / ProgressView).
|
|
1114
1122
|
const CHART_BAR_WIDTH = 20;
|
|
1115
1123
|
function ChartView({ node }) {
|
|
1116
|
-
const
|
|
1117
|
-
const
|
|
1118
|
-
const labelWidth =
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1124
|
+
const labels = node.labels ?? [];
|
|
1125
|
+
const series = node.series ?? [];
|
|
1126
|
+
const labelWidth = labels.reduce((w, l) => Math.max(w, l.length), 0);
|
|
1127
|
+
const isPie = node.kind === "pie" || node.kind === "donut";
|
|
1128
|
+
if (isPie) {
|
|
1129
|
+
// Single-series degrade: series[0]'s slices as label/value rows.
|
|
1130
|
+
const slice = series[0];
|
|
1131
|
+
const data = slice?.data ?? [];
|
|
1132
|
+
return (_jsxs("box", { flexDirection: "column", children: [node.title ? _jsx("text", { attributes: 1 /* BOLD */, children: node.title }) : null, slice ? _jsx("text", { attributes: 1 /* BOLD */, fg: "#888888", children: slice.name }) : null, labels.map((label, i) => (_jsxs("box", { flexDirection: "row", gap: 1, children: [_jsx("text", { fg: "#888888", children: label.padEnd(labelWidth) }), _jsx("text", { children: String(data[i] ?? 0) })] }, i)))] }));
|
|
1133
|
+
}
|
|
1134
|
+
// bar/line/area: scale every series' bars against the GLOBAL max so
|
|
1135
|
+
// multiple series are visually comparable to one another.
|
|
1136
|
+
const allValues = series.flatMap((s) => s.data ?? []);
|
|
1137
|
+
const maxValue = allValues.length ? Math.max(0, ...allValues) : 0;
|
|
1138
|
+
return (_jsxs("box", { flexDirection: "column", children: [node.title ? _jsx("text", { attributes: 1 /* BOLD */, children: node.title }) : null, series.map((s, si) => (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { attributes: 1 /* BOLD */, children: s.name }), labels.map((label, li) => {
|
|
1139
|
+
const value = s.data?.[li] ?? 0;
|
|
1140
|
+
// Clamped to >= 0: `value` can be negative even while `maxValue`
|
|
1141
|
+
// (the global max across ALL series) is positive — an unclamped
|
|
1142
|
+
// negative length would throw on "█".repeat() below.
|
|
1143
|
+
const barLen = maxValue > 0 ? Math.max(0, Math.round((value / maxValue) * CHART_BAR_WIDTH)) : 0;
|
|
1144
|
+
return (_jsxs("box", { flexDirection: "row", gap: 1, children: [_jsx("text", { fg: "#888888", children: label.padEnd(labelWidth) }), _jsx("text", { children: String(value).padStart(4) }), _jsx("text", { fg: "#4a9eff", children: "█".repeat(barLen) })] }, li));
|
|
1145
|
+
})] }, si)))] }));
|
|
1123
1146
|
}
|
|
1124
1147
|
// ── modal ─────────────────────────────────────────────────────────────────
|
|
1125
1148
|
// Modals are PORTALED to app-root by App (see findModal + ModalOverlay).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.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",
|
package/styles/default.css
CHANGED
|
@@ -32,6 +32,22 @@
|
|
|
32
32
|
--vms-priority-high: #d76410;
|
|
33
33
|
--vms-success: #2da359;
|
|
34
34
|
--vms-info: #2277dd;
|
|
35
|
+
/* Chart palette (CHARTBASE-02) — 8-slot categorical palette the browser
|
|
36
|
+
adapter cycles per series (per slice for pie/donut). Framework-owned,
|
|
37
|
+
theme-token-driven: zero raw color crosses the wire. Every theme file
|
|
38
|
+
re-tunes these 8 for its own surface/bg (light themes get this set;
|
|
39
|
+
dark themes get a lighter/more-saturated set — see styles/themes/*.css).
|
|
40
|
+
Hand-checked (not CI-gated — check:aa-contrast covers a fixed pair-set
|
|
41
|
+
by design) for ~3:1 non-text contrast against --vms-surface/--vms-bg
|
|
42
|
+
and mutual distinguishability; see 18-02-SUMMARY.md for the ratio table. */
|
|
43
|
+
--vms-chart-1: #3366cc;
|
|
44
|
+
--vms-chart-2: #cc6b1c;
|
|
45
|
+
--vms-chart-3: #1f8a5f;
|
|
46
|
+
--vms-chart-4: #b3314d;
|
|
47
|
+
--vms-chart-5: #7a4fc9;
|
|
48
|
+
--vms-chart-6: #1f8fa3;
|
|
49
|
+
--vms-chart-7: #767a1f;
|
|
50
|
+
--vms-chart-8: #c23fa0;
|
|
35
51
|
--vms-radius: 10px;
|
|
36
52
|
--vms-radius-sm: 6px;
|
|
37
53
|
--vms-image-small: 4rem;
|
|
@@ -963,9 +979,13 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
963
979
|
hairline. Buttons reuse .vms-button styling; only sizing/disabled is added. */
|
|
964
980
|
.vms-table__pagination {
|
|
965
981
|
display: flex;
|
|
982
|
+
flex-wrap: wrap;
|
|
966
983
|
align-items: center;
|
|
967
984
|
gap: var(--vms-space-sm);
|
|
968
|
-
padding
|
|
985
|
+
/* Inset to match the cell padding (space-md horizontal) so the footer content
|
|
986
|
+
aligns with the table cells above it, with breathing room below; the
|
|
987
|
+
border-top hairline still spans the full width. */
|
|
988
|
+
padding: var(--vms-space-sm) var(--vms-space-md);
|
|
969
989
|
margin-top: var(--vms-space-xs);
|
|
970
990
|
border-top: 1px solid var(--vms-border);
|
|
971
991
|
}
|
|
@@ -973,6 +993,26 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
973
993
|
.vms-table__pagination-btn { padding: var(--vms-space-xs) var(--vms-space-sm); font-size: var(--vms-text-sm); }
|
|
974
994
|
.vms-table__pagination-btn:disabled { opacity: 0.45; cursor: default; }
|
|
975
995
|
|
|
996
|
+
/* Jump-to-page control — "Page [ N ] of TOTAL Go", inserted between Prev/Next. */
|
|
997
|
+
.vms-table__pagination-jump { display: flex; align-items: center; gap: var(--vms-space-xs); }
|
|
998
|
+
.vms-table__pagination-jump-label { color: var(--vms-text-muted); font-size: var(--vms-text-sm); white-space: nowrap; }
|
|
999
|
+
.vms-table__pagination-jump-input {
|
|
1000
|
+
width: 3.5em;
|
|
1001
|
+
text-align: center;
|
|
1002
|
+
background: var(--vms-surface);
|
|
1003
|
+
border: 1px solid var(--vms-border);
|
|
1004
|
+
border-radius: var(--vms-radius);
|
|
1005
|
+
color: var(--vms-text);
|
|
1006
|
+
font-family: var(--vms-font-body);
|
|
1007
|
+
font-size: var(--vms-text-sm);
|
|
1008
|
+
padding: var(--vms-space-xs);
|
|
1009
|
+
color-scheme: var(--vms-color-scheme);
|
|
1010
|
+
}
|
|
1011
|
+
.vms-table__pagination-jump-input:focus {
|
|
1012
|
+
border-color: var(--vms-accent);
|
|
1013
|
+
outline: none;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
976
1016
|
/* 0.16.0 — `.vms-busy` is applied by BrowserAdapter when the shell is in a
|
|
977
1017
|
busy state (a user-initiated dispatch is in flight OR the server returned
|
|
978
1018
|
ShellResponse.busy: true). cursor: wait communicates the state; pointer-
|
|
@@ -23,5 +23,14 @@
|
|
|
23
23
|
--vms-priority-high: #e07015;
|
|
24
24
|
--vms-success: #4dd17a;
|
|
25
25
|
--vms-info: #4a9eff;
|
|
26
|
+
/* Chart palette (CHARTBASE-02) — same dark-family palette as dark-purple.css. */
|
|
27
|
+
--vms-chart-1: #6fa8ff;
|
|
28
|
+
--vms-chart-2: #ffa552;
|
|
29
|
+
--vms-chart-3: #5ed19a;
|
|
30
|
+
--vms-chart-4: #ff7a90;
|
|
31
|
+
--vms-chart-5: #b79bfa;
|
|
32
|
+
--vms-chart-6: #5cd6e8;
|
|
33
|
+
--vms-chart-7: #d0d661;
|
|
34
|
+
--vms-chart-8: #ef8fd6;
|
|
26
35
|
--vms-color-scheme: dark;
|
|
27
36
|
}
|
|
@@ -23,5 +23,14 @@
|
|
|
23
23
|
--vms-priority-high: #e07015;
|
|
24
24
|
--vms-success: #4dd17a;
|
|
25
25
|
--vms-info: #4a9eff;
|
|
26
|
+
/* Chart palette (CHARTBASE-02) — same dark-family palette as dark-purple.css. */
|
|
27
|
+
--vms-chart-1: #6fa8ff;
|
|
28
|
+
--vms-chart-2: #ffa552;
|
|
29
|
+
--vms-chart-3: #5ed19a;
|
|
30
|
+
--vms-chart-4: #ff7a90;
|
|
31
|
+
--vms-chart-5: #b79bfa;
|
|
32
|
+
--vms-chart-6: #5cd6e8;
|
|
33
|
+
--vms-chart-7: #d0d661;
|
|
34
|
+
--vms-chart-8: #ef8fd6;
|
|
26
35
|
--vms-color-scheme: dark;
|
|
27
36
|
}
|
|
@@ -23,5 +23,14 @@
|
|
|
23
23
|
--vms-priority-high: #e07015;
|
|
24
24
|
--vms-success: #4dd17a;
|
|
25
25
|
--vms-info: #4a9eff;
|
|
26
|
+
/* Chart palette (CHARTBASE-02) — same dark-family palette as dark-purple.css. */
|
|
27
|
+
--vms-chart-1: #6fa8ff;
|
|
28
|
+
--vms-chart-2: #ffa552;
|
|
29
|
+
--vms-chart-3: #5ed19a;
|
|
30
|
+
--vms-chart-4: #ff7a90;
|
|
31
|
+
--vms-chart-5: #b79bfa;
|
|
32
|
+
--vms-chart-6: #5cd6e8;
|
|
33
|
+
--vms-chart-7: #d0d661;
|
|
34
|
+
--vms-chart-8: #ef8fd6;
|
|
26
35
|
--vms-color-scheme: dark;
|
|
27
36
|
}
|
|
@@ -20,5 +20,16 @@
|
|
|
20
20
|
--vms-priority-high: #e07015;
|
|
21
21
|
--vms-success: #4dd17a;
|
|
22
22
|
--vms-info: #4a9eff;
|
|
23
|
+
/* Chart palette (CHARTBASE-02) — added on top of the byte-exact prior-default
|
|
24
|
+
capture (D-02); check-theme-byte-identity.mjs allows exactly this 8-token
|
|
25
|
+
addition alongside the frozen color block, see its comments. */
|
|
26
|
+
--vms-chart-1: #6fa8ff;
|
|
27
|
+
--vms-chart-2: #ffa552;
|
|
28
|
+
--vms-chart-3: #5ed19a;
|
|
29
|
+
--vms-chart-4: #ff7a90;
|
|
30
|
+
--vms-chart-5: #b79bfa;
|
|
31
|
+
--vms-chart-6: #5cd6e8;
|
|
32
|
+
--vms-chart-7: #d0d661;
|
|
33
|
+
--vms-chart-8: #ef8fd6;
|
|
23
34
|
--vms-color-scheme: dark;
|
|
24
35
|
}
|
|
@@ -23,5 +23,14 @@
|
|
|
23
23
|
--vms-priority-high: #e07015;
|
|
24
24
|
--vms-success: #4dd17a;
|
|
25
25
|
--vms-info: #4a9eff;
|
|
26
|
+
/* Chart palette (CHARTBASE-02) — same dark-family palette as dark-purple.css. */
|
|
27
|
+
--vms-chart-1: #6fa8ff;
|
|
28
|
+
--vms-chart-2: #ffa552;
|
|
29
|
+
--vms-chart-3: #5ed19a;
|
|
30
|
+
--vms-chart-4: #ff7a90;
|
|
31
|
+
--vms-chart-5: #b79bfa;
|
|
32
|
+
--vms-chart-6: #5cd6e8;
|
|
33
|
+
--vms-chart-7: #d0d661;
|
|
34
|
+
--vms-chart-8: #ef8fd6;
|
|
26
35
|
--vms-color-scheme: dark;
|
|
27
36
|
}
|
|
@@ -23,5 +23,14 @@
|
|
|
23
23
|
--vms-priority-high: #e07015;
|
|
24
24
|
--vms-success: #4dd17a;
|
|
25
25
|
--vms-info: #4a9eff;
|
|
26
|
+
/* Chart palette (CHARTBASE-02) — same dark-family palette as dark-purple.css. */
|
|
27
|
+
--vms-chart-1: #6fa8ff;
|
|
28
|
+
--vms-chart-2: #ffa552;
|
|
29
|
+
--vms-chart-3: #5ed19a;
|
|
30
|
+
--vms-chart-4: #ff7a90;
|
|
31
|
+
--vms-chart-5: #b79bfa;
|
|
32
|
+
--vms-chart-6: #5cd6e8;
|
|
33
|
+
--vms-chart-7: #d0d661;
|
|
34
|
+
--vms-chart-8: #ef8fd6;
|
|
26
35
|
--vms-color-scheme: dark;
|
|
27
36
|
}
|
|
@@ -17,5 +17,14 @@
|
|
|
17
17
|
--vms-priority-high:#d76410;
|
|
18
18
|
--vms-success: #2da359;
|
|
19
19
|
--vms-info: #2277dd;
|
|
20
|
+
/* Chart palette (CHARTBASE-02) — same light-family palette as default.css. */
|
|
21
|
+
--vms-chart-1: #3366cc;
|
|
22
|
+
--vms-chart-2: #cc6b1c;
|
|
23
|
+
--vms-chart-3: #1f8a5f;
|
|
24
|
+
--vms-chart-4: #b3314d;
|
|
25
|
+
--vms-chart-5: #7a4fc9;
|
|
26
|
+
--vms-chart-6: #1f8fa3;
|
|
27
|
+
--vms-chart-7: #767a1f;
|
|
28
|
+
--vms-chart-8: #c23fa0;
|
|
20
29
|
--vms-color-scheme: light;
|
|
21
30
|
}
|
|
@@ -17,5 +17,14 @@
|
|
|
17
17
|
--vms-priority-high:#d76410;
|
|
18
18
|
--vms-success: #2da359;
|
|
19
19
|
--vms-info: #2277dd;
|
|
20
|
+
/* Chart palette (CHARTBASE-02) — same light-family palette as default.css. */
|
|
21
|
+
--vms-chart-1: #3366cc;
|
|
22
|
+
--vms-chart-2: #cc6b1c;
|
|
23
|
+
--vms-chart-3: #1f8a5f;
|
|
24
|
+
--vms-chart-4: #b3314d;
|
|
25
|
+
--vms-chart-5: #7a4fc9;
|
|
26
|
+
--vms-chart-6: #1f8fa3;
|
|
27
|
+
--vms-chart-7: #767a1f;
|
|
28
|
+
--vms-chart-8: #c23fa0;
|
|
20
29
|
--vms-color-scheme: light;
|
|
21
30
|
}
|
|
@@ -17,5 +17,14 @@
|
|
|
17
17
|
--vms-priority-high:#d76410;
|
|
18
18
|
--vms-success: #2da359;
|
|
19
19
|
--vms-info: #2277dd;
|
|
20
|
+
/* Chart palette (CHARTBASE-02) — same light-family palette as default.css. */
|
|
21
|
+
--vms-chart-1: #3366cc;
|
|
22
|
+
--vms-chart-2: #cc6b1c;
|
|
23
|
+
--vms-chart-3: #1f8a5f;
|
|
24
|
+
--vms-chart-4: #b3314d;
|
|
25
|
+
--vms-chart-5: #7a4fc9;
|
|
26
|
+
--vms-chart-6: #1f8fa3;
|
|
27
|
+
--vms-chart-7: #767a1f;
|
|
28
|
+
--vms-chart-8: #c23fa0;
|
|
20
29
|
--vms-color-scheme: light;
|
|
21
30
|
}
|
|
@@ -20,5 +20,14 @@
|
|
|
20
20
|
--vms-priority-high:#d76410;
|
|
21
21
|
--vms-success: #2da359;
|
|
22
22
|
--vms-info: #2277dd;
|
|
23
|
+
/* Chart palette (CHARTBASE-02) — same light-family palette as default.css. */
|
|
24
|
+
--vms-chart-1: #3366cc;
|
|
25
|
+
--vms-chart-2: #cc6b1c;
|
|
26
|
+
--vms-chart-3: #1f8a5f;
|
|
27
|
+
--vms-chart-4: #b3314d;
|
|
28
|
+
--vms-chart-5: #7a4fc9;
|
|
29
|
+
--vms-chart-6: #1f8fa3;
|
|
30
|
+
--vms-chart-7: #767a1f;
|
|
31
|
+
--vms-chart-8: #c23fa0;
|
|
23
32
|
--vms-color-scheme: light;
|
|
24
33
|
}
|
|
@@ -17,5 +17,14 @@
|
|
|
17
17
|
--vms-priority-high:#d76410;
|
|
18
18
|
--vms-success: #2da359;
|
|
19
19
|
--vms-info: #2277dd;
|
|
20
|
+
/* Chart palette (CHARTBASE-02) — same light-family palette as default.css. */
|
|
21
|
+
--vms-chart-1: #3366cc;
|
|
22
|
+
--vms-chart-2: #cc6b1c;
|
|
23
|
+
--vms-chart-3: #1f8a5f;
|
|
24
|
+
--vms-chart-4: #b3314d;
|
|
25
|
+
--vms-chart-5: #7a4fc9;
|
|
26
|
+
--vms-chart-6: #1f8fa3;
|
|
27
|
+
--vms-chart-7: #767a1f;
|
|
28
|
+
--vms-chart-8: #c23fa0;
|
|
20
29
|
--vms-color-scheme: light;
|
|
21
30
|
}
|
|
@@ -17,5 +17,14 @@
|
|
|
17
17
|
--vms-priority-high:#d76410;
|
|
18
18
|
--vms-success: #2da359;
|
|
19
19
|
--vms-info: #2277dd;
|
|
20
|
+
/* Chart palette (CHARTBASE-02) — same light-family palette as default.css. */
|
|
21
|
+
--vms-chart-1: #3366cc;
|
|
22
|
+
--vms-chart-2: #cc6b1c;
|
|
23
|
+
--vms-chart-3: #1f8a5f;
|
|
24
|
+
--vms-chart-4: #b3314d;
|
|
25
|
+
--vms-chart-5: #7a4fc9;
|
|
26
|
+
--vms-chart-6: #1f8fa3;
|
|
27
|
+
--vms-chart-7: #767a1f;
|
|
28
|
+
--vms-chart-8: #c23fa0;
|
|
20
29
|
--vms-color-scheme: light;
|
|
21
30
|
}
|