tsquare 0.12.4 → 0.13.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.
@@ -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) {
@@ -2449,35 +2457,54 @@ function tooltipSize(text) {
2449
2457
  const lines = Math.max(1, Math.ceil(text.length * 7.4 / 216));
2450
2458
  return { w: width, h: lines * 18 + 12 + 6 };
2451
2459
  }
2460
+ function placeOverlay(spec, a, boxes) {
2461
+ const screen = boxes[a.screen];
2462
+ const anchor = boxes[a.field ? `${a.id}#field` : a.id];
2463
+ if (!screen || !anchor) return null;
2464
+ const p = propsOf(spec.elements, a.id);
2465
+ if (a.kind === "options") {
2466
+ const options = p.options?.length ? p.options : [p.value ?? "Option 1", "Option 2", "Option 3"];
2467
+ const size2 = { w: anchor.w, h: options.length * ROW + 8 };
2468
+ return { screen, anchor, size: size2, pos: place(anchor, size2, screen, { prefer: "below", align: "start", gap: 4 }), options };
2469
+ }
2470
+ if (a.kind === "menu") {
2471
+ const items = p.menu.map((i) => typeof i === "string" ? { label: i } : i);
2472
+ const width = a.end ? 200 : Math.max(180, anchor.w);
2473
+ const inRightHalf = anchor.x + anchor.w / 2 > screen.x + screen.w / 2;
2474
+ const size2 = { w: width, h: items.length * ROW + 8 };
2475
+ return { screen, anchor, size: size2, pos: place(anchor, size2, screen, { prefer: "below", align: a.end || inRightHalf ? "end" : "start", gap: a.end ? -6 : 4 }), items };
2476
+ }
2477
+ if (a.kind === "calendar") {
2478
+ const date = parseDate(p.value);
2479
+ const { start, days } = monthGrid(date?.month);
2480
+ const weeks = Math.ceil((start + days) / 7);
2481
+ const size2 = { w: 296, h: 92 + weeks * 38 };
2482
+ return { screen, anchor, size: size2, pos: place(anchor, size2, screen, { prefer: "below", align: "start", gap: 4 }), date };
2483
+ }
2484
+ const size = tooltipSize(p.tooltip);
2485
+ return { screen, anchor, size, pos: place(anchor, size, screen, { prefer: "above", align: "center", gap: 4 }) };
2486
+ }
2487
+ function overlayBoxes(spec, anchors, boxes) {
2488
+ return anchors.flatMap((a) => {
2489
+ const o2 = placeOverlay(spec, a, boxes);
2490
+ return o2 ? [{ id: a.id, box: { x: o2.screen.x + o2.pos.x, y: o2.screen.y + o2.pos.y, w: o2.size.w, h: o2.size.h } }] : [];
2491
+ });
2492
+ }
2452
2493
  function overlayLayer(spec, anchors, boxes, c, Calendar2) {
2453
2494
  const byScreen = /* @__PURE__ */ new Map();
2454
2495
  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;
2496
+ const o2 = placeOverlay(spec, a, boxes);
2497
+ if (!o2) continue;
2498
+ const { screen, anchor, size, pos } = o2;
2458
2499
  const p = propsOf(spec.elements, a.id);
2459
2500
  let node = null;
2460
- let pos = null;
2461
2501
  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 });
2502
+ node = /* @__PURE__ */ jsx2(ListCard, { items: o2.options.map((label) => ({ label })), width: size.w, selected: p.value, c });
2466
2503
  } 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 });
2504
+ node = /* @__PURE__ */ jsx2(ListCard, { items: o2.items, width: size.w, c });
2472
2505
  } 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 }) });
2506
+ 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
2507
  } else {
2479
- const size = tooltipSize(p.tooltip);
2480
- pos = place(anchor, size, screen, { prefer: "above", align: "center", gap: 4 });
2481
2508
  node = /* @__PURE__ */ jsx2(Tooltip, { text: p.tooltip, side: pos.side, width: size.w, arrowX: anchor.x + anchor.w / 2 - (screen.x + pos.x) });
2482
2509
  }
2483
2510
  const list = byScreen.get(a.screen) ?? [];
@@ -2619,9 +2646,7 @@ function flowColor(color, palette) {
2619
2646
  const s = String(color);
2620
2647
  return ACCENTS[s] ?? s;
2621
2648
  }
