lunchboxjs 0.2.1001-beta.0 → 0.2.1001-beta.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.
@@ -141,6 +141,7 @@
141
141
  const appRenderersKey = Symbol();
142
142
  const appSceneKey = Symbol();
143
143
  const appCameraKey = Symbol();
144
+ const lunchboxInteractables = Symbol();
144
145
  const startCallbackKey = Symbol();
145
146
 
146
147
  const ensuredCamera = () => vue.inject(appCameraKey); // ENSURE RENDERER
@@ -151,215 +152,11 @@
151
152
 
152
153
  const ensuredScene = () => vue.inject(appSceneKey);
153
154
 
154
- const interactables = [];
155
- const addInteractable = target => {
156
- interactables.push(target);
157
- };
158
- const removeInteractable = target => {
159
- const idx = interactables.indexOf(target);
160
-
161
- if (idx !== -1) {
162
- interactables.splice(idx, 1);
163
- }
164
- };
165
-
166
- /** Mouse is down, touch is pressed, etc */
167
-
168
- const inputActive = vue.ref(false);
169
-
170
- // let mouseDownListener: (event: MouseEvent) => void
171
- // let mouseUpListener: (event: MouseEvent) => void
172
-
173
- const mousePos = vue.ref({
174
- x: Infinity,
175
- y: Infinity
176
- }); // let autoRaycasterEventsInitialized = false
177
- // let frameID: number
178
- // export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
179
- // const instance = node.instance
180
- // if (!instance) return
181
- // // TODO: inject doesn't work here. replace this raycaster with a component so we can
182
- // // `inject` in `setup`?
183
- // const appLevelGlobals = { dpr: window.devicePixelRatio } //useGlobals()
184
- // // add mouse events once renderer is ready
185
- // let stopWatcher: WatchStopHandle | null = null
186
- // stopWatcher = watch(
187
- // () => ensureRenderer.value,
188
- // (renderer) => {
189
- // // make sure renderer exists
190
- // if (!renderer?.instance) return
191
- // // cancel early if autoraycaster exists
192
- // if (autoRaycasterEventsInitialized) {
193
- // if (stopWatcher) stopWatcher()
194
- // return
195
- // }
196
- // // create mouse events
197
- // mouseMoveListener = (evt) => {
198
- // const screenWidth =
199
- // (renderer.instance!.domElement.width ?? 1) /
200
- // appLevelGlobals.dpr
201
- // const screenHeight =
202
- // (renderer.instance!.domElement.height ?? 1) /
203
- // appLevelGlobals.dpr
204
- // mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
205
- // mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
206
- // }
207
- // mouseDownListener = () => (inputActive.value = true)
208
- // mouseUpListener = () => (inputActive.value = false)
209
- // // add mouse events
210
- // renderer.instance.domElement.addEventListener(
211
- // 'mousemove',
212
- // mouseMoveListener
213
- // )
214
- // renderer.instance.domElement.addEventListener(
215
- // 'mousedown',
216
- // mouseDownListener
217
- // )
218
- // renderer.instance.domElement.addEventListener(
219
- // 'mouseup',
220
- // mouseUpListener
221
- // )
222
- // // TODO: add touch events
223
- // // process mouse events asynchronously, whenever the mouse state changes
224
- // watch(
225
- // () => [inputActive.value, mousePos.value.x, mousePos.value.y],
226
- // () => {
227
- // if (frameID) cancelAnimationFrame(frameID)
228
- // frameID = requestAnimationFrame(() => {
229
- // autoRaycasterBeforeRender()
230
- // })
231
- // }
232
- // )
233
- // // mark complete
234
- // autoRaycasterEventsInitialized = true
235
- // // cancel setup watcher
236
- // if (stopWatcher) {
237
- // stopWatcher()
238
- // }
239
- // },
240
- // { immediate: true }
241
- // )
242
- // }
243
- // AUTO-RAYCASTER CALLBACK
244
- // ====================
245
-
246
- let currentIntersections = []; // const autoRaycasterBeforeRender = () => {
247
- // // setup
248
- // const raycaster = ensuredRaycaster.value?.instance
249
- // const camera = ensuredCamera.value?.instance
250
- // if (!raycaster || !camera) return
251
- // raycaster.setFromCamera(globals.mousePos.value, camera)
252
- // const intersections = raycaster.intersectObjects(
253
- // interactables.map((v) => v.instance as any as THREE.Object3D)
254
- // )
255
- // let enterValues: Array<Intersection<THREE.Object3D>> = [],
256
- // sameValues: Array<Intersection<THREE.Object3D>> = [],
257
- // leaveValues: Array<Intersection<THREE.Object3D>> = [],
258
- // entering: Array<{
259
- // element: Lunch.Node
260
- // intersection: Intersection<THREE.Object3D>
261
- // }> = [],
262
- // staying: Array<{
263
- // element: Lunch.Node
264
- // intersection: Intersection<THREE.Object3D>
265
- // }> = []
266
- // // intersection arrays
267
- // leaveValues = currentIntersections.map((v) => v.intersection)
268
- // // element arrays
269
- // intersections?.forEach((intersection) => {
270
- // const currentIdx = currentIntersections.findIndex(
271
- // (v) => v.intersection.object === intersection.object
272
- // )
273
- // if (currentIdx === -1) {
274
- // // new intersection
275
- // enterValues.push(intersection)
276
- // const found = interactables.find(
277
- // (v) => v.instance?.uuid === intersection.object.uuid
278
- // )
279
- // if (found) {
280
- // entering.push({ element: found, intersection })
281
- // }
282
- // } else {
283
- // // existing intersection
284
- // sameValues.push(intersection)
285
- // const found = interactables.find(
286
- // (v) => v.instance?.uuid === intersection.object.uuid
287
- // )
288
- // if (found) {
289
- // staying.push({ element: found, intersection })
290
- // }
291
- // }
292
- // // this is a current intersection, so it won't be in our `leave` array
293
- // const leaveIdx = leaveValues.findIndex(
294
- // (v) => v.object.uuid === intersection.object.uuid
295
- // )
296
- // if (leaveIdx !== -1) {
297
- // leaveValues.splice(leaveIdx, 1)
298
- // }
299
- // })
300
- // const leaving: Array<{
301
- // element: Lunch.Node
302
- // intersection: Intersection<THREE.Object3D>
303
- // }> = leaveValues.map((intersection) => {
304
- // return {
305
- // element: interactables.find(
306
- // (interactable) =>
307
- // interactable.instance?.uuid === intersection.object.uuid
308
- // ) as any as Lunch.Node,
309
- // intersection,
310
- // }
311
- // })
312
- // // new interactions
313
- // entering.forEach(({ element, intersection }) => {
314
- // fireEventsFromIntersections({
315
- // element,
316
- // eventKeys: ['onPointerEnter'],
317
- // intersection,
318
- // })
319
- // })
320
- // // unchanged interactions
321
- // staying.forEach(({ element, intersection }) => {
322
- // const eventKeys: Array<Lunch.EventKey> = [
323
- // 'onPointerOver',
324
- // 'onPointerMove',
325
- // ]
326
- // fireEventsFromIntersections({ element, eventKeys, intersection })
327
- // })
328
- // // exited interactions
329
- // leaving.forEach(({ element, intersection }) => {
330
- // const eventKeys: Array<Lunch.EventKey> = [
331
- // 'onPointerLeave',
332
- // 'onPointerOut',
333
- // ]
334
- // fireEventsFromIntersections({ element, eventKeys, intersection })
335
- // })
336
- // currentIntersections = ([] as any).concat(entering, staying)
337
- // }
338
- // utility function for firing multiple callbacks and multiple events on a Lunchbox.Element
339
- // const fireEventsFromIntersections = ({
340
- // element,
341
- // eventKeys,
342
- // intersection,
343
- // }: {
344
- // element: Lunch.Node
345
- // eventKeys: Array<Lunch.EventKey>
346
- // intersection: Intersection<THREE.Object3D>
347
- // }) => {
348
- // if (!element) return
349
- // eventKeys.forEach((eventKey) => {
350
- // if (element.eventListeners[eventKey]) {
351
- // element.eventListeners[eventKey].forEach((cb) => {
352
- // cb({ intersection })
353
- // })
354
- // }
355
- // })
356
- // }
357
-
358
155
  /** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
359
-
360
156
  function addEventListener({
361
157
  node,
362
158
  key,
159
+ interactables,
363
160
  value
364
161
  }) {
365
162
  // create new records for this key if needed
@@ -375,37 +172,18 @@
375
172
  node.eventListeners[key].push(value); // if we need it, let's get/create the main raycaster
376
173
 
377
174
  if (interactionsRequiringRaycaster.includes(key)) {
378
- // we're not using `v` here, we're just making sure the raycaster has been created
379
- // TODO: is this necessary?
380
- // const v = ensuredRaycaster.value
381
- if (node.instance && !interactables.includes(node)) {
382
- addInteractable(node);
383
- node.eventListenerRemoveFunctions[key].push(() => removeInteractable(node));
384
- }
385
- } // register click, pointerdown, pointerup
386
-
387
-
388
- if (key === 'onClick' || key === 'onPointerDown' || key === 'onPointerUp') {
389
- const stop = vue.watch(() => inputActive.value, isDown => {
390
- const idx = currentIntersections.map(v => v.element).findIndex(v => v.instance && v.instance.uuid === node.instance?.uuid);
391
-
392
- if (idx !== -1) {
393
- if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
394
- node.eventListeners[key].forEach(func => {
395
- func({
396
- intersection: currentIntersections[idx].intersection
397
- });
398
- });
399
- } else if (!isDown && key === 'onPointerUp') {
400
- node.eventListeners[key].forEach(func => {
401
- func({
402
- intersection: currentIntersections[idx].intersection
403
- });
404
- });
175
+ if (node.instance && !interactables.value.includes(node)) {
176
+ // add to interactables
177
+ interactables.value.push(node);
178
+ node.eventListenerRemoveFunctions[key].push(() => {
179
+ // remove from interactables
180
+ const idx = interactables.value.indexOf(node);
181
+
182
+ if (idx !== -1) {
183
+ interactables.value.splice(idx, 1);
405
184
  }
406
- }
407
- });
408
- node.eventListenerRemoveFunctions[key].push(stop);
185
+ });
186
+ }
409
187
  }
410
188
 
411
189
  return node;
@@ -499,6 +277,195 @@
499
277
 
500
278
  });
501
279
 
280
+ const LunchboxEventHandlers = vue.defineComponent({
281
+ name: 'LunchboxEventHandlers',
282
+
283
+ setup() {
284
+ const interactables = useLunchboxInteractables();
285
+ const camera = useCamera();
286
+ const renderer = useRenderer();
287
+ const globals = useGlobals();
288
+ const mousePos = vue.ref({
289
+ x: Infinity,
290
+ y: Infinity
291
+ });
292
+ const inputActive = vue.ref(false);
293
+ let currentIntersections = [];
294
+ const raycaster = new THREE__namespace.Raycaster(new THREE__namespace.Vector3(), new THREE__namespace.Vector3(0, 0, -1));
295
+
296
+ const fireEventsFromIntersections = ({
297
+ element,
298
+ eventKeys,
299
+ intersection
300
+ }) => {
301
+ if (!element) return;
302
+ eventKeys.forEach(eventKey => {
303
+ if (element.eventListeners[eventKey]) {
304
+ element.eventListeners[eventKey].forEach(cb => {
305
+ cb({
306
+ intersection
307
+ });
308
+ });
309
+ }
310
+ });
311
+ }; // add mouse listener to renderer DOM element when the element is ready
312
+
313
+
314
+ const stopWatch = vue.watch(renderer, v => {
315
+ if (!v?.domElement) return; // we have a DOM element, so let's add mouse listeners
316
+
317
+ const {
318
+ domElement
319
+ } = v;
320
+
321
+ const mouseMoveListener = evt => {
322
+ const screenWidth = (domElement.width ?? 1) / globals.dpr;
323
+ const screenHeight = (domElement.height ?? 1) / globals.dpr;
324
+ mousePos.value.x = evt.offsetX / screenWidth * 2 - 1;
325
+ mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1;
326
+ };
327
+
328
+ const mouseDownListener = () => inputActive.value = true;
329
+
330
+ const mouseUpListener = () => inputActive.value = false; // add mouse events
331
+
332
+
333
+ domElement.addEventListener('pointermove', mouseMoveListener);
334
+ domElement.addEventListener('pointerdown', mouseDownListener);
335
+ domElement.addEventListener('pointerup', mouseUpListener); // stop the watcher
336
+
337
+ stopWatch();
338
+ }, {
339
+ immediate: true
340
+ });
341
+
342
+ const update = () => {
343
+ const c = camera.value;
344
+ if (!c) return;
345
+ raycaster.setFromCamera(mousePos.value, c);
346
+ const intersections = raycaster.intersectObjects(interactables?.value.map(v => v.instance) ?? []);
347
+ let leaveValues = [],
348
+ entering = [],
349
+ staying = []; // intersection arrays
350
+
351
+ leaveValues = currentIntersections.map(v => v.intersection); // element arrays
352
+
353
+ intersections?.forEach(intersection => {
354
+ const currentIdx = currentIntersections.findIndex(v => v.intersection.object === intersection.object);
355
+
356
+ if (currentIdx === -1) {
357
+ const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
358
+
359
+ if (found) {
360
+ entering.push({
361
+ element: found,
362
+ intersection
363
+ });
364
+ }
365
+ } else {
366
+ const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
367
+
368
+ if (found) {
369
+ staying.push({
370
+ element: found,
371
+ intersection
372
+ });
373
+ }
374
+ } // this is a current intersection, so it won't be in our `leave` array
375
+
376
+
377
+ const leaveIdx = leaveValues.findIndex(v => v.object.uuid === intersection.object.uuid);
378
+
379
+ if (leaveIdx !== -1) {
380
+ leaveValues.splice(leaveIdx, 1);
381
+ }
382
+ });
383
+ const leaving = leaveValues.map(intersection => {
384
+ return {
385
+ element: interactables?.value.find(interactable => interactable.instance?.uuid === intersection.object.uuid),
386
+ intersection
387
+ };
388
+ }); // new interactions
389
+
390
+ entering.forEach(({
391
+ element,
392
+ intersection
393
+ }) => {
394
+ fireEventsFromIntersections({
395
+ element,
396
+ eventKeys: ['onPointerEnter'],
397
+ intersection
398
+ });
399
+ }); // unchanged interactions
400
+
401
+ staying.forEach(({
402
+ element,
403
+ intersection
404
+ }) => {
405
+ const eventKeys = ['onPointerOver', 'onPointerMove'];
406
+ fireEventsFromIntersections({
407
+ element,
408
+ eventKeys,
409
+ intersection
410
+ });
411
+ }); // exited interactions
412
+
413
+ leaving.forEach(({
414
+ element,
415
+ intersection
416
+ }) => {
417
+ const eventKeys = ['onPointerLeave', 'onPointerOut'];
418
+ fireEventsFromIntersections({
419
+ element,
420
+ eventKeys,
421
+ intersection
422
+ });
423
+ });
424
+ currentIntersections = [].concat(entering, staying);
425
+ }; // update function
426
+
427
+
428
+ onBeforeRender(update);
429
+
430
+ const teardown = () => offBeforeRender(update);
431
+
432
+ vue.onBeforeUnmount(teardown);
433
+ const clickEventKeys = ['onClick', 'onPointerDown', 'onPointerUp'];
434
+ vue.watch(inputActive, isDown => {
435
+ // meshes with multiple intersections receive multiple callbacks by default -
436
+ // let's make it so they only receive one callback of each type per frame.
437
+ // (ie usually when you click on a mesh, you expect only one click event to fire, even
438
+ // if there are technically multiple intersections with that mesh)
439
+ const uuidsInteractedWithThisFrame = [];
440
+ currentIntersections.forEach(v => {
441
+ clickEventKeys.forEach(key => {
442
+ const id = v.element.uuid + key;
443
+
444
+ if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
445
+ if (!uuidsInteractedWithThisFrame.includes(id)) {
446
+ v.element.eventListeners[key]?.forEach(cb => cb({
447
+ intersection: v.intersection
448
+ }));
449
+ uuidsInteractedWithThisFrame.push(id);
450
+ }
451
+ } else if (!isDown && key === 'onPointerUp') {
452
+ if (!uuidsInteractedWithThisFrame.includes(id)) {
453
+ v.element.eventListeners[key]?.forEach(cb => cb({
454
+ intersection: v.intersection
455
+ }));
456
+ uuidsInteractedWithThisFrame.push(id);
457
+ }
458
+ }
459
+ });
460
+ });
461
+ }); // return arbitrary object to ensure instantiation
462
+ // TODO: why can't we return a <raycaster/> here?
463
+
464
+ return () => vue.createVNode(vue.resolveComponent("object3D"), null, null);
465
+ }
466
+
467
+ });
468
+
502
469
  /** fixed & fill styling for container */
