@mirage-engine/core 0.1.0 → 0.2.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.
@@ -1,16 +1,18 @@
1
- (function(d,m){typeof exports=="object"&&typeof module!="undefined"?m(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],m):(d=typeof globalThis!="undefined"?globalThis:d||self,m(d.MirageEngine={},d.THREE))})(this,function(d,m){"use strict";var J=Object.defineProperty;var K=(d,m,T)=>m in d?J(d,m,{enumerable:!0,configurable:!0,writable:!0,value:T}):d[m]=T;var l=(d,m,T)=>(K(d,typeof m!="symbol"?m+"":m,T),T);function T(r){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const t in r)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(r,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>r[t]})}}return e.default=r,Object.freeze(e)}const u=T(m),R=0,S=1,D=2,I=4,B=8,F=16,M=0,A=29,L=30,X=["include-tree","exclude-tree","include-self","exclude-self","end"];function U(r,e,t){const i=e.split(`
2
- `),n=[];return i.forEach(s=>{const a=s.split(" ");let o=a[0];for(let h=1;h<a.length;h++){const c=a[h];r.measureText(o+" "+c).width<t?o+=" "+c:(n.push(o),o=c)}n.push(o)}),n}function _(r,e,t,i,n=2){const s=document.createElement("canvas"),a=s.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const h=(window.devicePixelRatio||1)*n;s.width=t*h,s.height=i*h,a.scale(h,h),a.font=e.font,a.fillStyle=e.color,a.textBaseline="top",a.globalAlpha=1;const c=U(a,r,t),p=e.lineHeight;c.forEach((w,E)=>{const C=E*p+2;let x=0;e.textAlign==="center"?x=t/2:e.textAlign==="right"&&(x=t),a.textAlign=e.textAlign,a.fillText(w,x,C)});const g=new u.CanvasTexture(s);return g.colorSpace=u.SRGBColorSpace,g.minFilter=u.LinearFilter,g.magFilter=u.LinearFilter,g.needsUpdate=!0,g}function b(r){return typeof r=="number"?r:parseFloat(r)||0}function k(r,e){var o,h,c,p;if(!e){r.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),i=b(t[0]),n=b((o=t[1])!=null?o:t[0]),s=b((h=t[2])!=null?h:t[0]),a=b((p=(c=t[3])!=null?c:t[1])!=null?p:t[0]);r.set(i,n,s,a)}const W=`
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 re=Object.defineProperty;var ie=(u,T,w)=>T in u?re(u,T,{enumerable:!0,configurable:!0,writable:!0,value:w}):u[T]=w;var h=(u,T,w)=>(ie(u,typeof T!="symbol"?T+"":T,w),w);function w(s){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const t in s)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(s,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>s[t]})}}return e.default=s,Object.freeze(e)}const d=w(T),D=0,M=1,O=2,Y=4,B=8,L=16,C=1,E=2,H=0,P=["include-tree","exclude-tree","include-self","exclude-self","end"];function $(s,e,t){const r=e.split(`
2
+ `),i=[];return r.forEach(a=>{const n=a.split(" ");let o=n[0];for(let l=1;l<n.length;l++){const c=n[l];s.measureText(o+" "+c).width<t?o+=" "+c:(i.push(o),o=c)}i.push(o)}),i}function k(s,e,t,r,i=2){const a=document.createElement("canvas"),n=a.getContext("2d");if(!n)throw new Error("[Mirage] Failed to create canvas context");const l=(window.devicePixelRatio||1)*i;a.width=t*l,a.height=r*l,n.scale(l,l),n.font=e.font,n.fillStyle=e.color,n.textBaseline="top",n.globalAlpha=1;const c=$(n,s,t),v=e.lineHeight;c.forEach((m,g)=>{const R=g*v+2;let y=0;e.textAlign==="center"?y=t/2:e.textAlign==="right"&&(y=t),n.textAlign=e.textAlign,n.fillText(m,y,R)});const p=new d.CanvasTexture(a);return p.colorSpace=d.SRGBColorSpace,p.minFilter=d.LinearFilter,p.magFilter=d.LinearFilter,p.needsUpdate=!0,p}function S(s){return typeof s=="number"?s:parseFloat(s)||0}function U(s,e){var o,l,c,v;if(!e){s.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),r=S(t[0]),i=S((o=t[1])!=null?o:t[0]),a=S((l=t[2])!=null?l:t[0]),n=S((v=(c=t[3])!=null?c:t[1])!=null?v:t[0]);s.set(r,i,a,n)}const G=`
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
- `,H=`
10
+ `,J=`
9
11
 
10
12
  varying vec2 vUv;
11
13
 
12
14
  uniform vec2 uSize;
13
- uniform vec4 uRadius;
15
+ uniform vec4 uBorderRadius;
14
16
  uniform float uBorderWidth;
15
17
  uniform vec3 uBgColor;
16
18
  uniform vec3 uBorderColor;
@@ -18,65 +20,66 @@
18
20
  uniform float uBgOpacity;
19
21
  uniform float uBorderOpacity;
20
22
 
21
- // SDF box
23
+ #INJECT_DECLARATIONS
24
+
22
25
  float sdRoundedBox(vec2 p, vec2 b, float r) {
23
26
  vec2 q = abs(p) - b + r;
24
- // return length(max(q, 0.0)) - r;
27
+
25
28
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
26
29
  }
