tsquare 0.5.1 → 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 +36 -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 +24 -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
package/dist/flows.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow arrows: `flow <from> -> <to> "label"` lines drawn between elements or screens,
|
|
3
|
+
* on top of the board. Element positions come from the measuring pass (anchors.tsx);
|
|
4
|
+
* screens' boxes come from boardLayout. Routing:
|
|
5
|
+
* - the next screen to the right: right side to left side, turning in the gap between them
|
|
6
|
+
* - skipping screens or going back: through the gaps and a lane under the screens, so lines
|
|
7
|
+
* never cross a screen they don't belong to
|
|
8
|
+
* - within one screen: the sides facing each other
|
|
9
|
+
* Crossings between flows aren't avoided. Two flows at the same height between neighboring screens
|
|
10
|
+
* share a narrow gap: ends and labels move apart where they can, but a crowded gap can still overlap.
|
|
11
|
+
*/
|
|
12
|
+
import { type ReactNode } from "react";
|
|
13
|
+
import type { Spec } from "@json-render/core";
|
|
14
|
+
import { type Palette } from "./colors.js";
|
|
15
|
+
import type { Rect } from "./anchors.js";
|
|
16
|
+
type Pt = {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
};
|
|
20
|
+
type Side = "left" | "right" | "top" | "bottom";
|
|
21
|
+
export declare const flowMargin: (flows: number) => number;
|
|
22
|
+
export interface Route {
|
|
23
|
+
from: Side;
|
|
24
|
+
to: Side;
|
|
25
|
+
points: Pt[];
|
|
26
|
+
lane?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Where a flow's ends are: each end's box and the frame of the screen it's in (a screen end is its own frame). */
|
|
29
|
+
export interface FlowBoxes {
|
|
30
|
+
from: Rect;
|
|
31
|
+
to: Rect;
|
|
32
|
+
fromScreen: Rect;
|
|
33
|
+
toScreen: Rect;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The sides and the right-angle path for a flow. Lines stay off screens they don't belong to:
|
|
37
|
+
* - same screen: right/left or top/bottom between the two elements
|
|
38
|
+
* - a neighboring screen: across the gap between the two screens
|
|
39
|
+
* - skipping over a screen: down the gap beside the source screen, along a lane under all the
|
|
40
|
+
* screens, and up the gap beside the target
|
|
41
|
+
* `lane` and `track` count the flows already using lanes and gaps, so each gets its own.
|
|
42
|
+
*/
|
|
43
|
+
export declare function route(ends: FlowBoxes, screens: Rect[], gap: number, lane?: number, track?: number, nudge?: number): Route;
|
|
44
|
+
/**
|
|
45
|
+
* The SVG path for a routed flow, and a point a fraction along it (for the label). `pts` are the
|
|
46
|
+
* route's points, trimmed for the markers. "straight" and "curved" only cut across when the route
|
|
47
|
+
* stays beside its screens; a route through the lane under the screens keeps its corners (plain for
|
|
48
|
+
* straight, softened for curved), so it never crosses a screen it doesn't connect.
|
|
49
|
+
*/
|
|
50
|
+
export declare function linePath(r: Route, pts: Pt[], line: string): {
|
|
51
|
+
d: string;
|
|
52
|
+
at: (t: number) => Pt;
|
|
53
|
+
};
|
|
54
|
+
/** A label pill's width, estimated from its text (labels are 12px semibold). */
|
|
55
|
+
export declare const flowLabelWidth: (label: string) => number;
|
|
56
|
+
/** A flow's line color: gray, an accent name, a hex color, or the board's accent. */
|
|
57
|
+
export declare function flowColor(color: unknown, palette: Palette): string;
|
|
58
|
+
/** The top layer with every flow: one SVG for the lines and markers, labels as text pills. */
|
|
59
|
+
export declare function flowLayer(spec: Spec, flows: {
|
|
60
|
+
key: string;
|
|
61
|
+
ends: FlowBoxes;
|
|
62
|
+
}[], screens: Rect[], gap: number, palette: Palette, size: {
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
}): ReactNode;
|
|
66
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -20,18 +20,19 @@ import {
|
|
|
20
20
|
upgradeSpec,
|
|
21
21
|
upgradeWireframe,
|
|
22
22
|
wireframePrompt
|
|
23
|
-
} from "./chunks/chunk-
|
|
23
|
+
} from "./chunks/chunk-HDSKRKDL.js";
|
|
24
24
|
|
|
25
25
|
// src/share.ts
|
|
26
26
|
import { deflateRawSync, inflateRawSync } from "node:zlib";
|
|
27
27
|
var MAX_SHARED_TEXT = 64e3;
|
|
28
28
|
function encodeWireframe(text) {
|
|
29
|
-
return "
|
|
29
|
+
return "y" + deflateRawSync(Buffer.from(text, "utf8"), { level: 9 }).toString("base64url");
|
|
30
30
|
}
|
|
31
31
|
function decodeWireframe(data) {
|
|
32
|
-
|
|
32
|
+
const prefix = data.slice(0, 1);
|
|
33
|
+
if (prefix !== "y" && prefix !== "z") throw new Error(`unknown encoding "${prefix}" (expected a "y" or "z" prefix)`);
|
|
33
34
|
const text = inflateRawSync(Buffer.from(data.slice(1), "base64url"), { maxOutputLength: MAX_SHARED_TEXT }).toString("utf8");
|
|
34
|
-
return upgradeWireframe(text, { fromLink:
|
|
35
|
+
return upgradeWireframe(text, { fromLink: prefix === "z" });
|
|
35
36
|
}
|
|
36
37
|
export {
|
|
37
38
|
DEVICES,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* GET /api/prompt the model prompt
|
|
17
17
|
* POST /api/render wireframe text → { issues, svg?, width?, height?, items? } (items: where each screen sits)
|
|
18
18
|
* POST /api/png?scale=2 wireframe text → image/png
|
|
19
|
+
* (both take ?flows=0 to leave out flow arrows)
|
|
19
20
|
*/
|
|
20
21
|
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
21
22
|
/** Problems with the reference examples: a component without an example or group, or an example that doesn't compile. */
|
|
@@ -100,6 +100,13 @@
|
|
|
100
100
|
.btn.icon-only { padding: 5px 7px; }
|
|
101
101
|
.btn.quiet { border-color: transparent; background: none; }
|
|
102
102
|
.btn.quiet:hover { background: var(--fill); }
|
|
103
|
+
.switch { display: inline-flex; align-items: center; gap: 8px; margin-left: 6px; padding: 4px 6px; border: 0; background: none; border-radius: 6px; cursor: pointer; font: inherit; font-size: 13px; font-weight: 500; color: inherit; white-space: nowrap; }
|
|
104
|
+
.switch[hidden] { display: none; }
|
|
105
|
+
.switch:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
|
|
106
|
+
.switch .track { position: relative; width: 30px; height: 18px; border-radius: 9px; background: var(--line-strong); transition: background .15s; }
|
|
107
|
+
.switch .knob { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.25); transition: transform .15s; }
|
|
108
|
+
.switch[aria-checked="true"] .track { background: var(--primary); }
|
|
109
|
+
.switch[aria-checked="true"] .knob { transform: translateX(12px); }
|
|
103
110
|
|
|
104
111
|
main { flex: 1; min-height: 0; display: flex; }
|
|
105
112
|
.view { flex: 1; min-width: 0; display: none; }
|
|
@@ -259,6 +266,7 @@
|
|
|
259
266
|
<button class="btn icon-only" id="zoom-out" type="button" aria-label="Zoom out" title="Zoom out"><span data-icon="zoom-out"></span></button>
|
|
260
267
|
<button class="btn zoom-level" id="zoom-fit" type="button" title="Fit to the window">100%</button>
|
|
261
268
|
<button class="btn icon-only" id="zoom-in" type="button" aria-label="Zoom in" title="Zoom in"><span data-icon="zoom-in"></span></button>
|
|
269
|
+
<button class="switch" id="flows" type="button" role="switch" aria-checked="true" title="Show or hide the flow arrows" hidden><span class="track"><span class="knob"></span></span>Flows</button>
|
|
262
270
|
<span class="grow"></span>
|
|
263
271
|
<div class="menu-wrap">
|
|
264
272
|
<button class="btn" id="share" type="button" aria-haspopup="menu" aria-expanded="false" title="Copy a link" disabled><span data-icon="link"></span><span class="label-wide">Copy link</span></button>
|
package/dist/playground/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
renderWireframeSvg,
|
|
14
14
|
takesUniversalProps,
|
|
15
15
|
wireframePrompt
|
|
16
|
-
} from "../chunks/chunk-
|
|
16
|
+
} from "../chunks/chunk-HDSKRKDL.js";
|
|
17
17
|
|
|
18
18
|
// src/playground/index.ts
|
|
19
19
|
import { createServer } from "node:http";
|
|
@@ -75,7 +75,7 @@ function languageData() {
|
|
|
75
75
|
|
|
76
76
|
// src/playground/reference.ts
|
|
77
77
|
var GROUPS = [
|
|
78
|
-
{ name: "Canvas", components: ["Board", "Screen", "Note"] },
|
|
78
|
+
{ name: "Canvas", components: ["Board", "Screen", "Note", "Flow"] },
|
|
79
79
|
{ name: "Layout", components: ["Stack", "Grid", "Card", "Accordion", "Divider", "Spacer"] },
|
|
80
80
|
{ name: "Content", components: ["Heading", "Text", "Bullets", "Image", "Chart", "Icon", "Avatar", "Badge"] },
|
|
81
81
|
{ name: "Controls", components: ["Button", "Input", "Checkbox", "Radio", "Toggle", "Select", "Slider", "Progress", "Calendar"] },
|
|
@@ -110,6 +110,23 @@ var EXAMPLES = {
|
|
|
110
110
|
note "blue" color=blue width=130
|
|
111
111
|
note "pink" color=pink width=130
|
|
112
112
|
note "green" color=green width=130`,
|
|
113
|
+
Flow: `board "Sign-in flow"
|
|
114
|
+
screen custom "Sign in" width=260 height=300 #signin
|
|
115
|
+
heading "Welcome back" level=3
|
|
116
|
+
input "Email"
|
|
117
|
+
button primary "Sign in" fullWidth #submit
|
|
118
|
+
screen custom "Home" width=260 height=300 #home
|
|
119
|
+
stack row justify=between align=center
|
|
120
|
+
heading "Home" level=3
|
|
121
|
+
button ghost leadingIcon=settings #gear
|
|
122
|
+
text lines=3
|
|
123
|
+
screen custom "Settings" width=260 height=300 #settings
|
|
124
|
+
heading "Settings" level=3
|
|
125
|
+
toggle "Notifications" on
|
|
126
|
+
button secondary "Sign out" #signout
|
|
127
|
+
flow submit -> home "Sign in"
|
|
128
|
+
flow gear -> settings
|
|
129
|
+
flow signout -> signin "Sign out" dashed start=dot`,
|
|
113
130
|
Stack: `board
|
|
114
131
|
screen custom width=560 height=300 padding=0
|
|
115
132
|
stack row grow
|
|
@@ -481,7 +498,7 @@ function reference() {
|
|
|
481
498
|
).then((components) => ({ groups: GROUPS, components }));
|
|
482
499
|
return referenceCache;
|
|
483
500
|
}
|
|
484
|
-
var EXAMPLE_ORDER = ["sign-in", "dashboard", "checkout", "states", "notes-mobile", "notes-web"];
|
|
501
|
+
var EXAMPLE_ORDER = ["sign-in", "dashboard", "checkout", "sign-up-flow", "states", "notes-mobile", "notes-web"];
|
|
485
502
|
var rank = (name) => EXAMPLE_ORDER.indexOf(name) + 1 || EXAMPLE_ORDER.length + 1;
|
|
486
503
|
async function examples() {
|
|
487
504
|
const dir = path.join(packageRoot, "examples");
|
|
@@ -534,15 +551,16 @@ function createPlaygroundHandler() {
|
|
|
534
551
|
}
|
|
535
552
|
if (req.method === "POST" && (url.pathname === "/api/render" || url.pathname === "/api/png")) {
|
|
536
553
|
const { spec, issues } = compileWireframe(await readBody(req));
|
|
554
|
+
const flows = url.searchParams.get("flows") !== "0";
|
|
537
555
|
if (url.pathname === "/api/render") {
|
|
538
556
|
if (!spec || issues.length) return json(res, 200, { issues });
|
|
539
|
-
const { width, height, items } = boardLayout(spec);
|
|
540
|
-
return json(res, 200, { issues, svg: await renderWireframeSvg(spec, { skipValidation: true }), width, height, items });
|
|
557
|
+
const { width, height, items } = boardLayout(spec, { flows });
|
|
558
|
+
return json(res, 200, { issues, svg: await renderWireframeSvg(spec, { skipValidation: true, flows }), width, height, items });
|
|
541
559
|
}
|
|
542
560
|
if (!spec || issues.length) return json(res, 400, { issues });
|
|
543
561
|
const scale = Math.min(4, Math.max(1, Number(url.searchParams.get("scale") ?? 2)));
|
|
544
562
|
res.writeHead(200, { "content-type": "image/png" });
|
|
545
|
-
res.end(await renderWireframePng(spec, { skipValidation: true, scale }));
|
|
563
|
+
res.end(await renderWireframePng(spec, { skipValidation: true, scale, flows }));
|
|
546
564
|
return true;
|
|
547
565
|
}
|
|
548
566
|
return false;
|