@cerebro-labs/agentnava-sdk 0.53.2 → 0.75.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/react.css CHANGED
@@ -12,17 +12,17 @@
12
12
  --an-w-ink: #16181d;
13
13
  --an-w-muted: #6b7280;
14
14
  --an-w-line: #e5e7eb;
15
- --an-w-surface: #f8f9fb;
16
- --an-w-tint: #eef2f7;
15
+ --an-w-surface: #f8f8f6;
16
+ --an-w-tint: #f1f0ec;
17
17
  --an-w-accent: #1f2937;
18
18
  --an-w-accent-ink: #ffffff;
19
- --an-w-s0: #2563eb;
20
- --an-w-s1: #d97706;
21
- --an-w-s2: #059669;
22
- --an-w-s3: #7c3aed;
23
- --an-w-high: #dc2626;
19
+ --an-w-s0: #2f6b55;
20
+ --an-w-s1: #b08a3e;
21
+ --an-w-s2: #4a6c8f;
22
+ --an-w-s3: #b4472f;
23
+ --an-w-high: #b4472f;
24
24
  --an-w-medium: #d97706;
25
- --an-w-good: #059669;
25
+ --an-w-good: #3e7c59;
26
26
  --an-w-info: #2563eb;
27
27
  --an-w-radius: 8px;
28
28
  }
@@ -87,8 +87,10 @@
87
87
  .an-w-card > img {
88
88
  display: block;
89
89
  width: 100%;
90
- aspect-ratio: 16 / 9;
90
+ aspect-ratio: 1 / 1;
91
+ max-height: 320px;
91
92
  object-fit: cover;
93
+ object-position: center 22%;
92
94
  }