27
30
 
28
31
  void main() {
29
- // uVu: (0.0, 0.0) ~ (1.0, 1.0) / center: (0.5, 0.5)
30
- // p: (-1.0, -1.0) ~ (1.0, 1.0) / center: (0.0, 0.0)
31
32
  vec2 p = (vUv - 0.5) * uSize;
32
33
  vec2 halfSize = uSize * 0.5;
34
+
35
+ #INJECT_UV_MODIFIER
33
36
 
34
- // # border-radius
35
- vec2 xRadii = mix(uRadius.xw, uRadius.yz, step(0.0, p.x));
37
+ // color decision pipeline
38
+ vec4 baseColor = vec4(uBgColor, uBgOpacity);
39
+
40
+ #INJECT_BASE_COLOR
41
+
42
+ // sdf shape pipeline
43
+ vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
36
44
  float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
37
45
  float d = sdRoundedBox(p, halfSize, r);
38
46
 
39
- // 1px blur for anti-aliasing
40
47
  float aa = 1.0;
41
-
42
- // x == 0~aa -> 1
43
- // x < 0 -> 0
44
- // x > aa -> 1
45
48
  float bgMask = 1.0 - smoothstep(0.0, aa, d);
46
49
 
47
- // v valley
48
- // 10px -> 5
49
50
  float halfBorder = uBorderWidth * 0.5;
50
- // 10px :
51
- // 0 -> 0
52
- // -10 -> 0
53
- // -5 -> -5
54
51
  float borderD = abs(d + halfBorder) - halfBorder;
55
- // ^ border 내부는 음수/외부는 양수
56
-
57
- // [!] 내부가 음수인 곳에서 시작점이 0이면 내부는 안티 얼리어싱 되지 않음.
58
- // 1에서 빼든 그대로든 상관없이 경계선 밖 1px
59
-
60
- // 최종 alpha를 위해 border 부분에 해당하는 픽셀 만 1로 반환.
61
52
  float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
62
-
63
53
  if (uBorderWidth <= 0.01) {
64
54
  borderAlpha = 0.0;
65
55
  }
66
56
 
57
+ // final blending (border + background)
67
58
  float aFront = borderAlpha;
68
- float aBack = uBgOpacity;
59
+ float aBack = baseColor.a;
69
60
  float aOut = aFront + aBack * (1.0 - aFront);
70
61
 
71
62
  float safeAlpha = max(aOut, 0.0001);
72
- vec3 cOut = (uBorderColor * aFront + uBgColor * aBack * (1.0 - aFront)) / safeAlpha;
63
+ vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
64
+ vec4 finalColor = vec4(cOut, aOut);
73
65
 
74
- float finalOpacity = aOut * bgMask * uOpacity;
66
+ // final color control (Tint, Noise)
67
+ #INJECT_COLOR_MODIFIER
75
68
 
69
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
76
70
  if (finalOpacity < 0.001) discard;
77
71
 
78
- gl_FragColor = vec4(cOut, finalOpacity);
72
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
79
73
 
80
74
  #include <colorspace_fragment>
81
75
  }
