tsquare 0.12.4 → 0.13.1

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.
@@ -706,6 +706,7 @@ function markRoot(root, color) {
706
706
  const kids = Children.toArray(node.props.children);
707
707
  return cloneElement(node, {}, ...kids, /* @__PURE__ */ jsx(Marker, { color }, "__marker"));
708
708
  }
709
+ var MARKS_ITSELF = /* @__PURE__ */ new Set(["Screen", "Modal", "Drawer", "Toast"]);
709
710
  function withMarkers(reg) {
710
711
  return Object.fromEntries(
711
712
  Object.entries(reg).map(([name, C]) => [
@@ -713,7 +714,7 @@ function withMarkers(reg) {
713
714
  (props) => {
714
715
  const root = C(props);
715
716
  const color = props.element?.props?.__mark;
716
- return color && name !== "Screen" ? markRoot(root, color) : root;
717
+ return color && !MARKS_ITSELF.has(name) ? markRoot(root, color) : root;
717
718
  }
718
719
  ])
719
720
  );
@@ -887,19 +888,22 @@ function Screen({ element, children }) {
887
888
  s.chrome && s.device === "desktop" ? /* @__PURE__ */ jsx(BrowserBar, {}) : null,
888
889
  /* @__PURE__ */ jsxs(Box, { style: { position: "relative", flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: [
889
890
  top,
890
- /* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
891
- Box,
892
- {
893
- style: {
894
- flexGrow: 1,
895
- flexShrink: 0,
896
- flexDirection: "column",
897
- padding: s.padding,
898
- gap: p.gap ?? 14
899
- },
900
- children: withDir(body, "column")
901
- }
902
- ) }),
891
+ /* @__PURE__ */ jsxs(Box, { style: { flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: [
892
+ /* @__PURE__ */ jsx(
893
+ Box,
894
+ {
895
+ style: {
896
+ flexGrow: 1,
897
+ flexShrink: 0,
898
+ flexDirection: "column",
899
+ padding: s.padding,
900
+ gap: p.gap ?? 14
901
+ },
902
+ children: withDir(body, "column")
903
+ }
904
+ ),
905
+ p.__markBody ? /* @__PURE__ */ jsx(Marker, { color: p.__markBody }) : null
906
+ ] }),
903
907
  bottom,
904
908
  overlays.map((o2) => isValidElement(o2) ? cloneElement(o2, { aboveTabBar: bottom.length > 0 }) : o2),
905
909
  p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
@@ -1754,7 +1758,8 @@ function Modal({ element, children }) {
1754
1758
  },
1755
1759
  children: [
1756
1760
  p.title ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 18, fontWeight: 600 }, children: p.title }) : null,
1757
- withDir(children, "column")
1761
+ withDir(children, "column"),
1762
+ p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
1758
1763
  ]
1759
1764
  }
1760
1765
  ) });
@@ -1789,7 +1794,8 @@ function Drawer({ element, children }) {
1789
1794
  /* @__PURE__ */ jsx(Box, { style: { fontSize: 18, fontWeight: 600 }, children: p.title }),
1790
1795
  /* @__PURE__ */ jsx(IconGlyph, { name: "x", size: 22, color: theme.muted })
1791
1796
  ] }) : null,
1792
- withDir(children, "column")
1797
+ withDir(children, "column"),
1798
+ p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
1793
1799
  ]
1794
1800
  }
1795
1801
  ) });
