@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.
- package/CHANGELOG.md +19 -0
- package/dist/mirage-engine.js +434 -328
- package/dist/mirage-engine.umd.js +36 -33
- package/dist/src/core/Syncer.d.ts +7 -3
- package/dist/src/renderer/Renderer.d.ts +7 -1
- package/dist/src/types/common.d.ts +3 -1
- package/dist/src/types/config.d.ts +12 -2
- package/dist/src/types/flags.d.ts +4 -4
- package/package.json +2 -2
- package/src/core/Engine.ts +1 -1
- package/src/core/Syncer.ts +71 -8
- package/src/dom/Extractor.ts +37 -12
- package/src/renderer/Renderer.ts +144 -24
- package/src/types/common.ts +6 -5
- package/src/types/config.ts +13 -3
- package/src/types/flags.ts +4 -5
|
@@ -1,16 +1,18 @@
|
|
|
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 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
|
-
`,
|
|
10
|
+
`,J=`
|
|
9
11
|
|
|
10
12
|
varying vec2 vUv;
|
|
11
13
|
|
|
12
14
|
uniform vec2 uSize;
|
|
13
|
-
uniform vec4
|
|
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
|
-
|
|
23
|
+
#INJECT_DECLARATIONS
|
|
24
|
+
|
|
22
25
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
23
26
|
vec2 q = abs(p) - b + r;
|
|
24
|
-
|
|
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
|
-
//
|
|
35
|
-
|
|
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 =
|
|
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 +
|
|
63
|
+
vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
|
|
64
|
+
vec4 finalColor = vec4(cOut, aOut);
|
|
73
65
|
|
|
74
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
8
|
-
export declare const
|
|
9
|
-
export declare const EXCLUDED =
|
|
10
|
-
export type Visibility =
|
|
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
|
|
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.
|
|
16
|
+
"@mirage-engine/painter": "0.4.0"
|
|
17
17
|
},
|
|
18
18
|
"private": false,
|
|
19
19
|
"publishConfig": {
|
package/src/core/Engine.ts
CHANGED
package/src/core/Syncer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
129
|
-
|
|
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
|
-
|
|
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);
|
package/src/dom/Extractor.ts
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
DIRTY_STRUCTURE,
|
|
7
7
|
SceneNode,
|
|
8
8
|
Visibility,
|
|
9
|
-
|
|
10
|
-
|
|
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 =
|
|
140
|
+
visibleFlow = (visibleFlow | USER_LAYER) as Visibility;
|
|
140
141
|
} else if (filterSet.has("exclude-tree")) {
|
|
141
|
-
visibleFlow =
|
|
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 =
|
|
148
|
+
visibleFlag = (visibleFlag | USER_LAYER) as Visibility;
|
|
148
149
|
} else if (filterSet.has("exclude-self")) {
|
|
149
|
-
visibleFlag =
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
}
|