82
- `;function O(r){if(!r||r==="transparent")return{color:new u.Color(16777215),alpha:0};const e=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(e){const t=parseInt(e[1],10),i=parseInt(e[2],10),n=parseInt(e[3],10),s=e[4]!==void 0?parseFloat(e[4]):1;return{color:new u.Color(`rgb(${t}, ${i}, ${n})`),alpha:s}}return{color:new u.Color(r),alpha:1}}function $(r,e,t){var o;const i=O(r.backgroundColor),n=O(r.borderColor),s={uSize:{value:new u.Vector2(e,t)},uRadius:{value:new u.Vector4(0,0,0,0)},uBorderWidth:{value:b(r.borderWidth)},uBgColor:{value:i.color},uBorderColor:{value:n.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha},uBorderOpacity:{value:n.alpha}};return k(s.uRadius.value,r.borderRadius),new u.ShaderMaterial({uniforms:s,vertexShader:W,fragmentShader:H,transparent:!0,side:u.FrontSide})}function j(r,e,t,i){var a;const n=O(e.backgroundColor),s=O(e.borderColor);r.uniforms.uSize.value.set(t,i),k(r.uniforms.uRadius.value,e.borderRadius),r.uniforms.uBorderWidth.value=b(e.borderWidth),r.uniforms.uBgColor.value.copy(n.color),r.uniforms.uBorderColor.value.copy(s.color),r.uniforms.uOpacity.value=(a=e.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=n.alpha,r.uniforms.uBorderOpacity.value=s.alpha}const N={create(r,e,t,i,n,s=2){if(r==="BOX")return $(e,i,n);if(r==="TEXT"){const a=_(t||"",e,i,n,s);return new u.MeshBasicMaterial({map:a,transparent:!0,side:u.FrontSide,color:16777215})}return new u.MeshBasicMaterial({visible:!1})},update(r,e,t,i,n,s,a=2){if(e==="BOX")j(r,t,n,s);else if(e==="TEXT"){const o=r;o.map&&o.map.dispose();const h=_(i||"",t,n,s,a);o.map=h,o.needsUpdate=!0}}};class Q{constructor(e,t,i){l(this,"canvas");l(this,"scene");l(this,"camera");l(this,"renderer");l(this,"renderOrder",0);l(this,"textQualityFactor",2);l(this,"mode","overlay");l(this,"target");l(this,"mountContainer");l(this,"targetRect");l(this,"meshMap",new Map);var a,o;this.target=e,this.mountContainer=i,this.mode=(a=t.mode)!=null?a:"overlay",this.canvas=document.createElement("canvas"),this.scene=new u.Scene,this.targetRect=this.target.getBoundingClientRect();const n=this.targetRect.width,s=this.targetRect.height;this.camera=new u.OrthographicCamera(n/-2,n/2,s/2,s/-2,1,1e3),this.camera.position.z=100,this.renderer=new u.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(n,s),this.applyTextQuality((o=t.textQuality)!=null?o:"medium")}applyTextQuality(e){if(typeof e=="number"){this.textQualityFactor=Math.max(.1,e);return}switch(e){case"low":this.textQualityFactor=1;break;case"high":this.textQualityFactor=4;break;case"medium":default:this.textQualityFactor=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.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(),i=Math.abs(t.width-this.targetRect.width)>.1||Math.abs(t.height-this.targetRect.height)>.1,n=this.mode==="overlay"&&(Math.abs(t.top-this.targetRect.top)>.1||Math.abs(t.left-this.targetRect.left)>.1);i?(this.targetRect=t,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.layers.set(0),this.camera.updateProjectionMatrix(),this.updateCanvasLayout()):n?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const s=new Set;this.reconcileNode(e,s);for(const[a,o]of this.meshMap.entries())s.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof u.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(e,t){t.add(e.element);let i=this.meshMap.get(e.element);if(!i){const n=new u.PlaneGeometry(1,1),s=N.create("BOX",e.styles,"",e.rect.width,e.rect.height);i=new u.Mesh(n,s),i.layers.set(e.visibility),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 n of e.children)this.reconcileNode(n,t);else e.type==="TEXT"&&this.reconcileTextChild(i,e)}reconcileTextChild(e,t){var o,h;let i=e.children.find(c=>c.name==="TEXT_CHILD");const n=JSON.stringify(t.textStyles),s=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||t.dirtyMask&F||n!==s){i&&((h=i.material.map)==null||h.dispose(),i.geometry.dispose(),e.remove(i));const c=N.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.textQualityFactor),p=new u.PlaneGeometry(1,1);i=new u.Mesh(p,c),i.name="TEXT_CHILD",i.userData={styleHash:n},i.layers.set(t.visibility),i.position.z=.005,e.add(i)}if(i){const c=e.userData.domRect,p=c.x+c.width/2,g=c.y+c.height/2,w=t.rect.x+t.rect.width/2,E=t.rect.y+t.rect.height/2,C=w-p,x=-(E-g);i.position.set(C,x,.005)}}updateMeshProperties(e,t){const{rect:i,styles:n}=t,s=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/s,o=this.renderer.domElement.height/s;e.scale.set(i.width,i.height,1);const h=.001;this.renderOrder++;const c=this.targetRect.left+window.scrollX,p=this.targetRect.top+window.scrollY,g=i.x-c,w=i.y-p;e.position.set(g-a/2+i.width/2,-w+o/2-i.height/2,n.zIndex+this.renderOrder*h),N.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function G(r){const e=document.createRange();e.selectNodeContents(r);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function q(r){const e=parseFloat(r.fontSize);let t=parseFloat(r.lineHeight);isNaN(t)&&(t=e*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:t,letterSpacing:i}}function Y(r,e=S|D|I|F|B,t,i){if(r.nodeType===Node.TEXT_NODE){const f=r;if(!f.textContent||!f.textContent.trim())return null;const y=f.textContent.replace(/\s+/g," ").trim();if(y.length===0)return null;const v=G(f);if(v.width===0||v.height===0)return null;const P=f.parentElement,z=P?window.getComputedStyle(P):null;return z?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:f,rect:{x:v.left+window.scrollX,y:v.top+window.scrollY,width:v.width,height:v.height},styles:{backgroundColor:"transparent",opacity:parseFloat(z.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:y,textStyles:q(z),dirtyMask:e,visibility:t,children:[]}:null}const n=r,s=n.dataset.mirageFilter;let a=t,o=t;if(s){const f=new Set(s.split(/\s+/));for(const y of f)if(!X.includes(y))throw new Error(`[MirageEngine] Invalid filter token: '${y}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`);if(f.has("end"))return null;if(f.has("include-tree")&&f.has("exclude-tree"))throw new Error("[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.");if(f.has("include-self")&&f.has("exclude-self"))throw new Error("[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.");f.has("include-tree")?a=M:f.has("exclude-tree")&&(a=L),o=a,f.has("include-self")?o=M:f.has("exclude-self")&&(o=L)}if(i&&i.end&&i.end.length>0&&i.end.some(y=>n.classList.contains(y)))return null;const h=n.getBoundingClientRect(),c=window.getComputedStyle(n);if(h.width===0||h.height===0||c.display==="none")return null;let p=n.getAttribute("data-mid");p||(p=Math.random().toString(36).substring(2,11),n.setAttribute("data-mid",p));const g=parseInt(c.zIndex),w={backgroundColor:c.backgroundColor,opacity:parseFloat(c.opacity),zIndex:isNaN(g)?0:g,borderRadius:c.borderRadius,borderColor:c.borderColor,borderWidth:c.borderWidth};let E,C;const x=[];return Array.from(n.childNodes).forEach(f=>{const y=f.nodeType===Node.TEXT_NODE?o:a,v=Y(f,e,y,i);v&&x.push(v)}),{id:p,type:"BOX",element:n,rect:{x:h.left+window.scrollX,y:h.top+window.scrollY,width:h.width,height:h.height},styles:w,textContent:E,textStyles:C,dirtyMask:e,visibility:o,children:x}}class V{constructor(e,t,i){l(this,"target");l(this,"renderer");l(this,"filter");l(this,"observer");l(this,"isDomDirty",!1);l(this,"isRunning",!1);l(this,"pendingMask",R);l(this,"mutationTimer",null);l(this,"cssTimer",null);l(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=S|D,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});l(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});l(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.filter=i,this.observer=new MutationObserver(n=>{let s=R;for(const a of n)a.type==="childList"?s|=B:a.type==="attributes"&&(a.attributeName==="style"||a.attributeName==="class")&&(s|=S|D);if(s!==R){if(this.pendingMask|=s,s&B){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=Y(this.target,this.pendingMask,M,this.filter);e&&this.renderer.syncScene(e),this.pendingMask=R}}class Z{constructor(e,t){l(this,"renderer");l(this,"syncer");l(this,"target");var n,s,a;this.target=e;let i;if(t.mode==="duplicate"?i=(s=(n=t.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 Q(this.target,t,i),this.renderer.mount(),this.syncer=new V(this.target,this.renderer,t.filter)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}d.ALLOWED_FILTERS=X,d.DIRTY_CONTENT=F,d.DIRTY_NONE=R,d.DIRTY_RECT=S,d.DIRTY_STRUCTURE=B,d.DIRTY_STYLE=D,d.DIRTY_ZINDEX=I,d.EXCLUDED=L,d.Engine=Z,d.INCLUDED=M,d.SYSTEM=A,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
76
+ `;function F(s){if(!s||s==="transparent")return{color:new d.Color(16777215),alpha:0};const e=s.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(e){const t=parseInt(e[1],10),r=parseInt(e[2],10),i=parseInt(e[3],10),a=e[4]!==void 0?parseFloat(e[4]):1;return{color:new d.Color(`rgb(${t}, ${r}, ${i})`),alpha:a}}return{color:new d.Color(s),alpha:1}}function j(s,e,t,r=null,i){var y;const a=r!==null,n=a?`
77
+ uniform sampler2D uTexture;
78
+ varying vec4 vScreenPos;
79
+ `:"",o=a?`
80
+ vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
81
+ vec2 resultUv = screenUv;
82
+ ${(i==null?void 0:i.uvModifier)||""}
83
+ `:"",l=a?`
84
+ baseColor = texture2D(uTexture, resultUv);
85
+ `:"",c=(i==null?void 0:i.colorModifier)||"",v=J.replace("#INJECT_DECLARATIONS",n).replace("#INJECT_UV_MODIFIER",o).replace("#INJECT_BASE_COLOR",l).replace("#INJECT_COLOR_MODIFIER",c),p=F(s.backgroundColor),m=F(s.borderColor),g={uSize:{value:new d.Vector2(e,t)},uBorderRadius:{value:new d.Vector4(0,0,0,0)},uBorderWidth:{value:S(s.borderWidth)},uBgColor:{value:p.color},uBorderColor:{value:m.color},uOpacity:{value:(y=s.opacity)!=null?y:1},uBgOpacity:{value:p.alpha},uBorderOpacity:{value:m.alpha},uTexture:{value:null}};return U(g.uBorderRadius.value,s.borderRadius),a&&(g.uTexture.value=r),new d.ShaderMaterial({uniforms:g,vertexShader:G,fragmentShader:v,transparent:!0,side:d.FrontSide})}function V(s,e,t,r,i){var o;const a=F(e.backgroundColor),n=F(e.borderColor);s.uniforms.uSize.value.set(t,r),U(s.uniforms.uBorderRadius.value,e.borderRadius),s.uniforms.uBorderWidth.value=S(e.borderWidth),s.uniforms.uBgColor.value.copy(a.color),s.uniforms.uBorderColor.value.copy(n.color),s.uniforms.uOpacity.value=(o=e.opacity)!=null?o:1,s.uniforms.uBgOpacity.value=a.alpha,s.uniforms.uBorderOpacity.value=n.alpha,s.uniforms.uTexture&&i!==void 0&&(s.uniforms.uTexture.value=i)}const I={create(s,e,t,r,i,a=2,n=null,o){if(s==="BOX")return j(e,r,i,n,o);if(s==="TEXT"){const l=k(t||"",e,r,i,a);return new d.MeshBasicMaterial({map:l,transparent:!0,side:d.FrontSide,color:16777215})}return new d.MeshBasicMaterial({visible:!1})},update(s,e,t,r,i,a,n=2,o){if(e==="BOX")V(s,t,i,a,o);else if(e==="TEXT"){const l=s;l.map&&l.map.dispose();const c=k(r||"",t,i,a,n);l.map=c,l.needsUpdate=!0}}};class Z{constructor(e,t,r){h(this,"canvas");h(this,"scene");h(this,"camera");h(this,"renderer");h(this,"renderTarget",null);h(this,"renderOrder",0);h(this,"qualityFactor",2);h(this,"mode","overlay");h(this,"clipArea",1);h(this,"target");h(this,"mountContainer");h(this,"targetRect");h(this,"meshMap",new Map);var n,o,l;this.target=e,this.mountContainer=r,this.mode=(n=t.mode)!=null?n:"overlay",this.clipArea=(o=t.travelerClipArea)!=null?o:1,this.canvas=document.createElement("canvas"),this.scene=new d.Scene,this.targetRect=this.target.getBoundingClientRect();const i=this.targetRect.width,a=this.targetRect.height;this.camera=new d.OrthographicCamera(i/-2,i/2,a/2,a/-2,1,1e3),this.camera.position.z=100,this.camera.layers.set(0),this.renderer=new d.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(i,a),this.applyTextQuality((l=t.quality)!=null?l:"medium")}createRenderTarget(){this.renderTarget=new d.WebGLRenderTarget(this.targetRect.width*this.qualityFactor,this.targetRect.height*this.qualityFactor,{minFilter:d.LinearFilter,magFilter:d.LinearFilter,format:d.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,i=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()):i?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const a=new Set;this.reconcileNode(e,a);for(const[n,o]of this.meshMap.entries())a.has(n)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof d.Material&&o.material.dispose(),this.meshMap.delete(n))}reconcileNode(e,t){var i;t.add(e.element);let r=this.meshMap.get(e.element);if(!r){const a=new d.PlaneGeometry(1,1);let n;n=I.create("BOX",e.styles,"",e.rect.width,e.rect.height,this.qualityFactor,e.isTraveler?(i=this.renderTarget)==null?void 0:i.texture:void 0,e.shaderHooks),r=new d.Mesh(a,n),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 a of e.children)this.reconcileNode(a,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 i=JSON.stringify(t.textStyles),a=(o=r==null?void 0:r.userData)==null?void 0:o.styleHash;if(!r||t.dirtyMask&L||i!==a){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),v=new d.PlaneGeometry(1,1);r=new d.Mesh(v,c),r.name="TEXT_CHILD",r.userData={styleHash:i},this.updateMeshLayers(r,t),r.position.z=.005,e.add(r)}if(r){const c=e.userData.domRect,v=c.x+c.width/2,p=c.y+c.height/2,m=t.rect.x+t.rect.width/2,g=t.rect.y+t.rect.height/2,R=m-v,y=-(g-p);r.position.set(R,y,.005)}}updateMeshProperties(e,t){var g;const{rect:r,styles:i}=t,a=this.renderer.getPixelRatio(),n=this.renderer.domElement.width/a,o=this.renderer.domElement.height/a;e.scale.set(r.width,r.height,1);const l=.001;this.renderOrder++;const c=this.targetRect.left+window.scrollX,v=this.targetRect.top+window.scrollY,p=r.x-c,m=r.y-v;e.position.set(p-n/2+r.width/2,-m+o/2-r.height/2,i.zIndex+this.renderOrder*l),I.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height,this.qualityFactor,t.isTraveler?(g=this.renderTarget)==null?void 0:g.texture:void 0)}updateMeshLayers(e,t){const r=(1-(t.visibility&C))*30;e.layers.set(r),t.visibility===(C|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 d.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 i=new d.Vector3,a=this.targetRect.width,n=this.targetRect.height,o=this.renderer.getPixelRatio();for(const l of e){i.setFromMatrixPosition(l.matrixWorld),i.project(this.camera);const c=(i.x+1)/2*a,v=(i.y+1)/2*n;let p=0,m=1;typeof this.clipArea=="number"?m=this.clipArea:this.clipArea.endsWith("%")?m=parseFloat(this.clipArea)/100:this.clipArea.endsWith("px")&&(p=parseFloat(this.clipArea));const g=l.scale.x*m+.5,R=l.scale.y*m+.5,y=c-g/2,_=v-R/2,N=(y*this.qualityFactor-p)/o,A=(_*this.qualityFactor-p)/o,z=(g*this.qualityFactor+p*2)/o,f=(R*this.qualityFactor+p*2)/o;this.renderer.setScissor(N,A,z,f),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 d.PlaneGeometry(e,t),i=new d.MeshBasicMaterial({map:this.renderTarget.texture,side:d.DoubleSide,transparent:!0,opacity:.8}),a=new d.Mesh(r,i);a.position.set(0,0,90),a.layers.set(28),this.scene.add(a)}}function Q(s){const e=document.createRange();e.selectNodeContents(s);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function K(s){const e=parseFloat(s.fontSize);let t=parseFloat(s.lineHeight);isNaN(t)&&(t=e*1.2);let r=parseFloat(s.letterSpacing);return isNaN(r)&&(r=0),{font:`${s.fontStyle} ${s.fontWeight} ${s.fontSize} ${s.fontFamily}`,color:s.color,textAlign:s.textAlign||"start",textBaseline:"alphabetic",direction:s.direction||"inherit",lineHeight:t,letterSpacing:r}}function q(s,e=M|O|Y|L|B,t,r){if(s.nodeType===Node.TEXT_NODE){const f=s;if(!f.textContent||!f.textContent.trim())return null;const x=f.textContent.replace(/\s+/g," ").trim();if(x.length===0)return null;const b=Q(f);if(b.width===0||b.height===0)return null;const W=f.parentElement,X=W?window.getComputedStyle(W):null;return X?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:f,rect:{x:b.left+window.scrollX,y:b.top+window.scrollY,width:b.width,height:b.height},styles:{backgroundColor:"transparent",opacity:parseFloat(X.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:x,textStyles:K(X),dirtyMask:e,visibility:t,isTraveler:!1,children:[]}:null}const i=s,a=i.dataset.mirageFilter;let n=t,o=t;if(a){const f=new Set(a.split(/\s+/));for(const x of f)if(!P.includes(x))throw new Error(`[MirageEngine] Invalid filter token: '${x}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`);if(f.has("end"))return null;if(f.has("include-tree")&&f.has("exclude-tree"))throw new Error("[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.");if(f.has("include-self")&&f.has("exclude-self"))throw new Error("[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.");f.has("include-tree")?n=n|C:f.has("exclude-tree")&&(n=n&~C),o=n,f.has("include-self")?o=o|C:f.has("exclude-self")&&(o=o&~C)}o=o|t&E;const l=i.dataset.mirageTravel;let c=!1;l&&new Set(l.split(/\s+/)).has("traveler")&&(o=o&~E,n=n&~E,c=!0);const v=i.dataset.mirageShader;let p;v&&(p=JSON.parse(v));const m=i.getBoundingClientRect(),g=window.getComputedStyle(i);if(m.width===0||m.height===0||g.display==="none")return null;let R=i.getAttribute("data-mid");R||(R=Math.random().toString(36).substring(2,11),i.setAttribute("data-mid",R));const y=parseInt(g.zIndex),_={backgroundColor:g.backgroundColor,opacity:parseFloat(g.opacity),zIndex:isNaN(y)?0:y,borderRadius:g.borderRadius,borderColor:g.borderColor,borderWidth:g.borderWidth};let N,A;const z=[];return Array.from(i.childNodes).forEach(f=>{const x=f.nodeType===Node.TEXT_NODE?o:n,b=q(f,e,x);b&&z.push(b)}),{id:R,type:"BOX",element:i,rect:{x:m.left+window.scrollX,y:m.top+window.scrollY,width:m.width,height:m.height},styles:_,textContent:N,textStyles:A,dirtyMask:e,visibility:o,isTraveler:c,children:z,shaderHooks:p}}class ee{constructor(e,t,r){h(this,"target");h(this,"renderer");h(this,"filter");h(this,"observer");h(this,"isDomDirty",!1);h(this,"isRunning",!1);h(this,"isTravelEnabled",!1);h(this,"pendingMask",D);h(this,"mutationTimer",null);h(this,"cssTimer",null);h(this,"resizeConfig");h(this,"resizeTimer",null);h(this,"isResizing",!1);h(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=M|O,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});h(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)});h(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});var a,n;this.target=e,this.renderer=t,this.filter=r.filter;const i=(a=r.resizeDebounce)!=null?a:!0;i===!1?this.resizeConfig={enabled:!1,delay:0}:i===!0?this.resizeConfig={enabled:!0,delay:150}:this.resizeConfig={enabled:!0,delay:(n=i.delay)!=null?n:150,onStart:i.onStart,onEnd:i.onEnd},this.observer=new MutationObserver(o=>{let l=D;for(const c of o)c.type==="childList"?l|=B:c.type==="attributes"&&(c.attributeName==="style"||c.attributeName==="class")&&(l|=M|O);if(l!==D){if(this.pendingMask|=l,l&B){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=q(this.target,this.pendingMask,e?C|E:C,this.filter);t&&this.renderer.syncScene(t),this.pendingMask=D}}class te{constructor(e,t){h(this,"renderer");h(this,"syncer");h(this,"target");var i,a,n;this.target=e;let r;if(t.mode==="duplicate"?r=(a=(i=t.container)!=null?i:this.target.parentElement)!=null?a:void 0:r=(n=this.target.parentElement)!=null?n:void 0,!r)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new Z(this.target,t,r),this.renderer.mount(),this.syncer=new ee(this.target,this.renderer,t)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}u.ALLOWED_FILTERS=P,u.DIRTY_CONTENT=L,u.DIRTY_NONE=D,u.DIRTY_RECT=M,u.DIRTY_STRUCTURE=B,u.DIRTY_STYLE=O,u.DIRTY_ZINDEX=Y,u.EXCLUDED=H,u.Engine=te,u.SYSTEM_LAYER=E,u.USER_LAYER=C,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
@@ -1,17 +1,21 @@
1
- import { FilterConfig } from '../types/config';
1
+ import { CoreConfig } from '../types/config';
2
2
  import { Renderer } from '../renderer/Renderer';
