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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.13.2
|
|
13
|
+
|
|
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.
|
|
15
|
+
|
|
16
|
+
- **Notes on the board get their real height** (#44). The layout estimated a note's height from its text's length, but the board draws the real wrap. In a grid, a row holding only notes could then be drawn a line or more taller or shorter than laid out, and every screen below it moved: flow arrows routed through the wrong place (an arrow's lane could cut across a screen), `measureElements` boxes missed the drawn screens, and a long note could be cut off at the bottom of the board, squeezing the title. Notes are now measured in a quick render of their own before the board is laid out (about 5 ms; none when a board has no notes).
|
|
17
|
+
- **`measureBoardLayout(spec, { flows })`**: the board's size and each screen's position, exact (async, since it renders the notes). Use it to size the image and place screen chips. `boardLayout` is unchanged: synchronous, with the estimate. `renderAll`, the playground and the MCP render tool use the exact one now.
|
|
18
|
+
- **A modal taller than its screen is measured below the status bar** (#45). The screen draws its modals, drawers and toasts below its status or browser bar, but `measureElements` cut them only to the frame, so a long form in a modal (16 fields on a phone) had a box over the status bar, and a click there returned the modal. They and their contents are now cut where they're drawn.
|
|
19
|
+
- **Measuring works in DOMs without `structuredClone`** (jsdom): boards with notes, tooltips or flows failed there with "structuredClone is not defined".
|
|
20
|
+
|
|
3
21
|
## 0.13.1
|
|
4
22
|
|
|
5
23
|
The language and the renderer haven't changed: every wireframe renders exactly as in 0.13.0.
|
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 `boardLayout
|
|
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
|
@@ -21840,7 +21840,7 @@ function findAnchors(spec) {
|
|
|
21840
21840
|
}
|
|
21841
21841
|
var MARK = (i2) => `#fe${(i2 >> 8).toString(16).padStart(2, "0")}${(i2 & 255).toString(16).padStart(2, "0")}`;
|
|
21842
21842
|
function tagForMeasuring(spec, anchors, bodies = []) {
|
|
21843
|
-
const tagged =
|
|
21843
|
+
const tagged = { ...spec, elements: Object.fromEntries(Object.entries(spec.elements).map(([k, el2]) => [k, { ...el2 }])) };
|
|
21844
21844
|
const colors = /* @__PURE__ */ new Map();
|
|
21845
21845
|
let n = 1;
|
|
21846
21846
|
const tag = (id, part) => {
|
|
@@ -22932,7 +22932,8 @@ function boardLayout(spec, opts = {}) {
|
|
|
22932
22932
|
}
|
|
22933
22933
|
if (el2.type === "Note") {
|
|
22934
22934
|
const w2 = props.width ?? NOTE_WIDTH;
|
|
22935
|
-
|
|
22935
|
+
const h2 = typeof props.__height === "number" ? props.__height : estimateNoteHeight(props.text ?? "", w2);
|
|
22936
|
+
return { type: "Note", name: String(props.text ?? ""), w: w2, h: LABEL_H + h2 };
|
|
22936
22937
|
}
|
|
22937
22938
|
return null;
|
|
22938
22939
|
});
|
|
@@ -22976,6 +22977,28 @@ function flowLabelGap(spec, laidOut, perRow) {
|
|
|
22976
22977
|
}
|
|
22977
22978
|
return widest ? widest + 32 : 0;
|
|
22978
22979
|
}
|
|
22980
|
+
async function withNoteHeights(spec) {
|
|
22981
|
+
const els = spec.elements;
|
|
22982
|
+
const notes = (els[spec.root]?.children ?? []).filter((k) => els[k]?.type === "Note");
|
|
22983
|
+
if (!notes.length || notes.every((k) => typeof els[k].props?.__height === "number")) return spec;
|
|
22984
|
+
let root = "__notes";
|
|
22985
|
+
while (root in els) root += "_";
|
|
22986
|
+
const probe = {
|
|
22987
|
+
root,
|
|
22988
|
+
elements: { [root]: { type: "Board", props: { layout: "grid", columns: 1, padding: 0, gap: 0 }, children: notes }, ...Object.fromEntries(notes.map((k) => [k, els[k]])) }
|
|
22989
|
+
};
|
|
22990
|
+
const { tagged, colors } = tagForMeasuring(probe, notes.map((id) => ({ id, screen: id, kind: "tooltip", field: false })));
|
|
22991
|
+
const props = (k) => els[k].props ?? {};
|
|
22992
|
+
const width = Math.max(...notes.map((k) => props(k).width ?? NOTE_WIDTH));
|
|
22993
|
+
const height = notes.reduce((sum, k) => sum + LABEL_H + 3 * estimateNoteHeight(props(k).text ?? "", props(k).width ?? NOTE_WIDTH), 100);
|
|
22994
|
+
const svg = await renderToSvg(tagged, { registry: withMarkers(withPalette(paletteFor())), includeStandard: false, fonts: await loadFonts(), width, height });
|
|
22995
|
+
const boxes = readMarkers(svg, colors);
|
|
22996
|
+
const measured = notes.filter((k) => boxes[k]).map((k) => [k, { ...els[k], props: { ...props(k), __height: boxes[k].h } }]);
|
|
22997
|
+
return { ...spec, elements: { ...els, ...Object.fromEntries(measured) } };
|
|
22998
|
+
}
|
|
22999
|
+
async function measureBoardLayout(spec, opts = {}) {
|
|
23000
|
+
return boardLayout(await withNoteHeights(spec), opts);
|
|
23001
|
+
}
|
|
22979
23002
|
function boardSize(spec, opts = {}) {
|
|
22980
23003
|
const { width, height } = boardLayout(spec, opts);
|
|
22981
23004
|
return { width, height };
|
|
@@ -22986,6 +23009,7 @@ async function renderWireframeSvg(spec, opts = {}) {
|
|
|
22986
23009
|
const issues = checkSpec(spec);
|
|
22987
23010
|
if (issues.length) throw new SpecError(issues);
|
|
22988
23011
|
}
|
|
23012
|
+
spec = await withNoteHeights(spec);
|
|
22989
23013
|
const flowsOn = opts.flows !== false;
|
|
22990
23014
|
const { width, height } = boardLayout(spec, { flows: opts.flows });
|
|
22991
23015
|
spec = withBoardGap(spec, opts.flows);
|
|
@@ -23420,7 +23444,7 @@ async function renderWireframeTool(args) {
|
|
|
23420
23444
|
const { spec, issues } = compileWireframe(args.text);
|
|
23421
23445
|
if (!spec || issues.length) return problems(issues);
|
|
23422
23446
|
const flows = args.flows !== false;
|
|
23423
|
-
const { width, height } =
|
|
23447
|
+
const { width, height } = await measureBoardLayout(spec, { flows });
|
|
23424
23448
|
const scale = Math.min(1, MODEL_IMAGE_MAX / Math.max(width, height));
|
|
23425
23449
|
const png = await renderWireframePng(spec, { skipValidation: true, flows, scale });
|
|
23426
23450
|
const shown = scale < 1 ? `, shown at ${Math.round(width * scale)}\xD7${Math.round(height * scale)}` : "";
|
|
@@ -23506,7 +23530,7 @@ async function renderBlock(doc, source) {
|
|
|
23506
23530
|
if (!spec || issues.length) return problems2(doc, issues.map((i2) => `line ${i2.line}: ${i2.message}`));
|
|
23507
23531
|
try {
|
|
23508
23532
|
const svg = await renderWireframeSvg(spec, { skipValidation: true });
|
|
23509
|
-
const { width, height } =
|
|
23533
|
+
const { width, height } = await measureBoardLayout(spec);
|
|
23510
23534
|
const title = spec.elements[spec.root]?.props?.title;
|
|
23511
23535
|
const figure = doc.createElement("figure");
|
|
23512
23536
|
figure.className = "tsquare-board";
|
|
@@ -23546,11 +23570,52 @@ function addStyles(doc) {
|
|
|
23546
23570
|
doc.head.append(style);
|
|
23547
23571
|
}
|
|
23548
23572
|
|
|
23549
|
-
// src/
|
|
23573
|
+
// src/hit.ts
|
|
23550
23574
|
var isFlow = (e) => e.type === "Flow";
|
|
23551
23575
|
var COVERS_SCREEN = /* @__PURE__ */ new Set(["Modal", "Drawer"]);
|
|
23552
23576
|
var OVERLAYS = /* @__PURE__ */ new Set(["Modal", "Drawer", "Toast"]);
|
|
23553
|
-
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
|
|
23554
23619
|
var BARS = /* @__PURE__ */ new Set(["NavBar", "TabBar"]);
|
|
23555
23620
|
var toBox = (r) => ({ x: round(r.x), y: round(r.y), width: round(r.w), height: round(r.h) });
|
|
23556
23621
|
var round = (n) => Math.round(n * 100) / 100;
|
|
@@ -23566,13 +23631,15 @@ async function measureElements(spec, lines, opts = {}) {
|
|
|
23566
23631
|
const issues = checkSpec(spec);
|
|
23567
23632
|
if (issues.length) throw new SpecError(issues);
|
|
23568
23633
|
}
|
|
23634
|
+
spec = await withNoteHeights(spec);
|
|
23569
23635
|
const { width, height } = boardLayout(spec, { flows: opts.flows });
|
|
23570
23636
|
spec = withBoardGap(spec, opts.flows);
|
|
23571
23637
|
const els = spec.elements;
|
|
23572
23638
|
const anchors = findAnchors(spec);
|
|
23573
23639
|
const every = Object.keys(els).filter((k) => !["Board", "Screen", "Flow"].includes(els[k].type)).map((id) => ({ id, screen: id, kind: "tooltip", field: false }));
|
|
23574
23640
|
const screens = Object.keys(els).filter((k) => els[k].type === "Screen");
|
|
23575
|
-
const
|
|
23641
|
+
const inners = screens.map((id) => ({ id, screen: id, kind: "tooltip", field: false }));
|
|
23642
|
+
const { tagged, colors } = tagForMeasuring(spec, [...anchors, ...every, ...inners], screens);
|
|
23576
23643
|
const svg = await renderToSvg(tagged, { registry: withMarkers(withPalette(paletteFor())), includeStandard: false, fonts: await loadFonts(), width, height });
|
|
23577
23644
|
const boxes = readMarkers(svg, colors);
|
|
23578
23645
|
const { items } = boardLayout(spec, { flows: opts.flows });
|
|
@@ -23618,9 +23685,10 @@ async function measureElements(spec, lines, opts = {}) {
|
|
|
23618
23685
|
const kids = el2.children ?? [];
|
|
23619
23686
|
if (el2.type === "Screen") {
|
|
23620
23687
|
const frame = frames.get(k) ?? null;
|
|
23621
|
-
const
|
|
23688
|
+
const inner = clip(boxes[k] ?? frame, frame);
|
|
23689
|
+
const body = clip(boxes[`${k}#body`] ?? inner, inner);
|
|
23622
23690
|
const over = (c2) => OVERLAYS.has(els[c2]?.type);
|
|
23623
|
-
for (const c2 of [...kids.filter((c3) => !over(c3)), ...kids.filter(over)]) visit(c2, k, k, BARS.has(els[c2]?.type) || over(c2) ?
|
|
23691
|
+
for (const c2 of [...kids.filter((c3) => !over(c3)), ...kids.filter(over)]) visit(c2, k, k, BARS.has(els[c2]?.type) || over(c2) ? inner : body);
|
|
23624
23692
|
} else {
|
|
23625
23693
|
const inner = el2.type === "Drawer" && boxes[k] ? clip(boxes[k], cut) : cut;
|
|
23626
23694
|
for (const c2 of kids) visit(c2, k, inScreen, inner);
|
|
@@ -23650,45 +23718,6 @@ async function measureElements(spec, lines, opts = {}) {
|
|
|
23650
23718
|
}
|
|
23651
23719
|
return out;
|
|
23652
23720
|
}
|
|
23653
|
-
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;
|
|
23654
|
-
var center = (b) => [b.x + b.width / 2, b.y + b.height / 2];
|
|
23655
|
-
function distanceToPath(path, x3, y) {
|
|
23656
|
-
let best = Infinity;
|
|
23657
|
-
for (let i2 = 1; i2 < path.length; i2++) {
|
|
23658
|
-
const [ax, ay] = path[i2 - 1], [bx, by] = path[i2];
|
|
23659
|
-
const dx = bx - ax, dy = by - ay, len = dx * dx + dy * dy;
|
|
23660
|
-
const t = len ? Math.max(0, Math.min(1, ((x3 - ax) * dx + (y - ay) * dy) / len)) : 0;
|
|
23661
|
-
best = Math.min(best, Math.hypot(x3 - ax - t * dx, y - ay - t * dy));
|
|
23662
|
-
}
|
|
23663
|
-
return best;
|
|
23664
|
-
}
|
|
23665
|
-
var isContainer = (e) => CONTAINERS.has(e.type);
|
|
23666
|
-
function elementsAt(elements, x3, y, opts = {}) {
|
|
23667
|
-
const tolerance = opts.tolerance ?? 6;
|
|
23668
|
-
const order = new Map(elements.map((e, i2) => [e, i2]));
|
|
23669
|
-
const topFirst = (a, b) => order.get(b) - order.get(a);
|
|
23670
|
-
const covered = /* @__PURE__ */ new Set();
|
|
23671
|
-
const screens = elements.filter((e) => e.type === "Screen");
|
|
23672
|
-
const screenAround = (b) => screens.find((s) => inside(s.box, ...center(b)));
|
|
23673
|
-
for (const [i2, m2] of elements.entries()) {
|
|
23674
|
-
if (isFlow(m2) || !OVERLAYS.has(m2.type)) continue;
|
|
23675
|
-
const screen = screenAround(m2.box);
|
|
23676
|
-
if (!screen || !inside(m2.box, x3, y) && !(m2.scrim && inside(screen.box, x3, y))) continue;
|
|
23677
|
-
for (const e of elements.slice(0, i2)) if (!isFlow(e) && e.type !== "Screen" && e.type !== "Board" && screenAround(e.box) === screen) covered.add(e);
|
|
23678
|
-
}
|
|
23679
|
-
const owners = [], labels = [], leaves = [], lines = [], containers = [];
|
|
23680
|
-
for (const e of elements) {
|
|
23681
|
-
if (isFlow(e)) {
|
|
23682
|
-
if (inside(e.labelBox, x3, y)) labels.push(e);
|
|
23683
|
-
else if (distanceToPath(e.path, x3, y) <= tolerance) lines.push(e);
|
|
23684
|
-
} else if (e.overlays?.some((o2) => inside(o2, x3, y))) owners.push(e);
|
|
23685
|
-
else if (!covered.has(e) && (inside(e.box, x3, y) || inside(e.labelBox, x3, y))) (isContainer(e) ? containers : leaves).push(e);
|
|
23686
|
-
}
|
|
23687
|
-
return [owners, labels, leaves, lines, containers].flatMap((g2) => g2.sort(topFirst));
|
|
23688
|
-
}
|
|
23689
|
-
function elementsAtLine(elements, line) {
|
|
23690
|
-
return elements.filter((e) => e.line > 0 && e.line <= line && line <= e.endLine).sort((a, b) => b.line - a.line);
|
|
23691
|
-
}
|
|
23692
23721
|
|
|
23693
23722
|
// src/playground/language-data.ts
|
|
23694
23723
|
function unwrap4(t) {
|
|
@@ -23765,6 +23794,7 @@ export {
|
|
|
23765
23794
|
printWireframeText as formatWireframe,
|
|
23766
23795
|
languageData,
|
|
23767
23796
|
mcpTools,
|
|
23797
|
+
measureBoardLayout,
|
|
23768
23798
|
measureElements,
|
|
23769
23799
|
parseWireframeText,
|
|
23770
23800
|
registry,
|
|
@@ -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
|
+
};
|
|
@@ -2383,7 +2383,7 @@ function findAnchors(spec) {
|
|
|
2383
2383
|
}
|
|
2384
2384
|
var MARK = (i) => `#fe${(i >> 8).toString(16).padStart(2, "0")}${(i & 255).toString(16).padStart(2, "0")}`;
|
|
2385
2385
|
function tagForMeasuring(spec, anchors, bodies = []) {
|
|
2386
|
-
const tagged =
|
|
2386
|
+
const tagged = { ...spec, elements: Object.fromEntries(Object.entries(spec.elements).map(([k, el]) => [k, { ...el }])) };
|
|
2387
2387
|
const colors = /* @__PURE__ */ new Map();
|
|
2388
2388
|
let n = 1;
|
|
2389
2389
|
const tag = (id, part) => {
|
|
@@ -2985,7 +2985,8 @@ function boardLayout(spec, opts = {}) {
|
|
|
2985
2985
|
}
|
|
2986
2986
|
if (el.type === "Note") {
|
|
2987
2987
|
const w = props.width ?? NOTE_WIDTH;
|
|
2988
|
-
|
|
2988
|
+
const h = typeof props.__height === "number" ? props.__height : estimateNoteHeight(props.text ?? "", w);
|
|
2989
|
+
return { type: "Note", name: String(props.text ?? ""), w, h: LABEL_H + h };
|
|
2989
2990
|
}
|
|
2990
2991
|
return null;
|
|
2991
2992
|
});
|
|
@@ -3029,6 +3030,28 @@ function flowLabelGap(spec, laidOut, perRow) {
|
|
|
3029
3030
|
}
|
|
3030
3031
|
return widest ? widest + 32 : 0;
|
|
3031
3032
|
}
|
|
3033
|
+
async function withNoteHeights(spec) {
|
|
3034
|
+
const els = spec.elements;
|
|
3035
|
+
const notes = (els[spec.root]?.children ?? []).filter((k) => els[k]?.type === "Note");
|
|
3036
|
+
if (!notes.length || notes.every((k) => typeof els[k].props?.__height === "number")) return spec;
|
|
3037
|
+
let root = "__notes";
|
|
3038
|
+
while (root in els) root += "_";
|
|
3039
|
+
const probe = {
|
|
3040
|
+
root,
|
|
3041
|
+
elements: { [root]: { type: "Board", props: { layout: "grid", columns: 1, padding: 0, gap: 0 }, children: notes }, ...Object.fromEntries(notes.map((k) => [k, els[k]])) }
|
|
3042
|
+
};
|
|
3043
|
+
const { tagged, colors } = tagForMeasuring(probe, notes.map((id) => ({ id, screen: id, kind: "tooltip", field: false })));
|
|
3044
|
+
const props = (k) => els[k].props ?? {};
|
|
3045
|
+
const width = Math.max(...notes.map((k) => props(k).width ?? NOTE_WIDTH));
|
|
3046
|
+
const height = notes.reduce((sum, k) => sum + LABEL_H + 3 * estimateNoteHeight(props(k).text ?? "", props(k).width ?? NOTE_WIDTH), 100);
|
|
3047
|
+
const svg = await renderToSvg(tagged, { registry: withMarkers(withPalette(paletteFor())), includeStandard: false, fonts: await loadFonts(), width, height });
|
|
3048
|
+
const boxes = readMarkers(svg, colors);
|
|
3049
|
+
const measured = notes.filter((k) => boxes[k]).map((k) => [k, { ...els[k], props: { ...props(k), __height: boxes[k].h } }]);
|
|
3050
|
+
return { ...spec, elements: { ...els, ...Object.fromEntries(measured) } };
|
|
3051
|
+
}
|
|
3052
|
+
async function measureBoardLayout(spec, opts = {}) {
|
|
3053
|
+
return boardLayout(await withNoteHeights(spec), opts);
|
|
3054
|
+
}
|
|
3032
3055
|
function boardSize(spec, opts = {}) {
|
|
3033
3056
|
const { width, height } = boardLayout(spec, opts);
|
|
3034
3057
|
return { width, height };
|
|
@@ -3039,6 +3062,7 @@ async function renderWireframeSvg(spec, opts = {}) {
|
|
|
3039
3062
|
const issues = checkSpec(spec);
|
|
3040
3063
|
if (issues.length) throw new SpecError(issues);
|
|
3041
3064
|
}
|
|
3065
|
+
spec = await withNoteHeights(spec);
|
|
3042
3066
|
const flowsOn = opts.flows !== false;
|
|
3043
3067
|
const { width, height } = boardLayout(spec, { flows: opts.flows });
|
|
3044
3068
|
spec = withBoardGap(spec, opts.flows);
|
|
@@ -3412,6 +3436,8 @@ export {
|
|
|
3412
3436
|
SpecError,
|
|
3413
3437
|
checkSpec,
|
|
3414
3438
|
boardLayout,
|
|
3439
|
+
withNoteHeights,
|
|
3440
|
+
measureBoardLayout,
|
|
3415
3441
|
boardSize,
|
|
3416
3442
|
renderWireframeSvg,
|
|
3417
3443
|
withBoardGap,
|
package/dist/cli.js
CHANGED
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 {
|