tsquare 0.13.2 → 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 +9 -0
- package/README.md +2 -2
- package/dist/browser/index.js +43 -41
- package/dist/chunks/chunk-NZWKURF4.js +236 -0
- package/dist/editor.js +10 -1
- package/dist/hit.d.ts +88 -0
- package/dist/index.js +6 -167
- package/dist/measure.d.ts +2 -74
- package/dist/playground/editor-options.d.ts +2 -0
- package/dist/playground/index.js +10 -5
- package/dist/playground/playground.js +19 -19
- package/dist/playground/viewer.d.ts +51 -6
- package/dist/playground/viewer.js +160 -5
- package/dist/tsquare.min.js +45 -45
- package/package.json +2 -2
- package/dist/chunks/chunk-W5RDSAXT.js +0 -65
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
The language and the renderer haven't changed: every wireframe renders exactly as in 0.13.2.
|
|
6
|
+
|
|
7
|
+
- **Click elements in the viewer.** `tsquare/viewer` takes a board's `measureElements` entries (`setElements`) and reports clicks and hovers on elements (`onElementClick(hit, hits, event)`, `onElementHover(hit, hits)`), with `hits` in `elementsAt`'s order. A click is a press that hardly moves (dragging still pans), and a flow's line counts within 8 screen pixels at any zoom. `highlight(entries, { kind: "selected" | "hover" })` outlines elements and flows inside the zoomed board (`--tsquare-highlight`), `zoomTo(entry)` fits one, `layer` is an element over the board in board pixels for the host's own marks, and `toBoard(clientX, clientY)` turns a point on the page into board pixels. The viewer only hit-tests: it never renders or measures, so the host measures where it renders and hands the entries over. Without entries it works as before. It's 4.6 KB compressed (2.3 KB before). See [Element positions](docs/element-positions.md#in-the-viewer).
|
|
8
|
+
- **The playground:** click an element on the board and the editor selects its line; move the caret and its element is outlined on the board; hovering outlines what's under the pointer. `/api/render?elements=1` returns the entries with the board.
|
|
9
|
+
- **`tsquare/editor`: `onCursorLine(line)`**, called when the caret moves to another line.
|
|
10
|
+
- `elementsAt` and `elementsAtLine` live in their own small module (`hit.ts`), so the viewer can use them without the renderer. The main package exports them as before.
|
|
11
|
+
|
|
3
12
|
## 0.13.2
|
|
4
13
|
|
|
5
14
|
The language hasn't changed. Boards without notes on the board render exactly as in 0.13.1, and so do the 438 example and eval boards; boards where a note's height was misjudged are fixed.
|
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ const { issues } = compileWireframe(source); // problems by line, empt
|
|
|
151
151
|
const png = await renderWireframe(source, { format: "png", scale: 2 });
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
`tsquare/playground` exports the playground's request handler, for hosting it inside your own Node server. `tsquare/viewer` is the playground's zoomable board (zoom, pan, pinch, a chip per screen) for your own page: pass it the elements, then `show()` the SVG from `renderWireframeSvg` with the size and screens from `measureBoardLayout` (exact; `boardLayout` is a synchronous estimate that can be off by a line or more on boards with notes). It sets the styles its zoom math needs itself; give the stage a size, and optionally style the classes it toggles on the stage: `panning` (while dragging), `animate` (while fitting or zooming to a screen, e.g. `.animate > * { transition: transform .25s }`) and `stale` (from `setStale`, e.g. `.stale img { opacity: .35 }`). Call `destroy()` when you remove it. `languageData()` lists every component, prop, option word, icon and accent, for editor highlighting and autocomplete. In a browser bundle (esbuild, Vite, webpack) the same imports render SVG with no setup; PNG and share links need Node. On a page without a build step, `<script src="https://cdn.jsdelivr.net/npm/tsquare@0.13/dist/tsquare.min.js"></script>` draws every ```` ```tsquare ```` code block on the page (see [Embedding](docs/embedding.md#pages-written-in-markdown)). See [Using it with AI](docs/ai.md) for the prompt and the repair loop. `measureElements` gives each element's box on the board and its line in the text, and `elementsAt` what's under a click, for making a board clickable (see [Element positions](docs/element-positions.md)).
|
|
154
|
+
`tsquare/playground` exports the playground's request handler, for hosting it inside your own Node server. `tsquare/viewer` is the playground's zoomable board (zoom, pan, pinch, a chip per screen) for your own page: pass it the elements, then `show()` the SVG from `renderWireframeSvg` with the size and screens from `measureBoardLayout` (exact; `boardLayout` is a synchronous estimate that can be off by a line or more on boards with notes). It sets the styles its zoom math needs itself; give the stage a size, and optionally style the classes it toggles on the stage: `panning` (while dragging), `animate` (while fitting or zooming to a screen, e.g. `.animate > * { transition: transform .25s }`) and `stale` (from `setStale`, e.g. `.stale img { opacity: .35 }`). Call `destroy()` when you remove it. Give it the board's `measureElements` entries (`setElements`) and it reports clicks and hovers on elements, outlines them (`highlight`) and zooms to them (`zoomTo`): see [Element positions](docs/element-positions.md#in-the-viewer). `languageData()` lists every component, prop, option word, icon and accent, for editor highlighting and autocomplete. In a browser bundle (esbuild, Vite, webpack) the same imports render SVG with no setup; PNG and share links need Node. On a page without a build step, `<script src="https://cdn.jsdelivr.net/npm/tsquare@0.13/dist/tsquare.min.js"></script>` draws every ```` ```tsquare ```` code block on the page (see [Embedding](docs/embedding.md#pages-written-in-markdown)). See [Using it with AI](docs/ai.md) for the prompt and the repair loop. `measureElements` gives each element's box on the board and its line in the text, and `elementsAt` what's under a click, for making a board clickable (see [Element positions](docs/element-positions.md)).
|
|
155
155
|
|
|
156
156
|
### The editor: `tsquare/editor`
|
|
157
157
|
|
|
@@ -168,7 +168,7 @@ const editor = createEditor(document.getElementById("code")!, {
|
|
|
168
168
|
});
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
Give the element a height; the editor fills it. Options: `doc`, `data`, `onChange`, `lineNumbers` (default `true`), `lineWrapping` (default `false`: wrap long lines instead of scrolling sideways, continuation rows indented 4 spaces past their line's indentation, so they don't look like a child element) and `ariaLabel` (default "Wireframe text"). The editor has `getText()`, `setText(text)` (one undoable step), `setIssues(issues)`, `goToLine(n)`, `focus()`, `dom` and `destroy()`.
|
|
171
|
+
Give the element a height; the editor fills it. Options: `doc`, `data`, `onChange`, `onCursorLine` (the caret's line, when it moves to another), `lineNumbers` (default `true`), `lineWrapping` (default `false`: wrap long lines instead of scrolling sideways, continuation rows indented 4 spaces past their line's indentation, so they don't look like a child element) and `ariaLabel` (default "Wireframe text"). The editor has `getText()`, `setText(text)` (one undoable step), `setIssues(issues)`, `goToLine(n)`, `focus()`, `dom` and `destroy()`.
|
|
172
172
|
|
|
173
173
|
Its colors are CSS variables, light or dark by the page's `color-scheme`, defaulting to the playground's:
|
|
174
174
|
|
package/dist/browser/index.js
CHANGED
|
@@ -23570,11 +23570,52 @@ function addStyles(doc) {
|
|
|
23570
23570
|
doc.head.append(style);
|
|
23571
23571
|
}
|
|
23572
23572
|
|
|
23573
|
-
// src/
|
|
23573
|
+
// src/hit.ts
|
|
23574
23574
|
var isFlow = (e) => e.type === "Flow";
|
|
23575
23575
|
var COVERS_SCREEN = /* @__PURE__ */ new Set(["Modal", "Drawer"]);
|
|
23576
23576
|
var OVERLAYS = /* @__PURE__ */ new Set(["Modal", "Drawer", "Toast"]);
|
|
23577
|
-
var CONTAINERS = new Set(
|
|
23577
|
+
var CONTAINERS = /* @__PURE__ */ new Set(["Board", "Screen", "Stack", "Grid", "Card", "Accordion", "List", "Modal", "Drawer"]);
|
|
23578
|
+
var inside = (b, x3, y) => !!b && b.width > 0 && b.height > 0 && x3 >= b.x && x3 <= b.x + b.width && y >= b.y && y <= b.y + b.height;
|
|
23579
|
+
var center = (b) => [b.x + b.width / 2, b.y + b.height / 2];
|
|
23580
|
+
function distanceToPath(path, x3, y) {
|
|
23581
|
+
let best = Infinity;
|
|
23582
|
+
for (let i2 = 1; i2 < path.length; i2++) {
|
|
23583
|
+
const [ax, ay] = path[i2 - 1], [bx, by] = path[i2];
|
|
23584
|
+
const dx = bx - ax, dy = by - ay, len = dx * dx + dy * dy;
|
|
23585
|
+
const t = len ? Math.max(0, Math.min(1, ((x3 - ax) * dx + (y - ay) * dy) / len)) : 0;
|
|
23586
|
+
best = Math.min(best, Math.hypot(x3 - ax - t * dx, y - ay - t * dy));
|
|
23587
|
+
}
|
|
23588
|
+
return best;
|
|
23589
|
+
}
|
|
23590
|
+
var isContainer = (e) => CONTAINERS.has(e.type);
|
|
23591
|
+
function elementsAt(elements, x3, y, opts = {}) {
|
|
23592
|
+
const tolerance = opts.tolerance ?? 6;
|
|
23593
|
+
const order = new Map(elements.map((e, i2) => [e, i2]));
|
|
23594
|
+
const topFirst = (a, b) => order.get(b) - order.get(a);
|
|
23595
|
+
const covered = /* @__PURE__ */ new Set();
|
|
23596
|
+
const screens = elements.filter((e) => e.type === "Screen");
|
|
23597
|
+
const screenAround = (b) => screens.find((s) => inside(s.box, ...center(b)));
|
|
23598
|
+
for (const [i2, m2] of elements.entries()) {
|
|
23599
|
+
if (isFlow(m2) || !OVERLAYS.has(m2.type)) continue;
|
|
23600
|
+
const screen = screenAround(m2.box);
|
|
23601
|
+
if (!screen || !inside(m2.box, x3, y) && !(m2.scrim && inside(screen.box, x3, y))) continue;
|
|
23602
|
+
for (const e of elements.slice(0, i2)) if (!isFlow(e) && e.type !== "Screen" && e.type !== "Board" && screenAround(e.box) === screen) covered.add(e);
|
|
23603
|
+
}
|
|
23604
|
+
const owners = [], labels = [], leaves = [], lines = [], containers = [];
|
|
23605
|
+
for (const e of elements) {
|
|
23606
|
+
if (isFlow(e)) {
|
|
23607
|
+
if (inside(e.labelBox, x3, y)) labels.push(e);
|
|
23608
|
+
else if (distanceToPath(e.path, x3, y) <= tolerance) lines.push(e);
|
|
23609
|
+
} else if (e.overlays?.some((o2) => inside(o2, x3, y))) owners.push(e);
|
|
23610
|
+
else if (!covered.has(e) && (inside(e.box, x3, y) || inside(e.labelBox, x3, y))) (isContainer(e) ? containers : leaves).push(e);
|
|
23611
|
+
}
|
|
23612
|
+
return [owners, labels, leaves, lines, containers].flatMap((g2) => g2.sort(topFirst));
|
|
23613
|
+
}
|
|
23614
|
+
function elementsAtLine(elements, line) {
|
|
23615
|
+
return elements.filter((e) => e.line > 0 && e.line <= line && line <= e.endLine).sort((a, b) => b.line - a.line);
|
|
23616
|
+
}
|
|
23617
|
+
|
|
23618
|
+
// src/measure.ts
|
|
23578
23619
|
var BARS = /* @__PURE__ */ new Set(["NavBar", "TabBar"]);
|
|
23579
23620
|
var toBox = (r) => ({ x: round(r.x), y: round(r.y), width: round(r.w), height: round(r.h) });
|
|
23580
23621
|
var round = (n) => Math.round(n * 100) / 100;
|
|
@@ -23677,45 +23718,6 @@ async function measureElements(spec, lines, opts = {}) {
|
|
|
23677
23718
|
}
|
|
23678
23719
|
return out;
|
|
23679
23720
|
}
|
|
23680
|
-
var inside = (b, x3, y) => !!b && b.width > 0 && b.height > 0 && x3 >= b.x && x3 <= b.x + b.width && y >= b.y && y <= b.y + b.height;
|
|
23681
|
-
var center = (b) => [b.x + b.width / 2, b.y + b.height / 2];
|
|
23682
|
-
function distanceToPath(path, x3, y) {
|
|
23683
|
-
let best = Infinity;
|
|
23684
|
-
for (let i2 = 1; i2 < path.length; i2++) {
|
|
23685
|
-
const [ax, ay] = path[i2 - 1], [bx, by] = path[i2];
|
|
23686
|
-
const dx = bx - ax, dy = by - ay, len = dx * dx + dy * dy;
|
|
23687
|
-
const t = len ? Math.max(0, Math.min(1, ((x3 - ax) * dx + (y - ay) * dy) / len)) : 0;
|
|
23688
|
-
best = Math.min(best, Math.hypot(x3 - ax - t * dx, y - ay - t * dy));
|
|
23689
|
-
}
|
|
23690
|
-
return best;
|
|
23691
|
-
}
|
|
23692
|
-
var isContainer = (e) => CONTAINERS.has(e.type);
|
|
23693
|
-
function elementsAt(elements, x3, y, opts = {}) {
|
|
23694
|
-
const tolerance = opts.tolerance ?? 6;
|
|
23695
|
-
const order = new Map(elements.map((e, i2) => [e, i2]));
|
|
23696
|
-
const topFirst = (a, b) => order.get(b) - order.get(a);
|
|
23697
|
-
const covered = /* @__PURE__ */ new Set();
|
|
23698
|
-
const screens = elements.filter((e) => e.type === "Screen");
|
|
23699
|
-
const screenAround = (b) => screens.find((s) => inside(s.box, ...center(b)));
|
|
23700
|
-
for (const [i2, m2] of elements.entries()) {
|
|
23701
|
-
if (isFlow(m2) || !OVERLAYS.has(m2.type)) continue;
|
|
23702
|
-
const screen = screenAround(m2.box);
|
|
23703
|
-
if (!screen || !inside(m2.box, x3, y) && !(m2.scrim && inside(screen.box, x3, y))) continue;
|
|
23704
|
-
for (const e of elements.slice(0, i2)) if (!isFlow(e) && e.type !== "Screen" && e.type !== "Board" && screenAround(e.box) === screen) covered.add(e);
|
|
23705
|
-
}
|
|
23706
|
-
const owners = [], labels = [], leaves = [], lines = [], containers = [];
|
|
23707
|
-
for (const e of elements) {
|
|
23708
|
-
if (isFlow(e)) {
|
|
23709
|
-
if (inside(e.labelBox, x3, y)) labels.push(e);
|
|
23710
|
-
else if (distanceToPath(e.path, x3, y) <= tolerance) lines.push(e);
|
|
23711
|
-
} else if (e.overlays?.some((o2) => inside(o2, x3, y))) owners.push(e);
|
|
23712
|
-
else if (!covered.has(e) && (inside(e.box, x3, y) || inside(e.labelBox, x3, y))) (isContainer(e) ? containers : leaves).push(e);
|
|
23713
|
-
}
|
|
23714
|
-
return [owners, labels, leaves, lines, containers].flatMap((g2) => g2.sort(topFirst));
|
|
23715
|
-
}
|
|
23716
|
-
function elementsAtLine(elements, line) {
|
|
23717
|
-
return elements.filter((e) => e.line > 0 && e.line <= line && line <= e.endLine).sort((a, b) => b.line - a.line);
|
|
23718
|
-
}
|
|
23719
23721
|
|
|
23720
23722
|
// src/playground/language-data.ts
|
|
23721
23723
|
function unwrap4(t) {
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCENTS,
|
|
3
|
+
LABEL_H,
|
|
4
|
+
PRIMARY_PROP,
|
|
5
|
+
SpecError,
|
|
6
|
+
UNIVERSAL_PROPS,
|
|
7
|
+
bareWords,
|
|
8
|
+
boardLayout,
|
|
9
|
+
checkSpec,
|
|
10
|
+
componentDefinitions,
|
|
11
|
+
findAnchors,
|
|
12
|
+
flowEnds,
|
|
13
|
+
flowPolyline,
|
|
14
|
+
iconNames,
|
|
15
|
+
layoutFlows,
|
|
16
|
+
loadFonts,
|
|
17
|
+
overlayBoxes,
|
|
18
|
+
paletteFor,
|
|
19
|
+
readMarkers,
|
|
20
|
+
tagForMeasuring,
|
|
21
|
+
takesUniversalProps,
|
|
22
|
+
upgradeSpec,
|
|
23
|
+
withBoardGap,
|
|
24
|
+
withMarkers,
|
|
25
|
+
withNoteHeights,
|
|
26
|
+
withPalette
|
|
27
|
+
} from "./chunk-QYGOXTAG.js";
|
|
28
|
+
|
|
29
|
+
// src/hit.ts
|
|
30
|
+
var isFlow = (e) => e.type === "Flow";
|
|
31
|
+
var COVERS_SCREEN = /* @__PURE__ */ new Set(["Modal", "Drawer"]);
|
|
32
|
+
var OVERLAYS = /* @__PURE__ */ new Set(["Modal", "Drawer", "Toast"]);
|
|
33
|
+
var CONTAINERS = /* @__PURE__ */ new Set(["Board", "Screen", "Stack", "Grid", "Card", "Accordion", "List", "Modal", "Drawer"]);
|
|
34
|
+
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;
|
|
35
|
+
var center = (b) => [b.x + b.width / 2, b.y + b.height / 2];
|
|
36
|
+
function distanceToPath(path, x, y) {
|
|
37
|
+
let best = Infinity;
|
|
38
|
+
for (let i = 1; i < path.length; i++) {
|
|
39
|
+
const [ax, ay] = path[i - 1], [bx, by] = path[i];
|
|
40
|
+
const dx = bx - ax, dy = by - ay, len = dx * dx + dy * dy;
|
|
41
|
+
const t = len ? Math.max(0, Math.min(1, ((x - ax) * dx + (y - ay) * dy) / len)) : 0;
|
|
42
|
+
best = Math.min(best, Math.hypot(x - ax - t * dx, y - ay - t * dy));
|
|
43
|
+
}
|
|
44
|
+
return best;
|
|
45
|
+
}
|
|
46
|
+
var isContainer = (e) => CONTAINERS.has(e.type);
|
|
47
|
+
function elementsAt(elements, x, y, opts = {}) {
|
|
48
|
+
const tolerance = opts.tolerance ?? 6;
|
|
49
|
+
const order = new Map(elements.map((e, i) => [e, i]));
|
|
50
|
+
const topFirst = (a, b) => order.get(b) - order.get(a);
|
|
51
|
+
const covered = /* @__PURE__ */ new Set();
|
|
52
|
+
const screens = elements.filter((e) => e.type === "Screen");
|
|
53
|
+
const screenAround = (b) => screens.find((s) => inside(s.box, ...center(b)));
|
|
54
|
+
for (const [i, m] of elements.entries()) {
|
|
55
|
+
if (isFlow(m) || !OVERLAYS.has(m.type)) continue;
|
|
56
|
+
const screen = screenAround(m.box);
|
|
57
|
+
if (!screen || !inside(m.box, x, y) && !(m.scrim && inside(screen.box, x, y))) continue;
|
|
58
|
+
for (const e of elements.slice(0, i)) if (!isFlow(e) && e.type !== "Screen" && e.type !== "Board" && screenAround(e.box) === screen) covered.add(e);
|
|
59
|
+
}
|
|
60
|
+
const owners = [], labels = [], leaves = [], lines = [], containers = [];
|
|
61
|
+
for (const e of elements) {
|
|
62
|
+
if (isFlow(e)) {
|
|
63
|
+
if (inside(e.labelBox, x, y)) labels.push(e);
|
|
64
|
+
else if (distanceToPath(e.path, x, y) <= tolerance) lines.push(e);
|
|
65
|
+
} else if (e.overlays?.some((o) => inside(o, x, y))) owners.push(e);
|
|
66
|
+
else if (!covered.has(e) && (inside(e.box, x, y) || inside(e.labelBox, x, y))) (isContainer(e) ? containers : leaves).push(e);
|
|
67
|
+
}
|
|
68
|
+
return [owners, labels, leaves, lines, containers].flatMap((g) => g.sort(topFirst));
|
|
69
|
+
}
|
|
70
|
+
function elementsAtLine(elements, line) {
|
|
71
|
+
return elements.filter((e) => e.line > 0 && e.line <= line && line <= e.endLine).sort((a, b) => b.line - a.line);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/measure.ts
|
|
75
|
+
import { renderToSvg } from "@json-render/image/render";
|
|
76
|
+
var BARS = /* @__PURE__ */ new Set(["NavBar", "TabBar"]);
|
|
77
|
+
var toBox = (r) => ({ x: round(r.x), y: round(r.y), width: round(r.w), height: round(r.h) });
|
|
78
|
+
var round = (n) => Math.round(n * 100) / 100;
|
|
79
|
+
function clip(r, to) {
|
|
80
|
+
if (!to) return r;
|
|
81
|
+
const x = Math.max(r.x, to.x), y = Math.max(r.y, to.y);
|
|
82
|
+
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;
|
|
83
|
+
return w >= 0 && h >= 0 && (w > 0 || r.w === 0) && (h > 0 || r.h === 0) ? { x, y, w, h } : null;
|
|
84
|
+
}
|
|
85
|
+
async function measureElements(spec, lines, opts = {}) {
|
|
86
|
+
upgradeSpec(spec);
|
|
87
|
+
if (!opts.skipValidation) {
|
|
88
|
+
const issues = checkSpec(spec);
|
|
89
|
+
if (issues.length) throw new SpecError(issues);
|
|
90
|
+
}
|
|
91
|
+
spec = await withNoteHeights(spec);
|
|
92
|
+
const { width, height } = boardLayout(spec, { flows: opts.flows });
|
|
93
|
+
spec = withBoardGap(spec, opts.flows);
|
|
94
|
+
const els = spec.elements;
|
|
95
|
+
const anchors = findAnchors(spec);
|
|
96
|
+
const every = Object.keys(els).filter((k) => !["Board", "Screen", "Flow"].includes(els[k].type)).map((id) => ({ id, screen: id, kind: "tooltip", field: false }));
|
|
97
|
+
const screens = Object.keys(els).filter((k) => els[k].type === "Screen");
|
|
98
|
+
const inners = screens.map((id) => ({ id, screen: id, kind: "tooltip", field: false }));
|
|
99
|
+
const { tagged, colors } = tagForMeasuring(spec, [...anchors, ...every, ...inners], screens);
|
|
100
|
+
const svg = await renderToSvg(tagged, { registry: withMarkers(withPalette(paletteFor())), includeStandard: false, fonts: await loadFonts(), width, height });
|
|
101
|
+
const boxes = readMarkers(svg, colors);
|
|
102
|
+
const { items } = boardLayout(spec, { flows: opts.flows });
|
|
103
|
+
const laidOut = (els[spec.root].children ?? []).filter((k) => els[k]?.type === "Screen" || els[k]?.type === "Note");
|
|
104
|
+
const frames = /* @__PURE__ */ new Map(), bands = /* @__PURE__ */ new Map();
|
|
105
|
+
laidOut.forEach((k, i) => {
|
|
106
|
+
const it = items[i];
|
|
107
|
+
if (!it || els[k].type !== "Screen") return;
|
|
108
|
+
frames.set(k, { x: it.x, y: it.y + LABEL_H, w: it.width, h: it.height - LABEL_H });
|
|
109
|
+
bands.set(k, { x: it.x, y: it.y, w: it.width, h: LABEL_H });
|
|
110
|
+
});
|
|
111
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const o of overlayBoxes(spec, anchors, boxes)) overlays.set(o.id, [...overlays.get(o.id) ?? [], o.box]);
|
|
113
|
+
const lineOf = (k) => k == null ? null : lines[k] ?? 0;
|
|
114
|
+
const endLine = (k) => Math.max(lines[k] ?? 0, ...(els[k].children ?? []).filter((c) => els[c]).map(endLine));
|
|
115
|
+
const labelOf = (k) => {
|
|
116
|
+
const p = els[k].props ?? {};
|
|
117
|
+
const v = p[PRIMARY_PROP[els[k].type] ?? ""];
|
|
118
|
+
return typeof v === "string" ? v : void 0;
|
|
119
|
+
};
|
|
120
|
+
const out = [];
|
|
121
|
+
const visit = (k, parent, screen, cut) => {
|
|
122
|
+
const el = els[k];
|
|
123
|
+
if (!el || el.type === "Flow") return;
|
|
124
|
+
const p = el.props ?? {};
|
|
125
|
+
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) };
|
|
126
|
+
let entry = null;
|
|
127
|
+
if (el.type === "Board") entry = { ...base, box: { x: 0, y: 0, width, height } };
|
|
128
|
+
else if (el.type === "Screen") {
|
|
129
|
+
const frame = frames.get(k);
|
|
130
|
+
if (frame) entry = { ...base, box: toBox(frame), labelBox: toBox(bands.get(k)) };
|
|
131
|
+
} else {
|
|
132
|
+
const r = boxes[k];
|
|
133
|
+
const seen = r && clip(r, cut);
|
|
134
|
+
if (seen) {
|
|
135
|
+
const view = screen ? boxes[screen] ?? frames.get(screen) : void 0;
|
|
136
|
+
const os = (overlays.get(k) ?? []).map((o) => clip(o, view)).filter((o) => !!o && o.w > 0 && o.h > 0);
|
|
137
|
+
entry = { ...base, box: toBox(seen), ...os.length ? { overlays: os.map(toBox) } : {}, ...COVERS_SCREEN.has(el.type) ? { scrim: p.scrim ?? true } : {} };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (entry) out.push(entry);
|
|
141
|
+
const inScreen = el.type === "Screen" ? k : screen;
|
|
142
|
+
const kids = el.children ?? [];
|
|
143
|
+
if (el.type === "Screen") {
|
|
144
|
+
const frame = frames.get(k) ?? null;
|
|
145
|
+
const inner = clip(boxes[k] ?? frame, frame);
|
|
146
|
+
const body = clip(boxes[`${k}#body`] ?? inner, inner);
|
|
147
|
+
const over = (c) => OVERLAYS.has(els[c]?.type);
|
|
148
|
+
for (const c of [...kids.filter((c2) => !over(c2)), ...kids.filter(over)]) visit(c, k, k, BARS.has(els[c]?.type) || over(c) ? inner : body);
|
|
149
|
+
} else {
|
|
150
|
+
const inner = el.type === "Drawer" && boxes[k] ? clip(boxes[k], cut) : cut;
|
|
151
|
+
for (const c of kids) visit(c, k, inScreen, inner);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
visit(spec.root, null, null, null);
|
|
155
|
+
if (opts.flows !== false) {
|
|
156
|
+
const flows = flowEnds(spec);
|
|
157
|
+
const resolved = flows.resolve(boxes);
|
|
158
|
+
const ends = new Map(flows.list.map((f) => [f.key, f]));
|
|
159
|
+
layoutFlows(spec, resolved, flows.screens, flows.gap, paletteFor()).forEach((f) => {
|
|
160
|
+
const p = els[f.key].props ?? {};
|
|
161
|
+
const e = ends.get(f.key);
|
|
162
|
+
out.push({
|
|
163
|
+
line: lineOf(f.key),
|
|
164
|
+
endLine: lineOf(f.key),
|
|
165
|
+
type: "Flow",
|
|
166
|
+
...f.label ? { label: f.label.text } : {},
|
|
167
|
+
from: String(p.from),
|
|
168
|
+
to: String(p.to),
|
|
169
|
+
fromLine: lineOf(e.from),
|
|
170
|
+
toLine: lineOf(e.to),
|
|
171
|
+
path: flowPolyline(f).map((q) => [round(q.x), round(q.y)]),
|
|
172
|
+
...f.label ? { labelBox: toBox(f.label) } : {}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/playground/language-data.ts
|
|
180
|
+
function unwrap(t) {
|
|
181
|
+
let cur = t;
|
|
182
|
+
while (cur && ["optional", "nullable", "default"].includes(cur.def?.type)) cur = cur.def.innerType;
|
|
183
|
+
return cur;
|
|
184
|
+
}
|
|
185
|
+
function describe(name, schema) {
|
|
186
|
+
const s = unwrap(schema);
|
|
187
|
+
const description = schema.description ?? s?.description;
|
|
188
|
+
const iconish = /Lucide icon|Icon names/i.test(description ?? "");
|
|
189
|
+
switch (s?.def?.type) {
|
|
190
|
+
case "enum":
|
|
191
|
+
return { name, kind: "enum", values: s.options, description };
|
|
192
|
+
case "boolean":
|
|
193
|
+
return { name, kind: "boolean", values: ["true", "false"], description };
|
|
194
|
+
case "number":
|
|
195
|
+
return { name, kind: "number", description };
|
|
196
|
+
case "array": {
|
|
197
|
+
const el = unwrap(s.def.element);
|
|
198
|
+
const objects = el?.def?.type === "union" ? el.def.options.map(unwrap) : [el];
|
|
199
|
+
const hasIcon = objects.some((x) => x?.def?.type === "object" && "icon" in x.shape);
|
|
200
|
+
return { name, kind: "list", icon: iconish || hasIcon, description };
|
|
201
|
+
}
|
|
202
|
+
case "object":
|
|
203
|
+
return { name, kind: "object", description };
|
|
204
|
+
default:
|
|
205
|
+
return { name, kind: "string", icon: iconish || void 0, accent: name === "accent" || void 0, description };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
var cache = null;
|
|
209
|
+
function languageData() {
|
|
210
|
+
if (cache) return cache;
|
|
211
|
+
const components = Object.entries(componentDefinitions).map(([type, def]) => {
|
|
212
|
+
const shape = def.props.shape;
|
|
213
|
+
const props = Object.entries(shape).map(([name, schema]) => describe(name, schema));
|
|
214
|
+
if (takesUniversalProps(type)) for (const [name, schema] of Object.entries(UNIVERSAL_PROPS)) props.push(describe(name, schema));
|
|
215
|
+
if (type === "Icon") props.find((p) => p.name === "name").icon = true;
|
|
216
|
+
const words = bareWords(type);
|
|
217
|
+
const off = words.booleans.flatMap((b) => b === "on" ? ["off"] : b === "checked" ? ["unchecked"] : [`no-${b}`]);
|
|
218
|
+
return {
|
|
219
|
+
name: type.toLowerCase(),
|
|
220
|
+
description: def.description,
|
|
221
|
+
main: PRIMARY_PROP[type],
|
|
222
|
+
children: def.slots.length > 0,
|
|
223
|
+
props,
|
|
224
|
+
words: [...words.options.map((o) => o.value), ...words.booleans, ...off]
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
cache = { components, icons: [...new Set(iconNames)].sort(), accents: Object.keys(ACCENTS) };
|
|
228
|
+
return cache;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export {
|
|
232
|
+
elementsAt,
|
|
233
|
+
elementsAtLine,
|
|
234
|
+
measureElements,
|
|
235
|
+
languageData
|
|
236
|
+
};
|
package/dist/editor.js
CHANGED
|
@@ -22601,6 +22601,14 @@ var hangingIndent = ViewPlugin.fromClass(
|
|
|
22601
22601
|
{ decorations: (p) => p.decorations }
|
|
22602
22602
|
);
|
|
22603
22603
|
var wrapping = [EditorView.lineWrapping, hangingIndent];
|
|
22604
|
+
function cursorLine(report) {
|
|
22605
|
+
let last2 = 0;
|
|
22606
|
+
return EditorView.updateListener.of((u) => {
|
|
22607
|
+
if (!u.selectionSet && !u.docChanged) return;
|
|
22608
|
+
const line = u.state.doc.lineAt(u.state.selection.main.head).number;
|
|
22609
|
+
if (line !== last2) report(last2 = line);
|
|
22610
|
+
});
|
|
22611
|
+
}
|
|
22604
22612
|
function createEditor(parent, opts = {}) {
|
|
22605
22613
|
const extensions = [
|
|
22606
22614
|
opts.lineNumbers === false ? [] : [lineNumbers(), highlightActiveLineGutter()],
|
|
@@ -22622,7 +22630,8 @@ function createEditor(parent, opts = {}) {
|
|
|
22622
22630
|
EditorView.contentAttributes.of({ "aria-label": opts.ariaLabel ?? "Wireframe text", spellcheck: "false", autocapitalize: "off" }),
|
|
22623
22631
|
EditorView.updateListener.of((u) => {
|
|
22624
22632
|
if (u.docChanged) opts.onChange?.(u.state.doc.toString());
|
|
22625
|
-
})
|
|
22633
|
+
}),
|
|
22634
|
+
opts.onCursorLine ? cursorLine(opts.onCursorLine) : []
|
|
22626
22635
|
];
|
|
22627
22636
|
const view = new EditorView({ parent, state: EditorState.create({ doc: opts.doc ?? "", extensions }) });
|
|
22628
22637
|
return {
|
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[];
|