tsquare 0.5.0 → 0.6.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 +43 -0
- package/dist/catalog.d.ts +78 -0
- package/dist/chunks/{chunk-B6KIVOSH.js → chunk-HDSKRKDL.js} +369 -25
- package/dist/cli.js +3 -3
- package/dist/compile.d.ts +1 -0
- package/dist/components.d.ts +3 -0
- package/dist/flows.d.ts +66 -0
- package/dist/index.js +5 -4
- package/dist/playground/index.d.ts +1 -0
- package/dist/playground/index.html +8 -0
- package/dist/playground/index.js +30 -6
- package/dist/playground/playground.js +17 -17
- package/dist/prompt.d.ts +1 -1
- package/dist/render.d.ts +13 -10
- package/dist/upgrade.d.ts +1 -1
- package/examples/sign-up-flow.tsq +26 -0
- package/package.json +1 -1
- package/skill/tsquare/SKILL.md +4 -3
- package/skill/tsquare/reference.md +12 -1
|
@@ -204,12 +204,13 @@ var wireframeSchema = defineSchema(
|
|
|
204
204
|
}),
|
|
205
205
|
{
|
|
206
206
|
defaultRules: [
|
|
207
|
-
"The root element MUST be a Board. A Board's children are Screens (and optional Notes placed beside them).",
|
|
207
|
+
"The root element MUST be a Board. A Board's children are Screens (and optional Notes placed beside them, and Flow elements after them).",
|
|
208
208
|
"Every Screen MUST be a direct child of the Board. Each Screen is one view of the product; use several Screens to show several views or states.",
|
|
209
209
|
"Screens lay out their children top to bottom. Use Stack (direction row/column) and Grid inside a Screen to arrange content.",
|
|
210
210
|
"Modal, Drawer and Toast are overlays. They MUST be direct children of a Screen, and SHOULD be listed last in the Screen's children so they draw on top.",
|
|
211
211
|
"NavBar goes first in a Screen's children and TabBar goes last (before any overlay).",
|
|
212
|
-
"
|
|
212
|
+
"To show how screens connect, give elements or screens an id prop and add Flow elements as Board children (after the Screens), with from and to set to those ids.",
|
|
213
|
+
"Any element except Board, Screen, Note and Flow can take a tooltip prop (text) to show a tooltip next to it. Select (with options), Input with type date, and Button, ListItem or NavBar (with menu) take open to show their list, calendar or menu over the screen.",
|
|
213
214
|
"This is a low-fidelity wireframe: prefer placeholders (Image boxes, Text with lines) over invented copy, unless the copy matters to the design.",
|
|
214
215
|
'Every element MUST include a "children" array. Leaf elements use an empty array: "children": [].',
|
|
215
216
|
"Every id listed in a children array MUST exist as its own element."
|
|
@@ -224,11 +225,14 @@ var justify = z.enum(["start", "center", "end", "between", "around"]);
|
|
|
224
225
|
var iconName = z.string().refine(isIcon, { error: (iss) => unknownIconMessage(String(iss.input)) }).describe("Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user");
|
|
225
226
|
var day = z.number().int().min(1).max(31);
|
|
226
227
|
var percent = z.number().min(0).max(100);
|
|
228
|
+
var flowEnd = z.enum(["none", "arrow", "dot", "circle", "bar"]);
|
|
227
229
|
var menuItem = z.union([z.string(), z.strictObject({ label: z.string(), icon: o(iconName) })]);
|
|
228
230
|
var UNIVERSAL_PROPS = {
|
|
229
231
|
tooltip: z.string().describe("Show a tooltip with this text next to the element")
|
|
230
232
|
};
|
|
231
|
-
var
|
|
233
|
+
var ID_PATTERN = /^[A-Za-z][\w-]*$/;
|
|
234
|
+
var takesId = (type) => type !== "Board" && type !== "Note" && type !== "Flow";
|
|
235
|
+
var takesUniversalProps = (type) => type !== "Board" && type !== "Screen" && type !== "Note" && type !== "Flow";
|
|
232
236
|
function universalPropMessage(type, key) {
|
|
233
237
|
const base = `${key}s go on elements inside a screen`;
|
|
234
238
|
return type === "Note" ? `${base}; a note is already an annotation, so put the text in the note` : `${base}, not on the ${type.toLowerCase()} itself`;
|
|
@@ -252,7 +256,7 @@ var componentDefinitions = {
|
|
|
252
256
|
)
|
|
253
257
|
}),
|
|
254
258
|
slots: ["default"],
|
|
255
|
-
description: "Root canvas (artboard). Holds Screens side by side, plus optional Notes. Must be the root element.",
|
|
259
|
+
description: "Root canvas (artboard). Holds Screens side by side, plus optional Notes and Flow lines. Must be the root element.",
|
|
256
260
|
example: { title: "Checkout flow", layout: "row", gap: 64 }
|
|
257
261
|
},
|
|
258
262
|
Screen: {
|
|
@@ -279,6 +283,23 @@ var componentDefinitions = {
|
|
|
279
283
|
description: "Sticky-note annotation. Put it on the Board beside screens, or inside a Screen next to what it explains.",
|
|
280
284
|
example: { text: "Empty state TBD", color: "yellow" }
|
|
281
285
|
},
|
|
286
|
+
Flow: {
|
|
287
|
+
props: z.object({
|
|
288
|
+
from: z.string().describe("The id where the arrow starts (written before ->)"),
|
|
289
|
+
to: z.string().describe("The id it points at (written after ->)"),
|
|
290
|
+
label: o(z.string()),
|
|
291
|
+
start: o(flowEnd).describe("Default none"),
|
|
292
|
+
end: o(flowEnd).describe("Default arrow"),
|
|
293
|
+
line: o(z.enum(["rounded", "hard", "curved", "straight"])).describe("Default rounded: right angles with rounded corners"),
|
|
294
|
+
dashed: o(z.boolean()),
|
|
295
|
+
color: o(z.string().refine((v) => v === "accent" || isAccent(v), { error: (iss) => `${accentMessage(String(iss.input))}, or accent for the board's` })).describe(
|
|
296
|
+
`Default gray. ${Object.keys(ACCENTS).join(", ")}, a hex color, or accent for the board's`
|
|
297
|
+
)
|
|
298
|
+
}),
|
|
299
|
+
slots: [],
|
|
300
|
+
description: 'An arrow between two elements or screens, named with #id. Written at the board level after the screens: flow signin -> home "Tap Sign in".',
|
|
301
|
+
example: { from: "signin", to: "home", label: "Tap Sign in" }
|
|
302
|
+
},
|
|
282
303
|
// ── Layout ────────────────────────────────────────────────────────────
|
|
283
304
|
Stack: {
|
|
284
305
|
props: z.object({
|
|
@@ -736,7 +757,7 @@ function IconGlyph({ name, size = 20, color = theme.ink, strokeWidth = 2 }) {
|
|
|
736
757
|
function Board({ element, children }) {
|
|
737
758
|
const p = element.props;
|
|
738
759
|
const gap = p.gap ?? BOARD_GAP;
|
|
739
|
-
const items = Children.toArray(children);
|
|
760
|
+
const items = Children.toArray(children).filter((c) => !(isValidElement(c) && c.props.element?.type === "Flow"));
|
|
740
761
|
const perRow = p.layout === "grid" ? Math.max(1, p.columns ?? 3) : Math.max(1, items.length);
|
|
741
762
|
const rows = [];
|
|
742
763
|
for (let i = 0; i < items.length; i += perRow) rows.push(items.slice(i, i + perRow));
|
|
@@ -1037,9 +1058,9 @@ function Bullets({ element }) {
|
|
|
1037
1058
|
const { label, icon, muted } = typeof item === "string" ? { label: item, icon: null, muted: false } : item;
|
|
1038
1059
|
const color = muted ? theme.muted : theme.text;
|
|
1039
1060
|
const glyph = icon ?? p.icon;
|
|
1040
|
-
const
|
|
1061
|
+
const marker2 = glyph ? /* @__PURE__ */ jsx(IconGlyph, { name: glyph, size: 18, color }) : p.numbered ? /* @__PURE__ */ jsx(Box, { style: { minWidth: 18, color, fontWeight: 600 }, children: `${i + 1}.` }) : /* @__PURE__ */ jsx(Box, { style: { width: 18, height: 22, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(Box, { style: { width: 6, height: 6, borderRadius: 3, backgroundColor: color } }) });
|
|
1041
1062
|
return /* @__PURE__ */ jsxs(Box, { style: { gap: 10, alignItems: glyph ? "center" : "flex-start", fontSize: 15, lineHeight: 1.45, color }, children: [
|
|
1042
|
-
|
|
1063
|
+
marker2,
|
|
1043
1064
|
/* @__PURE__ */ jsx(Box, { style: { flexShrink: 1 }, children: label })
|
|
1044
1065
|
] }, i);
|
|
1045
1066
|
}) });
|
|
@@ -1758,8 +1779,12 @@ function Toast({ element, aboveTabBar }) {
|
|
|
1758
1779
|
p.action ? /* @__PURE__ */ jsx(Box, { style: { fontWeight: 700, marginLeft: 8, flexShrink: 0 }, children: p.action }) : null
|
|
1759
1780
|
] }) });
|
|
1760
1781
|
}
|
|
1782
|
+
function Flow() {
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1761
1785
|
var registry = {
|
|
1762
1786
|
Board,
|
|
1787
|
+
Flow,
|
|
1763
1788
|
Screen,
|
|
1764
1789
|
Note,
|
|
1765
1790
|
Stack,
|
|
@@ -1824,6 +1849,7 @@ var PRIMARY_PROP = {
|
|
|
1824
1849
|
Modal: "title",
|
|
1825
1850
|
Drawer: "title",
|
|
1826
1851
|
Accordion: "title",
|
|
1852
|
+
Flow: "label",
|
|
1827
1853
|
Toast: "text"
|
|
1828
1854
|
};
|
|
1829
1855
|
function unwrap(t) {
|
|
@@ -2079,8 +2105,19 @@ function parseWireframeText(source) {
|
|
|
2079
2105
|
return;
|
|
2080
2106
|
}
|
|
2081
2107
|
const props = {};
|
|
2108
|
+
let rest = m[2];
|
|
2109
|
+
if (info.name === "Flow") {
|
|
2110
|
+
const ends = rest.match(/^\s*#?([A-Za-z][\w-]*)\s*->\s*#?([A-Za-z][\w-]*)(?=\s|$)(.*)$/);
|
|
2111
|
+
if (!ends) {
|
|
2112
|
+
issues.push({ line: lineNo, message: `a flow line looks like: flow <from> -> <to> "label", where from and to are ids written #name after an element or screen` });
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
props.from = ends[1];
|
|
2116
|
+
props.to = ends[2];
|
|
2117
|
+
rest = ends[3];
|
|
2118
|
+
}
|
|
2082
2119
|
try {
|
|
2083
|
-
const c = new Cursor(tokenize(
|
|
2120
|
+
const c = new Cursor(tokenize(rest), rest);
|
|
2084
2121
|
while (!c.done()) {
|
|
2085
2122
|
c.skipCommas();
|
|
2086
2123
|
if (c.done()) break;
|
|
@@ -2120,6 +2157,11 @@ function parseWireframeText(source) {
|
|
|
2120
2157
|
props[ANTONYMS[t.value]] = false;
|
|
2121
2158
|
} else if (t.kind === "word" && WORD_PRIMARY.has(info.name) && !(PRIMARY_PROP[info.name] in props)) {
|
|
2122
2159
|
props[PRIMARY_PROP[info.name]] = t.value;
|
|
2160
|
+
} else if (t.kind === "word" && t.value.startsWith("#") && ID_PATTERN.test(t.value.slice(1)) && takesId(info.name)) {
|
|
2161
|
+
if (props.id != null) issues.push({ line: lineNo, message: `two ids (#${props.id} and ${t.value}); an element has one` });
|
|
2162
|
+
else props.id = t.value.slice(1);
|
|
2163
|
+
} else if (t.kind === "word" && t.value.startsWith("#") && ID_PATTERN.test(t.value.slice(1))) {
|
|
2164
|
+
issues.push({ line: lineNo, message: `${info.name} can't have an id (${t.value}); ids name elements and screens for flow arrows` });
|
|
2123
2165
|
} else if (t.kind === "word" && t.value.startsWith("#")) {
|
|
2124
2166
|
issues.push({ line: lineNo, message: `"${t.value}": a comment must be on its own line, starting with #; to show # as text, quote it` });
|
|
2125
2167
|
break;
|
|
@@ -2422,6 +2464,191 @@ function overlayLayer(spec, anchors, boxes, c, Calendar2) {
|
|
|
2422
2464
|
}) }, "__overlays");
|
|
2423
2465
|
}
|
|
2424
2466
|
|
|
2467
|
+
// src/flows.tsx
|
|
2468
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2469
|
+
var DIR = { left: { x: -1, y: 0 }, right: { x: 1, y: 0 }, top: { x: 0, y: -1 }, bottom: { x: 0, y: 1 } };
|
|
2470
|
+
var port = (b, s) => s === "left" ? { x: b.x, y: b.y + b.h / 2 } : s === "right" ? { x: b.x + b.w, y: b.y + b.h / 2 } : s === "top" ? { x: b.x + b.w / 2, y: b.y } : { x: b.x + b.w / 2, y: b.y + b.h };
|
|
2471
|
+
var add = (p, d, k) => ({ x: p.x + d.x * k, y: p.y + d.y * k });
|
|
2472
|
+
var LANE_GAP = 16;
|
|
2473
|
+
var flowMargin = (flows) => 40 + LANE_GAP * Math.max(0, flows - 1);
|
|
2474
|
+
var same = (a, b) => a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
|
|
2475
|
+
var overlapsY = (a, b) => a.y < b.y + b.h && b.y < a.y + a.h;
|
|
2476
|
+
function route(ends, screens, gap, lane = 0, track = 0, nudge = 0) {
|
|
2477
|
+
const { from: a, to: b, fromScreen: sa, toScreen: sb } = ends;
|
|
2478
|
+
if (same(sa, sb)) {
|
|
2479
|
+
if (b.x >= a.x + a.w - 1) {
|
|
2480
|
+
const s3 = port(a, "right"), e3 = port(b, "left"), mx = (s3.x + e3.x) / 2;
|
|
2481
|
+
return { from: "right", to: "left", points: [s3, { x: mx, y: s3.y }, { x: mx, y: e3.y }, e3] };
|
|
2482
|
+
}
|
|
2483
|
+
if (b.x + b.w <= a.x + 1) {
|
|
2484
|
+
const s3 = port(a, "left"), e3 = port(b, "right"), mx = (s3.x + e3.x) / 2;
|
|
2485
|
+
return { from: "left", to: "right", points: [s3, { x: mx, y: s3.y }, { x: mx, y: e3.y }, e3] };
|
|
2486
|
+
}
|
|
2487
|
+
const down = b.y >= a.y + a.h / 2;
|
|
2488
|
+
const s2 = port(a, down ? "bottom" : "top"), e2 = port(b, down ? "top" : "bottom"), my = (s2.y + e2.y) / 2;
|
|
2489
|
+
return { from: down ? "bottom" : "top", to: down ? "top" : "bottom", points: [s2, { x: s2.x, y: my }, { x: e2.x, y: my }, e2] };
|
|
2490
|
+
}
|
|
2491
|
+
const right = sb.x >= sa.x + sa.w, left = sb.x + sb.w <= sa.x;
|
|
2492
|
+
const from = right ? "right" : "left", to = right ? "left" : "right";
|
|
2493
|
+
const s = port(a, from), e = port(b, to);
|
|
2494
|
+
const level = (y, r) => Math.max(r.y + 24, Math.min(r.y + r.h - 24, y));
|
|
2495
|
+
if (same(b, sb) && !same(a, sa)) e.y = level(s.y + nudge, sb);
|
|
2496
|
+
if (same(a, sa) && !same(b, sb)) s.y = level(e.y + nudge, sa);
|
|
2497
|
+
const shift = Math.max(-(gap / 2 - 8), Math.min(gap / 2 - 8, TRACKS[track % TRACKS.length]));
|
|
2498
|
+
const lo = right ? sa : sb, hi = right ? sb : sa;
|
|
2499
|
+
const between = screens.some((r) => !same(r, sa) && !same(r, sb) && overlapsY(r, sa) && r.x >= lo.x + lo.w && r.x + r.w <= hi.x);
|
|
2500
|
+
if ((right || left) && !between && overlapsY(sa, sb)) {
|
|
2501
|
+
const mx = (lo.x + lo.w + hi.x) / 2 + shift;
|
|
2502
|
+
return { from, to, points: [s, { x: mx, y: s.y }, { x: mx, y: e.y }, e] };
|
|
2503
|
+
}
|
|
2504
|
+
const below = Math.max(...screens.map((r) => r.y + r.h)) + 24 + lane * LANE_GAP;
|
|
2505
|
+
const gx1 = right ? sa.x + sa.w + gap / 2 + shift : sa.x - gap / 2 + shift;
|
|
2506
|
+
const gx2 = right ? sb.x - gap / 2 + shift : sb.x + sb.w + gap / 2 + shift;
|
|
2507
|
+
return { from, to, lane: below, points: [s, { x: gx1, y: s.y }, { x: gx1, y: below }, { x: gx2, y: below }, { x: gx2, y: e.y }, e] };
|
|
2508
|
+
}
|
|
2509
|
+
var runs = (r) => r.points.slice(1).flatMap((p, i) => {
|
|
2510
|
+
const q = r.points[i];
|
|
2511
|
+
return Math.abs(p.y - q.y) < 0.5 && Math.abs(p.x - q.x) > 0.5 ? [{ y: p.y, x1: Math.min(p.x, q.x), x2: Math.max(p.x, q.x) }] : [];
|
|
2512
|
+
});
|
|
2513
|
+
var TRACKS = [0, -10, 10, -20, 20, -5, 5, -15, 15];
|
|
2514
|
+
function simplify(points) {
|
|
2515
|
+
const out = [];
|
|
2516
|
+
for (const p of points) {
|
|
2517
|
+
const last = out[out.length - 1];
|
|
2518
|
+
if (last && Math.abs(last.x - p.x) < 0.5 && Math.abs(last.y - p.y) < 0.5) continue;
|
|
2519
|
+
if (out.length >= 2) {
|
|
2520
|
+
const a = out[out.length - 2], b = out[out.length - 1];
|
|
2521
|
+
if (Math.abs(a.x - b.x) < 0.5 && Math.abs(b.x - p.x) < 0.5 || Math.abs(a.y - b.y) < 0.5 && Math.abs(b.y - p.y) < 0.5) out.pop();
|
|
2522
|
+
}
|
|
2523
|
+
out.push(p);
|
|
2524
|
+
}
|
|
2525
|
+
return out;
|
|
2526
|
+
}
|
|
2527
|
+
function polylinePath(points, radius) {
|
|
2528
|
+
const pts = simplify(points);
|
|
2529
|
+
let d = `M${pts[0].x} ${pts[0].y}`;
|
|
2530
|
+
for (let i = 1; i < pts.length; i++) {
|
|
2531
|
+
const p = pts[i];
|
|
2532
|
+
if (radius > 0 && i < pts.length - 1) {
|
|
2533
|
+
const prev = pts[i - 1], next = pts[i + 1];
|
|
2534
|
+
const r = 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);
|
|
2535
|
+
const into = { x: Math.sign(p.x - prev.x), y: Math.sign(p.y - prev.y) }, out = { x: Math.sign(next.x - p.x), y: Math.sign(next.y - p.y) };
|
|
2536
|
+
d += ` L${p.x - into.x * r} ${p.y - into.y * r} Q${p.x} ${p.y} ${p.x + out.x * r} ${p.y + out.y * r}`;
|
|
2537
|
+
} else d += ` L${p.x} ${p.y}`;
|
|
2538
|
+
}
|
|
2539
|
+
return d;
|
|
2540
|
+
}
|
|
2541
|
+
function along(points, t) {
|
|
2542
|
+
const pts = simplify(points);
|
|
2543
|
+
const lengths = pts.slice(1).map((p, i) => Math.hypot(p.x - pts[i].x, p.y - pts[i].y));
|
|
2544
|
+
let left = lengths.reduce((a, b) => a + b, 0) * t;
|
|
2545
|
+
for (let i = 0; i < lengths.length; i++) {
|
|
2546
|
+
if (left <= lengths[i]) {
|
|
2547
|
+
const f = lengths[i] ? left / lengths[i] : 0;
|
|
2548
|
+
return { x: pts[i].x + (pts[i + 1].x - pts[i].x) * f, y: pts[i].y + (pts[i + 1].y - pts[i].y) * f };
|
|
2549
|
+
}
|
|
2550
|
+
left -= lengths[i];
|
|
2551
|
+
}
|
|
2552
|
+
return pts[pts.length - 1];
|
|
2553
|
+
}
|
|
2554
|
+
var INSET = { none: 0, arrow: 9, dot: 4, circle: 9, bar: 0 };
|
|
2555
|
+
function marker(kind, p, out, color, key) {
|
|
2556
|
+
if (kind === "none") return null;
|
|
2557
|
+
const perp = { x: -out.y, y: out.x };
|
|
2558
|
+
if (kind === "arrow") {
|
|
2559
|
+
const base = add(p, out, 10);
|
|
2560
|
+
const pts = [p, add(base, perp, 5.5), add(base, perp, -5.5)].map((q) => `${q.x},${q.y}`).join(" ");
|
|
2561
|
+
return /* @__PURE__ */ jsx3("polygon", { points: pts, fill: color }, key);
|
|
2562
|
+
}
|
|
2563
|
+
if (kind === "dot") {
|
|
2564
|
+
const c = add(p, out, 4);
|
|
2565
|
+
return /* @__PURE__ */ jsx3("circle", { cx: c.x, cy: c.y, r: 4, fill: color }, key);
|
|
2566
|
+
}
|
|
2567
|
+
if (kind === "circle") {
|
|
2568
|
+
const c = add(p, out, 5);
|
|
2569
|
+
return /* @__PURE__ */ jsx3("circle", { cx: c.x, cy: c.y, r: 4.5, fill: theme.paper, stroke: color, strokeWidth: 2 }, key);
|
|
2570
|
+
}
|
|
2571
|
+
const a = add(p, perp, 7), b = add(p, perp, -7);
|
|
2572
|
+
return /* @__PURE__ */ jsx3("line", { x1: a.x, y1: a.y, x2: b.x, y2: b.y, stroke: color, strokeWidth: 2.5, strokeLinecap: "round" }, key);
|
|
2573
|
+
}
|
|
2574
|
+
var GRAY2 = "#5f6770";
|
|
2575
|
+
function linePath(r, pts, line) {
|
|
2576
|
+
const s0 = pts[0], e0 = pts[pts.length - 1];
|
|
2577
|
+
if (r.lane == null && line === "straight") {
|
|
2578
|
+
return { d: `M${s0.x} ${s0.y} L${e0.x} ${e0.y}`, at: (t) => ({ x: s0.x + (e0.x - s0.x) * t, y: s0.y + (e0.y - s0.y) * t }) };
|
|
2579
|
+
}
|
|
2580
|
+
if (r.lane == null && line === "curved") {
|
|
2581
|
+
const k = Math.max(40, Math.hypot(e0.x - s0.x, e0.y - s0.y) * 0.4);
|
|
2582
|
+
const c1 = add(s0, DIR[r.from], k), c2 = add(e0, DIR[r.to], k);
|
|
2583
|
+
const bez = (a, b, c, e, t) => (1 - t) ** 3 * a + 3 * (1 - t) ** 2 * t * b + 3 * (1 - t) * t ** 2 * c + t ** 3 * e;
|
|
2584
|
+
return { d: `M${s0.x} ${s0.y} C${c1.x} ${c1.y} ${c2.x} ${c2.y} ${e0.x} ${e0.y}`, at: (t) => ({ x: bez(s0.x, c1.x, c2.x, e0.x, t), y: bez(s0.y, c1.y, c2.y, e0.y, t) }) };
|
|
2585
|
+
}
|
|
2586
|
+
return { d: polylinePath(pts, line === "curved" ? 40 : line === "rounded" ? 14 : 0), at: (t) => along(pts, t) };
|
|
2587
|
+
}
|
|
2588
|
+
var flowLabelWidth = (label) => Math.min(220, Math.ceil(label.length * 7.2) + 20);
|
|
2589
|
+
function flowColor(color, palette) {
|
|
2590
|
+
if (color == null) return GRAY2;
|
|
2591
|
+
if (color === "accent") return palette.accent;
|
|
2592
|
+
const s = String(color);
|
|
2593
|
+
return ACCENTS[s] ?? s;
|
|
2594
|
+
}
|
|
2595
|
+
function flowLayer(spec, flows, screens, gap, palette, size) {
|
|
2596
|
+
const shapes = [];
|
|
2597
|
+
const labels = [];
|
|
2598
|
+
const placed = [];
|
|
2599
|
+
const drawn = [];
|
|
2600
|
+
const tips = flows.flatMap(({ ends: ends2 }) => {
|
|
2601
|
+
const r = route(ends2, screens, gap);
|
|
2602
|
+
return [...same(ends2.from, ends2.fromScreen) ? [] : [r.points[0]], ...same(ends2.to, ends2.toScreen) ? [] : [r.points[r.points.length - 1]]];
|
|
2603
|
+
});
|
|
2604
|
+
let lanes = 0, tracks = 0;
|
|
2605
|
+
const routes = flows.map(({ ends: ends2 }) => {
|
|
2606
|
+
let r = route(ends2, screens, gap, lanes, tracks);
|
|
2607
|
+
const clash = (r2) => runs(r2).some((h) => drawn.some((g) => Math.abs(g.y - h.y) < 8 && g.x1 < h.x2 && h.x1 < g.x2)) || [r2.points[0], r2.points[r2.points.length - 1]].some((p) => tips.some((q) => q !== p && Math.hypot(p.x - q.x, p.y - q.y) > 0.5 && Math.hypot(p.x - q.x, p.y - q.y) < 24));
|
|
2608
|
+
for (let k = 1; k <= 4 && clash(r); k++) r = route(ends2, screens, gap, lanes, tracks, 48 * k);
|
|
2609
|
+
drawn.push(...runs(r));
|
|
2610
|
+
tips.push(r.points[0], r.points[r.points.length - 1]);
|
|
2611
|
+
if (r.lane != null) lanes++;
|
|
2612
|
+
if (!same(ends2.fromScreen, ends2.toScreen)) tracks++;
|
|
2613
|
+
return r;
|
|
2614
|
+
});
|
|
2615
|
+
const ends = routes.flatMap((r) => [r.points[0], r.points[r.points.length - 1]]);
|
|
2616
|
+
flows.forEach(({ key }, i) => {
|
|
2617
|
+
const r = routes[i];
|
|
2618
|
+
const p = spec.elements[key].props ?? {};
|
|
2619
|
+
const color = flowColor(p.color, palette);
|
|
2620
|
+
const start = p.start ?? "none", end = p.end ?? "arrow";
|
|
2621
|
+
const pts = [...r.points];
|
|
2622
|
+
pts[0] = add(pts[0], DIR[r.from], INSET[start]);
|
|
2623
|
+
pts[pts.length - 1] = add(pts[pts.length - 1], DIR[r.to], INSET[end]);
|
|
2624
|
+
const { d, at } = linePath(r, pts, p.line ?? "rounded");
|
|
2625
|
+
shapes.push(
|
|
2626
|
+
/* @__PURE__ */ jsx3("path", { d, fill: "none", stroke: color, strokeWidth: 2, strokeLinejoin: "round", strokeLinecap: "round", ...p.dashed ? { strokeDasharray: "7 6" } : {} }, `${key}-line`),
|
|
2627
|
+
marker(start, r.points[0], DIR[r.from], color, `${key}-start`),
|
|
2628
|
+
marker(end, r.points[r.points.length - 1], DIR[r.to], color, `${key}-end`)
|
|
2629
|
+
);
|
|
2630
|
+
if (p.label) {
|
|
2631
|
+
const w = flowLabelWidth(String(p.label));
|
|
2632
|
+
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;
|
|
2633
|
+
let mid = at(0.5), best = covers(mid);
|
|
2634
|
+
for (const t of [0.4, 0.6, 0.3, 0.7, 0.2, 0.8, 0.12, 0.88]) {
|
|
2635
|
+
if (!best) break;
|
|
2636
|
+
const c = at(t), n = covers(c);
|
|
2637
|
+
if (n < best) [mid, best] = [c, n];
|
|
2638
|
+
}
|
|
2639
|
+
const top = mid.y - 12;
|
|
2640
|
+
placed.push({ x: mid.x - w / 2, y: top, w, h: 24 });
|
|
2641
|
+
labels.push(
|
|
2642
|
+
/* @__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`)
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
return /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: 0, left: 0, width: size.width, height: size.height, display: "flex" }, children: [
|
|
2647
|
+
/* @__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 }),
|
|
2648
|
+
labels
|
|
2649
|
+
] }, "__flows");
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2425
2652
|
// src/render.ts
|
|
2426
2653
|
var fontFile = (name) => createRequire(import.meta.url).resolve(`@fontsource/inter/files/${name}`);
|
|
2427
2654
|
var fontCache = null;
|
|
@@ -2485,6 +2712,11 @@ function checkSpec(spec) {
|
|
|
2485
2712
|
}
|
|
2486
2713
|
}
|
|
2487
2714
|
for (const key of Object.keys(el.props ?? {})) {
|
|
2715
|
+
if (key === "id" && takesId(el.type)) {
|
|
2716
|
+
const v = el.props.id;
|
|
2717
|
+
if (typeof v !== "string" || !ID_PATTERN.test(v)) issues.push(`${id}: the id "${v}" should be a word: letters, digits, - and _, starting with a letter`);
|
|
2718
|
+
continue;
|
|
2719
|
+
}
|
|
2488
2720
|
if (key in UNIVERSAL_PROPS) {
|
|
2489
2721
|
if (!takesUniversalProps(el.type)) issues.push(`${id}: ${universalPropMessage(el.type, key)}`);
|
|
2490
2722
|
else {
|
|
@@ -2565,14 +2797,50 @@ function checkSpec(spec) {
|
|
|
2565
2797
|
}
|
|
2566
2798
|
}
|
|
2567
2799
|
}
|
|
2800
|
+
const owners = /* @__PURE__ */ new Map();
|
|
2801
|
+
for (const [key, el] of Object.entries(spec.elements)) {
|
|
2802
|
+
const ref = el.props?.id;
|
|
2803
|
+
if (typeof ref !== "string" || !takesId(el.type)) continue;
|
|
2804
|
+
const other = owners.get(ref.toLowerCase());
|
|
2805
|
+
if (other) issues.push(`${key}: the id #${ref} is also used by ${spec.elements[other.key].type} "${other.key}"${other.id === ref ? "" : ` (as #${other.id}; ids ignore case)`}; ids must be unique`);
|
|
2806
|
+
else owners.set(ref.toLowerCase(), { key, id: ref });
|
|
2807
|
+
}
|
|
2808
|
+
const written = [...owners.values()].map((o2) => o2.id);
|
|
2809
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
2810
|
+
for (const [k, el] of Object.entries(spec.elements)) for (const c of el.children ?? []) parentOf.set(c, k);
|
|
2811
|
+
for (const [key, el] of Object.entries(spec.elements)) {
|
|
2812
|
+
if (el.type !== "Flow") continue;
|
|
2813
|
+
if (!(root.children ?? []).includes(key)) issues.push(`${key}: flow lines go at the board level, after the screens (indented like a screen)`);
|
|
2814
|
+
const p = el.props ?? {};
|
|
2815
|
+
for (const end of ["from", "to"]) {
|
|
2816
|
+
if (typeof p[end] !== "string") continue;
|
|
2817
|
+
const owner = owners.get(p[end].toLowerCase());
|
|
2818
|
+
if (owner) {
|
|
2819
|
+
for (let k = parentOf.get(owner.key); k; k = parentOf.get(k)) {
|
|
2820
|
+
const el2 = spec.elements[k];
|
|
2821
|
+
if (el2?.type === "Accordion" && !el2.props?.open) {
|
|
2822
|
+
issues.push(`${key}: #${owner.id} is inside Accordion "${k}", which is closed, so it isn't drawn; add open to the accordion, or give the accordion the id instead`);
|
|
2823
|
+
break;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
continue;
|
|
2827
|
+
}
|
|
2828
|
+
const close = closeMatches(p[end], written);
|
|
2829
|
+
issues.push(
|
|
2830
|
+
`${key}: no element has the id #${p[end]}` + (close.length ? ` (did you mean ${close.map((c) => "#" + c).join(", ")}?)` : owners.size ? ` (ids on this board: ${written.map((c) => "#" + c).join(", ")})` : "; name an element by writing #name after it")
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
if (typeof p.from === "string" && typeof p.to === "string" && p.from.toLowerCase() === p.to.toLowerCase()) issues.push(`${key}: a flow needs two different ends (both are #${p.from})`);
|
|
2834
|
+
}
|
|
2568
2835
|
return issues;
|
|
2569
2836
|
}
|
|
2570
|
-
function boardLayout(spec) {
|
|
2837
|
+
function boardLayout(spec, opts = {}) {
|
|
2571
2838
|
const board = spec.elements[spec.root];
|
|
2572
2839
|
const p = board.props ?? {};
|
|
2573
|
-
const gap = p.gap ?? BOARD_GAP;
|
|
2574
2840
|
const pad = p.padding ?? BOARD_PADDING;
|
|
2575
|
-
const
|
|
2841
|
+
const laidOut = (board.children ?? []).filter((id) => spec.elements[id]?.type === "Screen" || spec.elements[id]?.type === "Note");
|
|
2842
|
+
const kids = laidOut.map((id) => spec.elements[id]);
|
|
2843
|
+
const gap = p.gap ?? (opts.flows !== false ? Math.max(BOARD_GAP, flowLabelGap(spec, laidOut, p.layout === "grid" ? Math.max(1, p.columns ?? 3) : laidOut.length)) : BOARD_GAP);
|
|
2576
2844
|
const boxes = kids.map((el) => {
|
|
2577
2845
|
const props = el.props ?? {};
|
|
2578
2846
|
if (el.type === "Screen") {
|
|
@@ -2600,10 +2868,33 @@ function boardLayout(spec) {
|
|
|
2600
2868
|
width = Math.max(width, row.reduce((sum, b) => sum + (b?.w ?? 0), 0) + gap * (row.length - 1));
|
|
2601
2869
|
y += Math.max(...row.map((b) => b?.h ?? 0));
|
|
2602
2870
|
}
|
|
2603
|
-
|
|
2871
|
+
const flowCount = (board.children ?? []).filter((id) => spec.elements[id]?.type === "Flow").length;
|
|
2872
|
+
const flowRoom = opts.flows !== false && flowCount ? flowMargin(flowCount) : 0;
|
|
2873
|
+
return { width: Math.ceil(width + pad * 2), height: Math.ceil(y + pad + flowRoom), items, gap };
|
|
2874
|
+
}
|
|
2875
|
+
function flowLabelGap(spec, laidOut, perRow) {
|
|
2876
|
+
const els = spec.elements;
|
|
2877
|
+
const parent = /* @__PURE__ */ new Map();
|
|
2878
|
+
for (const [key, el] of Object.entries(els)) for (const c of el.children ?? []) parent.set(c, key);
|
|
2879
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2880
|
+
for (const [key, el] of Object.entries(els)) if (typeof el.props?.id === "string") ids.set(el.props.id.toLowerCase(), key);
|
|
2881
|
+
const slot = (id) => {
|
|
2882
|
+
let k = ids.get(String(id).toLowerCase());
|
|
2883
|
+
while (k && els[k]?.type !== "Screen") k = parent.get(k);
|
|
2884
|
+
return k ? laidOut.indexOf(k) : -1;
|
|
2885
|
+
};
|
|
2886
|
+
let widest = 0;
|
|
2887
|
+
for (const key of els[spec.root].children ?? []) {
|
|
2888
|
+
const f = els[key];
|
|
2889
|
+
if (f?.type !== "Flow" || !f.props?.label) continue;
|
|
2890
|
+
const a = slot(f.props.from), b = slot(f.props.to);
|
|
2891
|
+
if (a < 0 || b < 0 || Math.abs(a - b) !== 1 || Math.floor(a / perRow) !== Math.floor(b / perRow)) continue;
|
|
2892
|
+
widest = Math.max(widest, flowLabelWidth(String(f.props.label)));
|
|
2893
|
+
}
|
|
2894
|
+
return widest ? widest + 32 : 0;
|
|
2604
2895
|
}
|
|
2605
|
-
function boardSize(spec) {
|
|
2606
|
-
const { width, height } = boardLayout(spec);
|
|
2896
|
+
function boardSize(spec, opts = {}) {
|
|
2897
|
+
const { width, height } = boardLayout(spec, opts);
|
|
2607
2898
|
return { width, height };
|
|
2608
2899
|
}
|
|
2609
2900
|
async function renderWireframeSvg(spec, opts = {}) {
|
|
@@ -2612,20 +2903,64 @@ async function renderWireframeSvg(spec, opts = {}) {
|
|
|
2612
2903
|
const issues = checkSpec(spec);
|
|
2613
2904
|
if (issues.length) throw new SpecError(issues);
|
|
2614
2905
|
}
|
|
2615
|
-
const
|
|
2906
|
+
const flowsOn = opts.flows !== false;
|
|
2907
|
+
const { width, height, gap } = boardLayout(spec, { flows: opts.flows });
|
|
2908
|
+
const board = spec.elements[spec.root];
|
|
2909
|
+
if ((board.props ?? {}).gap == null && gap !== BOARD_GAP)
|
|
2910
|
+
spec = { ...spec, elements: { ...spec.elements, [spec.root]: { ...board, props: { ...board.props, gap } } } };
|
|
2616
2911
|
const palette = paletteFor(spec.elements[spec.root]?.props?.accent);
|
|
2617
2912
|
const registry2 = withPalette(palette);
|
|
2618
2913
|
const draw = async (s, reg) => renderToSvg(s, { registry: reg, includeStandard: false, fonts: await loadFonts(), width, height });
|
|
2619
|
-
const
|
|
2620
|
-
|
|
2621
|
-
|
|
2914
|
+
const flows = flowsOn ? flowEnds(spec) : null;
|
|
2915
|
+
const { anchors, boxes } = await measureAnchors(spec, flows?.measure ?? []);
|
|
2916
|
+
if (!anchors.length && !flows?.list.length) return draw(spec, registry2);
|
|
2917
|
+
const layers = [];
|
|
2918
|
+
if (flows?.list.length) {
|
|
2919
|
+
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));
|
|
2920
|
+
layers.push(flowLayer(spec, resolved, flows.screens, flows.gap, palette, { width, height }));
|
|
2921
|
+
}
|
|
2922
|
+
if (anchors.length) layers.push(overlayLayer(spec, anchors, boxes, palette, registry2.Calendar));
|
|
2923
|
+
return draw(spec, withTopLayer(registry2, layers));
|
|
2924
|
+
}
|
|
2925
|
+
function flowEnds(spec) {
|
|
2926
|
+
const els = spec.elements;
|
|
2927
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2928
|
+
for (const [key, el] of Object.entries(els)) if (typeof el.props?.id === "string") byId.set(el.props.id.toLowerCase(), key);
|
|
2929
|
+
const parent = /* @__PURE__ */ new Map();
|
|
2930
|
+
for (const [key, el] of Object.entries(els)) for (const c of el.children ?? []) parent.set(c, key);
|
|
2931
|
+
const screenOf = (key) => {
|
|
2932
|
+
let k = key;
|
|
2933
|
+
while (k && els[k]?.type !== "Screen") k = parent.get(k);
|
|
2934
|
+
return k;
|
|
2935
|
+
};
|
|
2936
|
+
const { items } = boardLayout(spec);
|
|
2937
|
+
const laidOut = (els[spec.root].children ?? []).filter((k) => els[k]?.type === "Screen" || els[k]?.type === "Note");
|
|
2938
|
+
const screenRects = /* @__PURE__ */ new Map();
|
|
2939
|
+
laidOut.forEach((k, i) => {
|
|
2940
|
+
const it = items[i];
|
|
2941
|
+
if (it && els[k].type === "Screen") screenRects.set(k, { x: it.x, y: it.y + LABEL_H, w: it.width, h: it.height - LABEL_H });
|
|
2942
|
+
});
|
|
2943
|
+
const list = (els[spec.root].children ?? []).filter((k) => els[k]?.type === "Flow").map((key) => {
|
|
2944
|
+
const p = els[key].props ?? {};
|
|
2945
|
+
return { key, from: byId.get(String(p.from).toLowerCase()), to: byId.get(String(p.to).toLowerCase()) };
|
|
2946
|
+
}).filter((f) => !!f.from && !!f.to);
|
|
2947
|
+
const measure = [...new Set(list.flatMap((f) => [f.from, f.to]))].filter((k) => els[k].type !== "Screen");
|
|
2948
|
+
return {
|
|
2949
|
+
list,
|
|
2950
|
+
measure,
|
|
2951
|
+
screens: [...screenRects.values()],
|
|
2952
|
+
screenBox: (key) => screenRects.get(screenOf(key) ?? ""),
|
|
2953
|
+
gap: (els[spec.root].props ?? {}).gap ?? BOARD_GAP,
|
|
2954
|
+
box: (key, boxes) => els[key].type === "Screen" ? screenRects.get(key) : boxes[key]
|
|
2955
|
+
};
|
|
2622
2956
|
}
|
|
2623
|
-
async function measureAnchors(spec) {
|
|
2957
|
+
async function measureAnchors(spec, extra = []) {
|
|
2624
2958
|
const anchors = findAnchors(spec);
|
|
2625
|
-
|
|
2959
|
+
const extras = extra.map((id) => ({ id, screen: id, kind: "tooltip", field: false }));
|
|
2960
|
+
if (!anchors.length && !extras.length) return { anchors, boxes: {} };
|
|
2626
2961
|
const { width, height } = boardSize(spec);
|
|
2627
2962
|
const registry2 = withPalette(paletteFor());
|
|
2628
|
-
const { tagged, colors } = tagForMeasuring(spec, anchors);
|
|
2963
|
+
const { tagged, colors } = tagForMeasuring(spec, [...anchors, ...extras]);
|
|
2629
2964
|
const svg = await renderToSvg(tagged, { registry: withMarkers(registry2), includeStandard: false, fonts: await loadFonts(), width, height });
|
|
2630
2965
|
return { anchors, boxes: readMarkers(svg, colors) };
|
|
2631
2966
|
}
|
|
@@ -2676,7 +3011,7 @@ ${formatIssues(issues)}`);
|
|
|
2676
3011
|
async function renderWireframe(source, opts = {}) {
|
|
2677
3012
|
const { spec, issues } = compileWireframe(source);
|
|
2678
3013
|
if (!spec || issues.length) throw new WireframeError(issues);
|
|
2679
|
-
return opts.format === "png" ? renderWireframePng(spec, { skipValidation: true, scale: opts.scale }) : renderWireframeSvg(spec, { skipValidation: true });
|
|
3014
|
+
return opts.format === "png" ? renderWireframePng(spec, { skipValidation: true, scale: opts.scale, flows: opts.flows }) : renderWireframeSvg(spec, { skipValidation: true, flows: opts.flows });
|
|
2680
3015
|
}
|
|
2681
3016
|
|
|
2682
3017
|
// src/print.ts
|
|
@@ -2719,6 +3054,13 @@ function printWireframeText(spec, indentUnit = " ") {
|
|
|
2719
3054
|
const el = spec.elements[id];
|
|
2720
3055
|
const props = { ...el.props ?? {} };
|
|
2721
3056
|
const parts = [el.type.toLowerCase()];
|
|
3057
|
+
if (el.type === "Flow") {
|
|
3058
|
+
parts.push(`${props.from} -> ${props.to}`);
|
|
3059
|
+
delete props.from;
|
|
3060
|
+
delete props.to;
|
|
3061
|
+
}
|
|
3062
|
+
const elementId = typeof props.id === "string" ? props.id : null;
|
|
3063
|
+
delete props.id;
|
|
2722
3064
|
const enums = bareEnums(el.type);
|
|
2723
3065
|
const bools = booleans(el.type);
|
|
2724
3066
|
const primary = PRIMARY_PROP[el.type];
|
|
@@ -2737,6 +3079,7 @@ function printWireframeText(spec, indentUnit = " ") {
|
|
|
2737
3079
|
if (v === true && bools.has(k)) parts.push(k);
|
|
2738
3080
|
else parts.push(`${k}=${fmt(v)}`);
|
|
2739
3081
|
}
|
|
3082
|
+
if (elementId) parts.push(`#${elementId}`);
|
|
2740
3083
|
out.push(indentUnit.repeat(depth) + parts.join(" "));
|
|
2741
3084
|
for (const c of el.children ?? []) walk(c, depth + 1);
|
|
2742
3085
|
};
|
|
@@ -2839,15 +3182,16 @@ function componentReference() {
|
|
|
2839
3182
|
return lines.join("\n");
|
|
2840
3183
|
}
|
|
2841
3184
|
var RULES = `## Rules
|
|
2842
|
-
1. The top element is a Board. The Board's children are Screens, plus optional Notes beside them.
|
|
3185
|
+
1. The top element is a Board. The Board's children are Screens, plus optional Notes beside them and Flow lines after them (rule 10).
|
|
2843
3186
|
2. Each Screen is one view of the product. Use several Screens to show several views or states.
|
|
2844
3187
|
3. A Screen lays out its children top to bottom. Use Stack (direction row or column) and Grid to arrange content.
|
|
2845
3188
|
4. NavBar is pinned to the top of its Screen and TabBar to the bottom.
|
|
2846
3189
|
5. Modal, Drawer and Toast are overlays. They must be direct children of a Screen.
|
|
2847
3190
|
6. This is a low-fidelity wireframe. Prefer placeholders (Image boxes, Text with lines) over invented copy unless the copy matters.
|
|
2848
3191
|
7. Only use the components and props listed below. All props are optional unless marked required.
|
|
2849
|
-
8. Wireframes are grayscale. The only UI colors: Board accent (one color for primary buttons, checked controls, toggles, active tabs and ghost buttons), Badge tone (success, warning, danger) and
|
|
2850
|
-
9. Any element except Board, Screen and
|
|
3192
|
+
8. Wireframes are grayscale. The only UI colors: Board accent (one color for primary buttons, checked controls, toggles, active tabs and ghost buttons), Badge tone (success, warning, danger), Input error and Flow color. Only add an accent if the request asks for color or a brand.
|
|
3193
|
+
9. Any element except Board, Screen, Note and Flow can take tooltip="text" to show a tooltip next to it. Select (with options), Input type=date, and Button, ListItem or NavBar (with menu) take open to show their list, calendar or menu over the screen.
|
|
3194
|
+
10. To show how screens connect, name elements or screens by writing #name after them (button primary "Sign in" #signin, screen phone "Home" #home), then add board-level lines after the screens: flow signin -> home "Tap Sign in".`;
|
|
2851
3195
|
var TEXT_FORMAT = `## Output format: wireframe text
|
|
2852
3196
|
One element per line. Indent children two spaces under their parent. The first line is the board.
|
|
2853
3197
|
|
package/dist/cli.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
printWireframeText,
|
|
7
7
|
renderWireframe,
|
|
8
8
|
wireframePrompt
|
|
9
|
-
} from "./chunks/chunk-
|
|
9
|
+
} from "./chunks/chunk-HDSKRKDL.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
12
12
|
import { readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
var USAGE = `tsquare \u2014 render wireframe text to SVG or PNG
|
|
15
15
|
|
|
16
|
-
tsquare render <file.tsq|-> [-o out.svg|out.png] [--scale 2]
|
|
16
|
+
tsquare render <file.tsq|-> [-o out.svg|out.png] [--scale 2] [--no-flows]
|
|
17
17
|
tsquare check <file.tsq|-> report problems by line number
|
|
18
18
|
tsquare fmt <file.tsq|-> [-w] print in canonical form (-w rewrites the file; drops comments)
|
|
19
19
|
tsquare prompt print the system prompt for models
|
|
@@ -69,7 +69,7 @@ async function main(argv) {
|
|
|
69
69
|
}
|
|
70
70
|
const out = flag("-o") ?? (file === "-" ? "wireframe.svg" : file.replace(/\.(tsq|wf)$/, "") + ".svg");
|
|
71
71
|
const png = out.endsWith(".png");
|
|
72
|
-
const data = await renderWireframe(source, { format: png ? "png" : "svg", scale: Number(flag("--scale") ?? 1) });
|
|
72
|
+
const data = await renderWireframe(source, { format: png ? "png" : "svg", scale: Number(flag("--scale") ?? 1), flows: !rest.includes("--no-flows") });
|
|
73
73
|
await writeFile(out, data);
|
|
74
74
|
console.log(`\u2713 wrote ${path.relative(process.cwd(), out)}`);
|
|
75
75
|
}
|
package/dist/compile.d.ts
CHANGED
package/dist/components.d.ts
CHANGED
|
@@ -75,8 +75,11 @@ declare function Drawer({ element, children }: Props): React.JSX.Element;
|
|
|
75
75
|
declare function Toast({ element, aboveTabBar }: Props & {
|
|
76
76
|
aboveTabBar?: boolean;
|
|
77
77
|
}): React.JSX.Element;
|
|
78
|
+
/** Flow lines draw nothing in place; flows.tsx draws them over the board. */
|
|
79
|
+
declare function Flow(): null;
|
|
78
80
|
export declare const registry: {
|
|
79
81
|
Board: typeof Board;
|
|
82
|
+
Flow: typeof Flow;
|
|
80
83
|
Screen: typeof Screen;
|
|
81
84
|
Note: typeof Note;
|
|
82
85
|
Stack: typeof Stack;
|