3
3
 
4
4
  export declare class Syncer {
5
5
  private target;
6
6
  private renderer;
7
- private filter?;
7
+ private filter;
8
8
  private observer;
9
9
  private isDomDirty;
10
10
  private isRunning;
11
+ private isTravelEnabled;
11
12
  private pendingMask;
12
13
  private mutationTimer;
13
14
  private cssTimer;
14
- constructor(target: HTMLElement, renderer: Renderer, filter?: FilterConfig);
15
+ private resizeConfig;
16
+ private resizeTimer;
17
+ private isResizing;
18
+ constructor(target: HTMLElement, renderer: Renderer, config: CoreConfig);
15
19
  start(): void;
16
20
  stop(): void;
17
21
  private clearTimers;
@@ -5,14 +5,17 @@ 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 textQualityFactor;
10
+ private qualityFactor;
10
11
  private mode;
12
+ private clipArea;
11
13
  private target;
12
14
  private mountContainer;
13
15
  private targetRect;
14
16
  private meshMap;
15
17
  constructor(target: HTMLElement, config: CoreConfig, mountContainer: HTMLElement);
18
+ createRenderTarget(): void;
16
19
  private applyTextQuality;
17
20
  mount(): void;
18
21
  private updateCanvasLayout;
@@ -22,5 +25,8 @@ export declare class Renderer {
22
25
  private reconcileNode;
23
26
  private reconcileTextChild;
24
27
  private updateMeshProperties;
28
+ private updateMeshLayers;
29
+ private captureRenderTarget;
25
30
  render(): void;
31
+ showScissoredRenderTarget(): void;
26
32
  }
@@ -1,4 +1,4 @@
1
- import { TextStyles, BoxStyles } from '../../packages/painter/src/index.ts';
1
+ import { TextStyles, BoxStyles, ShaderHooks } from '../../packages/painter/src/index.ts';
2
2
  import { Visibility } from './flags';
3
3
 
4
4
  export type NodeType = "BOX" | "TEXT";
@@ -18,5 +18,7 @@ export interface SceneNode {
18
18
  textStyles?: TextStyles;
19
19
  dirtyMask: number;
20
20
  visibility: Visibility;
21
+ isTraveler: boolean;
22
+ shaderHooks?: ShaderHooks;
21
23
  children: SceneNode[];
22
24
  }
@@ -1,5 +1,8 @@
1
- export type TextQuality = "low" | "medium" | "high" | number;
1
+ export type Quality = "low" | "medium" | "high" | number;
2
2
  export type MirageMode = "overlay" | "duplicate";
3
+ export type PxUnit = `${number}px`;
4
+ export type PercentUnit = `${number}%`;
5
+ export type travelerClipArea = PxUnit | PercentUnit | number;
3
6
  export interface FilterConfig {
4
7
  includeTree?: string[];
5
8
  excludeTree?: string[];
@@ -7,13 +10,20 @@ export interface FilterConfig {
7
10
  excludeSelf?: string[];
8
11
  end?: string[];
9
12
  }
13
+ export interface ResizeConfig {
14
+ delay?: number;
15
+ onStart?: () => void;
16
+ onEnd?: () => void;
17
+ }
10
18
  interface BaseConfig {
11
19
  debug?: boolean;
12
- textQuality?: TextQuality;
20
+ quality?: Quality;
13
21
  style?: {
14
22
  zIndex?: string;
15
23
  };
16
24
  filter?: FilterConfig;
25
+ resizeDebounce?: boolean | ResizeConfig;
26
+ travelerClipArea?: travelerClipArea;
17
27
  }
18
28
  export interface OverlayConfig extends BaseConfig {
19
29
  mode?: "overlay";
@@ -4,8 +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 INCLUDED = 0;
8
- export declare const SYSTEM = 29;
9
- export declare const EXCLUDED = 30;
10
- export type Visibility = typeof EXCLUDED | typeof SYSTEM | typeof INCLUDED;
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
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.1.0",
3
+ "version": "0.2.1",
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.2.3"
16
+ "@mirage-engine/painter": "0.4.0"
17
17
  },
18
18
  "private": false,
19
19
  "publishConfig": {
@@ -27,7 +27,7 @@ export class Engine {
27
27
 
28
28
  this.renderer.mount();
29
29
 
30
- this.syncer = new Syncer(this.target, this.renderer, config.filter);
30
+ this.syncer = new Syncer(this.target, this.renderer, config);
31
31
  }
32
32
 
33
33
  public start() {
@@ -1,4 +1,4 @@
1
- import { FilterConfig } from "../types/config";
1
+ import { CoreConfig } from "../types/config";
2
2
  import { Renderer } from "../renderer/Renderer";
3
3
  import { extractSceneGraph } from "../dom/Extractor";
4
4
  import {
@@ -6,28 +6,57 @@ import {
6
6
  DIRTY_RECT,
7
7
  DIRTY_STRUCTURE,
8
8
  DIRTY_STYLE,
9
- INCLUDED
9
+ USER_LAYER,
10
+ SYSTEM_LAYER,
11
+ Visibility,
10
12
  } from "../types";
11
13
 
14
+ interface InternalResizeConfig {
15
+ enabled: boolean;
16
+ delay: number;
17
+ onStart?: () => void;
18
+ onEnd?: () => void;
19
+ }
20
+
12
21
  export class Syncer {
13
22
  private target: HTMLElement;
14
23
  private renderer: Renderer;
15
- private filter?: FilterConfig;
24
+ private filter: CoreConfig["filter"];
16
25
 
17
26
  private observer: MutationObserver;
18
27
 
19
28
  private isDomDirty: boolean = false;
20
29
  private isRunning: boolean = false;
30
+ private isTravelEnabled: boolean = false;
21
31
 
22
32
  private pendingMask: number = DIRTY_NONE;
23
33
 
24
34
  private mutationTimer: number | null = null;
25
35
  private cssTimer: number | null = null;
26
36
 
27
- constructor(target: HTMLElement, renderer: Renderer, filter?: FilterConfig) {
37
+ private resizeConfig: InternalResizeConfig;
38
+ private resizeTimer: number | null = null;
39
+ private isResizing: boolean = false;
40
+
41
+ constructor(target: HTMLElement, renderer: Renderer, config: CoreConfig) {
28
42
  this.target = target;
29
43
  this.renderer = renderer;
30
- this.filter = filter;
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
+ }
31
60
 
32
61
  this.observer = new MutationObserver((mutations) => {
33
62
  let currentMask = DIRTY_NONE;
@@ -85,6 +114,8 @@ export class Syncer {
85
114
 
86
115
  this.forceUpdateScene();
87
116
  this.renderLoop();
117
+ // for debugging
118
+ // this.renderer.showScissoredRenderTarget();
88
119
  }
89
120
 
90
121
  public stop() {
@@ -125,13 +156,45 @@ export class Syncer {
125
156
  };
126
157
 
127
158
  private onWindowResize = () => {
128
- this.renderer.setSize(window.innerWidth, window.innerHeight);
129
- this.isDomDirty = true;
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);
130
178
  };
131
179
 
132
180
  private forceUpdateScene() {
133
181
  this.isDomDirty = false;
134
- const sceneGraph = extractSceneGraph(this.target, this.pendingMask, INCLUDED, this.filter);
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
+ );
135
198
 
136
199
  if (sceneGraph) {
137
200
  this.renderer.syncScene(sceneGraph);
@@ -6,12 +6,12 @@ import {
6
6
  DIRTY_STRUCTURE,
7
7
  SceneNode,
8
8
  Visibility,
9
- EXCLUDED,
10
- INCLUDED,
9
+ USER_LAYER,
10
+ SYSTEM_LAYER,
11
11
  ALLOWED_FILTERS,
12
12
  } from "../types";
13
13
 
14
- import { BoxStyles, TextStyles } from "@mirage-engine/painter";
14
+ import { BoxStyles, TextStyles, ShaderHooks } from "@mirage-engine/painter";
15
15
  import { FilterConfig } from "../types/config";
16
16
 
17
17
  // Helper function: getTextNodeRect, isValidTextNode, isLeafTextElement, extractTextStyles
@@ -100,6 +100,7 @@ export function extractSceneGraph(
100
100
  textStyles: extractTextStyles(computed),
101
101
  dirtyMask: initialMask,
102
102
  visibility: inheritedFlow,
103
+ isTraveler: false,
103
104
  children: [],
104
105
  };
105
106
  }
@@ -136,27 +137,48 @@ export function extractSceneGraph(
136
137
  }
137
138
 
138
139
  if (filterSet.has("include-tree")) {
139
- visibleFlow = INCLUDED;
140
+ visibleFlow = (visibleFlow | USER_LAYER) as Visibility;
140
141
  } else if (filterSet.has("exclude-tree")) {
141
- visibleFlow = EXCLUDED;
142
+ visibleFlow = (visibleFlow & ~USER_LAYER) as Visibility;
142
143
  }
143
144
 
144
145
  visibleFlag = visibleFlow;
145
146
 
146
147
  if (filterSet.has("include-self")) {
147
- visibleFlag = INCLUDED;
148
+ visibleFlag = (visibleFlag | USER_LAYER) as Visibility;
148
149
  } else if (filterSet.has("exclude-self")) {
149
- visibleFlag = EXCLUDED;
150
+ visibleFlag = (visibleFlag & ~USER_LAYER) as Visibility;
150
151
  }
151
152
  }
152
153
 
153
154
  // [[filter]] class based filtering
154
155
  // [Filter] end
155
- if (filterConfig && filterConfig.end && filterConfig.end.length > 0) {
156
- const isEnd = filterConfig.end.some((cls) =>
157
- element.classList.contains(cls),
158
- );
159
- if (isEnd) return null;
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
+
176
+ const shaderData = element.dataset.mirageShader;
177
+ let shaderHooks: ShaderHooks | undefined;
178
+ if (shaderData) {
179
+ shaderHooks = JSON.parse(shaderData) as ShaderHooks;
180
+ // [TODO] object 형태로 shader hooks 받기
181
+ // [TODO] 변수형태로 저장해서 return scene node에 넣어주기
160
182
  }
161
183
 
162
184
  const rect = element.getBoundingClientRect();
@@ -167,6 +189,7 @@ export function extractSceneGraph(
167
189
  return null;
168
190
  }
169
191
 
192
+ // [TODO] dataset 방식으로 변경
170
193
  let id = element.getAttribute("data-mid");
171
194
  if (!id) {
172
195
  id = Math.random().toString(36).substring(2, 11);
@@ -216,6 +239,8 @@ export function extractSceneGraph(
216
239
  textStyles,
217
240
  dirtyMask: initialMask,
218
241
  visibility: visibleFlag,
242
+ isTraveler: isTraveler,
219
243
  children,
244
+ shaderHooks,
220
245
  };
221
246
  }