tsquare 0.12.3 → 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 +13 -0
- package/README.md +1 -1
- package/dist/anchors.d.ts +12 -3
- package/dist/browser/index.js +319 -86
- package/dist/chunks/{chunk-2FU5Z5ON.js → chunk-PGX7ANZH.js} +1 -1
- package/dist/chunks/{chunk-H4Y6VBEX.js → chunk-YOZFCNSK.js} +170 -70
- 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
|
);
|
|
@@ -836,6 +837,16 @@ function BrowserBar() {
|
|
|
836
837
|
}
|
|
837
838
|
);
|
|
838
839
|
}
|
|
840
|
+
function mayOverflow(name, width) {
|
|
841
|
+
let w = 0;
|
|
842
|
+
for (const ch of name) {
|
|
843
|
+
if (ch.charCodeAt(0) >= 128 || /[MWmw@%&]/.test(ch)) w += 16.5;
|
|
844
|
+
else if (/[A-Z]/.test(ch)) w += 12.5;
|
|
845
|
+
else if (/[ .,:;'!|il1jft()[\]{}"`-]/.test(ch)) w += 8.5;
|
|
846
|
+
else w += 11;
|
|
847
|
+
}
|
|
848
|
+
return w > width;
|
|
849
|
+
}
|
|
839
850
|
function Screen({ element, children }) {
|
|
840
851
|
const p = element.props;
|
|
841
852
|
const s = screenSize(p);
|
|
@@ -851,7 +862,12 @@ function Screen({ element, children }) {
|
|
|
851
862
|
else body.push(c);
|
|
852
863
|
}
|
|
853
864
|
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", flexShrink: 0 }, children: [
|
|
854
|
-
|
|
865
|
+
mayOverflow(p.name ?? "", s.width) ? (
|
|
866
|
+
// the board's layout sizes each column by its screen, so a name that may be wider than the
|
|
867
|
+
// screen gets the screen's width and ends in "…" instead of widening the column. Only then:
|
|
868
|
+
// the clip changes how the text is antialiased, and names that fit should render as before.
|
|
869
|
+
/* @__PURE__ */ jsx(Box, { style: { width: s.width, height: LABEL_H, fontSize: 16, fontWeight: 600, color: theme.text }, children: /* @__PURE__ */ jsx("div", { style: { overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }, children: p.name }) })
|
|
870
|
+
) : /* @__PURE__ */ jsx(Box, { style: { height: LABEL_H, fontSize: 16, fontWeight: 600, color: theme.text }, children: p.name ?? "" }),
|
|
855
871
|
/* @__PURE__ */ jsxs(
|
|
856
872
|
Box,
|
|
857
873
|
{
|
|
@@ -872,19 +888,22 @@ function Screen({ element, children }) {
|
|
|
872
888
|
s.chrome && s.device === "desktop" ? /* @__PURE__ */ jsx(BrowserBar, {}) : null,
|
|
873
889
|
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", flexGrow: 1, flexBasis: "0px", flexDirection: "column", overflow: "hidden" }, children: [
|
|
874
890
|
top,
|
|
875
|
-
/* @__PURE__ */
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
] }),
|
|
888
907
|
bottom,
|
|
889
908
|
overlays.map((o2) => isValidElement(o2) ? cloneElement(o2, { aboveTabBar: bottom.length > 0 }) : o2),
|
|
890
909
|
p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
|
|
@@ -1739,7 +1758,8 @@ function Modal({ element, children }) {
|
|
|
1739
1758
|
},
|
|
1740
1759
|
children: [
|
|
1741
1760
|
p.title ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 18, fontWeight: 600 }, children: p.title }) : null,
|
|
1742
|
-
withDir(children, "column")
|
|
1761
|
+
withDir(children, "column"),
|
|
1762
|
+
p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
|
|
1743
1763
|
]
|
|
1744
1764
|
}
|
|
1745
1765
|
) });
|
|
@@ -1774,7 +1794,8 @@ function Drawer({ element, children }) {
|
|
|
1774
1794
|
/* @__PURE__ */ jsx(Box, { style: { fontSize: 18, fontWeight: 600 }, children: p.title }),
|
|
1775
1795
|
/* @__PURE__ */ jsx(IconGlyph, { name: "x", size: 22, color: theme.muted })
|
|
1776
1796
|
] }) : null,
|
|
1777
|
-
withDir(children, "column")
|
|
1797
|
+
withDir(children, "column"),
|
|
1798
|
+
p.__mark ? /* @__PURE__ */ jsx(Marker, { color: p.__mark }) : null
|
|
1778
1799
|
]
|
|
1779
1800
|
}
|
|
1780
1801
|
) });
|
|
@@ -1787,7 +1808,8 @@ function Toast({ element, aboveTabBar }) {
|
|
|
1787
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: [
|
|
1788
1809
|
p.icon ? /* @__PURE__ */ jsx(IconGlyph, { name: p.icon, size: 18, color: theme.onPrimary }) : null,
|
|
1789
1810
|
/* @__PURE__ */ jsx(Box, { style: { flexShrink: 1 }, children: p.text ?? "" }),
|
|
1790
|
-
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
|
|
1791
1813
|
] }) });
|
|
1792
1814
|
}
|
|
1793
1815
|
function Flow() {
|
|
@@ -2360,22 +2382,23 @@ function findAnchors(spec) {
|
|
|
2360
2382
|
return [...out, ...tips];
|
|
2361
2383
|
}
|
|
2362
2384
|
var MARK = (i) => `#fe${(i >> 8).toString(16).padStart(2, "0")}${(i & 255).toString(16).padStart(2, "0")}`;
|
|
2363
|
-
function tagForMeasuring(spec, anchors) {
|
|
2385
|
+
function tagForMeasuring(spec, anchors, bodies = []) {
|
|
2364
2386
|
const tagged = structuredClone(spec);
|
|
2365
2387
|
const colors = /* @__PURE__ */ new Map();
|
|
2366
2388
|
let n = 1;
|
|
2367
|
-
const tag = (id,
|
|
2368
|
-
const key =
|
|
2389
|
+
const tag = (id, part) => {
|
|
2390
|
+
const key = part ? `${id}#${part}` : id;
|
|
2369
2391
|
if (colors.has(key)) return;
|
|
2370
2392
|
const color = MARK(n++);
|
|
2371
2393
|
colors.set(key, color);
|
|
2372
2394
|
const el = tagged.elements[id];
|
|
2373
|
-
el.props = { ...el.props ?? {}, [field ? "__markField" : "__mark"]: color };
|
|
2395
|
+
el.props = { ...el.props ?? {}, [part === "field" ? "__markField" : part === "body" ? "__markBody" : "__mark"]: color };
|
|
2374
2396
|
};
|
|
2375
2397
|
for (const a of anchors) {
|
|
2376
|
-
tag(a.screen,
|
|
2377
|
-
tag(a.id, a.field);
|
|
2398
|
+
tag(a.screen, "");
|
|
2399
|
+
tag(a.id, a.field ? "field" : "");
|
|
2378
2400
|
}
|
|
2401
|
+
for (const id of bodies) tag(id, "body");
|
|
2379
2402
|
return { tagged, colors };
|
|
2380
2403
|
}
|
|
2381
2404
|
function readMarkers(svg, colors) {
|
|
@@ -2434,35 +2457,54 @@ function tooltipSize(text) {
|
|
|
2434
2457
|
const lines = Math.max(1, Math.ceil(text.length * 7.4 / 216));
|
|
2435
2458
|
return { w: width, h: lines * 18 + 12 + 6 };
|
|
2436
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
|
+
}
|
|
2437
2493
|
function overlayLayer(spec, anchors, boxes, c, Calendar2) {
|
|
2438
2494
|
const byScreen = /* @__PURE__ */ new Map();
|
|
2439
2495
|
for (const a of anchors) {
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
|
|
2496
|
+
const o2 = placeOverlay(spec, a, boxes);
|
|
2497
|
+
if (!o2) continue;
|
|
2498
|
+
const { screen, anchor, size, pos } = o2;
|
|
2443
2499
|
const p = propsOf(spec.elements, a.id);
|
|
2444
2500
|
let node = null;
|
|
2445
|
-
let pos = null;
|
|
2446
2501
|
if (a.kind === "options") {
|
|
2447
|
-
|
|
2448
|
-
const size = { w: anchor.w, h: options.length * ROW + 8 };
|
|
2449
|
-
pos = place(anchor, size, screen, { prefer: "below", align: "start", gap: 4 });
|
|
2450
|
-
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 });
|
|
2451
2503
|
} else if (a.kind === "menu") {
|
|
2452
|
-
|
|
2453
|
-
const width = a.end ? 200 : Math.max(180, anchor.w);
|
|
2454
|
-
const inRightHalf = anchor.x + anchor.w / 2 > screen.x + screen.w / 2;
|
|
2455
|
-
pos = place(anchor, { w: width, h: items.length * ROW + 8 }, screen, { prefer: "below", align: a.end || inRightHalf ? "end" : "start", gap: a.end ? -6 : 4 });
|
|
2456
|
-
node = /* @__PURE__ */ jsx2(ListCard, { items, width, c });
|
|
2504
|
+
node = /* @__PURE__ */ jsx2(ListCard, { items: o2.items, width: size.w, c });
|
|
2457
2505
|
} else if (a.kind === "calendar") {
|
|
2458
|
-
|
|
2459
|
-
const { start, days } = monthGrid(date?.month);
|
|
2460
|
-
const weeks = Math.ceil((start + days) / 7);
|
|
2461
|
-
pos = place(anchor, { w: 296, h: 92 + weeks * 38 }, screen, { prefer: "below", align: "start", gap: 4 });
|
|
2462
|
-
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 }) });
|
|
2463
2507
|
} else {
|
|
2464
|
-
const size = tooltipSize(p.tooltip);
|
|
2465
|
-
pos = place(anchor, size, screen, { prefer: "above", align: "center", gap: 4 });
|
|
2466
2508
|
node = /* @__PURE__ */ jsx2(Tooltip, { text: p.tooltip, side: pos.side, width: size.w, arrowX: anchor.x + anchor.w / 2 - (screen.x + pos.x) });
|
|
2467
2509
|
}
|
|
2468
2510
|
const list = byScreen.get(a.screen) ?? [];
|
|
@@ -2604,9 +2646,7 @@ function flowColor(color, palette) {
|
|
|
2604
2646
|
const s = String(color);
|
|
2605
2647
|
return ACCENTS[s] ?? s;
|
|
2606
2648
|
}
|
|
2607
|
-
function
|
|
2608
|
-
const shapes = [];
|
|
2609
|
-
const labels = [];
|
|
2649
|
+
function layoutFlows(spec, flows, screens, gap, palette) {
|
|
2610
2650
|
const placed = [];
|
|
2611
2651
|
const drawn = [];
|
|
2612
2652
|
const tips = flows.flatMap(({ ends: ends2 }) => {
|
|
@@ -2625,20 +2665,16 @@ function flowLayer(spec, flows, screens, gap, palette, size) {
|
|
|
2625
2665
|
return r;
|
|
2626
2666
|
});
|
|
2627
2667
|
const ends = routes.flatMap((r) => [r.points[0], r.points[r.points.length - 1]]);
|
|
2628
|
-
flows.
|
|
2668
|
+
return flows.map(({ key }, i) => {
|
|
2629
2669
|
const r = routes[i];
|
|
2630
2670
|
const p = spec.elements[key].props ?? {};
|
|
2631
|
-
const color = flowColor(p.color, palette);
|
|
2632
2671
|
const start = p.start ?? "none", end = p.end ?? "arrow";
|
|
2633
2672
|
const pts = [...r.points];
|
|
2634
2673
|
pts[0] = add(pts[0], DIR[r.from], INSET[start]);
|
|
2635
2674
|
pts[pts.length - 1] = add(pts[pts.length - 1], DIR[r.to], INSET[end]);
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
marker(start, r.points[0], DIR[r.from], color, `${key}-start`),
|
|
2640
|
-
marker(end, r.points[r.points.length - 1], DIR[r.to], color, `${key}-end`)
|
|
2641
|
-
);
|
|
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 };
|
|
2642
2678
|
if (p.label) {
|
|
2643
2679
|
const w = flowLabelWidth(String(p.label));
|
|
2644
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;
|
|
@@ -2648,13 +2684,58 @@ function flowLayer(spec, flows, screens, gap, palette, size) {
|
|
|
2648
2684
|
const c = at(t), n = covers(c);
|
|
2649
2685
|
if (n < best) [mid, best] = [c, n];
|
|
2650
2686
|
}
|
|
2651
|
-
const
|
|
2652
|
-
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) {
|
|
2653
2734
|
labels.push(
|
|
2654
|
-
/* @__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`)
|
|
2655
2736
|
);
|
|
2656
2737
|
}
|
|
2657
|
-
}
|
|
2738
|
+
}
|
|
2658
2739
|
return /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: 0, left: 0, width: size.width, height: size.height, display: "flex" }, children: [
|
|
2659
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 }),
|
|
2660
2741
|
labels
|
|
@@ -2955,10 +3036,8 @@ async function renderWireframeSvg(spec, opts = {}) {
|
|
|
2955
3036
|
if (issues.length) throw new SpecError(issues);
|
|
2956
3037
|
}
|
|
2957
3038
|
const flowsOn = opts.flows !== false;
|
|
2958
|
-
const { width, height
|
|
2959
|
-
|
|
2960
|
-
if ((board.props ?? {}).gap == null && gap !== BOARD_GAP)
|
|
2961
|
-
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);
|
|
2962
3041
|
const palette = paletteFor(spec.elements[spec.root]?.props?.accent);
|
|
2963
3042
|
const registry2 = withPalette(palette);
|
|
2964
3043
|
const draw = async (s, reg) => renderToSvg(s, { registry: reg, includeStandard: false, fonts: await loadFonts(), width, height });
|
|
@@ -2966,13 +3045,17 @@ async function renderWireframeSvg(spec, opts = {}) {
|
|
|
2966
3045
|
const { anchors, boxes } = await measureAnchors(spec, flows?.measure ?? []);
|
|
2967
3046
|
if (!anchors.length && !flows?.list.length) return draw(spec, registry2);
|
|
2968
3047
|
const layers = [];
|
|
2969
|
-
if (flows?.list.length) {
|
|
2970
|
-
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));
|
|
2971
|
-
layers.push(flowLayer(spec, resolved, flows.screens, flows.gap, palette, { width, height }));
|
|
2972
|
-
}
|
|
3048
|
+
if (flows?.list.length) layers.push(flowLayer(layoutFlows(spec, flows.resolve(boxes), flows.screens, flows.gap, palette), { width, height }));
|
|
2973
3049
|
if (anchors.length) layers.push(overlayLayer(spec, anchors, boxes, palette, registry2.Calendar));
|
|
2974
3050
|
return draw(spec, withTopLayer(registry2, layers));
|
|
2975
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
|
+
}
|
|
2976
3059
|
function flowEnds(spec) {
|
|
2977
3060
|
const els = spec.elements;
|
|
2978
3061
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -3000,9 +3083,12 @@ function flowEnds(spec) {
|
|
|
3000
3083
|
list,
|
|
3001
3084
|
measure,
|
|
3002
3085
|
screens: [...screenRects.values()],
|
|
3003
|
-
screenBox: (key) => screenRects.get(screenOf(key) ?? ""),
|
|
3004
3086
|
gap: (els[spec.root].props ?? {}).gap ?? BOARD_GAP,
|
|
3005
|
-
|
|
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
|
+
}
|
|
3006
3092
|
};
|
|
3007
3093
|
}
|
|
3008
3094
|
async function measureAnchors(spec, extra = []) {
|
|
@@ -3046,7 +3132,7 @@ function compileWireframe(source) {
|
|
|
3046
3132
|
return true;
|
|
3047
3133
|
});
|
|
3048
3134
|
unique.sort((a, b) => a.line - b.line);
|
|
3049
|
-
return { spec: parsed.spec, issues: unique };
|
|
3135
|
+
return { spec: parsed.spec, issues: unique, lines: parsed.lines };
|
|
3050
3136
|
}
|
|
3051
3137
|
function formatIssues(issues) {
|
|
3052
3138
|
return issues.map((i) => `line ${i.line}: ${i.message}`).join("\n");
|
|
@@ -3295,23 +3381,37 @@ Reply with the corrected wireframe in a single code block.`;
|
|
|
3295
3381
|
export {
|
|
3296
3382
|
theme,
|
|
3297
3383
|
DEVICES,
|
|
3384
|
+
LABEL_H,
|
|
3298
3385
|
ACCENTS,
|
|
3386
|
+
paletteFor,
|
|
3299
3387
|
iconNames,
|
|
3300
3388
|
UNIVERSAL_PROPS,
|
|
3301
3389
|
takesUniversalProps,
|
|
3302
3390
|
componentDefinitions,
|
|
3303
3391
|
catalog,
|
|
3392
|
+
withPalette,
|
|
3393
|
+
withMarkers,
|
|
3304
3394
|
registry,
|
|
3395
|
+
findAnchors,
|
|
3396
|
+
tagForMeasuring,
|
|
3397
|
+
readMarkers,
|
|
3398
|
+
overlayBoxes,
|
|
3399
|
+
layoutFlows,
|
|
3400
|
+
flowPolyline,
|
|
3401
|
+
loadFonts,
|
|
3305
3402
|
PRIMARY_PROP,
|
|
3306
3403
|
bareWords,
|
|
3307
3404
|
parseWireframeText,
|
|
3308
3405
|
RENAMED_PROPS,
|
|
3309
3406
|
upgradeWireframe,
|
|
3310
3407
|
upgradeSpec,
|
|
3408
|
+
SpecError,
|
|
3311
3409
|
checkSpec,
|
|
3312
3410
|
boardLayout,
|
|
3313
3411
|
boardSize,
|
|
3314
3412
|
renderWireframeSvg,
|
|
3413
|
+
withBoardGap,
|
|
3414
|
+
flowEnds,
|
|
3315
3415
|
renderWireframePng,
|
|
3316
3416
|
compileWireframe,
|
|
3317
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";
|