93
95
  .an-w-card-body {
94
96
  display: flex;
@@ -103,6 +105,35 @@
103
105
  .an-w-card-link:hover .an-w-card {
104
106
  border-color: var(--an-w-muted);
105
107
  }
108
+ .an-w-card.has-action {
109
+ display: flex;
110
+ flex-direction: column;
111
+ }
112
+ .an-w-card-main {
113
+ display: block;
114
+ flex: 1;
115
+ color: inherit;
116
+ text-decoration: none;
117
+ }
118
+ .an-w-card-main > img {
119
+ display: block;
120
+ width: 100%;
121
+ aspect-ratio: 1 / 1;
122
+ max-height: 320px;
123
+ object-fit: cover;
124
+ object-position: center 22%;
125
+ }
126
+ .an-w-card-action {
127
+ display: block;
128
+ margin: 0 12px 12px;
129
+ padding: 8px 10px;
130
+ border-radius: 8px;
131
+ text-align: center;
132
+ font-weight: 600;
133
+ text-decoration: none;
134
+ background: var(--an-w-accent);
135
+ color: var(--an-w-accent-ink);
136
+ }
106
137
 
107
138
  .an-w-text.is-title {
108
139
  font-size: 1.15em;
@@ -125,23 +156,39 @@
125
156
  .an-w-stat {
126
157
  display: flex;
127
158
  flex-direction: column;
128
- gap: 1px;
129
- padding: 8px 10px;
130
- border-radius: var(--an-w-radius);
131
- background: var(--an-w-tint);
159
+ gap: 4px;
160
+ padding: 18px 20px 16px;
161
+ border: 1px solid var(--an-w-line);
162
+ border-radius: 14px;
163
+ background: linear-gradient(180deg, #ffffff, var(--an-w-surface));
164
+ box-shadow: 0 1px 2px rgba(16, 16, 16, 0.04), 0 6px 18px rgba(16, 16, 16, 0.05);
132
165
  }
133
- .an-w-stat strong {
134
- font-size: 1.25em;
166
+ .an-w-stat span {
135
167
  font-weight: 600;
136
- font-variant-numeric: tabular-nums;
168
+ font-size: 0.95em;
169
+ color: var(--an-w-ink);
137
170
  }
138
- .an-w-stat span {
139
- font-weight: 500;
171
+ .an-w-stat strong {
172
+ font-size: 2.1em;
173
+ line-height: 1.1;
174
+ font-weight: 700;
175
+ letter-spacing: -0.03em;
176
+ color: var(--an-w-ink);
177
+ font-variant-numeric: tabular-nums;
140
178
  }
141
179
  .an-w-stat small {
142
180
  color: var(--an-w-muted);
143
- font-size: 0.85em;
144
- line-height: 1.3;
181
+ font-size: 0.9em;
182
+ line-height: 1.35;
183
+ }
184
+ .an-w-stat small b {
185
+ font-weight: 600;
186
+ }
187
+ .an-w-stat small b.is-up {
188
+ color: var(--an-w-good);
189
+ }
190
+ .an-w-stat small b.is-down {
191
+ color: var(--an-w-high);
145
192
  }
146
193
 
147
194
  .an-w-kv dl > div {
@@ -169,6 +216,12 @@
169
216
 
170
217
  .an-w-table {
171
218
  overflow-x: auto;
219
+ border: 1px solid var(--an-w-line);
220
+ border-radius: 12px;
221
+ background: #fff;
222
+ }
223
+ .an-w-table > .an-w-title {
224
+ padding: 10px 12px 0;
172
225
  }
173
226
  .an-w-table table {
174
227
  border-collapse: collapse;
@@ -177,17 +230,24 @@
177
230
  .an-w-table th,
178
231
  .an-w-table td {
179
232
  text-align: left;
180
- padding: 5px 10px 5px 0;
233
+ padding: 9px 12px;
181
234
  border-bottom: 1px solid var(--an-w-line);
182
- white-space: nowrap;
235
+ vertical-align: top;
236
+ }
237
+ .an-w-table tbody tr:last-child td {
238
+ border-bottom: 0;
183
239
  }
184
240
  .an-w-table th {
185
- color: var(--an-w-muted);
186
- font-weight: 500;
241
+ color: var(--an-w-ink);
242
+ font-weight: 600;
243
+ background: var(--an-w-surface);
244
+ white-space: nowrap;
187
245
  }
246
+ .an-w-table th.is-num,
188
247
  .an-w-table td.is-num {
189
248
  text-align: right;
190
249
  font-variant-numeric: tabular-nums;
250
+ white-space: nowrap;
191
251
  }
192
252
 
193
253
  .an-w-chart figcaption {
@@ -196,16 +256,103 @@
196
256
  font-weight: 500;
197
257
  margin-bottom: 6px;
198
258
  }
259
+ .an-w-chart-head {
260
+ display: flex;
261
+ align-items: baseline;
262
+ flex-wrap: wrap;
263
+ gap: 4px 10px;
264
+ margin: -2px 0 6px;
265
+ }
266
+ .an-w-chart-head strong {
267
+ font-size: 1.9em;
268
+ font-weight: 700;
269
+ letter-spacing: -0.03em;
270
+ color: var(--an-w-ink);
271
+ font-variant-numeric: tabular-nums;
272
+ }
273
+ .an-w-chart-head span {
274
+ color: var(--an-w-muted);
275
+ font-size: 0.9em;
276
+ font-variant-numeric: tabular-nums;
277
+ }
278
+ .an-w-chart-box {
279
+ position: relative;
280
+ width: 100%;
281
+ }
199
282
  .an-w-chart svg {
200
283
  display: block;
201
- width: 100%;
202
- height: auto;
284
+ max-width: 100%;
203
285
  overflow: visible;
204
286
  }
205
287
  .an-w-chart text {
206
- font-size: 10px;
288
+ font-size: 11px;
207
289
  fill: var(--an-w-muted);
290
+ font-variant-numeric: tabular-nums;
291
+ }
292
+ .an-w-chart text.an-w-val {
293
+ fill: var(--an-w-ink);
294
+ font-weight: 500;
295
+ }
296
+ .an-w-grid line {
297
+ stroke: var(--an-w-line);
298
+ stroke-width: 1;
299
+ }
300
+ .an-w-zero {
301
+ stroke: var(--an-w-muted);
302
+ stroke-opacity: 0.5;
303
+ }
304
+ .an-w-cross {
305
+ stroke: var(--an-w-muted);
306
+ stroke-opacity: 0.4;
307
+ stroke-dasharray: 3 3;
208
308
  }
309
+ .an-w-chart rect.is-dim {
310
+ opacity: 0.45;
311
+ }
312
+ .an-w-chart circle {
313
+ stroke: #fff;
314
+ stroke-width: 2;
315
+ filter: drop-shadow(0 1px 3px rgba(16, 16, 16, 0.25));
316
+ }
317
+ .an-w-area {
318
+ stroke: none;
319
+ }
320
+ .an-w-stop.an-w-s0 { stop-color: var(--an-w-s0); }
321
+ .an-w-stop.an-w-s1 { stop-color: var(--an-w-s1); }
322
+ .an-w-stop.an-w-s2 { stop-color: var(--an-w-s2); }
323
+ .an-w-stop.an-w-s3 { stop-color: var(--an-w-s3); }
324
+ .an-w-tip {
325
+ position: absolute;
326
+ top: 8px;
327
+ pointer-events: none;
328
+ display: flex;
329
+ flex-direction: column;
330
+ gap: 2px;
331
+ padding: 6px 9px;
332
+ border: 1px solid var(--an-w-line);
333
+ border-radius: 8px;
334
+ background: rgba(255, 255, 255, 0.78);
335
+ -webkit-backdrop-filter: blur(14px) saturate(1.5);
336
+ backdrop-filter: blur(14px) saturate(1.5);
337
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6) inset, 0 6px 20px rgba(16, 16, 16, 0.1);
338
+ font-size: 12px;
339
+ white-space: nowrap;
340
+ }
341
+ .an-w-tip b {
342
+ color: var(--an-w-muted);
343
+ font-weight: 500;
344
+ }
345
+ .an-w-tip i {
346
+ display: inline-block;
347
+ width: 8px;
348
+ height: 8px;
349
+ border-radius: 50%;
350
+ margin-right: 6px;
351
+ }
352
+ .an-w-tip i.an-w-s0 { background: var(--an-w-s0); }
353
+ .an-w-tip i.an-w-s1 { background: var(--an-w-s1); }
354
+ .an-w-tip i.an-w-s2 { background: var(--an-w-s2); }
355
+ .an-w-tip i.an-w-s3 { background: var(--an-w-s3); }
209
356
  .an-w-chart rect.an-w-s0,
210
357
  .an-w-chart circle.an-w-s0 {
211
358
  fill: var(--an-w-s0);
@@ -224,7 +371,9 @@
224
371
  }
225
372
  .an-w-line {
226
373
  fill: none;
227
- stroke-width: 2;
374
+ stroke-width: 2.25;
375
+ stroke-linejoin: round;
376
+ stroke-linecap: round;
228
377
  }
229
378
  .an-w-line.an-w-s0 {
230
379
  stroke: var(--an-w-s0);
package/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Ready-made React views for the widgets an agent draws in its replies.
3
3
  //
4
4
  // import { ReplyView } from '@cerebro-labs/agentnava-sdk/react'
@@ -16,7 +16,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
16
16
  // once, even if two containers name it; links and pictures open only over
17
17
  // https:// (narrow it further with `allowUrl`); and a widget that throws while
18
18
  // drawing is left out rather than breaking the screen around it.
19
- import { Component, Fragment } from 'react';
19
+ import { Component, Fragment, useEffect, useId, useRef, useState } from 'react';
20
20
  import { placeWidgets } from "./widgets.js";
21
21
  export { placeWidgets } from "./widgets.js";
22
22
  const str = (v) => (typeof v === 'string' || typeof v === 'number' ? String(v) : '');
@@ -34,7 +34,63 @@ class Boundary extends Component {
34
34
  return this.state.failed ? null : this.props.children;
35
35
  }
36
36
  }
37
+ /** A caption's leading figure is bold; a signed one ("-1.55%", "+12") is red or green too. */
38
+ function caption(text) {
39
+ const m = /^([+\-\u2212]?[$\u20ac\u00a3\u20b9]?\d[\d,.]*[%KMBTx]?)(.*)$/s.exec(text);
40
+ if (!m)
41
+ return text;
42
+ // Only a signed figure says which way it moved; "12 errors" is not good news.
43
+ const dir = /^[-\u2212]/.test(m[1]) ? 'is-down' : m[1].startsWith('+') ? 'is-up' : '';
44
+ return (_jsxs(_Fragment, { children: [_jsx("b", { className: dir || undefined, children: m[1] }), m[2]] }));
45
+ }
46
+ /** Round numbers for the y axis: about `count` steps of 1, 2, 2.5 or 5 x 10^n. */
47
+ function niceTicks(lo, hi, count = 4) {
48
+ if (lo === hi) {
49
+ const pad = Math.abs(lo) * 0.1 || 1;
50
+ lo -= pad;
51
+ hi += pad;
52
+ }
53
+ const raw = (hi - lo) / count;
54
+ const mag = 10 ** Math.floor(Math.log10(raw));
55
+ const step = ([1, 2, 2.5, 5, 10].find((m) => m * mag >= raw) ?? 10) * mag;
56
+ const ticks = [];
57
+ for (let t = Math.floor(lo / step) * step; t <= hi + step * 1e-9; t += step)
58
+ ticks.push(Number(t.toPrecision(12)));
59
+ if (ticks[ticks.length - 1] < hi)
60
+ ticks.push(Number((ticks[ticks.length - 1] + step).toPrecision(12)));
61
+ return ticks;
62
+ }
63
+ /** Short axis numbers: 1.2K, 3.4M, 5B. */
64
+ function compact(n, locale) {
65
+ try {
66
+ return new Intl.NumberFormat(locale, {
67
+ notation: 'compact',
68
+ maximumFractionDigits: 1,
69
+ }).format(n);
70
+ }
71
+ catch {
72
+ return String(n);
73
+ }
74
+ }
37
75
  function Chart({ part, ctx }) {
76
+ const box = useRef(null);
77
+ const gid = `an-w-g${useId().replace(/:/g, '')}`;
78
+ // Drawn in real pixels at the box's own width, so text stays at body size
79
+ // however wide the card is (a scaled viewBox blew 10px labels up to 40px).
80
+ const [W, setW] = useState(560);
81
+ const [hover, setHover] = useState(null);
82
+ useEffect(() => {
83
+ const el = box.current;
84
+ if (!el || typeof ResizeObserver === 'undefined')
85
+ return;
86
+ const ro = new ResizeObserver(([e]) => {
87
+ const w = Math.round(e.contentRect.width);
88
+ if (w > 0)
89
+ setW(w);
90
+ });
91
+ ro.observe(el);
92
+ return () => ro.disconnect();
93
+ }, []);
38
94
  const series = list(part.series)
39
95
  .map((s) => ({
40
96
  name: str(s?.name),
@@ -44,26 +100,70 @@ function Chart({ part, ctx }) {
44
100
  if (series.length === 0)
45
101
  return null;
46
102
  const xs = series[0].points.map((p) => str(p.x));
103
+ const n = Math.max(...series.map((s) => s.points.length));
47
104
  const ref = part.reference;
48
105
  const refY = typeof ref?.y === 'number' && Number.isFinite(ref.y) ? ref.y : undefined;
106
+ const isBar = part.type === 'bar';
49
107
  const ys = [...series.flatMap((s) => s.points.map((p) => p.y)), ...(refY === undefined ? [] : [refY])];
50
- // Zero sits between the extremes, so a negative value draws below the zero
51
- // line instead of collapsing onto it and reading as 0.
52
- const lo = Math.min(...ys, 0);
53
- const span = Math.max(...ys, 0) - lo || 1;
54
- const W = 320;
55
- const H = 140;
56
- const step = W / Math.max(xs.length, 1);
57
- const fmt = (n) => (part.unit === 'currency' ? ctx.money(n) : part.unit === 'percent' ? `${ctx.number(n)}%` : ctx.number(n));
58
- const yOf = (y) => H - ((y - lo) / span) * (H - 18);
59
- const zero = yOf(0);
60
- const labelled = series.length === 1 && xs.length <= 6;
61
- return (_jsxs("figure", { className: "an-w-chart", children: [str(part.title) && _jsx("figcaption", { children: str(part.title) }), _jsxs("svg", { viewBox: `0 0 ${W} ${H + 18}`, role: "img", "aria-label": str(part.title) || 'Chart', children: [series.map((s, si) => part.type === 'bar' ? (s.points.map((p, i) => {
62
- const bw = (step * 0.7) / series.length;
63
- const x = i * step + step * 0.15 + si * bw;
64
- const y = yOf(p.y);
65
- return (_jsxs("g", { children: [_jsx("rect", { className: `an-w-s${si % 4}`, x: x, y: Math.min(y, zero), width: bw, height: Math.abs(zero - y), rx: 2, children: _jsx("title", { children: `${str(p.x)}: ${fmt(p.y)}` }) }), labelled && (_jsx("text", { x: x + bw / 2, y: Math.min(y, zero) - 4, textAnchor: "middle", children: fmt(p.y) }))] }, `${si}-${i}`));
66
- })) : (_jsxs("g", { children: [_jsx("polyline", { className: `an-w-line an-w-s${si % 4}${part.type === 'area' ? ' is-area' : ''}`, points: s.points.map((p, i) => `${i * step + step / 2},${yOf(p.y)}`).join(' ') }), s.points.map((p, i) => (_jsxs("g", { children: [_jsx("circle", { className: `an-w-s${si % 4}`, cx: i * step + step / 2, cy: yOf(p.y), r: 3, children: _jsx("title", { children: `${str(p.x)}: ${fmt(p.y)}` }) }), labelled && (_jsx("text", { x: i * step + step / 2, y: yOf(p.y) - 7, textAnchor: "middle", children: fmt(p.y) }))] }, i)))] }, si))), lo < 0 && _jsx("line", { x1: 0, x2: W, y1: zero, y2: zero, stroke: "currentColor", strokeOpacity: 0.4 }), refY !== undefined && (_jsxs("g", { className: "an-w-ref", children: [_jsx("line", { x1: 0, x2: W, y1: yOf(refY), y2: yOf(refY) }), str(ref?.label) && (_jsx("text", { x: W, y: yOf(refY) - 4, textAnchor: "end", children: str(ref?.label) }))] })), xs.map((x, i) => (_jsx("text", { x: i * step + step / 2, y: H + 14, textAnchor: "middle", children: x }, i)))] }), series.length > 1 && (_jsx("div", { className: "an-w-legend", children: series.map((s, si) => (_jsxs("span", { children: [_jsx("i", { className: `an-w-s${si % 4}` }), s.name] }, si))) }))] }));
108
+ // Bars measure from zero, so zero is always in range. A line shows change,
109
+ // so it fits its own range: a price moving 520 to 540 must not draw flat.
110
+ const dataLo = Math.min(...ys);
111
+ const dataHi = Math.max(...ys);
112
+ const ticks = isBar ? niceTicks(Math.min(dataLo, 0), Math.max(dataHi, 0)) : niceTicks(dataLo, dataHi);
113
+ const lo = ticks[0];
114
+ const hi = ticks[ticks.length - 1];
115
+ const span = hi - lo || 1;
116
+ const fmt = (v) => (part.unit === 'currency' ? ctx.money(v) : part.unit === 'percent' ? `${ctx.number(v)}%` : ctx.number(v));
117
+ const axis = (v) => {
118
+ const short = Math.abs(hi) >= 10000 || Math.abs(lo) >= 10000 ? compact(v, ctx.props.locale) : ctx.number(v);
119
+ return part.unit === 'currency' ? (fmt(0).replace(/[\d.,\s]+/g, '') || '') + short : part.unit === 'percent' ? `${short}%` : short;
120
+ };
121
+ const H = 220;
122
+ const top = 12;
123
+ const bottom = 26;
124
+ const left = Math.max(...ticks.map((t) => axis(t).length)) * 6.5 + 10;
125
+ const right = Math.max(12, Math.max(...xs.map((x) => x.length), 1) * 3.3);
126
+ const plotW = Math.max(W - left - right, 40);
127
+ const plotH = H - top - bottom;
128
+ const yOf = (y) => top + plotH - ((y - lo) / span) * plotH;
129
+ const step = plotW / Math.max(n, 1);
130
+ const xOf = (i) => (isBar ? left + i * step + step / 2 : left + (n === 1 ? plotW / 2 : (i / (n - 1)) * plotW));
131
+ const zero = yOf(Math.max(lo, Math.min(0, hi)));
132
+ // Thin the x labels until each fits in the room it has.
133
+ const longest = Math.max(...xs.map((x) => x.length), 1);
134
+ const every = Math.max(1, Math.ceil((longest * 6.5 + 12) / (plotW / Math.max(n, 1))));
135
+ const showDots = false;
136
+ const pick = (e) => {
137
+ const r = e.currentTarget.getBoundingClientRect();
138
+ const px = ((e.clientX - r.left) / r.width) * W;
139
+ let best = 0;
140
+ for (let i = 1; i < n; i++)
141
+ if (Math.abs(xOf(i) - px) < Math.abs(xOf(best) - px))
142
+ best = i;
143
+ setHover(best);
144
+ };
145
+ const tipX = hover === null ? 0 : xOf(hover);
146
+ // One trend line leads with where it ended and how far it moved, like a stat.
147
+ const only = series.length === 1 && !isBar && series[0].points.length >= 2 ? series[0].points : undefined;
148
+ const headline = only
149
+ ? (() => {
150
+ const first = only[0].y;
151
+ const last = only[only.length - 1].y;
152
+ const delta = last - first;
153
+ const abs = part.unit === 'percent' ? `${ctx.number(Math.abs(delta))} pts` : fmt(Math.abs(delta)).replace('-', '');
154
+ const pct = part.unit !== 'percent' && first > 0 && last >= 0 ? ` (${ctx.number(Math.round((Math.abs(delta) / first) * 1000) / 10)}%)` : '';
155
+ return { last, delta, change: abs + pct, from: str(only[0].x) };
156
+ })()
157
+ : undefined;
158
+ return (_jsxs("figure", { className: "an-w-chart", children: [str(part.title) && _jsx("figcaption", { children: str(part.title) }), headline && (_jsxs("div", { className: "an-w-chart-head", children: [_jsx("strong", { children: fmt(headline.last) }), _jsxs("span", { children: [headline.delta === 0 ? 'No change' : `${headline.delta > 0 ? '\u25B2' : '\u25BC'} ${headline.change}`, " since ", headline.from] })] })), _jsxs("div", { ref: box, className: "an-w-chart-box", children: [_jsxs("svg", { width: W, height: H, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": str(part.title) || 'Chart', onMouseMove: pick, onMouseLeave: () => setHover(null), children: [_jsx("defs", { children: [0, 1, 2, 3].map((k) => (_jsxs("linearGradient", { id: `${gid}-${k}`, x1: "0", x2: "0", y1: "0", y2: "1", children: [_jsx("stop", { offset: "0%", className: `an-w-stop an-w-s${k}`, stopOpacity: part.type === 'area' ? 0.28 : 0.18 }), _jsx("stop", { offset: "100%", className: `an-w-stop an-w-s${k}`, stopOpacity: 0 })] }, k))) }), ticks.map((t) => (_jsxs("g", { className: "an-w-grid", children: [_jsx("line", { x1: left, x2: W - right, y1: yOf(t), y2: yOf(t) }), _jsx("text", { x: left - 8, y: yOf(t), dy: "0.32em", textAnchor: "end", children: axis(t) })] }, t))), series.map((s, si) => isBar ? (s.points.map((p, i) => {
159
+ const bw = Math.min((step * 0.7) / series.length, 48);
160
+ const x = xOf(i) - (bw * series.length) / 2 + si * bw;
161
+ const y = yOf(p.y);
162
+ return (_jsxs("g", { children: [_jsx("rect", { className: `an-w-s${si % 4}${hover !== null && hover !== i ? ' is-dim' : ''}`, x: x + 1, y: Math.min(y, zero), width: Math.max(bw - 2, 1), height: Math.max(Math.abs(zero - y), 1), rx: 3 }), series.length === 1 && n <= 8 && (_jsx("text", { className: "an-w-val", x: x + bw / 2, y: Math.min(y, zero) - 6, textAnchor: "middle", children: fmt(p.y) }))] }, `${si}-${i}`));
163
+ })) : (_jsxs("g", { children: [(part.type === 'area' || series.length === 1) && (_jsx("path", { className: `an-w-area an-w-s${si % 4}`, fill: `url(#${gid}-${si % 4})`, d: `M${xOf(0)},${zero} ${s.points.map((p, i) => `L${xOf(i)},${yOf(p.y)}`).join(' ')} L${xOf(s.points.length - 1)},${zero}Z` })), _jsx("polyline", { className: `an-w-line an-w-s${si % 4}`, points: s.points.map((p, i) => `${xOf(i)},${yOf(p.y)}`).join(' ') }), s.points.map((p, i) => showDots || hover === i || (series.length === 1 && i === s.points.length - 1) ? (_jsx("circle", { className: `an-w-s${si % 4}`, cx: xOf(i), cy: yOf(p.y), r: hover === i ? 4.5 : 4 }, i)) : null)] }, si))), lo < 0 && hi > 0 && _jsx("line", { className: "an-w-zero", x1: left, x2: W - right, y1: zero, y2: zero }), refY !== undefined && (_jsxs("g", { className: "an-w-ref", children: [_jsx("line", { x1: left, x2: W - right, y1: yOf(refY), y2: yOf(refY) }), str(ref?.label) && (_jsx("text", { x: W - right, y: yOf(refY) - 5, textAnchor: "end", children: str(ref?.label) }))] })), xs.map((x, i) => i % every === 0 ? (_jsx("text", { x: xOf(i), y: H - 8, textAnchor: "middle", children: x }, i)) : null), hover !== null && !isBar && _jsx("line", { className: "an-w-cross", x1: tipX, x2: tipX, y1: top, y2: top + plotH })] }), hover !== null && (_jsxs("div", { className: "an-w-tip", style: {
164
+ left: tipX,
165
+ transform: `translateX(${tipX > W / 2 ? 'calc(-100% - 10px)' : '10px'})`,
166
+ }, children: [_jsx("b", { children: xs[hover] }), series.map((s, si) => s.points[hover] ? (_jsxs("span", { children: [_jsx("i", { className: `an-w-s${si % 4}` }), series.length > 1 && `${s.name} `, _jsx("strong", { children: fmt(s.points[hover].y) })] }, si)) : null)] }))] }), series.length > 1 && (_jsx("div", { className: "an-w-legend", children: series.map((s, si) => (_jsxs("span", { children: [_jsx("i", { className: `an-w-s${si % 4}` }), s.name] }, si))) }))] }));
67
167
  }
68
168
  /** The default look of one part. `kids` is its children, already drawn. */
69
169
  function defaultPart(part, kids, ctx) {
@@ -76,6 +176,14 @@ function defaultPart(part, kids, ctx) {
76
176
  case 'Card': {
77
177
  const image = ctx.url(part.image, 'image');
78
178
  const href = ctx.url(part.href, 'link');
179
+ const actionHref = ctx.url(part.actionHref, 'link');
180
+ const actionLabel = str(part.actionLabel);
181
+ if (actionHref && actionLabel) {
182
+ // The card's one button ("Add to cart") is a link beside the card's
183
+ // own link, never inside it.
184
+ const inner = (_jsxs(_Fragment, { children: [image && _jsx("img", { src: image, alt: str(part.imageAlt), referrerPolicy: "no-referrer", loading: "lazy" }), _jsx("div", { className: "an-w-card-body", children: kids })] }));
185
+ return (_jsxs("div", { className: "an-w-card has-action", children: [href ? (_jsx("a", { className: "an-w-card-main", href: href, target: "_blank", rel: "noreferrer noopener", children: inner })) : (_jsx("div", { className: "an-w-card-main", children: inner })), _jsx("a", { className: "an-w-card-action", href: actionHref, target: "_blank", rel: "noreferrer noopener", children: actionLabel })] }));
186
+ }
79
187
  const body = (_jsxs("div", { className: "an-w-card", children: [image && _jsx("img", { src: image, alt: str(part.imageAlt), referrerPolicy: "no-referrer", loading: "lazy" }), _jsx("div", { className: "an-w-card-body", children: kids })] }));
80
188
  return href ? (_jsx("a", { className: "an-w-card-link", href: href, target: "_blank", rel: "noreferrer noopener", children: body })) : (body);
81
189
  }
@@ -88,7 +196,7 @@ function defaultPart(part, kids, ctx) {
88
196
  case 'Stat':
89
197
  if (!str(part.value))
90
198
  return null;
91
- return (_jsxs("div", { className: "an-w-stat", children: [_jsx("strong", { children: str(part.value) }), str(part.label) && _jsx("span", { children: str(part.label) }), str(part.caption) && _jsx("small", { children: str(part.caption) })] }));
199
+ return (_jsxs("div", { className: "an-w-stat", children: [str(part.label) && _jsx("span", { children: str(part.label) }), _jsx("strong", { children: str(part.value) }), str(part.caption) && _jsx("small", { children: caption(str(part.caption)) })] }));
92
200
  case 'KeyValue': {
93
201
  const rows = list(part.rows).filter((r) => str(r?.label) || str(r?.value));
94
202
  if (rows.length === 0)
@@ -100,7 +208,15 @@ function defaultPart(part, kids, ctx) {
100
208
  const rows = list(part.rows).filter(Array.isArray);
101
209
  if (cols.length === 0 && rows.length === 0)
102
210
  return null;
103
- return (_jsxs("div", { className: "an-w-table", children: [str(part.title) && _jsx("div", { className: "an-w-title", children: str(part.title) }), _jsxs("table", { children: [cols.length > 0 && (_jsx("thead", { children: _jsx("tr", { children: cols.map((c, j) => (_jsx("th", { children: c }, j))) }) })), _jsx("tbody", { children: rows.map((r, i) => (_jsx("tr", { children: r.map((c, j) => (_jsx("td", { className: typeof c === 'number' ? 'is-num' : undefined, children: typeof c === 'number' ? ctx.number(c) : str(c) }, j))) }, i))) })] })] }));
211
+ // A column is numeric when every filled cell is a figure ("521.91", "-1.55%", "$3.2B"),
212
+ // and then its header lines up with its numbers on the right.
213
+ const figure = (c) => typeof c === 'number' || (typeof c === 'string' && /^[+\-\u2212]?[$\u20ac\u00a3\u20b9]?\d[\d,.]*\s?[%KMBTx]?$/.test(c.trim()));
214
+ const numCol = (j) => {
215
+ const filled = rows.map((r) => r[j]).filter((c) => c !== null && c !== undefined && c !== '');
216
+ return filled.length > 0 && filled.every(figure);
217
+ };
218
+ const nums = Array.from({ length: Math.max(cols.length, ...rows.map((r) => r.length)) }, (_, j) => numCol(j));
219
+ return (_jsxs("div", { className: "an-w-table", children: [str(part.title) && _jsx("div", { className: "an-w-title", children: str(part.title) }), _jsxs("table", { children: [cols.length > 0 && (_jsx("thead", { children: _jsx("tr", { children: cols.map((c, j) => (_jsx("th", { className: nums[j] ? 'is-num' : undefined, children: c }, j))) }) })), _jsx("tbody", { children: rows.map((r, i) => (_jsx("tr", { children: r.map((c, j) => (_jsx("td", { className: nums[j] ? 'is-num' : undefined, children: typeof c === 'number' ? ctx.number(c) : str(c) }, j))) }, i))) })] })] }));
104
220
  }
105
221
  case 'Chart':
106
222
  return _jsx(Chart, { part: part, ctx: ctx });
@@ -171,7 +287,11 @@ export function WidgetView(props) {
171
287
  return null;
172
288
  let moneyFormat;
173
289
  try {
174
- moneyFormat = new Intl.NumberFormat(locale, { style: 'currency', currency, maximumFractionDigits: 0 });
290
+ moneyFormat = new Intl.NumberFormat(locale, {
291
+ style: 'currency',
292
+ currency,
293
+ maximumFractionDigits: 0,
294
+ });
175
295
  }
176
296
  catch {
177
297
  moneyFormat = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });