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.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/anchors.d.ts +12 -3
- package/dist/browser/index.js +303 -85
- package/dist/chunks/{chunk-XWGHLFRX.js → chunk-PGX7ANZH.js} +1 -1
- package/dist/chunks/{chunk-BSE346UI.js → chunk-YOZFCNSK.js} +154 -69
- package/dist/cli.js +1 -1
- package/dist/compile.d.ts +2 -0
- package/dist/flows.d.ts +31 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +166 -3
- package/dist/measure.d.ts +97 -0
- package/dist/playground/index.js +2 -2
- package/dist/render.d.ts +24 -0
- package/dist/tsquare.min.js +68 -68
- package/package.json +2 -2
|
@@ -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
|
|
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__ */
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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,
|
|
2383
|
-
const key =
|
|
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,
|
|
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
|
|
2456
|
-
|
|
2457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
2652
|
-
|
|
2653
|
-
|
|
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
|
|
2667
|
-
placed.push(
|
|
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:
|
|
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
|
|
2974
|
-
|
|
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
|
-
|
|
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
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
|
-
/**
|
|
59
|
-
export
|
|
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
|
|
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";
|