@aiaiai-pt/design-system 0.36.0 → 0.38.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/components/EChart.svelte +78 -4
- package/components/renderer/DonutChartWidget.svelte +11 -0
- package/components/renderer/DonutChartWidget.svelte.d.ts +4 -0
- package/components/renderer/EChartWidget.svelte +7 -1
- package/components/renderer/LineChartWidget.svelte +12 -0
- package/components/renderer/LineChartWidget.svelte.d.ts +4 -0
- package/components/renderer/grid.d.ts +26 -0
- package/components/renderer/grid.ts +57 -0
- package/components/renderer/registry.ts +7 -0
- package/components/renderer/types.ts +28 -0
- package/package.json +1 -1
package/components/EChart.svelte
CHANGED
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
valueHeader = "Value",
|
|
55
55
|
/** @type {string} BCP-47 locale for number formatting. */
|
|
56
56
|
locale = "en",
|
|
57
|
+
/** @type {"bar" | "line" | "donut"} Chart kind (#176 Tier 1). `bar` is the
|
|
58
|
+
* horizontal categorical bar; `line` plots the same series as a value line
|
|
59
|
+
* over the category axis; `donut` is a ring pie. The data table below is
|
|
60
|
+
* the accessible source of truth for every kind. */
|
|
61
|
+
kind = "bar",
|
|
57
62
|
/** @type {string} CSS block-size for the chart canvas. */
|
|
58
63
|
height = "20rem",
|
|
59
64
|
/** @type {string} */
|
|
@@ -90,9 +95,73 @@
|
|
|
90
95
|
const textSecondary = token("--color-text-secondary", "#6b7280");
|
|
91
96
|
const border = token("--color-border", "#e5e7eb");
|
|
92
97
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
98
|
+
// Donut: a ring pie. No axes; the slice palette derives from the accent
|
|
99
|
+
// (alpha ramp) so it stays on-theme without a hardcoded colour list.
|
|
100
|
+
if (kind === "donut") {
|
|
101
|
+
const palette = items.map((_, i) => {
|
|
102
|
+
const alpha = Math.max(0.35, 1 - i * 0.12);
|
|
103
|
+
return `color-mix(in srgb, ${accent} ${Math.round(alpha * 100)}%, transparent)`;
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
animation: false,
|
|
107
|
+
tooltip: {
|
|
108
|
+
trigger: "item",
|
|
109
|
+
/** @param {{ name: string, value: number, percent: number }} p */
|
|
110
|
+
formatter: (p) => `${p.name}: ${fmt(p.value)} (${p.percent}%)`,
|
|
111
|
+
},
|
|
112
|
+
color: palette,
|
|
113
|
+
series: [
|
|
114
|
+
{
|
|
115
|
+
type: "pie",
|
|
116
|
+
radius: ["45%", "72%"],
|
|
117
|
+
data: items.map((it) => ({ name: it.label, value: it.value })),
|
|
118
|
+
label: { color: textPrimary },
|
|
119
|
+
labelLine: { lineStyle: { color: border } },
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Line: value over the category axis, in the consumer's order (desc by
|
|
126
|
+
// value). Shares the same accent + token theming as the bar.
|
|
127
|
+
if (kind === "line") {
|
|
128
|
+
return {
|
|
129
|
+
animation: false,
|
|
130
|
+
grid: { left: 8, right: 16, top: 8, bottom: 8, containLabel: true },
|
|
131
|
+
tooltip: {
|
|
132
|
+
trigger: "axis",
|
|
133
|
+
/** @param {{ name: string, value: number }[]} p */
|
|
134
|
+
formatter: (p) => `${p[0]?.name}: ${fmt(p[0]?.value)}`,
|
|
135
|
+
},
|
|
136
|
+
xAxis: {
|
|
137
|
+
type: "category",
|
|
138
|
+
data: items.map((it) => it.label),
|
|
139
|
+
axisLabel: { color: textPrimary },
|
|
140
|
+
axisLine: { lineStyle: { color: border } },
|
|
141
|
+
axisTick: { show: false },
|
|
142
|
+
},
|
|
143
|
+
yAxis: {
|
|
144
|
+
type: "value",
|
|
145
|
+
axisLabel: { color: textSecondary, formatter: (/** @type {number} */ v) => fmt(v) },
|
|
146
|
+
axisLine: { lineStyle: { color: border } },
|
|
147
|
+
splitLine: { lineStyle: { color: border } },
|
|
148
|
+
},
|
|
149
|
+
series: [
|
|
150
|
+
{
|
|
151
|
+
type: "line",
|
|
152
|
+
data: items.map((it) => it.value),
|
|
153
|
+
itemStyle: { color: accent },
|
|
154
|
+
lineStyle: { color: accent },
|
|
155
|
+
symbolSize: 7,
|
|
156
|
+
smooth: false,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Bar (default). ECharts plots the category axis bottom-up; reverse so the
|
|
163
|
+
// highest value sits at the top (matching ResultsChart's top-down order).
|
|
164
|
+
// `items` is already sorted desc by the consumer.
|
|
96
165
|
const ordered = [...items].reverse();
|
|
97
166
|
|
|
98
167
|
return {
|
|
@@ -150,6 +219,8 @@
|
|
|
150
219
|
|
|
151
220
|
core.use([
|
|
152
221
|
charts.BarChart,
|
|
222
|
+
charts.LineChart,
|
|
223
|
+
charts.PieChart,
|
|
153
224
|
components.GridComponent,
|
|
154
225
|
components.TooltipComponent,
|
|
155
226
|
renderers.CanvasRenderer,
|
|
@@ -187,7 +258,10 @@
|
|
|
187
258
|
// touch reactive deps so the effect re-runs when they change
|
|
188
259
|
void items;
|
|
189
260
|
void locale;
|
|
190
|
-
|
|
261
|
+
void kind;
|
|
262
|
+
// A kind switch changes which axes/series exist; replace (not merge) the
|
|
263
|
+
// option so stale axis components from the previous kind are cleared.
|
|
264
|
+
if (_chart) _chart.setOption(buildOption(), true);
|
|
191
265
|
});
|
|
192
266
|
</script>
|
|
193
267
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import EChartWidget from "./EChartWidget.svelte";
|
|
3
|
+
import type { WidgetProps } from "./types";
|
|
4
|
+
|
|
5
|
+
// `donut-chart` registry key (#176 Tier 1). Thin wrapper binding the ECharts
|
|
6
|
+
// `kind` to a ring pie — the registry maps this key here. Same aggregate data
|
|
7
|
+
// path + a11y data table as the bar/line.
|
|
8
|
+
let p: WidgetProps = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<EChartWidget {...p} kind="donut" />
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
// is never the only encoding). Same data path as ResultsChartWidget: reads
|
|
12
12
|
// the `{ items }` rows and projects `{ label, value }` via `toRankedItems`,
|
|
13
13
|
// so NO resolve-data / provider change is needed. Soft-empty on no rows.
|
|
14
|
-
|
|
14
|
+
// `kind` is bound by the registry wrapper (line-chart / donut-chart), NOT by
|
|
15
|
+
// the dispatcher (which renders this widget with the standard WidgetProps and
|
|
16
|
+
// no kind → the `chart` key stays the ECharts bar). #176 Tier 1.
|
|
17
|
+
let { data, props, ownsH1, locale, kind = "bar" }: WidgetProps & {
|
|
18
|
+
kind?: "bar" | "line" | "donut";
|
|
19
|
+
} = $props();
|
|
15
20
|
|
|
16
21
|
const rows = $derived(
|
|
17
22
|
data && typeof data === "object" && Array.isArray((data as { items?: unknown }).items)
|
|
@@ -63,6 +68,7 @@
|
|
|
63
68
|
{caption}
|
|
64
69
|
{labelHeader}
|
|
65
70
|
{valueHeader}
|
|
71
|
+
{kind}
|
|
66
72
|
locale={locale ?? "en"}
|
|
67
73
|
/>
|
|
68
74
|
</section>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import EChartWidget from "./EChartWidget.svelte";
|
|
3
|
+
import type { WidgetProps } from "./types";
|
|
4
|
+
|
|
5
|
+
// `line-chart` registry key (#176 Tier 1). Thin wrapper that binds the ECharts
|
|
6
|
+
// `kind` the dispatcher can't pass — the registry maps this key to this
|
|
7
|
+
// component, which renders the shared EChart widget as a line. Same aggregate
|
|
8
|
+
// data path + a11y data table as the bar.
|
|
9
|
+
let p: WidgetProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<EChartWidget {...p} kind="line" />
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid layout helpers (#176 Tier 1 / ADR 0003).
|
|
3
|
+
*
|
|
4
|
+
* A page is still a flat `Block[]`; a block may carry an optional `layout`
|
|
5
|
+
* placing it on a 12-column grid. These pure helpers turn that descriptor field
|
|
6
|
+
* into CSS, shared by BOTH host apps (admin dashboard + portal page) so the
|
|
7
|
+
* arrangement is identical and DRY. Presentation-only: no editing concern lives
|
|
8
|
+
* here — the editor merely writes the same `layout` coords the renderer reads.
|
|
9
|
+
*
|
|
10
|
+
* Back-compatible: a block without `layout` falls back to source order, and a
|
|
11
|
+
* page with NO laid-out blocks renders as the shipped stacked list.
|
|
12
|
+
*/
|
|
13
|
+
import type { Block, BlockLayout } from "./types";
|
|
14
|
+
export declare const GRID_COLUMNS = 12;
|
|
15
|
+
/** Clamp a layout to valid 12-col track bounds (defensive against operator /
|
|
16
|
+
* drag input). `w` is 1..12; `x` keeps the span on-track; `y`/`h` are >=0/>=1. */
|
|
17
|
+
export declare function normalizeLayout(layout: BlockLayout): BlockLayout;
|
|
18
|
+
/**
|
|
19
|
+
* CSS `grid-column` / `grid-row` placement for a block's layout, or `null` when
|
|
20
|
+
* the block has none (the caller renders it in source order). Grid lines are
|
|
21
|
+
* 1-based, so a 0-based `x`/`y` maps to line `x+1`/`y+1`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function gridStyleFor(block: Pick<Block, "layout">): string | null;
|
|
24
|
+
/** True when ANY block carries a `layout` — the page should render as a grid
|
|
25
|
+
* (12-col track) rather than the stacked list. */
|
|
26
|
+
export declare function hasGridLayout(blocks: ReadonlyArray<Pick<Block, "layout">>): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid layout helpers (#176 Tier 1 / ADR 0003).
|
|
3
|
+
*
|
|
4
|
+
* A page is still a flat `Block[]`; a block may carry an optional `layout`
|
|
5
|
+
* placing it on a 12-column grid. These pure helpers turn that descriptor field
|
|
6
|
+
* into CSS, shared by BOTH host apps (admin dashboard + portal page) so the
|
|
7
|
+
* arrangement is identical and DRY. Presentation-only: no editing concern lives
|
|
8
|
+
* here — the editor merely writes the same `layout` coords the renderer reads.
|
|
9
|
+
*
|
|
10
|
+
* Back-compatible: a block without `layout` falls back to source order, and a
|
|
11
|
+
* page with NO laid-out blocks renders as the shipped stacked list.
|
|
12
|
+
*/
|
|
13
|
+
import type { Block, BlockLayout } from "./types";
|
|
14
|
+
|
|
15
|
+
export const GRID_COLUMNS = 12;
|
|
16
|
+
|
|
17
|
+
/** Clamp a layout to valid 12-col track bounds (defensive against operator /
|
|
18
|
+
* drag input). `w` is 1..12; `x` keeps the span on-track; `y`/`h` are >=0/>=1. */
|
|
19
|
+
export function normalizeLayout(layout: BlockLayout): BlockLayout {
|
|
20
|
+
const w = clampInt(layout.w, 1, GRID_COLUMNS, 1);
|
|
21
|
+
const x = clampInt(layout.x, 0, GRID_COLUMNS - w, 0);
|
|
22
|
+
const h = clampInt(layout.h, 1, Number.MAX_SAFE_INTEGER, 1);
|
|
23
|
+
const y = clampInt(layout.y, 0, Number.MAX_SAFE_INTEGER, 0);
|
|
24
|
+
return { x, y, w, h };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* CSS `grid-column` / `grid-row` placement for a block's layout, or `null` when
|
|
29
|
+
* the block has none (the caller renders it in source order). Grid lines are
|
|
30
|
+
* 1-based, so a 0-based `x`/`y` maps to line `x+1`/`y+1`.
|
|
31
|
+
*/
|
|
32
|
+
export function gridStyleFor(block: Pick<Block, "layout">): string | null {
|
|
33
|
+
if (!block.layout) return null;
|
|
34
|
+
const { x, y, w, h } = normalizeLayout(block.layout);
|
|
35
|
+
return `grid-column:${x + 1} / span ${w};grid-row:${y + 1} / span ${h};`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** True when ANY block carries a `layout` — the page should render as a grid
|
|
39
|
+
* (12-col track) rather than the stacked list. */
|
|
40
|
+
export function hasGridLayout(
|
|
41
|
+
blocks: ReadonlyArray<Pick<Block, "layout">>,
|
|
42
|
+
): boolean {
|
|
43
|
+
return blocks.some((b) => b.layout != null);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clampInt(
|
|
47
|
+
value: unknown,
|
|
48
|
+
min: number,
|
|
49
|
+
max: number,
|
|
50
|
+
fallback: number,
|
|
51
|
+
): number {
|
|
52
|
+
const n =
|
|
53
|
+
typeof value === "number" && Number.isFinite(value)
|
|
54
|
+
? Math.round(value)
|
|
55
|
+
: fallback;
|
|
56
|
+
return Math.max(min, Math.min(max, n));
|
|
57
|
+
}
|
|
@@ -22,7 +22,9 @@ import {
|
|
|
22
22
|
} from "./dispatch";
|
|
23
23
|
import type { Block, OntologySchema, WidgetKind, WidgetProps } from "./types";
|
|
24
24
|
|
|
25
|
+
import DonutChartWidget from "./DonutChartWidget.svelte";
|
|
25
26
|
import EChartWidget from "./EChartWidget.svelte";
|
|
27
|
+
import LineChartWidget from "./LineChartWidget.svelte";
|
|
26
28
|
import ResultsChartWidget from "./ResultsChartWidget.svelte";
|
|
27
29
|
import StatGridWidget from "./StatGridWidget.svelte";
|
|
28
30
|
|
|
@@ -69,6 +71,11 @@ const _entries: RegistryEntry<WidgetComponent>[] = [
|
|
|
69
71
|
byKind("stat-grid", "kpi", StatGridWidget),
|
|
70
72
|
byTypeOnKind("results-chart", "aggregate", ResultsChartWidget),
|
|
71
73
|
byTypeOnKind("chart", "aggregate", EChartWidget),
|
|
74
|
+
// #176 Tier 1 — chart KINDS as registry entries, so the authoring `type`
|
|
75
|
+
// picker is a real chart picker. All three ECharts kinds share the same
|
|
76
|
+
// aggregate data path + a11y data table; only the rendered geometry differs.
|
|
77
|
+
byTypeOnKind("line-chart", "aggregate", LineChartWidget),
|
|
78
|
+
byTypeOnKind("donut-chart", "aggregate", DonutChartWidget),
|
|
72
79
|
];
|
|
73
80
|
|
|
74
81
|
/**
|
|
@@ -183,6 +183,34 @@ export interface Block {
|
|
|
183
183
|
* (set on the default block), not tenant content.
|
|
184
184
|
*/
|
|
185
185
|
bleed?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* #176 Tier 1 (ADR 0003) — 2D grid placement on a 12-column track. A block
|
|
188
|
+
* WITH `layout` is positioned by the renderer's grid (col `x`, span `w`; row
|
|
189
|
+
* `y`, span `h`); a block WITHOUT it falls back to source order (the shipped
|
|
190
|
+
* ordered-list behaviour — fully back-compatible). This is a DESCRIPTOR field
|
|
191
|
+
* consumed by BOTH render (places the block) and authoring (drag/resize writes
|
|
192
|
+
* the coords); the DS never learns it is being edited. `x`/`y` are 0-based;
|
|
193
|
+
* `w`/`h` are 1-based spans (w clamped to 1..12).
|
|
194
|
+
*/
|
|
195
|
+
layout?: BlockLayout;
|
|
196
|
+
/**
|
|
197
|
+
* #176 Tier 1 — child blocks for a future grid-IN-grid container. Reserved
|
|
198
|
+
* (single-level page grids use `layout` on flat blocks); nesting is deferred
|
|
199
|
+
* until a concrete need (ADR 0003 build-order note).
|
|
200
|
+
*/
|
|
201
|
+
children?: Block[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** 12-column grid placement (#176 Tier 1). All fields in grid track units. */
|
|
205
|
+
export interface BlockLayout {
|
|
206
|
+
/** 0-based start column (0..11). */
|
|
207
|
+
x: number;
|
|
208
|
+
/** 0-based start row. */
|
|
209
|
+
y: number;
|
|
210
|
+
/** Column span (1..12). */
|
|
211
|
+
w: number;
|
|
212
|
+
/** Row span (>=1). */
|
|
213
|
+
h: number;
|
|
186
214
|
}
|
|
187
215
|
|
|
188
216
|
export interface PageDescriptor {
|