@grafloria/element 0.3.23 → 0.4.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.
Files changed (69) hide show
  1. package/README.md +7 -7
  2. package/package.json +7 -9
  3. package/src/index.d.ts +16 -16
  4. package/src/index.js +58 -339
  5. package/src/lib/dashboard-kit/dashboard.d.ts +1 -1
  6. package/src/lib/dashboard-kit/dashboard.js +22 -26
  7. package/src/lib/dashboard-kit/grid-binder.d.ts +1 -1
  8. package/src/lib/dashboard-kit/grid-binder.js +62 -65
  9. package/src/lib/dashboard-kit/grid-mapping.js +14 -26
  10. package/src/lib/dashboard-kit/index.d.ts +5 -5
  11. package/src/lib/dashboard-kit/index.js +5 -30
  12. package/src/lib/dashboard-kit/styles.js +4 -8
  13. package/src/lib/dashboard-kit/widgets.d.ts +1 -1
  14. package/src/lib/dashboard-kit/widgets.js +16 -26
  15. package/src/lib/diagram-kit/card.d.ts +2 -2
  16. package/src/lib/diagram-kit/card.js +22 -32
  17. package/src/lib/diagram-kit/editing.js +15 -20
  18. package/src/lib/diagram-kit/er.d.ts +1 -1
  19. package/src/lib/diagram-kit/er.js +15 -18
  20. package/src/lib/diagram-kit/handles.d.ts +4 -4
  21. package/src/lib/diagram-kit/handles.js +16 -29
  22. package/src/lib/diagram-kit/index.d.ts +10 -10
  23. package/src/lib/diagram-kit/index.js +10 -50
  24. package/src/lib/diagram-kit/join-guidance.js +9 -18
  25. package/src/lib/diagram-kit/rows.js +1 -4
  26. package/src/lib/diagram-kit/styles.js +4 -8
  27. package/src/lib/diagram-kit/uml.js +10 -13
  28. package/src/lib/diagram-kit/update.d.ts +1 -1
  29. package/src/lib/diagram-kit/update.js +19 -26
  30. package/src/lib/grafloria-flow-element.js +19 -24
  31. package/src/lib/grafloria.d.ts +4 -4
  32. package/src/lib/grafloria.js +12 -17
  33. package/src/lib/load.d.ts +3 -3
  34. package/src/lib/load.js +19 -22
  35. package/src/lib/node-type-registry.js +6 -14
  36. package/src/lib/stencil-kit/builders.js +4 -10
  37. package/src/lib/stencil-kit/card-builders.js +14 -17
  38. package/src/lib/stencil-kit/index.d.ts +7 -7
  39. package/src/lib/stencil-kit/index.js +6 -16
  40. package/src/lib/stencil-kit/palette.js +14 -17
  41. package/src/lib/stencil-kit/shape-data.js +25 -28
  42. package/src/lib/stencil-kit/styles.js +1 -4
  43. package/esm/src/index.js +0 -295
  44. package/esm/src/lib/dashboard-kit/dashboard.js +0 -590
  45. package/esm/src/lib/dashboard-kit/grid-binder.js +0 -1633
  46. package/esm/src/lib/dashboard-kit/grid-mapping.js +0 -164
  47. package/esm/src/lib/dashboard-kit/index.js +0 -8
  48. package/esm/src/lib/dashboard-kit/styles.js +0 -205
  49. package/esm/src/lib/dashboard-kit/widgets.js +0 -379
  50. package/esm/src/lib/diagram-kit/card.js +0 -199
  51. package/esm/src/lib/diagram-kit/editing.js +0 -448
  52. package/esm/src/lib/diagram-kit/er.js +0 -160
  53. package/esm/src/lib/diagram-kit/handles.js +0 -312
  54. package/esm/src/lib/diagram-kit/index.js +0 -15
  55. package/esm/src/lib/diagram-kit/join-guidance.js +0 -322
  56. package/esm/src/lib/diagram-kit/rows.js +0 -144
  57. package/esm/src/lib/diagram-kit/styles.js +0 -118
  58. package/esm/src/lib/diagram-kit/uml.js +0 -135
  59. package/esm/src/lib/diagram-kit/update.js +0 -244
  60. package/esm/src/lib/grafloria-flow-element.js +0 -266
  61. package/esm/src/lib/grafloria.js +0 -69
  62. package/esm/src/lib/load.js +0 -252
  63. package/esm/src/lib/node-type-registry.js +0 -42
  64. package/esm/src/lib/stencil-kit/builders.js +0 -18
  65. package/esm/src/lib/stencil-kit/card-builders.js +0 -105
  66. package/esm/src/lib/stencil-kit/index.js +0 -9
  67. package/esm/src/lib/stencil-kit/palette.js +0 -427
  68. package/esm/src/lib/stencil-kit/shape-data.js +0 -537
  69. package/esm/src/lib/stencil-kit/styles.js +0 -126