@@ -1802,7 +1808,8 @@ function Toast({ element, aboveTabBar }) {
1802
1808
  return /* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: 16, right: 16, justifyContent: side, ...top ? { top: 16 } : { bottom: aboveTabBar ? 92 : 24 } }, children: /* @__PURE__ */ jsxs(Box, { style: { alignItems: "center", gap: 12, padding: "12px 16px", borderRadius: 12, backgroundColor: theme.ink, color: theme.onPrimary, fontSize: 14, boxShadow: "0 6px 20px rgba(0,0,0,0.22)", maxWidth: 420 }, children: [
1803
1809
  p.icon ? /* @__PURE__ */ jsx(IconGlyph, { name: p.icon, size: 18, color: theme.onPrimary }) : null,
1804
1810
  /* @__PURE__ */ jsx(Box, { style: { flexShrink: 1 }, children: p.text ?? "" }),
1805
- p.action ? /* @__PURE__ */ jsx(Box, { style: { fontWeight: 700, marginLeft: 8, flexShrink: 0 }, children: p.action }) : null
1811
+ p.action ? /* @__PURE__ */ jsx(Box, { style: { fontWeight: 700, marginLeft: 8, flexShrink: 0 }, children: p.action }) : null,
1812
+ p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
1806
1813
  ] }) });
1807
1814
  }
