mirage-engine 0.2.4 → 0.2.5

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.
@@ -1,22 +1,37 @@
1
- var b = Object.defineProperty;
2
- var D = (i, e, t) => e in i ? b(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
- var o = (i, e, t) => (D(i, typeof e != "symbol" ? e + "" : e, t), t);
4
- import * as c from "three";
5
- const g = 0, T = 1, y = 2, C = 4, w = 8, x = 16;
6
- function N(i, e, t, s) {
7
- const n = document.createElement("canvas"), r = n.getContext("2d");
8
- if (!r)
1
+ var v = Object.defineProperty;
2
+ var D = (n, e, t) => e in n ? v(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
+ var o = (n, e, t) => (D(n, typeof e != "symbol" ? e + "" : e, t), t);
4
+ import * as m from "three";
5
+ const T = 0, y = 1, x = 2, M = 4, S = 8, C = 16;
6
+ function F(n, e, t) {
7
+ const i = e.split(`
8
+ `), r = [];
9
+ return i.forEach((s) => {
10
+ const g = s.split(" ");
11
+ let l = g[0];
12
+ for (let p = 1; p < g.length; p++) {
13
+ const a = g[p];
14
+ n.measureText(l + " " + a).width < t ? l += " " + a : (r.push(l), l = a);
15
+ }
16
+ r.push(l);
17
+ }), r;
18
+ }
19
+ function N(n, e, t, i) {
20
+ const r = document.createElement("canvas"), s = r.getContext("2d");
21
+ if (!s)
9
22
  throw new Error("[Mirage] Failed to create canvas context");
10
- const a = window.devicePixelRatio || 2;
11
- n.width = t * a, n.height = s * a, r.scale(a, a), r.font = e.font, r.fillStyle = e.color, r.textAlign = e.textAlign, r.direction = e.direction, r.textBaseline = "middle";
12
- let l = 0;
13
- e.textAlign === "center" && (l = t / 2), e.textAlign === "right" && (l = t);
14
- const m = s / 2;
15
- r.fillText(i, l, m);
16
- const h = new c.CanvasTexture(n);
17
- return h.colorSpace = c.SRGBColorSpace, h.minFilter = c.LinearFilter, h.needsUpdate = !0, h;
23
+ const g = 2, l = (window.devicePixelRatio || 2) * g;
24
+ r.width = t * l, r.height = i * l, s.scale(l, l), s.font = e.font, s.fillStyle = e.color, s.textBaseline = "top", s.globalAlpha = 1;
25
+ const p = F(s, n, t), a = e.lineHeight;
26
+ p.forEach((h, u) => {
27
+ const f = u * a + 2;
28
+ let d = 0;
29
+ e.textAlign === "center" ? d = t / 2 : e.textAlign === "right" && (d = t), s.textAlign = e.textAlign, s.fillText(h, d, f);
30
+ });
31
+ const c = new m.CanvasTexture(r);
32
+ return c.colorSpace = m.SRGBColorSpace, c.minFilter = m.LinearFilter, c.magFilter = m.LinearFilter, c.needsUpdate = !0, c;
18
33
  }
19
- class R {
34
+ class L {
20
35
  constructor() {
21
36
  o(this, "canvas");
22
37
  o(this, "scene");
@@ -24,19 +39,19 @@ class R {
24
39
  o(this, "renderer");
25
40
  o(this, "renderOrder", 0);
26
41
  o(this, "meshMap", /* @__PURE__ */ new Map());
27
- this.canvas = document.createElement("canvas"), this.scene = new c.Scene();
42
+ this.canvas = document.createElement("canvas"), this.scene = new m.Scene();
28
43
  const e = window.innerWidth, t = window.innerHeight;
29
- this.camera = new c.OrthographicCamera(
44
+ this.camera = new m.OrthographicCamera(
30
45
  e / -2,
31
46
  e / 2,
32
47
  t / 2,
33
48
  t / -2,
34
49
  1,
35
50
  1e3
36
- ), this.camera.position.z = 100, this.renderer = new c.WebGLRenderer({
51
+ ), this.camera.position.z = 100, this.renderer = new m.WebGLRenderer({
37
52
  canvas: this.canvas,
38
53
  alpha: !0
39
- }), this.renderer.setSize(e, t);
54
+ }), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(e, t);
40
55
  }
41
56
  mount(e) {
42
57
  e.appendChild(this.canvas);
@@ -55,152 +70,184 @@ class R {
55
70
  this.renderOrder = 0;
56
71
  const t = /* @__PURE__ */ new Set();
57
72
  this.reconcileNode(e, t);
58
- for (const [s, n] of this.meshMap.entries())
59
- t.has(s) || (this.scene.remove(n), n.geometry.dispose(), n.material instanceof c.Material && n.material.dispose(), this.meshMap.delete(s));
73
+ for (const [i, r] of this.meshMap.entries())
74
+ t.has(i) || (this.scene.remove(r), r.geometry.dispose(), r.material instanceof m.Material && r.material.dispose(), this.meshMap.delete(i));
60
75
  }
61
76
  reconcileNode(e, t) {
62
- var s, n;
63
- if (e.type === "BOX") {
64
- t.add(e.element);
65
- let r = this.meshMap.get(e.element);
66
- if (!r) {
67
- const a = new c.PlaneGeometry(1, 1), l = new c.MeshBasicMaterial({ transparent: !0 });
68
- r = new c.Mesh(a, l), this.scene.add(r), this.meshMap.set(e.element, r);
69
- }
70
- this.updateMeshProperties(r, e);
71
- for (const a of e.children)
72
- this.reconcileNode(a, t);
77
+ t.add(e.element);
78
+ let i = this.meshMap.get(e.element);
79
+ if (!i) {
80
+ const r = new m.PlaneGeometry(1, 1), s = new m.MeshBasicMaterial({ transparent: !0 });
81
+ i = new m.Mesh(r, s), e.type === "TEXT" && (i.name = "BG_MESH"), this.scene.add(i), this.meshMap.set(e.element, i);
73
82
  }
74
- if (e.type === "TEXT") {
75
- t.add(e.element);
76
- let r = this.meshMap.get(e.element);
77
- if (!r) {
78
- const d = new c.PlaneGeometry(1, 1), u = new c.MeshBasicMaterial({ transparent: !0 });
79
- r = new c.Mesh(d, u), r.name = "BG_MESH", this.scene.add(r), this.meshMap.set(e.element, r);
80
- }
81
- this.updateMeshProperties(r, e);
82
- let a = r.children.find(
83
- (d) => d.name === "TEXT_CHILD"
84
- );
85
- const l = JSON.stringify(e.textStyles), m = (s = a == null ? void 0 : a.userData) == null ? void 0 : s.styleHash, h = e.dirtyMask & x;
86
- if (!a || h || l !== m) {
87
- a && ((n = a.material.map) == null || n.dispose(), a.geometry.dispose(), r.remove(a));
88
- const d = N(
89
- e.textContent || "",
90
- e.textStyles,
91
- e.rect.width,
92
- e.rect.height
93
- ), u = new c.PlaneGeometry(1, 1), f = new c.MeshBasicMaterial({
94
- map: d,
95
- transparent: !0,
96
- side: c.FrontSide,
97
- color: 16777215,
98
- opacity: 1
99
- });
100
- a = new c.Mesh(u, f), a.name = "TEXT_CHILD", a.userData = { styleHash: l }, a.position.z = 5e-3, r.add(a);
101
- }
83
+ if (i.userData.domRect = e.rect, this.updateMeshProperties(i, e), e.type === "BOX")
84
+ for (const r of e.children)
85
+ this.reconcileNode(r, t);
86
+ else
87
+ e.type === "TEXT" && this.reconcileTextChild(i, e);
88
+ }
89
+ reconcileTextChild(e, t) {
90
+ var l, p;
91
+ let i = e.children.find(
92
+ (a) => a.name === "TEXT_CHILD"
93
+ );
94
+ const r = JSON.stringify(t.textStyles), s = (l = i == null ? void 0 : i.userData) == null ? void 0 : l.styleHash;
95
+ if (!i || t.dirtyMask & C || r !== s) {
96
+ i && ((p = i.material.map) == null || p.dispose(), i.geometry.dispose(), e.remove(i));
97
+ const a = N(
98
+ t.textContent || "",
99
+ t.textStyles,
100
+ t.rect.width,
101
+ t.rect.height
102
+ ), c = new m.PlaneGeometry(1, 1), h = new m.MeshBasicMaterial({
103
+ map: a,
104
+ transparent: !0,
105
+ side: m.FrontSide,
106
+ color: 16777215
107
+ });
108
+ i = new m.Mesh(c, h), i.name = "TEXT_CHILD", i.userData = { styleHash: r }, e.add(i);
109
+ }
110
+ if (i) {
111
+ const a = e.userData.domRect, c = a.x + a.width / 2, h = a.y + a.height / 2, u = t.rect.x + t.rect.width / 2, f = t.rect.y + t.rect.height / 2, d = u - c, w = -(f - h);
112
+ i.position.set(d, w, 5e-3);
102
113
  }
103
114
  }
104
115
  updateMeshProperties(e, t) {
105
- const { rect: s, styles: n } = t, r = this.renderer.domElement.width, a = this.renderer.domElement.height;
106
- e.scale.set(s.width, s.height, 1);
107
- const l = 1e-3;
116
+ const { rect: i, styles: r } = t, s = this.renderer.getPixelRatio(), g = this.renderer.domElement.width / s, l = this.renderer.domElement.height / s;
117
+ e.scale.set(i.width, i.height, 1);
118
+ const p = 1e-3;
108
119
  this.renderOrder++, e.position.set(
109
- s.x - r / 2 + s.width / 2,
110
- -s.y + a / 2 - s.height / 2,
111
- n.zIndex + this.renderOrder * l
120
+ i.x - g / 2 + i.width / 2,
121
+ -i.y + l / 2 - i.height / 2,
122
+ r.zIndex + this.renderOrder * p
112
123
  );
113
- const m = e.material, h = n.backgroundColor;
114
- let p = h, d = 1;
115
- if (h === "transparent" || h === "rgba(0, 0, 0, 0)")
116
- p = "#ffffff", d = 0;
117
- else if (h.startsWith("rgba")) {
118
- const f = h.match(/[\d.]+/g);
119
- if (f && f.length >= 4) {
120
- const E = f[0], M = f[1], v = f[2];
121
- d = parseFloat(f[3]), p = `rgb(${E}, ${M}, ${v})`;
124
+ const a = e.material, c = r.backgroundColor;
125
+ let h = c, u = 1;
126
+ if (c === "transparent" || c === "rgba(0, 0, 0, 0)")
127
+ h = "#ffffff", u = 0;
128
+ else if (c.startsWith("rgba")) {
129
+ const d = c.match(/[\d.]+/g);
130
+ if (d && d.length >= 4) {
131
+ const w = d[0], R = d[1], E = d[2];
132
+ u = parseFloat(d[3]), h = `rgb(${w}, ${R}, ${E})`;
122
133
  }
123
134
  }
124
- const u = n.opacity * d;
125
- m.color.set(p), m.opacity = u, m.transparent = u < 1;
135
+ const f = r.opacity * u;
136
+ a.color.set(h), a.opacity = f, a.transparent = f < 1;
126
137
  }
127
138
  render() {
128
139
  this.renderer.render(this.scene, this.camera);
129
140
  }
130
141
  }
131
- function F(i) {
132
- var e;
133
- return i.nodeType === Node.TEXT_NODE && (((e = i.textContent) == null ? void 0 : e.trim().length) || 0) > 0;
134
- }
135
- function z(i) {
136
- const e = Array.from(i.childNodes);
137
- return e.length === 0 || e.some(
138
- (n) => n.nodeType === Node.ELEMENT_NODE
139
- ) ? !1 : e.some(F);
142
+ function z(n) {
143
+ const e = document.createRange();
144
+ e.selectNodeContents(n);
145
+ const t = e.getBoundingClientRect();
146
+ return {
147
+ left: t.left,
148
+ top: t.top,
149
+ width: t.width,
150
+ height: t.height
151
+ };
140
152
  }
141
- function L(i) {
142
- const e = parseFloat(i.fontSize);
143
- let t = parseFloat(i.lineHeight);
153
+ function I(n) {
154
+ const e = parseFloat(n.fontSize);
155
+ let t = parseFloat(n.lineHeight);
144
156
  isNaN(t) && (t = e * 1.2);
145
- let s = parseFloat(i.letterSpacing);
146
- return isNaN(s) && (s = 0), {
147
- font: `${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,
148
- color: i.color,
149
- textAlign: i.textAlign || "start",
157
+ let i = parseFloat(n.letterSpacing);
158
+ return isNaN(i) && (i = 0), {
159
+ font: `${n.fontStyle} ${n.fontWeight} ${n.fontSize} ${n.fontFamily}`,
160
+ color: n.color,
161
+ textAlign: n.textAlign || "start",
150
162
  textBaseline: "alphabetic",
151
- direction: i.direction || "inherit",
163
+ direction: n.direction || "inherit",
152
164
  lineHeight: t,
153
- letterSpacing: s
165
+ letterSpacing: i
154
166
  };
155
167
  }
156
- function S(i, e = T | y | C | x | w) {
157
- const t = i.getBoundingClientRect(), s = window.getComputedStyle(i);
158
- if (t.width === 0 || t.height === 0 || s.display === "none")
168
+ function b(n, e = y | x | M | C | S) {
169
+ if (n.nodeType === Node.TEXT_NODE) {
170
+ const h = n;
171
+ if (!h.textContent || !h.textContent.trim())
172
+ return null;
173
+ const u = h.textContent.replace(/\s+/g, " ").trim();
174
+ if (u.length === 0)
175
+ return null;
176
+ const f = z(h);
177
+ if (f.width === 0 || f.height === 0)
178
+ return null;
179
+ const d = h.parentElement, w = d ? window.getComputedStyle(d) : null;
180
+ return w ? {
181
+ id: Math.random().toString(36).substring(2, 9),
182
+ type: "TEXT",
183
+ element: h,
184
+ rect: {
185
+ x: f.left + window.scrollX,
186
+ y: f.top + window.scrollY,
187
+ width: f.width,
188
+ height: f.height
189
+ },
190
+ styles: {
191
+ backgroundColor: "transparent",
192
+ opacity: parseFloat(w.opacity),
193
+ zIndex: 0,
194
+ borderRadius: "0px",
195
+ borderColor: "transparent",
196
+ borderWidth: "0px"
197
+ },
198
+ textContent: u,
199
+ textStyles: I(w),
200
+ dirtyMask: e,
201
+ children: []
202
+ } : null;
203
+ }
204
+ const t = n, i = t.getBoundingClientRect(), r = window.getComputedStyle(t);
205
+ if (i.width === 0 || i.height === 0 || r.display === "none")
159
206
  return null;
160
- let n = i.getAttribute("data-mid");
161
- n || (n = Math.random().toString(36).substring(2, 11), i.setAttribute("data-mid", n));
162
- const r = parseInt(s.zIndex), a = {
163
- backgroundColor: s.backgroundColor,
164
- opacity: parseFloat(s.opacity),
165
- zIndex: isNaN(r) ? 0 : r,
166
- borderRadius: s.borderRadius,
167
- borderColor: s.borderColor,
168
- borderWidth: s.borderWidth
207
+ let s = t.getAttribute("data-mid");
208
+ s || (s = Math.random().toString(36).substring(2, 11), t.setAttribute("data-mid", s));
209
+ const g = parseInt(r.zIndex), l = {
210
+ backgroundColor: r.backgroundColor,
211
+ opacity: parseFloat(r.opacity),
212
+ zIndex: isNaN(g) ? 0 : g,
213
+ borderRadius: r.borderRadius,
214
+ borderColor: r.borderColor,
215
+ borderWidth: r.borderWidth
169
216
  };
170
- let l = "BOX", m, h;
171
- const p = [];
172
- return z(i) ? (l = "TEXT", m = i.textContent || "", h = L(s)) : Array.from(i.children).forEach((d) => {
173
- const u = S(d, e);
174
- u && p.push(u);
217
+ let p, a;
218
+ const c = [];
219
+ return Array.from(t.childNodes).forEach((h) => {
220
+ const u = b(h, e);
221
+ u && c.push(u);
175
222
  }), {
176
- id: n,
177
- type: l,
178
- element: i,
223
+ id: s,
224
+ type: "BOX",
225
+ element: t,
179
226
  rect: {
180
- x: t.left + window.scrollX,
181
- y: t.top + window.scrollY,
182
- width: t.width,
183
- height: t.height
227
+ x: i.left + window.scrollX,
228
+ y: i.top + window.scrollY,
229
+ width: i.width,
230
+ height: i.height
184
231
  },
185
- styles: a,
186
- textContent: m,
187
- textStyles: h,
232
+ styles: l,
233
+ textContent: p,
234
+ textStyles: a,
188
235
  dirtyMask: e,
189
- children: p
236
+ children: c
190
237
  };
191
238
  }
192
- class O {
239
+ class H {
193
240
  constructor(e, t) {
194
241
  o(this, "target");
195
242
  o(this, "renderer");
196
243
  o(this, "observer");
197
244
  o(this, "isDomDirty", !1);
198
245
  o(this, "isRunning", !1);
199
- o(this, "pendingMask", g);
246
+ o(this, "pendingMask", T);
200
247
  o(this, "mutationTimer", null);
201
248
  o(this, "cssTimer", null);
202
249
  o(this, "onTransitionFinished", (e) => {
203
- this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= T | y, this.cssTimer = window.setTimeout(() => {
250
+ this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= y | x, this.cssTimer = window.setTimeout(() => {
204
251
  this.isDomDirty = !0, this.cssTimer = null;
205
252
  }, 50));
206
253
  });
@@ -210,12 +257,12 @@ class O {
210
257
  o(this, "renderLoop", () => {
211
258
  this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
212
259
  });
213
- this.target = e, this.renderer = t, this.observer = new MutationObserver((s) => {
214
- let n = g;
215
- for (const r of s)
216
- r.type === "childList" ? n |= w : r.type === "attributes" && (r.attributeName === "style" || r.attributeName === "class") && (n |= T | y);
217
- if (n !== g) {
218
- if (this.pendingMask |= n, n & w) {
260
+ this.target = e, this.renderer = t, this.observer = new MutationObserver((i) => {
261
+ let r = T;
262
+ for (const s of i)
263
+ s.type === "childList" ? r |= S : s.type === "attributes" && (s.attributeName === "style" || s.attributeName === "class") && (r |= y | x);
264
+ if (r !== T) {
265
+ if (this.pendingMask |= r, r & S) {
219
266
  this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
220
267
  return;
221
268
  }
@@ -241,11 +288,11 @@ class O {
241
288
  }
242
289
  forceUpdateScene() {
243
290
  this.isDomDirty = !1;
244
- const e = S(this.target, this.pendingMask);
245
- e && this.renderer.syncScene(e), this.pendingMask = g;
291
+ const e = b(this.target, this.pendingMask);
292
+ e && this.renderer.syncScene(e), this.pendingMask = T;
246
293
  }
247
294
  }
248
- class _ {
295
+ class X {
249
296
  constructor(e) {
250
297
  o(this, "renderer");
251
298
  o(this, "syncer");
@@ -253,7 +300,7 @@ class _ {
253
300
  const t = document.querySelector(e);
254
301
  if (!t)
255
302
  throw new Error(`[Mirage] Element not found: ${e}`);
256
- this.target = t, this.renderer = new R(), this.renderer.mount(document.body), this.syncer = new O(this.target, this.renderer);
303
+ this.target = t, this.renderer = new L(), this.renderer.mount(document.body), this.syncer = new H(this.target, this.renderer);
257
304
  }
258
305
  start() {
259
306
  this.syncer.start();
@@ -263,5 +310,5 @@ class _ {
263
310
  }
264
311
  }
265
312
  export {
266
- _ as Mirage
313
+ X as Mirage
267
314
  };
@@ -1 +1,2 @@
1
- (function(l,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],d):(l=typeof globalThis<"u"?globalThis:l||self,d(l.MirageEngine={},l.THREE))})(this,function(l,d){"use strict";var H=Object.defineProperty;var k=(l,d,T)=>d in l?H(l,d,{enumerable:!0,configurable:!0,writable:!0,value:T}):l[d]=T;var o=(l,d,T)=>(k(l,typeof d!="symbol"?d+"":d,T),T);function T(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const c=T(d),w=0,S=1,x=2,v=4,E=8,M=16;function D(i,e,t,r){const s=document.createElement("canvas"),n=s.getContext("2d");if(!n)throw new Error("[Mirage] Failed to create canvas context");const a=window.devicePixelRatio||2;s.width=t*a,s.height=r*a,n.scale(a,a),n.font=e.font,n.fillStyle=e.color,n.textAlign=e.textAlign,n.direction=e.direction,n.textBaseline="middle";let m=0;e.textAlign==="center"&&(m=t/2),e.textAlign==="right"&&(m=t);const f=r/2;n.fillText(i,m,f);const h=new c.CanvasTexture(s);return h.colorSpace=c.SRGBColorSpace,h.minFilter=c.LinearFilter,h.needsUpdate=!0,h}class C{constructor(){o(this,"canvas");o(this,"scene");o(this,"camera");o(this,"renderer");o(this,"renderOrder",0);o(this,"meshMap",new Map);this.canvas=document.createElement("canvas"),this.scene=new c.Scene;const e=window.innerWidth,t=window.innerHeight;this.camera=new c.OrthographicCamera(e/-2,e/2,t/2,t/-2,1,1e3),this.camera.position.z=100,this.renderer=new c.WebGLRenderer({canvas:this.canvas,alpha:!0}),this.renderer.setSize(e,t)}mount(e){e.appendChild(this.canvas)}dispose(){try{this.renderer.dispose()}catch{}this.canvas.parentElement&&this.canvas.parentElement.removeChild(this.canvas)}setSize(e,t){this.renderer.setSize(e,t),this.camera.left=e/-2,this.camera.right=e/2,this.camera.top=t/2,this.camera.bottom=t/-2,this.camera.updateProjectionMatrix()}syncScene(e){this.renderOrder=0;const t=new Set;this.reconcileNode(e,t);for(const[r,s]of this.meshMap.entries())t.has(r)||(this.scene.remove(s),s.geometry.dispose(),s.material instanceof c.Material&&s.material.dispose(),this.meshMap.delete(r))}reconcileNode(e,t){var r,s;if(e.type==="BOX"){t.add(e.element);let n=this.meshMap.get(e.element);if(!n){const a=new c.PlaneGeometry(1,1),m=new c.MeshBasicMaterial({transparent:!0});n=new c.Mesh(a,m),this.scene.add(n),this.meshMap.set(e.element,n)}this.updateMeshProperties(n,e);for(const a of e.children)this.reconcileNode(a,t)}if(e.type==="TEXT"){t.add(e.element);let n=this.meshMap.get(e.element);if(!n){const u=new c.PlaneGeometry(1,1),p=new c.MeshBasicMaterial({transparent:!0});n=new c.Mesh(u,p),n.name="BG_MESH",this.scene.add(n),this.meshMap.set(e.element,n)}this.updateMeshProperties(n,e);let a=n.children.find(u=>u.name==="TEXT_CHILD");const m=JSON.stringify(e.textStyles),f=(r=a==null?void 0:a.userData)==null?void 0:r.styleHash,h=e.dirtyMask&M;if(!a||h||m!==f){a&&((s=a.material.map)==null||s.dispose(),a.geometry.dispose(),n.remove(a));const u=D(e.textContent||"",e.textStyles,e.rect.width,e.rect.height),p=new c.PlaneGeometry(1,1),g=new c.MeshBasicMaterial({map:u,transparent:!0,side:c.FrontSide,color:16777215,opacity:1});a=new c.Mesh(p,g),a.name="TEXT_CHILD",a.userData={styleHash:m},a.position.z=.005,n.add(a)}}}updateMeshProperties(e,t){const{rect:r,styles:s}=t,n=this.renderer.domElement.width,a=this.renderer.domElement.height;e.scale.set(r.width,r.height,1);const m=.001;this.renderOrder++,e.position.set(r.x-n/2+r.width/2,-r.y+a/2-r.height/2,s.zIndex+this.renderOrder*m);const f=e.material,h=s.backgroundColor;let y=h,u=1;if(h==="transparent"||h==="rgba(0, 0, 0, 0)")y="#ffffff",u=0;else if(h.startsWith("rgba")){const g=h.match(/[\d.]+/g);if(g&&g.length>=4){const L=g[0],_=g[1],I=g[2];u=parseFloat(g[3]),y=`rgb(${L}, ${_}, ${I})`}}const p=s.opacity*u;f.color.set(y),f.opacity=p,f.transparent=p<1}render(){this.renderer.render(this.scene,this.camera)}}function N(i){var e;return i.nodeType===Node.TEXT_NODE&&(((e=i.textContent)==null?void 0:e.trim().length)||0)>0}function R(i){const e=Array.from(i.childNodes);return e.length===0||e.some(s=>s.nodeType===Node.ELEMENT_NODE)?!1:e.some(N)}function O(i){const e=parseFloat(i.fontSize);let t=parseFloat(i.lineHeight);isNaN(t)&&(t=e*1.2);let r=parseFloat(i.letterSpacing);return isNaN(r)&&(r=0),{font:`${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,color:i.color,textAlign:i.textAlign||"start",textBaseline:"alphabetic",direction:i.direction||"inherit",lineHeight:t,letterSpacing:r}}function b(i,e=S|x|v|M|E){const t=i.getBoundingClientRect(),r=window.getComputedStyle(i);if(t.width===0||t.height===0||r.display==="none")return null;let s=i.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),i.setAttribute("data-mid",s));const n=parseInt(r.zIndex),a={backgroundColor:r.backgroundColor,opacity:parseFloat(r.opacity),zIndex:isNaN(n)?0:n,borderRadius:r.borderRadius,borderColor:r.borderColor,borderWidth:r.borderWidth};let m="BOX",f,h;const y=[];return R(i)?(m="TEXT",f=i.textContent||"",h=O(r)):Array.from(i.children).forEach(u=>{const p=b(u,e);p&&y.push(p)}),{id:s,type:m,element:i,rect:{x:t.left+window.scrollX,y:t.top+window.scrollY,width:t.width,height:t.height},styles:a,textContent:f,textStyles:h,dirtyMask:e,children:y}}class z{constructor(e,t){o(this,"target");o(this,"renderer");o(this,"observer");o(this,"isDomDirty",!1);o(this,"isRunning",!1);o(this,"pendingMask",w);o(this,"mutationTimer",null);o(this,"cssTimer",null);o(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=S|x,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});o(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});o(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.observer=new MutationObserver(r=>{let s=w;for(const n of r)n.type==="childList"?s|=E:n.type==="attributes"&&(n.attributeName==="style"||n.attributeName==="class")&&(s|=S|x);if(s!==w){if(this.pendingMask|=s,s&E){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const e=b(this.target,this.pendingMask);e&&this.renderer.syncScene(e),this.pendingMask=w}}class F{constructor(e){o(this,"renderer");o(this,"syncer");o(this,"target");const t=document.querySelector(e);if(!t)throw new Error(`[Mirage] Element not found: ${e}`);this.target=t,this.renderer=new C,this.renderer.mount(document.body),this.syncer=new z(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop(),this.renderer.dispose()}}l.Mirage=F,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
1
+ (function(m,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],f):(m=typeof globalThis<"u"?globalThis:m||self,f(m.MirageEngine={},m.THREE))})(this,function(m,f){"use strict";var X=Object.defineProperty;var k=(m,f,y)=>f in m?X(m,f,{enumerable:!0,configurable:!0,writable:!0,value:y}):m[f]=y;var o=(m,f,y)=>(k(m,typeof f!="symbol"?f+"":f,y),y);function y(n){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const t in n)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>n[t]})}}return e.default=n,Object.freeze(e)}const d=y(f),S=0,b=1,C=2,D=4,E=8,R=16;function M(n,e,t){const i=e.split(`
2
+ `),r=[];return i.forEach(s=>{const T=s.split(" ");let l=T[0];for(let w=1;w<T.length;w++){const a=T[w];n.measureText(l+" "+a).width<t?l+=" "+a:(r.push(l),l=a)}r.push(l)}),r}function F(n,e,t,i){const r=document.createElement("canvas"),s=r.getContext("2d");if(!s)throw new Error("[Mirage] Failed to create canvas context");const T=2,l=(window.devicePixelRatio||2)*T;r.width=t*l,r.height=i*l,s.scale(l,l),s.font=e.font,s.fillStyle=e.color,s.textBaseline="top",s.globalAlpha=1;const w=M(s,n,t),a=e.lineHeight;w.forEach((h,p)=>{const g=p*a+2;let u=0;e.textAlign==="center"?u=t/2:e.textAlign==="right"&&(u=t),s.textAlign=e.textAlign,s.fillText(h,u,g)});const c=new d.CanvasTexture(r);return c.colorSpace=d.SRGBColorSpace,c.minFilter=d.LinearFilter,c.magFilter=d.LinearFilter,c.needsUpdate=!0,c}class N{constructor(){o(this,"canvas");o(this,"scene");o(this,"camera");o(this,"renderer");o(this,"renderOrder",0);o(this,"meshMap",new Map);this.canvas=document.createElement("canvas"),this.scene=new d.Scene;const e=window.innerWidth,t=window.innerHeight;this.camera=new d.OrthographicCamera(e/-2,e/2,t/2,t/-2,1,1e3),this.camera.position.z=100,this.renderer=new d.WebGLRenderer({canvas:this.canvas,alpha:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(e,t)}mount(e){e.appendChild(this.canvas)}dispose(){try{this.renderer.dispose()}catch{}this.canvas.parentElement&&this.canvas.parentElement.removeChild(this.canvas)}setSize(e,t){this.renderer.setSize(e,t),this.camera.left=e/-2,this.camera.right=e/2,this.camera.top=t/2,this.camera.bottom=t/-2,this.camera.updateProjectionMatrix()}syncScene(e){this.renderOrder=0;const t=new Set;this.reconcileNode(e,t);for(const[i,r]of this.meshMap.entries())t.has(i)||(this.scene.remove(r),r.geometry.dispose(),r.material instanceof d.Material&&r.material.dispose(),this.meshMap.delete(i))}reconcileNode(e,t){t.add(e.element);let i=this.meshMap.get(e.element);if(!i){const r=new d.PlaneGeometry(1,1),s=new d.MeshBasicMaterial({transparent:!0});i=new d.Mesh(r,s),e.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(e.element,i)}if(i.userData.domRect=e.rect,this.updateMeshProperties(i,e),e.type==="BOX")for(const r of e.children)this.reconcileNode(r,t);else e.type==="TEXT"&&this.reconcileTextChild(i,e)}reconcileTextChild(e,t){var l,w;let i=e.children.find(a=>a.name==="TEXT_CHILD");const r=JSON.stringify(t.textStyles),s=(l=i==null?void 0:i.userData)==null?void 0:l.styleHash;if(!i||t.dirtyMask&R||r!==s){i&&((w=i.material.map)==null||w.dispose(),i.geometry.dispose(),e.remove(i));const a=F(t.textContent||"",t.textStyles,t.rect.width,t.rect.height),c=new d.PlaneGeometry(1,1),h=new d.MeshBasicMaterial({map:a,transparent:!0,side:d.FrontSide,color:16777215});i=new d.Mesh(c,h),i.name="TEXT_CHILD",i.userData={styleHash:r},e.add(i)}if(i){const a=e.userData.domRect,c=a.x+a.width/2,h=a.y+a.height/2,p=t.rect.x+t.rect.width/2,g=t.rect.y+t.rect.height/2,u=p-c,x=-(g-h);i.position.set(u,x,.005)}}updateMeshProperties(e,t){const{rect:i,styles:r}=t,s=this.renderer.getPixelRatio(),T=this.renderer.domElement.width/s,l=this.renderer.domElement.height/s;e.scale.set(i.width,i.height,1);const w=.001;this.renderOrder++,e.position.set(i.x-T/2+i.width/2,-i.y+l/2-i.height/2,r.zIndex+this.renderOrder*w);const a=e.material,c=r.backgroundColor;let h=c,p=1;if(c==="transparent"||c==="rgba(0, 0, 0, 0)")h="#ffffff",p=0;else if(c.startsWith("rgba")){const u=c.match(/[\d.]+/g);if(u&&u.length>=4){const x=u[0],I=u[1],_=u[2];p=parseFloat(u[3]),h=`rgb(${x}, ${I}, ${_})`}}const g=r.opacity*p;a.color.set(h),a.opacity=g,a.transparent=g<1}render(){this.renderer.render(this.scene,this.camera)}}function O(n){const e=document.createRange();e.selectNodeContents(n);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function z(n){const e=parseFloat(n.fontSize);let t=parseFloat(n.lineHeight);isNaN(t)&&(t=e*1.2);let i=parseFloat(n.letterSpacing);return isNaN(i)&&(i=0),{font:`${n.fontStyle} ${n.fontWeight} ${n.fontSize} ${n.fontFamily}`,color:n.color,textAlign:n.textAlign||"start",textBaseline:"alphabetic",direction:n.direction||"inherit",lineHeight:t,letterSpacing:i}}function v(n,e=b|C|D|R|E){if(n.nodeType===Node.TEXT_NODE){const h=n;if(!h.textContent||!h.textContent.trim())return null;const p=h.textContent.replace(/\s+/g," ").trim();if(p.length===0)return null;const g=O(h);if(g.width===0||g.height===0)return null;const u=h.parentElement,x=u?window.getComputedStyle(u):null;return x?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:h,rect:{x:g.left+window.scrollX,y:g.top+window.scrollY,width:g.width,height:g.height},styles:{backgroundColor:"transparent",opacity:parseFloat(x.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:p,textStyles:z(x),dirtyMask:e,children:[]}:null}const t=n,i=t.getBoundingClientRect(),r=window.getComputedStyle(t);if(i.width===0||i.height===0||r.display==="none")return null;let s=t.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),t.setAttribute("data-mid",s));const T=parseInt(r.zIndex),l={backgroundColor:r.backgroundColor,opacity:parseFloat(r.opacity),zIndex:isNaN(T)?0:T,borderRadius:r.borderRadius,borderColor:r.borderColor,borderWidth:r.borderWidth};let w,a;const c=[];return Array.from(t.childNodes).forEach(h=>{const p=v(h,e);p&&c.push(p)}),{id:s,type:"BOX",element:t,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:l,textContent:w,textStyles:a,dirtyMask:e,children:c}}class L{constructor(e,t){o(this,"target");o(this,"renderer");o(this,"observer");o(this,"isDomDirty",!1);o(this,"isRunning",!1);o(this,"pendingMask",S);o(this,"mutationTimer",null);o(this,"cssTimer",null);o(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=b|C,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});o(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});o(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.observer=new MutationObserver(i=>{let r=S;for(const s of i)s.type==="childList"?r|=E:s.type==="attributes"&&(s.attributeName==="style"||s.attributeName==="class")&&(r|=b|C);if(r!==S){if(this.pendingMask|=r,r&E){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const e=v(this.target,this.pendingMask);e&&this.renderer.syncScene(e),this.pendingMask=S}}class H{constructor(e){o(this,"renderer");o(this,"syncer");o(this,"target");const t=document.querySelector(e);if(!t)throw new Error(`[Mirage] Element not found: ${e}`);this.target=t,this.renderer=new N,this.renderer.mount(document.body),this.syncer=new L(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop(),this.renderer.dispose()}}m.Mirage=H,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})});
@@ -1,2 +1,2 @@
1
1
  import { SceneNode } from '../types';
2
- export declare function extractSceneGraph(element: HTMLElement, initialMask?: number): SceneNode | null;
2
+ export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask?: number): SceneNode | null;
@@ -12,6 +12,7 @@ export declare class Renderer {
12
12
  setSize(width: number, height: number): void;
13
13
  syncScene(graphNode: SceneNode): void;
14
14
  private reconcileNode;
15
+ private reconcileTextChild;
15
16
  private updateMeshProperties;
16
17
  render(): void;
17
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirage-engine",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "An engine that mirrors HTML DOM elements to a WebGL scene in real-time.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",