lunchboxjs 0.1.4015 → 0.1.4018

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.
@@ -64,16 +64,30 @@
64
64
  }
65
65
  };
66
66
 
67
- const prepCanvas = (container, canvasElement, onBeforeUnmount) => {
67
+ const getInnerDimensions = (node) => {
68
+ const computedStyle = getComputedStyle(node);
69
+ const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
70
+ const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
71
+ return { width, height };
72
+ };
73
+ const prepCanvas = (container, canvasElement, onBeforeUnmount, sizePolicy) => {
68
74
  const containerElement = container.value?.domElement;
69
75
  if (!containerElement)
70
76
  throw new Error('missing container');
71
77
  // save...
72
78
  // ...and size element
73
- resizeCanvas();
79
+ const resizeCanvasByPolicy = () => {
80
+ if (sizePolicy === "container") {
81
+ const dims = getInnerDimensions(containerElement);
82
+ resizeCanvas(dims.width, dims.height);
83
+ }
84
+ else
85
+ resizeCanvas();
86
+ };
87
+ resizeCanvasByPolicy();
74
88
  // attach listeners
75
89
  const observer = new ResizeObserver(([canvas]) => {
76
- resizeCanvas();
90
+ resizeCanvasByPolicy();
77
91
  });
78
92
  // window.addEventListener('resize', resizeCanvas)
79
93
  if (containerElement) {
@@ -99,6 +113,7 @@
99
113
  left: 0,
100
114
  width: '100%',
101
115
  height: '100%',
116
+ display: 'block',
102
117
  };
103
118
  };
104
119
  const LunchboxWrapper = {
@@ -116,9 +131,11 @@
116
131
  r3f: Boolean,
117
132
  rendererArguments: Object,
118
133
  rendererProperties: Object,
134
+ sizePolicy: String,
119
135
  shadow: [Boolean, Object],
120
136
  transparent: Boolean,
121
137
  zoom: Number,
138
+ updateSource: Object,
122
139
  },
123
140
  setup(props, context) {
124
141
  const canvas = vue.ref();
@@ -273,7 +290,7 @@
273
290
  renderer.instance.setPixelRatio(dpr.value);
274
291
  globals.dpr.value = dpr.value;
275
292
  // prep canvas (sizing, observe, unmount, etc)
276
- prepCanvas(container, renderer.instance.domElement, vue.onBeforeUnmount);
293
+ prepCanvas(container, renderer.instance.domElement, vue.onBeforeUnmount, props.sizePolicy);
277
294
  }
278
295
  else {
279
296
  throw new Error('missing renderer');
@@ -298,24 +315,28 @@
298
315
  camera: camera.instance,
299
316
  renderer: renderer.instance,
300
317
  scene: scene.instance,
318
+ updateSource: props.updateSource,
301
319
  });
302
320
  });
303
321
  // UNMOUNT
304
322
  // ====================
305
323
  vue.onBeforeUnmount(() => {
306
324
  cancelUpdate();
325
+ cancelUpdateSource();
307
326
  });
308
327
  // RENDER FUNCTION
309
328
  // ====================
