@klarkxy/dsh-deep-view 0.0.0-stage → 0.1.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/lib/client.js ADDED
@@ -0,0 +1,3082 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@klarkxy/dsh-deep-view",
3
+ factory: (dshRequire) => {
4
+ var require = dshRequire;
5
+ var module = { exports: {} };
6
+ var exports = module.exports;
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ let react = require("react");
9
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ //#region src/core/formula.js
12
+ const FUNCTIONS = {
13
+ min: {
14
+ args: [1, Infinity],
15
+ run: (...values) => Math.min(...values.map(num))
16
+ },
17
+ max: {
18
+ args: [1, Infinity],
19
+ run: (...values) => Math.max(...values.map(num))
20
+ },
21
+ abs: {
22
+ args: [1, 1],
23
+ run: (value) => Math.abs(num(value))
24
+ },
25
+ floor: {
26
+ args: [1, 1],
27
+ run: (value) => Math.floor(num(value))
28
+ },
29
+ ceil: {
30
+ args: [1, 1],
31
+ run: (value) => Math.ceil(num(value))
32
+ },
33
+ sqrt: {
34
+ args: [1, 1],
35
+ run: (value) => Math.sqrt(num(value))
36
+ },
37
+ pow: {
38
+ args: [2, 2],
39
+ run: (base, power) => num(base) ** num(power)
40
+ },
41
+ round: {
42
+ args: [1, 2],
43
+ run: (value, digits = 0) => roundTo(num(value), digitsOf(digits))
44
+ },
45
+ clamp: {
46
+ args: [3, 3],
47
+ run: (value, low, high) => Math.min(Math.max(num(value), num(low)), num(high))
48
+ },
49
+ fixed: {
50
+ args: [1, 2],
51
+ run: (value, digits = 0) => roundTo(num(value), digitsOf(digits)).toFixed(digitsOf(digits))
52
+ },
53
+ format: {
54
+ args: [1, 2],
55
+ run: (value, digits = 0) => grouped$1(num(value), digitsOf(digits))
56
+ },
57
+ percent: {
58
+ args: [1, 2],
59
+ run: (value, digits = 0) => `${grouped$1(num(value) * 100, digitsOf(digits))}%`
60
+ }
61
+ };
62
+ const FORMULA_FUNCTIONS = Object.keys(FUNCTIONS);
63
+ const CONSTANTS = /* @__PURE__ */ new Map([["true", true], ["false", false]]);
64
+ const MAX_NESTING = 64;
65
+ var FormulaError = class extends Error {};
66
+ const fail = (message) => {
67
+ throw new FormulaError(message);
68
+ };
69
+ function num(value) {
70
+ if (typeof value === "number") return value;
71
+ if (typeof value === "boolean") return value ? 1 : 0;
72
+ const parsed = Number(value);
73
+ if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(parsed)) return parsed;
74
+ return fail(`${JSON.stringify(value)} is not a number`);
75
+ }
76
+ const digitsOf = (digits) => {
77
+ const value = num(digits);
78
+ if (!Number.isInteger(value) || value < 0 || value > 10) fail("digits must be a whole number from 0 to 10");
79
+ return value;
80
+ };
81
+ const roundTo = (value, digits) => {
82
+ const scale = 10 ** digits;
83
+ return Math.round(value * scale) / scale;
84
+ };
85
+ function grouped$1(value, digits) {
86
+ if (!Number.isFinite(value)) return String(value);
87
+ const [whole, part] = Math.abs(roundTo(value, digits)).toFixed(digits).split(".");
88
+ return `${value < 0 && Number(whole + (part ?? "")) !== 0 ? "-" : ""}${whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}${part ? `.${part}` : ""}`;
89
+ }
90
+ const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
91
+ const isName = (value) => typeof value === "string" && IDENT.test(value);
92
+ const OPERATORS = [
93
+ "==",
94
+ "!=",
95
+ "<=",
96
+ ">=",
97
+ "&&",
98
+ "||",
99
+ "+",
100
+ "-",
101
+ "*",
102
+ "/",
103
+ "%",
104
+ "<",
105
+ ">",
106
+ "!",
107
+ "?",
108
+ ":",
109
+ "(",
110
+ ")",
111
+ ","
112
+ ];
113
+ function tokenize(source) {
114
+ const tokens = [];
115
+ let at = 0;
116
+ while (at < source.length) {
117
+ const char = source[at];
118
+ if (/\s/.test(char)) {
119
+ at += 1;
120
+ continue;
121
+ }
122
+ const number = /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(source.slice(at));
123
+ if (number) {
124
+ tokens.push({
125
+ kind: "number",
126
+ value: Number(number[0]),
127
+ at
128
+ });
129
+ at += number[0].length;
130
+ continue;
131
+ }
132
+ const name = /^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(at));
133
+ if (name) {
134
+ tokens.push({
135
+ kind: "name",
136
+ value: name[0],
137
+ at
138
+ });
139
+ at += name[0].length;
140
+ continue;
141
+ }
142
+ if (char === "\"" || char === "'") {
143
+ let end = at + 1;
144
+ let text = "";
145
+ while (end < source.length && source[end] !== char) {
146
+ if (source[end] === "\\" && end + 1 < source.length) {
147
+ text += source[end + 1];
148
+ end += 2;
149
+ continue;
150
+ }
151
+ text += source[end];
152
+ end += 1;
153
+ }
154
+ if (end >= source.length) fail(`unclosed string at ${at + 1}`);
155
+ tokens.push({
156
+ kind: "string",
157
+ value: text,
158
+ at
159
+ });
160
+ at = end + 1;
161
+ continue;
162
+ }
163
+ const operator = OPERATORS.find((op) => source.startsWith(op, at));
164
+ if (operator === void 0) fail(`unexpected ${JSON.stringify(char)} at ${at + 1}`);
165
+ tokens.push({
166
+ kind: "op",
167
+ value: operator,
168
+ at
169
+ });
170
+ at += operator.length;
171
+ }
172
+ return tokens;
173
+ }
174
+ const BINARY = [
175
+ ["||"],
176
+ ["&&"],
177
+ ["==", "!="],
178
+ [
179
+ "<",
180
+ "<=",
181
+ ">",
182
+ ">="
183
+ ],
184
+ ["+", "-"],
185
+ [
186
+ "*",
187
+ "/",
188
+ "%"
189
+ ]
190
+ ];
191
+ function parse(source) {
192
+ const tokens = tokenize(source);
193
+ let index = 0;
194
+ let depth = 0;
195
+ const peek = () => tokens[index];
196
+ const isOp = (value) => peek()?.kind === "op" && peek().value === value;
197
+ const expect = (value) => {
198
+ if (!isOp(value)) fail(peek() ? `expected "${value}" at ${peek().at + 1}` : `expected "${value}" at the end`);
199
+ index += 1;
200
+ };
201
+ const nest = (read) => {
202
+ depth += 1;
203
+ if (depth > MAX_NESTING) fail(`nested more than ${MAX_NESTING} levels`);
204
+ const node = read();
205
+ depth -= 1;
206
+ return node;
207
+ };
208
+ const ternary = () => nest(() => {
209
+ const test = binary(0);
210
+ if (!isOp("?")) return test;
211
+ index += 1;
212
+ const yes = ternary();
213
+ expect(":");
214
+ return {
215
+ type: "if",
216
+ test,
217
+ yes,
218
+ no: ternary()
219
+ };
220
+ });
221
+ const binary = (level) => {
222
+ if (level === BINARY.length) return unary();
223
+ let left = binary(level + 1);
224
+ while (peek()?.kind === "op" && BINARY[level].includes(peek().value)) {
225
+ const op = peek().value;
226
+ index += 1;
227
+ left = {
228
+ type: "binary",
229
+ op,
230
+ left,
231
+ right: binary(level + 1)
232
+ };
233
+ }
234
+ return left;
235
+ };
236
+ const unary = () => {
237
+ if (isOp("-") || isOp("!") || isOp("+")) {
238
+ const op = peek().value;
239
+ index += 1;
240
+ return nest(() => ({
241
+ type: "unary",
242
+ op,
243
+ value: unary()
244
+ }));
245
+ }
246
+ return primary();
247
+ };
248
+ const primary = () => {
249
+ const token = peek();
250
+ if (token === void 0) fail("the formula ends too early");
251
+ index += 1;
252
+ if (token.kind === "number" || token.kind === "string") return {
253
+ type: "value",
254
+ value: token.value
255
+ };
256
+ if (token.kind === "op" && token.value === "(") {
257
+ const inner = ternary();
258
+ expect(")");
259
+ return inner;
260
+ }
261
+ if (token.kind !== "name") fail(`unexpected "${token.value}" at ${token.at + 1}`);
262
+ if (!isOp("(")) return CONSTANTS.has(token.value) ? {
263
+ type: "value",
264
+ value: CONSTANTS.get(token.value)
265
+ } : {
266
+ type: "name",
267
+ name: token.value
268
+ };
269
+ const fn = Object.hasOwn(FUNCTIONS, token.value) ? FUNCTIONS[token.value] : void 0;
270
+ if (fn === void 0) fail(`unknown function "${token.value}"; allowed: ${FORMULA_FUNCTIONS.join(", ")}`);
271
+ index += 1;
272
+ const args = [];
273
+ if (!isOp(")")) {
274
+ args.push(ternary());
275
+ while (isOp(",")) {
276
+ index += 1;
277
+ args.push(ternary());
278
+ }
279
+ }
280
+ expect(")");
281
+ if (args.length < fn.args[0] || args.length > fn.args[1]) fail(`${token.value}() takes ${fn.args[0] === fn.args[1] ? fn.args[0] : fn.args[1] === Infinity ? `at least ${fn.args[0]}` : `${fn.args[0]} or ${fn.args[1]}`} argument${fn.args[1] === 1 ? "" : "s"}; got ${args.length}`);
282
+ return {
283
+ type: "call",
284
+ name: token.value,
285
+ args
286
+ };
287
+ };
288
+ if (tokens.length === 0) fail("the formula is empty");
289
+ const tree = ternary();
290
+ if (index < tokens.length) fail(`unexpected "${peek().value}" at ${peek().at + 1}`);
291
+ return tree;
292
+ }
293
+ function namesIn(node, found = /* @__PURE__ */ new Set()) {
294
+ if (node.type === "name") found.add(node.name);
295
+ else if (node.type === "unary") namesIn(node.value, found);
296
+ else if (node.type === "binary") {
297
+ namesIn(node.left, found);
298
+ namesIn(node.right, found);
299
+ } else if (node.type === "if") {
300
+ namesIn(node.test, found);
301
+ namesIn(node.yes, found);
302
+ namesIn(node.no, found);
303
+ } else if (node.type === "call") for (const arg of node.args) namesIn(arg, found);
304
+ return found;
305
+ }
306
+ function compileFormula(source) {
307
+ if (typeof source !== "string") return { error: "expected a formula string" };
308
+ try {
309
+ const tree = parse(source);
310
+ return {
311
+ tree,
312
+ names: [...namesIn(tree)]
313
+ };
314
+ } catch (error) {
315
+ if (error instanceof FormulaError) return { error: error.message };
316
+ throw error;
317
+ }
318
+ }
319
+ function run(node, lookup) {
320
+ switch (node.type) {
321
+ case "value": return node.value;
322
+ case "name": return lookup(node.name);
323
+ case "unary": {
324
+ const value = run(node.value, lookup);
325
+ if (node.op === "!") return !truthy(value);
326
+ return node.op === "-" ? -num(value) : num(value);
327
+ }
328
+ case "if": return truthy(run(node.test, lookup)) ? run(node.yes, lookup) : run(node.no, lookup);
329
+ case "call": return FUNCTIONS[node.name].run(...node.args.map((arg) => run(arg, lookup)));
330
+ default: return binary(node, lookup);
331
+ }
332
+ }
333
+ const truthy = (value) => typeof value === "string" ? value !== "" : Boolean(value);
334
+ function binary(node, lookup) {
335
+ if (node.op === "&&") return truthy(run(node.left, lookup)) ? run(node.right, lookup) : false;
336
+ if (node.op === "||") {
337
+ const left = run(node.left, lookup);
338
+ return truthy(left) ? left : run(node.right, lookup);
339
+ }
340
+ const left = run(node.left, lookup);
341
+ const right = run(node.right, lookup);
342
+ switch (node.op) {
343
+ case "+": return typeof left === "string" || typeof right === "string" ? `${show(left)}${show(right)}` : num(left) + num(right);
344
+ case "-": return num(left) - num(right);
345
+ case "*": return num(left) * num(right);
346
+ case "/": return num(left) / num(right);
347
+ case "%": return num(left) % num(right);
348
+ case "==": return left === right;
349
+ case "!=": return left !== right;
350
+ case "<": return num(left) < num(right);
351
+ case "<=": return num(left) <= num(right);
352
+ case ">": return num(left) > num(right);
353
+ default: return num(left) >= num(right);
354
+ }
355
+ }
356
+ function show(value) {
357
+ if (typeof value === "number") return Number.isFinite(value) ? String(Number(value.toPrecision(12))) : String(value);
358
+ return String(value);
359
+ }
360
+ function createScope(inputs, lets) {
361
+ const done = /* @__PURE__ */ new Map();
362
+ const busy = /* @__PURE__ */ new Set();
363
+ const lookup = (name) => {
364
+ if (inputs.has(name)) return inputs.get(name);
365
+ if (done.has(name)) return done.get(name);
366
+ const tree = lets.get(name);
367
+ if (tree === void 0) fail(`unknown name "${name}"`);
368
+ if (busy.has(name)) fail(`"${name}" refers to itself`);
369
+ busy.add(name);
370
+ try {
371
+ const value = run(tree, lookup);
372
+ done.set(name, value);
373
+ return value;
374
+ } finally {
375
+ busy.delete(name);
376
+ }
377
+ };
378
+ return lookup;
379
+ }
380
+ function evaluate(tree, lookup) {
381
+ try {
382
+ return { value: run(tree, lookup) };
383
+ } catch (error) {
384
+ if (error instanceof FormulaError) return { error: error.message };
385
+ if (error instanceof RangeError) return { error: "the formula is too deep to work out" };
386
+ throw error;
387
+ }
388
+ }
389
+ //#endregion
390
+ //#region src/core/views.js
391
+ const LAYOUT = ["stack", "row"];
392
+ const DISPLAY = [
393
+ "text",
394
+ "metric",
395
+ "table",
396
+ "chart",
397
+ "progress",
398
+ "callout",
399
+ "sources"
400
+ ];
401
+ const INPUT_TYPES = [
402
+ "slider",
403
+ "choice",
404
+ "toggle",
405
+ "number"
406
+ ];
407
+ const ANSWERS = [
408
+ "options",
409
+ "confirm",
410
+ "form",
411
+ "submit"
412
+ ];
413
+ const COMPONENTS = [
414
+ ...LAYOUT,
415
+ ...DISPLAY,
416
+ ...INPUT_TYPES,
417
+ ...ANSWERS
418
+ ];
419
+ const TONES$1 = [
420
+ "good",
421
+ "warn",
422
+ "bad"
423
+ ];
424
+ const TEXT_TONES = ["muted"];
425
+ const CALLOUT_TONES$1 = [
426
+ "info",
427
+ "good",
428
+ "warn",
429
+ "bad"
430
+ ];
431
+ const CHART_KINDS = [
432
+ "bar",
433
+ "hbar",
434
+ "line",
435
+ "area",
436
+ "donut"
437
+ ];
438
+ const INTERACTIVE = new Set(ANSWERS);
439
+ const INPUTS = new Set(INPUT_TYPES);
440
+ const PROPS = {
441
+ stack: ["children", "let"],
442
+ row: ["children", "let"],
443
+ text: ["text", "tone"],
444
+ metric: [
445
+ "label",
446
+ "value",
447
+ "tone",
448
+ "caption"
449
+ ],
450
+ table: ["columns", "rows"],
451
+ chart: [
452
+ "kind",
453
+ "categories",
454
+ "series",
455
+ "unit",
456
+ "caption"
457
+ ],
458
+ progress: [
459
+ "label",
460
+ "percent",
461
+ "value",
462
+ "max",
463
+ "valueLabel",
464
+ "tone"
465
+ ],
466
+ callout: ["text", "tone"],
467
+ sources: ["items"],
468
+ slider: [
469
+ "id",
470
+ "label",
471
+ "min",
472
+ "max",
473
+ "step",
474
+ "value",
475
+ "unit"
476
+ ],
477
+ choice: [
478
+ "id",
479
+ "label",
480
+ "options",
481
+ "value"
482
+ ],
483
+ toggle: [
484
+ "id",
485
+ "label",
486
+ "value"
487
+ ],
488
+ number: [
489
+ "id",
490
+ "label",
491
+ "min",
492
+ "max",
493
+ "step",
494
+ "value",
495
+ "unit"
496
+ ],
497
+ options: [
498
+ "id",
499
+ "options",
500
+ "multi"
501
+ ],
502
+ confirm: [
503
+ "id",
504
+ "confirmLabel",
505
+ "cancelLabel"
506
+ ],
507
+ form: [
508
+ "id",
509
+ "fields",
510
+ "submitLabel"
511
+ ],
512
+ submit: ["id", "label"]
513
+ };
514
+ const MAX_DEPTH = 8;
515
+ const MAX_NODES = 64;
516
+ const MAX_CHILDREN = 16;
517
+ const MAX_TEXT = 2e3;
518
+ const MAX_CELL = 200;
519
+ const MAX_COLUMNS = 8;
520
+ const MAX_ROWS = 50;
521
+ const MAX_OPTIONS = 8;
522
+ const MAX_FIELDS = 6;
523
+ const RESERVED = /* @__PURE__ */ new Set(["true", "false"]);
524
+ const VIEW_BYTE_LIMIT = 8192;
525
+ const isRecord$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
526
+ const quoted = (value) => JSON.stringify(value);
527
+ const allowed = (list) => list.join(", ");
528
+ const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
529
+ const isScalar = (value) => typeof value === "string" || typeof value === "boolean" || isNumber(value);
530
+ const isFormula = (value) => isRecord$1(value) && Object.keys(value).length === 1 && Object.hasOwn(value, "=");
531
+ function viewBytes(title, view) {
532
+ return new TextEncoder().encode(`${JSON.stringify(view) ?? ""}${title ?? ""}`).length;
533
+ }
534
+ const NUMERIC = [
535
+ "value",
536
+ "percent",
537
+ "min",
538
+ "max",
539
+ "step",
540
+ "height"
541
+ ];
542
+ const numeric = (value) => typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value)) ? Number(value) : value;
543
+ function normalizeView(value) {
544
+ if (Array.isArray(value)) return value.map(normalizeView);
545
+ if (!isRecord$1(value)) return value;
546
+ const keys = Object.keys(value);
547
+ if (keys.length === 1 && keys[0] === "item") {
548
+ const inner = normalizeView(value.item);
549
+ if (Array.isArray(inner)) return inner;
550
+ }
551
+ const out = {};
552
+ for (const key of keys) Object.defineProperty(out, key, {
553
+ value: normalizeView(value[key]),
554
+ writable: true,
555
+ enumerable: true,
556
+ configurable: true
557
+ });
558
+ for (const field of NUMERIC) if (Object.hasOwn(out, field)) out[field] = numeric(out[field]);
559
+ if (Array.isArray(out.values)) out.values = out.values.map(numeric);
560
+ return out;
561
+ }
562
+ function validateView(view) {
563
+ const ctx = {
564
+ ids: /* @__PURE__ */ new Set(),
565
+ nodes: 0,
566
+ interactive: false,
567
+ inputs: /* @__PURE__ */ new Map(),
568
+ lets: /* @__PURE__ */ new Map(),
569
+ formulas: []
570
+ };
571
+ const error = checkNode(view, "view", 1, ctx) ?? checkFormulas(ctx);
572
+ return error === void 0 ? {
573
+ ok: true,
574
+ interactive: ctx.interactive
575
+ } : {
576
+ ok: false,
577
+ error
578
+ };
579
+ }
580
+ function checkShownView(title, sent) {
581
+ let bytes;
582
+ try {
583
+ bytes = viewBytes(title, sent);
584
+ } catch {
585
+ return { error: "The view is not JSON data." };
586
+ }
587
+ if (bytes > 8192) return { error: `The view is ${bytes} bytes; the limit is ${VIEW_BYTE_LIMIT}.` };
588
+ if (title !== void 0 && title !== null && typeof title !== "string") return { error: "title: expected a string" };
589
+ const named = String(title ?? "").trim();
590
+ const view = normalizeView(sent);
591
+ const checked = validateView(view);
592
+ if (!checked.ok) return { error: checked.error };
593
+ return {
594
+ title: named,
595
+ view,
596
+ interactive: checked.interactive
597
+ };
598
+ }
599
+ function checkNode(node, path, depth, ctx) {
600
+ if (!isRecord$1(node)) return `${path}: expected a component object`;
601
+ if (depth > MAX_DEPTH) return `${path}: ${depth} levels exceeds the limit of ${MAX_DEPTH}`;
602
+ ctx.nodes += 1;
603
+ if (ctx.nodes > MAX_NODES) return `${path}: ${ctx.nodes} components exceeds the limit of ${MAX_NODES}`;
604
+ const type = node.type;
605
+ if (!COMPONENTS.includes(type)) return `${path}.type: unknown component ${quoted(type)}; allowed: ${allowed(COMPONENTS)}`;
606
+ const props = PROPS[type];
607
+ for (const key of Object.keys(node)) {
608
+ if (key === "type" || props.includes(key)) continue;
609
+ return `${path}.${key}: unknown property ${quoted(key)}; allowed: ${allowed(props)}`;
610
+ }
611
+ if (INTERACTIVE.has(type)) ctx.interactive = true;
612
+ switch (type) {
613
+ case "stack":
614
+ case "row": return checkLayout(node, path, depth, ctx);
615
+ case "text": return checkText(node, path, ctx);
616
+ case "metric": return checkMetric(node, path, ctx);
617
+ case "table": return checkTable(node, path, ctx);
618
+ case "chart": return checkChart(node, path, ctx);
619
+ case "progress": return checkProgress(node, path, ctx);
620
+ case "callout": return checkCallout(node, path, ctx);
621
+ case "sources": return checkSources(node, path);
622
+ case "slider":
623
+ case "number": return checkNumberInput(node, path, ctx);
624
+ case "choice": return checkChoice(node, path, ctx);
625
+ case "toggle": return checkToggle(node, path, ctx);
626
+ case "options": return checkOptions(node, path, ctx);
627
+ case "confirm": return checkConfirm(node, path, ctx);
628
+ case "form": return checkForm(node, path, ctx);
629
+ default: return checkSubmit(node, path, ctx);
630
+ }
631
+ }
632
+ function checkLayout(node, path, depth, ctx) {
633
+ if (node.let !== void 0) {
634
+ if (depth !== 1) return `${path}.let: only the outermost component can define names`;
635
+ if (!isRecord$1(node.let)) return `${path}.let: expected an object of names to formulas`;
636
+ for (const [name, source] of Object.entries(node.let)) {
637
+ if (!isName(name) || RESERVED.has(name)) return `${path}.let.${name}: a name is letters, digits and _, and starts with a letter or _`;
638
+ if (typeof source !== "string") return `${path}.let.${name}: expected a formula string`;
639
+ ctx.lets.set(name, {
640
+ source,
641
+ path: `${path}.let.${name}`
642
+ });
643
+ }
644
+ }
645
+ if (!Array.isArray(node.children)) return `${path}.children: expected an array of 1 to ${MAX_CHILDREN} components`;
646
+ if (node.children.length < 1 || node.children.length > MAX_CHILDREN) return `${path}.children: expected 1 to ${MAX_CHILDREN} children; got ${node.children.length}`;
647
+ for (let index = 0; index < node.children.length; index += 1) {
648
+ const error = checkNode(node.children[index], `${path}.children[${index}]`, depth + 1, ctx);
649
+ if (error !== void 0) return error;
650
+ }
651
+ }
652
+ function checkValue(node, key, path, ctx, kind, { required = false, max = MAX_TEXT } = {}) {
653
+ const value = node[key];
654
+ const at = `${path}.${key}`;
655
+ if (value === void 0) return required ? `${at}: ${expected(kind)}` : void 0;
656
+ if (isFormula(value)) {
657
+ ctx.formulas.push({
658
+ source: value["="],
659
+ path: at,
660
+ kind
661
+ });
662
+ return;
663
+ }
664
+ if (kind === "number") return isNumber(value) ? void 0 : `${at}: ${expected(kind)}`;
665
+ if (Array.isArray(kind)) return kind.includes(value) ? void 0 : `${at}: unknown tone ${quoted(value)}; allowed: ${allowed(kind)}`;
666
+ if (kind === "string") {
667
+ if (typeof value !== "string") return `${at}: ${expected(kind)}`;
668
+ return value.length > max ? `${at}: ${value.length} characters exceeds the limit of ${max}` : void 0;
669
+ }
670
+ if (!isScalar(value)) return `${at}: ${expected(kind)}`;
671
+ }
672
+ const expected = (kind) => kind === "number" ? "expected a number" : kind === "string" ? "expected a string" : Array.isArray(kind) ? `expected one of ${allowed(kind)}` : "expected a string or a number";
673
+ function checkText(node, path, ctx) {
674
+ return checkValue(node, "text", path, ctx, "string", { required: true }) ?? checkValue(node, "tone", path, ctx, TEXT_TONES);
675
+ }
676
+ function checkMetric(node, path, ctx) {
677
+ if (typeof node.label !== "string") return `${path}.label: expected a string`;
678
+ if (node.value === void 0) return `${path}.value: expected a string or a number`;
679
+ if (typeof node.value === "number" && !Number.isFinite(node.value)) return `${path}.value: expected a finite number`;
680
+ return checkValue(node, "value", path, ctx, "scalar") ?? checkValue(node, "tone", path, ctx, TONES$1) ?? checkValue(node, "caption", path, ctx, "string");
681
+ }
682
+ function checkTable(node, path, ctx) {
683
+ if (!Array.isArray(node.columns)) return `${path}.columns: expected an array of at most ${MAX_COLUMNS} columns`;
684
+ if (node.columns.length > MAX_COLUMNS) return `${path}.columns: ${node.columns.length} columns exceeds the limit of ${MAX_COLUMNS}`;
685
+ for (let index = 0; index < node.columns.length; index += 1) {
686
+ const column = node.columns[index];
687
+ const at = `${path}.columns[${index}]`;
688
+ if (!isRecord$1(column)) return `${at}: expected a column object`;
689
+ for (const key of Object.keys(column)) if (key !== "key" && key !== "label") return `${at}.${key}: unknown property ${quoted(key)}; allowed: key, label`;
690
+ if (typeof column.key !== "string" || column.key === "") return `${at}.key: expected a non-empty string`;
691
+ if (typeof column.label !== "string") return `${at}.label: expected a string`;
692
+ }
693
+ if (!Array.isArray(node.rows)) return `${path}.rows: expected an array of at most ${MAX_ROWS} rows`;
694
+ if (node.rows.length > MAX_ROWS) return `${path}.rows: ${node.rows.length} rows exceeds the limit of ${MAX_ROWS}`;
695
+ for (let row = 0; row < node.rows.length; row += 1) {
696
+ const cells = node.rows[row];
697
+ if (!Array.isArray(cells)) return `${path}.rows[${row}]: expected an array of cells`;
698
+ for (let column = 0; column < cells.length; column += 1) {
699
+ const at = `${path}.rows[${row}][${column}]`;
700
+ if (isFormula(cells[column])) {
701
+ ctx.formulas.push({
702
+ source: cells[column]["="],
703
+ path: at,
704
+ kind: "scalar"
705
+ });
706
+ continue;
707
+ }
708
+ const text = cellText$1(cells[column]);
709
+ if (text.length > MAX_CELL) return `${at}: ${text.length} characters exceeds the limit of ${MAX_CELL}`;
710
+ }
711
+ }
712
+ }
713
+ function checkChart(node, path, ctx) {
714
+ if (node.kind !== void 0 && !CHART_KINDS.includes(node.kind)) return `${path}.kind: unknown chart ${quoted(node.kind)}; allowed: ${allowed(CHART_KINDS)}`;
715
+ if (!Array.isArray(node.categories) || node.categories.length === 0) return `${path}.categories: expected a non-empty array of labels`;
716
+ for (let index = 0; index < node.categories.length; index += 1) {
717
+ const label = node.categories[index];
718
+ if (typeof label !== "string" && !isNumber(label)) return `${path}.categories[${index}]: expected a string`;
719
+ }
720
+ if (!Array.isArray(node.series) || node.series.length === 0) return `${path}.series: expected a non-empty array of { name?, values }`;
721
+ if (node.kind === "donut" && node.series.length > 1) return `${path}.series: a donut takes one series; got ${node.series.length}`;
722
+ for (let index = 0; index < node.series.length; index += 1) {
723
+ const series = node.series[index];
724
+ const at = `${path}.series[${index}]`;
725
+ if (!isRecord$1(series)) return `${at}: expected a series object`;
726
+ for (const key of Object.keys(series)) if (key !== "name" && key !== "values") return `${at}.${key}: unknown property ${quoted(key)}; allowed: name, values`;
727
+ if (series.name !== void 0 && typeof series.name !== "string") return `${at}.name: expected a string`;
728
+ if (!Array.isArray(series.values)) return `${at}.values: expected an array of numbers`;
729
+ if (series.values.length !== node.categories.length) return `${at}.values: ${series.values.length} values for ${node.categories.length} categories`;
730
+ for (let item = 0; item < series.values.length; item += 1) {
731
+ const value = series.values[item];
732
+ if (isFormula(value)) ctx.formulas.push({
733
+ source: value["="],
734
+ path: `${at}.values[${item}]`,
735
+ kind: "number"
736
+ });
737
+ else if (!isNumber(value)) return `${at}.values[${item}]: expected a number`;
738
+ }
739
+ }
740
+ if (node.unit !== void 0 && typeof node.unit !== "string") return `${path}.unit: expected a string`;
741
+ if (node.caption !== void 0 && typeof node.caption !== "string") return `${path}.caption: expected a string`;
742
+ }
743
+ function checkProgress(node, path, ctx) {
744
+ if (node.label !== void 0 && typeof node.label !== "string") return `${path}.label: expected a string`;
745
+ if (node.percent === void 0 === (node.value === void 0)) return `${path}: give percent (0 to 100), or value with max`;
746
+ if (node.percent !== void 0 && !isFormula(node.percent)) {
747
+ if (typeof node.percent !== "number" || !Number.isFinite(node.percent) || node.percent < 0 || node.percent > 100) return `${path}.percent: expected a number from 0 to 100`;
748
+ }
749
+ if (node.value !== void 0 && node.max === void 0) return `${path}.max: expected a number with value`;
750
+ if (node.value === void 0 && node.max !== void 0) return `${path}.value: expected a number with max`;
751
+ return checkValue(node, "percent", path, ctx, "number") ?? checkValue(node, "value", path, ctx, "number") ?? checkValue(node, "max", path, ctx, "number") ?? checkValue(node, "valueLabel", path, ctx, "scalar") ?? checkValue(node, "tone", path, ctx, TONES$1);
752
+ }
753
+ function checkCallout(node, path, ctx) {
754
+ return checkValue(node, "text", path, ctx, "string", { required: true }) ?? checkValue(node, "tone", path, ctx, CALLOUT_TONES$1);
755
+ }
756
+ function checkSources(node, path) {
757
+ if (!Array.isArray(node.items) || node.items.length === 0) return `${path}.items: expected a non-empty array of { label, url }`;
758
+ for (let index = 0; index < node.items.length; index += 1) {
759
+ const item = node.items[index];
760
+ const at = `${path}.items[${index}]`;
761
+ if (!isRecord$1(item)) return `${at}: expected a { label, url } object`;
762
+ for (const key of Object.keys(item)) if (key !== "label" && key !== "url") return `${at}.${key}: unknown property ${quoted(key)}; allowed: label, url`;
763
+ if (typeof item.label !== "string" || item.label.trim() === "") return `${at}.label: expected a non-empty string`;
764
+ if (typeof item.url !== "string" || !/^https?:\/\/[^\s]+$/i.test(item.url)) return `${at}.url: expected an http(s) address`;
765
+ }
766
+ }
767
+ function checkInputId(node, path, ctx) {
768
+ if (!isName(node.id) || RESERVED.has(node.id)) return `${path}.id: an input id is letters, digits and _, and starts with a letter or _, so formulas can use it`;
769
+ if (ctx.ids.has(node.id)) return `${path}.id: duplicate id ${quoted(node.id)}`;
770
+ ctx.ids.add(node.id);
771
+ if (typeof node.label !== "string") return `${path}.label: expected a string`;
772
+ }
773
+ function checkNumberInput(node, path, ctx) {
774
+ const idError = checkInputId(node, path, ctx);
775
+ if (idError !== void 0) return idError;
776
+ for (const key of [
777
+ "min",
778
+ "max",
779
+ "step"
780
+ ]) if (node[key] !== void 0 && !isNumber(node[key])) return `${path}.${key}: expected a number`;
781
+ if (node.type === "slider" && (node.min === void 0 || node.max === void 0)) return `${path}: a slider needs min and max`;
782
+ if (node.min !== void 0 && node.max !== void 0 && node.max <= node.min) return `${path}.max: expected more than min (${node.min})`;
783
+ if (node.step !== void 0 && node.step <= 0) return `${path}.step: expected more than 0`;
784
+ if (!isNumber(node.value)) return `${path}.value: expected a number`;
785
+ if (node.min !== void 0 && node.value < node.min || node.max !== void 0 && node.value > node.max) return `${path}.value: expected a number from ${node.min ?? "-∞"} to ${node.max ?? "∞"}`;
786
+ if (node.unit !== void 0 && typeof node.unit !== "string") return `${path}.unit: expected a string`;
787
+ ctx.inputs.set(node.id, node.value);
788
+ }
789
+ function checkChoice(node, path, ctx) {
790
+ const idError = checkInputId(node, path, ctx);
791
+ if (idError !== void 0) return idError;
792
+ if (!Array.isArray(node.options) || node.options.length === 0) return `${path}.options: expected a non-empty array of { label, value }`;
793
+ const labels = /* @__PURE__ */ new Set();
794
+ const values = /* @__PURE__ */ new Set();
795
+ for (let index = 0; index < node.options.length; index += 1) {
796
+ const option = node.options[index];
797
+ const at = `${path}.options[${index}]`;
798
+ if (!isRecord$1(option)) return `${at}: expected a { label, value } object`;
799
+ for (const key of Object.keys(option)) if (key !== "label" && key !== "value") return `${at}.${key}: unknown property ${quoted(key)}; allowed: label, value`;
800
+ if (typeof option.label !== "string" || option.label.trim() === "") return `${at}.label: expected a non-empty string`;
801
+ if (labels.has(option.label)) return `${at}.label: duplicate label ${quoted(option.label)}`;
802
+ labels.add(option.label);
803
+ const value = option.value ?? option.label;
804
+ if (!isScalar(value)) return `${at}.value: expected a string, a number or true/false`;
805
+ if (values.has(value)) return `${at}.value: duplicate value ${quoted(value)}`;
806
+ values.add(value);
807
+ }
808
+ const chosen = node.value ?? choiceValue(node.options[0]);
809
+ if (!values.has(chosen)) return `${path}.value: expected one of the options' values`;
810
+ ctx.inputs.set(node.id, chosen);
811
+ }
812
+ function checkToggle(node, path, ctx) {
813
+ const idError = checkInputId(node, path, ctx);
814
+ if (idError !== void 0) return idError;
815
+ if (node.value !== void 0 && typeof node.value !== "boolean") return `${path}.value: expected true or false`;
816
+ ctx.inputs.set(node.id, node.value === true);
817
+ }
818
+ function checkOptions(node, path, ctx) {
819
+ const idError = checkId(node, path, ctx);
820
+ if (idError !== void 0) return idError;
821
+ if (node.multi !== void 0 && typeof node.multi !== "boolean") return `${path}.multi: expected a boolean`;
822
+ if (!Array.isArray(node.options)) return `${path}.options: expected an array of 1 to ${MAX_OPTIONS} options`;
823
+ if (node.options.length < 1 || node.options.length > MAX_OPTIONS) return `${path}.options: expected 1 to ${MAX_OPTIONS} options; got ${node.options.length}`;
824
+ const labels = /* @__PURE__ */ new Set();
825
+ for (let index = 0; index < node.options.length; index += 1) {
826
+ const option = node.options[index];
827
+ const at = `${path}.options[${index}]`;
828
+ if (!isRecord$1(option)) return `${at}: expected an option object`;
829
+ for (const key of Object.keys(option)) if (key !== "label" && key !== "description") return `${at}.${key}: unknown property ${quoted(key)}; allowed: label, description`;
830
+ if (typeof option.label !== "string" || option.label.trim() === "") return `${at}.label: expected a non-empty string`;
831
+ if (labels.has(option.label)) return `${at}.label: duplicate label ${quoted(option.label)}`;
832
+ labels.add(option.label);
833
+ if (option.description !== void 0 && typeof option.description !== "string") return `${at}.description: expected a string`;
834
+ }
835
+ }
836
+ function checkConfirm(node, path, ctx) {
837
+ const idError = checkId(node, path, ctx);
838
+ if (idError !== void 0) return idError;
839
+ if (node.confirmLabel !== void 0 && typeof node.confirmLabel !== "string") return `${path}.confirmLabel: expected a string`;
840
+ if (node.cancelLabel !== void 0 && typeof node.cancelLabel !== "string") return `${path}.cancelLabel: expected a string`;
841
+ }
842
+ function checkForm(node, path, ctx) {
843
+ const idError = checkId(node, path, ctx);
844
+ if (idError !== void 0) return idError;
845
+ if (node.submitLabel !== void 0 && typeof node.submitLabel !== "string") return `${path}.submitLabel: expected a string`;
846
+ if (!Array.isArray(node.fields)) return `${path}.fields: expected an array of at most ${MAX_FIELDS} fields`;
847
+ if (node.fields.length > MAX_FIELDS) return `${path}.fields: ${node.fields.length} fields exceeds the limit of ${MAX_FIELDS}`;
848
+ const seen = /* @__PURE__ */ new Set();
849
+ for (let index = 0; index < node.fields.length; index += 1) {
850
+ const field = node.fields[index];
851
+ const at = `${path}.fields[${index}]`;
852
+ if (!isRecord$1(field)) return `${at}: expected a field object`;
853
+ for (const key of Object.keys(field)) if (key !== "id" && key !== "label" && key !== "placeholder") return `${at}.${key}: unknown property ${quoted(key)}; allowed: id, label, placeholder`;
854
+ if (typeof field.id !== "string" || field.id === "") return `${at}.id: expected a non-empty string`;
855
+ if (field.id === "__proto__") return `${at}.id: "__proto__" cannot be an id`;
856
+ if (seen.has(field.id)) return `${at}.id: duplicate field id ${quoted(field.id)}`;
857
+ seen.add(field.id);
858
+ if (typeof field.label !== "string") return `${at}.label: expected a string`;
859
+ if (field.placeholder !== void 0 && typeof field.placeholder !== "string") return `${at}.placeholder: expected a string`;
860
+ }
861
+ }
862
+ function checkSubmit(node, path, ctx) {
863
+ const idError = checkId(node, path, ctx);
864
+ if (idError !== void 0) return idError;
865
+ if (node.label !== void 0 && typeof node.label !== "string") return `${path}.label: expected a string`;
866
+ }
867
+ function checkId(node, path, ctx) {
868
+ if (typeof node.id !== "string" || node.id === "") return `${path}.id: expected a non-empty string`;
869
+ if (node.id === "__proto__") return `${path}.id: "__proto__" cannot be an id`;
870
+ if (ctx.ids.has(node.id)) return `${path}.id: duplicate id ${quoted(node.id)}`;
871
+ ctx.ids.add(node.id);
872
+ }
873
+ function checkFormulas(ctx) {
874
+ const known = /* @__PURE__ */ new Set([...ctx.inputs.keys(), ...ctx.lets.keys()]);
875
+ const lets = /* @__PURE__ */ new Map();
876
+ for (const [name, entry] of ctx.lets) {
877
+ if (ctx.inputs.has(name)) return `${entry.path}: ${quoted(name)} is already an input id`;
878
+ const compiled = compileFormula(entry.source);
879
+ if (compiled.error !== void 0) return `${entry.path}: ${compiled.error}`;
880
+ const unknown = compiled.names.find((item) => !known.has(item));
881
+ if (unknown !== void 0) return `${entry.path}: unknown name ${quoted(unknown)}; known: ${allowed([...known]) || "(none)"}`;
882
+ lets.set(name, compiled.tree);
883
+ }
884
+ const lookup = createScope(ctx.inputs, lets);
885
+ for (const [name, entry] of ctx.lets) {
886
+ const result = evaluate(lets.get(name), lookup);
887
+ if (result.error !== void 0) return `${entry.path}: ${result.error}`;
888
+ }
889
+ for (const formula of ctx.formulas) {
890
+ const compiled = compileFormula(formula.source);
891
+ if (compiled.error !== void 0) return `${formula.path}: ${compiled.error}`;
892
+ const unknown = compiled.names.find((item) => !known.has(item));
893
+ if (unknown !== void 0) return `${formula.path}: unknown name ${quoted(unknown)}; known: ${allowed([...known]) || "(none)"}`;
894
+ const result = evaluate(compiled.tree, lookup);
895
+ if (result.error !== void 0) return `${formula.path}: ${result.error}`;
896
+ const problem = kindProblem(result.value, formula.kind);
897
+ if (problem !== void 0) return `${formula.path}: the formula gives ${quoted(result.value)}; ${problem}`;
898
+ }
899
+ }
900
+ function kindProblem(value, kind) {
901
+ if (kind === "number") return isNumber(value) ? void 0 : "expected a number";
902
+ if (Array.isArray(kind)) return kind.includes(value) ? void 0 : `expected one of ${allowed(kind)}`;
903
+ if (kind === "string") return isScalar(value) ? void 0 : "expected text";
904
+ return isScalar(value) ? void 0 : "expected a string or a number";
905
+ }
906
+ function cellText$1(value) {
907
+ if (typeof value === "string") return value;
908
+ const text = JSON.stringify(value);
909
+ return typeof text === "string" ? text : "";
910
+ }
911
+ const compiled = /* @__PURE__ */ new WeakMap();
912
+ const treeOf = (formula) => {
913
+ let entry = compiled.get(formula);
914
+ if (entry === void 0) {
915
+ entry = compileFormula(formula["="]);
916
+ compiled.set(formula, entry);
917
+ }
918
+ return entry;
919
+ };
920
+ const choiceValue = (option) => option?.value ?? option?.label;
921
+ function walk(node, visit) {
922
+ if (!isRecord$1(node)) return;
923
+ visit(node);
924
+ if (LAYOUT.includes(node.type) && Array.isArray(node.children)) for (const child of node.children) walk(child, visit);
925
+ }
926
+ function viewInputs(view) {
927
+ const found = [];
928
+ walk(view, (node) => {
929
+ if (INPUTS.has(node.type)) found.push(node);
930
+ });
931
+ return found;
932
+ }
933
+ function inputDefault(node) {
934
+ if (node.type === "toggle") return node.value === true;
935
+ if (node.type === "choice") return node.value ?? choiceValue(Array.isArray(node.options) ? node.options[0] : void 0);
936
+ return node.value;
937
+ }
938
+ function viewScope(view, values = {}) {
939
+ const inputs = /* @__PURE__ */ new Map();
940
+ for (const node of viewInputs(view)) inputs.set(node.id, Object.hasOwn(values, node.id) ? values[node.id] : inputDefault(node));
941
+ const lets = /* @__PURE__ */ new Map();
942
+ for (const [name, source] of Object.entries(isRecord$1(view?.let) ? view.let : {})) {
943
+ const entry = compileFormula(source);
944
+ if (entry.tree !== void 0) lets.set(name, entry.tree);
945
+ }
946
+ return createScope(inputs, lets);
947
+ }
948
+ function resolve(value, lookup, fallback = void 0) {
949
+ if (!isFormula(value)) return value;
950
+ const entry = treeOf(value);
951
+ if (entry.tree === void 0) return fallback;
952
+ const result = evaluate(entry.tree, lookup);
953
+ return result.error === void 0 ? result.value : fallback;
954
+ }
955
+ const asText = (value) => value === void 0 || value === null ? "" : show(value);
956
+ const choiceLabel = (node, value) => (Array.isArray(node.options) ? node.options : []).find((option) => choiceValue(option) === value)?.label ?? asText(value);
957
+ function inputText(node, value) {
958
+ if (node.type === "toggle") return value === true ? "on" : "off";
959
+ if (node.type === "choice") return choiceLabel(node, value);
960
+ return `${asText(value)}${node.unit ? ` ${node.unit}` : ""}`;
961
+ }
962
+ function inputsSummary(view, inputs) {
963
+ if (!isRecord$1(inputs)) return "";
964
+ return viewInputs(view).filter((node) => Object.hasOwn(inputs, node.id)).map((node) => `${node.label || node.id}=${inputText(node, inputs[node.id])}`).join("; ");
965
+ }
966
+ function interactiveNodes(node, found = []) {
967
+ walk(node, (item) => {
968
+ if (INTERACTIVE.has(item.type)) found.push(item);
969
+ });
970
+ return found;
971
+ }
972
+ function validateViewAnswer(view, action, values) {
973
+ const nodes = interactiveNodes(view);
974
+ if (typeof action !== "string" || action === "") return { error: `action: expected an interactive component id; allowed: ${allowed(nodes.map((node) => node.id))}` };
975
+ const node = nodes.find((item) => item.id === action);
976
+ if (node === void 0) return { error: `action: no interactive component with id ${quoted(action)}; allowed: ${allowed(nodes.map((item) => item.id))}` };
977
+ if (!isRecord$1(values)) return { error: "values: expected an object" };
978
+ const inputs = validateInputs(view, values.inputs);
979
+ if (inputs.error !== void 0) return inputs;
980
+ const extra = inputs.inputs === void 0 ? {} : { inputs: inputs.inputs };
981
+ if (node.type === "submit") return { ...extra };
982
+ const own = node.type === "options" ? validateOptions(node, values) : node.type === "confirm" ? validateConfirm(values) : validateForm(node, values);
983
+ return own.error === void 0 ? {
984
+ ...own,
985
+ ...extra
986
+ } : own;
987
+ }
988
+ function validateInputs(view, sent) {
989
+ const nodes = viewInputs(view);
990
+ if (nodes.length === 0) return sent === void 0 ? {} : { error: "values.inputs: this view has no inputs" };
991
+ if (sent !== void 0 && !isRecord$1(sent)) return { error: "values.inputs: expected an object of input ids to values" };
992
+ const given = sent ?? {};
993
+ const ids = nodes.map((node) => node.id);
994
+ const unknown = Object.keys(given).find((key) => !ids.includes(key));
995
+ if (unknown !== void 0) return { error: `values.inputs.${unknown}: unknown input; allowed: ${allowed(ids)}` };
996
+ const inputs = {};
997
+ for (const node of nodes) {
998
+ const value = Object.hasOwn(given, node.id) ? given[node.id] : inputDefault(node);
999
+ const at = `values.inputs.${node.id}`;
1000
+ if (node.type === "toggle" && typeof value !== "boolean") return { error: `${at}: expected true or false` };
1001
+ if (node.type === "choice" && !(Array.isArray(node.options) ? node.options : []).some((option) => choiceValue(option) === value)) return { error: `${at}: expected one of the options' values` };
1002
+ if (node.type === "slider" || node.type === "number") {
1003
+ if (!isNumber(value)) return { error: `${at}: expected a number` };
1004
+ if (node.min !== void 0 && value < node.min || node.max !== void 0 && value > node.max) return { error: `${at}: expected a number from ${node.min ?? "-∞"} to ${node.max ?? "∞"}` };
1005
+ }
1006
+ inputs[node.id] = value;
1007
+ }
1008
+ return { inputs };
1009
+ }
1010
+ function validateOptions(node, values) {
1011
+ if (!Array.isArray(values.selected)) return { error: "values.selected: expected an array of option labels" };
1012
+ const labels = new Set((node.options ?? []).map((option) => option?.label));
1013
+ const picked = values.selected.filter((label) => typeof label === "string" && labels.has(label));
1014
+ const selected = node.multi === true ? [...new Set(picked)] : picked.slice(0, 1);
1015
+ if (selected.length === 0) return { error: "values.selected: select at least one option" };
1016
+ return { selected };
1017
+ }
1018
+ function validateConfirm(values) {
1019
+ if (typeof values.confirmed !== "boolean") return { error: "values.confirmed: expected a boolean" };
1020
+ return { confirmed: values.confirmed };
1021
+ }
1022
+ function validateForm(node, values) {
1023
+ if (!isRecord$1(values.fields)) return { error: "values.fields: expected an object of field ids to strings" };
1024
+ const declared = (node.fields ?? []).map((field) => field?.id).filter((id) => typeof id === "string");
1025
+ const allowedIds = new Set(declared);
1026
+ const fields = {};
1027
+ for (const [key, value] of Object.entries(values.fields)) {
1028
+ if (!allowedIds.has(key)) return { error: `values.fields.${key}: unknown field; allowed: ${allowed(declared)}` };
1029
+ if (typeof value !== "string") return { error: `values.fields.${key}: expected a string` };
1030
+ if (value.length > MAX_TEXT) return { error: `values.fields.${key}: ${value.length} characters exceeds the limit of ${MAX_TEXT}` };
1031
+ fields[key] = value;
1032
+ }
1033
+ return { fields };
1034
+ }
1035
+ //#endregion
1036
+ //#region src/contracts.ts
1037
+ const PLUGIN_NAME = "@klarkxy/dsh-deep-view";
1038
+ const RPC_CHANNEL = "/dsh-deep-view";
1039
+ const VIEW_OWNER = "dsh-deep-view";
1040
+ function isRecord(value) {
1041
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1042
+ }
1043
+ //#endregion
1044
+ //#region src/ui.tsx
1045
+ const english = (key, params) => key.replace(/\{(\w+)\}/g, (_, name) => String(params?.[name] ?? `{${name}}`));
1046
+ function LineIcon({ paths }) {
1047
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1048
+ width: "15",
1049
+ height: "15",
1050
+ viewBox: "0 0 20 20",
1051
+ fill: "none",
1052
+ stroke: "currentColor",
1053
+ strokeWidth: "1.5",
1054
+ strokeLinecap: "round",
1055
+ strokeLinejoin: "round",
1056
+ "aria-hidden": "true",
1057
+ children: paths.map((d, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d }, index))
1058
+ });
1059
+ }
1060
+ const DotsIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineIcon, { paths: ["M5 10h.01M10 10h.01M15 10h.01"] });
1061
+ const CheckIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineIcon, { paths: ["M4.5 10.5l3.5 3.5 7.5-8"] });
1062
+ const CopyIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineIcon, { paths: ["M8.5 7h6A1.5 1.5 0 0 1 16 8.5v6a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 7 14.5v-6A1.5 1.5 0 0 1 8.5 7z", "M4 12.5V5.5A1.5 1.5 0 0 1 5.5 4H13"] });
1063
+ const TableIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineIcon, { paths: ["M4.5 4h11A1.5 1.5 0 0 1 17 5.5v9a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 3 14.5v-9A1.5 1.5 0 0 1 4.5 4z", "M3 8.5h14M8 8.5V16"] });
1064
+ const InfoIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineIcon, { paths: [
1065
+ "M10 3.5a6.5 6.5 0 1 1 0 13 6.5 6.5 0 0 1 0-13z",
1066
+ "M10 9.25v4",
1067
+ "M10 6.6h.01"
1068
+ ] });
1069
+ const LinkIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1070
+ width: "12",
1071
+ height: "12",
1072
+ viewBox: "0 0 20 20",
1073
+ fill: "none",
1074
+ stroke: "currentColor",
1075
+ strokeWidth: "1.8",
1076
+ strokeLinecap: "round",
1077
+ strokeLinejoin: "round",
1078
+ "aria-hidden": "true",
1079
+ children: [
1080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8.5 11.5l3-3" }),
1081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M7 9.5L5.5 11a2.5 2.5 0 0 0 3.5 3.5L10.5 13" }),
1082
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13 10.5l1.5-1.5A2.5 2.5 0 0 0 11 5.5L9.5 7" })
1083
+ ]
1084
+ });
1085
+ const keyLetter = (index) => String.fromCharCode(65 + index);
1086
+ function Segmented({ label, value, className, children }) {
1087
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1088
+ className: className ? `dvui-seg ${className}` : "dvui-seg",
1089
+ role: "group",
1090
+ "aria-label": label,
1091
+ "data-active": value,
1092
+ children
1093
+ });
1094
+ }
1095
+ //#endregion
1096
+ //#region src/chart.tsx
1097
+ const PALETTE = [
1098
+ "var(--dsw-alias-state-business-primary)",
1099
+ "var(--dsw-alias-brand-primary)",
1100
+ "var(--dsw-alias-state-success-primary)",
1101
+ "var(--dsw-alias-state-warn-primary)",
1102
+ "var(--dsw-alias-state-error-primary)"
1103
+ ];
1104
+ const colorAt = (index) => PALETTE[index % PALETTE.length];
1105
+ const TICK = 10;
1106
+ const LABEL = 11;
1107
+ const textWidth = (text, size) => [...String(text)].reduce((sum, char) => sum + (char.charCodeAt(0) > 11904 ? size : size * .58), 0);
1108
+ const fit = (text, room, size) => {
1109
+ if (textWidth(text, size) <= room) return String(text);
1110
+ let out = "";
1111
+ for (const char of String(text)) {
1112
+ if (textWidth(`${out}${char}…`, size) > room) break;
1113
+ out += char;
1114
+ }
1115
+ return out === "" ? "" : `${out}…`;
1116
+ };
1117
+ function formatNumber(value) {
1118
+ if (!Number.isFinite(value)) return asText(value);
1119
+ const abs = Math.abs(value);
1120
+ const digits = abs >= 100 ? 0 : abs >= 10 ? 1 : 2;
1121
+ return Number(value.toFixed(digits)).toLocaleString("en-US", { maximumFractionDigits: digits });
1122
+ }
1123
+ const withUnit$1 = (value, unit) => unit ? unit === "%" ? `${formatNumber(value)}%` : `${formatNumber(value)} ${unit}` : formatNumber(value);
1124
+ function niceTicks(low, high) {
1125
+ let lo = low;
1126
+ let hi = high;
1127
+ if (lo === hi) {
1128
+ lo = Math.min(0, lo);
1129
+ hi = hi === 0 ? 1 : Math.max(0, hi);
1130
+ }
1131
+ if (lo === hi) hi = lo + 1;
1132
+ const raw = (hi - lo) / 4;
1133
+ const magnitude = 10 ** Math.floor(Math.log10(raw));
1134
+ const step = [
1135
+ 1,
1136
+ 2,
1137
+ 2.5,
1138
+ 5,
1139
+ 10
1140
+ ].map((scale) => scale * magnitude).find((candidate) => candidate >= raw);
1141
+ const start = Math.floor(lo / step + 1e-9) * step;
1142
+ const end = Math.ceil(hi / step - 1e-9) * step;
1143
+ const ticks = [];
1144
+ for (let value = start; value <= end + step / 2; value += step) ticks.push(Number(value.toPrecision(12)));
1145
+ return ticks;
1146
+ }
1147
+ function chartData(node, lookup) {
1148
+ const categories = (Array.isArray(node.categories) ? node.categories : []).map(asText);
1149
+ const series = (Array.isArray(node.series) ? node.series : []).map((item, index) => ({
1150
+ name: typeof item?.name === "string" ? item.name : "",
1151
+ color: colorAt(index),
1152
+ values: categories.map((_, at) => {
1153
+ const value = Number(resolve(Array.isArray(item?.values) ? item.values[at] : void 0, lookup, 0));
1154
+ return Number.isFinite(value) ? value : 0;
1155
+ })
1156
+ }));
1157
+ return {
1158
+ kind: node.kind ?? "bar",
1159
+ categories,
1160
+ series,
1161
+ unit: typeof node.unit === "string" ? node.unit : ""
1162
+ };
1163
+ }
1164
+ const csvCell = (value) => /[",\n]/.test(value) ? `"${value.replace(/"/g, "\"\"")}"` : value;
1165
+ function chartCsv({ categories, series, unit }, t = english) {
1166
+ return [["", ...series.map((item, index) => item.name || (series.length === 1 ? unit || t("Value") : `${t("Series")} ${index + 1}`))], ...categories.map((label, at) => [label, ...series.map((item) => String(item.values[at]))])].map((row) => row.map((cell) => csvCell(String(cell))).join(",")).join("\n");
1167
+ }
1168
+ function valueAxis(values, zero) {
1169
+ const finite = values.length > 0 ? values : [0];
1170
+ const low = Math.min(...finite);
1171
+ const high = Math.max(...finite);
1172
+ if (zero) return niceTicks(Math.min(0, low), Math.max(0, high));
1173
+ const pad = (high - low) * .1 || Math.abs(high) * .1 || 1;
1174
+ return niceTicks(low - pad, high + pad);
1175
+ }
1176
+ function columns({ categories, series, unit, kind }, width, active, shown) {
1177
+ const visible = series.filter((_, index) => shown[index]);
1178
+ const ticks = valueAxis(visible.flatMap((item) => item.values), kind !== "line");
1179
+ const lo = ticks[0];
1180
+ const hi = ticks.at(-1);
1181
+ const left = Math.ceil(Math.max(...ticks.map((tick) => textWidth(withUnit$1(tick, unit), TICK)))) + 10;
1182
+ const top = 8;
1183
+ const height = 220;
1184
+ const plot = {
1185
+ x: left,
1186
+ y: top,
1187
+ w: Math.max(40, width - left - 4),
1188
+ h: 188
1189
+ };
1190
+ const y = (value) => plot.y + plot.h - (value - lo) / (hi - lo) * plot.h;
1191
+ const band = plot.w / categories.length;
1192
+ const parts = [];
1193
+ for (const tick of ticks) {
1194
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
1195
+ className: "dvui-chart-grid",
1196
+ x1: plot.x,
1197
+ x2: plot.x + plot.w,
1198
+ y1: y(tick),
1199
+ y2: y(tick)
1200
+ }, `g${tick}`));
1201
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1202
+ className: "dvui-chart-tick",
1203
+ x: plot.x - 8,
1204
+ y: y(tick) + 3,
1205
+ textAnchor: "end",
1206
+ children: withUnit$1(tick, unit)
1207
+ }, `t${tick}`));
1208
+ }
1209
+ const points = [];
1210
+ categories.forEach((label, at) => {
1211
+ const cx = plot.x + band * at + band / 2;
1212
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1213
+ className: "dvui-chart-label",
1214
+ x: cx,
1215
+ y: 213,
1216
+ textAnchor: "middle",
1217
+ children: fit(label, band - 4, LABEL)
1218
+ }, `c${at}`));
1219
+ points.push({
1220
+ x: cx,
1221
+ y: y(Math.max(...visible.map((item) => item.values[at]), lo))
1222
+ });
1223
+ });
1224
+ if (kind === "bar") {
1225
+ const group = band * .72;
1226
+ const gap = visible.length > 1 ? 2 : 0;
1227
+ const barW = Math.max(2, (group - gap * (visible.length - 1)) / visible.length);
1228
+ const zero = y(Math.max(lo, Math.min(0, hi)));
1229
+ categories.forEach((_, at) => visible.forEach((item, row) => {
1230
+ const value = item.values[at];
1231
+ const x = plot.x + band * at + (band - group) / 2 + row * (barW + gap);
1232
+ const top2 = Math.min(y(value), zero);
1233
+ const tall = Math.max(1, Math.abs(y(value) - zero));
1234
+ const r = Math.min(4, barW / 2, tall);
1235
+ const d = value >= 0 ? `M${x},${top2 + tall}V${top2 + r}q0,-${r} ${r},-${r}h${barW - 2 * r}q${r},0 ${r},${r}V${top2 + tall}z` : `M${x},${top2}V${top2 + tall - r}q0,${r} ${r},${r}h${barW - 2 * r}q${r},0 ${r},-${r}V${top2}z`;
1236
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1237
+ className: "dvui-chart-mark",
1238
+ "data-dim": active !== null && active !== at ? "" : void 0,
1239
+ d,
1240
+ style: { fill: item.color }
1241
+ }, `b${at}-${row}`));
1242
+ }));
1243
+ } else {
1244
+ const xs = categories.map((_, at) => plot.x + band * at + band / 2);
1245
+ visible.forEach((item, row) => {
1246
+ const line = item.values.map((value, at) => `${at === 0 ? "M" : "L"}${xs[at]},${y(value)}`).join("");
1247
+ if (kind === "area") {
1248
+ const base = y(Math.max(lo, Math.min(0, hi)));
1249
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1250
+ className: "dvui-chart-area",
1251
+ d: `${line}L${xs.at(-1)},${base}L${xs[0]},${base}z`,
1252
+ style: { fill: item.color }
1253
+ }, `a${row}`));
1254
+ }
1255
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1256
+ className: "dvui-chart-line",
1257
+ d: line,
1258
+ style: { stroke: item.color }
1259
+ }, `l${row}`));
1260
+ item.values.forEach((value, at) => parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1261
+ className: "dvui-chart-dot",
1262
+ cx: xs[at],
1263
+ cy: y(value),
1264
+ r: active === at ? 4 : 2.5,
1265
+ style: { fill: item.color }
1266
+ }, `p${row}-${at}`)));
1267
+ });
1268
+ if (active !== null) parts.unshift(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
1269
+ className: "dvui-chart-guide",
1270
+ x1: xs[active],
1271
+ x2: xs[active],
1272
+ y1: plot.y,
1273
+ y2: plot.y + plot.h
1274
+ }, "guide"));
1275
+ }
1276
+ return {
1277
+ parts,
1278
+ height,
1279
+ points,
1280
+ hits: categories.map((_, at) => ({
1281
+ x: plot.x + band * at,
1282
+ y: plot.y,
1283
+ w: band,
1284
+ h: plot.h
1285
+ }))
1286
+ };
1287
+ }
1288
+ function rows({ categories, series, unit }, width, active, shown) {
1289
+ const visible = series.filter((_, index) => shown[index]);
1290
+ const ticks = valueAxis(visible.flatMap((item) => item.values), true);
1291
+ const lo = ticks[0];
1292
+ const hi = ticks.at(-1);
1293
+ const labelW = Math.min(width * .38, Math.ceil(Math.max(...categories.map((label) => textWidth(label, LABEL)))) + 10);
1294
+ const valueW = Math.ceil(Math.max(...visible.flatMap((item) => item.values).map((value) => textWidth(withUnit$1(value, unit), LABEL)))) + 10;
1295
+ const barH = 14;
1296
+ const rowH = 10 + visible.length * barH + (visible.length - 1) * 2;
1297
+ const top = 4;
1298
+ const height = top + rowH * categories.length + 22;
1299
+ const plot = {
1300
+ x: labelW,
1301
+ y: top,
1302
+ w: Math.max(40, width - labelW - valueW),
1303
+ h: rowH * categories.length
1304
+ };
1305
+ const x = (value) => plot.x + (value - lo) / (hi - lo) * plot.w;
1306
+ const parts = [];
1307
+ for (const tick of ticks) {
1308
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
1309
+ className: "dvui-chart-grid",
1310
+ x1: x(tick),
1311
+ x2: x(tick),
1312
+ y1: plot.y,
1313
+ y2: plot.y + plot.h
1314
+ }, `g${tick}`));
1315
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1316
+ className: "dvui-chart-tick",
1317
+ x: x(tick),
1318
+ y: height - 6,
1319
+ textAnchor: "middle",
1320
+ children: withUnit$1(tick, unit)
1321
+ }, `t${tick}`));
1322
+ }
1323
+ const points = [];
1324
+ const zero = x(Math.max(lo, Math.min(0, hi)));
1325
+ categories.forEach((label, at) => {
1326
+ const rowY = plot.y + rowH * at + 5;
1327
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1328
+ className: "dvui-chart-label",
1329
+ x: labelW - 10,
1330
+ y: rowY + (rowH - 10) / 2 + 4,
1331
+ textAnchor: "end",
1332
+ children: fit(label, labelW - 12, LABEL)
1333
+ }, `c${at}`));
1334
+ visible.forEach((item, row) => {
1335
+ const value = item.values[at];
1336
+ const by = rowY + row * 16;
1337
+ const left = Math.min(x(value), zero);
1338
+ const long = Math.max(1, Math.abs(x(value) - zero));
1339
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1340
+ className: "dvui-chart-mark",
1341
+ "data-dim": active !== null && active !== at ? "" : void 0,
1342
+ x: left,
1343
+ y: by,
1344
+ width: long,
1345
+ height: barH,
1346
+ rx: Math.min(4, long / 2),
1347
+ style: { fill: item.color }
1348
+ }, `b${at}-${row}`));
1349
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1350
+ className: "dvui-chart-value",
1351
+ x: left + long + 6,
1352
+ y: by + barH - 3,
1353
+ children: withUnit$1(value, unit)
1354
+ }, `v${at}-${row}`));
1355
+ });
1356
+ points.push({
1357
+ x: Math.max(...visible.map((item) => x(item.values[at])), zero),
1358
+ y: rowY
1359
+ });
1360
+ });
1361
+ return {
1362
+ parts,
1363
+ height,
1364
+ points,
1365
+ hits: categories.map((_, at) => ({
1366
+ x: 0,
1367
+ y: plot.y + rowH * at,
1368
+ w: width,
1369
+ h: rowH
1370
+ }))
1371
+ };
1372
+ }
1373
+ function donut({ categories, series }, width, active, hover) {
1374
+ const values = (series[0]?.values ?? []).map((value) => Math.max(0, value));
1375
+ const total = values.reduce((sum, value) => sum + value, 0);
1376
+ const size = Math.min(180, Math.max(120, width * .42));
1377
+ const r = size / 2;
1378
+ const inner = r * .62;
1379
+ const cx = r;
1380
+ const cy = r;
1381
+ const parts = [];
1382
+ const points = [];
1383
+ let angle = -Math.PI / 2;
1384
+ values.forEach((value, at) => {
1385
+ const sweep = total > 0 ? value / total * Math.PI * 2 : 0;
1386
+ const end = angle + sweep;
1387
+ const mid = angle + sweep / 2;
1388
+ points.push({
1389
+ x: cx + Math.cos(mid) * (r - (r - inner) / 2),
1390
+ y: cy + Math.sin(mid) * (r - (r - inner) / 2)
1391
+ });
1392
+ if (sweep > 0) {
1393
+ const large = sweep > Math.PI ? 1 : 0;
1394
+ const full = sweep >= Math.PI * 2 - 1e-6;
1395
+ const p = (radius, theta) => `${cx + Math.cos(theta) * radius},${cy + Math.sin(theta) * radius}`;
1396
+ const d = full ? `M${p(r, angle)}A${r},${r} 0 1 1 ${p(r, angle + Math.PI)}A${r},${r} 0 1 1 ${p(r, angle)}M${p(inner, angle)}A${inner},${inner} 0 1 0 ${p(inner, angle + Math.PI)}A${inner},${inner} 0 1 0 ${p(inner, angle)}z` : `M${p(r, angle)}A${r},${r} 0 ${large} 1 ${p(r, end)}L${p(inner, end)}A${inner},${inner} 0 ${large} 0 ${p(inner, angle)}z`;
1397
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1398
+ className: "dvui-chart-mark",
1399
+ "data-dim": active !== null && active !== at ? "" : void 0,
1400
+ d,
1401
+ fillRule: "evenodd",
1402
+ style: { fill: colorAt(at) },
1403
+ onPointerEnter: () => hover(at)
1404
+ }, `s${at}`));
1405
+ }
1406
+ angle = end;
1407
+ });
1408
+ return {
1409
+ parts,
1410
+ height: size,
1411
+ size,
1412
+ points,
1413
+ total,
1414
+ values,
1415
+ hits: []
1416
+ };
1417
+ }
1418
+ function useWidth(box, initial) {
1419
+ const [width, setWidth] = (0, react.useState)(initial);
1420
+ (0, react.useLayoutEffect)(() => {
1421
+ const node = box.current;
1422
+ if (!node || typeof ResizeObserver !== "function") return void 0;
1423
+ const measure = () => {
1424
+ const next = Math.round(node.clientWidth);
1425
+ if (next > 0) setWidth(next);
1426
+ };
1427
+ measure();
1428
+ const observer = new ResizeObserver(measure);
1429
+ observer.observe(node);
1430
+ return () => observer.disconnect();
1431
+ }, [box]);
1432
+ return width;
1433
+ }
1434
+ function ChartTable({ data, t = english }) {
1435
+ const { categories, series, unit } = data;
1436
+ const heads = series.map((item, index) => item.name || (series.length === 1 ? unit || t("Value") : `${t("Series")} ${index + 1}`));
1437
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1438
+ className: "dvui-table",
1439
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { scope: "col" }), heads.map((head, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1440
+ scope: "col",
1441
+ className: "dvui-num",
1442
+ children: head
1443
+ }, index))] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: categories.map((label, at) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1444
+ scope: "row",
1445
+ children: label
1446
+ }), series.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1447
+ className: "dvui-num",
1448
+ children: withUnit$1(item.values[at], series.length === 1 && !item.name ? "" : unit)
1449
+ }, index))] }, `${at}-${label}`)) })] })
1450
+ });
1451
+ }
1452
+ function WidgetChart({ node, lookup, registerChart, refreshCharts, t = english }) {
1453
+ const data = chartData(node, lookup);
1454
+ const { kind, categories, series, unit } = data;
1455
+ const box = (0, react.useRef)(null);
1456
+ const width = useWidth(box, 520);
1457
+ const [active, setActive] = (0, react.useState)(null);
1458
+ const [hidden, setHidden] = (0, react.useState)([]);
1459
+ const [asTable, setAsTable] = (0, react.useState)(false);
1460
+ const [copied, setCopied] = (0, react.useState)(false);
1461
+ const labelId = (0, react.useId)();
1462
+ const menuKey = (0, react.useId)();
1463
+ const table = (0, react.useRef)(asTable);
1464
+ table.current = asTable;
1465
+ const shown = series.map((_, index) => !hidden.includes(index));
1466
+ const label = typeof node.caption === "string" && node.caption !== "" ? node.caption : t("Chart");
1467
+ const isDonut = kind === "donut";
1468
+ const legendBeside = isDonut && width >= 360;
1469
+ const drawn = isDonut ? donut(data, legendBeside ? width - 200 : width, active, setActive) : kind === "hbar" ? rows(data, width, active, shown) : columns(data, width, active, shown);
1470
+ const svgWidth = isDonut ? drawn.size : width;
1471
+ const move = (event) => {
1472
+ const keys = {
1473
+ ArrowRight: 1,
1474
+ ArrowDown: 1,
1475
+ ArrowLeft: -1,
1476
+ ArrowUp: -1
1477
+ };
1478
+ if (event.key === "Escape") {
1479
+ setActive(null);
1480
+ return;
1481
+ }
1482
+ if (keys[event.key] === void 0) return;
1483
+ event.preventDefault();
1484
+ setActive((current) => current === null ? 0 : (current + keys[event.key] + categories.length) % categories.length);
1485
+ };
1486
+ const point = active === null ? null : drawn.points[active];
1487
+ const share = (at) => unit === "%" ? withUnit$1(drawn.values[at], unit) : `${withUnit$1(drawn.values[at], unit)} · ${drawn.total > 0 ? Math.round(drawn.values[at] / drawn.total * 100) : 0}%`;
1488
+ const tipRows = active === null || isDonut ? [] : series.filter((_, index) => shown[index]).map((item) => ({
1489
+ color: item.color,
1490
+ name: item.name,
1491
+ value: withUnit$1(item.values[active], unit)
1492
+ }));
1493
+ const copy = () => {
1494
+ navigator.clipboard?.writeText(chartCsv(data, t)).then(() => {
1495
+ setCopied(true);
1496
+ setTimeout(() => setCopied(false), 1200);
1497
+ }).catch(() => {});
1498
+ };
1499
+ const toggleTable = () => {
1500
+ table.current = !table.current;
1501
+ setAsTable(table.current);
1502
+ };
1503
+ const action = (0, react.useRef)({
1504
+ label: null,
1505
+ asTable: false,
1506
+ toggleTable,
1507
+ copy
1508
+ });
1509
+ action.current = {
1510
+ label: typeof node.caption === "string" && node.caption !== "" ? node.caption : null,
1511
+ get asTable() {
1512
+ return table.current;
1513
+ },
1514
+ toggleTable,
1515
+ copy
1516
+ };
1517
+ (0, react.useLayoutEffect)(() => registerChart?.(menuKey, {
1518
+ get label() {
1519
+ return action.current.label;
1520
+ },
1521
+ get asTable() {
1522
+ return action.current.asTable;
1523
+ },
1524
+ toggleTable: () => action.current.toggleTable(),
1525
+ copy: () => action.current.copy()
1526
+ }), [registerChart, menuKey]);
1527
+ (0, react.useLayoutEffect)(() => {
1528
+ refreshCharts?.();
1529
+ }, [
1530
+ refreshCharts,
1531
+ asTable,
1532
+ label
1533
+ ]);
1534
+ const legend = isDonut ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1535
+ className: "dvui-chart-legend dvui-chart-legend-list",
1536
+ children: categories.map((name, at) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1537
+ className: "dvui-chart-key",
1538
+ "data-dim": active !== null && active !== at ? "" : void 0,
1539
+ onPointerEnter: () => setActive(at),
1540
+ onPointerLeave: () => setActive(null),
1541
+ children: [
1542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: colorAt(at) } }),
1543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1544
+ className: "dvui-chart-key-name",
1545
+ children: name
1546
+ }),
1547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1548
+ className: "dvui-chart-key-value",
1549
+ children: share(at)
1550
+ })
1551
+ ]
1552
+ }, `${at}-${name}`))
1553
+ }) : series.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1554
+ className: "dvui-chart-legend",
1555
+ children: series.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1556
+ type: "button",
1557
+ className: "dvui-chart-key",
1558
+ "aria-pressed": shown[index],
1559
+ onClick: () => setHidden((before) => before.includes(index) ? before.filter((item) => item !== index) : shown.filter(Boolean).length > 1 ? [...before, index] : before),
1560
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: item.color } }), item.name || `${t("Series")} ${index + 1}`]
1561
+ }, `${index}-${item.name}`))
1562
+ }) : null;
1563
+ const plot = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1564
+ className: "dvui-chart-plot",
1565
+ style: isDonut ? { width: svgWidth } : void 0,
1566
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1567
+ className: "dvui-chart-svg",
1568
+ width: svgWidth,
1569
+ height: drawn.height,
1570
+ viewBox: `0 0 ${svgWidth} ${drawn.height}`,
1571
+ role: "img",
1572
+ "aria-labelledby": labelId,
1573
+ tabIndex: 0,
1574
+ onKeyDown: move,
1575
+ onBlur: () => setActive(null),
1576
+ onPointerLeave: () => setActive(null),
1577
+ children: [drawn.parts, isDonut ? active === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1578
+ className: "dvui-chart-center",
1579
+ x: svgWidth / 2,
1580
+ y: drawn.height / 2 + 5,
1581
+ textAnchor: "middle",
1582
+ children: unit === "%" ? "" : withUnit$1(drawn.total, unit)
1583
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1584
+ className: "dvui-chart-center-name",
1585
+ x: svgWidth / 2,
1586
+ y: drawn.height / 2 - 4,
1587
+ textAnchor: "middle",
1588
+ children: fit(categories[active], drawn.size * .56, LABEL)
1589
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1590
+ className: "dvui-chart-center",
1591
+ x: svgWidth / 2,
1592
+ y: drawn.height / 2 + 14,
1593
+ textAnchor: "middle",
1594
+ children: share(active)
1595
+ })] }) : drawn.hits.map((hit, at) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
1596
+ className: "dvui-chart-hit",
1597
+ x: hit.x,
1598
+ y: hit.y,
1599
+ width: hit.w,
1600
+ height: hit.h,
1601
+ onPointerEnter: () => setActive(at)
1602
+ }, `h${at}`))]
1603
+ }), point && !asTable && !isDonut ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1604
+ className: "dvui-chart-tip",
1605
+ role: "status",
1606
+ style: {
1607
+ left: point.x,
1608
+ top: point.y
1609
+ },
1610
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: categories[active] }), tipRows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1611
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: row.color } }),
1612
+ row.name ? `${row.name} ` : "",
1613
+ row.value
1614
+ ] }, index))]
1615
+ }) : null]
1616
+ });
1617
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("figure", {
1618
+ ref: box,
1619
+ className: "dvui-chart",
1620
+ "data-kind": kind,
1621
+ children: [
1622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1623
+ id: labelId,
1624
+ hidden: true,
1625
+ children: label
1626
+ }),
1627
+ asTable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTable, {
1628
+ data,
1629
+ t
1630
+ }) : isDonut ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1631
+ className: legendBeside ? "dvui-chart-donut dvui-chart-donut-beside" : "dvui-chart-donut",
1632
+ onPointerLeave: () => setActive(null),
1633
+ children: [plot, legend]
1634
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [legend, plot] }),
1635
+ copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1636
+ className: "dvui-chart-copied",
1637
+ role: "status",
1638
+ children: t("Copied")
1639
+ }) : null,
1640
+ typeof node.caption === "string" && node.caption !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("figcaption", {
1641
+ className: "dvui-caption",
1642
+ children: node.caption
1643
+ }) : null
1644
+ ]
1645
+ });
1646
+ }
1647
+ //#endregion
1648
+ //#region src/widget.tsx
1649
+ const TONES = [
1650
+ "good",
1651
+ "warn",
1652
+ "bad"
1653
+ ];
1654
+ const CALLOUT_TONES = [
1655
+ "info",
1656
+ "good",
1657
+ "warn",
1658
+ "bad"
1659
+ ];
1660
+ const optionsOf = (node) => Array.isArray(node.options) ? node.options : [];
1661
+ const fieldsOf = (node) => Array.isArray(node.fields) ? node.fields : [];
1662
+ const cellText = (value) => value === null || value === void 0 ? "" : typeof value === "object" ? JSON.stringify(value) : asText(value);
1663
+ const NUMERIC_CELL = /^[-+]?[\d.,]+\s?(%|[a-zA-Z]{1,4})?$/;
1664
+ function hasType(node, types) {
1665
+ return types.has(node.type) || Array.isArray(node.children) && node.children.some((child) => hasType(child, types));
1666
+ }
1667
+ const answerValues = (answer, node) => answer != null && answer.action === node.id ? answer.values : void 0;
1668
+ const withUnit = (value, unit) => `${asText(value)}${unit ? ` ${unit}` : ""}`;
1669
+ const grouped = (value) => typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US", { maximumFractionDigits: 6 }) : asText(value);
1670
+ function FallbackMarkdown({ text }) {
1671
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text });
1672
+ }
1673
+ function OptionsInput({ node, ctx, t }) {
1674
+ const [picked, setPicked] = (0, react.useState)([]);
1675
+ const options = optionsOf(node);
1676
+ const multi = node.multi === true;
1677
+ const choose = (label) => {
1678
+ if (ctx.answerLocked) return;
1679
+ if (multi) setPicked((current) => current.includes(label) ? current.filter((item) => item !== label) : [...current, label]);
1680
+ else ctx.onSubmit?.(node.id, { selected: [label] });
1681
+ };
1682
+ const onKey = (event) => {
1683
+ const index = event.key.toUpperCase().charCodeAt(0) - 65;
1684
+ if (index >= 0 && index < options.length && !event.ctrlKey && !event.metaKey && !event.altKey) {
1685
+ event.preventDefault();
1686
+ choose(options[index].label);
1687
+ }
1688
+ };
1689
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1690
+ className: "dvui-options",
1691
+ role: "group",
1692
+ "aria-label": t("Options"),
1693
+ onKeyDown: onKey,
1694
+ children: options.map((option, index) => {
1695
+ const selected = multi && picked.includes(option.label);
1696
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1697
+ type: "button",
1698
+ className: "dvui-option",
1699
+ disabled: ctx.answerLocked,
1700
+ "aria-pressed": multi ? selected : void 0,
1701
+ "aria-keyshortcuts": keyLetter(index).toLowerCase(),
1702
+ onClick: () => choose(option.label),
1703
+ children: [
1704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1705
+ className: "dvui-key",
1706
+ "aria-hidden": "true",
1707
+ children: keyLetter(index)
1708
+ }),
1709
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1710
+ className: "dvui-option-body",
1711
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1712
+ className: "dvui-option-label",
1713
+ children: option.label
1714
+ }), option.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1715
+ className: "dvui-option-desc",
1716
+ children: option.description
1717
+ }) : null]
1718
+ }),
1719
+ selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1720
+ className: "dvui-check",
1721
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {})
1722
+ }) : null
1723
+ ]
1724
+ }, option.label);
1725
+ })
1726
+ }), multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1727
+ className: "dvui-actions",
1728
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1729
+ type: "button",
1730
+ className: "dvui-submit",
1731
+ disabled: ctx.answerLocked || picked.length === 0,
1732
+ onClick: () => ctx.onSubmit?.(node.id, { selected: picked }),
1733
+ children: t("Submit")
1734
+ })
1735
+ }) : null] });
1736
+ }
1737
+ function SettledOptions({ node, answer, t }) {
1738
+ const selected = answerValues(answer, node)?.selected;
1739
+ const chosen = Array.isArray(selected) ? selected : [];
1740
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1741
+ className: "dvui-options",
1742
+ role: "group",
1743
+ "aria-label": t("Your answer"),
1744
+ children: optionsOf(node).map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1745
+ className: "dvui-option",
1746
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1747
+ className: "dvui-option-body",
1748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1749
+ className: "dvui-option-label",
1750
+ children: option.label
1751
+ }), option.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1752
+ className: "dvui-option-desc",
1753
+ children: option.description
1754
+ }) : null]
1755
+ }), chosen.includes(option.label) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1756
+ className: "dvui-check",
1757
+ role: "img",
1758
+ "aria-label": t("Selected"),
1759
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, {})
1760
+ }) : null]
1761
+ }, option.label))
1762
+ });
1763
+ }
1764
+ function ConfirmInput({ node, ctx, t }) {
1765
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1766
+ className: "dvui-actions",
1767
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1768
+ type: "button",
1769
+ className: "dvui-button",
1770
+ disabled: ctx.answerLocked,
1771
+ onClick: () => ctx.onSubmit?.(node.id, { confirmed: false }),
1772
+ children: node.cancelLabel ?? t("Cancel")
1773
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1774
+ type: "button",
1775
+ className: "dvui-submit",
1776
+ disabled: ctx.answerLocked,
1777
+ onClick: () => ctx.onSubmit?.(node.id, { confirmed: true }),
1778
+ children: node.confirmLabel ?? t("Confirm")
1779
+ })]
1780
+ });
1781
+ }
1782
+ function SettledConfirm({ node, answer, t }) {
1783
+ const confirmed = answerValues(answer, node)?.confirmed;
1784
+ if (typeof confirmed !== "boolean") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmInput, {
1785
+ node,
1786
+ ctx: { answerLocked: true },
1787
+ t
1788
+ });
1789
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1790
+ className: "dvui-badge",
1791
+ children: confirmed ? node.confirmLabel ?? t("Confirm") : node.cancelLabel ?? t("Cancel")
1792
+ });
1793
+ }
1794
+ function FormInput({ node, ctx, t }) {
1795
+ const fields = fieldsOf(node);
1796
+ const [draft, setDraft] = (0, react.useState)({});
1797
+ const submit = () => {
1798
+ if (!ctx.answerLocked) ctx.onSubmit?.(node.id, { fields: Object.fromEntries(fields.map((field) => [field.id, draft[field.id] ?? ""])) });
1799
+ };
1800
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1801
+ className: "dvui-form",
1802
+ onSubmit: (event) => {
1803
+ event.preventDefault();
1804
+ submit();
1805
+ },
1806
+ children: [fields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1807
+ className: "dvui-field",
1808
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1809
+ className: "dvui-field-label",
1810
+ children: field.label
1811
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1812
+ className: "dvui-input",
1813
+ type: "text",
1814
+ value: draft[field.id] ?? "",
1815
+ autoComplete: "off",
1816
+ spellCheck: false,
1817
+ readOnly: ctx.answerLocked,
1818
+ "aria-label": field.label,
1819
+ placeholder: field.placeholder,
1820
+ onChange: (event) => setDraft((current) => ({
1821
+ ...current,
1822
+ [field.id]: event.target.value
1823
+ }))
1824
+ })]
1825
+ }, field.id)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1826
+ className: "dvui-actions",
1827
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1828
+ type: "submit",
1829
+ className: "dvui-submit",
1830
+ disabled: ctx.answerLocked,
1831
+ children: node.submitLabel ?? t("Submit")
1832
+ })
1833
+ })]
1834
+ });
1835
+ }
1836
+ function SettledForm({ node, answer }) {
1837
+ const fields = answerValues(answer, node)?.fields;
1838
+ const values = fields !== null && typeof fields === "object" ? fields : {};
1839
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1840
+ className: "dvui-form",
1841
+ children: fieldsOf(node).map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1842
+ className: "dvui-field",
1843
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1844
+ className: "dvui-field-label",
1845
+ children: field.label
1846
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1847
+ className: "dvui-input",
1848
+ value: String(values[field.id] ?? ""),
1849
+ disabled: true,
1850
+ readOnly: true,
1851
+ "aria-label": field.label
1852
+ })]
1853
+ }, field.id))
1854
+ });
1855
+ }
1856
+ function WidgetTable({ node, lookup }) {
1857
+ const columns = Array.isArray(node.columns) ? node.columns : [];
1858
+ const rows = (Array.isArray(node.rows) ? node.rows : []).map((row) => (Array.isArray(row) ? row : []).map((cell) => resolve(cell, lookup, "")));
1859
+ const numeric = columns.map((_, index) => {
1860
+ const cells = rows.map((row) => row[index]).filter((cell) => cell !== void 0 && cell !== "");
1861
+ return cells.length > 0 && cells.every((cell) => typeof cell === "number" || typeof cell === "string" && NUMERIC_CELL.test(cell.trim()));
1862
+ });
1863
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1864
+ className: "dvui-table",
1865
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: columns.map((column, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1866
+ scope: "col",
1867
+ className: numeric[index] ? "dvui-num" : void 0,
1868
+ children: column.label
1869
+ }, column.key ?? index)) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: columns.map((column, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1870
+ className: numeric[index] ? "dvui-num" : void 0,
1871
+ children: cellText(row[index])
1872
+ }, column.key ?? index)) }, rowIndex)) })] })
1873
+ });
1874
+ }
1875
+ function WidgetProgress({ node, lookup, t }) {
1876
+ let percent;
1877
+ if (node.percent !== void 0) percent = Number(resolve(node.percent, lookup, 0));
1878
+ else {
1879
+ const max = Number(resolve(node.max, lookup, 0));
1880
+ percent = max > 0 ? Number(resolve(node.value, lookup, 0)) / max * 100 : 0;
1881
+ }
1882
+ percent = Math.max(0, Math.min(100, Number.isFinite(percent) ? percent : 0));
1883
+ const tone = resolve(node.tone, lookup);
1884
+ const right = node.valueLabel !== void 0 ? asText(resolve(node.valueLabel, lookup, "")) : `${Math.round(percent)}%`;
1885
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1886
+ className: "dvui-progress",
1887
+ "data-tone": TONES.includes(String(tone)) ? String(tone) : void 0,
1888
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1889
+ className: "dvui-progress-label",
1890
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: node.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: right })]
1891
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1892
+ className: "dvui-progress-track",
1893
+ role: "progressbar",
1894
+ "aria-valuenow": Math.round(percent),
1895
+ "aria-valuemin": 0,
1896
+ "aria-valuemax": 100,
1897
+ "aria-label": node.label ?? t("Progress"),
1898
+ "aria-valuetext": right,
1899
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1900
+ className: "dvui-progress-fill",
1901
+ style: { width: `${percent}%` }
1902
+ })
1903
+ })]
1904
+ });
1905
+ }
1906
+ function WidgetMetric({ node, lookup }) {
1907
+ const tone = resolve(node.tone, lookup);
1908
+ const caption = node.caption === void 0 ? "" : asText(resolve(node.caption, lookup, ""));
1909
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1910
+ className: "dvui-metric",
1911
+ "data-tone": TONES.includes(String(tone)) ? String(tone) : void 0,
1912
+ children: [
1913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1914
+ className: "dvui-metric-label",
1915
+ children: node.label
1916
+ }),
1917
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1918
+ className: "dvui-metric-value",
1919
+ children: asText(resolve(node.value, lookup, "—"))
1920
+ }),
1921
+ caption !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1922
+ className: "dvui-caption",
1923
+ children: caption
1924
+ }) : null
1925
+ ]
1926
+ });
1927
+ }
1928
+ function WidgetCallout({ node, lookup, Markdown, labels }) {
1929
+ const tone = resolve(node.tone, lookup);
1930
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1931
+ className: "dvui-callout",
1932
+ "data-tone": CALLOUT_TONES.includes(String(tone)) ? String(tone) : "info",
1933
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1934
+ className: "dvui-callout-icon",
1935
+ "aria-hidden": "true",
1936
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoIcon, {})
1937
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Markdown, {
1938
+ text: asText(resolve(node.text, lookup, "")),
1939
+ labels,
1940
+ variant: "compact"
1941
+ }) })]
1942
+ });
1943
+ }
1944
+ function WidgetSources({ node, t }) {
1945
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1946
+ className: "dvui-sources",
1947
+ role: "list",
1948
+ "aria-label": t("Sources"),
1949
+ children: (node.items ?? []).map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
1950
+ role: "listitem",
1951
+ className: "dvui-source",
1952
+ href: item.url,
1953
+ target: "_blank",
1954
+ rel: "noopener noreferrer",
1955
+ title: item.url,
1956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkIcon, {}), item.label]
1957
+ }, `${item.url}-${index}`))
1958
+ });
1959
+ }
1960
+ function InputHead({ id, label, value }) {
1961
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1962
+ className: "dvui-input-head",
1963
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1964
+ htmlFor: id,
1965
+ className: "dvui-input-label",
1966
+ children: label
1967
+ }), value !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("output", {
1968
+ htmlFor: id,
1969
+ className: "dvui-input-value",
1970
+ children: value
1971
+ }) : null]
1972
+ });
1973
+ }
1974
+ const numericBound = (bound) => typeof bound === "number" && Number.isFinite(bound) ? bound : typeof bound === "string" && bound.trim() !== "" && Number.isFinite(Number(bound)) ? Number(bound) : void 0;
1975
+ function SliderInput({ node, ctx }) {
1976
+ const id = (0, react.useId)();
1977
+ const value = Number(ctx.values[node.id]);
1978
+ const min = numericBound(resolve(node.min, ctx.lookup, void 0));
1979
+ const max = numericBound(resolve(node.max, ctx.lookup, void 0));
1980
+ const percent = min !== void 0 && max !== void 0 && max > min ? (value - min) / (max - min) * 100 : 0;
1981
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1982
+ className: "dvui-input-row",
1983
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InputHead, {
1984
+ id,
1985
+ label: node.label,
1986
+ value: withUnit(grouped(value), node.unit)
1987
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1988
+ id,
1989
+ type: "range",
1990
+ className: "dvui-range",
1991
+ min,
1992
+ max,
1993
+ step: node.step ?? "any",
1994
+ value,
1995
+ disabled: ctx.locked,
1996
+ style: { "--_dvui-fill": `${percent}%` },
1997
+ onChange: (event) => ctx.setInput(node.id, Number(event.target.value))
1998
+ })]
1999
+ });
2000
+ }
2001
+ function NumberInput({ node, ctx }) {
2002
+ const id = (0, react.useId)();
2003
+ const [draft, setDraft] = (0, react.useState)(null);
2004
+ const value = ctx.values[node.id];
2005
+ const min = numericBound(resolve(node.min, ctx.lookup, void 0));
2006
+ const max = numericBound(resolve(node.max, ctx.lookup, void 0));
2007
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2008
+ className: "dvui-input-row",
2009
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InputHead, {
2010
+ id,
2011
+ label: node.label
2012
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2013
+ className: "dvui-number",
2014
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2015
+ id,
2016
+ className: "dvui-input",
2017
+ type: "number",
2018
+ inputMode: "decimal",
2019
+ value: draft ?? asText(value),
2020
+ min,
2021
+ max,
2022
+ step: node.step ?? "any",
2023
+ disabled: ctx.locked,
2024
+ onChange: (event) => {
2025
+ const raw = event.target.value;
2026
+ setDraft(raw);
2027
+ const next = Number(raw);
2028
+ if (raw.trim() === "" || !Number.isFinite(next) || min !== void 0 && next < min || max !== void 0 && next > max) return;
2029
+ ctx.setInput(node.id, next);
2030
+ },
2031
+ onBlur: () => setDraft(null)
2032
+ }), node.unit ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2033
+ className: "dvui-unit",
2034
+ children: node.unit
2035
+ }) : null]
2036
+ })]
2037
+ });
2038
+ }
2039
+ function ChoiceInput({ node, ctx }) {
2040
+ const labelId = (0, react.useId)();
2041
+ const options = optionsOf(node);
2042
+ const current = ctx.values[node.id];
2043
+ const side = options.length <= 4 && options.every((option) => [...option.label].length <= 14);
2044
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2045
+ className: "dvui-input-row",
2046
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2047
+ className: "dvui-input-label",
2048
+ id: labelId,
2049
+ children: node.label
2050
+ }), side ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Segmented, {
2051
+ label: node.label ?? "",
2052
+ value: options.findIndex((option) => choiceValue(option) === current),
2053
+ className: "dvui-choice-seg",
2054
+ children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2055
+ type: "button",
2056
+ "aria-pressed": choiceValue(option) === current,
2057
+ disabled: ctx.locked,
2058
+ onClick: () => ctx.setInput(node.id, choiceValue(option)),
2059
+ children: option.label
2060
+ }, option.label))
2061
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2062
+ className: "dvui-radios",
2063
+ role: "radiogroup",
2064
+ "aria-labelledby": labelId,
2065
+ children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2066
+ type: "button",
2067
+ role: "radio",
2068
+ className: "dvui-radio",
2069
+ "aria-checked": choiceValue(option) === current,
2070
+ disabled: ctx.locked,
2071
+ onClick: () => ctx.setInput(node.id, choiceValue(option)),
2072
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2073
+ className: "dvui-radio-dot",
2074
+ "aria-hidden": "true"
2075
+ }), option.label]
2076
+ }, option.label))
2077
+ })]
2078
+ });
2079
+ }
2080
+ function ToggleInput({ node, ctx }) {
2081
+ const on = ctx.values[node.id] === true;
2082
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2083
+ className: "dvui-toggle",
2084
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2085
+ className: "dvui-input-label",
2086
+ children: node.label
2087
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2088
+ type: "button",
2089
+ role: "switch",
2090
+ className: "dvui-switch",
2091
+ "aria-checked": on,
2092
+ "aria-label": node.label,
2093
+ disabled: ctx.locked,
2094
+ onClick: () => ctx.setInput(node.id, !on),
2095
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2096
+ className: "dvui-switch-knob",
2097
+ "aria-hidden": "true"
2098
+ })
2099
+ })]
2100
+ });
2101
+ }
2102
+ function WidgetNode({ node, ctx, Markdown, labels, t }) {
2103
+ const { lookup } = ctx;
2104
+ switch (node.type) {
2105
+ case "stack":
2106
+ case "row": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2107
+ className: node.type === "row" ? "dvui-row" : "dvui-stack",
2108
+ children: (node.children ?? []).map((child, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetNode, {
2109
+ node: child,
2110
+ ctx,
2111
+ Markdown,
2112
+ labels,
2113
+ t
2114
+ }, child.id ?? `${child.type}-${index}`))
2115
+ });
2116
+ case "text": {
2117
+ const text = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Markdown, {
2118
+ text: asText(resolve(node.text, lookup, "")),
2119
+ labels,
2120
+ variant: "compact"
2121
+ });
2122
+ return node.tone === "muted" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2123
+ className: "dvui-muted",
2124
+ children: text
2125
+ }) : text;
2126
+ }
2127
+ case "metric": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetMetric, {
2128
+ node,
2129
+ lookup
2130
+ });
2131
+ case "table": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetTable, {
2132
+ node,
2133
+ lookup
2134
+ });
2135
+ case "chart": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetChart, {
2136
+ node,
2137
+ lookup,
2138
+ registerChart: ctx.registerChart,
2139
+ refreshCharts: ctx.refreshCharts,
2140
+ t
2141
+ });
2142
+ case "progress": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetProgress, {
2143
+ node,
2144
+ lookup,
2145
+ t
2146
+ });
2147
+ case "callout": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetCallout, {
2148
+ node,
2149
+ lookup,
2150
+ Markdown,
2151
+ labels
2152
+ });
2153
+ case "sources": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetSources, {
2154
+ node,
2155
+ t
2156
+ });
2157
+ case "slider": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderInput, {
2158
+ node,
2159
+ ctx
2160
+ });
2161
+ case "number": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberInput, {
2162
+ node,
2163
+ ctx
2164
+ });
2165
+ case "choice": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChoiceInput, {
2166
+ node,
2167
+ ctx
2168
+ });
2169
+ case "toggle": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleInput, {
2170
+ node,
2171
+ ctx
2172
+ });
2173
+ case "submit": return ctx.settled ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2174
+ className: "dvui-actions",
2175
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2176
+ type: "button",
2177
+ className: "dvui-submit",
2178
+ disabled: ctx.answerLocked,
2179
+ onClick: () => ctx.onSubmit?.(node.id, {}),
2180
+ children: node.label || t("Send")
2181
+ })
2182
+ });
2183
+ case "options": return ctx.settled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettledOptions, {
2184
+ node,
2185
+ answer: ctx.answer,
2186
+ t
2187
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionsInput, {
2188
+ node,
2189
+ ctx,
2190
+ t
2191
+ });
2192
+ case "confirm": return ctx.settled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettledConfirm, {
2193
+ node,
2194
+ answer: ctx.answer,
2195
+ t
2196
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmInput, {
2197
+ node,
2198
+ ctx,
2199
+ t
2200
+ });
2201
+ case "form": return ctx.settled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettledForm, {
2202
+ node,
2203
+ answer: ctx.answer
2204
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormInput, {
2205
+ node,
2206
+ ctx,
2207
+ t
2208
+ });
2209
+ default: return null;
2210
+ }
2211
+ }
2212
+ function WidgetView({ view, onSubmit, settled = false, answer = null, busy = false, registerChart, refreshCharts, t = english, labels = {
2213
+ code: {
2214
+ copyLabel: "Copy",
2215
+ copiedLabel: "Copied"
2216
+ },
2217
+ footnotes: "Footnotes"
2218
+ }, Markdown = FallbackMarkdown }) {
2219
+ const inputs = viewInputs(view);
2220
+ const [values, setValues] = (0, react.useState)(() => Object.fromEntries(inputs.map((node) => [node.id, inputDefault(node)])));
2221
+ const sent = answer?.values?.inputs;
2222
+ const current = settled && sent !== null && typeof sent === "object" ? {
2223
+ ...values,
2224
+ ...sent
2225
+ } : values;
2226
+ const submit = (action, extra) => onSubmit?.(action, inputs.length > 0 ? {
2227
+ ...extra,
2228
+ inputs: current
2229
+ } : extra);
2230
+ const ctx = {
2231
+ onSubmit: submit,
2232
+ settled,
2233
+ answer,
2234
+ busy,
2235
+ lookup: viewScope(view, current),
2236
+ values: current,
2237
+ locked: settled || busy,
2238
+ answerLocked: settled || busy || onSubmit === void 0,
2239
+ setInput: (id, value) => setValues((before) => ({
2240
+ ...before,
2241
+ [id]: value
2242
+ })),
2243
+ registerChart,
2244
+ refreshCharts
2245
+ };
2246
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetNode, {
2247
+ node: view,
2248
+ ctx,
2249
+ Markdown,
2250
+ labels,
2251
+ t
2252
+ });
2253
+ }
2254
+ function useChartHandles() {
2255
+ const [, bump] = (0, react.useState)(0);
2256
+ const charts = (0, react.useRef)(/* @__PURE__ */ new Map());
2257
+ const refreshCharts = (0, react.useCallback)(() => bump((value) => value + 1), []);
2258
+ const registerChart = (0, react.useCallback)((key, handle) => {
2259
+ charts.current.set(key, handle);
2260
+ refreshCharts();
2261
+ return () => {
2262
+ charts.current.delete(key);
2263
+ refreshCharts();
2264
+ };
2265
+ }, [refreshCharts]);
2266
+ return {
2267
+ charts: [...charts.current.values()],
2268
+ registerChart,
2269
+ refreshCharts
2270
+ };
2271
+ }
2272
+ function ViewMenu({ charts, t }) {
2273
+ const [open, setOpen] = (0, react.useState)(false);
2274
+ const root = (0, react.useRef)(null);
2275
+ (0, react.useEffect)(() => {
2276
+ if (!open) return void 0;
2277
+ const away = (event) => {
2278
+ if (!root.current?.contains(event.target)) setOpen(false);
2279
+ };
2280
+ const key = (event) => {
2281
+ if (event.key === "Escape") setOpen(false);
2282
+ };
2283
+ document.addEventListener("pointerdown", away, true);
2284
+ document.addEventListener("keydown", key);
2285
+ return () => {
2286
+ document.removeEventListener("pointerdown", away, true);
2287
+ document.removeEventListener("keydown", key);
2288
+ };
2289
+ }, [open]);
2290
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2291
+ ref: root,
2292
+ className: "dvui-tools",
2293
+ "data-open": open ? "" : void 0,
2294
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2295
+ type: "button",
2296
+ className: "dvui-more",
2297
+ "aria-label": t("Chart options"),
2298
+ "aria-haspopup": "menu",
2299
+ "aria-expanded": open,
2300
+ onClick: () => setOpen((value) => !value),
2301
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DotsIcon, {})
2302
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2303
+ className: "dvui-menu",
2304
+ role: "menu",
2305
+ children: charts.map((chart, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
2306
+ charts.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2307
+ className: "dvui-menu-label",
2308
+ children: chart.label ?? t("Chart {n}", { n: index + 1 })
2309
+ }) : null,
2310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2311
+ type: "button",
2312
+ role: "menuitem",
2313
+ onClick: () => {
2314
+ setOpen(false);
2315
+ chart.toggleTable();
2316
+ },
2317
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TableIcon, {}), chart.asTable ? t("View as chart") : t("View as table")]
2318
+ }),
2319
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2320
+ type: "button",
2321
+ role: "menuitem",
2322
+ onClick: () => {
2323
+ setOpen(false);
2324
+ chart.copy();
2325
+ },
2326
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyIcon, {}), t("Copy data")]
2327
+ })
2328
+ ] }, `${chart.label}-${index}`))
2329
+ }) : null]
2330
+ });
2331
+ }
2332
+ function WidgetCard({ title = "", view, onSubmit, onDismiss, busy = false, settled = false, answer = null, t = english, labels, Markdown, localOnly = false }) {
2333
+ const titleId = (0, react.useId)();
2334
+ const { charts, registerChart, refreshCharts } = useChartHandles();
2335
+ const framed = hasType(view, /* @__PURE__ */ new Set([
2336
+ "slider",
2337
+ "number",
2338
+ "choice",
2339
+ "toggle",
2340
+ "options",
2341
+ "confirm",
2342
+ "form",
2343
+ "submit"
2344
+ ]));
2345
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2346
+ className: `dvui-view ${framed ? "dvui-framed" : "dvui-bare"}${settled ? " dvui-settled" : ""}`,
2347
+ role: "group",
2348
+ "aria-labelledby": title ? titleId : void 0,
2349
+ "aria-label": title ? void 0 : t("View"),
2350
+ "aria-busy": busy || void 0,
2351
+ children: [
2352
+ title || onDismiss || charts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2353
+ className: "dvui-head",
2354
+ children: [
2355
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2356
+ className: "dvui-title",
2357
+ id: titleId,
2358
+ children: title
2359
+ }),
2360
+ charts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewMenu, {
2361
+ charts,
2362
+ t
2363
+ }) : null,
2364
+ onDismiss ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2365
+ type: "button",
2366
+ className: "dvui-dismiss",
2367
+ "aria-label": t("Dismiss view"),
2368
+ onClick: onDismiss,
2369
+ disabled: busy,
2370
+ children: "×"
2371
+ }) : null
2372
+ ]
2373
+ }) : null,
2374
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2375
+ className: "dvui-body",
2376
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetView, {
2377
+ view,
2378
+ onSubmit,
2379
+ settled,
2380
+ answer,
2381
+ busy,
2382
+ registerChart,
2383
+ refreshCharts,
2384
+ t,
2385
+ labels,
2386
+ Markdown
2387
+ })
2388
+ }),
2389
+ localOnly && hasType(view, /* @__PURE__ */ new Set([
2390
+ "options",
2391
+ "confirm",
2392
+ "form",
2393
+ "submit"
2394
+ ])) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2395
+ className: "dvui-local-note",
2396
+ role: "note",
2397
+ children: t("Local preview only; this card does not send an answer.")
2398
+ }) : null
2399
+ ]
2400
+ });
2401
+ }
2402
+ //#endregion
2403
+ //#region src/styles.ts
2404
+ const viewStyles = `
2405
+ .dvui-root { min-width: 0; color: var(--dsw-alias-label-primary); font-family: inherit; font-size: 14px; line-height: 22px; }
2406
+ .dvui-root * { box-sizing: border-box; }
2407
+ .dvui-root .dvui-view { min-width: 0; display: flex; flex-direction: column; gap: 10px; margin: 10px 0; }
2408
+ .dvui-root .dvui-bare { gap: 8px; }
2409
+ .dvui-root .dvui-framed { width: min(100%, 560px); padding: 14px 16px; border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-1); box-shadow: var(--dsw-elevation-stroke); }
2410
+ .dvui-root .dvui-settled { color: var(--dsw-alias-label-secondary); }
2411
+ .dvui-root .dvui-head { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
2412
+ .dvui-root .dvui-title { flex: 1 1 0; min-width: 0; margin: 0; font-size: 13px; line-height: 18px; font-weight: 500; overflow-wrap: anywhere; }
2413
+ .dvui-root .dvui-body { min-width: 0; }
2414
+ .dvui-root .dvui-stack { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
2415
+ .dvui-root .dvui-row { display: flex; flex-wrap: wrap; gap: 12px 24px; min-width: 0; }
2416
+ .dvui-root .dvui-row > * { flex: 1 1 160px; min-width: 0; }
2417
+ .dvui-root .dvui-metric { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
2418
+ .dvui-root .dvui-metric-label,.dvui-root .dvui-caption,.dvui-root .dvui-field-label,.dvui-root .dvui-input-label { font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
2419
+ .dvui-root .dvui-metric-value { font-size: 22px; line-height: 28px; font-weight: 600; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
2420
+ .dvui-root .dvui-metric[data-tone=good] .dvui-metric-value,.dvui-root .dvui-progress[data-tone=good] .dvui-progress-fill { color: var(--dsw-alias-state-success-primary); background: var(--dsw-alias-state-success-primary); }
2421
+ .dvui-root .dvui-metric[data-tone=warn] .dvui-metric-value,.dvui-root .dvui-progress[data-tone=warn] .dvui-progress-fill { color: var(--dsw-alias-state-warn-primary); background: var(--dsw-alias-state-warn-primary); }
2422
+ .dvui-root .dvui-metric[data-tone=bad] .dvui-metric-value,.dvui-root .dvui-progress[data-tone=bad] .dvui-progress-fill { color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-state-error-primary); }
2423
+ .dvui-root .dvui-muted { font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-secondary); }
2424
+ .dvui-root .dvui-muted * { font-size: inherit !important; line-height: inherit !important; color: inherit !important; }
2425
+ .dvui-root .dvui-table { max-width: 100%; overflow-x: auto; border: .5px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-md); }
2426
+ .dvui-root .dvui-table table { border-collapse: collapse; min-width: 100%; font-size: 13px; line-height: 18px; }
2427
+ .dvui-root .dvui-table th { padding: 6px 10px; font-weight: 500; text-align: start; white-space: nowrap; color: var(--dsw-alias-label-secondary); border-bottom: .5px solid var(--dsw-alias-border-l2); }
2428
+ .dvui-root .dvui-table td { padding: 6px 10px; max-width: 280px; overflow-wrap: anywhere; }
2429
+ .dvui-root .dvui-table tbody tr + tr > * { border-top: .5px solid var(--dsw-alias-border-l1); }
2430
+ .dvui-root .dvui-table tbody th { font-weight: 400; border-bottom: 0; color: var(--dsw-alias-label-primary); }
2431
+ .dvui-root .dvui-num { text-align: end; font-variant-numeric: tabular-nums; }
2432
+ .dvui-root .dvui-progress { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
2433
+ .dvui-root .dvui-progress-label { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; }
2434
+ .dvui-root .dvui-progress-label > span:first-child { color: var(--dsw-alias-label-primary); font-weight: 500; }
2435
+ .dvui-root .dvui-progress-track { height: 6px; border-radius: 999px; background: var(--dsw-alias-interactive-bg-hover); overflow: hidden; }
2436
+ .dvui-root .dvui-progress-fill { height: 100%; border-radius: inherit; background: var(--dsw-alias-state-business-primary); transition: width .12s ease; }
2437
+ .dvui-root .dvui-callout { display: flex; gap: 8px; padding: 8px 12px; border-radius: var(--dsw-radius-md); background: color-mix(in srgb, var(--dsw-alias-state-business-primary) 8%, transparent); }
2438
+ .dvui-root .dvui-callout[data-tone=good] { background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent); }
2439
+ .dvui-root .dvui-callout[data-tone=warn] { background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent); }
2440
+ .dvui-root .dvui-callout[data-tone=bad] { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent); }
2441
+ .dvui-root .dvui-callout-icon { flex: none; display: flex; padding-top: 3px; color: var(--dsw-alias-state-business-primary); }
2442
+ .dvui-root .dvui-callout[data-tone=good] .dvui-callout-icon { color: var(--dsw-alias-state-success-primary); }
2443
+ .dvui-root .dvui-callout[data-tone=warn] .dvui-callout-icon { color: var(--dsw-alias-state-warn-primary); }
2444
+ .dvui-root .dvui-callout[data-tone=bad] .dvui-callout-icon { color: var(--dsw-alias-state-error-primary); }
2445
+ .dvui-root .dvui-callout > div { flex: 1 1 auto; min-width: 0; }
2446
+ .dvui-root .dvui-sources { display: flex; flex-wrap: wrap; gap: 6px; }
2447
+ .dvui-root .dvui-source { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; height: 22px; padding: 0 6px; border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 16px; text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2448
+ .dvui-root .dvui-source:hover { color: var(--dsw-alias-label-primary); }
2449
+ .dvui-root .dvui-input-row { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
2450
+ .dvui-root .dvui-input-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
2451
+ .dvui-root .dvui-input-value { font-size: 15px; line-height: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }
2452
+ .dvui-root .dvui-range { appearance: none; width: 100%; height: 20px; margin: 0; background: transparent; cursor: pointer; }
2453
+ .dvui-root .dvui-range:disabled { cursor: default; opacity: .6; }
2454
+ .dvui-root .dvui-range::-webkit-slider-runnable-track { height: 6px; border-radius: 999px; background: linear-gradient(to right, var(--dsw-alias-state-business-primary) var(--_dvui-fill, 0%), var(--dsw-alias-interactive-bg-hover) var(--_dvui-fill, 0%)); }
2455
+ .dvui-root .dvui-range::-moz-range-track { height: 6px; border-radius: 999px; background: var(--dsw-alias-interactive-bg-hover); }
2456
+ .dvui-root .dvui-range::-moz-range-progress { height: 6px; border-radius: 999px; background: var(--dsw-alias-state-business-primary); }
2457
+ .dvui-root .dvui-range::-webkit-slider-thumb { appearance: none; width: 16px; height: 16px; margin-top: -5px; border-radius: 50%; background: var(--dsw-alias-label-primary-foreground); box-shadow: var(--dsw-elevation-soft); }
2458
+ .dvui-root .dvui-range::-moz-range-thumb { width: 16px; height: 16px; border: 0; border-radius: 50%; background: var(--dsw-alias-label-primary-foreground); box-shadow: var(--dsw-elevation-soft); }
2459
+ .dvui-root .dvui-number { display: flex; align-items: center; gap: 6px; max-width: 200px; }
2460
+ .dvui-root .dvui-unit { flex: none; font-size: 13px; color: var(--dsw-alias-label-secondary); }
2461
+ .dvui-root .dvui-seg { display: flex; flex-wrap: wrap; gap: 2px; padding: 2px; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-interactive-bg-hover); }
2462
+ .dvui-root .dvui-choice-seg { align-self: flex-start; max-width: 100%; }
2463
+ .dvui-root .dvui-seg button { flex: 0 1 auto; min-height: 30px; padding: 4px 12px; border: 0; border-radius: var(--dsw-radius-sm); background: none; font: inherit; font-size: 13px; color: var(--dsw-alias-label-secondary); cursor: pointer; overflow-wrap: anywhere; }
2464
+ .dvui-root .dvui-seg button[aria-pressed=true] { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); box-shadow: var(--dsw-elevation-soft); }
2465
+ .dvui-root .dvui-radios { display: flex; flex-direction: column; gap: 2px; }
2466
+ .dvui-root .dvui-radio { display: flex; align-items: center; gap: 8px; min-height: 30px; padding: 4px 8px; margin: 0 -8px; border: 0; border-radius: var(--dsw-radius-sm); background: none; font: inherit; text-align: start; color: inherit; cursor: pointer; }
2467
+ .dvui-root .dvui-radio:hover:not(:disabled),.dvui-root .dvui-option:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
2468
+ .dvui-root .dvui-radio-dot { flex: none; width: 16px; height: 16px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--dsw-alias-border-l3); }
2469
+ .dvui-root .dvui-radio[aria-checked=true] .dvui-radio-dot { box-shadow: inset 0 0 0 5px var(--dsw-alias-state-business-primary); }
2470
+ .dvui-root .dvui-toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 28px; }
2471
+ .dvui-root .dvui-switch { position: relative; flex: none; width: 36px; height: 20px; padding: 0; border: 0; border-radius: 999px; background: var(--dsw-alias-border-l3); cursor: pointer; }
2472
+ .dvui-root .dvui-switch[aria-checked=true] { background: var(--dsw-alias-button-primary-fill); }
2473
+ .dvui-root .dvui-switch-knob { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--dsw-alias-label-primary-foreground); box-shadow: var(--dsw-elevation-soft); transition: transform .15s ease; }
2474
+ .dvui-root .dvui-switch[aria-checked=true] .dvui-switch-knob { transform: translateX(16px); }
2475
+ .dvui-root .dvui-options { display: flex; flex-direction: column; gap: 1px; min-width: 0; margin: 0 -8px; }
2476
+ .dvui-root .dvui-option { position: relative; display: flex; align-items: flex-start; gap: 12px; width: 100%; min-width: 0; margin: 0; border: 0; border-radius: var(--dsw-radius-md); background: transparent; padding: 10px 8px; font: inherit; color: inherit; text-align: start; cursor: pointer; }
2477
+ .dvui-root .dvui-key { flex: none; width: 24px; height: 24px; border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 24px; text-align: center; }
2478
+ .dvui-root .dvui-option-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; padding-top: 2px; }
2479
+ .dvui-root .dvui-option-label { font-size: 14px; line-height: 20px; font-weight: 500; overflow-wrap: anywhere; }
2480
+ .dvui-root .dvui-option-desc { font-size: 12px; line-height: 17px; color: var(--dsw-alias-label-secondary); overflow-wrap: anywhere; }
2481
+ .dvui-root .dvui-check { flex: none; display: inline-flex; align-items: center; color: var(--dsw-alias-state-success-primary); }
2482
+ .dvui-root .dvui-form { display: flex; flex-direction: column; gap: 8px; margin: 0; }
2483
+ .dvui-root .dvui-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
2484
+ .dvui-root .dvui-input { width: 100%; min-width: 0; min-height: 32px; padding: 5px 11px; border: .5px solid var(--dsw-alias-border-l3); border-radius: var(--dsw-radius-sm); outline: none; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 14px; line-height: 20px; }
2485
+ .dvui-root .dvui-input::placeholder { color: var(--dsw-alias-label-dimmed); }
2486
+ .dvui-root .dvui-input:focus-visible { border-color: var(--dsw-alias-state-business-primary); }
2487
+ .dvui-root .dvui-actions { display: flex; justify-content: flex-end; gap: 8px; }
2488
+ .dvui-root .dvui-submit,.dvui-root .dvui-button { flex: none; min-height: 34px; padding: 0 14px; border: 0; border-radius: 999px; font: inherit; font-size: 14px; font-weight: 500; cursor: pointer; }
2489
+ .dvui-root .dvui-submit { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }
2490
+ .dvui-root .dvui-submit:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }
2491
+ .dvui-root .dvui-button { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
2492
+ .dvui-root .dvui-button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-active); }
2493
+ .dvui-root button:disabled { opacity: .45; cursor: default; }
2494
+ .dvui-root .dvui-badge { height: 22px; padding: 0 7px; border-radius: 999px; border: 1px dotted var(--dsw-alias-border-l3); color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 20px; }
2495
+ .dvui-root .dvui-tools { position: relative; flex: none; align-self: flex-start; }
2496
+ .dvui-root .dvui-more,.dvui-root .dvui-dismiss { width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%; background: none; color: var(--dsw-alias-label-secondary); display: flex; align-items: center; justify-content: center; cursor: pointer; }
2497
+ .dvui-root .dvui-more:hover,.dvui-root .dvui-dismiss:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
2498
+ .dvui-root .dvui-menu { position: absolute; top: 32px; right: 0; z-index: 4; min-width: 180px; padding: 6px; border-radius: var(--dsw-radius-md); background: var(--dsw-menu-surface-fill); box-shadow: var(--dsw-elevation-panel); display: flex; flex-direction: column; gap: 2px; }
2499
+ .dvui-root .dvui-menu button { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 30px; text-align: left; border: 0; background: none; border-radius: var(--dsw-radius-sm); padding: 6px 8px; font: inherit; font-size: 13px; line-height: 18px; color: var(--dsw-alias-label-primary); cursor: pointer; }
2500
+ .dvui-root .dvui-menu button:hover { background: var(--dsw-alias-interactive-bg-hover); }
2501
+ .dvui-root .dvui-menu-label { padding: 2px 8px; font-size: 11px; line-height: 14px; color: var(--dsw-alias-label-tertiary); }
2502
+ .dvui-root .dvui-local-note { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
2503
+ .dvui-root .dvui-chart { position: relative; margin: 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
2504
+ .dvui-root .dvui-chart-plot { position: relative; min-width: 0; }
2505
+ .dvui-root .dvui-chart-svg { display: block; overflow: visible; outline: none; font-family: inherit; }
2506
+ .dvui-root .dvui-chart-svg text { pointer-events: none; }
2507
+ .dvui-root .dvui-chart-grid { stroke: var(--dsw-alias-border-l1); stroke-width: 1; stroke-dasharray: 3 3; }
2508
+ .dvui-root .dvui-chart-tick { font-size: 10px; fill: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }
2509
+ .dvui-root .dvui-chart-label,.dvui-root .dvui-chart-value { font-size: 11px; fill: var(--dsw-alias-label-secondary); }
2510
+ .dvui-root .dvui-chart-value { font-variant-numeric: tabular-nums; }
2511
+ .dvui-root .dvui-chart-center { font-size: 15px; font-weight: 600; fill: var(--dsw-alias-label-primary); font-variant-numeric: tabular-nums; }
2512
+ .dvui-root .dvui-chart-center-name { font-size: 11px; fill: var(--dsw-alias-label-secondary); }
2513
+ .dvui-root .dvui-chart-mark[data-dim] { opacity: .28; }
2514
+ .dvui-root .dvui-chart-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
2515
+ .dvui-root .dvui-chart-area { opacity: .14; }
2516
+ .dvui-root .dvui-chart-guide { stroke: var(--dsw-alias-border-l3); stroke-width: 1; }
2517
+ .dvui-root .dvui-chart-hit { fill: transparent; }
2518
+ .dvui-root .dvui-chart-tip { position: absolute; z-index: 4; pointer-events: none; transform: translate(-50%, calc(-100% - 8px)); display: flex; flex-direction: column; gap: 2px; padding: 6px 10px; border-radius: var(--dsw-radius-md); background: var(--dsw-menu-surface-fill); box-shadow: var(--dsw-elevation-panel); font-size: 12px; line-height: 16px; white-space: nowrap; font-variant-numeric: tabular-nums; }
2519
+ .dvui-root .dvui-chart-tip span { display: flex; align-items: center; gap: 6px; color: var(--dsw-alias-label-secondary); }
2520
+ .dvui-root .dvui-chart-tip i,.dvui-root .dvui-chart-key i { flex: none; width: 8px; height: 8px; border-radius: 50%; }
2521
+ .dvui-root .dvui-chart-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; padding-right: 32px; }
2522
+ .dvui-root .dvui-chart-key { display: inline-flex; align-items: center; gap: 6px; min-height: 20px; padding: 0; border: 0; background: none; font: inherit; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-secondary); cursor: pointer; }
2523
+ .dvui-root .dvui-chart-key[aria-pressed=false],.dvui-root .dvui-chart-key[data-dim] { opacity: .4; }
2524
+ .dvui-root .dvui-chart-legend-list { flex-direction: column; flex-wrap: nowrap; gap: 6px; padding: 0; min-width: 0; }
2525
+ .dvui-root .dvui-chart-legend-list .dvui-chart-key { cursor: default; width: 100%; }
2526
+ .dvui-root .dvui-chart-key-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }
2527
+ .dvui-root .dvui-chart-key-value { flex: none; font-variant-numeric: tabular-nums; }
2528
+ .dvui-root .dvui-chart-donut { display: flex; flex-direction: column; align-items: center; gap: 12px; }
2529
+ .dvui-root .dvui-chart-donut-beside { flex-direction: row; align-items: center; gap: 24px; }
2530
+ .dvui-root .dvui-chart-donut-beside .dvui-chart-legend-list { flex: 1 1 auto; max-width: 280px; }
2531
+ .dvui-root .dvui-chart-copied { position: absolute; top: 2px; right: 36px; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-secondary); }
2532
+ .dvui-root .dvui-reasoning { margin: 6px 0; color: var(--dsw-alias-label-secondary); }
2533
+ .dvui-root .dvui-reasoning > summary { cursor: pointer; font-size: 12px; line-height: 20px; }
2534
+ .dvui-root .dvui-reasoning-body { padding: 4px 0 4px 12px; border-left: 1px solid var(--dsw-alias-border-l2); }
2535
+ .dvui-root .dvui-unknown { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--dsw-alias-label-secondary); font-size: 12px; }
2536
+ .dvui-root .dvui-tool-loading, .dvui-root.dvui-tool-loading { padding: 8px 0; color: var(--dsw-alias-label-secondary); font-size: 13px; }
2537
+ .dvui-root .dvui-tool-error, .dvui-root.dvui-tool-error { margin: 8px 0; color: var(--dsw-alias-label-error); font-size: 13px; }
2538
+ .dvui-root button:focus-visible,.dvui-root a:focus-visible,.dvui-root input:focus-visible,.dvui-root .dvui-chart-svg:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
2539
+ @media (hover: hover) { .dvui-root .dvui-tools:not(:focus-within):not([data-open]) { opacity: 0; } .dvui-root .dvui-view:hover .dvui-tools { opacity: 1; } }
2540
+ @media (prefers-reduced-motion: reduce) { .dvui-root * { transition: none !important; } }
2541
+ `;
2542
+ //#endregion
2543
+ //#region src/answer-text.ts
2544
+ function controls(view) {
2545
+ return [
2546
+ "options",
2547
+ "confirm",
2548
+ "form",
2549
+ "submit"
2550
+ ].includes(view.type) ? [view] : (view.children ?? []).flatMap(controls);
2551
+ }
2552
+ function answerText(card, action, values) {
2553
+ const node = controls(card.view).find((node) => node.id === action);
2554
+ const lines = [card.title || "Deep View"];
2555
+ if (Array.isArray(values.selected)) lines.push(values.selected.map(String).join(", "));
2556
+ if (typeof values.confirmed === "boolean") lines.push(values.confirmed ? node?.confirmLabel ?? "Confirm" : node?.cancelLabel ?? "Cancel");
2557
+ if (isRecord(values.fields)) for (const field of node?.fields ?? []) lines.push(`${field.label}: ${String(values.fields[field.id] ?? "")}`);
2558
+ const inputs = inputsSummary(card.view, values.inputs);
2559
+ if (inputs) lines.push(inputs);
2560
+ if (node?.type === "submit") lines.push(node.label ?? "Submit");
2561
+ return lines.join("\n");
2562
+ }
2563
+ /** Legacy messages have no metadata. Restore only unambiguous, fully validated
2564
+ * answers that reproduce the exact text written by this plugin. Never guess
2565
+ * from ordinary prose, and never modify the stored message. */
2566
+ function legacyAnswer(card, text) {
2567
+ if (text.startsWith("{")) try {
2568
+ const data = JSON.parse(text);
2569
+ if (!isRecord(data) || data.kind !== "answer_to_pending_view" || data.tool !== "show_view" || !Array.isArray(data.answers)) return null;
2570
+ const answer = data.answers.find((value) => isRecord(value) && value.id === card.id);
2571
+ if (!isRecord(answer) || typeof answer.action !== "string") return null;
2572
+ const values = validateViewAnswer(card.view, answer.action, answer.values);
2573
+ return values.error === void 0 ? {
2574
+ id: card.id,
2575
+ action: answer.action,
2576
+ values
2577
+ } : null;
2578
+ } catch {
2579
+ return null;
2580
+ }
2581
+ const lines = text.split("\n");
2582
+ if (lines.shift() !== (card.title || "Deep View")) return null;
2583
+ const candidates = [];
2584
+ for (const node of controls(card.view)) {
2585
+ const rest = [...lines];
2586
+ const values = {};
2587
+ if (node.type === "options") {
2588
+ const line = rest.shift();
2589
+ if (line === void 0) continue;
2590
+ const labels = node.options?.map((option) => option.label) ?? [];
2591
+ if (node.multi && labels.some((label) => label.includes(", "))) continue;
2592
+ values.selected = node.multi ? line.split(", ") : [line];
2593
+ } else if (node.type === "confirm") {
2594
+ const line = rest.shift(), yes = node.confirmLabel ?? "Confirm", no = node.cancelLabel ?? "Cancel";
2595
+ if (yes === no || line !== yes && line !== no) continue;
2596
+ values.confirmed = line === yes;
2597
+ } else if (node.type === "form") {
2598
+ const fields = {};
2599
+ let valid = true;
2600
+ for (const field of node.fields ?? []) {
2601
+ const line = rest.shift(), prefix = `${field.label}: `;
2602
+ if (line === void 0 || !line.startsWith(prefix)) {
2603
+ valid = false;
2604
+ break;
2605
+ }
2606
+ fields[field.id] = line.slice(prefix.length);
2607
+ }
2608
+ if (!valid) continue;
2609
+ values.fields = fields;
2610
+ }
2611
+ const inputs = viewInputs(card.view);
2612
+ if (inputs.length) {
2613
+ const pairs = rest.shift()?.split("; ");
2614
+ if (!pairs || pairs.length !== inputs.length) continue;
2615
+ const sent = {};
2616
+ let valid = true;
2617
+ for (let i = 0; i < inputs.length; i++) {
2618
+ const input = inputs[i], prefix = `${input.label || input.id}=`;
2619
+ if (!pairs[i].startsWith(prefix)) {
2620
+ valid = false;
2621
+ break;
2622
+ }
2623
+ const value = pairs[i].slice(prefix.length);
2624
+ if (input.type === "toggle") sent[input.id] = value === "on" ? true : value === "off" ? false : void 0;
2625
+ else if (input.type === "choice") {
2626
+ const choices = input.options?.filter((option) => option.label === value) ?? [];
2627
+ sent[input.id] = choices.length === 1 ? choiceValue(choices[0]) : void 0;
2628
+ } else {
2629
+ const unit = input.unit ? ` ${input.unit}` : "";
2630
+ sent[input.id] = !unit || value.endsWith(unit) ? Number(unit ? value.slice(0, -unit.length) : value) : void 0;
2631
+ }
2632
+ }
2633
+ if (!valid) continue;
2634
+ values.inputs = sent;
2635
+ }
2636
+ if (node.type === "submit" && rest.shift() !== (node.label ?? "Submit")) continue;
2637
+ if (rest.length || !node.id) continue;
2638
+ const checked = validateViewAnswer(card.view, node.id, values);
2639
+ if (checked.error === void 0 && answerText(card, node.id, checked) === text) candidates.push({
2640
+ id: card.id,
2641
+ action: node.id,
2642
+ values: checked
2643
+ });
2644
+ }
2645
+ return candidates.length === 1 ? candidates[0] : null;
2646
+ }
2647
+ //#endregion
2648
+ //#region src/answers.ts
2649
+ /** Read visible, durable user/steering messages only, including inherited history. */
2650
+ function replyAnswers(snapshot) {
2651
+ const answers = /* @__PURE__ */ new Map();
2652
+ let cards = [];
2653
+ const collect = (block) => {
2654
+ const meta = block.meta;
2655
+ if (block.kind === "tool-result" && !block.isError && isRecord(meta) && meta.owner === "dsh-deep-view" && meta.awaitReply === true && typeof meta.viewId === "string") {
2656
+ const shown = checkShownView(meta.title, meta.view);
2657
+ if (!("error" in shown)) cards.push({
2658
+ id: meta.viewId,
2659
+ title: shown.title,
2660
+ view: shown.view
2661
+ });
2662
+ }
2663
+ for (const child of block.subCalls ?? []) collect(child);
2664
+ };
2665
+ for (const key of snapshot.order) {
2666
+ const node = snapshot.nodes.get(key);
2667
+ if (node?.kind === "tool-call" && node.data?.root) collect(node.data.root);
2668
+ if (node?.kind !== "user" && node?.kind !== "steering") continue;
2669
+ const source = node.data?.source;
2670
+ if (!isRecord(source) || source.kind !== "user") continue;
2671
+ if (source.deepView === void 0) {
2672
+ const text = node.data?.content?.filter(isRecord).filter((block) => block.type === "text").map((block) => block.text).join("") ?? "";
2673
+ const candidates = cards.map((card) => legacyAnswer(card, text)).filter((answer) => answer !== null);
2674
+ if (candidates.length === 1 && !answers.has(candidates[0].id)) answers.set(candidates[0].id, candidates[0]);
2675
+ }
2676
+ cards = [];
2677
+ if (!isRecord(source.deepView)) continue;
2678
+ const meta = source.deepView;
2679
+ if (meta.owner !== "dsh-deep-view" || meta.version !== 1 || !isRecord(meta.answer)) continue;
2680
+ const answer = meta.answer;
2681
+ if (typeof answer.id !== "string" || typeof answer.action !== "string" || !isRecord(answer.values)) continue;
2682
+ if (!answers.has(answer.id)) answers.set(answer.id, answer);
2683
+ }
2684
+ return answers;
2685
+ }
2686
+ function checkedAnswer(answer, id, view) {
2687
+ if (!answer || !id || answer.id !== id) return null;
2688
+ const values = validateViewAnswer(view, answer.action, answer.values);
2689
+ return values.error === void 0 ? {
2690
+ id,
2691
+ action: answer.action,
2692
+ values
2693
+ } : null;
2694
+ }
2695
+ function sameAnswers(left, right) {
2696
+ return left.size === right.size && [...left].every(([id, answer]) => {
2697
+ const next = right.get(id);
2698
+ return next === answer || next?.action === answer.action && JSON.stringify(next.values) === JSON.stringify(answer.values);
2699
+ });
2700
+ }
2701
+ //#endregion
2702
+ //#region src/client.tsx
2703
+ const name = "dsh-deep-view-client";
2704
+ const inject = [
2705
+ "slots",
2706
+ "connection",
2707
+ "locale"
2708
+ ];
2709
+ const LOCALE = "deep-view";
2710
+ const uiZh = {
2711
+ copy: "复制",
2712
+ copied: "已复制",
2713
+ footnotes: "脚注",
2714
+ code: "代码",
2715
+ wrap: "自动换行",
2716
+ unwrap: "不换行",
2717
+ prepared: "卡片将在最终回复中显示",
2718
+ preparing: "正在准备视图…",
2719
+ error: "视图未能显示",
2720
+ invalid: "视图数据无效",
2721
+ local: "仅本地预览;此卡片不会发送回答。",
2722
+ answerFailed: "回答未能发送,请重试。",
2723
+ chart: "图表",
2724
+ chartOptions: "图表选项",
2725
+ viewAsChart: "查看图表",
2726
+ viewAsTable: "查看表格",
2727
+ copyData: "复制数据",
2728
+ chartNumber: "图表 {n}",
2729
+ value: "值",
2730
+ series: "系列",
2731
+ view: "视图",
2732
+ sources: "来源",
2733
+ progress: "进度",
2734
+ yourAnswer: "你的回答",
2735
+ options: "选项",
2736
+ submit: "提交",
2737
+ cancel: "取消",
2738
+ confirm: "确认",
2739
+ send: "发送",
2740
+ selected: "已选择"
2741
+ };
2742
+ const uiEn = {
2743
+ copy: "Copy",
2744
+ copied: "Copied",
2745
+ footnotes: "Footnotes",
2746
+ code: "Code",
2747
+ wrap: "Wrap lines",
2748
+ unwrap: "Do not wrap lines",
2749
+ prepared: "The card will appear in the final reply",
2750
+ preparing: "Preparing view…",
2751
+ error: "The view could not be shown",
2752
+ invalid: "Invalid view data",
2753
+ local: "Local preview only; this card does not send an answer.",
2754
+ answerFailed: "The answer could not be sent; try again.",
2755
+ chart: "Chart",
2756
+ chartOptions: "Chart options",
2757
+ viewAsChart: "View as chart",
2758
+ viewAsTable: "View as table",
2759
+ copyData: "Copy data",
2760
+ chartNumber: "Chart {n}",
2761
+ value: "Value",
2762
+ series: "Series",
2763
+ view: "View",
2764
+ sources: "Sources",
2765
+ progress: "Progress",
2766
+ yourAnswer: "Your answer",
2767
+ options: "Options",
2768
+ submit: "Submit",
2769
+ cancel: "Cancel",
2770
+ confirm: "Confirm",
2771
+ send: "Send",
2772
+ selected: "Selected"
2773
+ };
2774
+ const viewT = (t) => (key, params) => {
2775
+ const keys = {
2776
+ Copy: "copy",
2777
+ Copied: "copied",
2778
+ Footnotes: "footnotes",
2779
+ Chart: "chart",
2780
+ "Chart options": "chartOptions",
2781
+ "View as chart": "viewAsChart",
2782
+ "View as table": "viewAsTable",
2783
+ "Copy data": "copyData",
2784
+ "Chart {n}": "chartNumber",
2785
+ Value: "value",
2786
+ Series: "series",
2787
+ View: "view",
2788
+ Sources: "sources",
2789
+ Progress: "progress",
2790
+ "Your answer": "yourAnswer",
2791
+ "Local preview only; this card does not send an answer.": "local",
2792
+ Options: "options",
2793
+ Submit: "submit",
2794
+ Cancel: "cancel",
2795
+ Confirm: "confirm",
2796
+ Send: "send",
2797
+ Selected: "selected"
2798
+ };
2799
+ return keys[key] ? t(keys[key], params) : key.replace(/\{(\w+)\}/g, (_, name) => String(params?.[name] ?? `{${name}}`));
2800
+ };
2801
+ function ViewMarkdown(props) {
2802
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, { ...props });
2803
+ }
2804
+ function useMarkdownLabels(t) {
2805
+ return (0, react.useMemo)(() => ({
2806
+ code: {
2807
+ copyLabel: t("copy"),
2808
+ copiedLabel: t("copied"),
2809
+ toolbarLabels: {
2810
+ codeLabel: t("code"),
2811
+ wrapLabel: t("wrap"),
2812
+ unwrapLabel: t("unwrap")
2813
+ }
2814
+ },
2815
+ footnotes: t("footnotes")
2816
+ }), [t]);
2817
+ }
2818
+ function isArgsView(value) {
2819
+ return isRecord(value) && typeof value.text === "function" && typeof value.value === "function";
2820
+ }
2821
+ function argField(args, key) {
2822
+ if (!isArgsView(args)) return void 0;
2823
+ const value = args.value(key);
2824
+ return value !== void 0 ? value : args.text(key);
2825
+ }
2826
+ function readToolCall(props) {
2827
+ const block = props.block;
2828
+ const phase = props.phase ?? (block.kind === "tool-result" ? "result" : "start");
2829
+ const isError = phase === "result" && block.isError === true;
2830
+ const meta = phase === "result" ? block.meta : void 0;
2831
+ if (isRecord(meta) && meta.owner === "dsh-deep-view") {
2832
+ const shown = checkShownView(meta.title, meta.view);
2833
+ if ("error" in shown) return {
2834
+ title: meta.title,
2835
+ view: meta.view,
2836
+ viewReady: true,
2837
+ phase,
2838
+ isError: true
2839
+ };
2840
+ return {
2841
+ presentation: {
2842
+ owner: VIEW_OWNER,
2843
+ ...shown,
2844
+ ...typeof meta.viewId === "string" ? { viewId: meta.viewId } : {},
2845
+ awaitReply: meta.awaitReply === true
2846
+ },
2847
+ title: meta.title,
2848
+ view: meta.view,
2849
+ viewReady: true,
2850
+ phase,
2851
+ isError
2852
+ };
2853
+ }
2854
+ const args = block.args;
2855
+ const title = argField(args, "title");
2856
+ const viewReady = phase !== "preparing" || isArgsView(args) && args.complete("view");
2857
+ return {
2858
+ title,
2859
+ view: viewReady ? argField(args, "view") : void 0,
2860
+ viewReady,
2861
+ phase,
2862
+ isError
2863
+ };
2864
+ }
2865
+ function ToolCallView(props) {
2866
+ const t = props.t;
2867
+ const labels = useMarkdownLabels(t);
2868
+ const read = (0, react.useMemo)(() => readToolCall(props), [props.block, props.phase]);
2869
+ const shown = (0, react.useMemo)(() => {
2870
+ if (read.presentation) return read.presentation;
2871
+ if (!read.viewReady || read.phase === "preparing") return void 0;
2872
+ const checked = checkShownView(read.title, read.view);
2873
+ return "error" in checked ? void 0 : {
2874
+ ...checked,
2875
+ awaitReply: checked.interactive
2876
+ };
2877
+ }, [
2878
+ read.presentation,
2879
+ read.viewReady,
2880
+ read.phase,
2881
+ read.title,
2882
+ read.view
2883
+ ]);
2884
+ const [pending, setPending] = (0, react.useState)(null);
2885
+ const [busy, setBusy] = (0, react.useState)(false);
2886
+ const [failed, setFailed] = (0, react.useState)(false);
2887
+ const [localAnswer, setAnswer] = (0, react.useState)(null);
2888
+ const restored = (0, react.useMemo)(() => shown ? checkedAnswer(props.restoredAnswer, read.presentation?.viewId, shown.view) : null, [
2889
+ props.restoredAnswer,
2890
+ read.presentation?.viewId,
2891
+ shown
2892
+ ]);
2893
+ const answer = restored ?? localAnswer;
2894
+ const sequence = (0, react.useRef)(0);
2895
+ const callId = props.callId;
2896
+ (0, react.useEffect)(() => {
2897
+ const seq = ++sequence.current;
2898
+ setPending(null);
2899
+ setFailed(false);
2900
+ if (restored || !read.presentation?.awaitReply || !callId) return;
2901
+ props.call("pending", {
2902
+ sessionId: props.sessionId,
2903
+ callId
2904
+ }).then((result) => {
2905
+ if (sequence.current !== seq) return;
2906
+ const envelope = result;
2907
+ if (envelope?.ok) setPending(envelope.value);
2908
+ }).catch(() => {});
2909
+ return () => {
2910
+ sequence.current += 1;
2911
+ };
2912
+ }, [
2913
+ props.call,
2914
+ props.sessionId,
2915
+ callId,
2916
+ read.presentation?.awaitReply,
2917
+ read.presentation?.viewId,
2918
+ restored
2919
+ ]);
2920
+ const submit = (0, react.useCallback)((action, values) => {
2921
+ if (!pending || busy) return;
2922
+ setBusy(true);
2923
+ setFailed(false);
2924
+ props.call("reply", {
2925
+ sessionId: props.sessionId,
2926
+ viewId: pending.id,
2927
+ action,
2928
+ values
2929
+ }).then((result) => {
2930
+ if (result?.ok) {
2931
+ setAnswer({
2932
+ id: pending.id,
2933
+ action,
2934
+ values
2935
+ });
2936
+ setPending(null);
2937
+ } else setFailed(true);
2938
+ }).catch(() => setFailed(true)).finally(() => setBusy(false));
2939
+ }, [
2940
+ props.call,
2941
+ props.sessionId,
2942
+ pending,
2943
+ busy
2944
+ ]);
2945
+ if (read.phase === "preparing") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2946
+ className: "dvui-root dvui-tool-loading",
2947
+ role: "status",
2948
+ children: t("preparing")
2949
+ });
2950
+ if (read.isError || !shown) {
2951
+ if (read.phase !== "result" && !read.isError) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2952
+ className: "dvui-root dvui-tool-loading",
2953
+ role: "status",
2954
+ children: t("preparing")
2955
+ });
2956
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2957
+ className: "dvui-root dvui-tool-error",
2958
+ role: "status",
2959
+ children: t(read.isError ? "error" : "invalid")
2960
+ });
2961
+ }
2962
+ const canAnswer = shown.awaitReply && pending !== null && answer === null;
2963
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2964
+ className: "dvui-root",
2965
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetCard, {
2966
+ title: shown.title,
2967
+ view: shown.view,
2968
+ onSubmit: canAnswer ? submit : void 0,
2969
+ busy,
2970
+ settled: answer !== null || !canAnswer,
2971
+ answer,
2972
+ t: viewT(t),
2973
+ labels,
2974
+ Markdown: ViewMarkdown,
2975
+ localOnly: answer === null && !canAnswer && shown.interactive
2976
+ }), failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2977
+ className: "dvui-tool-error",
2978
+ role: "alert",
2979
+ children: t("answerFailed")
2980
+ }) : null]
2981
+ });
2982
+ }
2983
+ /** Collect only this turn's successfully persisted cards, including PTC children.
2984
+ * The turn index also covers hidden process rows; no all-history scan or DOM
2985
+ * relocation is needed. Result metadata works even when arguments were evicted.
2986
+ */
2987
+ function replyCards(snapshot, turn) {
2988
+ const cards = [];
2989
+ const seen = /* @__PURE__ */ new Set();
2990
+ const visit = (block) => {
2991
+ if (block.kind === "tool-result" && !block.isError && !seen.has(block.callId) && isRecord(block.meta) && block.meta.owner === "dsh-deep-view") {
2992
+ const read = readToolCall({
2993
+ phase: "result",
2994
+ block
2995
+ });
2996
+ if (read.presentation && !read.isError) {
2997
+ seen.add(block.callId);
2998
+ cards.push(block);
2999
+ }
3000
+ }
3001
+ for (const child of block.subCalls ?? []) visit(child);
3002
+ };
3003
+ for (const key of snapshot.locations.getTurn(turn)) {
3004
+ const node = snapshot.nodes.get(key);
3005
+ if (node?.kind === "tool-call" && node.data?.root) visit(node.data.root);
3006
+ }
3007
+ return cards;
3008
+ }
3009
+ const sameCards = (left, right) => left.length === right.length && left.every((block, index) => block === right[index]);
3010
+ /** Native completed-turn surface: always outside the process disclosure. */
3011
+ function ReplyView(props) {
3012
+ const cards = props.useChat((snapshot) => replyCards(snapshot, props.turn.turn), sameCards);
3013
+ const answers = props.useChat(replyAnswers, sameAnswers);
3014
+ if (props.turn.status !== "closed" || cards.length === 0) return null;
3015
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3016
+ className: "dvui-root",
3017
+ "data-deep-view-reply": true,
3018
+ children: cards.map((block) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallView, {
3019
+ sessionId: props.sessionId,
3020
+ callId: block.callId,
3021
+ phase: "result",
3022
+ block,
3023
+ t: props.t,
3024
+ call: props.call,
3025
+ restoredAnswer: isRecord(block.meta) && typeof block.meta.viewId === "string" ? answers.get(block.meta.viewId) : void 0
3026
+ }, `${props.sessionId}:${block.callId}`))
3027
+ });
3028
+ }
3029
+ /** Tool activity contains status only, never the user's interactive card. */
3030
+ function ToolCallStatus(props) {
3031
+ const read = readToolCall(props);
3032
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3033
+ className: "dvui-root",
3034
+ role: "status",
3035
+ children: props.t(read.isError ? "error" : read.phase === "result" && !read.presentation ? "invalid" : read.phase === "result" ? "prepared" : "preparing")
3036
+ });
3037
+ }
3038
+ function apply(ctx) {
3039
+ const client = ctx;
3040
+ ctx.effect(() => client.locale.register(LOCALE, {
3041
+ zh: { ...uiZh },
3042
+ en: { ...uiEn }
3043
+ }), "deep-view.locale");
3044
+ ctx.effect(() => {
3045
+ if (typeof document === "undefined") return () => {};
3046
+ const style = document.createElement("style");
3047
+ style.setAttribute("data-plugin", PLUGIN_NAME);
3048
+ style.textContent = viewStyles;
3049
+ document.head.appendChild(style);
3050
+ return () => style.remove();
3051
+ }, "deep-view.styles");
3052
+ ctx.effect(() => client.slots.inject("tool.call.toolview", () => client.slots.register({
3053
+ name: "tool.call.toolview",
3054
+ key: "show_view",
3055
+ locale: LOCALE
3056
+ }, ToolCallStatus)), "deep-view.tool");
3057
+ ctx.effect(() => client.slots.inject("conversation.chat.turnTail", () => client.slots.register({
3058
+ name: "conversation.chat.turnTail",
3059
+ id: "deep-view",
3060
+ order: 20,
3061
+ locale: LOCALE,
3062
+ inject: () => ({ call: (endpoint, payload) => client.connection.rpc.call(RPC_CHANNEL, endpoint, payload) })
3063
+ }, ReplyView)), "deep-view.reply");
3064
+ }
3065
+ //#endregion
3066
+ exports.LOCALE = LOCALE;
3067
+ exports.ReplyView = ReplyView;
3068
+ exports.ToolCallStatus = ToolCallStatus;
3069
+ exports.ToolCallView = ToolCallView;
3070
+ exports.apply = apply;
3071
+ exports.inject = inject;
3072
+ exports.name = name;
3073
+ exports.readToolCall = readToolCall;
3074
+ exports.replyCards = replyCards;
3075
+ exports.uiEn = uiEn;
3076
+ exports.uiZh = uiZh;
3077
+ exports.useMarkdownLabels = useMarkdownLabels;
3078
+
3079
+ //# sourceMappingURL=client.inner.cjs.map
3080
+ return module.exports;
3081
+ }
3082
+ });