tsquare 0.13.1 → 0.14.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 +18 -0
- package/README.md +2 -2
- package/dist/browser/index.js +78 -48
- package/dist/chunks/chunk-NZWKURF4.js +236 -0
- package/dist/chunks/{chunk-K3Y2E2QA.js → chunk-QYGOXTAG.js} +28 -2
- package/dist/cli.js +1 -1
- package/dist/editor.js +10 -1
- package/dist/hit.d.ts +88 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -166
- package/dist/measure.d.ts +2 -74
- package/dist/playground/editor-options.d.ts +2 -0
- package/dist/playground/index.js +12 -7
- package/dist/playground/playground.js +19 -19
- package/dist/playground/viewer.d.ts +51 -6
- package/dist/playground/viewer.js +160 -5
- package/dist/render.d.ts +20 -0
- package/dist/tsquare.min.js +72 -72
- package/package.json +3 -2
- package/dist/chunks/chunk-HVGRKWIV.js +0 -65
package/dist/hit.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What's under a point or a line, from measureElements' entries: plain data in, plain data out, and
|
|
3
|
+
* nothing heavy imported, so the viewer (tsquare/viewer) can hit-test in the browser without the
|
|
4
|
+
* renderer. measure.ts produces the entries and re-exports all of this.
|
|
5
|
+
*/
|
|
6
|
+
import type { ComponentName } from "./catalog.js";
|
|
7
|
+
/** A box on the board, in board pixels: the same coordinates as the rendered SVG. */
|
|
8
|
+
export interface MeasuredBox {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
export interface MeasuredElement {
|
|
15
|
+
/** The element's line in the wireframe text; 0 when `lines` doesn't have it. */
|
|
16
|
+
line: number;
|
|
17
|
+
/** The last line of its block (its children included); `line` when it has no children. */
|
|
18
|
+
endLine: number;
|
|
19
|
+
/** Its #id, when the text gives one. */
|
|
20
|
+
id?: string;
|
|
21
|
+
/** "Button", "Screen", … */
|
|
22
|
+
type: Exclude<ComponentName, "Flow">;
|
|
23
|
+
/** Its main text: a button's label, a screen's name, a heading's text. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** The line of the screen it's on; null for the board, screens and notes beside them. */
|
|
26
|
+
screenLine: number | null;
|
|
27
|
+
/** The line of the element containing it; null for the board. */
|
|
28
|
+
parentLine: number | null;
|
|
29
|
+
/**
|
|
30
|
+
* Where it's drawn: the part that shows, for content cut off by its screen (past the bottom, under
|
|
31
|
+
* a tab bar, or under the status or browser bar) or by a drawer's panel. A screen's box is its frame. Can be 0 wide or 0 tall
|
|
32
|
+
* (a spacer in a row): such an element is listed, but never under a point.
|
|
33
|
+
*/
|
|
34
|
+
box: MeasuredBox;
|
|
35
|
+
/** Screens: the band above the frame with the screen's name. */
|
|
36
|
+
labelBox?: MeasuredBox;
|
|
37
|
+
/** Its open menu, option list or date picker, and its tooltip, in drawing order (a tooltip's and a date picker's size within a few px). */
|
|
38
|
+
overlays?: MeasuredBox[];
|
|
39
|
+
/** Modals and drawers: whether the screen behind is dimmed (on unless the text says no-scrim). */
|
|
40
|
+
scrim?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface MeasuredFlow {
|
|
43
|
+
/** The flow's line in the wireframe text; 0 when `lines` doesn't have it. */
|
|
44
|
+
line: number;
|
|
45
|
+
/** Always `line`: a flow has no children. */
|
|
46
|
+
endLine: number;
|
|
47
|
+
type: "Flow";
|
|
48
|
+
label?: string;
|
|
49
|
+
/** The ids it connects, as written in the flow line. */
|
|
50
|
+
from: string;
|
|
51
|
+
to: string;
|
|
52
|
+
/** The lines of the elements (or screens) it connects. */
|
|
53
|
+
fromLine: number;
|
|
54
|
+
toLine: number;
|
|
55
|
+
/** The drawn line as points, from the tip of one end's marker to the other's; curves and corners are sampled. */
|
|
56
|
+
path: [number, number][];
|
|
57
|
+
/** The label pill. */
|
|
58
|
+
labelBox?: MeasuredBox;
|
|
59
|
+
}
|
|
60
|
+
export type Measured = MeasuredElement | MeasuredFlow;
|
|
61
|
+
export declare const isFlow: (e: Measured) => e is MeasuredFlow;
|
|
62
|
+
/** Dim their screen while open (unless no-scrim): a click on the dimmed part is a click on the screen. */
|
|
63
|
+
export declare const COVERS_SCREEN: Set<string>;
|
|
64
|
+
export declare const OVERLAYS: Set<string>;
|
|
65
|
+
/**
|
|
66
|
+
* Components that hold others (the catalog's components with slots; a test keeps the two equal):
|
|
67
|
+
* from the type, not from the lines, so the order doesn't depend on them.
|
|
68
|
+
*/
|
|
69
|
+
export declare const CONTAINERS: ReadonlySet<string>;
|
|
70
|
+
/**
|
|
71
|
+
* Everything under a point (board pixels), most likely meant first:
|
|
72
|
+
* 1. an open menu, option list, date picker or tooltip: the element it belongs to
|
|
73
|
+
* 2. a flow's label
|
|
74
|
+
* 3. elements that hold no others (buttons, inputs, text, …), topmost first
|
|
75
|
+
* 4. a flow's line, within `tolerance` px of it (default 6; pass more when the board is shown zoomed out)
|
|
76
|
+
* 5. stacks, cards, modals and drawers, then the screen, then the board
|
|
77
|
+
* A modal, drawer or toast hides the screen's content under it. One that dims its screen (modals and
|
|
78
|
+
* drawers, unless no-scrim) hides all of it: on the dimmed part, only the screen and the board.
|
|
79
|
+
*/
|
|
80
|
+
export declare function elementsAt(elements: Measured[], x: number, y: number, opts?: {
|
|
81
|
+
tolerance?: number;
|
|
82
|
+
}): Measured[];
|
|
83
|
+
/**
|
|
84
|
+
* The element on a line and every block around it, innermost first: for a code view that follows
|
|
85
|
+
* the caret. A comment or blank line between an element's children gives that element. A flow's
|
|
86
|
+
* line gives the flow, then the board.
|
|
87
|
+
*/
|
|
88
|
+
export declare function elementsAtLine(elements: Measured[], line: number): Measured[];
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { mcpTools, MCP_INSTRUCTIONS, LINK_KINDS, DEFAULT_LINKS, type LinkKind, w
|
|
|
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
9
|
export { measureElements, elementsAt, elementsAtLine, type Measured, type MeasuredElement, type MeasuredFlow, type MeasuredBox, type MeasureOptions } from "./measure.js";
|
|
10
|
-
export { renderWireframeSvg, renderWireframePng, checkSpec, boardSize, boardLayout, type BoardItem, type RenderWireframeOptions } from "./render.js";
|
|
10
|
+
export { renderWireframeSvg, renderWireframePng, checkSpec, boardSize, boardLayout, measureBoardLayout, type BoardItem, type RenderWireframeOptions } from "./render.js";
|
|
11
11
|
export { parseWireframeText, type TextIssue, type ParseResult } from "./text.js";
|
|
12
12
|
export { catalog, componentDefinitions } from "./catalog.js";
|
|
13
13
|
export { registry } from "./components.js";
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
elementsAt,
|
|
3
|
+
elementsAtLine,
|
|
4
|
+
languageData,
|
|
5
|
+
measureElements
|
|
6
|
+
} from "./chunks/chunk-NZWKURF4.js";
|
|
4
7
|
import {
|
|
5
8
|
DEVICES,
|
|
6
|
-
LABEL_H,
|
|
7
|
-
PRIMARY_PROP,
|
|
8
9
|
RENAMED_PROPS,
|
|
9
|
-
SpecError,
|
|
10
10
|
WireframeError,
|
|
11
11
|
boardLayout,
|
|
12
12
|
boardSize,
|
|
@@ -14,32 +14,21 @@ import {
|
|
|
14
14
|
checkSpec,
|
|
15
15
|
compileWireframe,
|
|
16
16
|
componentDefinitions,
|
|
17
|
-
findAnchors,
|
|
18
|
-
flowEnds,
|
|
19
|
-
flowPolyline,
|
|
20
17
|
formatIssues,
|
|
21
18
|
languageReference,
|
|
22
|
-
|
|
23
|
-
loadFonts,
|
|
24
|
-
overlayBoxes,
|
|
25
|
-
paletteFor,
|
|
19
|
+
measureBoardLayout,
|
|
26
20
|
parseWireframeText,
|
|
27
21
|
printWireframeText,
|
|
28
|
-
readMarkers,
|
|
29
22
|
registry,
|
|
30
23
|
renderWireframe,
|
|
31
24
|
renderWireframePng,
|
|
32
25
|
renderWireframeSvg,
|
|
33
26
|
repairPrompt,
|
|
34
|
-
tagForMeasuring,
|
|
35
27
|
theme,
|
|
36
28
|
upgradeSpec,
|
|
37
29
|
upgradeWireframe,
|
|
38
|
-
wireframePrompt
|
|
39
|
-
|
|
40
|
-
withMarkers,
|
|
41
|
-
withPalette
|
|
42
|
-
} from "./chunks/chunk-K3Y2E2QA.js";
|
|
30
|
+
wireframePrompt
|
|
31
|
+
} from "./chunks/chunk-QYGOXTAG.js";
|
|
43
32
|
|
|
44
33
|
// src/deflate.ts
|
|
45
34
|
import { deflateRawSync, inflateRawSync } from "node:zlib";
|
|
@@ -130,7 +119,7 @@ async function renderWireframeTool(args) {
|
|
|
130
119
|
const { spec, issues } = compileWireframe(args.text);
|
|
131
120
|
if (!spec || issues.length) return problems(issues);
|
|
132
121
|
const flows = args.flows !== false;
|
|
133
|
-
const { width, height } =
|
|
122
|
+
const { width, height } = await measureBoardLayout(spec, { flows });
|
|
134
123
|
const scale = Math.min(1, MODEL_IMAGE_MAX / Math.max(width, height));
|
|
135
124
|
const png = await renderWireframePng(spec, { skipValidation: true, flows, scale });
|
|
136
125
|
const shown = scale < 1 ? `, shown at ${Math.round(width * scale)}\xD7${Math.round(height * scale)}` : "";
|
|
@@ -216,7 +205,7 @@ async function renderBlock(doc, source) {
|
|
|
216
205
|
if (!spec || issues.length) return problems2(doc, issues.map((i) => `line ${i.line}: ${i.message}`));
|
|
217
206
|
try {
|
|
218
207
|
const svg = await renderWireframeSvg(spec, { skipValidation: true });
|
|
219
|
-
const { width, height } =
|
|
208
|
+
const { width, height } = await measureBoardLayout(spec);
|
|
220
209
|
const title = spec.elements[spec.root]?.props?.title;
|
|
221
210
|
const figure = doc.createElement("figure");
|
|
222
211
|
figure.className = "tsquare-board";
|
|
@@ -255,151 +244,6 @@ function addStyles(doc) {
|
|
|
255
244
|
style.textContent = STYLES;
|
|
256
245
|
doc.head.append(style);
|
|
257
246
|
}
|
|
258
|
-
|
|
259
|
-
// src/measure.ts
|
|
260
|
-
import { renderToSvg } from "@json-render/image/render";
|
|
261
|
-
var isFlow = (e) => e.type === "Flow";
|
|
262
|
-
var COVERS_SCREEN = /* @__PURE__ */ new Set(["Modal", "Drawer"]);
|
|
263
|
-
var OVERLAYS = /* @__PURE__ */ new Set(["Modal", "Drawer", "Toast"]);
|
|
264
|
-
var CONTAINERS = new Set(Object.entries(componentDefinitions).filter(([, d]) => d.slots.length).map(([name]) => name));
|
|
265
|
-
var BARS = /* @__PURE__ */ new Set(["NavBar", "TabBar"]);
|
|
266
|
-
var toBox = (r) => ({ x: round(r.x), y: round(r.y), width: round(r.w), height: round(r.h) });
|
|
267
|
-
var round = (n) => Math.round(n * 100) / 100;
|
|
268
|
-
function clip(r, to) {
|
|
269
|
-
if (!to) return r;
|
|
270
|
-
const x = Math.max(r.x, to.x), y = Math.max(r.y, to.y);
|
|
271
|
-
const w = Math.min(r.x + r.w, to.x + to.w) - x, h = Math.min(r.y + r.h, to.y + to.h) - y;
|
|
272
|
-
return w >= 0 && h >= 0 && (w > 0 || r.w === 0) && (h > 0 || r.h === 0) ? { x, y, w, h } : null;
|
|
273
|
-
}
|
|
274
|
-
async function measureElements(spec, lines, opts = {}) {
|
|
275
|
-
upgradeSpec(spec);
|
|
276
|
-
if (!opts.skipValidation) {
|
|
277
|
-
const issues = checkSpec(spec);
|
|
278
|
-
if (issues.length) throw new SpecError(issues);
|
|
279
|
-
}
|
|
280
|
-
const { width, height } = boardLayout(spec, { flows: opts.flows });
|
|
281
|
-
spec = withBoardGap(spec, opts.flows);
|
|
282
|
-
const els = spec.elements;
|
|
283
|
-
const anchors = findAnchors(spec);
|
|
284
|
-
const every = Object.keys(els).filter((k) => !["Board", "Screen", "Flow"].includes(els[k].type)).map((id) => ({ id, screen: id, kind: "tooltip", field: false }));
|
|
285
|
-
const screens = Object.keys(els).filter((k) => els[k].type === "Screen");
|
|
286
|
-
const { tagged, colors } = tagForMeasuring(spec, [...anchors, ...every], screens);
|
|
287
|
-
const svg = await renderToSvg(tagged, { registry: withMarkers(withPalette(paletteFor())), includeStandard: false, fonts: await loadFonts(), width, height });
|
|
288
|
-
const boxes = readMarkers(svg, colors);
|
|
289
|
-
const { items } = boardLayout(spec, { flows: opts.flows });
|
|
290
|
-
const laidOut = (els[spec.root].children ?? []).filter((k) => els[k]?.type === "Screen" || els[k]?.type === "Note");
|
|
291
|
-
const frames = /* @__PURE__ */ new Map(), bands = /* @__PURE__ */ new Map();
|
|
292
|
-
laidOut.forEach((k, i) => {
|
|
293
|
-
const it = items[i];
|
|
294
|
-
if (!it || els[k].type !== "Screen") return;
|
|
295
|
-
frames.set(k, { x: it.x, y: it.y + LABEL_H, w: it.width, h: it.height - LABEL_H });
|
|
296
|
-
bands.set(k, { x: it.x, y: it.y, w: it.width, h: LABEL_H });
|
|
297
|
-
});
|
|
298
|
-
const overlays = /* @__PURE__ */ new Map();
|
|
299
|
-
for (const o of overlayBoxes(spec, anchors, boxes)) overlays.set(o.id, [...overlays.get(o.id) ?? [], o.box]);
|
|
300
|
-
const lineOf = (k) => k == null ? null : lines[k] ?? 0;
|
|
301
|
-
const endLine = (k) => Math.max(lines[k] ?? 0, ...(els[k].children ?? []).filter((c) => els[c]).map(endLine));
|
|
302
|
-
const labelOf = (k) => {
|
|
303
|
-
const p = els[k].props ?? {};
|
|
304
|
-
const v = p[PRIMARY_PROP[els[k].type] ?? ""];
|
|
305
|
-
return typeof v === "string" ? v : void 0;
|
|
306
|
-
};
|
|
307
|
-
const out = [];
|
|
308
|
-
const visit = (k, parent, screen, cut) => {
|
|
309
|
-
const el = els[k];
|
|
310
|
-
if (!el || el.type === "Flow") return;
|
|
311
|
-
const p = el.props ?? {};
|
|
312
|
-
const base = { line: lineOf(k), endLine: endLine(k), ...typeof p.id === "string" ? { id: p.id } : {}, type: el.type, ...labelOf(k) != null ? { label: labelOf(k) } : {}, screenLine: lineOf(screen), parentLine: lineOf(parent) };
|
|
313
|
-
let entry = null;
|
|
314
|
-
if (el.type === "Board") entry = { ...base, box: { x: 0, y: 0, width, height } };
|
|
315
|
-
else if (el.type === "Screen") {
|
|
316
|
-
const frame = frames.get(k);
|
|
317
|
-
if (frame) entry = { ...base, box: toBox(frame), labelBox: toBox(bands.get(k)) };
|
|
318
|
-
} else {
|
|
319
|
-
const r = boxes[k];
|
|
320
|
-
const seen = r && clip(r, cut);
|
|
321
|
-
if (seen) {
|
|
322
|
-
const view = screen ? boxes[screen] ?? frames.get(screen) : void 0;
|
|
323
|
-
const os = (overlays.get(k) ?? []).map((o) => clip(o, view)).filter((o) => !!o && o.w > 0 && o.h > 0);
|
|
324
|
-
entry = { ...base, box: toBox(seen), ...os.length ? { overlays: os.map(toBox) } : {}, ...COVERS_SCREEN.has(el.type) ? { scrim: p.scrim ?? true } : {} };
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (entry) out.push(entry);
|
|
328
|
-
const inScreen = el.type === "Screen" ? k : screen;
|
|
329
|
-
const kids = el.children ?? [];
|
|
330
|
-
if (el.type === "Screen") {
|
|
331
|
-
const frame = frames.get(k) ?? null;
|
|
332
|
-
const body = clip(boxes[`${k}#body`] ?? frame, frame);
|
|
333
|
-
const over = (c) => OVERLAYS.has(els[c]?.type);
|
|
334
|
-
for (const c of [...kids.filter((c2) => !over(c2)), ...kids.filter(over)]) visit(c, k, k, BARS.has(els[c]?.type) || over(c) ? frame : body);
|
|
335
|
-
} else {
|
|
336
|
-
const inner = el.type === "Drawer" && boxes[k] ? clip(boxes[k], cut) : cut;
|
|
337
|
-
for (const c of kids) visit(c, k, inScreen, inner);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
visit(spec.root, null, null, null);
|
|
341
|
-
if (opts.flows !== false) {
|
|
342
|
-
const flows = flowEnds(spec);
|
|
343
|
-
const resolved = flows.resolve(boxes);
|
|
344
|
-
const ends = new Map(flows.list.map((f) => [f.key, f]));
|
|
345
|
-
layoutFlows(spec, resolved, flows.screens, flows.gap, paletteFor()).forEach((f) => {
|
|
346
|
-
const p = els[f.key].props ?? {};
|
|
347
|
-
const e = ends.get(f.key);
|
|
348
|
-
out.push({
|
|
349
|
-
line: lineOf(f.key),
|
|
350
|
-
endLine: lineOf(f.key),
|
|
351
|
-
type: "Flow",
|
|
352
|
-
...f.label ? { label: f.label.text } : {},
|
|
353
|
-
from: String(p.from),
|
|
354
|
-
to: String(p.to),
|
|
355
|
-
fromLine: lineOf(e.from),
|
|
356
|
-
toLine: lineOf(e.to),
|
|
357
|
-
path: flowPolyline(f).map((q) => [round(q.x), round(q.y)]),
|
|
358
|
-
...f.label ? { labelBox: toBox(f.label) } : {}
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
return out;
|
|
363
|
-
}
|
|
364
|
-
var inside = (b, x, y) => !!b && b.width > 0 && b.height > 0 && x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height;
|
|
365
|
-
var center = (b) => [b.x + b.width / 2, b.y + b.height / 2];
|
|
366
|
-
function distanceToPath(path, x, y) {
|
|
367
|
-
let best = Infinity;
|
|
368
|
-
for (let i = 1; i < path.length; i++) {
|
|
369
|
-
const [ax, ay] = path[i - 1], [bx, by] = path[i];
|
|
370
|
-
const dx = bx - ax, dy = by - ay, len = dx * dx + dy * dy;
|
|
371
|
-
const t = len ? Math.max(0, Math.min(1, ((x - ax) * dx + (y - ay) * dy) / len)) : 0;
|
|
372
|
-
best = Math.min(best, Math.hypot(x - ax - t * dx, y - ay - t * dy));
|
|
373
|
-
}
|
|
374
|
-
return best;
|
|
375
|
-
}
|
|
376
|
-
var isContainer = (e) => CONTAINERS.has(e.type);
|
|
377
|
-
function elementsAt(elements, x, y, opts = {}) {
|
|
378
|
-
const tolerance = opts.tolerance ?? 6;
|
|
379
|
-
const order = new Map(elements.map((e, i) => [e, i]));
|
|
380
|
-
const topFirst = (a, b) => order.get(b) - order.get(a);
|
|
381
|
-
const covered = /* @__PURE__ */ new Set();
|
|
382
|
-
const screens = elements.filter((e) => e.type === "Screen");
|
|
383
|
-
const screenAround = (b) => screens.find((s) => inside(s.box, ...center(b)));
|
|
384
|
-
for (const [i, m] of elements.entries()) {
|
|
385
|
-
if (isFlow(m) || !OVERLAYS.has(m.type)) continue;
|
|
386
|
-
const screen = screenAround(m.box);
|
|
387
|
-
if (!screen || !inside(m.box, x, y) && !(m.scrim && inside(screen.box, x, y))) continue;
|
|
388
|
-
for (const e of elements.slice(0, i)) if (!isFlow(e) && e.type !== "Screen" && e.type !== "Board" && screenAround(e.box) === screen) covered.add(e);
|
|
389
|
-
}
|
|
390
|
-
const owners = [], labels = [], leaves = [], lines = [], containers = [];
|
|
391
|
-
for (const e of elements) {
|
|
392
|
-
if (isFlow(e)) {
|
|
393
|
-
if (inside(e.labelBox, x, y)) labels.push(e);
|
|
394
|
-
else if (distanceToPath(e.path, x, y) <= tolerance) lines.push(e);
|
|
395
|
-
} else if (e.overlays?.some((o) => inside(o, x, y))) owners.push(e);
|
|
396
|
-
else if (!covered.has(e) && (inside(e.box, x, y) || inside(e.labelBox, x, y))) (isContainer(e) ? containers : leaves).push(e);
|
|
397
|
-
}
|
|
398
|
-
return [owners, labels, leaves, lines, containers].flatMap((g) => g.sort(topFirst));
|
|
399
|
-
}
|
|
400
|
-
function elementsAtLine(elements, line) {
|
|
401
|
-
return elements.filter((e) => e.line > 0 && e.line <= line && line <= e.endLine).sort((a, b) => b.line - a.line);
|
|
402
|
-
}
|
|
403
247
|
export {
|
|
404
248
|
DEFAULT_LINKS,
|
|
405
249
|
DEFAULT_SELECTOR,
|
|
@@ -424,6 +268,7 @@ export {
|
|
|
424
268
|
printWireframeText as formatWireframe,
|
|
425
269
|
languageData,
|
|
426
270
|
mcpTools,
|
|
271
|
+
measureBoardLayout,
|
|
427
272
|
measureElements,
|
|
428
273
|
parseWireframeText,
|
|
429
274
|
registry,
|
package/dist/measure.d.ts
CHANGED
|
@@ -8,61 +8,8 @@
|
|
|
8
8
|
* so measure once per text and look up as often as needed (they also run on the JSON in a browser).
|
|
9
9
|
*/
|
|
10
10
|
import type { Spec } from "@json-render/core";
|
|
11
|
-
import { type
|
|
12
|
-
|
|
13
|
-
export interface MeasuredBox {
|
|
14
|
-
x: number;
|
|
15
|
-
y: number;
|
|
16
|
-
width: number;
|
|
17
|
-
height: number;
|
|
18
|
-
}
|
|
19
|
-
export interface MeasuredElement {
|
|
20
|
-
/** The element's line in the wireframe text; 0 when `lines` doesn't have it. */
|
|
21
|
-
line: number;
|
|
22
|
-
/** The last line of its block (its children included); `line` when it has no children. */
|
|
23
|
-
endLine: number;
|
|
24
|
-
/** Its #id, when the text gives one. */
|
|
25
|
-
id?: string;
|
|
26
|
-
/** "Button", "Screen", … */
|
|
27
|
-
type: Exclude<ComponentName, "Flow">;
|
|
28
|
-
/** Its main text: a button's label, a screen's name, a heading's text. */
|
|
29
|
-
label?: string;
|
|
30
|
-
/** The line of the screen it's on; null for the board, screens and notes beside them. */
|
|
31
|
-
screenLine: number | null;
|
|
32
|
-
/** The line of the element containing it; null for the board. */
|
|
33
|
-
parentLine: number | null;
|
|
34
|
-
/**
|
|
35
|
-
* Where it's drawn: the part that shows, for content cut off by its screen (past the bottom, or
|
|
36
|
-
* under a tab bar) or by a drawer's panel. A screen's box is its frame. Can be 0 wide or 0 tall
|
|
37
|
-
* (a spacer in a row): such an element is listed, but never under a point.
|
|
38
|
-
*/
|
|
39
|
-
box: MeasuredBox;
|
|
40
|
-
/** Screens: the band above the frame with the screen's name. */
|
|
41
|
-
labelBox?: MeasuredBox;
|
|
42
|
-
/** Its open menu, option list or date picker, and its tooltip, in drawing order (a tooltip's and a date picker's size within a few px). */
|
|
43
|
-
overlays?: MeasuredBox[];
|
|
44
|
-
/** Modals and drawers: whether the screen behind is dimmed (on unless the text says no-scrim). */
|
|
45
|
-
scrim?: boolean;
|
|
46
|
-
}
|
|
47
|
-
export interface MeasuredFlow {
|
|
48
|
-
/** The flow's line in the wireframe text; 0 when `lines` doesn't have it. */
|
|
49
|
-
line: number;
|
|
50
|
-
/** Always `line`: a flow has no children. */
|
|
51
|
-
endLine: number;
|
|
52
|
-
type: "Flow";
|
|
53
|
-
label?: string;
|
|
54
|
-
/** The ids it connects, as written in the flow line. */
|
|
55
|
-
from: string;
|
|
56
|
-
to: string;
|
|
57
|
-
/** The lines of the elements (or screens) it connects. */
|
|
58
|
-
fromLine: number;
|
|
59
|
-
toLine: number;
|
|
60
|
-
/** The drawn line as points, from the tip of one end's marker to the other's; curves and corners are sampled. */
|
|
61
|
-
path: [number, number][];
|
|
62
|
-
/** The label pill. */
|
|
63
|
-
labelBox?: MeasuredBox;
|
|
64
|
-
}
|
|
65
|
-
export type Measured = MeasuredElement | MeasuredFlow;
|
|
11
|
+
import { type Measured, type MeasuredElement } from "./hit.js";
|
|
12
|
+
export { elementsAt, elementsAtLine, type Measured, type MeasuredBox, type MeasuredElement, type MeasuredFlow } from "./hit.js";
|
|
66
13
|
export interface MeasureOptions {
|
|
67
14
|
/** As passed to renderWireframeSvg (default true): flow labels can widen the gaps between screens, so the boxes only match a render with the same setting. */
|
|
68
15
|
flows?: boolean;
|
|
@@ -76,22 +23,3 @@ export interface MeasureOptions {
|
|
|
76
23
|
* Elements that aren't drawn (content entirely cut off, children of a closed accordion) are left out.
|
|
77
24
|
*/
|
|
78
25
|
export declare function measureElements(spec: Spec, lines: Record<string, number>, opts?: MeasureOptions): Promise<[MeasuredElement, ...Measured[]]>;
|
|
79
|
-
/**
|
|
80
|
-
* Everything under a point (board pixels), most likely meant first:
|
|
81
|
-
* 1. an open menu, option list, date picker or tooltip: the element it belongs to
|
|
82
|
-
* 2. a flow's label
|
|
83
|
-
* 3. elements that hold no others (buttons, inputs, text, …), topmost first
|
|
84
|
-
* 4. a flow's line, within `tolerance` px of it (default 6; pass more when the board is shown zoomed out)
|
|
85
|
-
* 5. stacks, cards, modals and drawers, then the screen, then the board
|
|
86
|
-
* A modal, drawer or toast hides the screen's content under it. One that dims its screen (modals and
|
|
87
|
-
* drawers, unless no-scrim) hides all of it: on the dimmed part, only the screen and the board.
|
|
88
|
-
*/
|
|
89
|
-
export declare function elementsAt(elements: Measured[], x: number, y: number, opts?: {
|
|
90
|
-
tolerance?: number;
|
|
91
|
-
}): Measured[];
|
|
92
|
-
/**
|
|
93
|
-
* The element on a line and every block around it, innermost first: for a code view that follows
|
|
94
|
-
* the caret. A comment or blank line between an element's children gives that element. A flow's
|
|
95
|
-
* line gives the flow, then the board.
|
|
96
|
-
*/
|
|
97
|
-
export declare function elementsAtLine(elements: Measured[], line: number): Measured[];
|
|
@@ -6,6 +6,8 @@ export interface EditorOptions {
|
|
|
6
6
|
data?: LanguageData;
|
|
7
7
|
/** Called with the whole text after every change. */
|
|
8
8
|
onChange?: (text: string) => void;
|
|
9
|
+
/** Called with the caret's line (1 = the first) when it moves to another line. */
|
|
10
|
+
onCursorLine?: (line: number) => void;
|
|
9
11
|
/** Line numbers in a gutter. Default true. */
|
|
10
12
|
lineNumbers?: boolean;
|
|
11
13
|
/** Wrap long lines instead of scrolling sideways; continuation rows are indented under their line. Default false. */
|
package/dist/playground/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
languageData
|
|
3
|
-
|
|
2
|
+
languageData,
|
|
3
|
+
measureElements
|
|
4
|
+
} from "../chunks/chunk-NZWKURF4.js";
|
|
4
5
|
import {
|
|
5
|
-
boardLayout,
|
|
6
6
|
compileWireframe,
|
|
7
7
|
componentDocs,
|
|
8
8
|
formatIssues,
|
|
9
|
+
measureBoardLayout,
|
|
9
10
|
renderWireframePng,
|
|
10
11
|
renderWireframeSvg,
|
|
11
12
|
wireframePrompt
|
|
12
|
-
} from "../chunks/chunk-
|
|
13
|
+
} from "../chunks/chunk-QYGOXTAG.js";
|
|
13
14
|
|
|
14
15
|
// src/playground/index.ts
|
|
15
16
|
import { createServer } from "node:http";
|
|
@@ -494,12 +495,16 @@ function createPlaygroundHandler() {
|
|
|
494
495
|
return true;
|
|
495
496
|
}
|
|
496
497
|
if (req.method === "POST" && (url.pathname === "/api/render" || url.pathname === "/api/png")) {
|
|
497
|
-
const { spec, issues } = compileWireframe(await readBody(req));
|
|
498
|
+
const { spec, issues, lines } = compileWireframe(await readBody(req));
|
|
498
499
|
const flows = url.searchParams.get("flows") !== "0";
|
|
499
500
|
if (url.pathname === "/api/render") {
|
|
500
501
|
if (!spec || issues.length) return json(res, 200, { issues });
|
|
501
|
-
const { width, height, items } =
|
|
502
|
-
|
|
502
|
+
const [svg, { width, height, items }, elements] = await Promise.all([
|
|
503
|
+
renderWireframeSvg(spec, { skipValidation: true, flows }),
|
|
504
|
+
measureBoardLayout(spec, { flows }),
|
|
505
|
+
url.searchParams.get("elements") === "1" ? measureElements(spec, lines, { skipValidation: true, flows }) : void 0
|
|
506
|
+
]);
|
|
507
|
+
return json(res, 200, { issues, svg, width, height, items, ...elements ? { elements } : {} });
|
|
503
508
|
}
|
|
504
509
|
if (!spec || issues.length) return json(res, 400, { issues });
|
|
505
510
|
const scale = Math.min(4, Math.max(1, Number(url.searchParams.get("scale") ?? 2)));
|