@glissade/backend-dom 0.22.0-pre.0 → 0.22.0-pre.1
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/dist/index.js +97 -5
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -69,6 +69,57 @@ function segsToD(segs) {
|
|
|
69
69
|
}
|
|
70
70
|
return parts.join(" ");
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Axis-aligned bounding box of a path in its LOCAL coordinate space, or null for
|
|
74
|
+
* an empty path. Curve control points give a safe superset (the painted curve
|
|
75
|
+
* never exceeds its hull); `E` uses `max(rx,ry)` so the box contains the ellipse
|
|
76
|
+
* at any rotation (exact for the rounded-rect `rx==ry` case). Used to size each
|
|
77
|
+
* shape's `<svg>` island TIGHTLY around its geometry instead of full-canvas —
|
|
78
|
+
* the paint is unchanged (the viewBox maps local coords 1:1), but the SVG box no
|
|
79
|
+
* longer spans the whole viewport, so shapes don't overlap as giant transparent
|
|
80
|
+
* hit-targets.
|
|
81
|
+
*/
|
|
82
|
+
function pathBBox(segs) {
|
|
83
|
+
let minX = Infinity;
|
|
84
|
+
let minY = Infinity;
|
|
85
|
+
let maxX = -Infinity;
|
|
86
|
+
let maxY = -Infinity;
|
|
87
|
+
const pt = (x, y) => {
|
|
88
|
+
if (x < minX) minX = x;
|
|
89
|
+
if (x > maxX) maxX = x;
|
|
90
|
+
if (y < minY) minY = y;
|
|
91
|
+
if (y > maxY) maxY = y;
|
|
92
|
+
};
|
|
93
|
+
for (const seg of segs) switch (seg[0]) {
|
|
94
|
+
case "M":
|
|
95
|
+
case "L":
|
|
96
|
+
pt(seg[1], seg[2]);
|
|
97
|
+
break;
|
|
98
|
+
case "C":
|
|
99
|
+
pt(seg[1], seg[2]);
|
|
100
|
+
pt(seg[3], seg[4]);
|
|
101
|
+
pt(seg[5], seg[6]);
|
|
102
|
+
break;
|
|
103
|
+
case "Q":
|
|
104
|
+
pt(seg[1], seg[2]);
|
|
105
|
+
pt(seg[3], seg[4]);
|
|
106
|
+
break;
|
|
107
|
+
case "E": {
|
|
108
|
+
const r = Math.max(seg[3], seg[4]);
|
|
109
|
+
pt(seg[1] - r, seg[2] - r);
|
|
110
|
+
pt(seg[1] + r, seg[2] + r);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "Z": break;
|
|
114
|
+
}
|
|
115
|
+
if (!Number.isFinite(minX)) return null;
|
|
116
|
+
return {
|
|
117
|
+
x: minX,
|
|
118
|
+
y: minY,
|
|
119
|
+
w: maxX - minX,
|
|
120
|
+
h: maxY - minY
|
|
121
|
+
};
|
|
122
|
+
}
|
|
72
123
|
/** Short, stable FNV-1a hash (hex) over a string — for deterministic def ids
|
|
73
124
|
* that survive reorder (same scope+key → same id across frames). */
|
|
74
125
|
function hashKey(s) {
|
|
@@ -141,15 +192,24 @@ var DomBackend = class {
|
|
|
141
192
|
const res = list.resources[id];
|
|
142
193
|
return res && res.kind === "path" ? res.segs : [];
|
|
143
194
|
};
|
|
144
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* A fresh `<svg>` geometry island. Starts collapsed (0×0) — fillPath/
|
|
197
|
+
* strokePath size it TIGHTLY to the path bbox per render (#sizeIsland);
|
|
198
|
+
* clip's defs-only island stays collapsed (it renders nothing, only holds a
|
|
199
|
+
* <clipPath> referenced by id). `overflow:visible` keeps any curve/miter
|
|
200
|
+
* overshoot painted; `pointer-events:none` makes the box's transparent area
|
|
201
|
+
* click-through, so it can't swallow clicks meant for shapes behind it (the
|
|
202
|
+
* painted path re-enables hit-testing with its own `pointer-events`).
|
|
203
|
+
*/
|
|
145
204
|
const island = () => {
|
|
146
205
|
const svg = doc.createElementNS(SVG_NS, "svg");
|
|
147
|
-
svg.setAttribute("width",
|
|
148
|
-
svg.setAttribute("height",
|
|
206
|
+
svg.setAttribute("width", "0");
|
|
207
|
+
svg.setAttribute("height", "0");
|
|
149
208
|
svg.style.position = "absolute";
|
|
150
209
|
svg.style.left = "0";
|
|
151
210
|
svg.style.top = "0";
|
|
152
211
|
svg.style.overflow = "visible";
|
|
212
|
+
svg.style.pointerEvents = "none";
|
|
153
213
|
return svg;
|
|
154
214
|
};
|
|
155
215
|
list.commands.forEach((cmd, i) => {
|
|
@@ -225,6 +285,7 @@ var DomBackend = class {
|
|
|
225
285
|
const o = this.#matchOrCreate(cursor, key, "fillPath", () => {
|
|
226
286
|
const svg = island();
|
|
227
287
|
const path = doc.createElementNS(SVG_NS, "path");
|
|
288
|
+
path.style.pointerEvents = "auto";
|
|
228
289
|
svg.appendChild(path);
|
|
229
290
|
return {
|
|
230
291
|
op: "fillPath",
|
|
@@ -234,8 +295,10 @@ var DomBackend = class {
|
|
|
234
295
|
};
|
|
235
296
|
});
|
|
236
297
|
const path = o.path;
|
|
237
|
-
|
|
298
|
+
const segs = pathSegs(cmd.path);
|
|
299
|
+
this.#setAttr(path, o, "d", "d", segsToD(segs));
|
|
238
300
|
this.#setAttr(path, o, "fill", "fill", this.#resolvePaint(cmd.paint, o, scope, key));
|
|
301
|
+
this.#sizeIsland(o.el, o, segs, 0);
|
|
239
302
|
this.#stamp(o, path, id);
|
|
240
303
|
break;
|
|
241
304
|
}
|
|
@@ -244,6 +307,7 @@ var DomBackend = class {
|
|
|
244
307
|
const o = this.#matchOrCreate(cursor, key, "strokePath", () => {
|
|
245
308
|
const svg = island();
|
|
246
309
|
const path = doc.createElementNS(SVG_NS, "path");
|
|
310
|
+
path.style.pointerEvents = "stroke";
|
|
247
311
|
svg.appendChild(path);
|
|
248
312
|
return {
|
|
249
313
|
op: "strokePath",
|
|
@@ -253,10 +317,12 @@ var DomBackend = class {
|
|
|
253
317
|
};
|
|
254
318
|
});
|
|
255
319
|
const path = o.path;
|
|
256
|
-
|
|
320
|
+
const segs = pathSegs(cmd.path);
|
|
321
|
+
this.#setAttr(path, o, "d", "d", segsToD(segs));
|
|
257
322
|
this.#setAttr(path, o, "fill", "fill", "none");
|
|
258
323
|
this.#setAttr(path, o, "stroke", "stroke", this.#resolvePaint(cmd.paint, o, scope, key));
|
|
259
324
|
this.#applyStroke(path, o, cmd.stroke);
|
|
325
|
+
this.#sizeIsland(o.el, o, segs, cmd.stroke.width);
|
|
260
326
|
this.#stamp(o, path, id);
|
|
261
327
|
break;
|
|
262
328
|
}
|
|
@@ -523,6 +589,32 @@ var DomBackend = class {
|
|
|
523
589
|
this.#setAttr(el, o, "nodeId", "data-node-id", id);
|
|
524
590
|
}
|
|
525
591
|
/**
|
|
592
|
+
* Size a geometry island's `<svg>` box tightly to its path bbox (in the
|
|
593
|
+
* cursor's local space) via the `viewBox`, so the painted coordinates are
|
|
594
|
+
* UNCHANGED (1:1 mapping) while the element box shrinks from full-canvas to the
|
|
595
|
+
* shape. Cached writes — only touches the DOM when the bbox moves. `pad`
|
|
596
|
+
* (stroke width) grows the box so it contains a stroke straddling the
|
|
597
|
+
* centerline; `overflow:visible` covers any residual curve/miter overshoot.
|
|
598
|
+
*/
|
|
599
|
+
#sizeIsland(svg, o, segs, pad) {
|
|
600
|
+
const bb = pathBBox(segs);
|
|
601
|
+
if (bb === null) {
|
|
602
|
+
this.#setAttr(svg, o, "svgW", "width", "0");
|
|
603
|
+
this.#setAttr(svg, o, "svgH", "height", "0");
|
|
604
|
+
this.#setAttr(svg, o, "svgVB", "viewBox", void 0);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const x = bb.x - pad;
|
|
608
|
+
const y = bb.y - pad;
|
|
609
|
+
const w = bb.w + 2 * pad;
|
|
610
|
+
const h = bb.h + 2 * pad;
|
|
611
|
+
this.#setStyle(o, svg, "left", `${x}px`);
|
|
612
|
+
this.#setStyle(o, svg, "top", `${y}px`);
|
|
613
|
+
this.#setAttr(svg, o, "svgW", "width", String(w));
|
|
614
|
+
this.#setAttr(svg, o, "svgH", "height", String(h));
|
|
615
|
+
this.#setAttr(svg, o, "svgVB", "viewBox", `${x} ${y} ${w} ${h}`);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
526
618
|
* Caret-preserving text write. RULE B (freeze): never touch the text while the
|
|
527
619
|
* div (or a descendant) is the focused contentEditable. RULE A (patch-only):
|
|
528
620
|
* write nothing when unchanged; otherwise mutate the SAME Text node's `.data`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/backend-dom",
|
|
3
|
-
"version": "0.22.0-pre.
|
|
3
|
+
"version": "0.22.0-pre.1",
|
|
4
4
|
"description": "glissade DOM render backend: DisplayList -> HTML/SVG elements. A preview / non-parity realtime tier (accessibility, selectable text, CSS-native embedding) — NOT a Skia-export twin.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@glissade/core": "0.22.0-pre.
|
|
22
|
-
"@glissade/scene": "0.22.0-pre.
|
|
21
|
+
"@glissade/core": "0.22.0-pre.1",
|
|
22
|
+
"@glissade/scene": "0.22.0-pre.1"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|