503
470
 
504
471
  const fillStyle = position => {
@@ -551,9 +518,10 @@
551
518
 
552
519
  if (props.r3f && THREE__namespace?.ColorManagement) {
553
520
  THREE__namespace.ColorManagement.legacyMode = false;
554
- } // MOUNT
555
- // ====================
521
+ }
556
522
 
523
+ const interactables = useLunchboxInteractables(); // MOUNT
524
+ // ====================
557
525
 
558
526
  vue.onMounted(async () => {
559
527
  // canvas needs to exist (or user needs to handle it on their own)
@@ -589,23 +557,17 @@
589
557
  updateGlobals?.({
590
558
  dpr
591
559
  });
592
- console.log(1);
593
560
 
594
561
  while (!renderer.value?.$el?.instance && // TODO: remove `as any`
595
562
  !renderer.value?.component?.ctx.$el?.instance) {
596
- console.log(2);
597
563
  await new Promise(r => requestAnimationFrame(r));
598
564
  }
599
565
 
600
- console.log(3);
601
-
602
566
  while (!scene.value?.$el?.instance && // TODO: remove `as any`
603
567
  !scene.value?.component?.ctx.$el?.instance) {
604
- console.log(4);
605
568
  await new Promise(r => requestAnimationFrame(r));
606
569
  }
607
570
 
608
- console.log(5);
609
571
  const normalizedRenderer = renderer.value?.$el?.instance ?? renderer.value?.component?.ctx.$el?.instance;
610
572
  normalizedRenderer.setPixelRatio(globals.dpr);
611
573
  const normalizedScene = scene.value?.$el?.instance ?? scene.value?.component?.ctx.$el?.instance;
@@ -711,7 +673,7 @@
711
673
  }, consolidatedCameraProperties), null) : vue.createVNode(vue.resolveComponent("perspectiveCamera"), vue.mergeProps({
712
674
  "ref": camera,
713
675
  "args": props.cameraArgs ?? [props.r3f ? 75 : 45, 0.5625, 1, 1000]
714
- }, consolidatedCameraProperties), null)]);
676
+ }, consolidatedCameraProperties), null), interactables?.value.length && vue.createVNode(LunchboxEventHandlers, null, null)]);
715
677
  }
716
678
 
717
679
  });
@@ -726,7 +688,7 @@
726
688
  'light', 'spotLightShadow', 'spotLight', 'pointLight', 'rectAreaLight', 'hemisphereLight', 'directionalLightShadow', 'directionalLight', 'ambientLight', 'lightShadow', 'ambientLightProbe', 'hemisphereLightProbe', 'lightProbe', // textures
727
689
  'texture', 'videoTexture', 'dataTexture', 'dataTexture3D', 'compressedTexture', 'cubeTexture', 'canvasTexture', 'depthTexture', // Texture loaders
728
690
  'textureLoader', // misc
729
- 'group', 'catmullRomCurve3', 'points', // helpers
691
+ 'group', 'catmullRomCurve3', 'points', 'raycaster', // helpers
730
692
  'cameraHelper', // cameras
731
693
  'camera', 'perspectiveCamera', 'orthographicCamera', 'cubeCamera', 'arrayCamera', // renderers
732
694
  'webGLRenderer'
@@ -761,7 +723,6 @@
761
723
  arrowHelper: ArrowHelperProps
762
724
  axesHelper: AxesHelperProps
763
725
  // misc
764
- raycaster: RaycasterProps
765
726
  vector2: Vector2Props
766
727
  vector3: Vector3Props
767
728
  vector4: Vector4Props