2622
- function flowLayer(spec, flows, screens, gap, palette, size) {
2623
- const shapes = [];
2624
- const labels = [];
2649
+ function layoutFlows(spec, flows, screens, gap, palette) {
2625
2650
  const placed = [];
2626
2651
  const drawn = [];
2627
2652
  const tips = flows.flatMap(({ ends: ends2 }) => {
@@ -2640,20 +2665,16 @@ function flowLayer(spec, flows, screens, gap, palette, size) {
2640
2665
  return r;
2641
2666
  });
2642
2667
  const ends = routes.flatMap((r) => [r.points[0], r.points[r.points.length - 1]]);
2643
- flows.forEach(({ key }, i) => {
2668
+ return flows.map(({ key }, i) => {
2644
2669
  const r = routes[i];
2645
2670
  const p = spec.elements[key].props ?? {};
2646
- const color = flowColor(p.color, palette);
2647
2671
  const start = p.start ?? "none", end = p.end ?? "arrow";
2648
2672
  const pts = [...r.points];
2649
2673
  pts[0] = add(pts[0], DIR[r.from], INSET[start]);
2650
2674
  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
- );
2675
+ const line = p.line ?? "rounded";
2676
+ const { d, at } = linePath(r, pts, line);
2677
+ const out = { key, route: r, points: pts, d, color: flowColor(p.color, palette), start, end, line, dashed: !!p.dashed };
2657
2678
  if (p.label) {
2658
2679
  const w = flowLabelWidth(String(p.label));
2659
2680
  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 +2684,58 @@ function flowLayer(spec, flows, screens, gap, palette, size) {
2663
2684
  const c = at(t), n = covers(c);
2664
2685
  if (n < best) [mid, best] = [c, n];
2665
2686
  }
2666
- const top = mid.y - 12;
2667
- placed.push({ x: mid.x - w / 2, y: top, w, h: 24 });
2687
+ const box = { x: mid.x - w / 2, y: mid.y - 12, w, h: 24 };
2688
+ placed.push(box);
2689
+ out.label = { text: String(p.label), ...box };
2690
+ }
2691
+ return out;
2692
+ });
2693
+ }
2694
+ function flowPolyline(f) {
2695
+ const r = f.route, pts = f.points;
2696
+ const s0 = pts[0], e0 = pts[pts.length - 1];
2697
+ let out;
2698
+ if (r.lane == null && f.line === "straight") out = [s0, e0];
2699
+ else if (r.lane == null && f.line === "curved") {
2700
+ const { at } = linePath(r, pts, f.line);
2701
+ out = Array.from({ length: 25 }, (_, i) => at(i / 24));
2702
+ } else {
2703
+ const radius = f.line === "curved" ? 40 : f.line === "rounded" ? 14 : 0;
2704
+ const q = simplify(pts);
2705
+ out = [q[0]];
2706
+ for (let i = 1; i < q.length; i++) {
2707
+ const p = q[i];
2708
+ if (radius > 0 && i < q.length - 1) {
2709
+ const prev = q[i - 1], next = q[i + 1];
2710
+ 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);
2711
+ 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) };
2712
+ 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 };
2713
+ for (let k = 0; k <= 6; k++) {
2714
+ const t = k / 6;
2715
+ 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 });
2716
+ }
2717
+ } else out.push(p);
2718
+ }
2719
+ }
2720
+ const first = r.points[0], last = r.points[r.points.length - 1];
2721
+ 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);
2722
+ }
2723
+ function flowLayer(layout, size) {
2724
+ const shapes = [];
2725
+ const labels = [];
2726
+ for (const f of layout) {
2727
+ const r = f.route, { key, color } = f;
2728
+ shapes.push(
2729
+ /* @__PURE__ */ jsx3("path", { d: f.d, fill: "none", stroke: color, strokeWidth: 2, strokeLinejoin: "round", strokeLinecap: "round", ...f.dashed ? { strokeDasharray: "7 6" } : {} }, `${key}-line`),
2730
+ marker(f.start, r.points[0], DIR[r.from], color, `${key}-start`),
2731
+ marker(f.end, r.points[r.points.length - 1], DIR[r.to], color, `${key}-end`)
2732
+ );
2733
+ if (f.label) {
2668
2734
  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`)
2735
+ /* @__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
2736
  );
2671
2737
  }
2672
- });
2738
+ }
2673
2739
  return /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: 0, left: 0, width: size.width, height: size.height, display: "flex" }, children: [
2674
2740
  /* @__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
2741
  labels
@@ -2970,10 +3036,8 @@ async function renderWireframeSvg(spec, opts = {}) {
2970
3036
  if (issues.length) throw new SpecError(issues);
2971
3037
  }
2972
3038
  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 } } } };
3039
+ const { width, height } = boardLayout(spec, { flows: opts.flows });
3040
+ spec = withBoardGap(spec, opts.flows);
2977
3041
  const palette = paletteFor(spec.elements[spec.root]?.props?.accent);
2978
3042
  const registry2 = withPalette(palette);
2979
3043
  const draw = async (s, reg) => renderToSvg(s, { registry: reg, includeStandard: false, fonts: await loadFonts(), width, height });
@@ -2981,13 +3045,17 @@ async function renderWireframeSvg(spec, opts = {}) {
2981
3045
  const { anchors, boxes } = await measureAnchors(spec, flows?.measure ?? []);
2982
3046
  if (!anchors.length && !flows?.list.length) return draw(spec, registry2);
2983
3047
  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
- }
3048
+ if (flows?.list.length) layers.push(flowLayer(layoutFlows(spec, flows.resolve(boxes), flows.screens, flows.gap, palette), { width, height }));
2988
3049
  if (anchors.length) layers.push(overlayLayer(spec, anchors, boxes, palette, registry2.Calendar));
2989
3050
  return draw(spec, withTopLayer(registry2, layers));
2990
3051
  }
3052
+ function withBoardGap(spec, flows) {
3053
+ const { gap } = boardLayout(spec, { flows });
3054
+ const board = spec.elements[spec.root];
3055
+ if ((board.props ?? {}).gap == null && gap !== BOARD_GAP)
3056
+ return { ...spec, elements: { ...spec.elements, [spec.root]: { ...board, props: { ...board.props, gap } } } };
3057
+ return spec;
3058
+ }
2991
3059
  function flowEnds(spec) {
2992
3060
  const els = spec.elements;
2993
3061
  const byId = /* @__PURE__ */ new Map();
@@ -3015,9 +3083,12 @@ function flowEnds(spec) {
3015
3083
  list,
3016
3084
  measure,
3017
3085
  screens: [...screenRects.values()],
3018
- screenBox: (key) => screenRects.get(screenOf(key) ?? ""),
3019
3086
  gap: (els[spec.root].props ?? {}).gap ?? BOARD_GAP,
3020
- box: (key, boxes) => els[key].type === "Screen" ? screenRects.get(key) : boxes[key]
3087
+ resolve(boxes) {
3088
+ const box = (key) => els[key].type === "Screen" ? screenRects.get(key) : boxes[key];
3089
+ const screenBox = (key) => screenRects.get(screenOf(key) ?? "");
3090
+ 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));
3091
+ }
3021
3092
  };
3022
3093
  }
3023
3094
  async function measureAnchors(spec, extra = []) {
@@ -3061,7 +3132,7 @@ function compileWireframe(source) {
3061
3132
  return true;
3062
3133
  });
3063
3134
  unique.sort((a, b) => a.line - b.line);
3064
- return { spec: parsed.spec, issues: unique };
3135
+ return { spec: parsed.spec, issues: unique, lines: parsed.lines };
3065
3136
  }
3066
3137
  function formatIssues(issues) {
3067
3138
  return issues.map((i) => `line ${i.line}: ${i.message}`).join("\n");
@@ -3310,23 +3381,37 @@ Reply with the corrected wireframe in a single code block.`;
3310
3381
  export {
3311
3382
  theme,
3312
3383
  DEVICES,
3384
+ LABEL_H,
3313
3385
  ACCENTS,
3386
+ paletteFor,
3314
3387
  iconNames,
3315
3388
  UNIVERSAL_PROPS,
3316
3389
  takesUniversalProps,
3317
3390
  componentDefinitions,
3318
3391
  catalog,
3392
+ withPalette,
3393
+ withMarkers,
3319
3394
  registry,
3395
+ findAnchors,
3396
+ tagForMeasuring,
3397
+ readMarkers,
3398
+ overlayBoxes,
3399
+ layoutFlows,
3400
+ flowPolyline,
3401
+ loadFonts,
3320
3402
  PRIMARY_PROP,
3321
3403
  bareWords,
3322
3404
  parseWireframeText,
3323
3405
  RENAMED_PROPS,
3324
3406
  upgradeWireframe,
3325
3407
  upgradeSpec,
3408
+ SpecError,
3326
3409
  checkSpec,
3327
3410
  boardLayout,
3328
3411
  boardSize,
3329
3412
  renderWireframeSvg,
3413
+ withBoardGap,
3414
+ flowEnds,
3330
3415
  renderWireframePng,
3331
3416
  compileWireframe,
3332
3417
  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-YOZFCNSK.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";