@mirage-engine/core 0.0.2 → 0.2.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 +17 -0
- package/dist/mirage-engine.js +483 -299
- package/dist/mirage-engine.umd.js +57 -27
- package/dist/src/core/Syncer.d.ts +7 -1
- package/dist/src/dom/Extractor.d.ts +3 -2
- package/dist/src/renderer/Renderer.d.ts +6 -2
- package/dist/src/types/common.d.ts +3 -0
- package/dist/src/types/config.d.ts +16 -2
- package/dist/src/types/flags.d.ts +5 -0
- package/package.json +2 -2
- package/src/core/Engine.ts +1 -1
- package/src/core/Syncer.ts +72 -4
- package/src/dom/Extractor.ts +87 -4
- package/src/renderer/Renderer.ts +135 -31
- package/src/types/common.ts +8 -5
- package/src/types/config.ts +21 -6
- package/src/types/flags.ts +16 -1
|
@@ -1,55 +1,85 @@
|
|
|
1
|
-
(function(
|
|
2
|
-
`),
|
|
1
|
+
(function(u,T){typeof exports=="object"&&typeof module!="undefined"?T(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],T):(u=typeof globalThis!="undefined"?globalThis:u||self,T(u.MirageEngine={},u.THREE))})(this,function(u,T){"use strict";var ee=Object.defineProperty;var te=(u,T,w)=>T in u?ee(u,T,{enumerable:!0,configurable:!0,writable:!0,value:w}):u[T]=w;var d=(u,T,w)=>(te(u,typeof T!="symbol"?T+"":T,w),w);function w(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 h=w(T),M=0,B=1,O=2,A=4,D=8,L=16,R=1,E=2,q=0,X=["include-tree","exclude-tree","include-self","exclude-self","end"];function W(i,e,t){const r=e.split(`
|
|
2
|
+
`),s=[];return r.forEach(n=>{const a=n.split(" ");let o=a[0];for(let l=1;l<a.length;l++){const c=a[l];i.measureText(o+" "+c).width<t?o+=" "+c:(s.push(o),o=c)}s.push(o)}),s}function Y(i,e,t,r,s=2){const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const l=(window.devicePixelRatio||1)*s;n.width=t*l,n.height=r*l,a.scale(l,l),a.font=e.font,a.fillStyle=e.color,a.textBaseline="top",a.globalAlpha=1;const c=W(a,i,t),p=e.lineHeight;c.forEach((m,v)=>{const b=v*p+2;let y=0;e.textAlign==="center"?y=t/2:e.textAlign==="right"&&(y=t),a.textAlign=e.textAlign,a.fillText(m,y,b)});const f=new h.CanvasTexture(n);return f.colorSpace=h.SRGBColorSpace,f.minFilter=h.LinearFilter,f.magFilter=h.LinearFilter,f.needsUpdate=!0,f}function S(i){return typeof i=="number"?i:parseFloat(i)||0}function P(i,e){var o,l,c,p;if(!e){i.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),r=S(t[0]),s=S((o=t[1])!=null?o:t[0]),n=S((l=t[2])!=null?l:t[0]),a=S((p=(c=t[3])!=null?c:t[1])!=null?p:t[0]);i.set(r,s,n,a)}const H=`
|
|
3
3
|
varying vec2 vUv;
|
|
4
|
+
varying vec4 vScreenPos;
|
|
4
5
|
void main() {
|
|
5
6
|
vUv = uv;
|
|
6
7
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
8
|
+
vScreenPos = gl_Position;
|
|
7
9
|
}
|
|
8
|
-
|
|
10
|
+
`,$=`
|
|
11
|
+
|
|
9
12
|
varying vec2 vUv;
|
|
10
13
|
|
|
11
14
|
uniform vec2 uSize;
|
|
12
|
-
uniform
|
|
15
|
+
uniform vec4 uBorderRadius;
|
|
13
16
|
uniform float uBorderWidth;
|
|
14
|
-
uniform vec3
|
|
17
|
+
uniform vec3 uBgColor;
|
|
15
18
|
uniform vec3 uBorderColor;
|
|
16
19
|
uniform float uOpacity;
|
|
17
20
|
uniform float uBgOpacity;
|
|
21
|
+
uniform float uBorderOpacity;
|
|
22
|
+
|
|
23
|
+
#INJECT_DECLARATIONS
|
|
18
24
|
|
|
19
|
-
// SDF 박스 함수
|
|
20
25
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
21
26
|
vec2 q = abs(p) - b + r;
|
|
27
|
+
|
|
22
28
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
void main() {
|
|
26
32
|
vec2 p = (vUv - 0.5) * uSize;
|
|
27
33
|
vec2 halfSize = uSize * 0.5;
|
|
28
|
-
|
|
29
|
-
float d = sdRoundedBox(p, halfSize, uRadius);
|
|
30
|
-
|
|
31
|
-
float smoothEdge = 1.0;
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
#INJECT_UV_MODIFIER
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (uBorderWidth > 0.01) {
|
|
38
|
-
borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
|
|
39
|
-
}
|
|
37
|
+
// color decision pipeline
|
|
38
|
+
vec4 baseColor = vec4(uBgColor, uBgOpacity);
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
#INJECT_BASE_COLOR
|
|
41
|
+
|
|
42
|
+
// sdf shape pipeline
|
|
43
|
+
vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
|
|
44
|
+
float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
|
|
45
|
+
float d = sdRoundedBox(p, halfSize, r);
|
|
46
|
+
|
|
47
|
+
float aa = 1.0;
|
|
48
|
+
float bgMask = 1.0 - smoothstep(0.0, aa, d);
|
|
49
|
+
|
|
50
|
+
float halfBorder = uBorderWidth * 0.5;
|
|
51
|
+
float borderD = abs(d + halfBorder) - halfBorder;
|
|
52
|
+
float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
|
|
53
|
+
if (uBorderWidth <= 0.01) {
|
|
54
|
+
borderAlpha = 0.0;
|
|
46
55
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
float
|
|
50
|
-
|
|
56
|
+
|
|
57
|
+
// final blending (border + background)
|
|
58
|
+
float aFront = borderAlpha;
|
|
59
|
+
float aBack = baseColor.a;
|
|
60
|
+
float aOut = aFront + aBack * (1.0 - aFront);
|
|
61
|
+
|
|
62
|
+
float safeAlpha = max(aOut, 0.0001);
|
|
63
|
+
vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
|
|
64
|
+
vec4 finalColor = vec4(cOut, aOut);
|
|
65
|
+
|
|
66
|
+
// final color control (Tint, Noise)
|
|
67
|
+
#INJECT_COLOR_MODIFIER
|
|
68
|
+
|
|
69
|
+
float finalOpacity = finalColor.a * bgMask * uOpacity;
|
|
51
70
|
if (finalOpacity < 0.001) discard;
|
|
52
71
|
|
|
53
|
-
gl_FragColor = vec4(
|
|
72
|
+
gl_FragColor = vec4(finalColor.rgb, finalOpacity);
|
|
73
|
+
|
|
74
|
+
#include <colorspace_fragment>
|
|
54
75
|
}
|
|
55
|
-
`;function E(r){if(!r||r==="transparent")return{color:new h.Color(16777215),alpha:0};const t=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(t){const e=parseInt(t[1],10),i=parseInt(t[2],10),n=parseInt(t[3],10),s=t[4]!==void 0?parseFloat(t[4]):1;return{color:new h.Color(`rgb(${e}, ${i}, ${n})`),alpha:s}}return{color:new h.Color(r),alpha:1}}function L(r,t,e){var o;const i=E(r.backgroundColor),n=E(r.borderColor),s={uSize:{value:new h.Vector2(t,e)},uRadius:{value:S(r.borderRadius)},uBorderWidth:{value:S(r.borderWidth)},uColor:{value:i.color},uBorderColor:{value:n.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha}};return new h.ShaderMaterial({uniforms:s,vertexShader:F,fragmentShader:N,transparent:!0,side:h.FrontSide})}function A(r,t,e,i){var a;const n=E(t.backgroundColor),s=E(t.borderColor);r.uniforms.uSize.value.set(e,i),r.uniforms.uRadius.value=S(t.borderRadius),r.uniforms.uBorderWidth.value=S(t.borderWidth),r.uniforms.uColor.value.copy(n.color),r.uniforms.uBorderColor.value.copy(s.color),r.uniforms.uOpacity.value=(a=t.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=n.alpha}const D={create(r,t,e,i,n,s=2){if(r==="BOX")return L(t,i,n);if(r==="TEXT"){const a=O(e||"",t,i,n,s);return new h.MeshBasicMaterial({map:a,transparent:!0,side:h.FrontSide,color:16777215})}return new h.MeshBasicMaterial({visible:!1})},update(r,t,e,i,n,s,a=2){if(t==="BOX")A(r,e,n,s);else if(t==="TEXT"){const o=r;o.map&&o.map.dispose();const l=O(i||"",e,n,s,a);o.map=l,o.needsUpdate=!0}}};class X{constructor(t,e,i){c(this,"canvas");c(this,"scene");c(this,"camera");c(this,"renderer");c(this,"renderOrder",0);c(this,"textQualityFactor",2);c(this,"mode","overlay");c(this,"customZIndex","9999");c(this,"target");c(this,"mountContainer");c(this,"targetRect");c(this,"meshMap",new Map);var a,o,l;this.target=t,this.mountContainer=i,this.mode=(a=e.mode)!=null?a:"overlay",(o=e.style)!=null&&o.zIndex&&(this.customZIndex=e.style.zIndex),this.canvas=document.createElement("canvas"),this.scene=new h.Scene,this.targetRect=this.target.getBoundingClientRect();const n=this.targetRect.width,s=this.targetRect.height;this.camera=new h.OrthographicCamera(n/-2,n/2,s/2,s/-2,1,1e3),this.camera.position.z=100,this.renderer=new h.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(n,s),this.applyTextQuality((l=e.textQuality)!=null?l:"medium")}applyTextQuality(t){if(typeof t=="number"){this.textQualityFactor=Math.max(.1,t);return}switch(t){case"low":this.textQualityFactor=1;break;case"high":this.textQualityFactor=4;break;case"medium":default:this.textQualityFactor=2;break}}mount(){this.mountContainer.appendChild(this.canvas),this.canvas.style.zIndex=this.customZIndex,this.canvas.style.pointerEvents=this.mode==="overlay"?"none":"auto",this.updateCanvasLayout()}updateCanvasLayout(){this.canvas.style.width=`${this.targetRect.width}px`,this.canvas.style.height=`${this.targetRect.height}px`,this.mode==="duplicate"?(this.canvas.style.position="",this.canvas.style.top="",this.canvas.style.left="",this.canvas.style.display="block"):(this.canvas.style.position="absolute",this.canvas.style.top=`${this.target.offsetTop}px`,this.canvas.style.left=`${this.target.offsetLeft}px`,this.canvas.style.display="block")}dispose(){this.renderer.dispose(),this.canvas.remove()}setSize(t,e){this.renderer.setSize(t,e),this.camera.left=t/-2,this.camera.right=t/2,this.camera.top=e/2,this.camera.bottom=e/-2,this.camera.updateProjectionMatrix()}syncScene(t){const e=this.target.getBoundingClientRect(),i=Math.abs(e.width-this.targetRect.width)>.1||Math.abs(e.height-this.targetRect.height)>.1,n=this.mode==="overlay"&&(Math.abs(e.top-this.targetRect.top)>.1||Math.abs(e.left-this.targetRect.left)>.1);i?(this.targetRect=e,this.renderer.setSize(this.targetRect.width,this.targetRect.height),this.camera.left=this.targetRect.width/-2,this.camera.right=this.targetRect.width/2,this.camera.top=this.targetRect.height/2,this.camera.bottom=this.targetRect.height/-2,this.camera.updateProjectionMatrix(),this.updateCanvasLayout()):n?(this.targetRect=e,this.updateCanvasLayout()):this.targetRect=e,this.renderOrder=0;const s=new Set;this.reconcileNode(t,s);for(const[a,o]of this.meshMap.entries())s.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof h.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(t,e){e.add(t.element);let i=this.meshMap.get(t.element);if(!i){const n=new h.PlaneGeometry(1,1),s=D.create("BOX",t.styles,"",t.rect.width,t.rect.height);i=new h.Mesh(n,s),t.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(t.element,i)}if(i.userData.domRect=t.rect,this.updateMeshProperties(i,t),t.type==="BOX")for(const n of t.children)this.reconcileNode(n,e);else t.type==="TEXT"&&this.reconcileTextChild(i,t)}reconcileTextChild(t,e){var o,l;let i=t.children.find(u=>u.name==="TEXT_CHILD");const n=JSON.stringify(e.textStyles),s=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||e.dirtyMask&M||n!==s){i&&((l=i.material.map)==null||l.dispose(),i.geometry.dispose(),t.remove(i));const u=D.create("TEXT",e.textStyles,e.textContent||"",e.rect.width,e.rect.height,this.textQualityFactor),m=new h.PlaneGeometry(1,1);i=new h.Mesh(m,u),i.name="TEXT_CHILD",i.userData={styleHash:n},i.position.z=.005,t.add(i)}if(i){const u=t.userData.domRect,m=u.x+u.width/2,p=u.y+u.height/2,g=e.rect.x+e.rect.width/2,y=e.rect.y+e.rect.height/2,w=g-m,v=-(y-p);i.position.set(w,v,.005)}}updateMeshProperties(t,e){const{rect:i,styles:n}=e,s=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/s,o=this.renderer.domElement.height/s;t.scale.set(i.width,i.height,1);const l=.001;this.renderOrder++;const u=this.targetRect.left+window.scrollX,m=this.targetRect.top+window.scrollY,p=i.x-u,g=i.y-m;t.position.set(p-a/2+i.width/2,-g+o/2-i.height/2,n.zIndex+this.renderOrder*l),D.update(t.material,"BOX",e.styles,"",e.rect.width,e.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function _(r){const t=document.createRange();t.selectNodeContents(r);const e=t.getBoundingClientRect();return{left:e.left,top:e.top,width:e.width,height:e.height}}function Y(r){const t=parseFloat(r.fontSize);let e=parseFloat(r.lineHeight);isNaN(e)&&(e=t*1.2);let i=parseFloat(r.letterSpacing);return isNaN(i)&&(i=0),{font:`${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,color:r.color,textAlign:r.textAlign||"start",textBaseline:"alphabetic",direction:r.direction||"inherit",lineHeight:e,letterSpacing:i}}function z(r,t=R|b|B|M|C){if(r.nodeType===Node.TEXT_NODE){const p=r;if(!p.textContent||!p.textContent.trim())return null;const g=p.textContent.replace(/\s+/g," ").trim();if(g.length===0)return null;const y=_(p);if(y.width===0||y.height===0)return null;const w=p.parentElement,v=w?window.getComputedStyle(w):null;return v?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:p,rect:{x:y.left+window.scrollX,y:y.top+window.scrollY,width:y.width,height:y.height},styles:{backgroundColor:"transparent",opacity:parseFloat(v.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:g,textStyles:Y(v),dirtyMask:t,children:[]}:null}const e=r,i=e.getBoundingClientRect(),n=window.getComputedStyle(e);if(i.width===0||i.height===0||n.display==="none")return null;let s=e.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),e.setAttribute("data-mid",s));const a=parseInt(n.zIndex),o={backgroundColor:n.backgroundColor,opacity:parseFloat(n.opacity),zIndex:isNaN(a)?0:a,borderRadius:n.borderRadius,borderColor:n.borderColor,borderWidth:n.borderWidth};let l,u;const m=[];return Array.from(e.childNodes).forEach(p=>{const g=z(p,t);g&&m.push(g)}),{id:s,type:"BOX",element:e,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:o,textContent:l,textStyles:u,dirtyMask:t,children:m}}class k{constructor(t,e){c(this,"target");c(this,"renderer");c(this,"observer");c(this,"isDomDirty",!1);c(this,"isRunning",!1);c(this,"pendingMask",x);c(this,"mutationTimer",null);c(this,"cssTimer",null);c(this,"onTransitionFinished",t=>{this.target.contains(t.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=R|b,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});c(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});c(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=t,this.renderer=e,this.observer=new MutationObserver(i=>{let n=x;for(const s of i)s.type==="childList"?n|=C:s.type==="attributes"&&(s.attributeName==="style"||s.attributeName==="class")&&(n|=R|b);if(n!==x){if(this.pendingMask|=n,n&C){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 t=z(this.target,this.pendingMask);t&&this.renderer.syncScene(t),this.pendingMask=x}}class P{constructor(t,e){c(this,"renderer");c(this,"syncer");c(this,"target");var n,s,a;this.target=t;let i;if(e.mode==="duplicate"?i=(s=(n=e.container)!=null?n:this.target.parentElement)!=null?s:void 0:i=(a=this.target.parentElement)!=null?a:void 0,!i)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new X(this.target,e,i),this.renderer.mount(),this.syncer=new k(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}d.DIRTY_CONTENT=M,d.DIRTY_NONE=x,d.DIRTY_RECT=R,d.DIRTY_STRUCTURE=C,d.DIRTY_STYLE=b,d.DIRTY_ZINDEX=B,d.Engine=P,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
|
|
76
|
+
`;function F(i){if(!i||i==="transparent")return{color:new h.Color(16777215),alpha:0};const e=i.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(e){const t=parseInt(e[1],10),r=parseInt(e[2],10),s=parseInt(e[3],10),n=e[4]!==void 0?parseFloat(e[4]):1;return{color:new h.Color(`rgb(${t}, ${r}, ${s})`),alpha:n}}return{color:new h.Color(i),alpha:1}}function G(i,e,t,r=null,s){var y;const n=r!==null,a=n?`
|
|
77
|
+
uniform sampler2D uTexture;
|
|
78
|
+
varying vec4 vScreenPos;
|
|
79
|
+
`:"",o=n?`
|
|
80
|
+
vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
|
|
81
|
+
vec2 resultUv = screenUv;
|
|
82
|
+
${(s==null?void 0:s.uvModifier)||""}
|
|
83
|
+
`:"",l=n?`
|
|
84
|
+
baseColor = texture2D(uTexture, resultUv);
|
|
85
|
+
`:"",c=(s==null?void 0:s.colorModifier)||"",p=$.replace("#INJECT_DECLARATIONS",a).replace("#INJECT_UV_MODIFIER",o).replace("#INJECT_BASE_COLOR",l).replace("#INJECT_COLOR_MODIFIER",c),f=F(i.backgroundColor),m=F(i.borderColor),v={uSize:{value:new h.Vector2(e,t)},uBorderRadius:{value:new h.Vector4(0,0,0,0)},uBorderWidth:{value:S(i.borderWidth)},uBgColor:{value:f.color},uBorderColor:{value:m.color},uOpacity:{value:(y=i.opacity)!=null?y:1},uBgOpacity:{value:f.alpha},uBorderOpacity:{value:m.alpha},uTexture:{value:null}};return P(v.uBorderRadius.value,i.borderRadius),n&&(v.uTexture.value=r),new h.ShaderMaterial({uniforms:v,vertexShader:H,fragmentShader:p,transparent:!0,side:h.FrontSide})}function j(i,e,t,r,s){var o;const n=F(e.backgroundColor),a=F(e.borderColor);i.uniforms.uSize.value.set(t,r),P(i.uniforms.uBorderRadius.value,e.borderRadius),i.uniforms.uBorderWidth.value=S(e.borderWidth),i.uniforms.uBgColor.value.copy(n.color),i.uniforms.uBorderColor.value.copy(a.color),i.uniforms.uOpacity.value=(o=e.opacity)!=null?o:1,i.uniforms.uBgOpacity.value=n.alpha,i.uniforms.uBorderOpacity.value=a.alpha,i.uniforms.uTexture&&s!==void 0&&(i.uniforms.uTexture.value=s)}const I={create(i,e,t,r,s,n=2,a=null){if(i==="BOX")return G(e,r,s,a);if(i==="TEXT"){const o=Y(t||"",e,r,s,n);return new h.MeshBasicMaterial({map:o,transparent:!0,side:h.FrontSide,color:16777215})}return new h.MeshBasicMaterial({visible:!1})},update(i,e,t,r,s,n,a=2,o){if(e==="BOX")j(i,t,s,n,o);else if(e==="TEXT"){const l=i;l.map&&l.map.dispose();const c=Y(r||"",t,s,n,a);l.map=c,l.needsUpdate=!0}}};class J{constructor(e,t,r){d(this,"canvas");d(this,"scene");d(this,"camera");d(this,"renderer");d(this,"renderTarget",null);d(this,"renderOrder",0);d(this,"qualityFactor",2);d(this,"mode","overlay");d(this,"target");d(this,"mountContainer");d(this,"targetRect");d(this,"meshMap",new Map);var a,o;this.target=e,this.mountContainer=r,this.mode=(a=t.mode)!=null?a:"overlay",this.canvas=document.createElement("canvas"),this.scene=new h.Scene,this.targetRect=this.target.getBoundingClientRect();const s=this.targetRect.width,n=this.targetRect.height;this.camera=new h.OrthographicCamera(s/-2,s/2,n/2,n/-2,1,1e3),this.camera.position.z=100,this.camera.layers.set(0),this.renderer=new h.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(s,n),this.applyTextQuality((o=t.quality)!=null?o:"medium")}createRenderTarget(){this.renderTarget=new h.WebGLRenderTarget(this.targetRect.width*this.qualityFactor,this.targetRect.height*this.qualityFactor,{minFilter:h.LinearFilter,magFilter:h.LinearFilter,format:h.RGBAFormat,stencilBuffer:!1,depthBuffer:!0,samples:4})}applyTextQuality(e){if(typeof e=="number"){this.qualityFactor=Math.max(.1,e);return}switch(e){case"low":this.qualityFactor=1;break;case"high":this.qualityFactor=4;break;case"medium":default:this.qualityFactor=2;break}}mount(){this.mountContainer.prepend(this.canvas),this.canvas.style.pointerEvents=this.mode==="overlay"?"none":"auto",this.updateCanvasLayout()}updateCanvasLayout(){this.canvas.style.width=`${this.targetRect.width}px`,this.canvas.style.height=`${this.targetRect.height}px`,this.mode==="duplicate"?(this.canvas.style.position="",this.canvas.style.top="",this.canvas.style.left="",this.canvas.style.display="block"):(this.canvas.style.position="absolute",this.canvas.style.top=`${this.target.offsetTop}px`,this.canvas.style.left=`${this.target.offsetLeft}px`,this.canvas.style.display="block")}dispose(){this.renderer.dispose(),this.canvas.remove()}setSize(e,t){this.renderer.setSize(e,t),this.renderTarget&&this.renderTarget.setSize(e*this.qualityFactor,t*this.qualityFactor),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){const t=this.target.getBoundingClientRect(),r=Math.abs(t.width-this.targetRect.width)>.1||Math.abs(t.height-this.targetRect.height)>.1,s=this.mode==="overlay"&&(Math.abs(t.top-this.targetRect.top)>.1||Math.abs(t.left-this.targetRect.left)>.1);r?(this.targetRect=t,this.setSize(this.targetRect.width,this.targetRect.height),this.updateCanvasLayout()):s?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const n=new Set;this.reconcileNode(e,n);for(const[a,o]of this.meshMap.entries())n.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof h.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(e,t){var s;t.add(e.element);let r=this.meshMap.get(e.element);if(!r){const n=new h.PlaneGeometry(1,1);let a;a=I.create("BOX",e.styles,"",e.rect.width,e.rect.height,this.qualityFactor,e.isTraveler?(s=this.renderTarget)==null?void 0:s.texture:void 0),r=new h.Mesh(n,a),e.type==="TEXT"&&(r.name="BG_MESH"),this.scene.add(r),this.meshMap.set(e.element,r)}if(r.userData.domRect=e.rect,this.updateMeshProperties(r,e),this.updateMeshLayers(r,e),e.isTraveler&&r.layers.enable(28),e.type==="BOX")for(const n of e.children)this.reconcileNode(n,t);else e.type==="TEXT"&&this.reconcileTextChild(r,e)}reconcileTextChild(e,t){var o,l;let r=e.children.find(c=>c.name==="TEXT_CHILD");const s=JSON.stringify(t.textStyles),n=(o=r==null?void 0:r.userData)==null?void 0:o.styleHash;if(!r||t.dirtyMask&L||s!==n){r&&((l=r.material.map)==null||l.dispose(),r.geometry.dispose(),e.remove(r));const c=I.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.qualityFactor),p=new h.PlaneGeometry(1,1);r=new h.Mesh(p,c),r.name="TEXT_CHILD",r.userData={styleHash:s},this.updateMeshLayers(r,t),r.position.z=.005,e.add(r)}if(r){const c=e.userData.domRect,p=c.x+c.width/2,f=c.y+c.height/2,m=t.rect.x+t.rect.width/2,v=t.rect.y+t.rect.height/2,b=m-p,y=-(v-f);r.position.set(b,y,.005)}}updateMeshProperties(e,t){var v;const{rect:r,styles:s}=t,n=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/n,o=this.renderer.domElement.height/n;e.scale.set(r.width,r.height,1);const l=.001;this.renderOrder++;const c=this.targetRect.left+window.scrollX,p=this.targetRect.top+window.scrollY,f=r.x-c,m=r.y-p;e.position.set(f-a/2+r.width/2,-m+o/2-r.height/2,s.zIndex+this.renderOrder*l),I.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height,this.qualityFactor,t.isTraveler?(v=this.renderTarget)==null?void 0:v.texture:void 0)}updateMeshLayers(e,t){const r=(1-(t.visibility&R))*30;e.layers.set(r),t.visibility===(R|E)&&e.layers.enable(29)}captureRenderTarget(){const e=[];for(const l of this.meshMap.values())l.layers.mask&1<<28&&e.push(l);if(e.length===0)return;const t=new h.Color,r=this.renderer.getClearAlpha();this.renderer.getClearColor(t),this.renderer.setClearColor(0,0),this.renderer.setRenderTarget(this.renderTarget),this.renderer.clear(),this.renderer.autoClear=!1,this.renderer.setScissorTest(!0),this.camera.layers.set(29);const s=new h.Vector3,n=this.targetRect.width,a=this.targetRect.height,o=this.renderer.getPixelRatio();for(const l of e){s.setFromMatrixPosition(l.matrixWorld),s.project(this.camera);const c=(s.x+1)/2*n,p=(s.y+1)/2*a,f=l.scale.x,m=l.scale.y,v=c-f/2,b=p-m/2,y=v*this.qualityFactor/o,_=b*this.qualityFactor/o,z=f*this.qualityFactor/o,g=m*this.qualityFactor/o;this.renderer.setScissor(y,_,z,g),this.renderer.render(this.scene,this.camera)}this.renderer.setScissorTest(!1),this.renderer.autoClear=!0,this.renderer.setRenderTarget(null),this.camera.layers.set(28),this.renderer.setClearColor(t,r)}render(){this.renderTarget&&this.captureRenderTarget(),this.renderer.render(this.scene,this.camera)}showScissoredRenderTarget(){if(!this.renderTarget)return;const e=this.targetRect.width,t=this.targetRect.height,r=new h.PlaneGeometry(e,t),s=new h.MeshBasicMaterial({map:this.renderTarget.texture,side:h.DoubleSide,transparent:!0,opacity:.8}),n=new h.Mesh(r,s);n.position.set(0,0,90),n.layers.set(28),this.scene.add(n)}}function V(i){const e=document.createRange();e.selectNodeContents(i);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function Z(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 U(i,e=B|O|A|L|D,t,r){if(i.nodeType===Node.TEXT_NODE){const g=i;if(!g.textContent||!g.textContent.trim())return null;const x=g.textContent.replace(/\s+/g," ").trim();if(x.length===0)return null;const C=V(g);if(C.width===0||C.height===0)return null;const k=g.parentElement,N=k?window.getComputedStyle(k):null;return N?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:g,rect:{x:C.left+window.scrollX,y:C.top+window.scrollY,width:C.width,height:C.height},styles:{backgroundColor:"transparent",opacity:parseFloat(N.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:x,textStyles:Z(N),dirtyMask:e,visibility:t,isTraveler:!1,children:[]}:null}const s=i,n=s.dataset.mirageFilter;let a=t,o=t;if(n){const g=new Set(n.split(/\s+/));for(const x of g)if(!X.includes(x))throw new Error(`[MirageEngine] Invalid filter token: '${x}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`);if(g.has("end"))return null;if(g.has("include-tree")&&g.has("exclude-tree"))throw new Error("[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.");if(g.has("include-self")&&g.has("exclude-self"))throw new Error("[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.");g.has("include-tree")?a=a|R:g.has("exclude-tree")&&(a=a&~R),o=a,g.has("include-self")?o=o|R:g.has("exclude-self")&&(o=o&~R)}o=o|t&E;const l=s.dataset.mirageTravel;let c=!1;l&&new Set(l.split(/\s+/)).has("traveler")&&(o=o&~E,a=a&~E,c=!0);const p=s.getBoundingClientRect(),f=window.getComputedStyle(s);if(p.width===0||p.height===0||f.display==="none")return null;let m=s.getAttribute("data-mid");m||(m=Math.random().toString(36).substring(2,11),s.setAttribute("data-mid",m));const v=parseInt(f.zIndex),b={backgroundColor:f.backgroundColor,opacity:parseFloat(f.opacity),zIndex:isNaN(v)?0:v,borderRadius:f.borderRadius,borderColor:f.borderColor,borderWidth:f.borderWidth};let y,_;const z=[];return Array.from(s.childNodes).forEach(g=>{const x=g.nodeType===Node.TEXT_NODE?o:a,C=U(g,e,x);C&&z.push(C)}),{id:m,type:"BOX",element:s,rect:{x:p.left+window.scrollX,y:p.top+window.scrollY,width:p.width,height:p.height},styles:b,textContent:y,textStyles:_,dirtyMask:e,visibility:o,isTraveler:c,children:z}}class Q{constructor(e,t,r){d(this,"target");d(this,"renderer");d(this,"filter");d(this,"observer");d(this,"isDomDirty",!1);d(this,"isRunning",!1);d(this,"isTravelEnabled",!1);d(this,"pendingMask",M);d(this,"mutationTimer",null);d(this,"cssTimer",null);d(this,"resizeConfig");d(this,"resizeTimer",null);d(this,"isResizing",!1);d(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=B|O,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});d(this,"onWindowResize",()=>{if(!this.resizeConfig.enabled){this.isDomDirty=!0;return}this.isResizing||(this.isResizing=!0,this.resizeConfig.onStart&&this.resizeConfig.onStart()),this.resizeTimer&&clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.resizeConfig.onEnd&&this.resizeConfig.onEnd(),this.isResizing=!1,this.resizeTimer=null},this.resizeConfig.delay)});d(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});var n,a;this.target=e,this.renderer=t,this.filter=r.filter;const s=(n=r.resizeDebounce)!=null?n:!0;s===!1?this.resizeConfig={enabled:!1,delay:0}:s===!0?this.resizeConfig={enabled:!0,delay:150}:this.resizeConfig={enabled:!0,delay:(a=s.delay)!=null?a:150,onStart:s.onStart,onEnd:s.onEnd},this.observer=new MutationObserver(o=>{let l=M;for(const c of o)c.type==="childList"?l|=D:c.type==="attributes"&&(c.attributeName==="style"||c.attributeName==="class")&&(l|=B|O);if(l!==M){if(this.pendingMask|=l,l&D){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=document.querySelector("[data-mirage-travel='traveler']")!==null;e&&!this.isTravelEnabled&&(this.isTravelEnabled=!0,this.renderer.createRenderTarget());const t=U(this.target,this.pendingMask,e?R|E:R,this.filter);t&&this.renderer.syncScene(t),this.pendingMask=M}}class K{constructor(e,t){d(this,"renderer");d(this,"syncer");d(this,"target");var s,n,a;this.target=e;let r;if(t.mode==="duplicate"?r=(n=(s=t.container)!=null?s:this.target.parentElement)!=null?n:void 0:r=(a=this.target.parentElement)!=null?a:void 0,!r)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new J(this.target,t,r),this.renderer.mount(),this.syncer=new Q(this.target,this.renderer,t)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}u.ALLOWED_FILTERS=X,u.DIRTY_CONTENT=L,u.DIRTY_NONE=M,u.DIRTY_RECT=B,u.DIRTY_STRUCTURE=D,u.DIRTY_STYLE=O,u.DIRTY_ZINDEX=A,u.EXCLUDED=q,u.Engine=K,u.SYSTEM_LAYER=E,u.USER_LAYER=R,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
import { CoreConfig } from '../types/config';
|
|
1
2
|
import { Renderer } from '../renderer/Renderer';
|
|
2
3
|
|
|
3
4
|
export declare class Syncer {
|
|
4
5
|
private target;
|
|
5
6
|
private renderer;
|
|
7
|
+
private filter;
|
|
6
8
|
private observer;
|
|
7
9
|
private isDomDirty;
|
|
8
10
|
private isRunning;
|
|
11
|
+
private isTravelEnabled;
|
|
9
12
|
private pendingMask;
|
|
10
13
|
private mutationTimer;
|
|
11
14
|
private cssTimer;
|
|
12
|
-
|
|
15
|
+
private resizeConfig;
|
|
16
|
+
private resizeTimer;
|
|
17
|
+
private isResizing;
|
|
18
|
+
constructor(target: HTMLElement, renderer: Renderer, config: CoreConfig);
|
|
13
19
|
start(): void;
|
|
14
20
|
stop(): void;
|
|
15
21
|
private clearTimers;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { SceneNode } from '../types';
|
|
1
|
+
import { SceneNode, Visibility } from '../types';
|
|
2
|
+
import { FilterConfig } from '../types/config';
|
|
2
3
|
|
|
3
|
-
export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask?:
|
|
4
|
+
export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask: number | undefined, inheritedFlow: Visibility, filterConfig?: FilterConfig): SceneNode | null;
|
|
@@ -5,15 +5,16 @@ export declare class Renderer {
|
|
|
5
5
|
private readonly scene;
|
|
6
6
|
private readonly camera;
|
|
7
7
|
private readonly renderer;
|
|
8
|
+
private renderTarget;
|
|
8
9
|
private renderOrder;
|
|
9
|
-
private
|
|
10
|
+
private qualityFactor;
|
|
10
11
|
private mode;
|
|
11
|
-
private customZIndex;
|
|
12
12
|
private target;
|
|
13
13
|
private mountContainer;
|
|
14
14
|
private targetRect;
|
|
15
15
|
private meshMap;
|
|
16
16
|
constructor(target: HTMLElement, config: CoreConfig, mountContainer: HTMLElement);
|
|
17
|
+
createRenderTarget(): void;
|
|
17
18
|
private applyTextQuality;
|
|
18
19
|
mount(): void;
|
|
19
20
|
private updateCanvasLayout;
|
|
@@ -23,5 +24,8 @@ export declare class Renderer {
|
|
|
23
24
|
private reconcileNode;
|
|
24
25
|
private reconcileTextChild;
|
|
25
26
|
private updateMeshProperties;
|
|
27
|
+
private updateMeshLayers;
|
|
28
|
+
private captureRenderTarget;
|
|
26
29
|
render(): void;
|
|
30
|
+
showScissoredRenderTarget(): void;
|
|
27
31
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TextStyles, BoxStyles } from '../../packages/painter/src/index.ts';
|
|
2
|
+
import { Visibility } from './flags';
|
|
2
3
|
|
|
3
4
|
export type NodeType = "BOX" | "TEXT";
|
|
4
5
|
export interface NodeRect {
|
|
@@ -16,5 +17,7 @@ export interface SceneNode {
|
|
|
16
17
|
textContent?: string;
|
|
17
18
|
textStyles?: TextStyles;
|
|
18
19
|
dirtyMask: number;
|
|
20
|
+
visibility: Visibility;
|
|
21
|
+
isTraveler: boolean;
|
|
19
22
|
children: SceneNode[];
|
|
20
23
|
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type Quality = "low" | "medium" | "high" | number;
|
|
2
2
|
export type MirageMode = "overlay" | "duplicate";
|
|
3
|
+
export interface FilterConfig {
|
|
4
|
+
includeTree?: string[];
|
|
5
|
+
excludeTree?: string[];
|
|
6
|
+
includeSelf?: string[];
|
|
7
|
+
excludeSelf?: string[];
|
|
8
|
+
end?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface ResizeConfig {
|
|
11
|
+
delay?: number;
|
|
12
|
+
onStart?: () => void;
|
|
13
|
+
onEnd?: () => void;
|
|
14
|
+
}
|
|
3
15
|
interface BaseConfig {
|
|
4
16
|
debug?: boolean;
|
|
5
|
-
|
|
17
|
+
quality?: Quality;
|
|
6
18
|
style?: {
|
|
7
19
|
zIndex?: string;
|
|
8
20
|
};
|
|
21
|
+
filter?: FilterConfig;
|
|
22
|
+
resizeDebounce?: boolean | ResizeConfig;
|
|
9
23
|
}
|
|
10
24
|
export interface OverlayConfig extends BaseConfig {
|
|
11
25
|
mode?: "overlay";
|
|
@@ -4,3 +4,8 @@ export declare const DIRTY_STYLE: number;
|
|
|
4
4
|
export declare const DIRTY_ZINDEX: number;
|
|
5
5
|
export declare const DIRTY_STRUCTURE: number;
|
|
6
6
|
export declare const DIRTY_CONTENT: number;
|
|
7
|
+
export declare const USER_LAYER: number;
|
|
8
|
+
export declare const SYSTEM_LAYER: number;
|
|
9
|
+
export declare const EXCLUDED = 0;
|
|
10
|
+
export type Visibility = 0 | 1 | 2 | 3;
|
|
11
|
+
export declare const ALLOWED_FILTERS: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirage-engine/core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"types": "src/index.ts",
|
|
6
6
|
"peerDependencies": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@types/three": "^0.181.0",
|
|
16
|
-
"@mirage-engine/painter": "0.
|
|
16
|
+
"@mirage-engine/painter": "0.3.0"
|
|
17
17
|
},
|
|
18
18
|
"private": false,
|
|
19
19
|
"publishConfig": {
|
package/src/core/Engine.ts
CHANGED
package/src/core/Syncer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CoreConfig } from "../types/config";
|
|
1
2
|
import { Renderer } from "../renderer/Renderer";
|
|
2
3
|
import { extractSceneGraph } from "../dom/Extractor";
|
|
3
4
|
import {
|
|
@@ -5,24 +6,57 @@ import {
|
|
|
5
6
|
DIRTY_RECT,
|
|
6
7
|
DIRTY_STRUCTURE,
|
|
7
8
|
DIRTY_STYLE,
|
|
9
|
+
USER_LAYER,
|
|
10
|
+
SYSTEM_LAYER,
|
|
11
|
+
Visibility,
|
|
8
12
|
} from "../types";
|
|
9
13
|
|
|
14
|
+
interface InternalResizeConfig {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
delay: number;
|
|
17
|
+
onStart?: () => void;
|
|
18
|
+
onEnd?: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
export class Syncer {
|
|
11
22
|
private target: HTMLElement;
|
|
12
23
|
private renderer: Renderer;
|
|
24
|
+
private filter: CoreConfig["filter"];
|
|
25
|
+
|
|
13
26
|
private observer: MutationObserver;
|
|
14
27
|
|
|
15
28
|
private isDomDirty: boolean = false;
|
|
16
29
|
private isRunning: boolean = false;
|
|
30
|
+
private isTravelEnabled: boolean = false;
|
|
17
31
|
|
|
18
32
|
private pendingMask: number = DIRTY_NONE;
|
|
19
33
|
|
|
20
34
|
private mutationTimer: number | null = null;
|
|
21
35
|
private cssTimer: number | null = null;
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
private resizeConfig: InternalResizeConfig;
|
|
38
|
+
private resizeTimer: number | null = null;
|
|
39
|
+
private isResizing: boolean = false;
|
|
40
|
+
|
|
41
|
+
constructor(target: HTMLElement, renderer: Renderer, config: CoreConfig) {
|
|
24
42
|
this.target = target;
|
|
25
43
|
this.renderer = renderer;
|
|
44
|
+
this.filter = config.filter;
|
|
45
|
+
|
|
46
|
+
// Resize Debounce Configuration
|
|
47
|
+
const debounceOpt = config.resizeDebounce ?? true;
|
|
48
|
+
if (debounceOpt === false) {
|
|
49
|
+
this.resizeConfig = { enabled: false, delay: 0 };
|
|
50
|
+
} else if (debounceOpt === true) {
|
|
51
|
+
this.resizeConfig = { enabled: true, delay: 150 };
|
|
52
|
+
} else {
|
|
53
|
+
this.resizeConfig = {
|
|
54
|
+
enabled: true,
|
|
55
|
+
delay: debounceOpt.delay ?? 150,
|
|
56
|
+
onStart: debounceOpt.onStart,
|
|
57
|
+
onEnd: debounceOpt.onEnd,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
26
60
|
|
|
27
61
|
this.observer = new MutationObserver((mutations) => {
|
|
28
62
|
let currentMask = DIRTY_NONE;
|
|
@@ -80,6 +114,8 @@ export class Syncer {
|
|
|
80
114
|
|
|
81
115
|
this.forceUpdateScene();
|
|
82
116
|
this.renderLoop();
|
|
117
|
+
// for debugging
|
|
118
|
+
// this.renderer.showScissoredRenderTarget();
|
|
83
119
|
}
|
|
84
120
|
|
|
85
121
|
public stop() {
|
|
@@ -120,13 +156,45 @@ export class Syncer {
|
|
|
120
156
|
};
|
|
121
157
|
|
|
122
158
|
private onWindowResize = () => {
|
|
123
|
-
this.
|
|
124
|
-
|
|
159
|
+
if (!this.resizeConfig.enabled) {
|
|
160
|
+
this.isDomDirty = true;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!this.isResizing) {
|
|
165
|
+
this.isResizing = true;
|
|
166
|
+
if (this.resizeConfig.onStart) this.resizeConfig.onStart();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
170
|
+
|
|
171
|
+
this.resizeTimer = window.setTimeout(() => {
|
|
172
|
+
this.isDomDirty = true;
|
|
173
|
+
|
|
174
|
+
if (this.resizeConfig.onEnd) this.resizeConfig.onEnd();
|
|
175
|
+
this.isResizing = false;
|
|
176
|
+
this.resizeTimer = null;
|
|
177
|
+
}, this.resizeConfig.delay);
|
|
125
178
|
};
|
|
126
179
|
|
|
127
180
|
private forceUpdateScene() {
|
|
128
181
|
this.isDomDirty = false;
|
|
129
|
-
|
|
182
|
+
|
|
183
|
+
const discoveredTraveler =
|
|
184
|
+
document.querySelector("[data-mirage-travel='traveler']") !== null;
|
|
185
|
+
if (discoveredTraveler && !this.isTravelEnabled) {
|
|
186
|
+
this.isTravelEnabled = true;
|
|
187
|
+
this.renderer.createRenderTarget();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sceneGraph = extractSceneGraph(
|
|
191
|
+
this.target,
|
|
192
|
+
this.pendingMask,
|
|
193
|
+
(discoveredTraveler
|
|
194
|
+
? USER_LAYER | SYSTEM_LAYER
|
|
195
|
+
: USER_LAYER) as Visibility,
|
|
196
|
+
this.filter,
|
|
197
|
+
);
|
|
130
198
|
|
|
131
199
|
if (sceneGraph) {
|
|
132
200
|
this.renderer.syncScene(sceneGraph);
|
package/src/dom/Extractor.ts
CHANGED
|
@@ -5,9 +5,14 @@ import {
|
|
|
5
5
|
DIRTY_ZINDEX,
|
|
6
6
|
DIRTY_STRUCTURE,
|
|
7
7
|
SceneNode,
|
|
8
|
+
Visibility,
|
|
9
|
+
USER_LAYER,
|
|
10
|
+
SYSTEM_LAYER,
|
|
11
|
+
ALLOWED_FILTERS,
|
|
8
12
|
} from "../types";
|
|
9
13
|
|
|
10
14
|
import { BoxStyles, TextStyles } from "@mirage-engine/painter";
|
|
15
|
+
import { FilterConfig } from "../types/config";
|
|
11
16
|
|
|
12
17
|
// Helper function: getTextNodeRect, isValidTextNode, isLeafTextElement, extractTextStyles
|
|
13
18
|
|
|
@@ -50,12 +55,13 @@ export function extractSceneGraph(
|
|
|
50
55
|
DIRTY_STYLE |
|
|
51
56
|
DIRTY_ZINDEX |
|
|
52
57
|
DIRTY_CONTENT |
|
|
53
|
-
DIRTY_STRUCTURE
|
|
58
|
+
DIRTY_STRUCTURE,
|
|
59
|
+
inheritedFlow: Visibility,
|
|
60
|
+
filterConfig?: FilterConfig,
|
|
54
61
|
): SceneNode | null {
|
|
55
62
|
// Check text node
|
|
56
63
|
if (sourceNode.nodeType === Node.TEXT_NODE) {
|
|
57
64
|
const textNode = sourceNode as Text;
|
|
58
|
-
|
|
59
65
|
// empthy text check
|
|
60
66
|
if (!textNode.textContent || !textNode.textContent.trim()) return null;
|
|
61
67
|
const normalizedText = textNode.textContent.replace(/\s+/g, " ").trim();
|
|
@@ -93,11 +99,80 @@ export function extractSceneGraph(
|
|
|
93
99
|
textContent: normalizedText,
|
|
94
100
|
textStyles: extractTextStyles(computed),
|
|
95
101
|
dirtyMask: initialMask,
|
|
102
|
+
visibility: inheritedFlow,
|
|
103
|
+
isTraveler: false,
|
|
96
104
|
children: [],
|
|
97
105
|
};
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
const element = sourceNode as HTMLElement;
|
|
109
|
+
// [[Filter]] data attribute based filtering
|
|
110
|
+
const filterData = element.dataset.mirageFilter;
|
|
111
|
+
let visibleFlow = inheritedFlow;
|
|
112
|
+
let visibleFlag = inheritedFlow;
|
|
113
|
+
if (filterData) {
|
|
114
|
+
const filterSet = new Set(filterData.split(/\s+/));
|
|
115
|
+
// error check
|
|
116
|
+
for (const token of filterSet) {
|
|
117
|
+
if (!ALLOWED_FILTERS.includes(token)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[MirageEngine] Invalid filter token: '${token}'. ` +
|
|
120
|
+
`Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (filterSet.has("end")) return null;
|
|
126
|
+
|
|
127
|
+
// error check
|
|
128
|
+
if (filterSet.has("include-tree") && filterSet.has("exclude-tree")) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (filterSet.has("include-self") && filterSet.has("exclude-self")) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (filterSet.has("include-tree")) {
|
|
140
|
+
visibleFlow = (visibleFlow | USER_LAYER) as Visibility;
|
|
141
|
+
} else if (filterSet.has("exclude-tree")) {
|
|
142
|
+
visibleFlow = (visibleFlow & ~USER_LAYER) as Visibility;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
visibleFlag = visibleFlow;
|
|
146
|
+
|
|
147
|
+
if (filterSet.has("include-self")) {
|
|
148
|
+
visibleFlag = (visibleFlag | USER_LAYER) as Visibility;
|
|
149
|
+
} else if (filterSet.has("exclude-self")) {
|
|
150
|
+
visibleFlag = (visibleFlag & ~USER_LAYER) as Visibility;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// [[filter]] class based filtering
|
|
155
|
+
// [Filter] end
|
|
156
|
+
// if (filterConfig && filterConfig.end && filterConfig.end.length > 0) {
|
|
157
|
+
// const isEnd = filterConfig.end.some((cls) =>
|
|
158
|
+
// element.classList.contains(cls),
|
|
159
|
+
// );
|
|
160
|
+
// if (isEnd) return null;
|
|
161
|
+
// }
|
|
162
|
+
|
|
163
|
+
visibleFlag = (visibleFlag | (inheritedFlow & SYSTEM_LAYER)) as Visibility;
|
|
164
|
+
|
|
165
|
+
const travelData = element.dataset.mirageTravel;
|
|
166
|
+
let isTraveler = false;
|
|
167
|
+
if (travelData) {
|
|
168
|
+
const travelSet = new Set(travelData.split(/\s+/));
|
|
169
|
+
if (travelSet.has("traveler")) {
|
|
170
|
+
visibleFlag = (visibleFlag & ~SYSTEM_LAYER) as Visibility;
|
|
171
|
+
visibleFlow = (visibleFlow & ~SYSTEM_LAYER) as Visibility;
|
|
172
|
+
isTraveler = true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
101
176
|
const rect = element.getBoundingClientRect();
|
|
102
177
|
const computed = window.getComputedStyle(element);
|
|
103
178
|
|
|
@@ -127,8 +202,14 @@ export function extractSceneGraph(
|
|
|
127
202
|
const children: SceneNode[] = [];
|
|
128
203
|
|
|
129
204
|
Array.from(element.childNodes).forEach((child) => {
|
|
130
|
-
|
|
131
|
-
|
|
205
|
+
const visibleFlowToPass =
|
|
206
|
+
child.nodeType === Node.TEXT_NODE ? visibleFlag : visibleFlow;
|
|
207
|
+
const childNode = extractSceneGraph(
|
|
208
|
+
child,
|
|
209
|
+
initialMask,
|
|
210
|
+
visibleFlowToPass,
|
|
211
|
+
filterConfig,
|
|
212
|
+
);
|
|
132
213
|
if (childNode) {
|
|
133
214
|
children.push(childNode);
|
|
134
215
|
}
|
|
@@ -148,6 +229,8 @@ export function extractSceneGraph(
|
|
|
148
229
|
textContent,
|
|
149
230
|
textStyles,
|
|
150
231
|
dirtyMask: initialMask,
|
|
232
|
+
visibility: visibleFlag,
|
|
233
|
+
isTraveler: isTraveler,
|
|
151
234
|
children,
|
|
152
235
|
};
|
|
153
236
|
}
|