@@ -778,7 +739,7 @@
778
739
  */
779
740
  ];
780
741
 
781
- const catalogue = {};
742
+ const catalogue = {}; // component creation utility
782
743
 
783
744
  const createComponent$1 = tag => vue.defineComponent({
784
745
  inheritAttrs: false,
@@ -1174,10 +1135,6 @@
1174
1135
  return item?.minidomType === 'RendererNode';
1175
1136
  }
1176
1137
 
1177
- // let watchStopHandle: WatchStopHandle
1178
- // export const beforeRender = [] as Lunch.UpdateCallback[]
1179
- // export const afterRender = [] as Lunch.UpdateCallback[]
1180
-
1181
1138
  const requestUpdate = opts => {
1182
1139
  if (typeof opts.app.config.globalProperties.lunchbox.frameId === 'number') {
1183
1140
  cancelAnimationFrame(opts.app.config.globalProperties.lunchbox.frameId);
@@ -1266,22 +1223,7 @@
1266
1223
 
1267
1224
  const offAfterRender = cb => {
1268
1225
  useBeforeRender().offBeforeRender?.(cb);
1269
- }; // export const onAfterRender = (cb: Lunch.UpdateCallback, index = Infinity) => {
1270
- // if (index === Infinity) {
1271
- // afterRender.push(cb)
1272
- // } else {
1273
- // afterRender.splice(index, 0, cb)
1274
- // }
1275
- // }
1276
- // export const offAfterRender = (cb: Lunch.UpdateCallback | number) => {
1277
- // if (isFinite(cb as number)) {
1278
- // afterRender.splice(cb as number, 1)
1279
- // } else {
1280
- // const idx = afterRender.findIndex((v) => v == cb)
1281
- // afterRender.splice(idx, 1)
1282
- // }
1283
- // }
1284
- // TODO: document
1226
+ }; // TODO: document
1285
1227
 
1286
1228
  const useCancelUpdate = () => {
1287
1229
  const frameId = vue.inject(frameIdKey);
@@ -1303,28 +1245,12 @@
1303
1245
  useCancelUpdateSource()?.();
1304
1246
  };
1305
1247
 
1306
- /** Update the given node so all of its props are current. */
1307
-
1308
- function updateAllObjectProps({
1309
- node
1310
- }) {
1311
- // set props
1312
- const props = node.props || {};
1313
- let output = node;
1314
- Object.keys(props).forEach(key => {
1315
- output = updateObjectProp({
1316
- node,
1317
- key,
1318
- value: props[key]
1319
- });
1320
- });
1321
- return output;
1322
- }
1323
1248
  /** Update a single prop on a given node. */
1324
1249
 
1325
1250
  function updateObjectProp({
1326
1251
  node,
1327
1252
  key,
1253
+ interactables,
1328
1254
  value
1329
1255
  }) {
1330
1256
  // handle and return early if prop is an event
@@ -1333,6 +1259,7 @@
1333
1259
  return addEventListener({
1334
1260
  node,
1335
1261
  key,
1262
+ interactables,
1336
1263
  value
1337
1264
  });
1338
1265
  } // update THREE property
@@ -1588,72 +1515,86 @@
1588
1515
  Elements are `create`d from the outside in, then `insert`ed from the inside out.
1589
1516
  */
1590
1517
 
1591
- const nodeOps = {
1592
- createElement,
1593
-
1594
- createText(text) {
1595
- return createTextNode({
1596
- text
1597
- });
1598
- },
1599
-
1600
- createComment(text) {
1601
- return createCommentNode({
1602
- text
1603
- });
1604
- },
1605
-
1606
- insert,
1607
-
1608
- nextSibling(node) {
1609
- const result = node.nextSibling; // console.log('found', result)
1518
+ const createNodeOps = () => {
1519
+ // APP-LEVEL GLOBALS
1520
+ // ====================
1521
+ // These need to exist at the app level in a place where the node ops can access them.
1522
+ // It'd be better to set these via `app.provide` at app creation, but the node ops need access
1523
+ // to these values before the app is instantiated, so this is the next-best place for them to exist.
1524
+ const interactables = vue.ref([]); // NODE OPS
1525
+ // ====================
1610
1526
 
1611
- if (!result) return null;
1612
- return result;
1613
- },
1527
+ const nodeOps = {
1528
+ createElement,
1614
1529
 
1615
- parentNode(node) {
1616
- const result = node.parentNode;
1617
- if (!result) return null;
1618
- return result;
1619
- },
1530
+ createText(text) {
1531
+ return createTextNode({
1532
+ text
1533
+ });
1534
+ },
1620
1535
 
1621
- patchProp(node, key, prevValue, nextValue) {
1622
- if (isLunchboxDomComponent(node)) {
1623
- // handle DOM node
1624
- if (key === 'style') {
1625
- // special handling for style
1626
- Object.keys(nextValue).forEach(k => {
1627
- node.domElement.style[k] = nextValue[k];
1628
- });
1629
- } else {
1630
- node.domElement.setAttribute(key, nextValue);
1631
- }
1536
+ createComment(text) {
1537
+ return createCommentNode({
1538
+ text
1539
+ });
1540
+ },
1541
+
1542
+ insert,
1543
+
1544
+ nextSibling(node) {
1545
+ const result = node.nextSibling;
1546
+ if (!result) return null;
1547
+ return result;
1548
+ },
1549
+
1550
+ parentNode(node) {
1551
+ const result = node.parentNode;
1552
+ if (!result) return null;
1553
+ return result;
1554
+ },
1555
+
1556
+ patchProp(node, key, prevValue, nextValue) {
1557
+ if (isLunchboxDomComponent(node)) {
1558
+ // handle DOM node
1559
+ if (key === 'style') {
1560
+ // special handling for style
1561
+ Object.keys(nextValue).forEach(k => {
1562
+ node.domElement.style[k] = nextValue[k];
1563
+ });
1564
+ } else {
1565
+ node.domElement.setAttribute(key, nextValue);
1566
+ }
1632
1567
 
1633
- return;
1634
- } // ignore if root node, or Lunchbox internal prop
1568
+ return;
1569
+ } // ignore if root node, or Lunchbox internal prop
1635
1570
 
1636
1571
 
1637
- if (isLunchboxRootNode(node) || key.startsWith('$')) {
1638
- return;
1639
- } // otherwise, update prop
1572
+ if (isLunchboxRootNode(node) || key.startsWith('$')) {
1573
+ return;
1574
+ } // otherwise, update prop
1640
1575
 
1641
1576
 
1642
- updateObjectProp({
1643
- node: node,
1644
- key,
1645
- value: nextValue
1646
- });
1647
- },
1577
+ updateObjectProp({
1578
+ node: node,
1579
+ key,
1580
+ interactables,
1581
+ value: nextValue
1582
+ });
1583
+ },
1648
1584
 
1649
- remove,
1585
+ remove,
1650
1586
 
1651
- setElementText() {// noop
1652
- },
1587
+ setElementText() {// noop
1588
+ },
1653
1589
 
1654
- setText() {// noop
1655
- }
1590
+ setText() {// noop
1591
+ }
1656
1592
 
1593
+ };
1594
+ return {
1595
+ nodeOps,
1596
+ interactables
1597
+ };
1657
1598
  };
1658
1599
 
1659
1600
  /** The current camera. Often easier to use `useCamera` instead of this. */
@@ -1756,11 +1697,20 @@
1756
1697
  } else {
1757
1698
  callbacks?.splice(index, 0, cb);
1758
1699
  }
1759
- }; // CREATE APP
1700
+ }; // TODO: document
1701
+
1702
+ const useLunchboxInteractables = () => vue.inject(lunchboxInteractables); // CREATE APP
1760
1703
  // ====================