@@ -1,379 +0,0 @@
1
- /**
2
- * BUILT-IN WIDGET RENDERERS — what `kind` means when you write no `renderWidget`.
3
- *
4
- * `dashboard({ views: [...] })` is data-first: a widget is `{ id, kind, span,
5
- * rows, data }`. Until now `kind` was a free-form string that only meant
6
- * something if the page supplied `renderWidget`; omitting it drew a titled
7
- * empty frame. This module is the other half — six real renderers driven by
8
- * the DEVELOPER'S OWN `data`, so a dashboard is useful from pure data alone:
9
- *
10
- * ```js
11
- * dashboard({ widgets: [
12
- * { id: 'rev', kind: 'kpi', span: 3, data: { label: 'Revenue', value: '$6.8M', delta: 12.4 } },
13
- * { id: 'trend', kind: 'line', span: 8, rows: 2, data: { series: [10, 14, 12, 19] } },
14
- * ]}); // no renderWidget, no charting library, real widgets
15
- * ```
16
- *
17
- * TWO THINGS THIS DELIBERATELY IS NOT:
18
- * - A charting library. Every mark is hand-drawn inline SVG (the same
19
- * technique the dashboard demo uses), so the kit still adds ZERO
20
- * dependencies and the whole file is readable.
21
- * - A dataset. Nothing here ships sample numbers; every renderer reads the
22
- * `data` the developer declared and degrades to an empty-state note when it
23
- * is missing, partial or the wrong shape. A widget renderer must NEVER
24
- * throw: it paints into a live board, mid-gesture, on every reflow.
25
- *
26
- * `renderWidget` remains the seam for anything richer — and it composes:
27
- * call `defaultWidgetRenderer(widget, host)` first, then decorate the host
28
- * (that is exactly what demos/dashboard/dashboard-builder.html does for its
29
- * focus ring and pin marker).
30
- */
31
- import { ensureDashboardKitStyles } from './styles';
32
- /** The kinds `defaultWidgetRenderer` knows; anything else gets the placeholder. */
33
- export const BUILT_IN_WIDGET_KINDS = ['kpi', 'line', 'bar', 'donut', 'funnel', 'table'];
34
- /**
35
- * Categorical default palette — readable on both light and dark cards. Per-mark
36
- * overrides win (`slices[].color`); everything else cycles this list.
37
- */
38
- const PALETTE = ['#3b52d9', '#0ea5e9', '#14b8a6', '#f59e0b', '#8b5cf6', '#64748b'];
39
- const colorAt = (i) => PALETTE[((i % PALETTE.length) + PALETTE.length) % PALETTE.length];
40
- // -- primitives ---------------------------------------------------------------
41
- const esc = (v) => String(v !== null && v !== void 0 ? v : '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
42
- /** Finite numbers only — a NaN in the data must not become a NaN in a path. */
43
- const num = (v, fallback = 0) => (typeof v === 'number' && Number.isFinite(v) ? v : fallback);
44
- const isNum = (v) => typeof v === 'number' && Number.isFinite(v);
45
- /** 6730 → '6.7k'. Axis and centre figures only; your `value` is never touched. */
46
- function compact(n) {
47
- const a = Math.abs(n);
48
- if (a >= 1e9)
49
- return `${(n / 1e9).toFixed(1)}B`;
50
- if (a >= 1e6)
51
- return `${(n / 1e6).toFixed(1)}M`;
52
- if (a >= 1e4)
53
- return `${(n / 1e3).toFixed(1)}k`;
54
- return `${Math.round(n * 100) / 100}`;
55
- }
56
- /** Round an axis maximum up to a friendly step (762 → 800, 2860 → 3000). */
57
- function niceMax(v) {
58
- if (!(v > 0))
59
- return 1;
60
- const step = Math.pow(10, Math.floor(Math.log10(v))) / 2;
61
- return Math.ceil(v / step) * step;
62
- }
63
- /** The card shell: `<div class="axdb-widget"><div h/><div b/></div>`, body returned. */
64
- function card(host, widget, title) {
65
- var _a, _b;
66
- const doc = (_a = host.ownerDocument) !== null && _a !== void 0 ? _a : document;
67
- ensureDashboardKitStyles(doc);
68
- host.innerHTML = '';
69
- const root = doc.createElement('div');
70
- root.className = `axdb-widget axdb-widget--${(_b = widget.kind) !== null && _b !== void 0 ? _b : 'widget'}`;
71
- const head = doc.createElement('div');
72
- head.className = 'axdb-widget-h';
73
- head.textContent = title;
74
- const body = doc.createElement('div');
75
- body.className = 'axdb-widget-b';
76
- root.appendChild(head);
77
- root.appendChild(body);
78
- host.appendChild(root);
79
- return body;
80
- }
81
- /** Header text: an explicit title wins, then a data-supplied label, then the kind. */
82
- function titleOf(widget, label) {
83
- var _a, _b, _c;
84
- const t = (_c = (_b = (_a = widget.title) !== null && _a !== void 0 ? _a : (typeof label === 'string' && label ? label : undefined)) !== null && _b !== void 0 ? _b : widget.kind) !== null && _c !== void 0 ? _c : widget.id;
85
- return String(t);
86
- }
87
- /** The one empty state, so every kind fails the same readable way. */
88
- function empty(body, note = 'no data') {
89
- body.innerHTML = `<div class="axdb-widget-empty">${esc(note)}</div>`;
90
- }
91
- const data = (widget) => { var _a; return ((_a = widget.data) !== null && _a !== void 0 ? _a : {}); };
92
- const legend = (items, column = false) => `<div class="axdb-lg${column ? ' axdb-lg--col' : ''}">` +
93
- items.map((i) => `<i><b style="background:${esc(i.color)}"></b>${esc(i.label)}</i>`).join('') +
94
- '</div>';
95
- // -- kpi ----------------------------------------------------------------------
96
- /** `{ label, value, delta?, spark? }` — headline number + change + sparkline. */
97
- export const renderKpiWidget = (widget, host) => {
98
- var _a;
99
- const d = data(widget);
100
- const body = card(host, widget, titleOf(widget, d.label));
101
- const value = d.value === undefined || d.value === null || d.value === '' ? '—' : d.value;
102
- let html = `<div class="axdb-kpi-v">${esc(value)}</div>`;
103
- if (isNum(d.delta)) {
104
- const up = d.delta >= 0;
105
- html +=
106
- `<div class="axdb-kpi-d ${up ? 'up' : 'down'}">${up ? '▲' : '▼'} ${Math.abs(d.delta)}% ` +
107
- `<span>${esc((_a = d.deltaLabel) !== null && _a !== void 0 ? _a : 'vs previous')}</span></div>`;
108
- }
109
- const spark = (Array.isArray(d.spark) ? d.spark : []).filter(isNum);
110
- if (spark.length > 1) {
111
- const W = 240;
112
- const H = 40;
113
- const max = Math.max(...spark);
114
- const min = Math.min(...spark);
115
- const pts = spark.map((v, i) => {
116
- const x = (i / (spark.length - 1)) * W;
117
- const y = H - ((v - min) / (max - min || 1)) * (H - 6) - 3;
118
- return `${x.toFixed(1)},${y.toFixed(1)}`;
119
- });
120
- html +=
121
- `<svg class="axdb-kpi-s" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true">` +
122
- `<path d="M0,${H} L${pts.join(' L')} L${W},${H} Z" fill="${colorAt(0)}" fill-opacity="0.12"></path>` +
123
- `<polyline points="${pts.join(' ')}" fill="none" stroke="${colorAt(0)}" stroke-width="2" ` +
124
- `stroke-linejoin="round" stroke-linecap="round"></polyline></svg>`;
125
- }
126
- body.classList.add('axdb-kpi');
127
- body.innerHTML = html;
128
- };
129
- // -- line ---------------------------------------------------------------------
130
- /** Accept both `number[]` and `{name, values}[]`, drop anything unusable. */
131
- function normalizeSeries(raw) {
132
- if (!Array.isArray(raw) || raw.length === 0)
133
- return [];
134
- if (raw.every((s) => typeof s === 'number')) {
135
- const values = raw.filter(isNum);
136
- return values.length ? [{ values }] : [];
137
- }
138
- return raw
139
- .filter((s) => !!s && Array.isArray(s.values))
140
- .map((s) => ({ name: s.name, values: s.values.filter(isNum) }))
141
- .filter((s) => s.values.length > 0);
142
- }
143
- /** `{ series, labels? }` — area under the first series, a line per series. */
144
- export const renderLineWidget = (widget, host) => {
145
- const d = data(widget);
146
- const body = card(host, widget, titleOf(widget));
147
- const series = normalizeSeries(d.series);
148
- if (!series.length)
149
- return empty(body);
150
- const W = 640;
151
- const H = 250;
152
- const pad = { l: 34, r: 12, t: 12, b: 22 };
153
- const iw = W - pad.l - pad.r;
154
- const ih = H - pad.t - pad.b;
155
- const all = series.flatMap((s) => s.values);
156
- const count = Math.max(...series.map((s) => s.values.length));
157
- const max = niceMax(Math.max(...all));
158
- const min = Math.min(0, ...all);
159
- const xAt = (i) => pad.l + (count > 1 ? (i / (count - 1)) * iw : iw / 2);
160
- const yAt = (v) => pad.t + ih - ((v - min) / (max - min || 1)) * ih;
161
- const grid = [0, 0.25, 0.5, 0.75, 1]
162
- .map((f) => {
163
- const y = pad.t + ih - f * ih;
164
- return (`<line x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W - pad.r}" y2="${y.toFixed(1)}" ` +
165
- `stroke="var(--axdb-grid)" stroke-width="1"></line>` +
166
- `<text x="${pad.l - 6}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" ` +
167
- `fill="var(--axdb-muted)">${esc(compact(min + f * (max - min)))}</text>`);
168
- })
169
- .join('');
170
- const labels = Array.isArray(d.labels) ? d.labels : [];
171
- const every = labels.length > 8 ? 2 : 1;
172
- const ticks = labels
173
- .slice(0, count)
174
- .map((l, i) => i % every === 0
175
- ? `<text x="${xAt(i).toFixed(1)}" y="${H - 6}" text-anchor="middle" font-size="9" ` +
176
- `fill="var(--axdb-muted)">${esc(l)}</text>`
177
- : '')
178
- .join('');
179
- const marks = series
180
- .map((s, si) => {
181
- const pts = s.values.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`).join(' ');
182
- const area = si === 0 && s.values.length > 1
183
- ? `<path d="M${xAt(0).toFixed(1)},${(pad.t + ih).toFixed(1)} L${pts.replace(/ /g, ' L')} ` +
184
- `L${xAt(s.values.length - 1).toFixed(1)},${(pad.t + ih).toFixed(1)} Z" ` +
185
- `fill="${colorAt(si)}" fill-opacity="0.10"></path>`
186
- : '';
187
- return (area +
188
- `<polyline points="${pts}" fill="none" stroke="${colorAt(si)}" stroke-width="${si === 0 ? 2.4 : 1.8}" ` +
189
- `stroke-linejoin="round" stroke-linecap="round"></polyline>`);
190
- })
191
- .join('');
192
- const named = series.filter((s) => s.name);
193
- if (named.length)
194
- body.classList.add('axdb-has-lg');
195
- body.innerHTML =
196
- `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
197
- `aria-label="${esc(titleOf(widget))}">${grid}${ticks}${marks}</svg>` +
198
- (named.length ? legend(series.map((s, i) => { var _a; return ({ label: String((_a = s.name) !== null && _a !== void 0 ? _a : ''), color: colorAt(i) }); })) : '');
199
- };
200
- // -- bar ----------------------------------------------------------------------
201
- /** `{ bars: [{label, value}] }` — columns, value above, category below. */
202
- export const renderBarWidget = (widget, host) => {
203
- const d = data(widget);
204
- const body = card(host, widget, titleOf(widget));
205
- const bars = (Array.isArray(d.bars) ? d.bars : []).filter((b) => !!b);
206
- if (!bars.length)
207
- return empty(body);
208
- const W = 640;
209
- const H = 250;
210
- const pad = { l: 34, r: 12, t: 12, b: 26 };
211
- const iw = W - pad.l - pad.r;
212
- const ih = H - pad.t - pad.b;
213
- const max = niceMax(Math.max(...bars.map((b) => num(b.value))));
214
- const slot = iw / bars.length;
215
- const bw = slot * 0.56;
216
- const grid = [0, 0.5, 1]
217
- .map((f) => {
218
- const y = pad.t + ih - f * ih;
219
- return (`<line x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W - pad.r}" y2="${y.toFixed(1)}" ` +
220
- `stroke="var(--axdb-grid)" stroke-width="1"></line>` +
221
- `<text x="${pad.l - 6}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" ` +
222
- `fill="var(--axdb-muted)">${esc(compact(f * max))}</text>`);
223
- })
224
- .join('');
225
- const marks = bars
226
- .map((b, i) => {
227
- var _a;
228
- const v = Math.max(0, num(b.value));
229
- const h = (v / max) * ih;
230
- const x = pad.l + i * slot + (slot - bw) / 2;
231
- const y = pad.t + ih - h;
232
- return (`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${h.toFixed(1)}" ` +
233
- `rx="4" fill="${colorAt(i)}"></rect>` +
234
- `<text x="${(x + bw / 2).toFixed(1)}" y="${(y - 4).toFixed(1)}" text-anchor="middle" font-size="9.5" ` +
235
- `font-weight="600" fill="var(--axdb-ink)">${esc(compact(num(b.value)))}</text>` +
236
- `<text x="${(x + bw / 2).toFixed(1)}" y="${H - 8}" text-anchor="middle" font-size="9" ` +
237
- `fill="var(--axdb-muted)">${esc((_a = b.label) !== null && _a !== void 0 ? _a : '')}</text>`);
238
- })
239
- .join('');
240
- body.innerHTML =
241
- `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
242
- `aria-label="${esc(titleOf(widget))}">${grid}${marks}</svg>`;
243
- };
244
- // -- donut --------------------------------------------------------------------
245
- /** One arc of the ring, as an SVG path command. */
246
- function arcPath(cx, cy, r, a0, a1) {
247
- const at = (a) => [cx + r * Math.cos(a), cy + r * Math.sin(a)];
248
- const [x0, y0] = at(a0);
249
- const [x1, y1] = at(a1);
250
- return `M${x0.toFixed(2)},${y0.toFixed(2)} A${r},${r} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${x1.toFixed(2)},${y1.toFixed(2)}`;
251
- }
252
- /** `{ slices: [{label, value, color?}], centerLabel? }` — ring + legend. */
253
- export const renderDonutWidget = (widget, host) => {
254
- var _a, _b;
255
- const d = data(widget);
256
- const body = card(host, widget, titleOf(widget));
257
- const slices = (Array.isArray(d.slices) ? d.slices : []).filter((s) => !!s && num(s.value) > 0);
258
- const total = slices.reduce((s, x) => s + num(x.value), 0);
259
- if (!slices.length || total <= 0)
260
- return empty(body);
261
- const cx = 90;
262
- const cy = 90;
263
- const r = 66;
264
- let a = -Math.PI / 2;
265
- const segs = slices
266
- .map((s, i) => {
267
- var _a;
268
- const sweep = (num(s.value) / total) * Math.PI * 2;
269
- const a1 = a + sweep;
270
- // A hair of padding each side keeps neighbouring arcs visually separate —
271
- // clamped so a very thin slice cannot invert into a backwards arc.
272
- const inset = Math.min(0.02, sweep / 4);
273
- const path = arcPath(cx, cy, r, a + inset, a1 - inset);
274
- a = a1;
275
- return `<path d="${path}" fill="none" stroke="${esc((_a = s.color) !== null && _a !== void 0 ? _a : colorAt(i))}" stroke-width="26" stroke-linecap="round"></path>`;
276
- })
277
- .join('');
278
- body.classList.add('axdb-donut');
279
- body.innerHTML =
280
- `<svg viewBox="0 0 180 180" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${esc(titleOf(widget))}">` +
281
- segs +
282
- `<text x="${cx}" y="${cy - 2}" text-anchor="middle" font-size="20" font-weight="700" ` +
283
- `fill="var(--axdb-ink)">${esc((_a = d.centerLabel) !== null && _a !== void 0 ? _a : compact(total))}</text>` +
284
- `<text x="${cx}" y="${cy + 16}" text-anchor="middle" font-size="10" ` +
285
- `fill="var(--axdb-muted)">${esc((_b = d.centerCaption) !== null && _b !== void 0 ? _b : 'total')}</text>` +
286
- '</svg>' +
287
- legend(slices.map((s, i) => {
288
- var _a, _b;
289
- return ({
290
- label: `${(_a = s.label) !== null && _a !== void 0 ? _a : ''} · ${Math.round((num(s.value) / total) * 100)}%`,
291
- color: (_b = s.color) !== null && _b !== void 0 ? _b : colorAt(i),
292
- });
293
- }), true);
294
- };
295
- // -- funnel -------------------------------------------------------------------
296
- /** `{ stages: [{label, value}] }` — centred bars scaled against the first stage. */
297
- export const renderFunnelWidget = (widget, host) => {
298
- const d = data(widget);
299
- const body = card(host, widget, titleOf(widget));
300
- const stages = (Array.isArray(d.stages) ? d.stages : []).filter((s) => !!s);
301
- if (!stages.length)
302
- return empty(body);
303
- const W = 260;
304
- const rowH = 34;
305
- const gap = 8;
306
- const H = stages.length * (rowH + gap);
307
- const max = Math.max(...stages.map((s) => num(s.value)), 1);
308
- const track = W - 90;
309
- const marks = stages
310
- .map((s, i) => {
311
- var _a;
312
- const w = Math.max(2, (num(s.value) / max) * track);
313
- const x = (track - w) / 2 + 8;
314
- const y = i * (rowH + gap);
315
- return (`<rect x="${x.toFixed(1)}" y="${y}" width="${w.toFixed(1)}" height="${rowH}" rx="6" fill="${colorAt(i)}"></rect>` +
316
- `<text x="${(x + w / 2).toFixed(1)}" y="${y + rowH / 2 + 4}" text-anchor="middle" font-size="11" ` +
317
- `font-weight="600" fill="#fff">${esc(compact(num(s.value)))}</text>` +
318
- `<text x="${W - 78}" y="${y + rowH / 2 + 4}" font-size="10.5" fill="var(--axdb-muted)">${esc((_a = s.label) !== null && _a !== void 0 ? _a : '')}</text>`);
319
- })
320
- .join('');
321
- body.innerHTML =
322
- `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
323
- `aria-label="${esc(titleOf(widget))}">${marks}</svg>`;
324
- };
325
- // -- table --------------------------------------------------------------------
326
- /** `{ columns, rows }` — plain rows; numeric cells right-align themselves. */
327
- export const renderTableWidget = (widget, host) => {
328
- const d = data(widget);
329
- const body = card(host, widget, titleOf(widget));
330
- const columns = Array.isArray(d.columns) ? d.columns : [];
331
- const rows = (Array.isArray(d.rows) ? d.rows : []).filter((r) => Array.isArray(r));
332
- if (!columns.length && !rows.length)
333
- return empty(body);
334
- const head = columns.length
335
- ? `<thead><tr>${columns.map((c) => `<th>${esc(c)}</th>`).join('')}</tr></thead>`
336
- : '';
337
- const cols = columns.length || Math.max(0, ...rows.map((r) => r.length));
338
- const tbody = rows
339
- .map((r) => '<tr>' +
340
- Array.from({ length: cols }, (_, i) => r[i])
341
- .map((cell) => `<td class="${isNum(cell) ? 'num' : ''}">${esc(cell !== null && cell !== void 0 ? cell : '')}</td>`)
342
- .join('') +
343
- '</tr>')
344
- .join('');
345
- body.classList.add('axdb-scroll');
346
- body.innerHTML = `<table class="axdb-table">${head}<tbody>${tbody}</tbody></table>`;
347
- };
348
- // -- dispatch -----------------------------------------------------------------
349
- const BY_KIND = {
350
- kpi: renderKpiWidget,
351
- line: renderLineWidget,
352
- bar: renderBarWidget,
353
- donut: renderDonutWidget,
354
- funnel: renderFunnelWidget,
355
- table: renderTableWidget,
356
- };
357
- /**
358
- * Paint `widget` into `host` by its `kind` — `dashboard()`'s default
359
- * `renderWidget`, and the composable base for your own.
360
- *
361
- * An unknown (or absent) kind falls back to the titled placeholder frame, so a
362
- * board of not-yet-implemented widgets still lays out and is still testable.
363
- * Never throws: bad data paints an empty state.
364
- */
365
- export const defaultWidgetRenderer = (widget, host) => {
366
- var _a;
367
- const renderer = BY_KIND[(_a = widget === null || widget === void 0 ? void 0 : widget.kind) !== null && _a !== void 0 ? _a : ''];
368
- if (renderer) {
369
- try {
370
- renderer(widget, host);
371
- return;
372
- }
373
- catch (_b) {
374
- /* a broken widget must not take the board down — fall through to the frame */
375
- }
376
- }
377
- card(host, widget !== null && widget !== void 0 ? widget : { id: '' }, titleOf(widget !== null && widget !== void 0 ? widget : { id: '' }));
378
- };
379
- //# sourceMappingURL=widgets.js.map
@@ -1,199 +0,0 @@
1
- /**
2
- * Card building — the ONE source of truth for the ER/UML card HTML trees, the
3
- * layout constants and the size math.
4
- *
5
- * Both the builders (er.ts / uml.ts, which emit the initial render() spec) and
6
- * the live editor (update.ts, which regenerates a card after an edit) call
7
- * these. Keeping them here is what stops the two paths from drifting: an edited
8
- * card is byte-for-byte the card the builder would have produced for the same
9
- * data, so a re-rendered table looks identical to a freshly built one.
10
- *
11
- * `editable` threads through: when true the card grows the in-canvas editing
12
- * chrome (a per-row delete control and a trailing "add" affordance). That
13
- * chrome is DELIBERATELY not a `.axk-row`/`.axk-member` — the row-selection and
14
- * port-reconciliation code count those, and the affordance is neither a column
15
- * nor a member.
16
- */
17
- /** Row height / header height of the entity card — sizing is derived from these. */
18
- export const ER_ROW_H = 25;
19
- export const ER_HEAD_H = 28;
20
- /**
21
- * Auto-height slack: the html wrapper's padding (8px) + the card's 1px
22
- * top/bottom borders + the head rendering slightly under ER_HEAD_H. Measured
23
- * live — with less, an auto-sized card overflows a few px, and the wheel
24
- * delegation would steal that much scroll on EVERY card.
25
- */
26
- export const ER_BORDER_SLACK = 9;
27
- /** The editable "add column" affordance row's height (excluded from row math). */
28
- export const ER_ADD_H = 26;
29
- /** UML compartment metrics (mirrors the pre-kit demos, measured live). */
30
- export const UML_LINE_H = 19;
31
- export const UML_NAME_H = 30;
32
- export const UML_STEREO_H = 14;
33
- export const UML_PAD = 8;
34
- /** Node-local y of a row's centre (the +1 offsets past the card's top border). */
35
- export function erRowCenterY(rowIndex) {
36
- return ER_HEAD_H + rowIndex * ER_ROW_H + ER_ROW_H / 2 + 1;
37
- }
38
- /** Inverse of {@link erRowCenterY}: which column row a pinned port sits on. */
39
- export function rowIndexFromY(y) {
40
- return Math.round((y - ER_HEAD_H - ER_ROW_H / 2 - 1) / ER_ROW_H);
41
- }
42
- /**
43
- * The `.axk-entity` content tree — the SAME tree er.ts ships and update.ts
44
- * regenerates. `editable` adds the delete control per row and the trailing add
45
- * affordance.
46
- */
47
- export function entityCardContent(entity, editable = false) {
48
- var _a;
49
- const rows = entity.columns.map((c) => {
50
- var _a;
51
- const children = [
52
- { tag: 'span', className: 'axk-key' + (c.fk ? ' axk-fk' : ''), text: c.pk ? 'PK' : c.fk ? 'FK' : '' },
53
- { tag: 'span', className: 'axk-col', text: c.name },
54
- { tag: 'span', className: 'axk-ty', text: (_a = c.type) !== null && _a !== void 0 ? _a : '' },
55
- ];
56
- // The delete control is a real child so it survives re-renders (the card is
57
- // regenerated wholesale on every edit) and hover-reveals via CSS only.
58
- if (editable)
59
- children.push({ tag: 'span', className: 'axk-col-del', text: '×' });
60
- return { tag: 'div', className: 'axk-row' + (c.pk ? ' axk-pk' : ''), children };
61
- });
62
- const body = {
63
- // Scroll is OPT-IN via an explicit height: an auto-sized card fits by
64
- // construction and must never trap the wheel, not even by a pixel.
65
- tag: 'div',
66
- className: entity.height != null ? 'axk-entity-body axk-scroll' : 'axk-entity-body',
67
- children: rows,
68
- };
69
- const children = [
70
- { tag: 'div', className: 'axk-entity-head', text: (_a = entity.name) !== null && _a !== void 0 ? _a : entity.id },
71
- body,
72
- ];
73
- // The affordance sits OUTSIDE the (scrollable) body so it is always reachable.
74
- if (editable)
75
- children.push({ tag: 'div', className: 'axk-entity-add', text: '+ add column' });
76
- return { tag: 'div', className: 'axk-entity', children };
77
- }
78
- /** Card height for an entity — explicit height wins, else derived from columns. */
79
- export function entityAutoHeight(entity, editable = false) {
80
- if (entity.height != null)
81
- return entity.height;
82
- return ER_HEAD_H + entity.columns.length * ER_ROW_H + ER_BORDER_SLACK + (editable ? ER_ADD_H : 0);
83
- }
84
- /**
85
- * The `.axk-uml` content tree. `editable` adds a delete control per member and
86
- * a trailing add affordance in each compartment.
87
- */
88
- export function classCardContent(cls, editable = false) {
89
- var _a, _b, _c;
90
- const attrs = (_a = cls.attributes) !== null && _a !== void 0 ? _a : [];
91
- const methods = (_b = cls.methods) !== null && _b !== void 0 ? _b : [];
92
- const italic = cls.abstract || cls.stereotype === 'abstract' || cls.stereotype === 'interface';
93
- // Non-editable output stays BYTE-IDENTICAL to the pre-editing builder (a
94
- // member is a text div); editable additions are purely additive.
95
- const member = (text) => editable
96
- ? {
97
- tag: 'div',
98
- className: 'axk-member',
99
- children: [
100
- { tag: 'span', className: 'axk-mtext', text },
101
- { tag: 'span', className: 'axk-col-del', text: '×' },
102
- ],
103
- }
104
- : { tag: 'div', className: 'axk-member', text };
105
- const compartment = (members, section) => {
106
- const children = members.map(member);
107
- if (editable) {
108
- children.push({
109
- tag: 'div',
110
- className: 'axk-uml-add',
111
- text: section === 'attributes' ? '+ attribute' : '+ method',
112
- });
113
- }
114
- return {
115
- tag: 'div',
116
- className: 'axk-uml-comp' + (members.length ? '' : ' axk-empty'),
117
- children,
118
- };
119
- };
120
- const name = {
121
- tag: 'div',
122
- className: 'axk-uml-name' + (italic ? ' axk-abstract' : ''),
123
- children: [
124
- ...(cls.stereotype ? [{ tag: 'span', className: 'axk-uml-stereo', text: `«${cls.stereotype}»` }] : []),
125
- { tag: 'span', text: (_c = cls.name) !== null && _c !== void 0 ? _c : cls.id },
126
- ],
127
- };
128
- return {
129
- tag: 'div',
130
- className: 'axk-uml',
131
- children: [
132
- name,
133
- {
134
- tag: 'div',
135
- className: cls.height != null ? 'axk-uml-body axk-scroll' : 'axk-uml-body',
136
- children: [compartment(attrs, 'attributes'), compartment(methods, 'methods')],
137
- },
138
- ],
139
- };
140
- }
141
- /** Card height for a class — explicit height wins, else derived from members. */
142
- export function classAutoHeight(cls, editable = false) {
143
- var _a, _b;
144
- if (cls.height != null)
145
- return cls.height;
146
- const attrs = (_a = cls.attributes) !== null && _a !== void 0 ? _a : [];
147
- const methods = (_b = cls.methods) !== null && _b !== void 0 ? _b : [];
148
- const addRows = editable ? 2 : 0; // one "add" affordance per compartment
149
- return (UML_NAME_H +
150
- (cls.stereotype ? UML_STEREO_H : 0) +
151
- (attrs.length + methods.length + addRows) * UML_LINE_H +
152
- UML_PAD * 2 +
153
- 12);
154
- }
155
- /**
156
- * Match old columns to new columns for port reconciliation. Returns a map from
157
- * OLD index → NEW index; an old index absent from the map is a REMOVED column
158
- * (its pinned ports and their edges are dropped).
159
- *
160
- * Three passes, most-certain first, so a single edit is always read the way a
161
- * human means it:
162
- * 1. object identity — reorder / add / remove that preserved references;
163
- * 2. name — a retype (same name, new object) or a rebuilt array;
164
- * 3. positional remainder — the leftovers matched in order, which is exactly
165
- * what a rename is (one old + one new in the same slot). Without pass 3 a
166
- * rename reads as remove+add and the edge is severed.
167
- */
168
- export function matchColumns(oldCols, newCols) {
169
- const out = new Map();
170
- const usedNew = new Set();
171
- const matchedOld = new Set();
172
- const claim = (oldIdx, newIdx) => {
173
- out.set(oldIdx, newIdx);
174
- usedNew.add(newIdx);
175
- matchedOld.add(oldIdx);
176
- };
177
- // Pass 1: object identity.
178
- oldCols.forEach((oc, oi) => {
179
- const ni = newCols.indexOf(oc);
180
- if (ni !== -1 && !usedNew.has(ni))
181
- claim(oi, ni);
182
- });
183
- // Pass 2: by name, among the unmatched.
184
- oldCols.forEach((oc, oi) => {
185
- if (matchedOld.has(oi))
186
- return;
187
- const ni = newCols.findIndex((nc, i) => !usedNew.has(i) && nc.name === oc.name);
188
- if (ni !== -1)
189
- claim(oi, ni);
190
- });
191
- // Pass 3: positional remainder (rename / retype-and-rename).
192
- const remOld = oldCols.map((_, i) => i).filter((i) => !matchedOld.has(i));
193
- const remNew = newCols.map((_, i) => i).filter((i) => !usedNew.has(i));
194
- const n = Math.min(remOld.length, remNew.length);
195
- for (let k = 0; k < n; k++)
196
- claim(remOld[k], remNew[k]);
197
- return out;
198
- }
199
- //# sourceMappingURL=card.js.map