1808
1815
  function Flow() {
@@ -2375,22 +2382,23 @@ function findAnchors(spec) {
2375
2382
  return [...out, ...tips];
2376
2383
  }
2377
2384
  var MARK = (i) => `#fe${(i >> 8).toString(16).padStart(2, "0")}${(i & 255).toString(16).padStart(2, "0")}`;
2378
- function tagForMeasuring(spec, anchors) {
2385
+ function tagForMeasuring(spec, anchors, bodies = []) {
2379
2386
  const tagged = structuredClone(spec);
2380
2387
  const colors = /* @__PURE__ */ new Map();
2381
2388
  let n = 1;
2382
- const tag = (id, field) => {
2383
- const key = field ? `${id}#field` : id;
2389
+ const tag = (id, part) => {
2390
+ const key = part ? `${id}#${part}` : id;
2384
2391
  if (colors.has(key)) return;
2385
2392
  const color = MARK(n++);
2386
2393
  colors.set(key, color);
2387
2394
  const el = tagged.elements[id];
2388
- el.props = { ...el.props ?? {}, [field ? "__markField" : "__mark"]: color };
2395
+ el.props = { ...el.props ?? {}, [part === "field" ? "__markField" : part === "body" ? "__markBody" : "__mark"]: color };
2389
2396
  };
2390
2397
  for (const a of anchors) {
2391
- tag(a.screen, false);
2392
- tag(a.id, a.field);
2398
+ tag(a.screen, "");
2399
+ tag(a.id, a.field ? "field" : "");
2393
2400
  }
2401
+ for (const id of bodies) tag(id, "body");
2394
2402
  return { tagged, colors };
2395
2403
  }
2396
2404
  function readMarkers(svg, colors) {
@@ -2414,6 +2422,7 @@ function place(anchor, size, screen, opts) {
2414
2422
  return { x: x - screen.x, y: y - screen.y, bottom: screen.y + screen.h - (anchor.y - opts.gap), side };
2415
2423
  }
2416
2424
  var ROW = 36;
2425
+ var listHeight = (rows) => rows * (ROW - 4) + 2 * 4 + 2 * 1.5;
2417
2426
  function ListCard({ items, width, selected, c }) {
2418
2427
  return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", width, padding: 4, backgroundColor: theme.paper, border: `1.5px solid ${theme.lineStrong}`, borderRadius: 8, boxShadow: "0 8px 24px rgba(0,0,0,0.18)" }, children: items.map((item, i) => {
2419
2428
  const on = selected != null && item.label === selected;
@@ -2449,35 +2458,57 @@ function tooltipSize(text) {
2449
2458
  const lines = Math.max(1, Math.ceil(text.length * 7.4 / 216));
2450
2459
  return { w: width, h: lines * 18 + 12 + 6 };
2451
2460
  }
2461
+ function placeOverlay(spec, a, boxes) {
2462
+ const screen = boxes[a.screen];
2463
+ const anchor = boxes[a.field ? `${a.id}#field` : a.id];
2464
+ if (!screen || !anchor) return null;
2465
+ const p = propsOf(spec.elements, a.id);
2466
+ if (a.kind === "options") {
2467
+ const options = p.options?.length ? p.options : [p.value ?? "Option 1", "Option 2", "Option 3"];
2468
+ const size2 = { w: anchor.w, h: options.length * ROW + 8 };
2469
+ return { screen, anchor, size: size2, drawnH: listHeight(options.length), pos: place(anchor, size2, screen, { prefer: "below", align: "start", gap: 4 }), options };
2470
+ }
2471
+ if (a.kind === "menu") {
2472
+ const items = p.menu.map((i) => typeof i === "string" ? { label: i } : i);
2473
+ const width = a.end ? 200 : Math.max(180, anchor.w);
2474
+ const inRightHalf = anchor.x + anchor.w / 2 > screen.x + screen.w / 2;
2475
+ const size2 = { w: width, h: items.length * ROW + 8 };
2476
+ return { screen, anchor, size: size2, drawnH: listHeight(items.length), pos: place(anchor, size2, screen, { prefer: "below", align: a.end || inRightHalf ? "end" : "start", gap: a.end ? -6 : 4 }), items };
2477
+ }
2478
+ if (a.kind === "calendar") {
2479
+ const date = parseDate(p.value);
2480
+ const { start, days } = monthGrid(date?.month);
2481
+ const weeks = Math.ceil((start + days) / 7);
2482
+ const size2 = { w: 296, h: 92 + weeks * 38 };
2483
+ return { screen, anchor, size: size2, pos: place(anchor, size2, screen, { prefer: "below", align: "start", gap: 4 }), date };
2484
+ }
2485
+ const size = tooltipSize(p.tooltip);
2486
+ return { screen, anchor, size, pos: place(anchor, size, screen, { prefer: "above", align: "center", gap: 4 }) };
2487
+ }
2488
+ function overlayBoxes(spec, anchors, boxes) {
2489
+ return anchors.flatMap((a) => {
2490
+ const o2 = placeOverlay(spec, a, boxes);
2491
+ if (!o2) return [];
2492
+ const h = "drawnH" in o2 && o2.drawnH != null ? o2.drawnH : o2.size.h;
2493
+ const top = o2.screen.y + o2.pos.y + (o2.pos.side === "above" ? o2.size.h - h : 0);
2494
+ return [{ id: a.id, box: { x: o2.screen.x + o2.pos.x, y: top, w: o2.size.w, h } }];
2495
+ });
2496
+ }
2452
2497
  function overlayLayer(spec, anchors, boxes, c, Calendar2) {
2453
2498
  const byScreen = /* @__PURE__ */ new Map();
2454
2499
  for (const a of anchors) {
2455
- const screen = boxes[a.screen];
2456
- const anchor = boxes[a.field ? `${a.id}#field` : a.id];
2457
- if (!screen || !anchor) continue;
2500
+ const o2 = placeOverlay(spec, a, boxes);
2501
+ if (!o2) continue;
2502
+ const { screen, anchor, size, pos } = o2;
2458
2503
  const p = propsOf(spec.elements, a.id);
2459
2504
  let node = null;
2460
- let pos = null;
2461
2505
  if (a.kind === "options") {
2462
- const options = p.options?.length ? p.options : [p.value ?? "Option 1", "Option 2", "Option 3"];
2463
- const size = { w: anchor.w, h: options.length * ROW + 8 };
2464
- pos = place(anchor, size, screen, { prefer: "below", align: "start", gap: 4 });
2465
- node = /* @__PURE__ */ jsx2(ListCard, { items: options.map((label) => ({ label })), width: size.w, selected: p.value, c });
2506
+ node = /* @__PURE__ */ jsx2(ListCard, { items: o2.options.map((label) => ({ label })), width: size.w, selected: p.value, c });
2466
2507
  } else if (a.kind === "menu") {
2467
- const items = p.menu.map((i) => typeof i === "string" ? { label: i } : i);
2468
- const width = a.end ? 200 : Math.max(180, anchor.w);
2469
- const inRightHalf = anchor.x + anchor.w / 2 > screen.x + screen.w / 2;
2470
- pos = place(anchor, { w: width, h: items.length * ROW + 8 }, screen, { prefer: "below", align: a.end || inRightHalf ? "end" : "start", gap: a.end ? -6 : 4 });
2471
- node = /* @__PURE__ */ jsx2(ListCard, { items, width, c });
2508
+ node = /* @__PURE__ */ jsx2(ListCard, { items: o2.items, width: size.w, c });
2472
2509
  } else if (a.kind === "calendar") {
2473
- const date = parseDate(p.value);
2474
- const { start, days } = monthGrid(date?.month);
2475
- const weeks = Math.ceil((start + days) / 7);
2476
- pos = place(anchor, { w: 296, h: 92 + weeks * 38 }, screen, { prefer: "below", align: "start", gap: 4 });
2477
- node = /* @__PURE__ */ jsx2("div", { style: { display: "flex", boxShadow: "0 8px 24px rgba(0,0,0,0.18)", borderRadius: 12 }, children: Calendar2({ element: { type: "Calendar", props: { month: date?.month ?? "Month", selected: date?.day } }, colors: c }) });
2510
+ node = /* @__PURE__ */ jsx2("div", { style: { display: "flex", boxShadow: "0 8px 24px rgba(0,0,0,0.18)", borderRadius: 12 }, children: Calendar2({ element: { type: "Calendar", props: { month: o2.date?.month ?? "Month", selected: o2.date?.day } }, colors: c }) });
2478
2511
  } else {
2479
- const size = tooltipSize(p.tooltip);
2480
- pos = place(anchor, size, screen, { prefer: "above", align: "center", gap: 4 });
2481
2512
  node = /* @__PURE__ */ jsx2(Tooltip, { text: p.tooltip, side: pos.side, width: size.w, arrowX: anchor.x + anchor.w / 2 - (screen.x + pos.x) });
2482
2513
  }
2483
2514
  const list = byScreen.get(a.screen) ?? [];
@@ -2619,9 +2650,7 @@ function flowColor(color, palette) {
2619
2650
  const s = String(color);
2620
2651
  return ACCENTS[s] ?? s;
2621
2652
  }
2622
- function flowLayer(spec, flows, screens, gap, palette, size) {
2623
- const shapes = [];
2624
- const labels = [];
2653
+ function layoutFlows(spec, flows, screens, gap, palette) {
2625
2654
  const placed = [];
2626
2655
  const drawn = [];
2627
2656
  const tips = flows.flatMap(({ ends: ends2 }) => {
@@ -2640,20 +2669,16 @@ function flowLayer(spec, flows, screens, gap, palette, size) {
2640
2669
  return r;
2641
2670
  });
2642
2671
  const ends = routes.flatMap((r) => [r.points[0], r.points[r.points.length - 1]]);
2643
- flows.forEach(({ key }, i) => {
2672
+ return flows.map(({ key }, i) => {
2644
2673
  const r = routes[i];
2645
2674
  const p = spec.elements[key].props ?? {};
2646
- const color = flowColor(p.color, palette);
2647
2675
  const start = p.start ?? "none", end = p.end ?? "arrow";
2648
2676
  const pts = [...r.points];
2649
2677
  pts[0] = add(pts[0], DIR[r.from], INSET[start]);
2650
2678
  pts[pts.length - 1] = add(pts[pts.length - 1], DIR[r.to], INSET[end]);
2651
- const { d, at } = linePath(r, pts, p.line ?? "rounded");
2652
- shapes.push(
2653
- /* @__PURE__ */ jsx3("path", { d, fill: "none", stroke: color, strokeWidth: 2, strokeLinejoin: "round", strokeLinecap: "round", ...p.dashed ? { strokeDasharray: "7 6" } : {} }, `${key}-line`),
2654
- marker(start, r.points[0], DIR[r.from], color, `${key}-start`),
2655
- marker(end, r.points[r.points.length - 1], DIR[r.to], color, `${key}-end`)
2656
- );
2679
+ const line = p.line ?? "rounded";
2680
+ const { d, at } = linePath(r, pts, line);
2681
+ const out = { key, route: r, points: pts, d, color: flowColor(p.color, palette), start, end, line, dashed: !!p.dashed };
2657
2682
  if (p.label) {
2658
2683
  const w = flowLabelWidth(String(p.label));
2659
2684
  const covers = (c) => placed.filter((q) => c.x - w / 2 < q.x + q.w && q.x < c.x + w / 2 && c.y - 14 < q.y + q.h && q.y < c.y + 14).length + ends.filter((q) => Math.abs(q.x - c.x) < w / 2 + 12 && Math.abs(q.y - c.y) < 24).length;
@@ -2663,13 +2688,58 @@ function flowLayer(spec, flows, screens, gap, palette, size) {
2663
2688
  const c = at(t), n = covers(c);
2664
2689
  if (n < best) [mid, best] = [c, n];
2665
2690
  }
2666
- const top = mid.y - 12;
2667
- placed.push({ x: mid.x - w / 2, y: top, w, h: 24 });
2691
+ const box = { x: mid.x - w / 2, y: mid.y - 12, w, h: 24 };
2692
+ placed.push(box);
2693
+ out.label = { text: String(p.label), ...box };
2694
+ }
2695
+ return out;
2696
+ });
2697
+ }
2698
+ function flowPolyline(f) {
2699
+ const r = f.route, pts = f.points;
2700
+ const s0 = pts[0], e0 = pts[pts.length - 1];
2701
+ let out;
2702
+ if (r.lane == null && f.line === "straight") out = [s0, e0];
2703
+ else if (r.lane == null && f.line === "curved") {
2704
+ const { at } = linePath(r, pts, f.line);
2705
+ out = Array.from({ length: 25 }, (_, i) => at(i / 24));
2706
+ } else {
2707
+ const radius = f.line === "curved" ? 40 : f.line === "rounded" ? 14 : 0;
2708
+ const q = simplify(pts);
2709
+ out = [q[0]];
2710
+ for (let i = 1; i < q.length; i++) {
2711
+ const p = q[i];
2712
+ if (radius > 0 && i < q.length - 1) {
2713
+ const prev = q[i - 1], next = q[i + 1];
2714
+ const rr = Math.min(radius, Math.hypot(p.x - prev.x, p.y - prev.y) / 2, Math.hypot(next.x - p.x, next.y - p.y) / 2);
2715
+ const into = { x: Math.sign(p.x - prev.x), y: Math.sign(p.y - prev.y) }, outd = { x: Math.sign(next.x - p.x), y: Math.sign(next.y - p.y) };
2716
+ const a = { x: p.x - into.x * rr, y: p.y - into.y * rr }, b = { x: p.x + outd.x * rr, y: p.y + outd.y * rr };
2717
+ for (let k = 0; k <= 6; k++) {
2718
+ const t = k / 6;
2719
+ out.push({ x: (1 - t) ** 2 * a.x + 2 * (1 - t) * t * p.x + t ** 2 * b.x, y: (1 - t) ** 2 * a.y + 2 * (1 - t) * t * p.y + t ** 2 * b.y });
2720
+ }
2721
+ } else out.push(p);
2722
+ }
2723
+ }
2724
+ const first = r.points[0], last = r.points[r.points.length - 1];
2725
+ return [first, ...out, last].filter((p, i, all) => i === 0 || Math.hypot(p.x - all[i - 1].x, p.y - all[i - 1].y) >= 0.01);
2726
+ }
2727
+ function flowLayer(layout, size) {
2728
+ const shapes = [];
2729
+ const labels = [];
2730
+ for (const f of layout) {
2731
+ const r = f.route, { key, color } = f;
2732
+ shapes.push(
2733
+ /* @__PURE__ */ jsx3("path", { d: f.d, fill: "none", stroke: color, strokeWidth: 2, strokeLinejoin: "round", strokeLinecap: "round", ...f.dashed ? { strokeDasharray: "7 6" } : {} }, `${key}-line`),
2734
+ marker(f.start, r.points[0], DIR[r.from], color, `${key}-start`),
2735
+ marker(f.end, r.points[r.points.length - 1], DIR[r.to], color, `${key}-end`)
2736
+ );
2737
+ if (f.label) {
2668
2738
  labels.push(
2669
- /* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: mid.x - w / 2, top, width: w, height: 24, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: 12, backgroundColor: theme.paper, border: `1.5px solid ${color}`, color, fontSize: 12, fontWeight: 600 }, children: p.label }, `${key}-label`)
2739
+ /* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: f.label.x, top: f.label.y, width: f.label.w, height: 24, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: 12, backgroundColor: theme.paper, border: `1.5px solid ${color}`, color, fontSize: 12, fontWeight: 600 }, children: f.label.text }, `${key}-label`)
2670
2740
  );
2671
2741
  }
2672
- });
2742
+ }
2673
2743
  return /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: 0, left: 0, width: size.width, height: size.height, display: "flex" }, children: [
2674
2744
  /* @__PURE__ */ jsx3("svg", { width: size.width, height: size.height, viewBox: `0 0 ${size.width} ${size.height}`, style: { position: "absolute", top: 0, left: 0 }, children: shapes }),
2675
2745
  labels
@@ -2970,10 +3040,8 @@ async function renderWireframeSvg(spec, opts = {}) {
2970
3040
  if (issues.length) throw new SpecError(issues);
2971
3041
  }
2972
3042
  const flowsOn = opts.flows !== false;
2973
- const { width, height, gap } = boardLayout(spec, { flows: opts.flows });
2974
- const board = spec.elements[spec.root];
2975
- if ((board.props ?? {}).gap == null && gap !== BOARD_GAP)
2976
- spec = { ...spec, elements: { ...spec.elements, [spec.root]: { ...board, props: { ...board.props, gap } } } };
3043
+ const { width, height } = boardLayout(spec, { flows: opts.flows });
3044
+ spec = withBoardGap(spec, opts.flows);
2977
3045
  const palette = paletteFor(spec.elements[spec.root]?.props?.accent);
2978
3046
  const registry2 = withPalette(palette);
2979
3047
  const draw = async (s, reg) => renderToSvg(s, { registry: reg, includeStandard: false, fonts: await loadFonts(), width, height });
@@ -2981,13 +3049,17 @@ async function renderWireframeSvg(spec, opts = {}) {
2981
3049
  const { anchors, boxes } = await measureAnchors(spec, flows?.measure ?? []);
2982
3050
  if (!anchors.length && !flows?.list.length) return draw(spec, registry2);
2983
3051
  const layers = [];
2984
- if (flows?.list.length) {
2985
- const resolved = flows.list.map(({ key, from, to }) => ({ key, ends: { from: flows.box(from, boxes) ?? flows.screenBox(from), to: flows.box(to, boxes) ?? flows.screenBox(to), fromScreen: flows.screenBox(from), toScreen: flows.screenBox(to) } })).filter((f) => !!(f.ends.from && f.ends.to && f.ends.fromScreen && f.ends.toScreen));
2986
- layers.push(flowLayer(spec, resolved, flows.screens, flows.gap, palette, { width, height }));
2987
- }
3052
+ if (flows?.list.length) layers.push(flowLayer(layoutFlows(spec, flows.resolve(boxes), flows.screens, flows.gap, palette), { width, height }));
2988
3053
  if (anchors.length) layers.push(overlayLayer(spec, anchors, boxes, palette, registry2.Calendar));
2989
3054
  return draw(spec, withTopLayer(registry2, layers));
2990
3055
  }
3056
+ function withBoardGap(spec, flows) {
3057
+ const { gap } = boardLayout(spec, { flows });
3058
+ const board = spec.elements[spec.root];
3059
+ if ((board.props ?? {}).gap == null && gap !== BOARD_GAP)
3060
+ return { ...spec, elements: { ...spec.elements, [spec.root]: { ...board, props: { ...board.props, gap } } } };
3061
+ return spec;
3062
+ }
2991
3063
  function flowEnds(spec) {
2992
3064
  const els = spec.elements;
2993
3065
  const byId = /* @__PURE__ */ new Map();
@@ -3015,9 +3087,12 @@ function flowEnds(spec) {
3015
3087
  list,
3016
3088
  measure,
3017
3089
  screens: [...screenRects.values()],
3018
- screenBox: (key) => screenRects.get(screenOf(key) ?? ""),
3019
3090
  gap: (els[spec.root].props ?? {}).gap ?? BOARD_GAP,
3020
- box: (key, boxes) => els[key].type === "Screen" ? screenRects.get(key) : boxes[key]
3091
+ resolve(boxes) {
3092
+ const box = (key) => els[key].type === "Screen" ? screenRects.get(key) : boxes[key];
3093
+ const screenBox = (key) => screenRects.get(screenOf(key) ?? "");
3094
+ return list.map(({ key, from, to }) => ({ key, ends: { from: box(from) ?? screenBox(from), to: box(to) ?? screenBox(to), fromScreen: screenBox(from), toScreen: screenBox(to) } })).filter((f) => !!(f.ends.from && f.ends.to && f.ends.fromScreen && f.ends.toScreen));
3095
+ }
3021
3096
  };
3022
3097
  }
3023
3098
  async function measureAnchors(spec, extra = []) {
@@ -3061,7 +3136,7 @@ function compileWireframe(source) {
3061
3136
  return true;
3062
3137
  });
3063
3138
  unique.sort((a, b) => a.line - b.line);
3064
- return { spec: parsed.spec, issues: unique };
3139
+ return { spec: parsed.spec, issues: unique, lines: parsed.lines };
3065
3140
  }
3066
3141
  function formatIssues(issues) {
3067
3142
  return issues.map((i) => `line ${i.line}: ${i.message}`).join("\n");
@@ -3310,23 +3385,37 @@ Reply with the corrected wireframe in a single code block.`;
3310
3385
  export {
3311
3386
  theme,
3312
3387
  DEVICES,
3388
+ LABEL_H,
3313
3389
  ACCENTS,
3390
+ paletteFor,
3314
3391
  iconNames,
3315
3392
  UNIVERSAL_PROPS,
3316
3393
  takesUniversalProps,
3317
3394
  componentDefinitions,
3318
3395
  catalog,
3396
+ withPalette,
3397
+ withMarkers,
3319
3398
  registry,
3399
+ findAnchors,
3400
+ tagForMeasuring,
3401
+ readMarkers,
3402
+ overlayBoxes,
3403
+ layoutFlows,
3404
+ flowPolyline,
3405
+ loadFonts,
3320
3406
  PRIMARY_PROP,
3321
3407
  bareWords,
3322
3408
  parseWireframeText,
3323
3409
  RENAMED_PROPS,
3324
3410
  upgradeWireframe,
3325
3411
  upgradeSpec,
3412
+ SpecError,
3326
3413
  checkSpec,
3327
3414
  boardLayout,
3328
3415
  boardSize,
3329
3416
  renderWireframeSvg,
3417
+ withBoardGap,
3418
+ flowEnds,
3330
3419
  renderWireframePng,
3331
3420
  compileWireframe,
3332
3421
  formatIssues,
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  printWireframeText,
7
7
  renderWireframe,
8
8
  wireframePrompt
9
- } from "./chunks/chunk-BSE346UI.js";
9
+ } from "./chunks/chunk-K3Y2E2QA.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import { readFile, writeFile } from "node:fs/promises";
package/dist/compile.d.ts CHANGED
@@ -5,6 +5,8 @@ export interface CompileResult {
5
5
  spec: Spec | null;
6
6
  /** Every problem, tied to a source line. Empty means the wireframe is valid. */
7
7
  issues: TextIssue[];
8
+ /** Each element's line in the text, by its key in the spec (for measureElements). */
9
+ lines: Record<string, number>;
8
10
  }
9
11
  /**
10
12
  * Parse wireframe text and validate it against the catalog, reporting every
package/dist/flows.d.ts CHANGED
@@ -13,7 +13,7 @@ import { type ReactNode } from "react";
13
13
  import type { Spec } from "@json-render/core";
14
14
  import { type Palette } from "./colors.js";
15
15
  import type { Rect } from "./anchors.js";
16
- type Pt = {
16
+ export type Pt = {
17
17
  x: number;
18
18
  y: number;
19
19
  };
@@ -41,6 +41,7 @@ export interface FlowBoxes {
41
41
  * `lane` and `track` count the flows already using lanes and gaps, so each gets its own.
42
42
  */
43
43
  export declare function route(ends: FlowBoxes, screens: Rect[], gap: number, lane?: number, track?: number, nudge?: number): Route;
44
+ type End = "none" | "arrow" | "dot" | "circle" | "bar";
44
45
  /**
45
46
  * The SVG path for a routed flow, and a point a fraction along it (for the label). `pts` are the
46
47
  * route's points, trimmed for the markers. "straight" and "curved" only cut across when the route
@@ -55,11 +56,37 @@ export declare function linePath(r: Route, pts: Pt[], line: string): {
55
56
  export declare const flowLabelWidth: (label: string) => number;
56
57
  /** A flow's line color: gray, an accent name, a hex color, or the board's accent. */
57
58
  export declare function flowColor(color: unknown, palette: Palette): string;
58
- /** The top layer with every flow: one SVG for the lines and markers, labels as text pills. */
59
- export declare function flowLayer(spec: Spec, flows: {
59
+ /** A flow laid out: its route, the drawn line, its ends and its label pill, all in board pixels. */
60
+ export interface FlowLayout {
61
+ key: string;
62
+ route: Route;
63
+ /** The route's points with the ends pulled back for their markers: what the line is drawn through. */
64
+ points: Pt[];
65
+ d: string;
66
+ color: string;
67
+ start: End;
68
+ end: End;
69
+ line: string;
70
+ dashed: boolean;
71
+ label?: {
72
+ text: string;
73
+ } & Rect;
74
+ }
75
+ /**
76
+ * Where every flow goes: routes (each in its own lane and gap track), the line through them, and
77
+ * each label's spot. flowLayer draws this; measureElements reads it for hit-testing.
78
+ */
79
+ export declare function layoutFlows(spec: Spec, flows: {
60
80
  key: string;
61
81
  ends: FlowBoxes;
62
- }[], screens: Rect[], gap: number, palette: Palette, size: {
82
+ }[], screens: Rect[], gap: number, palette: Palette): FlowLayout[];
83
+ /**
84
+ * The line a flow draws, as points close enough to the drawn curve for hit-testing (corners and
85
+ * curves sampled), from the tip of its start marker to the tip of its end marker.
86
+ */
87
+ export declare function flowPolyline(f: FlowLayout): Pt[];
88
+ /** The top layer with every flow: one SVG for the lines and markers, labels as text pills. */
89
+ export declare function flowLayer(layout: FlowLayout[], size: {
63
90
  width: number;
64
91
  height: number;
65
92
  }): ReactNode;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export { encodeWireframe, decodeWireframe, MAX_SHARED_TEXT } from "./share.js";
6
6
  export { mcpTools, MCP_INSTRUCTIONS, LINK_KINDS, DEFAULT_LINKS, type LinkKind, wireframeGuide, renderWireframeTool, shareWireframeTool, callMcpTool, type ToolResult, type ToolContent, type ToolOptions, type McpToolName } from "./mcp.js";
7
7
  export { upgradeWireframe, upgradeSpec, RENAMED_PROPS, type UpgradeOptions } from "./upgrade.js";
8
8
  export { renderAll, DEFAULT_SELECTOR, type RenderAllOptions, type RenderAllResult } from "./dom.js";
9
+ export { measureElements, elementsAt, elementsAtLine, type Measured, type MeasuredElement, type MeasuredFlow, type MeasuredBox, type MeasureOptions } from "./measure.js";
9
10
  export { renderWireframeSvg, renderWireframePng, checkSpec, boardSize, boardLayout, type BoardItem, type RenderWireframeOptions } from "./render.js";
10
11
  export { parseWireframeText, type TextIssue, type ParseResult } from "./text.js";
11
12
  export { catalog, componentDefinitions } from "./catalog.js";