1761
1704
 
1762
1705
  const createApp = root => {
1763
- const app = vue.createRenderer(nodeOps).createApp(root); // register all components
1706
+ const {
1707
+ nodeOps,
1708
+ interactables
1709
+ } = createNodeOps();
1710
+ const app = vue.createRenderer(nodeOps).createApp(root); // provide Lunchbox interaction handlers flag (modified when user references events via
1711
+ // @click, etc)
1712
+
1713
+ app.provide(lunchboxInteractables, interactables); // register all components
1764
1714
  // ====================
1765
1715
 
1766
1716
  Object.keys(components).forEach(key => {
@@ -1911,7 +1861,6 @@
1911
1861
  };
1912
1862
 
1913
1863
  exports.addEventListener = addEventListener;
1914
- exports.addInteractable = addInteractable;
1915
1864
  exports.afterRenderKey = afterRenderKey;
1916
1865
  exports.appCameraKey = appCameraKey;
1917
1866
  exports.appKey = appKey;
@@ -1929,7 +1878,6 @@
1929
1878
  exports.createDomNode = createDomNode;
1930
1879
  exports.createNode = createNode;
1931
1880
  exports.createTextNode = createTextNode;
1932
- exports.currentIntersections = currentIntersections;
1933
1881
  exports.ensureRenderer = ensureRenderer;
1934
1882
  exports.ensuredCamera = ensuredCamera;
1935
1883
  exports.ensuredScene = ensuredScene;
@@ -1937,11 +1885,9 @@
1937
1885
  exports.find = find;
1938
1886
  exports.frameIdKey = frameIdKey;
1939
1887
  exports.globalsInjectionKey = globalsInjectionKey;
1940
- exports.inputActive = inputActive;
1941
1888
  exports.instantiateThreeObject = instantiateThreeObject;
1942
- exports.interactables = interactables;
1943
1889
  exports.isMinidomNode = isMinidomNode;
1944
- exports.mousePos = mousePos;
1890
+ exports.lunchboxInteractables = lunchboxInteractables;
1945
1891
  exports.nestedPropertiesToCheck = nestedPropertiesToCheck;
1946
1892
  exports.offAfterRender = offAfterRender;
1947
1893
  exports.offAfterRenderKey = offAfterRenderKey;
@@ -1952,14 +1898,12 @@
1952
1898
  exports.onBeforeRender = onBeforeRender;
1953
1899
  exports.onBeforeRenderKey = onBeforeRenderKey;
1954
1900
  exports.onStart = onStart;
1955
- exports.removeInteractable = removeInteractable;
1956
1901
  exports.renderer = renderer;
1957
1902
  exports.scene = scene;
1958
1903
  exports.setCustomRender = setCustomRender;
1959
1904
  exports.setCustomRenderKey = setCustomRenderKey;
1960
1905
  exports.startCallbackKey = startCallbackKey;
1961
1906
  exports.update = update;
1962
- exports.updateAllObjectProps = updateAllObjectProps;
1963
1907
  exports.updateGlobals = updateGlobals;
1964
1908
  exports.updateGlobalsInjectionKey = updateGlobalsInjectionKey;
1965
1909
  exports.updateObjectProp = updateObjectProp;
@@ -1971,6 +1915,7 @@
1971
1915
  exports.useCancelUpdateSource = useCancelUpdateSource;
1972
1916
  exports.useCustomRender = useCustomRender;
1973
1917
  exports.useGlobals = useGlobals;
1918
+ exports.useLunchboxInteractables = useLunchboxInteractables;
1974
1919
  exports.useRenderer = useRenderer;
1975
1920
  exports.useRootNode = useRootNode;
1976
1921
  exports.useScene = useScene;