329
+ const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
330
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
310
331
  return () => [
311
332
  context.slots.default?.() ?? null,
312
333
  vue.h('div', {
313
- style: fillStyle('absolute'),
334
+ style: fillStyle(containerFillStyle),
314
335
  ref: container,
315
336
  }, [
316
337
  useFallbackRenderer.value
317
338
  ? vue.h('canvas', {
318
- style: fillStyle('fixed'),
339
+ style: fillStyle(canvasFillStyle),
319
340
  class: 'lunchbox-canvas',
320
341
  ref: canvas,
321
342
  })
@@ -649,6 +670,7 @@
649
670
  let mouseUpListener;
650
671
  const mousePos = vue.ref({ x: Infinity, y: Infinity });
651
672
  let autoRaycasterEventsInitialized = false;
673
+ let frameID$1;
652
674
  const setupAutoRaycaster = (node) => {
653
675
  const instance = node.instance;
654
676
  if (!instance)
@@ -681,8 +703,14 @@
681
703
  renderer.instance.domElement.addEventListener('mousedown', mouseDownListener);
682
704
  renderer.instance.domElement.addEventListener('mouseup', mouseUpListener);
683
705
  // TODO: add touch events
684
- // add to update loop
685
- onBeforeRender(autoRaycasterBeforeRender);
706
+ // process mouse events asynchronously, whenever the mouse state changes
707
+ vue.watch(() => [inputActive.value, mousePos.value.x, mousePos.value.y], () => {
708
+ if (frameID$1)
709
+ cancelAnimationFrame(frameID$1);
710
+ frameID$1 = requestAnimationFrame(() => {
711
+ autoRaycasterBeforeRender();
712
+ });
713
+ });
686
714
  // mark complete
687
715
  autoRaycasterEventsInitialized = true;
688
716
  // cancel setup watcher
@@ -1011,7 +1039,7 @@
1011
1039
  };
1012
1040
 
1013
1041
  /** Process props into either themselves or the $attached value */
1014
- function processProp({ node, prop }) {
1042
+ function processProp({ node, prop, }) {
1015
1043
  // return $attachedArray value if needed
1016
1044
  if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
1017
1045
  return node.attachedArray[prop.replace('$attachedArray.', '')];
@@ -1023,10 +1051,12 @@
1023
1051
  // otherwise, return plain value
1024
1052
  return prop;
1025
1053
  }
1026
- function processPropAsArray({ node, prop }) {
1054
+ function processPropAsArray({ node, prop, }) {
1027
1055
  const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray');
1028
1056
  const output = processProp({ node, prop });
1029
- return Array.isArray(output) && isAttachedArray ? output : [output];
1057
+ return Array.isArray(output) && isAttachedArray
1058
+ ? output
1059
+ : [output];
1030
1060
  }
1031
1061
 
1032
1062
  function instantiateThreeObject(node) {
@@ -1325,16 +1355,34 @@
1325
1355
  };
1326
1356
 
1327
1357
  let frameID;
1358
+ let watchStopHandle;
1328
1359
  const beforeRender = [];
1329
1360
  const afterRender = [];
1330
- const update = (opts) => {
1331
- // request next frame
1361
+ const requestUpdate = (opts) => {
1362
+ cancelUpdate();
1332
1363
  frameID = requestAnimationFrame(() => update({
1333
1364
  app: opts.app,
1334
1365
  renderer: ensureRenderer.value?.instance,
1335
1366
  scene: ensuredScene.value.instance,
1336
1367
  camera: ensuredCamera.value?.instance,
1368
+ updateSource: opts.updateSource,
1337
1369
  }));
1370
+ };
1371
+ const update = (opts) => {
1372
+ if (opts.updateSource) {
1373
+ if (!watchStopHandle) {
1374
+ // request next frame only when state changes
1375
+ watchStopHandle = vue.watch(opts.updateSource, () => {
1376
+ requestUpdate(opts);
1377
+ }, {
1378
+ deep: true,
1379
+ });
1380
+ }
1381
+ }
1382
+ else {
1383
+ // request next frame on a continuous loop
1384
+ requestUpdate(opts);
1385
+ }
1338
1386
  // prep options
1339
1387
  const { app, renderer, scene, camera } = opts;
1340
1388
  // BEFORE RENDER
@@ -1397,6 +1445,10 @@
1397
1445
  if (frameID)
1398
1446
  cancelAnimationFrame(frameID);
1399
1447
  };
1448
+ const cancelUpdateSource = () => {
1449
+ if (watchStopHandle)
1450
+ watchStopHandle();
1451
+ };
1400
1452
 
1401
1453
  /** Update a single prop on a given node. */
1402
1454
  function updateObjectProp({ node, key, value, }) {
@@ -1434,6 +1486,7 @@
1434
1486
  liveProperty = liveProperty = lodash.get(target, fullPath);
1435
1487
  }
1436
1488
  // change property
1489
+ // first, save as array in case we need to spread it
1437
1490
  if (liveProperty && lodash.isNumber(value) && liveProperty.setScalar) {
1438
1491
  // if value is a number and the property has a `setScalar` method, use that
1439
1492
  liveProperty.setScalar(value);
@@ -1444,8 +1497,18 @@
1444
1497
  target[finalKey].set(...nextValueAsArray);
1445
1498
  }
1446
1499
  else if (typeof liveProperty === 'function') {
1447
- // if property is a function, let's try calling it
1448
- liveProperty.bind(node.instance)(...value);
1500
+ // some function properties are set rather than called, so let's handle them
1501
+ if (finalKey.toLowerCase() === 'onbeforerender' ||
1502
+ finalKey.toLowerCase() === 'onafterrender') {
1503
+ target[finalKey] = value;
1504
+ }
1505
+ else {
1506
+ if (!Array.isArray(value)) {
1507
+ throw new Error('Arguments on a declarative method must be wrapped in an array.\nWorks:\n<example :methodCall="[256]" />\nDoesn\'t work:\n<example :methodCall="256" />');
1508
+ }
1509
+ // if property is a function, let's try calling it
1510
+ liveProperty.bind(node.instance)(...value);
1511
+ }
1449
1512
  // pass the result to the parent
1450
1513
  // const parent = node.parentNode
1451
1514
  // if (parent) {
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue"),require("three"),require("lodash")):"function"==typeof define&&define.amd?define(["exports","vue","three","lodash"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).LunchboxRenderer={},e.vue,e.three,e.lodash)}(this,(function(e,t,n,r){"use strict";function o(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,Object.freeze(t)}var a=o(n);const s=[],i=(e,n)=>{const r=W.value?.instance,o=I.value.instance,a=D.value;if(!r?.domElement||!o||!a)return;const s=(e=e??window.innerWidth)/(n=n??window.innerHeight);if("perspectivecamera"===a.type?.toLowerCase()){const e=a.instance;e.aspect=s,e.updateProjectionMatrix()}else if("orthographiccamera"===a.type?.toLowerCase()){const t=a.instance,r=n/e;t.top=10*r,t.bottom=10*-r,t.right=10,t.left=-10,t.updateProjectionMatrix()}else console.log("TODO: non-ortho or perspective camera");r.setSize(e,n),o&&a.instance&&r.render(t.toRaw(o),t.toRaw(a.instance))},c=e=>({position:e,top:0,right:0,bottom:0,left:0,width:"100%",height:"100%"}),d={name:"Lunchbox",props:{background:String,cameraArgs:Array,cameraLook:Array,cameraLookAt:Array,cameraPosition:Array,dpr:Number,ortho:Boolean,orthographic:Boolean,r3f:Boolean,rendererArguments:Object,rendererProperties:Object,shadow:[Boolean,Object],transparent:Boolean,zoom:Number},setup(e,n){const o=t.ref(),s=t.ref(!0),d=t.ref(e.dpr??-1),u=t.ref();let l,p,m;return e.r3f&&a?.ColorManagement&&(a.ColorManagement.legacyMode=!1),t.onMounted((()=>{if(!o.value)throw new Error("missing canvas");if(l=N(["WebGLRenderer"]),l)s.value=!1,U.value=!0;else{const t={alpha:e.transparent,antialias:!0,canvas:o.value.domElement,powerPreference:e.r3f?"high-performance":"default",...e.rendererArguments??{}};W.value=K({type:"WebGLRenderer",uuid:F,props:{args:[t]}}),U.value=!0;const n=W;e.r3f&&n.value.instance&&(n.value.instance.outputEncoding=a.sRGBEncoding,n.value.instance.toneMapping=a.ACESFilmicToneMapping);const s={shadow:e.shadow};n.value.instance&&s?.shadow&&(n.value.instance.shadowMap.enabled=!0,"object"==typeof s.shadow&&(n.value.instance.shadowMap.type=s.shadow.type)),e.rendererProperties&&Object.keys(e.rendererProperties).forEach((t=>{r.set(n.value,t,e.rendererProperties[t])})),l=n.value}if(p=N(["PerspectiveCamera","OrthographicCamera"]),p?S.value=!0:(e.ortho||e.orthographic?D.value=K({props:{args:e.cameraArgs??[]},type:"OrthographicCamera",uuid:j}):D.value=K({props:{args:e.cameraArgs??[e.r3f?75:45,.5625,1,1e3]},type:"PerspectiveCamera",uuid:j}),S.value=!0,p=D.value),!p.instance)throw new Error("Error creating camera.");if(p&&e.cameraPosition&&p.instance.position.set(...e.cameraPosition),p&&(e.cameraLookAt||e.cameraLook)){const t=e.cameraLookAt||e.cameraLook;p.instance.lookAt(...t)}if(p&&void 0!==e.zoom&&(p.instance.zoom=e.zoom),m=I.value,m&&m.instance&&e.background&&(m.instance.background=new a.Color(e.background)),-1===d.value&&(d.value=window.devicePixelRatio),!l?.instance)throw new Error("missing renderer");l.instance.setPixelRatio(d.value),he.dpr.value=d.value,((e,t,n)=>{const r=e.value?.domElement;if(!r)throw new Error("missing container");i();const o=new ResizeObserver((([e])=>{i()}));r&&o.observe(r),n((()=>{t&&o.unobserve(t)}))})(u,l.instance.domElement,t.onBeforeUnmount);const n=t.getCurrentInstance().appContext.app;for(let e of te)e({app:n,camera:p.instance,renderer:l.instance,scene:m.instance});ae({app:n,camera:p.instance,renderer:l.instance,scene:m.instance})})),t.onBeforeUnmount((()=>{ie()})),()=>[n.slots.default?.()??null,t.h("div",{style:c("absolute"),ref:u},[s.value?t.h("canvas",{style:c("fixed"),class:"lunchbox-canvas",ref:o}):null])]}},u={},l=["canvas","div","LunchboxWrapper"],p={...["mesh","instancedMesh","scene","sprite","object3D","instancedBufferGeometry","bufferGeometry","boxBufferGeometry","circleBufferGeometry","coneBufferGeometry","cylinderBufferGeometry","dodecahedronBufferGeometry","extrudeBufferGeometry","icosahedronBufferGeometry","latheBufferGeometry","octahedronBufferGeometry","parametricBufferGeometry","planeBufferGeometry","polyhedronBufferGeometry","ringBufferGeometry","shapeBufferGeometry","sphereBufferGeometry","tetrahedronBufferGeometry","textBufferGeometry","torusBufferGeometry","torusKnotBufferGeometry","tubeBufferGeometry","wireframeGeometry","parametricGeometry","tetrahedronGeometry","octahedronGeometry","icosahedronGeometry","dodecahedronGeometry","polyhedronGeometry","tubeGeometry","torusKnotGeometry","torusGeometry","sphereGeometry","ringGeometry","planeGeometry","latheGeometry","shapeGeometry","extrudeGeometry","edgesGeometry","coneGeometry","cylinderGeometry","circleGeometry","boxGeometry","material","shadowMaterial","spriteMaterial","rawShaderMaterial","shaderMaterial","pointsMaterial","meshPhysicalMaterial","meshStandardMaterial","meshPhongMaterial","meshToonMaterial","meshNormalMaterial","meshLambertMaterial","meshDepthMaterial","meshDistanceMaterial","meshBasicMaterial","meshMatcapMaterial","lineDashedMaterial","lineBasicMaterial","light","spotLightShadow","spotLight","pointLight","rectAreaLight","hemisphereLight","directionalLightShadow","directionalLight","ambientLight","lightShadow","ambientLightProbe","hemisphereLightProbe","lightProbe","texture","videoTexture","dataTexture","dataTexture3D","compressedTexture","cubeTexture","canvasTexture","depthTexture","textureLoader","group","catmullRomCurve3","points","cameraHelper","camera","perspectiveCamera","orthographicCamera","cubeCamera","arrayCamera","webGLRenderer"].map((e=>t.defineComponent({inheritAttrs:!1,name:e,setup:(n,r)=>()=>t.h(e,r.attrs,r.slots?.default?.()||[])}))).reduce(((e,t)=>(e[t.name]=t,e)),{}),Lunchbox:d};const m=e=>e?.$el&&e?.$el?.hasOwnProperty?.("instance"),h=e=>{if("domMeta"===e?.metaType)return!0;const t="string"==typeof e?e:e?.type;return l.includes(t??"")},f=e=>"standardMeta"===e?.metaType,y=e=>e.isLunchboxRootNode,v=[],g=t.ref(!1);function b({node:e,key:n,value:r}){var o;if(e.eventListeners[n]||(e.eventListeners[n]=[]),e.eventListenerRemoveFunctions[n]||(e.eventListenerRemoveFunctions[n]=[]),e.eventListeners[n].push(r),x.includes(n)&&(z.value,e.instance&&!v.includes(e)&&(o=e,v.push(o),e.eventListenerRemoveFunctions[n].push((()=>(e=>{const t=v.indexOf(e);-1!==t&&v.splice(t,1)})(e))))),"onClick"===n||"onPointerDown"===n||"onPointerUp"===n){const r=t.watch((()=>g.value),(t=>{const r=L.map((e=>e.element)).findIndex((t=>t.instance&&t.instance.uuid===e.instance?.uuid));-1!==r&&((!t||"onClick"!==n&&"onPointerDown"!==n)&&(t||"onPointerUp"!==n)||e.eventListeners[n].forEach((e=>{e({intersection:L[r].intersection})})))}));e.eventListenerRemoveFunctions[n].push(r)}return e}const x=["onClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove"];let R,w,A;const C=t.ref({x:1/0,y:1/0});let E=!1;let L=[];const P=()=>{const e=z.value?.instance,t=D.value?.instance;if(!e||!t)return;e.setFromCamera(he.mousePos.value,t);const n=e.intersectObjects(v.map((e=>e.instance)));let r=[],o=[],a=[];r=L.map((e=>e.intersection)),n?.forEach((e=>{if(-1===L.findIndex((t=>t.intersection.object===e.object))){const t=v.find((t=>t.instance?.uuid===e.object.uuid));t&&o.push({element:t,intersection:e})}else{const t=v.find((t=>t.instance?.uuid===e.object.uuid));t&&a.push({element:t,intersection:e})}const t=r.findIndex((t=>t.object.uuid===e.object.uuid));-1!==t&&r.splice(t,1)}));const s=r.map((e=>({element:v.find((t=>t.instance?.uuid===e.object.uuid)),intersection:e})));o.forEach((({element:e,intersection:t})=>{M({element:e,eventKeys:["onPointerEnter"],intersection:t})})),a.forEach((({element:e,intersection:t})=>{M({element:e,eventKeys:["onPointerOver","onPointerMove"],intersection:t})})),s.forEach((({element:e,intersection:t})=>{M({element:e,eventKeys:["onPointerLeave","onPointerOut"],intersection:t})})),L=[].concat(o,a)},M=({element:e,eventKeys:t,intersection:n})=>{e&&t.forEach((t=>{e.eventListeners[t]&&e.eventListeners[t].forEach((e=>{e({intersection:n})}))}))};function B(t={}){return e.lunchboxTree||(e.lunchboxTree=new J.RendererRootNode(t)),e.lunchboxTree}function N(e){Array.isArray(e)||(e=[e]);for(let t of e)if(T[t])return T[t];for(let n of e){const e=G[n]||s.find((e=>e.type?.toLowerCase()===n.toLowerCase()));if(!(t=e,"RendererNode"!==t?.minidomType||!1!==e.props["is-default"]&&!1!=!e.props.isDefault))return null;if(e){const t=e;return G[n]=t,t}}var t;return null}e.lunchboxTree=void 0;const G=t.reactive({}),T=t.reactive({});function O(e,n,r={},o=null){Array.isArray(e)||(e=[e]);for(let t of e)G[t]||(G[t]=null),T[t]||(T[t]=null);return t.computed({get(){const t=N(e);if(t)return t;const a=B(),s=K({type:e[0],uuid:n,props:r});return a.addChild(s),G[e[0]]=s,o&&o(s),s},set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);T[n]=e}})}const j="FALLBACK_CAMERA",k=O(["PerspectiveCamera","OrthographicCamera"],j,{args:[45,.5625,1,1e3]}),S=t.ref(!1),D=t.computed({get:()=>S.value?k.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);T[n]=e}}),F="FALLBACK_RENDERER",$=O(["WebGLRenderer"],F,{}),U=t.ref(!1),W=t.computed({get:()=>U.value?$.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);T[n]=e}}),I=O("Scene","FALLBACK_SCENE"),z=O("Raycaster","AUTO_RAYCASTER",{},(e=>(e=>{if(!e.instance)return;let n=null;n=t.watch((()=>W.value),(e=>{e?.instance&&(E||(R=t=>{const n=(e.instance.domElement.width??1)/he.dpr.value,r=(e.instance.domElement.height??1)/he.dpr.value;C.value.x=t.offsetX/n*2-1,C.value.y=-t.offsetY/r*2+1},w=()=>g.value=!0,A=()=>g.value=!1,e.instance.domElement.addEventListener("mousemove",R),e.instance.domElement.addEventListener("mousedown",w),e.instance.domElement.addEventListener("mouseup",A),se(P),E=!0),n&&n())}),{immediate:!0})})(e)));function K(e={},t={}){const n={attached:e.attached??[],attachedArray:e.attachedArray??{},instance:e.instance??null},r=new J.RendererStandardNode({...e,...n,metaType:"standardMeta"});return!r.type||y(r)||r.instance||(r.instance=function(e){if(!e.type)return null;const t=e.type[0].toUpperCase()+e.type.slice(1),n=u[e.type]||a[t];if(!n)throw`${t} is not part of the THREE namespace! Did you forget to extend? import {extend} from 'lunchbox'; extend({app, YourComponent, ...})`;const r=(e.props.args??[]).map((t=>function({node:e,prop:t}){const n="string"==typeof t&&t.startsWith("$attachedArray"),r=function({node:e,prop:t}){if("string"==typeof t&&t.startsWith("$attachedArray"))return e.attachedArray[t.replace("$attachedArray.","")];if("string"==typeof t&&t.startsWith("$attached"))return e.attached[t.replace("$attached.","")];return t}({node:e,prop:t});return Array.isArray(r)&&n?r:[r]}({node:e,prop:t})));let o=[];r.forEach((e=>{o=o.concat(e)}));return new n(...o)}({...r,props:{...r.props,...t}})),"scene"===r.type?.toLowerCase()?I.value=r:r.type?.toLowerCase().endsWith("camera")&&(D.value=r),r}const _=e=>t.defineComponent({inheritAttrs:!1,name:e,render(){return t.h(e,this.$attrs,this.$slots?.default?.()||[])}});var V,q=new Uint8Array(16);function H(){if(!V&&!(V="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return V(q)}var Y=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function X(e){return"string"==typeof e&&Y.test(e)}for(var J,Q=[],Z=0;Z<256;++Z)Q.push((Z+256).toString(16).substr(1));function ee(e,t,n){var r=(e=e||{}).random||(e.rng||H)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(Q[e[t+0]]+Q[e[t+1]]+Q[e[t+2]]+Q[e[t+3]]+"-"+Q[e[t+4]]+Q[e[t+5]]+"-"+Q[e[t+6]]+Q[e[t+7]]+"-"+Q[e[t+8]]+Q[e[t+9]]+"-"+Q[e[t+10]]+Q[e[t+11]]+Q[e[t+12]]+Q[e[t+13]]+Q[e[t+14]]+Q[e[t+15]]).toLowerCase();if(!X(n))throw TypeError("Stringified UUID is invalid");return n}(r)}!function(e){e.BaseNode=class{constructor(e={},t){this.parentNode=e?.parentNode??t??null,this.minidomType="MinidomBaseNode",this.uuid=e?.uuid??ee(),s.push(this)}uuid;parentNode;get nextSibling(){if(!this.parentNode)return null;const e=this.parentNode.children.findIndex((e=>e.uuid===this.uuid));return-1!==e&&e<this.parentNode.children.length-1?this.parentNode.children[e+1]:null}insertBefore(e,t){e.removeAsChildFromAnyParents(),e.parentNode=this;const n=this.children.findIndex((e=>e.uuid===t?.uuid));-1!==n?this.children.splice(n,0,e):this.children.push(e)}removeChild(e){const t=this.children.findIndex((t=>t?.uuid===e?.uuid));-1!==t&&this.children.splice(t,1)}children=[];addChild(e){return e&&(e.removeAsChildFromAnyParents(),e.parentNode=this,this.insertBefore(e,null)),this}getPath(){const e=[];let t=this;for(;t;)e.unshift(t),t=t.parentNode;return e}drop(){this.parentNode=null,this.removeAsChildFromAnyParents()}walk(e){const t=[this,...this.children],n=[];let r=!0;for(;t.length&&r;){const o=t.shift();if(o){if(n.includes(o))continue;n.push(o),t.push(...o.children.filter((e=>!n.includes(e)))),r=e(o)}else r=!1}}minidomType;removeAsChildFromAnyParents(){s.forEach((e=>e.removeChild(this)))}};class t extends e.BaseNode{constructor(e={},t){super(e,t),this.minidomType="RendererNode",this.eventListeners={},this.eventListenerRemoveFunctions={},this.name=e.name??"",this.metaType=e.metaType??"standardMeta",this.props=e.props??[],this.type=e.type??""}eventListeners;eventListenerRemoveFunctions;name;metaType;props;type;drop(){super.drop(),Object.keys(this.eventListenerRemoveFunctions).forEach((e=>{this.eventListenerRemoveFunctions[e].forEach((e=>e()))}))}}e.RendererBaseNode=t;class n extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement;isLunchboxRootNode=!0}e.RendererRootNode=n;class r extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererCommentNode=r;class o extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement}e.RendererDomNode=o;class a extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererTextNode=a;class i extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.attached=e.attached??[],this.attachedArray=e.attachedArray??{},this.instance=e.instance??null}attached;attachedArray;instance}e.RendererStandardNode=i}(J||(J={}));(new J.RendererRootNode).minidomType="RootNode";const te=[];let ne;const re=[],oe=[],ae=e=>{ne=requestAnimationFrame((()=>ae({app:e.app,renderer:W.value?.instance,scene:I.value.instance,camera:D.value?.instance})));const{app:n,renderer:r,scene:o,camera:a}=e;re.forEach((t=>{t&&t(e)})),r&&o&&a&&(n.customRender?n.customRender(e):r.render(t.toRaw(o),t.toRaw(a))),oe.forEach((t=>{t&&t(e)}))},se=(e,t=1/0)=>{t===1/0?re.push(e):re.splice(t,0,e)},ie=()=>{ne&&cancelAnimationFrame(ne)};const ce={x:"position.x",y:"position.y",z:"position.z"},de=["","parameters"],ue=["args","attach","attachArray","is.default","isDefault","key","onAdded","ref","src"],le=["geometry","material"];function pe(e,t,n,r){const o=r??e.instance,a=t.instance;e.props.attach===n&&(t.attached={[n]:o,...t.attached||{}},a[n]=r??e.instance),e.props.attachArray===n&&(t.attachedArray[e.props.attachArray]||(t.attachedArray[e.props.attachArray]=[]),t.attachedArray[e.props.attachArray].push(o),a[n]=[a[n]])}const me={createElement:(e,t,n,r)=>{const o={type:e};r&&(o.props=r);if(h(e)){const e=function(e={}){const t={domElement:document.createElement(e.type??"")};return new J.RendererDomNode({...t,...e,metaType:"domMeta"})}(o);return e}const a=K(o);return le.forEach((t=>{e.toLowerCase().endsWith(t)&&(a.props.attach=t)})),a},createText:e=>function(e={}){const t={text:e.text??""};return new J.RendererTextNode({...e,...t,metaType:"textMeta"})}({text:e}),createComment:e=>function(e={}){const t={text:e.text??""};return new J.RendererCommentNode({...t,...e,metaType:"commentMeta"})}({text:e}),insert:(e,t,n)=>{let r=t??B();if(r.insertBefore(e,n),"commentMeta"!==e.metaType&&"textMeta"!==e.metaType&&(h(e)&&(h(t)||y(t))&&t.domElement.appendChild(e.domElement),f(e))){let n=r.metaType;if("textMeta"===n||"commentMeta"===n){const e=r.getPath();for(let t=e.length-1;t>=0;t--)if("textMeta"!==e[t].metaType&&"commentMeta"!==e[t].metaType){r=e[t];break}}if("standardMeta"===e.metaType&&"scene"!==e.type&&y(r)){const t=I.value;t.instance&&e&&t.addChild(e),e.instance&&e.instance.isObject3D&&t.instance&&t!==e&&t.instance.add(e.instance)}else f(e)&&e.instance?.isObject3D&&f(r)&&r.instance?.isObject3D&&r.instance?.add?.(e.instance);if(e?.props?.attach&&f(t)&&t?.instance){e.type?.toLowerCase().endsWith("loader")&&e.props.src&&(e.props.attach||e.props.attachArray)?function(e,t){const n=e.instance;if(t.attached=t.attached||{},t.attachedArray=t.attachedArray||{},!e.props.attach)return;if("textureloader"===e.type?.toLowerCase()){const r=n.load(e.props.src);pe(e,t,e.props.attach,r)}else n.load(e.props.src,(n=>{pe(e,t,e.props.attach,n)}),null,(e=>{throw new Error(e)}))}(e,t):pe(e,t,e.props.attach)}e.props?.onAdded&&e.props.onAdded({instance:e.instance})}},nextSibling(e){const t=e.nextSibling;return t||null},parentNode(e){const t=e.parentNode;return t||null},patchProp(e,t,n,o){h(e)?"style"===t?Object.keys(o).forEach((t=>{e.domElement.style[t]=o[t]})):e.domElement.setAttribute(t,o):y(e)||t.startsWith("$")||function({node:e,key:t,value:n}){if((e=>["onClick","onContextMenu","onDoubleClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove","onWheel"].includes(e))(t))return b({node:e,key:t,value:n});const o=t.replace(/-/g,"."),a=ce[o]||o;if(ue.includes(t)||ue.includes(a))return e;if(!f(e))return e;if("string"==typeof n&&n.startsWith("$attached")){const t=n.replace("$attached.","");n=r.get(e.attached,t,null)}const s=e.instance;if(!s)return e;let i;for(let e=0;e<de.length&&!i;e++){const t=[de[e],a].filter(Boolean).join(".");i=i=r.get(s,t)}if(i&&r.isNumber(n)&&i.setScalar)i.setScalar(n);else if(i&&i.set){const e=Array.isArray(n)?n:[n];s[a].set(...e)}else"function"==typeof i?i.bind(e.instance)(...n):void 0!==r.get(s,a,void 0)?r.set(s,a,""===n||n):console.log(`No property ${a} found on ${s}`);const c=s?.texture?.type||s?.type;if("string"==typeof c){const e=c.toLowerCase();switch(!0){case e.includes("material"):s.needsUpdate=!0;break;case e.includes("camera")&&s.updateProjectionMatrix:s.updateProjectionMatrix()}}}({node:e,key:t,value:o})},remove:e=>{if(!e)return;const t=Object.keys(T),n=[];e.walk((e=>(n.push(e),!0))),n.forEach((e=>{const n=t.find((t=>T[t]?.uuid===e.uuid));if(n&&(T[n]=null),f(e)){e.instance?.removeFromParent?.();const t="scene"!==e.type&&e.instance?.dispose;t&&t.bind(e.instance)(),e.instance=null}e.drop();const r=s.findIndex((t=>t.uuid===e.uuid));-1!==r&&s.splice(r,1)}))},setElementText(){},setText(){}},he={dpr:t.ref(1),inputActive:g,mousePos:C},fe=t.computed((()=>D.value?.instance??null));const ye=t.computed((()=>W.value?.instance??null));const ve=t.computed((()=>I.value.instance));let ge=null,be=null;e.camera=fe,e.clearCustomRender=()=>{ge?ge.clearCustomRender():be=null},e.createApp=e=>{ge=t.createRenderer(me).createApp(e),Object.keys(p).forEach((e=>{ge?.component(e,p[e])}));const{mount:n}=ge;return ge.mount=(e,...t)=>{const r=B({domElement:"string"==typeof e?document.querySelector(e):e,isLunchboxRootNode:!0,name:"root",metaType:"rootMeta",type:"root",uuid:"LUNCHBOX_ROOT"});ge.rootNode=r;return n(r,...t)},ge.extend=e=>((({app:e,...t})=>{Object.keys(t).forEach((n=>{e.component(n,_(n)),u[n]=t[n]}))})({app:ge,...e}),ge),ge.setCustomRender=e=>{ge.customRender=e},be&&(ge.setCustomRender(be),be=null),ge.clearCustomRender=()=>{ge.customRender=null},ge},e.find=function(e){return e=t.isRef(e)?e.value:e,f(e)?e?.instance:m(e)?e?.$el?.instance:t.isVNode(e)?e.el?.instance:null},e.globals=he,e.offAfterRender=e=>{if(isFinite(e))oe.splice(e,1);else{const t=oe.findIndex((t=>t==e));oe.splice(t,1)}},e.offBeforeRender=e=>{if(isFinite(e))re.splice(e,1);else{const t=re.findIndex((t=>t==e));re.splice(t,1)}},e.onAfterRender=(e,t=1/0)=>{t===1/0?oe.push(e):oe.splice(t,0,e)},e.onBeforeRender=se,e.onStart=(e,t=1/0)=>{t===1/0?te.push(e):te.splice(t,0,e)},e.renderer=ye,e.scene=ve,e.setCustomRender=e=>{ge?ge.setCustomRender(e):be=e},e.useCamera=function(e){return t.watch(fe,(t=>{t&&e(t)}),{immediate:!0})},e.useRenderer=function(e){return t.watch(ye,(t=>{t&&e(t)}),{immediate:!0})},e.useScene=function(e){return t.watch(ve,(t=>{t&&e(t)}),{immediate:!0})},Object.defineProperty(e,"__esModule",{value:!0})}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue"),require("three"),require("lodash")):"function"==typeof define&&define.amd?define(["exports","vue","three","lodash"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).LunchboxRenderer={},e.vue,e.three,e.lodash)}(this,(function(e,t,n,r){"use strict";function o(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,Object.freeze(t)}var a=o(n);const s=[],i=(e,n)=>{const r=I.value?.instance,o=K.value.instance,a=$.value;if(!r?.domElement||!o||!a)return;const s=(e=e??window.innerWidth)/(n=n??window.innerHeight);if("perspectivecamera"===a.type?.toLowerCase()){const e=a.instance;e.aspect=s,e.updateProjectionMatrix()}else if("orthographiccamera"===a.type?.toLowerCase()){const t=a.instance,r=n/e;t.top=10*r,t.bottom=10*-r,t.right=10,t.left=-10,t.updateProjectionMatrix()}else console.log("TODO: non-ortho or perspective camera");r.setSize(e,n),o&&a.instance&&r.render(t.toRaw(o),t.toRaw(a.instance))},c=(e,t,n,r)=>{const o=e.value?.domElement;if(!o)throw new Error("missing container");const a=()=>{if("container"===r){const e=(e=>{const t=getComputedStyle(e);return{width:e.clientWidth-parseFloat(t.paddingLeft)-parseFloat(t.paddingRight),height:e.clientHeight-parseFloat(t.paddingTop)-parseFloat(t.paddingBottom)}})(o);i(e.width,e.height)}else i()};a();const s=new ResizeObserver((([e])=>{a()}));o&&s.observe(o),n((()=>{t&&s.unobserve(t)}))},d=e=>({position:e,top:0,right:0,bottom:0,left:0,width:"100%",height:"100%",display:"block"}),u={name:"Lunchbox",props:{background:String,cameraArgs:Array,cameraLook:Array,cameraLookAt:Array,cameraPosition:Array,dpr:Number,ortho:Boolean,orthographic:Boolean,r3f:Boolean,rendererArguments:Object,rendererProperties:Object,sizePolicy:String,shadow:[Boolean,Object],transparent:Boolean,zoom:Number,updateSource:Object},setup(e,n){const o=t.ref(),s=t.ref(!0),i=t.ref(e.dpr??-1),u=t.ref();let l,p,m;e.r3f&&a?.ColorManagement&&(a.ColorManagement.legacyMode=!1),t.onMounted((()=>{if(!o.value)throw new Error("missing canvas");if(l=T(["WebGLRenderer"]),l)s.value=!1,z.value=!0;else{const t={alpha:e.transparent,antialias:!0,canvas:o.value.domElement,powerPreference:e.r3f?"high-performance":"default",...e.rendererArguments??{}};I.value=q({type:"WebGLRenderer",uuid:U,props:{args:[t]}}),z.value=!0;const n=I;e.r3f&&n.value.instance&&(n.value.instance.outputEncoding=a.sRGBEncoding,n.value.instance.toneMapping=a.ACESFilmicToneMapping);const s={shadow:e.shadow};n.value.instance&&s?.shadow&&(n.value.instance.shadowMap.enabled=!0,"object"==typeof s.shadow&&(n.value.instance.shadowMap.type=s.shadow.type)),e.rendererProperties&&Object.keys(e.rendererProperties).forEach((t=>{r.set(n.value,t,e.rendererProperties[t])})),l=n.value}if(p=T(["PerspectiveCamera","OrthographicCamera"]),p?D.value=!0:(e.ortho||e.orthographic?$.value=q({props:{args:e.cameraArgs??[]},type:"OrthographicCamera",uuid:S}):$.value=q({props:{args:e.cameraArgs??[e.r3f?75:45,.5625,1,1e3]},type:"PerspectiveCamera",uuid:S}),D.value=!0,p=$.value),!p.instance)throw new Error("Error creating camera.");if(p&&e.cameraPosition&&p.instance.position.set(...e.cameraPosition),p&&(e.cameraLookAt||e.cameraLook)){const t=e.cameraLookAt||e.cameraLook;p.instance.lookAt(...t)}if(p&&void 0!==e.zoom&&(p.instance.zoom=e.zoom),m=K.value,m&&m.instance&&e.background&&(m.instance.background=new a.Color(e.background)),-1===i.value&&(i.value=window.devicePixelRatio),!l?.instance)throw new Error("missing renderer");l.instance.setPixelRatio(i.value),ge.dpr.value=i.value,c(u,l.instance.domElement,t.onBeforeUnmount,e.sizePolicy);const n=t.getCurrentInstance().appContext.app;for(let e of re)e({app:n,camera:p.instance,renderer:l.instance,scene:m.instance});de({app:n,camera:p.instance,renderer:l.instance,scene:m.instance,updateSource:e.updateSource})})),t.onBeforeUnmount((()=>{ue(),le()}));const h="container"===e.sizePolicy?"static":"absolute",f="container"===e.sizePolicy?"static":"fixed";return()=>[n.slots.default?.()??null,t.h("div",{style:d(h),ref:u},[s.value?t.h("canvas",{style:d(f),class:"lunchbox-canvas",ref:o}):null])]}},l={},p=["canvas","div","LunchboxWrapper"],m={...["mesh","instancedMesh","scene","sprite","object3D","instancedBufferGeometry","bufferGeometry","boxBufferGeometry","circleBufferGeometry","coneBufferGeometry","cylinderBufferGeometry","dodecahedronBufferGeometry","extrudeBufferGeometry","icosahedronBufferGeometry","latheBufferGeometry","octahedronBufferGeometry","parametricBufferGeometry","planeBufferGeometry","polyhedronBufferGeometry","ringBufferGeometry","shapeBufferGeometry","sphereBufferGeometry","tetrahedronBufferGeometry","textBufferGeometry","torusBufferGeometry","torusKnotBufferGeometry","tubeBufferGeometry","wireframeGeometry","parametricGeometry","tetrahedronGeometry","octahedronGeometry","icosahedronGeometry","dodecahedronGeometry","polyhedronGeometry","tubeGeometry","torusKnotGeometry","torusGeometry","sphereGeometry","ringGeometry","planeGeometry","latheGeometry","shapeGeometry","extrudeGeometry","edgesGeometry","coneGeometry","cylinderGeometry","circleGeometry","boxGeometry","material","shadowMaterial","spriteMaterial","rawShaderMaterial","shaderMaterial","pointsMaterial","meshPhysicalMaterial","meshStandardMaterial","meshPhongMaterial","meshToonMaterial","meshNormalMaterial","meshLambertMaterial","meshDepthMaterial","meshDistanceMaterial","meshBasicMaterial","meshMatcapMaterial","lineDashedMaterial","lineBasicMaterial","light","spotLightShadow","spotLight","pointLight","rectAreaLight","hemisphereLight","directionalLightShadow","directionalLight","ambientLight","lightShadow","ambientLightProbe","hemisphereLightProbe","lightProbe","texture","videoTexture","dataTexture","dataTexture3D","compressedTexture","cubeTexture","canvasTexture","depthTexture","textureLoader","group","catmullRomCurve3","points","cameraHelper","camera","perspectiveCamera","orthographicCamera","cubeCamera","arrayCamera","webGLRenderer"].map((e=>t.defineComponent({inheritAttrs:!1,name:e,setup:(n,r)=>()=>t.h(e,r.attrs,r.slots?.default?.()||[])}))).reduce(((e,t)=>(e[t.name]=t,e)),{}),Lunchbox:u};const h=e=>e?.$el&&e?.$el?.hasOwnProperty?.("instance"),f=e=>{if("domMeta"===e?.metaType)return!0;const t="string"==typeof e?e:e?.type;return p.includes(t??"")},y=e=>"standardMeta"===e?.metaType,v=e=>e.isLunchboxRootNode,g=[],b=t.ref(!1);function x({node:e,key:n,value:r}){var o;if(e.eventListeners[n]||(e.eventListeners[n]=[]),e.eventListenerRemoveFunctions[n]||(e.eventListenerRemoveFunctions[n]=[]),e.eventListeners[n].push(r),w.includes(n)&&(_.value,e.instance&&!g.includes(e)&&(o=e,g.push(o),e.eventListenerRemoveFunctions[n].push((()=>(e=>{const t=g.indexOf(e);-1!==t&&g.splice(t,1)})(e))))),"onClick"===n||"onPointerDown"===n||"onPointerUp"===n){const r=t.watch((()=>b.value),(t=>{const r=M.map((e=>e.element)).findIndex((t=>t.instance&&t.instance.uuid===e.instance?.uuid));-1!==r&&((!t||"onClick"!==n&&"onPointerDown"!==n)&&(t||"onPointerUp"!==n)||e.eventListeners[n].forEach((e=>{e({intersection:M[r].intersection})})))}));e.eventListenerRemoveFunctions[n].push(r)}return e}const w=["onClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove"];let A,R,C;const E=t.ref({x:1/0,y:1/0});let L,P=!1;let M=[];const B=()=>{const e=_.value?.instance,t=$.value?.instance;if(!e||!t)return;e.setFromCamera(ge.mousePos.value,t);const n=e.intersectObjects(g.map((e=>e.instance)));let r=[],o=[],a=[];r=M.map((e=>e.intersection)),n?.forEach((e=>{if(-1===M.findIndex((t=>t.intersection.object===e.object))){const t=g.find((t=>t.instance?.uuid===e.object.uuid));t&&o.push({element:t,intersection:e})}else{const t=g.find((t=>t.instance?.uuid===e.object.uuid));t&&a.push({element:t,intersection:e})}const t=r.findIndex((t=>t.object.uuid===e.object.uuid));-1!==t&&r.splice(t,1)}));const s=r.map((e=>({element:g.find((t=>t.instance?.uuid===e.object.uuid)),intersection:e})));o.forEach((({element:e,intersection:t})=>{N({element:e,eventKeys:["onPointerEnter"],intersection:t})})),a.forEach((({element:e,intersection:t})=>{N({element:e,eventKeys:["onPointerOver","onPointerMove"],intersection:t})})),s.forEach((({element:e,intersection:t})=>{N({element:e,eventKeys:["onPointerLeave","onPointerOut"],intersection:t})})),M=[].concat(o,a)},N=({element:e,eventKeys:t,intersection:n})=>{e&&t.forEach((t=>{e.eventListeners[t]&&e.eventListeners[t].forEach((e=>{e({intersection:n})}))}))};function G(t={}){return e.lunchboxTree||(e.lunchboxTree=new Z.RendererRootNode(t)),e.lunchboxTree}function T(e){Array.isArray(e)||(e=[e]);for(let t of e)if(j[t])return j[t];for(let n of e){const e=O[n]||s.find((e=>e.type?.toLowerCase()===n.toLowerCase()));if(!(t=e,"RendererNode"!==t?.minidomType||!1!==e.props["is-default"]&&!1!=!e.props.isDefault))return null;if(e){const t=e;return O[n]=t,t}}var t;return null}e.lunchboxTree=void 0;const O=t.reactive({}),j=t.reactive({});function k(e,n,r={},o=null){Array.isArray(e)||(e=[e]);for(let t of e)O[t]||(O[t]=null),j[t]||(j[t]=null);return t.computed({get(){const t=T(e);if(t)return t;const a=G(),s=q({type:e[0],uuid:n,props:r});return a.addChild(s),O[e[0]]=s,o&&o(s),s},set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);j[n]=e}})}const S="FALLBACK_CAMERA",F=k(["PerspectiveCamera","OrthographicCamera"],S,{args:[45,.5625,1,1e3]}),D=t.ref(!1),$=t.computed({get:()=>D.value?F.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);j[n]=e}}),U="FALLBACK_RENDERER",W=k(["WebGLRenderer"],U,{}),z=t.ref(!1),I=t.computed({get:()=>z.value?W.value:null,set(e){const t=e.type??"",n=t[0].toUpperCase()+t.slice(1);j[n]=e}}),K=k("Scene","FALLBACK_SCENE"),_=k("Raycaster","AUTO_RAYCASTER",{},(e=>(e=>{if(!e.instance)return;let n=null;n=t.watch((()=>I.value),(e=>{e?.instance&&(P||(A=t=>{const n=(e.instance.domElement.width??1)/ge.dpr.value,r=(e.instance.domElement.height??1)/ge.dpr.value;E.value.x=t.offsetX/n*2-1,E.value.y=-t.offsetY/r*2+1},R=()=>b.value=!0,C=()=>b.value=!1,e.instance.domElement.addEventListener("mousemove",A),e.instance.domElement.addEventListener("mousedown",R),e.instance.domElement.addEventListener("mouseup",C),t.watch((()=>[b.value,E.value.x,E.value.y]),(()=>{L&&cancelAnimationFrame(L),L=requestAnimationFrame((()=>{B()}))})),P=!0),n&&n())}),{immediate:!0})})(e)));function q(e={},t={}){const n={attached:e.attached??[],attachedArray:e.attachedArray??{},instance:e.instance??null},r=new Z.RendererStandardNode({...e,...n,metaType:"standardMeta"});return!r.type||v(r)||r.instance||(r.instance=function(e){if(!e.type)return null;const t=e.type[0].toUpperCase()+e.type.slice(1),n=l[e.type]||a[t];if(!n)throw`${t} is not part of the THREE namespace! Did you forget to extend? import {extend} from 'lunchbox'; extend({app, YourComponent, ...})`;const r=(e.props.args??[]).map((t=>function({node:e,prop:t}){const n="string"==typeof t&&t.startsWith("$attachedArray"),r=function({node:e,prop:t}){if("string"==typeof t&&t.startsWith("$attachedArray"))return e.attachedArray[t.replace("$attachedArray.","")];if("string"==typeof t&&t.startsWith("$attached"))return e.attached[t.replace("$attached.","")];return t}({node:e,prop:t});return Array.isArray(r)&&n?r:[r]}({node:e,prop:t})));let o=[];r.forEach((e=>{o=o.concat(e)}));return new n(...o)}({...r,props:{...r.props,...t}})),"scene"===r.type?.toLowerCase()?K.value=r:r.type?.toLowerCase().endsWith("camera")&&($.value=r),r}const V=e=>t.defineComponent({inheritAttrs:!1,name:e,render(){return t.h(e,this.$attrs,this.$slots?.default?.()||[])}});var H,Y=new Uint8Array(16);function X(){if(!H&&!(H="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return H(Y)}var J=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;function Q(e){return"string"==typeof e&&J.test(e)}for(var Z,ee=[],te=0;te<256;++te)ee.push((te+256).toString(16).substr(1));function ne(e,t,n){var r=(e=e||{}).random||(e.rng||X)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(ee[e[t+0]]+ee[e[t+1]]+ee[e[t+2]]+ee[e[t+3]]+"-"+ee[e[t+4]]+ee[e[t+5]]+"-"+ee[e[t+6]]+ee[e[t+7]]+"-"+ee[e[t+8]]+ee[e[t+9]]+"-"+ee[e[t+10]]+ee[e[t+11]]+ee[e[t+12]]+ee[e[t+13]]+ee[e[t+14]]+ee[e[t+15]]).toLowerCase();if(!Q(n))throw TypeError("Stringified UUID is invalid");return n}(r)}!function(e){e.BaseNode=class{constructor(e={},t){this.parentNode=e?.parentNode??t??null,this.minidomType="MinidomBaseNode",this.uuid=e?.uuid??ne(),s.push(this)}uuid;parentNode;get nextSibling(){if(!this.parentNode)return null;const e=this.parentNode.children.findIndex((e=>e.uuid===this.uuid));return-1!==e&&e<this.parentNode.children.length-1?this.parentNode.children[e+1]:null}insertBefore(e,t){e.removeAsChildFromAnyParents(),e.parentNode=this;const n=this.children.findIndex((e=>e.uuid===t?.uuid));-1!==n?this.children.splice(n,0,e):this.children.push(e)}removeChild(e){const t=this.children.findIndex((t=>t?.uuid===e?.uuid));-1!==t&&this.children.splice(t,1)}children=[];addChild(e){return e&&(e.removeAsChildFromAnyParents(),e.parentNode=this,this.insertBefore(e,null)),this}getPath(){const e=[];let t=this;for(;t;)e.unshift(t),t=t.parentNode;return e}drop(){this.parentNode=null,this.removeAsChildFromAnyParents()}walk(e){const t=[this,...this.children],n=[];let r=!0;for(;t.length&&r;){const o=t.shift();if(o){if(n.includes(o))continue;n.push(o),t.push(...o.children.filter((e=>!n.includes(e)))),r=e(o)}else r=!1}}minidomType;removeAsChildFromAnyParents(){s.forEach((e=>e.removeChild(this)))}};class t extends e.BaseNode{constructor(e={},t){super(e,t),this.minidomType="RendererNode",this.eventListeners={},this.eventListenerRemoveFunctions={},this.name=e.name??"",this.metaType=e.metaType??"standardMeta",this.props=e.props??[],this.type=e.type??""}eventListeners;eventListenerRemoveFunctions;name;metaType;props;type;drop(){super.drop(),Object.keys(this.eventListenerRemoveFunctions).forEach((e=>{this.eventListenerRemoveFunctions[e].forEach((e=>e()))}))}}e.RendererBaseNode=t;class n extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement;isLunchboxRootNode=!0}e.RendererRootNode=n;class r extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererCommentNode=r;class o extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.domElement=e.domElement??document.createElement("div")}domElement}e.RendererDomNode=o;class a extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.text=e.text??""}text}e.RendererTextNode=a;class i extends e.RendererBaseNode{constructor(e={},t){super(e,t),this.attached=e.attached??[],this.attachedArray=e.attachedArray??{},this.instance=e.instance??null}attached;attachedArray;instance}e.RendererStandardNode=i}(Z||(Z={}));(new Z.RendererRootNode).minidomType="RootNode";const re=[];let oe,ae;const se=[],ie=[],ce=e=>{ue(),oe=requestAnimationFrame((()=>de({app:e.app,renderer:I.value?.instance,scene:K.value.instance,camera:$.value?.instance,updateSource:e.updateSource})))},de=e=>{e.updateSource?ae||(ae=t.watch(e.updateSource,(()=>{ce(e)}),{deep:!0})):ce(e);const{app:n,renderer:r,scene:o,camera:a}=e;se.forEach((t=>{t&&t(e)})),r&&o&&a&&(n.customRender?n.customRender(e):r.render(t.toRaw(o),t.toRaw(a))),ie.forEach((t=>{t&&t(e)}))},ue=()=>{oe&&cancelAnimationFrame(oe)},le=()=>{ae&&ae()};const pe={x:"position.x",y:"position.y",z:"position.z"},me=["","parameters"],he=["args","attach","attachArray","is.default","isDefault","key","onAdded","ref","src"],fe=["geometry","material"];function ye(e,t,n,r){const o=r??e.instance,a=t.instance;e.props.attach===n&&(t.attached={[n]:o,...t.attached||{}},a[n]=r??e.instance),e.props.attachArray===n&&(t.attachedArray[e.props.attachArray]||(t.attachedArray[e.props.attachArray]=[]),t.attachedArray[e.props.attachArray].push(o),a[n]=[a[n]])}const ve={createElement:(e,t,n,r)=>{const o={type:e};r&&(o.props=r);if(f(e)){const e=function(e={}){const t={domElement:document.createElement(e.type??"")};return new Z.RendererDomNode({...t,...e,metaType:"domMeta"})}(o);return e}const a=q(o);return fe.forEach((t=>{e.toLowerCase().endsWith(t)&&(a.props.attach=t)})),a},createText:e=>function(e={}){const t={text:e.text??""};return new Z.RendererTextNode({...e,...t,metaType:"textMeta"})}({text:e}),createComment:e=>function(e={}){const t={text:e.text??""};return new Z.RendererCommentNode({...t,...e,metaType:"commentMeta"})}({text:e}),insert:(e,t,n)=>{let r=t??G();if(r.insertBefore(e,n),"commentMeta"!==e.metaType&&"textMeta"!==e.metaType&&(f(e)&&(f(t)||v(t))&&t.domElement.appendChild(e.domElement),y(e))){let n=r.metaType;if("textMeta"===n||"commentMeta"===n){const e=r.getPath();for(let t=e.length-1;t>=0;t--)if("textMeta"!==e[t].metaType&&"commentMeta"!==e[t].metaType){r=e[t];break}}if("standardMeta"===e.metaType&&"scene"!==e.type&&v(r)){const t=K.value;t.instance&&e&&t.addChild(e),e.instance&&e.instance.isObject3D&&t.instance&&t!==e&&t.instance.add(e.instance)}else y(e)&&e.instance?.isObject3D&&y(r)&&r.instance?.isObject3D&&r.instance?.add?.(e.instance);if(e?.props?.attach&&y(t)&&t?.instance){e.type?.toLowerCase().endsWith("loader")&&e.props.src&&(e.props.attach||e.props.attachArray)?function(e,t){const n=e.instance;if(t.attached=t.attached||{},t.attachedArray=t.attachedArray||{},!e.props.attach)return;if("textureloader"===e.type?.toLowerCase()){const r=n.load(e.props.src);ye(e,t,e.props.attach,r)}else n.load(e.props.src,(n=>{ye(e,t,e.props.attach,n)}),null,(e=>{throw new Error(e)}))}(e,t):ye(e,t,e.props.attach)}e.props?.onAdded&&e.props.onAdded({instance:e.instance})}},nextSibling(e){const t=e.nextSibling;return t||null},parentNode(e){const t=e.parentNode;return t||null},patchProp(e,t,n,o){f(e)?"style"===t?Object.keys(o).forEach((t=>{e.domElement.style[t]=o[t]})):e.domElement.setAttribute(t,o):v(e)||t.startsWith("$")||function({node:e,key:t,value:n}){if((e=>["onClick","onContextMenu","onDoubleClick","onPointerUp","onPointerDown","onPointerOver","onPointerOut","onPointerEnter","onPointerLeave","onPointerMove","onWheel"].includes(e))(t))return x({node:e,key:t,value:n});const o=t.replace(/-/g,"."),a=pe[o]||o;if(he.includes(t)||he.includes(a))return e;if(!y(e))return e;if("string"==typeof n&&n.startsWith("$attached")){const t=n.replace("$attached.","");n=r.get(e.attached,t,null)}const s=e.instance;if(!s)return e;let i;for(let e=0;e<me.length&&!i;e++){const t=[me[e],a].filter(Boolean).join(".");i=i=r.get(s,t)}if(i&&r.isNumber(n)&&i.setScalar)i.setScalar(n);else if(i&&i.set){const e=Array.isArray(n)?n:[n];s[a].set(...e)}else if("function"==typeof i)if("onbeforerender"===a.toLowerCase()||"onafterrender"===a.toLowerCase())s[a]=n;else{if(!Array.isArray(n))throw new Error('Arguments on a declarative method must be wrapped in an array.\nWorks:\n<example :methodCall="[256]" />\nDoesn\'t work:\n<example :methodCall="256" />');i.bind(e.instance)(...n)}else void 0!==r.get(s,a,void 0)?r.set(s,a,""===n||n):console.log(`No property ${a} found on ${s}`);const c=s?.texture?.type||s?.type;if("string"==typeof c){const e=c.toLowerCase();switch(!0){case e.includes("material"):s.needsUpdate=!0;break;case e.includes("camera")&&s.updateProjectionMatrix:s.updateProjectionMatrix()}}}({node:e,key:t,value:o})},remove:e=>{if(!e)return;const t=Object.keys(j),n=[];e.walk((e=>(n.push(e),!0))),n.forEach((e=>{const n=t.find((t=>j[t]?.uuid===e.uuid));if(n&&(j[n]=null),y(e)){e.instance?.removeFromParent?.();const t="scene"!==e.type&&e.instance?.dispose;t&&t.bind(e.instance)(),e.instance=null}e.drop();const r=s.findIndex((t=>t.uuid===e.uuid));-1!==r&&s.splice(r,1)}))},setElementText(){},setText(){}},ge={dpr:t.ref(1),inputActive:b,mousePos:E},be=t.computed((()=>$.value?.instance??null));const xe=t.computed((()=>I.value?.instance??null));const we=t.computed((()=>K.value.instance));let Ae=null,Re=null;e.camera=be,e.clearCustomRender=()=>{Ae?Ae.clearCustomRender():Re=null},e.createApp=e=>{Ae=t.createRenderer(ve).createApp(e),Object.keys(m).forEach((e=>{Ae?.component(e,m[e])}));const{mount:n}=Ae;return Ae.mount=(e,...t)=>{const r=G({domElement:"string"==typeof e?document.querySelector(e):e,isLunchboxRootNode:!0,name:"root",metaType:"rootMeta",type:"root",uuid:"LUNCHBOX_ROOT"});Ae.rootNode=r;return n(r,...t)},Ae.extend=e=>((({app:e,...t})=>{Object.keys(t).forEach((n=>{e.component(n,V(n)),l[n]=t[n]}))})({app:Ae,...e}),Ae),Ae.setCustomRender=e=>{Ae.customRender=e},Re&&(Ae.setCustomRender(Re),Re=null),Ae.clearCustomRender=()=>{Ae.customRender=null},Ae},e.find=function(e){return e=t.isRef(e)?e.value:e,y(e)?e?.instance:h(e)?e?.$el?.instance:t.isVNode(e)?e.el?.instance:null},e.globals=ge,e.offAfterRender=e=>{if(isFinite(e))ie.splice(e,1);else{const t=ie.findIndex((t=>t==e));ie.splice(t,1)}},e.offBeforeRender=e=>{if(isFinite(e))se.splice(e,1);else{const t=se.findIndex((t=>t==e));se.splice(t,1)}},e.onAfterRender=(e,t=1/0)=>{t===1/0?ie.push(e):ie.splice(t,0,e)},e.onBeforeRender=(e,t=1/0)=>{t===1/0?se.push(e):se.splice(t,0,e)},e.onStart=(e,t=1/0)=>{t===1/0?re.push(e):re.splice(t,0,e)},e.renderer=xe,e.scene=we,e.setCustomRender=e=>{Ae?Ae.setCustomRender(e):Re=e},e.useCamera=function(e){return t.watch(be,(t=>{t&&e(t)}),{immediate:!0})},e.useRenderer=function(e){return t.watch(xe,(t=>{t&&e(t)}),{immediate:!0})},e.useScene=function(e){return t.watch(we,(t=>{t&&e(t)}),{immediate:!0})},Object.defineProperty(e,"__esModule",{value:!0})}));
@@ -1,4 +1,4 @@
1
- import { toRaw, ref, onMounted, getCurrentInstance, onBeforeUnmount, h, defineComponent, isRef, isVNode, watch, reactive, computed, createRenderer } from 'vue';
1
+ import { toRaw, ref, onMounted, onBeforeUnmount, getCurrentInstance, h, defineComponent, isRef, isVNode, watch, reactive, computed, createRenderer } from 'vue';
2
2
  import * as THREE from 'three';
