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.
- package/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/dist/anchors.d.ts +13 -3
- package/dist/browser/index.js +307 -85
- package/dist/chunks/{chunk-XWGHLFRX.js → chunk-HVGRKWIV.js} +1 -1
- package/dist/chunks/{chunk-BSE346UI.js → chunk-K3Y2E2QA.js} +158 -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) {
|
|
@@ -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
|
|
2456
|
-
|
|
2457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
);
|
|
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
|
|
2667
|
-
placed.push(
|
|
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:
|
|
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
|
|
2974
|
-
|
|
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
|
-
|
|
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
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";
|