@broadpaper/core 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -879,22 +879,22 @@ var Scope = class _Scope {
879
879
  }
880
880
  };
881
881
  var FUNCTIONS = {
882
- sum: ([list]) => numbers(list).reduce((a, b) => a + b, 0),
883
- count: ([list]) => Array.isArray(list) ? list.length : isEmptyValue(list) ? 0 : 1,
884
- avg: ([list]) => {
885
- const ns = numbers(list);
882
+ sum: ([list2]) => numbers(list2).reduce((a, b) => a + b, 0),
883
+ count: ([list2]) => Array.isArray(list2) ? list2.length : isEmptyValue(list2) ? 0 : 1,
884
+ avg: ([list2]) => {
885
+ const ns = numbers(list2);
886
886
  return ns.length ? ns.reduce((a, b) => a + b, 0) / ns.length : null;
887
887
  },
888
- min: ([list, ...rest]) => {
889
- const ns = rest.length ? numbers([list, ...rest]) : numbers(list);
888
+ min: ([list2, ...rest]) => {
889
+ const ns = rest.length ? numbers([list2, ...rest]) : numbers(list2);
890
890
  return ns.length ? Math.min(...ns) : null;
891
891
  },
892
- max: ([list, ...rest]) => {
893
- const ns = rest.length ? numbers([list, ...rest]) : numbers(list);
892
+ max: ([list2, ...rest]) => {
893
+ const ns = rest.length ? numbers([list2, ...rest]) : numbers(list2);
894
894
  return ns.length ? Math.max(...ns) : null;
895
895
  },
896
- first: ([list]) => Array.isArray(list) ? list[0] : list,
897
- last: ([list]) => Array.isArray(list) ? list[list.length - 1] : list,
896
+ first: ([list2]) => Array.isArray(list2) ? list2[0] : list2,
897
+ last: ([list2]) => Array.isArray(list2) ? list2[list2.length - 1] : list2,
898
898
  length: ([v]) => Array.isArray(v) ? v.length : toDisplayString(v).length,
899
899
  coalesce: (args) => args.find((a) => !isEmptyValue(a)) ?? null,
900
900
  isEmpty: ([v]) => isEmptyValue(v),
@@ -933,13 +933,13 @@ function dateOf(v) {
933
933
  }
934
934
  return null;
935
935
  }
936
- function numbers(list) {
937
- if (!Array.isArray(list)) {
938
- const n = toNumber(list);
936
+ function numbers(list2) {
937
+ if (!Array.isArray(list2)) {
938
+ const n = toNumber(list2);
939
939
  return n === null ? [] : [n];
940
940
  }
941
941
  const out = [];
942
- for (const v of list) {
942
+ for (const v of list2) {
943
943
  const n = toNumber(v);
944
944
  if (n !== null) out.push(n);
945
945
  }
@@ -1442,8 +1442,8 @@ function defaultDocumentStyles() {
1442
1442
  function isTokenRef(value) {
1443
1443
  return typeof value === "string" && value.startsWith("$") && value.length > 1;
1444
1444
  }
1445
- function resolveToken(token, theme) {
1446
- const path = token.slice(1).split(".");
1445
+ function resolveToken(token2, theme) {
1446
+ const path = token2.slice(1).split(".");
1447
1447
  const fromTheme = walk(theme, path);
1448
1448
  if (typeof fromTheme === "string") return fromTheme;
1449
1449
  const fromDefault = walk(DEFAULT_THEME, path);
@@ -2204,6 +2204,169 @@ function humanizeKey(key) {
2204
2204
  return key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
2205
2205
  }
2206
2206
 
2207
+ // src/data/pipes.ts
2208
+ function defaultPipeFor(schema) {
2209
+ if (!schema) return null;
2210
+ switch (schema.type) {
2211
+ case "currency": {
2212
+ const code = schema.currency?.trim().toUpperCase();
2213
+ return code ? `currency:'${code}'` : "currency";
2214
+ }
2215
+ case "percent":
2216
+ return "percent";
2217
+ case "number":
2218
+ return "number";
2219
+ case "date":
2220
+ return schema.format ? `date:'${escapePattern(schema.format)}'` : "date";
2221
+ case "datetime":
2222
+ return schema.format ? `datetime:'${escapePattern(schema.format)}'` : "datetime";
2223
+ default:
2224
+ return null;
2225
+ }
2226
+ }
2227
+ function withDefaultPipe(expr, schema) {
2228
+ const trimmed = expr.trim();
2229
+ if (!trimmed || hasPipe(trimmed)) return trimmed;
2230
+ if (!isPlainPath(trimmed)) return trimmed;
2231
+ const pipe = defaultPipeFor(schema);
2232
+ return pipe ? `${trimmed} | ${pipe}` : trimmed;
2233
+ }
2234
+ function hasPipe(expr) {
2235
+ for (let i = 0; i < expr.length; i++) {
2236
+ if (expr[i] !== "|") continue;
2237
+ if (expr[i + 1] === "|") {
2238
+ i++;
2239
+ continue;
2240
+ }
2241
+ if (expr[i - 1] === "|") continue;
2242
+ return true;
2243
+ }
2244
+ return false;
2245
+ }
2246
+ function isPlainPath(expr) {
2247
+ return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[\d+\])*$/.test(expr.trim());
2248
+ }
2249
+ function escapePattern(pattern) {
2250
+ return pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2251
+ }
2252
+
2253
+ // src/data/calculation.ts
2254
+ function calcPaths(calc) {
2255
+ const out = [];
2256
+ const add = (term) => {
2257
+ if (term.kind === "field" || term.kind === "aggregate") out.push(term.path);
2258
+ };
2259
+ add(calc.start);
2260
+ for (const step of calc.steps) add(step.term);
2261
+ return out;
2262
+ }
2263
+ function termAst(term) {
2264
+ if (term.kind === "number") return { kind: "literal", value: term.value };
2265
+ const path = { kind: "path", segments: term.path.split(".").map((name) => ({ kind: "prop", name })) };
2266
+ return term.kind === "aggregate" ? { kind: "call", name: term.fn, args: [path] } : path;
2267
+ }
2268
+ function safe(term) {
2269
+ if (term.kind === "number") return termAst(term);
2270
+ return { kind: "call", name: "coalesce", args: [termAst(term), { kind: "literal", value: 0 }] };
2271
+ }
2272
+ function applyStep(left, step) {
2273
+ switch (step.op) {
2274
+ case "add":
2275
+ return { kind: "binary", op: "+", left, right: termAst(step.term) };
2276
+ case "subtract":
2277
+ return { kind: "binary", op: "-", left, right: termAst(step.term) };
2278
+ case "multiply":
2279
+ return { kind: "binary", op: "*", left, right: termAst(step.term) };
2280
+ case "divide":
2281
+ return { kind: "binary", op: "/", left, right: termAst(step.term) };
2282
+ case "addPercent":
2283
+ return { kind: "binary", op: "*", left, right: { kind: "binary", op: "+", left: { kind: "literal", value: 1 }, right: safe(step.term) } };
2284
+ case "subtractPercent":
2285
+ return { kind: "binary", op: "*", left, right: { kind: "binary", op: "-", left: { kind: "literal", value: 1 }, right: safe(step.term) } };
2286
+ }
2287
+ }
2288
+ function calcToAst(calc) {
2289
+ let ast = termAst(calc.start);
2290
+ for (const step of calc.steps) ast = applyStep(ast, step);
2291
+ if (!calc.format) return ast;
2292
+ const piped = parseExpression(`__x__ | ${calc.format}`);
2293
+ return graft(piped, ast);
2294
+ }
2295
+ function graft(piped, input) {
2296
+ if (piped.kind === "pipe") return { ...piped, input: graft(piped.input, input) };
2297
+ return input;
2298
+ }
2299
+ function toExpression(calc) {
2300
+ return exprToString(calcToAst(calc));
2301
+ }
2302
+ var AGGREGATES = ["sum", "avg", "count", "min", "max"];
2303
+ function asTerm(e) {
2304
+ if (e.kind === "literal" && typeof e.value === "number") return { kind: "number", value: e.value };
2305
+ if (e.kind === "path" && e.segments.every((s) => s.kind === "prop")) {
2306
+ return { kind: "field", path: e.segments.map((s) => s.kind === "prop" ? s.name : "").join(".") };
2307
+ }
2308
+ if (e.kind === "call" && AGGREGATES.includes(e.name) && e.args.length === 1) {
2309
+ const inner = asTerm(e.args[0]);
2310
+ if (inner?.kind === "field") return { kind: "aggregate", fn: e.name, path: inner.path };
2311
+ }
2312
+ return null;
2313
+ }
2314
+ function asSafeTerm(e) {
2315
+ if (e.kind === "call" && e.name === "coalesce" && e.args.length === 2) {
2316
+ const fallback = e.args[1];
2317
+ if (fallback.kind === "literal" && fallback.value === 0) return asTerm(e.args[0]);
2318
+ return null;
2319
+ }
2320
+ return asTerm(e);
2321
+ }
2322
+ function asPercent(e) {
2323
+ if (e.kind !== "binary" || e.op !== "+" && e.op !== "-") return null;
2324
+ if (e.left.kind !== "literal" || e.left.value !== 1) return null;
2325
+ const term = asSafeTerm(e.right);
2326
+ if (!term) return null;
2327
+ return { op: e.op === "+" ? "addPercent" : "subtractPercent", term };
2328
+ }
2329
+ var OPS = { "+": "add", "-": "subtract", "*": "multiply", "/": "divide" };
2330
+ function fromExpression(expr) {
2331
+ let ast;
2332
+ try {
2333
+ ast = parseExpression(expr);
2334
+ } catch {
2335
+ return null;
2336
+ }
2337
+ const formats = [];
2338
+ while (ast.kind === "pipe") {
2339
+ formats.unshift(`${ast.name}${ast.args.length ? ":" + ast.args.map(exprToString).join(":") : ""}`);
2340
+ ast = ast.input;
2341
+ }
2342
+ const format = formats.length ? formats.join(" | ") : void 0;
2343
+ const steps = [];
2344
+ let cursor = ast;
2345
+ while (cursor.kind === "binary") {
2346
+ const op = OPS[cursor.op];
2347
+ if (!op) return null;
2348
+ if (op === "multiply") {
2349
+ const percent = asPercent(cursor.right);
2350
+ if (percent) {
2351
+ steps.unshift(percent);
2352
+ cursor = cursor.left;
2353
+ continue;
2354
+ }
2355
+ }
2356
+ const term = asTerm(cursor.right);
2357
+ if (!term) return null;
2358
+ steps.unshift({ op, term });
2359
+ cursor = cursor.left;
2360
+ }
2361
+ const start = asTerm(cursor);
2362
+ if (!start) return null;
2363
+ const calc = { start, steps, ...format ? { format } : {} };
2364
+ return toExpression(calc) === expr.trim() ? calc : null;
2365
+ }
2366
+ function isCalculation(calc) {
2367
+ return calc.steps.length > 0 || calc.start.kind === "aggregate";
2368
+ }
2369
+
2207
2370
  // src/resolve/describe.ts
2208
2371
  function describeExpression(expr, scope) {
2209
2372
  try {
@@ -2232,6 +2395,522 @@ function humaniseLabel(text, scope) {
2232
2395
  });
2233
2396
  }
2234
2397
 
2398
+ // src/diff/template-diff.ts
2399
+ var DISPLAY_LIMIT = 80;
2400
+ var SUMMARY_LIMIT = 120;
2401
+ var AREA_RANK = { document: 0, page: 1, styles: 2, header: 3, body: 4, footer: 5 };
2402
+ function diffTemplates(before, after, options = {}) {
2403
+ const ctx = {
2404
+ registry: options.registry,
2405
+ scope: createSchemaScope(options.dataSources ?? [])
2406
+ };
2407
+ const placed = [
2408
+ ...documentChanges(before, after, ctx),
2409
+ ...pageChanges(before.page, after.page),
2410
+ ...styleChanges(before, after, ctx),
2411
+ ...nodeChanges(before, after, ctx)
2412
+ ];
2413
+ const ordered = placed.map((c, i) => ({ c, i })).sort((a, b) => AREA_RANK[a.c.area] - AREA_RANK[b.c.area] || a.c.rank - b.c.rank || a.i - b.i).map(({ c }) => strip(c));
2414
+ const counts = { added: 0, removed: 0, changed: 0, moved: 0 };
2415
+ for (const change of ordered) counts[change.kind]++;
2416
+ const max = Math.max(0, options.maxChanges ?? 200);
2417
+ return {
2418
+ changes: ordered.slice(0, max),
2419
+ counts,
2420
+ truncated: Math.max(0, ordered.length - max),
2421
+ identical: ordered.length === 0
2422
+ };
2423
+ }
2424
+ function summariseDiff(diff) {
2425
+ const total = diff.counts.added + diff.counts.removed + diff.counts.changed + diff.counts.moved;
2426
+ if (total === 0) return "No changes";
2427
+ if (total === 1) {
2428
+ const only = diff.changes[0];
2429
+ return clip(only ? `1 change: ${only.summary}` : "1 change", SUMMARY_LIMIT);
2430
+ }
2431
+ const parts = ["added", "changed", "moved", "removed"].filter((k) => diff.counts[k] > 0).map((k) => `${diff.counts[k]} ${k}`);
2432
+ return clip(`${total} changes: ${parts.join(", ")}`, SUMMARY_LIMIT);
2433
+ }
2434
+ function strip(change) {
2435
+ const { rank, ...rest } = change;
2436
+ void rank;
2437
+ const out = { kind: rest.kind, area: rest.area, subject: rest.subject, summary: rest.summary };
2438
+ if (rest.nodeId !== void 0) out.nodeId = rest.nodeId;
2439
+ if (rest.before !== void 0) out.before = clip(rest.before, DISPLAY_LIMIT);
2440
+ if (rest.after !== void 0) out.after = clip(rest.after, DISPLAY_LIMIT);
2441
+ return out;
2442
+ }
2443
+ function documentChanges(before, after, ctx) {
2444
+ void ctx;
2445
+ const out = [];
2446
+ if (before.name !== after.name) {
2447
+ out.push({ kind: "changed", area: "document", subject: "Design name", summary: "Renamed the design", before: before.name, after: after.name, rank: 0 });
2448
+ }
2449
+ const settings = [
2450
+ ["locale", "Language and region", "Changed the language and region"],
2451
+ ["currency", "Currency", "Changed the currency"],
2452
+ ["timeZone", "Time zone", "Changed the time zone"]
2453
+ ];
2454
+ settings.forEach(([key, subject, summary], i) => {
2455
+ const a = before.settings?.[key];
2456
+ const b = after.settings?.[key];
2457
+ if (a === b) return;
2458
+ out.push({ kind: "changed", area: "document", subject, summary, before: a === void 0 ? "Default" : String(a), after: b === void 0 ? "Default" : String(b), rank: i + 1 });
2459
+ });
2460
+ return out;
2461
+ }
2462
+ function pageChanges(before, after) {
2463
+ const out = [];
2464
+ let rank = 0;
2465
+ const push = (subject, summary, a, b) => {
2466
+ out.push({ kind: "changed", area: "page", subject, summary, ...a !== void 0 ? { before: a } : {}, ...b !== void 0 ? { after: b } : {}, rank: rank++ });
2467
+ };
2468
+ if (!same(before.size, after.size)) push("Page size", "Changed the page size", pageSize(before.size), pageSize(after.size));
2469
+ if (before.orientation !== after.orientation) push("Page orientation", "Changed the page orientation", humanize(before.orientation), humanize(after.orientation));
2470
+ if (!same(before.margins, after.margins)) push("Page margins", "Changed the page margins", margins(before.margins), margins(after.margins));
2471
+ for (const region of ["header", "footer"]) {
2472
+ const a = before[region];
2473
+ const b = after[region];
2474
+ const name = region === "header" ? "Header" : "Footer";
2475
+ if (a?.height !== b?.height) push(`${name} height`, `Changed the ${region} height`, regionHeight(a?.height), regionHeight(b?.height));
2476
+ if (Boolean(a?.differentFirstPage) !== Boolean(b?.differentFirstPage)) {
2477
+ push(
2478
+ `Different first-page ${region}`,
2479
+ b?.differentFirstPage ? `Turned on a different ${region} for the first page` : `Turned off the different ${region} for the first page`,
2480
+ onOff(a?.differentFirstPage),
2481
+ onOff(b?.differentFirstPage)
2482
+ );
2483
+ }
2484
+ }
2485
+ if (before.background !== after.background) push("Page background", "Changed the page background", colour(before.background), colour(after.background));
2486
+ if (before.minPages !== after.minPages) {
2487
+ push("Empty pages shown", "Changed how many pages an empty document shows", String(before.minPages ?? 1), String(after.minPages ?? 1));
2488
+ }
2489
+ const known = /* @__PURE__ */ new Set(["size", "orientation", "margins", "header", "footer", "background", "minPages"]);
2490
+ const extra = keysOf(before, after).filter((k) => !known.has(k) && !same(before[k], after[k]));
2491
+ if (extra.length) push("Page settings", "Changed the page settings");
2492
+ return out;
2493
+ }
2494
+ function pageSize(size) {
2495
+ return typeof size === "string" ? size : `${size.width} \xD7 ${size.height} mm`;
2496
+ }
2497
+ function margins(m) {
2498
+ return [m.top, m.right, m.bottom, m.left].map((v) => `${v} mm`).join(" \xB7 ");
2499
+ }
2500
+ function regionHeight(height) {
2501
+ return !height ? "None" : `${height} mm`;
2502
+ }
2503
+ var TEXT_STYLE_WORDS = {
2504
+ fontFamily: "font",
2505
+ fontSize: "size",
2506
+ fontWeight: "weight",
2507
+ fontStyle: "slant",
2508
+ lineHeight: "line height",
2509
+ letterSpacing: "letter spacing",
2510
+ textTransform: "capitalisation",
2511
+ color: "colour",
2512
+ marginTop: "space above",
2513
+ marginBottom: "space below",
2514
+ keepWithNext: "keep with next"
2515
+ };
2516
+ var TABLE_WORDS = {
2517
+ fontSize: "text size",
2518
+ headerBackground: "header background",
2519
+ headerColor: "header text colour",
2520
+ headerFontWeight: "header weight",
2521
+ borderColor: "border colour",
2522
+ borderWidth: "border width",
2523
+ borders: "lines",
2524
+ zebraBackground: "stripe colour",
2525
+ cellPaddingX: "cell padding",
2526
+ cellPaddingY: "cell padding"
2527
+ };
2528
+ var CARD_WORDS = {
2529
+ background: "background",
2530
+ borderColor: "border colour",
2531
+ borderWidth: "border width",
2532
+ borderRadius: "corner radius",
2533
+ padding: "padding",
2534
+ shadow: "shadow"
2535
+ };
2536
+ var LINK_WORDS = { color: "colour", underline: "underline" };
2537
+ var SPACING_WORDS = {
2538
+ blockGap: "space between blocks",
2539
+ sectionPadding: "section padding",
2540
+ columnGap: "space between columns"
2541
+ };
2542
+ function styleChanges(before, after, ctx) {
2543
+ void ctx;
2544
+ const out = [];
2545
+ let rank = 0;
2546
+ const a = before.styles ?? {};
2547
+ const b = after.styles ?? {};
2548
+ const beforeText = a.text ?? {};
2549
+ const afterText = b.text ?? {};
2550
+ const ids = [...Object.keys(afterText), ...Object.keys(beforeText).filter((id) => !(id in afterText))];
2551
+ for (const id of ids) {
2552
+ const label = textStyleLabel(id);
2553
+ const subject = `${label} style`;
2554
+ if (!(id in beforeText)) {
2555
+ out.push({ kind: "added", area: "styles", subject, summary: `Added the ${label} style`, rank: rank++ });
2556
+ } else if (!(id in afterText)) {
2557
+ out.push({ kind: "removed", area: "styles", subject, summary: `Removed the ${label} style`, rank: rank++ });
2558
+ } else {
2559
+ const change = groupChange(beforeText[id], afterText[id], TEXT_STYLE_WORDS, (words) => `Changed the ${words} of the ${label} style`, subject);
2560
+ if (change) out.push({ ...change, rank: rank++ });
2561
+ }
2562
+ }
2563
+ const groups = [
2564
+ ["table", TABLE_WORDS, "Table style", (w) => `Changed the ${w} of tables`],
2565
+ ["card", CARD_WORDS, "Card style", (w) => `Changed the ${w} of cards`],
2566
+ ["link", LINK_WORDS, "Link style", (w) => `Changed the ${w} of links`],
2567
+ ["spacing", SPACING_WORDS, "Spacing", (w) => `Changed the ${w}`]
2568
+ ];
2569
+ for (const [key, words, subject, sentence] of groups) {
2570
+ const change = groupChange(
2571
+ a[key] ?? {},
2572
+ b[key] ?? {},
2573
+ words,
2574
+ sentence,
2575
+ subject
2576
+ );
2577
+ if (change) out.push({ ...change, rank: rank++ });
2578
+ }
2579
+ const known = /* @__PURE__ */ new Set(["text", "table", "card", "link", "spacing"]);
2580
+ const extra = keysOf(a, b).filter((k) => !known.has(k) && !same(a[k], b[k]));
2581
+ if (extra.length) out.push({ kind: "changed", area: "styles", subject: "Document styles", summary: "Changed the document styles", rank: rank++ });
2582
+ return out;
2583
+ }
2584
+ function groupChange(before, after, words, sentence, subject) {
2585
+ const changed = keysOf(before, after).filter((k) => !same(before[k], after[k]));
2586
+ if (!changed.length) return null;
2587
+ const names = unique(changed.map((k) => words[k] ?? lowerFirst(humanize(k))));
2588
+ const change = { kind: "changed", area: "styles", subject, summary: sentence(list(names)) };
2589
+ if (changed.length === 1) {
2590
+ const key = changed[0];
2591
+ change.before = styleValue(key, before[key]);
2592
+ change.after = styleValue(key, after[key]);
2593
+ }
2594
+ return change;
2595
+ }
2596
+ function styleValue(key, value) {
2597
+ if (value === void 0 || value === null) return "Default";
2598
+ if (typeof value === "boolean") return onOff(value);
2599
+ if (typeof value === "number") return /line|weight/i.test(key) ? String(value) : `${value} px`;
2600
+ if (typeof value === "string") return /colou?r|background/i.test(key) ? colour(value) : value.startsWith("$") ? token(value) : humanize(value);
2601
+ return "Custom";
2602
+ }
2603
+ function indexTemplate(template) {
2604
+ const byId = /* @__PURE__ */ new Map();
2605
+ const ordered = [];
2606
+ const children = /* @__PURE__ */ new Map();
2607
+ const lists = [
2608
+ ["header", "header:default", template.header?.default],
2609
+ ["header", "header:firstPage", template.header?.firstPage],
2610
+ ["body", "body", template.body],
2611
+ ["footer", "footer:default", template.footer?.default],
2612
+ ["footer", "footer:firstPage", template.footer?.firstPage]
2613
+ ];
2614
+ const walk2 = (nodes, area, parentKey, parentId) => {
2615
+ const ids = [];
2616
+ for (const node of nodes) {
2617
+ if (byId.has(node.id)) continue;
2618
+ const entry = { node, parentId, parentKey, area, order: ordered.length };
2619
+ byId.set(node.id, entry);
2620
+ ordered.push(entry);
2621
+ ids.push(node.id);
2622
+ if (node.children?.length) walk2(node.children, area, node.id, node.id);
2623
+ }
2624
+ children.set(parentKey, ids);
2625
+ };
2626
+ for (const [area, key, nodes] of lists) if (nodes) walk2(nodes, area, key, null);
2627
+ return { byId, ordered, children };
2628
+ }
2629
+ function nodeChanges(before, after, ctx) {
2630
+ const a = indexTemplate(before);
2631
+ const b = indexTemplate(after);
2632
+ const out = [];
2633
+ const removedRank = (entry) => {
2634
+ for (let i = entry.order - 1; i >= 0; i--) {
2635
+ const survivor = b.byId.get(a.ordered[i].node.id);
2636
+ if (survivor) return survivor.order + 0.5;
2637
+ }
2638
+ return -0.5;
2639
+ };
2640
+ for (const entry of a.ordered) {
2641
+ const id = entry.node.id;
2642
+ if (b.byId.has(id)) continue;
2643
+ if (entry.parentId && !b.byId.has(entry.parentId)) continue;
2644
+ out.push({
2645
+ kind: "removed",
2646
+ area: entry.area,
2647
+ nodeId: id,
2648
+ subject: subjectOf(entry, a, ctx),
2649
+ summary: `Removed ${refOf(entry, a, ctx, "definite")}${place(entry, a, "from", ctx)}`,
2650
+ rank: removedRank(entry)
2651
+ });
2652
+ }
2653
+ const reordered = /* @__PURE__ */ new Set();
2654
+ for (const [key, afterIds] of b.children) {
2655
+ const beforeIds = a.children.get(key);
2656
+ if (!beforeIds) continue;
2657
+ const shared = beforeIds.filter((id) => afterIds.includes(id));
2658
+ if (shared.length < 2) continue;
2659
+ const positions = shared.map((id) => afterIds.indexOf(id));
2660
+ const keep = longestIncreasing(positions);
2661
+ shared.forEach((id, i) => {
2662
+ if (!keep.has(i)) reordered.add(id);
2663
+ });
2664
+ }
2665
+ for (const entry of b.ordered) {
2666
+ const id = entry.node.id;
2667
+ const was = a.byId.get(id);
2668
+ if (!was) {
2669
+ if (entry.parentId && !a.byId.has(entry.parentId)) continue;
2670
+ out.push({
2671
+ kind: "added",
2672
+ area: entry.area,
2673
+ nodeId: id,
2674
+ subject: subjectOf(entry, b, ctx),
2675
+ summary: `Added ${refOf(entry, b, ctx, "indefinite")}${place(entry, b, "to", ctx)}`,
2676
+ rank: entry.order
2677
+ });
2678
+ continue;
2679
+ }
2680
+ if (was.parentKey !== entry.parentKey) {
2681
+ const from = place(was, a, "from", ctx);
2682
+ const to = place(entry, b, "to", ctx);
2683
+ const within = place(entry, b, "within", ctx);
2684
+ out.push({
2685
+ kind: "moved",
2686
+ area: entry.area,
2687
+ nodeId: id,
2688
+ subject: subjectOf(entry, b, ctx),
2689
+ // Between two columns of the same section is still "within" it: naming the
2690
+ // same section twice would read as a mistake.
2691
+ summary: `Moved ${refOf(entry, b, ctx, "definite")}${from.replace(/^ from /, "") === to.replace(/^ to /, "") ? within : `${from}${to}`}`,
2692
+ rank: entry.order
2693
+ });
2694
+ } else if (reordered.has(id)) {
2695
+ out.push({
2696
+ kind: "moved",
2697
+ area: entry.area,
2698
+ nodeId: id,
2699
+ subject: subjectOf(entry, b, ctx),
2700
+ summary: `Moved ${refOf(entry, b, ctx, "definite")}${place(entry, b, "within", ctx)}`,
2701
+ rank: entry.order
2702
+ });
2703
+ }
2704
+ const changed = nodeEdit(was, a, entry, b, ctx);
2705
+ if (changed) out.push({ ...changed, rank: entry.order + 0.25 });
2706
+ }
2707
+ return out;
2708
+ }
2709
+ function nodeEdit(was, wasIndex, now, nowIndex, ctx) {
2710
+ const x = was.node;
2711
+ const y = now.node;
2712
+ const propKeys = keysOf(x.props ?? {}, y.props ?? {}).filter((k) => !same(x.props?.[k], y.props?.[k]));
2713
+ const typeChanged = x.type !== y.type;
2714
+ const styleChanged = !same(x.style, y.style);
2715
+ const visibilityChanged = !same(x.visibleWhen, y.visibleWhen);
2716
+ const flowChanged = !same(x.flow, y.flow);
2717
+ const nameChanged = (x.name ?? "") !== (y.name ?? "");
2718
+ if (!propKeys.length && !typeChanged && !styleChanged && !visibilityChanged && !flowChanged && !nameChanged) return null;
2719
+ const ref = refOf(now, nowIndex, ctx, "definite");
2720
+ const where = place(now, nowIndex, "in", ctx);
2721
+ const base = { kind: "changed", area: now.area, nodeId: y.id, subject: subjectOf(now, nowIndex, ctx), rank: 0 };
2722
+ const aspects = propKeys.length + Number(styleChanged) + Number(visibilityChanged) + Number(flowChanged) + Number(nameChanged) + Number(typeChanged);
2723
+ if (aspects === 1 && nameChanged) {
2724
+ return { ...base, summary: `Renamed ${refOf(was, wasIndex, ctx, "definite")}${place(was, wasIndex, "in", ctx)}`, before: x.name ?? "", after: y.name ?? "" };
2725
+ }
2726
+ if (aspects === 1 && styleChanged) return { ...base, summary: `Changed the look of ${ref}${where}` };
2727
+ if (aspects === 1 && visibilityChanged) return { ...base, summary: `Changed when ${ref}${where} appears` };
2728
+ if (aspects === 1 && flowChanged) return { ...base, summary: `Changed how ${ref}${where} breaks across pages` };
2729
+ const names = unique([
2730
+ ...typeChanged ? ["type"] : [],
2731
+ ...propKeys.map((k) => propLabel(y.type, k, ctx)),
2732
+ ...styleChanged ? ["look"] : [],
2733
+ ...visibilityChanged ? ["visibility"] : [],
2734
+ ...flowChanged ? ["page breaks"] : [],
2735
+ ...nameChanged ? ["name"] : []
2736
+ ]);
2737
+ const summary = names.length > 3 ? `Changed ${names.length} settings of ${ref}${where}` : `Changed the ${list(names)} of ${ref}${where}`;
2738
+ const change = { ...base, summary };
2739
+ if (aspects === 1 && propKeys.length === 1) {
2740
+ const key = propKeys[0];
2741
+ const from = simpleValue(x.type, key, x.props?.[key], ctx);
2742
+ const to = simpleValue(y.type, key, y.props?.[key], ctx);
2743
+ if (from !== null && to !== null) {
2744
+ change.before = from;
2745
+ change.after = to;
2746
+ }
2747
+ }
2748
+ return change;
2749
+ }
2750
+ var STRUCTURE_NOUNS = { section: "section", row: "row of columns", column: "column" };
2751
+ function blockName(type, ctx) {
2752
+ if (STRUCTURE_NOUNS[type]) return humanize(STRUCTURE_NOUNS[type]);
2753
+ const name = ctx.registry?.get(type)?.name;
2754
+ return name && name.trim() ? name.trim() : humanize(type.replace(/[-_.]+/g, " "));
2755
+ }
2756
+ function subjectOf(entry, index, ctx) {
2757
+ const node = entry.node;
2758
+ if (node.name?.trim()) return node.name.trim();
2759
+ if (node.type === "section") {
2760
+ const label = sectionLabel(entry, index, ctx);
2761
+ return label.quoted ? `Section \u201C${label.text}\u201D` : humanize(label.text);
2762
+ }
2763
+ return blockName(node.type, ctx);
2764
+ }
2765
+ function refOf(entry, index, ctx, form) {
2766
+ const node = entry.node;
2767
+ if (node.type === "section") {
2768
+ const label = sectionLabel(entry, index, ctx);
2769
+ return label.quoted ? `the section \u201C${label.text}\u201D` : label.text;
2770
+ }
2771
+ if (node.name?.trim()) return `\u201C${node.name.trim()}\u201D`;
2772
+ const noun = lowerFirst(blockName(node.type, ctx));
2773
+ if (form === "definite") return `the ${noun}`;
2774
+ return `${/^[aeiou]/i.test(noun) ? "an" : "a"} ${noun}`;
2775
+ }
2776
+ function sectionLabel(entry, index, ctx) {
2777
+ const node = entry.node;
2778
+ if (node.name?.trim()) return { text: node.name.trim(), quoted: true };
2779
+ const heading = firstHeading(node, ctx);
2780
+ if (heading) return { text: heading, quoted: true };
2781
+ const siblings = index.children.get(entry.parentKey) ?? [];
2782
+ const sections = siblings.filter((id) => index.byId.get(id)?.node.type === "section");
2783
+ return { text: `section ${sections.indexOf(node.id) + 1}`, quoted: false };
2784
+ }
2785
+ function firstHeading(node, ctx) {
2786
+ for (const child of node.children ?? []) {
2787
+ if (child.type === "heading") {
2788
+ const text = child.props?.text;
2789
+ if (typeof text === "string" && text.trim()) return clip(readableText(text, ctx).trim(), 60);
2790
+ if (isRichTextDoc(text)) return clip(richTextToPlain(text, (e) => expressionLabel(e, ctx)).trim(), 60);
2791
+ }
2792
+ const nested = firstHeading(child, ctx);
2793
+ if (nested) return nested;
2794
+ }
2795
+ return null;
2796
+ }
2797
+ function place(entry, index, preposition, ctx) {
2798
+ let cursor = entry.parentId ? index.byId.get(entry.parentId) : void 0;
2799
+ while (cursor) {
2800
+ if (cursor.node.type === "section") {
2801
+ const label = sectionLabel(cursor, index, ctx);
2802
+ return ` ${preposition} ${label.quoted ? `\u201C${label.text}\u201D` : label.text}`;
2803
+ }
2804
+ cursor = cursor.parentId ? index.byId.get(cursor.parentId) : void 0;
2805
+ }
2806
+ if (entry.area === "header") return ` ${preposition} the header`;
2807
+ if (entry.area === "footer") return ` ${preposition} the footer`;
2808
+ return preposition === "within" ? " within the document" : "";
2809
+ }
2810
+ function propLabel(type, prop, ctx) {
2811
+ const def = ctx.registry?.get(type);
2812
+ const find = (fields) => {
2813
+ for (const field of fields) if (field.prop === prop || field.prop.startsWith(`${prop}.`)) return field.label;
2814
+ return null;
2815
+ };
2816
+ for (const section of def?.inspector ?? []) {
2817
+ const label = find(section.fields);
2818
+ if (label) return lowerFirst(label);
2819
+ }
2820
+ return lowerFirst(humanize(prop));
2821
+ }
2822
+ function simpleValue(type, prop, value, ctx) {
2823
+ if (value === void 0 || value === null) return "None";
2824
+ if (typeof value === "boolean") return onOff(value);
2825
+ if (typeof value === "number") return String(value);
2826
+ if (isRichTextDoc(value)) return oneLine(richTextToPlain(value, (e) => expressionLabel(e, ctx)));
2827
+ if (typeof value !== "string") return null;
2828
+ const kind = ctx.registry?.bindingsOf(type).find((b) => b.prop === prop)?.kind;
2829
+ if (kind === "value" || kind === "collection") return expressionLabel(value, ctx);
2830
+ if (kind === "condition" || kind === "list") return null;
2831
+ if (!kind && looksLikeExpression(value)) return expressionLabel(value, ctx);
2832
+ return oneLine(readableText(value, ctx));
2833
+ }
2834
+ function readableText(text, ctx) {
2835
+ return text.replace(/\{\{([^}]*)\}\}/g, (_, expr) => expressionLabel(expr.trim(), ctx));
2836
+ }
2837
+ function looksLikeExpression(value) {
2838
+ return /\|\s*[A-Za-z_]\w*/.test(value) || /^\s*[A-Za-z_]\w*\s*\(/.test(value);
2839
+ }
2840
+ function expressionLabel(expr, ctx) {
2841
+ if (!expr.trim()) return "nothing";
2842
+ const { label } = describeExpression(expr, ctx.scope);
2843
+ if (label === expr && /[^\w\s'’-]/.test(expr)) return "a calculated value";
2844
+ return label;
2845
+ }
2846
+ function colour(value) {
2847
+ if (!value) return "None";
2848
+ return value.startsWith("$") ? token(value) : value;
2849
+ }
2850
+ function token(value) {
2851
+ const last = value.slice(1).split(".").pop() ?? value;
2852
+ return `Theme ${lowerFirst(humanize(last))}`;
2853
+ }
2854
+ function onOff(value) {
2855
+ return value ? "On" : "Off";
2856
+ }
2857
+ function list(words) {
2858
+ if (words.length <= 1) return words[0] ?? "";
2859
+ return `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}`;
2860
+ }
2861
+ function lowerFirst(text) {
2862
+ if (text.length > 1 && text[1] === text[1].toUpperCase() && /[A-Z]/.test(text[1])) return text;
2863
+ return text.charAt(0).toLowerCase() + text.slice(1);
2864
+ }
2865
+ function oneLine(text) {
2866
+ return text.replace(/\s+/g, " ").trim();
2867
+ }
2868
+ function clip(text, limit) {
2869
+ return text.length <= limit ? text : `${text.slice(0, limit - 1).trimEnd()}\u2026`;
2870
+ }
2871
+ function unique(items) {
2872
+ return [...new Set(items)];
2873
+ }
2874
+ function keysOf(a, b) {
2875
+ const keys = /* @__PURE__ */ new Set();
2876
+ for (const o of [a, b]) if (o && typeof o === "object") for (const k of Object.keys(o)) keys.add(k);
2877
+ return [...keys];
2878
+ }
2879
+ function same(a, b) {
2880
+ if (a === b) return true;
2881
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
2882
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2883
+ if (Array.isArray(a)) {
2884
+ const other = b;
2885
+ return a.length === other.length && a.every((v, i) => same(v, other[i]));
2886
+ }
2887
+ const x = a;
2888
+ const y = b;
2889
+ return keysOf(x, y).every((k) => same(x[k], y[k]));
2890
+ }
2891
+ function longestIncreasing(values) {
2892
+ const tails = [];
2893
+ const previous = new Array(values.length).fill(-1);
2894
+ for (let i = 0; i < values.length; i++) {
2895
+ let lo = 0;
2896
+ let hi = tails.length;
2897
+ while (lo < hi) {
2898
+ const mid = lo + hi >> 1;
2899
+ if (values[tails[mid]] < values[i]) lo = mid + 1;
2900
+ else hi = mid;
2901
+ }
2902
+ if (lo > 0) previous[i] = tails[lo - 1];
2903
+ tails[lo] = i;
2904
+ }
2905
+ const keep = /* @__PURE__ */ new Set();
2906
+ let cursor = tails.length ? tails[tails.length - 1] : -1;
2907
+ while (cursor >= 0) {
2908
+ keep.add(cursor);
2909
+ cursor = previous[cursor];
2910
+ }
2911
+ return keep;
2912
+ }
2913
+
2235
2914
  // src/resolve/resolve.ts
2236
2915
  var display = toDisplayString;
2237
2916
  function resolveTemplate(opts) {
@@ -2495,8 +3174,8 @@ function resolveNode(ctx, node, scope, schemaScope, keyPrefix, _parentRole) {
2495
3174
  for (const spec of bindingSpecsFor(def)) {
2496
3175
  if (spec.perItem) continue;
2497
3176
  if (spec.items) {
2498
- const list = getPath(props, spec.prop);
2499
- if (Array.isArray(list)) list.forEach((item, i) => item && typeof item === "object" && spec.items.forEach((s) => !s.perItem && applySpec(s, item, `${spec.prop}.${i}.`)));
3177
+ const list2 = getPath(props, spec.prop);
3178
+ if (Array.isArray(list2)) list2.forEach((item, i) => item && typeof item === "object" && spec.items.forEach((s) => !s.perItem && applySpec(s, item, `${spec.prop}.${i}.`)));
2500
3179
  continue;
2501
3180
  }
2502
3181
  applySpec(spec, props, "");
@@ -2684,7 +3363,7 @@ function computePageGeometry(page) {
2684
3363
  const portrait = page.orientation !== "landscape";
2685
3364
  const width = mmToPx(portrait ? size.width : size.height);
2686
3365
  const height = mmToPx(portrait ? size.height : size.width);
2687
- const margins = {
3366
+ const margins2 = {
2688
3367
  top: mmToPx(page.margins.top),
2689
3368
  right: mmToPx(page.margins.right),
2690
3369
  bottom: mmToPx(page.margins.bottom),
@@ -2692,18 +3371,18 @@ function computePageGeometry(page) {
2692
3371
  };
2693
3372
  const headerHeight = mmToPx(Math.max(0, page.header.height));
2694
3373
  const footerHeight = mmToPx(Math.max(0, page.footer.height));
2695
- const contentTop = margins.top + headerHeight;
2696
- const footerTop = height - margins.bottom - footerHeight;
3374
+ const contentTop = margins2.top + headerHeight;
3375
+ const footerTop = height - margins2.bottom - footerHeight;
2697
3376
  return {
2698
3377
  width,
2699
3378
  height,
2700
- margins,
3379
+ margins: margins2,
2701
3380
  headerHeight,
2702
3381
  footerHeight,
2703
- contentWidth: Math.max(0, width - margins.left - margins.right),
3382
+ contentWidth: Math.max(0, width - margins2.left - margins2.right),
2704
3383
  contentHeight: Math.max(0, footerTop - contentTop),
2705
3384
  contentTop,
2706
- headerTop: margins.top,
3385
+ headerTop: margins2.top,
2707
3386
  footerTop
2708
3387
  };
2709
3388
  }
@@ -2713,16 +3392,16 @@ function g(ctx, node) {
2713
3392
  return ctx.geom.nodes[node.key] ?? { top: 0, height: 0, left: 0, width: 0 };
2714
3393
  }
2715
3394
  var EPS = 0.01;
2716
- function bestBreak(ctx, list, limit, minY) {
3395
+ function bestBreak(ctx, list2, limit, minY) {
2717
3396
  const visible = (from, step) => {
2718
- for (let j = from; j >= 0 && j < list.length; j += step) {
2719
- const s = list[j];
3397
+ for (let j = from; j >= 0 && j < list2.length; j += step) {
3398
+ const s = list2[j];
2720
3399
  if (g(ctx, s).height > EPS) return s;
2721
3400
  }
2722
3401
  return void 0;
2723
3402
  };
2724
- for (let i = list.length - 1; i >= 0; i--) {
2725
- const child = list[i];
3403
+ for (let i = list2.length - 1; i >= 0; i--) {
3404
+ const child = list2[i];
2726
3405
  const cg = g(ctx, child);
2727
3406
  const top = cg.top;
2728
3407
  const bottom = cg.top + cg.height;
@@ -3044,10 +3723,10 @@ function validateTemplate(opts) {
3044
3723
  }
3045
3724
  if (kind === "collection" && r.issues.length === 0 && r.type !== "array" && r.type !== "any" && r.type !== "unknown") out.push(issue("error", "missing-collection", `"${expr}" is a ${r.type}, not a list`, { ...where, prop }));
3046
3725
  if (expected && r.issues.length === 0 && r.type !== "unknown" && r.type !== "any" && r.type !== "null") {
3047
- const list = Array.isArray(expected) ? expected : [expected];
3726
+ const list2 = Array.isArray(expected) ? expected : [expected];
3048
3727
  const numeric = ["number", "currency", "percent"];
3049
- const compatible = list.includes(r.type) || list.some((t) => numeric.includes(t)) && numeric.includes(r.type) || list.includes("string") && r.type !== "array" && r.type !== "object";
3050
- if (!compatible) out.push(issue("warning", "type-mismatch", `"${expr}" is a ${r.type}; expected ${list.join(" or ")}`, { ...where, prop }));
3728
+ const compatible = list2.includes(r.type) || list2.some((t) => numeric.includes(t)) && numeric.includes(r.type) || list2.includes("string") && r.type !== "array" && r.type !== "object";
3729
+ if (!compatible) out.push(issue("warning", "type-mismatch", `"${expr}" is a ${r.type}; expected ${list2.join(" or ")}`, { ...where, prop }));
3051
3730
  }
3052
3731
  issues.push(...out);
3053
3732
  return out;
@@ -3106,8 +3785,8 @@ function validateTemplate(opts) {
3106
3785
  continue;
3107
3786
  }
3108
3787
  if (spec.items) {
3109
- const list = getPath2(props, spec.prop);
3110
- if (Array.isArray(list)) list.forEach((item, i) => item && typeof item === "object" && spec.items.forEach((sub) => runSpec(sub, item, `${spec.prop}.${i}.`, sub.perItem ? itemScope : scope)));
3788
+ const list2 = getPath2(props, spec.prop);
3789
+ if (Array.isArray(list2)) list2.forEach((item, i) => item && typeof item === "object" && spec.items.forEach((sub) => runSpec(sub, item, `${spec.prop}.${i}.`, sub.perItem ? itemScope : scope)));
3111
3790
  continue;
3112
3791
  }
3113
3792
  runSpec(spec, props, "", scope);
@@ -3401,7 +4080,7 @@ function readRegion(template, region) {
3401
4080
  return template.footer.firstPage ?? [];
3402
4081
  }
3403
4082
  }
3404
- function indexTemplate(template) {
4083
+ function indexTemplate2(template) {
3405
4084
  const index = /* @__PURE__ */ new Map();
3406
4085
  const visit = (nodes, parent, region, path) => {
3407
4086
  nodes.forEach((node, i) => {
@@ -3414,21 +4093,21 @@ function indexTemplate(template) {
3414
4093
  return index;
3415
4094
  }
3416
4095
  function findNode(template, id) {
3417
- return indexTemplate(template).get(id);
4096
+ return indexTemplate2(template).get(id);
3418
4097
  }
3419
4098
  function listAt(template, location) {
3420
- let list = getRegion(template, location.region);
4099
+ let list2 = getRegion(template, location.region);
3421
4100
  for (let i = 0; i < location.path.length - 1; i++) {
3422
- const n = list[location.path[i]];
4101
+ const n = list2[location.path[i]];
3423
4102
  if (!n) throw new Error("Invalid node path");
3424
4103
  n.children ??= [];
3425
- list = n.children;
4104
+ list2 = n.children;
3426
4105
  }
3427
- return { list, index: location.path[location.path.length - 1] ?? 0 };
4106
+ return { list: list2, index: location.path[location.path.length - 1] ?? 0 };
3428
4107
  }
3429
4108
  function nodeAt(template, location) {
3430
- const { list, index } = listAt(template, location);
3431
- return list[index];
4109
+ const { list: list2, index } = listAt(template, location);
4110
+ return list2[index];
3432
4111
  }
3433
4112
  function ancestors(index, id) {
3434
4113
  const chain = [];
@@ -3496,28 +4175,28 @@ function applyCommand(draft, cmd) {
3496
4175
  const t = draft;
3497
4176
  switch (cmd.type) {
3498
4177
  case "insertNode": {
3499
- const { list, index } = listAt(t, cmd.at);
3500
- list.splice(Math.min(index, list.length), 0, cmd.node);
4178
+ const { list: list2, index } = listAt(t, cmd.at);
4179
+ list2.splice(Math.min(index, list2.length), 0, cmd.node);
3501
4180
  return;
3502
4181
  }
3503
4182
  case "insertNodes": {
3504
- const { list, index } = listAt(t, cmd.at);
3505
- list.splice(Math.min(index, list.length), 0, ...cmd.nodes);
4183
+ const { list: list2, index } = listAt(t, cmd.at);
4184
+ list2.splice(Math.min(index, list2.length), 0, ...cmd.nodes);
3506
4185
  return;
3507
4186
  }
3508
4187
  case "removeNodes": {
3509
- const index = indexTemplate(t);
4188
+ const index = indexTemplate2(t);
3510
4189
  const entries = cmd.ids.map((id) => index.get(id)).filter((e) => !!e);
3511
4190
  entries.sort((a, b) => b.depth - a.depth || b.index - a.index);
3512
4191
  for (const e of entries) {
3513
- const list = e.parent ? e.parent.children ??= [] : getRegion(t, e.region);
3514
- const i = list.findIndex((n) => n.id === e.node.id);
3515
- if (i >= 0) list.splice(i, 1);
4192
+ const list2 = e.parent ? e.parent.children ??= [] : getRegion(t, e.region);
4193
+ const i = list2.findIndex((n) => n.id === e.node.id);
4194
+ if (i >= 0) list2.splice(i, 1);
3516
4195
  }
3517
4196
  return;
3518
4197
  }
3519
4198
  case "moveNode": {
3520
- const index = indexTemplate(t);
4199
+ const index = indexTemplate2(t);
3521
4200
  const entry = index.get(cmd.id);
3522
4201
  if (!entry) throw new CommandError(`Node ${cmd.id} not found`);
3523
4202
  const target = listAt(t, cmd.to);
@@ -3542,13 +4221,13 @@ function applyCommand(draft, cmd) {
3542
4221
  return;
3543
4222
  }
3544
4223
  case "duplicateNode": {
3545
- const index = indexTemplate(t);
4224
+ const index = indexTemplate2(t);
3546
4225
  const entry = index.get(cmd.id);
3547
4226
  if (!entry) throw new CommandError(`Node ${cmd.id} not found`);
3548
- const list = entry.parent ? entry.parent.children ??= [] : getRegion(t, entry.region);
4227
+ const list2 = entry.parent ? entry.parent.children ??= [] : getRegion(t, entry.region);
3549
4228
  const copy = cloneWithNewIds(isDraft(entry.node) ? current(entry.node) : entry.node);
3550
4229
  if (cmd.newId) copy.id = cmd.newId;
3551
- list.splice(entry.index + 1, 0, copy);
4230
+ list2.splice(entry.index + 1, 0, copy);
3552
4231
  return;
3553
4232
  }
3554
4233
  case "setProps": {
@@ -3621,27 +4300,27 @@ function applyCommand(draft, cmd) {
3621
4300
  return;
3622
4301
  }
3623
4302
  case "wrapNodes": {
3624
- const index = indexTemplate(t);
4303
+ const index = indexTemplate2(t);
3625
4304
  const entries = cmd.ids.map((id) => index.get(id)).filter((e) => !!e);
3626
4305
  if (entries.length === 0) return;
3627
4306
  const first = entries[0];
3628
- const list = first.parent ? first.parent.children ??= [] : getRegion(t, first.region);
4307
+ const list2 = first.parent ? first.parent.children ??= [] : getRegion(t, first.region);
3629
4308
  const nodes = entries.map((e) => e.node);
3630
- const startIndex = list.indexOf(first.node);
4309
+ const startIndex = list2.indexOf(first.node);
3631
4310
  for (const n of nodes) {
3632
- const i = list.indexOf(n);
3633
- if (i >= 0) list.splice(i, 1);
4311
+ const i = list2.indexOf(n);
4312
+ if (i >= 0) list2.splice(i, 1);
3634
4313
  }
3635
4314
  const wrapper = { ...cmd.wrapper, children: [...cmd.wrapper.children ?? [], ...nodes.map((n) => isDraft(n) ? current(n) : n)] };
3636
- list.splice(Math.min(startIndex, list.length), 0, wrapper);
4315
+ list2.splice(Math.min(startIndex, list2.length), 0, wrapper);
3637
4316
  return;
3638
4317
  }
3639
4318
  case "replaceNode": {
3640
- const index = indexTemplate(t);
4319
+ const index = indexTemplate2(t);
3641
4320
  const entry = index.get(cmd.id);
3642
4321
  if (!entry) throw new CommandError(`Node ${cmd.id} not found`);
3643
- const list = entry.parent ? entry.parent.children ??= [] : getRegion(t, entry.region);
3644
- list[entry.index] = cmd.node;
4322
+ const list2 = entry.parent ? entry.parent.children ??= [] : getRegion(t, entry.region);
4323
+ list2[entry.index] = cmd.node;
3645
4324
  return;
3646
4325
  }
3647
4326
  case "setPage": {
@@ -3701,7 +4380,7 @@ function applyCommand(draft, cmd) {
3701
4380
  }
3702
4381
  }
3703
4382
  function requireNode(t, id) {
3704
- const e = indexTemplate(t).get(id);
4383
+ const e = indexTemplate2(t).get(id);
3705
4384
  if (!e) throw new CommandError(`Node ${id} not found`);
3706
4385
  return e.node;
3707
4386
  }
@@ -3774,7 +4453,7 @@ var EditorStore = class {
3774
4453
  /** Cached id → node index for the current document version. */
3775
4454
  index() {
3776
4455
  if (!this.indexCache || this.indexCache.version !== this.state.version) {
3777
- this.indexCache = { version: this.state.version, index: indexTemplate(this.state.template) };
4456
+ this.indexCache = { version: this.state.version, index: indexTemplate2(this.state.template) };
3778
4457
  }
3779
4458
  return this.indexCache.index;
3780
4459
  }
@@ -3968,7 +4647,7 @@ var EditorStore = class {
3968
4647
  }
3969
4648
  };
3970
4649
  function findIn(template, id) {
3971
- return indexTemplate(template).get(id)?.node;
4650
+ return indexTemplate2(template).get(id)?.node;
3972
4651
  }
3973
4652
  function sameIds(a, b) {
3974
4653
  return a.length === b.length && a.every((x, i) => x === b[i]);
@@ -4061,9 +4740,9 @@ function markWrap(children, marks, opts) {
4061
4740
  if (marks.color) out = h("span", { style: { color: resolveValue(marks.color, opts.theme, "inherit") } }, out);
4062
4741
  if (marks.link) {
4063
4742
  const href = marks.link;
4064
- const safe = isSafeUrl(href);
4743
+ const safe2 = isSafeUrl(href);
4065
4744
  const linkColor = resolveValue(opts.styles.link.color, opts.theme, opts.theme.colors.secondary);
4066
- out = h("a", { class: "bp-link", href: safe ? href : void 0, rel: "noopener", style: { color: linkColor, "text-decoration": opts.styles.link.underline === false ? "none" : "underline" } }, out);
4745
+ out = h("a", { class: "bp-link", href: safe2 ? href : void 0, rel: "noopener", style: { color: linkColor, "text-decoration": opts.styles.link.underline === false ? "none" : "underline" } }, out);
4067
4746
  }
4068
4747
  return out;
4069
4748
  }
@@ -4223,6 +4902,8 @@ export {
4223
4902
  bestBreak,
4224
4903
  bindingSpecsFor,
4225
4904
  blockStyleSchema,
4905
+ calcPaths,
4906
+ calcToAst,
4226
4907
  checkExpressionSyntax,
4227
4908
  checkOutput,
4228
4909
  classNames,
@@ -4246,8 +4927,10 @@ export {
4246
4927
  dataSourceSchemas,
4247
4928
  defaultDocumentStyles,
4248
4929
  defaultPageSettings,
4930
+ defaultPipeFor,
4249
4931
  defineBlock,
4250
4932
  describeExpression,
4933
+ diffTemplates,
4251
4934
  documentStylesSchema,
4252
4935
  escapeHtml,
4253
4936
  evaluate,
@@ -4263,19 +4946,23 @@ export {
4263
4946
  formatDatePattern,
4264
4947
  formatNumber,
4265
4948
  formatPercent,
4949
+ fromExpression,
4266
4950
  getField,
4267
4951
  getRegion,
4268
4952
  h,
4953
+ hasPipe,
4269
4954
  humaniseLabel,
4270
4955
  humanize,
4271
4956
  humanizeKey,
4272
- indexTemplate,
4957
+ indexTemplate2 as indexTemplate,
4273
4958
  inlineFromTemplate,
4274
4959
  inlineToPlain,
4275
4960
  interpolate,
4961
+ isCalculation,
4276
4962
  isDarkColor,
4277
4963
  isDescendant,
4278
4964
  isEmptyValue,
4965
+ isPlainPath,
4279
4966
  isRichTextDoc,
4280
4967
  isSafeUrl,
4281
4968
  isTokenRef,
@@ -4322,12 +5009,14 @@ export {
4322
5009
  splitTemplate,
4323
5010
  styleString,
4324
5011
  styleToCss,
5012
+ summariseDiff,
4325
5013
  synthesizeSampleData,
4326
5014
  synthesizeValue,
4327
5015
  templateSchema,
4328
5016
  textStyleLabel,
4329
5017
  toDate,
4330
5018
  toDisplayString,
5019
+ toExpression,
4331
5020
  toHtml,
4332
5021
  toNumber,
4333
5022
  tokenChip,
@@ -4338,6 +5027,7 @@ export {
4338
5027
  usedBlockTypes,
4339
5028
  validateTemplate,
4340
5029
  walkTemplate,
4341
- walkVNodes
5030
+ walkVNodes,
5031
+ withDefaultPipe
4342
5032
  };
4343
5033
  //# sourceMappingURL=index.js.map