3
3
  import { set, get, isNumber } from 'lodash';
4
4
 
@@ -42,16 +42,30 @@ const resizeCanvas = (width, height) => {
42
42
  }
43
43
  };
44
44
 
45
- const prepCanvas = (container, canvasElement, onBeforeUnmount) => {
45
+ const getInnerDimensions = (node) => {
46
+ const computedStyle = getComputedStyle(node);
47
+ const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
48
+ const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
49
+ return { width, height };
50
+ };
51
+ const prepCanvas = (container, canvasElement, onBeforeUnmount, sizePolicy) => {
46
52
  const containerElement = container.value?.domElement;
47
53
  if (!containerElement)
48
54
  throw new Error('missing container');
49
55
  // save...
50
56
  // ...and size element
51
- resizeCanvas();
57
+ const resizeCanvasByPolicy = () => {
58
+ if (sizePolicy === "container") {
59
+ const dims = getInnerDimensions(containerElement);
60
+ resizeCanvas(dims.width, dims.height);
61
+ }
62
+ else
63
+ resizeCanvas();
64
+ };
65
+ resizeCanvasByPolicy();
52
66
  // attach listeners
53
67
  const observer = new ResizeObserver(([canvas]) => {
54
- resizeCanvas();
68
+ resizeCanvasByPolicy();
55
69
  });
56
70
  // window.addEventListener('resize', resizeCanvas)
57
71
  if (containerElement) {
@@ -77,6 +91,7 @@ const fillStyle = (position) => {
77
91
  left: 0,
78
92
  width: '100%',
79
93
  height: '100%',
94
+ display: 'block',
80
95
  };
81
96
  };
82
97
  const LunchboxWrapper = {
@@ -94,9 +109,11 @@ const LunchboxWrapper = {
94
109
  r3f: Boolean,
95
110
  rendererArguments: Object,
96
111
  rendererProperties: Object,
112
+ sizePolicy: String,
97
113
  shadow: [Boolean, Object],
98
114
  transparent: Boolean,
99
115
  zoom: Number,
116
+ updateSource: Object,
100
117
  },
101
118
  setup(props, context) {
102
119
  const canvas = ref();
@@ -251,7 +268,7 @@ const LunchboxWrapper = {
251
268
  renderer.instance.setPixelRatio(dpr.value);
252
269
  globals.dpr.value = dpr.value;
253
270
  // prep canvas (sizing, observe, unmount, etc)
254
- prepCanvas(container, renderer.instance.domElement, onBeforeUnmount);
271
+ prepCanvas(container, renderer.instance.domElement, onBeforeUnmount, props.sizePolicy);
255
272
  }
256
273
  else {
257
274
  throw new Error('missing renderer');
@@ -276,24 +293,28 @@ const LunchboxWrapper = {
276
293
  camera: camera.instance,
277
294
  renderer: renderer.instance,
278
295
  scene: scene.instance,
296
+ updateSource: props.updateSource,
279
297
  });
280
298
  });
281
299
  // UNMOUNT
282
300
  // ====================
283
301
  onBeforeUnmount(() => {
284
302
  cancelUpdate();
303
+ cancelUpdateSource();
285
304
  });
286
305
  // RENDER FUNCTION
287
306
  // ====================
307
+ const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
308
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
288
309
  return () => [
289
310
  context.slots.default?.() ?? null,
290
311
  h('div', {
291
- style: fillStyle('absolute'),
312
+ style: fillStyle(containerFillStyle),
292
313
  ref: container,
293
314
  }, [
294
315
  useFallbackRenderer.value
295
316
  ? h('canvas', {
296
- style: fillStyle('fixed'),
317
+ style: fillStyle(canvasFillStyle),
297
318
  class: 'lunchbox-canvas',
298
319
  ref: canvas,
299
320
  })
@@ -627,6 +648,7 @@ let mouseDownListener;
627
648
  let mouseUpListener;
628
649
  const mousePos = ref({ x: Infinity, y: Infinity });
629
650
  let autoRaycasterEventsInitialized = false;
651
+ let frameID$1;
630
652
  const setupAutoRaycaster = (node) => {
631
653
  const instance = node.instance;
632
654
  if (!instance)
@@ -659,8 +681,14 @@ const setupAutoRaycaster = (node) => {
659
681
  renderer.instance.domElement.addEventListener('mousedown', mouseDownListener);
660
682
  renderer.instance.domElement.addEventListener('mouseup', mouseUpListener);
661
683
  // TODO: add touch events
662
- // add to update loop
663
- onBeforeRender(autoRaycasterBeforeRender);
684
+ // process mouse events asynchronously, whenever the mouse state changes
685
+ watch(() => [inputActive.value, mousePos.value.x, mousePos.value.y], () => {
686
+ if (frameID$1)
687
+ cancelAnimationFrame(frameID$1);
688
+ frameID$1 = requestAnimationFrame(() => {
689
+ autoRaycasterBeforeRender();
690
+ });
691
+ });
664
692
  // mark complete
665
693
  autoRaycasterEventsInitialized = true;
666
694
  // cancel setup watcher
@@ -989,7 +1017,7 @@ const extend = ({ app, ...targets }) => {
989
1017
  };
990
1018
 
991
1019
  /** Process props into either themselves or the $attached value */
992
- function processProp({ node, prop }) {
1020
+ function processProp({ node, prop, }) {
993
1021
  // return $attachedArray value if needed
994
1022
  if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
995
1023
  return node.attachedArray[prop.replace('$attachedArray.', '')];
@@ -1001,10 +1029,12 @@ function processProp({ node, prop }) {
1001
1029
  // otherwise, return plain value
1002
1030
  return prop;
1003
1031
  }
1004
- function processPropAsArray({ node, prop }) {
1032
+ function processPropAsArray({ node, prop, }) {
1005
1033
  const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray');
1006
1034
  const output = processProp({ node, prop });
1007
- return Array.isArray(output) && isAttachedArray ? output : [output];
1035
+ return Array.isArray(output) && isAttachedArray
1036
+ ? output
1037
+ : [output];
1008
1038
  }
1009
1039
 
1010
1040
  function instantiateThreeObject(node) {
@@ -1303,16 +1333,34 @@ const onStart = (cb, index = Infinity) => {
1303
1333
  };
1304
1334
 
1305
1335
  let frameID;
1336
+ let watchStopHandle;
1306
1337
  const beforeRender = [];
1307
1338
  const afterRender = [];
1308
- const update = (opts) => {
1309
- // request next frame
1339
+ const requestUpdate = (opts) => {
1340
+ cancelUpdate();
1310
1341
  frameID = requestAnimationFrame(() => update({
1311
1342
  app: opts.app,
1312
1343
  renderer: ensureRenderer.value?.instance,
1313
1344
  scene: ensuredScene.value.instance,
1314
1345
  camera: ensuredCamera.value?.instance,
1346
+ updateSource: opts.updateSource,
1315
1347
  }));
1348
+ };
1349
+ const update = (opts) => {
1350
+ if (opts.updateSource) {
1351
+ if (!watchStopHandle) {
1352
+ // request next frame only when state changes
1353
+ watchStopHandle = watch(opts.updateSource, () => {
1354
+ requestUpdate(opts);
1355
+ }, {
1356
+ deep: true,
1357
+ });
1358
+ }
1359
+ }
1360
+ else {
1361
+ // request next frame on a continuous loop
1362
+ requestUpdate(opts);
1363
+ }
1316
1364
  // prep options
1317
1365
  const { app, renderer, scene, camera } = opts;
1318
1366
  // BEFORE RENDER
@@ -1375,6 +1423,10 @@ const cancelUpdate = () => {
1375
1423
  if (frameID)
1376
1424
  cancelAnimationFrame(frameID);
1377
1425
  };
1426
+ const cancelUpdateSource = () => {
1427
+ if (watchStopHandle)
1428
+ watchStopHandle();
1429
+ };
1378
1430
 
1379
1431
  /** Update a single prop on a given node. */
1380
1432
  function updateObjectProp({ node, key, value, }) {
@@ -1412,6 +1464,7 @@ function updateObjectProp({ node, key, value, }) {
1412
1464
  liveProperty = liveProperty = get(target, fullPath);
1413
1465
  }
1414
1466
  // change property
1467
+ // first, save as array in case we need to spread it
1415
1468
  if (liveProperty && isNumber(value) && liveProperty.setScalar) {
1416
1469
  // if value is a number and the property has a `setScalar` method, use that
1417
1470
  liveProperty.setScalar(value);
@@ -1422,8 +1475,18 @@ function updateObjectProp({ node, key, value, }) {
1422
1475
  target[finalKey].set(...nextValueAsArray);
1423
1476
  }
1424
1477
  else if (typeof liveProperty === 'function') {
1425
- // if property is a function, let's try calling it
1426
- liveProperty.bind(node.instance)(...value);
1478
+ // some function properties are set rather than called, so let's handle them
1479
+ if (finalKey.toLowerCase() === 'onbeforerender' ||
1480
+ finalKey.toLowerCase() === 'onafterrender') {
1481
+ target[finalKey] = value;
1482
+ }
1483
+ else {
1484
+ if (!Array.isArray(value)) {
1485
+ throw new Error('Arguments on a declarative method must be wrapped in an array.\nWorks:\n<example :methodCall="[256]" />\nDoesn\'t work:\n<example :methodCall="256" />');
1486
+ }
1487
+ // if property is a function, let's try calling it
1488
+ liveProperty.bind(node.instance)(...value);
1489
+ }
1427
1490
  // pass the result to the parent
1428
1491
  // const parent = node.parentNode
1429
1492
  // if (parent) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunchboxjs",
3
- "version": "0.1.4015",
3
+ "version": "0.1.4018",
4
4
  "scripts": {
5
5
  "dev": "vite -c utils/vite.config.ts",
6
6
  "build": "vue-tsc --noEmit && vite build -c utils/vite.config.ts",
@@ -5,11 +5,13 @@ import {
5
5
  onBeforeUnmount,
6
6
  onMounted,
7
7
  ref,
8
+ WatchSource,
8
9
  WritableComputedRef,
9
10
  } from 'vue'
10
11
  import {
11
12
  cameraReady,
12
13
  cancelUpdate,
14
+ cancelUpdateSource,
13
15
  createNode,
14
16
  ensuredCamera,
15
17
  ensureRenderer,
@@ -41,6 +43,7 @@ const fillStyle = (position: string) => {
41
43
  left: 0,
42
44
  width: '100%',
43
45
  height: '100%',
46
+ display: 'block',
44
47
  }
45
48
  }
46
49
 
@@ -59,9 +62,11 @@ export const LunchboxWrapper: ComponentOptions = {
59
62
  r3f: Boolean,
60
63
  rendererArguments: Object,
61
64
  rendererProperties: Object,
65
+ sizePolicy: String,
62
66
  shadow: [Boolean, Object],
63
67
  transparent: Boolean,
64
68
  zoom: Number,
69
+ updateSource: Object,
65
70
  },
66
71
  setup(props: Lunch.WrapperProps, context) {
67
72
  const canvas = ref<MiniDom.RendererDomNode>()
@@ -239,7 +244,8 @@ export const LunchboxWrapper: ComponentOptions = {
239
244
  prepCanvas(
240
245
  container,
241
246
  renderer.instance.domElement,
242
- onBeforeUnmount
247
+ onBeforeUnmount,
248
+ props.sizePolicy,
243
249
  )
244
250
  } else {
245
251
  throw new Error('missing renderer')
@@ -267,6 +273,7 @@ export const LunchboxWrapper: ComponentOptions = {
267
273
  camera: camera.instance,
268
274
  renderer: renderer.instance,
269
275
  scene: scene.instance,
276
+ updateSource: props.updateSource,
270
277
  })
271
278
  })
272
279
 
@@ -274,22 +281,26 @@ export const LunchboxWrapper: ComponentOptions = {
274
281
  // ====================
275
282
  onBeforeUnmount(() => {
276
283
  cancelUpdate()
284
+ cancelUpdateSource()
277
285
  })
278
286
 
279
287
  // RENDER FUNCTION
280
288
  // ====================
289
+ const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute'
290
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed'
291
+
281
292
  return () => [
282
293
  context.slots.default?.() ?? null,
283
294
  h(
284
295
  'div',
285
296
  {
286
- style: fillStyle('absolute'),
297
+ style: fillStyle(containerFillStyle),
287
298
  ref: container,
288
299
  },
289
300
  [
290
301
  useFallbackRenderer.value
291
302
  ? h('canvas', {
292
- style: fillStyle('fixed'),
303
+ style: fillStyle(canvasFillStyle),
293
304
  class: 'lunchbox-canvas',
294
305
  ref: canvas,
295
306
  })
@@ -1,22 +1,39 @@
1
1
  import { MiniDom } from '../../core'
2
+ import { Lunch } from '../../types'
2
3
  import { Ref } from 'vue'
3
4
  import { resizeCanvas } from './resizeCanvas'
4
5
 
6
+ const getInnerDimensions = (node: Element) => {
7
+ const computedStyle = getComputedStyle(node)
8
+ const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight)
9
+ const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom)
10
+ return { width, height }
11
+ }
12
+
5
13
  export const prepCanvas = (
6
14
  container: Ref<MiniDom.RendererDomNode | undefined>,
7
15
  canvasElement: HTMLCanvasElement,
8
16
  onBeforeUnmount: Function,
17
+ sizePolicy?: Lunch.SizePolicy
9
18
  ) => {
10
19
  const containerElement = container.value?.domElement
11
20
  if (!containerElement) throw new Error('missing container')
12
21
 
13
22
  // save...
14
23
  // ...and size element
15
- resizeCanvas()
24
+ const resizeCanvasByPolicy = () => {
25
+ if (sizePolicy === "container") {
26
+ const dims = getInnerDimensions(containerElement);
27
+ resizeCanvas(dims.width, dims.height);
28
+ }
29
+ else
30
+ resizeCanvas()
31
+ };
32
+ resizeCanvasByPolicy();
16
33
 
17
34
  // attach listeners
18
35
  const observer = new ResizeObserver(([canvas]) => {
19
- resizeCanvas()
36
+ resizeCanvasByPolicy();
20
37
  })
21
38
  // window.addEventListener('resize', resizeCanvas)
22
39
  if (containerElement) {
@@ -29,4 +46,4 @@ export const prepCanvas = (
29
46
  observer.unobserve(canvasElement)
30
47
  }
31
48
  })
32
- }
49
+ }
@@ -1,3 +1,3 @@
1
1
  import { Lunch } from '..'
2
2
 
3
- export const catalogue: Lunch.Catalogue = {}
3
+ export const catalogue: Lunch.Catalogue = {}
@@ -1,10 +1,18 @@
1
1
  import { Lunch } from '../..'
2
2
 
3
3
  /** Process props into either themselves or the $attached value */
4
- export function processProp<T, U = THREE.Object3D>({ node, prop }: { node: Lunch.StandardMeta<U>, prop: any }) {
4
+ export function processProp<T, U = THREE.Object3D>({
5
+ node,
6
+ prop,
7
+ }: {
8
+ node: Lunch.StandardMeta<U>
9
+ prop: any
10
+ }) {
5
11
  // return $attachedArray value if needed
6
12
  if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
7
- return node.attachedArray[prop.replace('$attachedArray.', '')] as any as T
13
+ return node.attachedArray[
14
+ prop.replace('$attachedArray.', '')
15
+ ] as any as T
8
16
  }
9
17
 
10
18
  // return $attached value if needed
@@ -16,8 +24,17 @@ export function processProp<T, U = THREE.Object3D>({ node, prop }: { node: Lunch
16
24
  return prop as T
17
25
  }
18
26
 
19
- export function processPropAsArray<T, U = THREE.Object3D>({ node, prop }: { node: Lunch.StandardMeta<U>, prop: any }) {
20
- const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray')
27
+ export function processPropAsArray<T, U = THREE.Object3D>({
28
+ node,
29
+ prop,
30
+ }: {
31
+ node: Lunch.StandardMeta<U>
32
+ prop: any
33
+ }) {
34
+ const isAttachedArray =
35
+ typeof prop === 'string' && prop.startsWith('$attachedArray')
21
36
  const output = processProp<T, U>({ node, prop })
22
- return Array.isArray(output) && isAttachedArray ? output as Array<T> : [output]
23
- }
37
+ return Array.isArray(output) && isAttachedArray
38
+ ? (output as Array<T>)
39
+ : [output]
40
+ }
@@ -1,4 +1,4 @@
1
1
  import { ref } from 'vue'
2
2
 
3
3
  /** Mouse is down, touch is pressed, etc */
4
- export const inputActive = ref(false)
4
+ export const inputActive = ref(false)
@@ -17,6 +17,8 @@ let mouseUpListener: (event: MouseEvent) => void
17
17
  export const mousePos = ref({ x: Infinity, y: Infinity })
18
18
  let autoRaycasterEventsInitialized = false
19
19
 
20
+ let frameID: number
21
+
20
22
  export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
21
23
  const instance = node.instance
22
24
 
@@ -67,8 +69,16 @@ export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
67
69
 
68
70
  // TODO: add touch events
69
71
 
70
- // add to update loop
71
- onBeforeRender(autoRaycasterBeforeRender)
72
+ // process mouse events asynchronously, whenever the mouse state changes
73
+ watch(
74
+ () => [inputActive.value, mousePos.value.x, mousePos.value.y],
75
+ () => {
76
+ if (frameID) cancelAnimationFrame(frameID)
77
+ frameID = requestAnimationFrame(() => {
78
+ autoRaycasterBeforeRender()
79
+ })
80
+ }
81
+ )
72
82
 
73
83
  // mark complete
74
84
  autoRaycasterEventsInitialized = true
@@ -1,22 +1,44 @@
1
1
  import { ensureRenderer, ensuredScene, ensuredCamera } from '.'
2
2
  import { Lunch } from '..'
3
- import { toRaw } from 'vue'
3
+ import { toRaw, watch, WatchStopHandle } from 'vue'
4
4
 
5
5
  let frameID: number
6
+ let watchStopHandle: WatchStopHandle
6
7
 
7
8
  export const beforeRender = [] as Lunch.UpdateCallback[]
8
9
  export const afterRender = [] as Lunch.UpdateCallback[]
9
10
 
10
- export const update: Lunch.UpdateCallback = (opts) => {
11
- // request next frame
11
+ const requestUpdate = (opts: Lunch.UpdateCallbackProperties) => {
12
+ cancelUpdate()
12
13
  frameID = requestAnimationFrame(() =>
13
14
  update({
14
15
  app: opts.app,
15
16
  renderer: ensureRenderer.value?.instance,
16
17
  scene: ensuredScene.value.instance,
17
18
  camera: ensuredCamera.value?.instance,
19
+ updateSource: opts.updateSource,
18
20
  })
19
21
  )
22
+ }
23
+
24
+ export const update: Lunch.UpdateCallback = (opts) => {
25
+ if (opts.updateSource) {
26
+ if (!watchStopHandle) {
27
+ // request next frame only when state changes
28
+ watchStopHandle = watch(
29
+ opts.updateSource,
30
+ () => {
31
+ requestUpdate(opts)
32
+ },
33
+ {
34
+ deep: true,
35
+ }
36
+ )
37
+ }
38
+ } else {
39
+ // request next frame on a continuous loop
40
+ requestUpdate(opts)
41
+ }
20
42
 
21
43
  // prep options
22
44
  const { app, renderer, scene, camera } = opts
@@ -82,3 +104,7 @@ export const offAfterRender = (cb: Lunch.UpdateCallback | number) => {
82
104
  export const cancelUpdate = () => {
83
105
  if (frameID) cancelAnimationFrame(frameID)
84
106
  }
107
+
108
+ export const cancelUpdateSource = () => {
109
+ if (watchStopHandle) watchStopHandle()
110
+ }
@@ -67,6 +67,7 @@ export function updateObjectProp({
67
67
  }
68
68
 
69
69
  // change property
70
+ // first, save as array in case we need to spread it
70
71
  if (liveProperty && isNumber(value) && liveProperty.setScalar) {
71
72
  // if value is a number and the property has a `setScalar` method, use that
72
73
  liveProperty.setScalar(value)
@@ -75,8 +76,21 @@ export function updateObjectProp({
75
76
  const nextValueAsArray = Array.isArray(value) ? value : [value]
76
77
  ;(target as any)[finalKey].set(...nextValueAsArray)
77
78
  } else if (typeof liveProperty === 'function') {
78
- // if property is a function, let's try calling it
79
- liveProperty.bind(node.instance)(...value)
79
+ // some function properties are set rather than called, so let's handle them
80
+ if (
81
+ finalKey.toLowerCase() === 'onbeforerender' ||
82
+ finalKey.toLowerCase() === 'onafterrender'
83
+ ) {
84
+ ;(target as any)[finalKey] = value
85
+ } else {
86
+ if (!Array.isArray(value)) {
87
+ throw new Error(
88
+ 'Arguments on a declarative method must be wrapped in an array.\nWorks:\n<example :methodCall="[256]" />\nDoesn\'t work:\n<example :methodCall="256" />'
89
+ )
90
+ }
91
+ // if property is a function, let's try calling it
92
+ liveProperty.bind(node.instance)(...value)
93
+ }
80
94
 
81
95
  // pass the result to the parent
82
96
  // const parent = node.parentNode
package/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  type RootNode = import('./core/minidom').MiniDom.RendererRootNode
2
2
  type VNodeProps = import('vue').VNodeProps
3
3
  type VueApp<T> = import('vue').App<T>
4
+ type WatchSource = import('vue').WatchSource
4
5
  type RendererStandardNode<T = THREE.Object3D> =
5
6
  import('./core').MiniDom.RendererStandardNode<T>
6
7
 
@@ -127,6 +128,7 @@ export declare namespace Lunch {
127
128
  scene?: THREE.Scene | null
128
129
  renderer?: THREE.Renderer | null
129
130
  camera?: THREE.Camera | null
131
+ updateSource?: WatchSource | null
130
132
 
131
133
  // sceneNode: Node<THREE.Scene> | null
132
134
  // rendererNode: Node<THREE.Renderer> | null
@@ -136,6 +138,8 @@ export declare namespace Lunch {
136
138
  /** Universally unique identifier. */
137
139
  type Uuid = string
138
140
 
141
+ type SizePolicy = 'full' | 'container'
142
+
139
143
  interface WrapperProps {
140
144
  background?: string
141
145
  cameraArgs?: any[]
@@ -149,8 +153,10 @@ export declare namespace Lunch {
149
153
  // TODO: Why doesn't ConstructorParameters<THREE.WebGLRenderer> work here?
150
154
  rendererArguments?: object
151
155
  rendererProperties?: Partial<THREE.WebGLRenderer>
156
+ sizePolicy?: SizePolicy
152
157
  shadow?: ShadowSugar
153
158
  transparent?: boolean
154
159
  zoom?: number
160
+ updateSource?: WatchSource | null
155
161
  }
156
162
  }