lunchboxjs 0.2.1001-beta.0 → 0.2.1001-beta.301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -47
- package/dist/lunchboxjs.js +438 -420
- package/dist/lunchboxjs.min.js +1 -1
- package/dist/lunchboxjs.module.js +435 -408
- package/extras/OrbitControlsWrapper.vue +2 -12
- package/package.json +6 -2
- package/src/components/LunchboxEventHandlers.tsx +237 -0
- package/src/components/LunchboxWrapper/LunchboxWrapper.tsx +49 -7
- package/src/components/autoGeneratedComponents.ts +1 -1
- package/src/components/index.ts +2 -2
- package/src/core/createNode.ts +1 -1
- package/src/core/extend.ts +1 -1
- package/src/core/index.ts +0 -1
- package/src/core/instantiateThreeObject/index.ts +1 -1
- package/src/core/instantiateThreeObject/processProps.ts +1 -1
- package/src/core/interaction.ts +55 -0
- package/src/core/minidom.ts +1 -1
- package/src/core/update.ts +32 -44
- package/src/core/updateObjectProp.ts +21 -16
- package/src/index.ts +65 -34
- package/src/keys.ts +1 -0
- package/src/nodeOps/index.ts +70 -57
- package/src/types.ts +0 -8
- package/src/utils/index.ts +10 -0
- package/src/components/catalogue.ts +0 -3
- package/src/core/ensure.ts +0 -16
- package/src/core/interaction/index.ts +0 -102
- package/src/core/interaction/input.ts +0 -4
- package/src/core/interaction/interactables.ts +0 -14
- package/src/core/interaction/setupAutoRaycaster.ts +0 -232
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRef, isVNode,
|
|
1
|
+
import { isRef, isVNode, toRaw, defineComponent, createVNode, resolveComponent, ref, onBeforeUnmount, watch, reactive, onMounted, computed, Fragment, mergeProps, h, inject, createRenderer } from 'vue';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { get, isNumber, set } from 'lodash';
|
|
4
4
|
|
|
@@ -102,242 +102,11 @@ function createNode(options = {}, props = {}) {
|
|
|
102
102
|
return node;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const globalsInjectionKey = Symbol();
|
|
106
|
-
const updateGlobalsInjectionKey = Symbol();
|
|
107
|
-
const setCustomRenderKey = Symbol();
|
|
108
|
-
const clearCustomRenderKey = Symbol();
|
|
109
|
-
const beforeRenderKey = Symbol();
|
|
110
|
-
const onBeforeRenderKey = Symbol();
|
|
111
|
-
const offBeforeRenderKey = Symbol();
|
|
112
|
-
const afterRenderKey = Symbol();
|
|
113
|
-
const onAfterRenderKey = Symbol();
|
|
114
|
-
const offAfterRenderKey = Symbol();
|
|
115
|
-
const frameIdKey = Symbol();
|
|
116
|
-
const watchStopHandleKey = Symbol();
|
|
117
|
-
const appRootNodeKey = Symbol();
|
|
118
|
-
const appKey = Symbol();
|
|
119
|
-
const appRenderersKey = Symbol();
|
|
120
|
-
const appSceneKey = Symbol();
|
|
121
|
-
const appCameraKey = Symbol();
|
|
122
|
-
const startCallbackKey = Symbol();
|
|
123
|
-
|
|
124
|
-
const ensuredCamera = () => inject(appCameraKey); // ENSURE RENDERER
|
|
125
|
-
// ====================
|
|
126
|
-
|
|
127
|
-
const ensureRenderer = () => inject(appRenderersKey); // ENSURE SCENE
|
|
128
|
-
// ====================
|
|
129
|
-
|
|
130
|
-
const ensuredScene = () => inject(appSceneKey);
|
|
131
|
-
|
|
132
|
-
const interactables = [];
|
|
133
|
-
const addInteractable = target => {
|
|
134
|
-
interactables.push(target);
|
|
135
|
-
};
|
|
136
|
-
const removeInteractable = target => {
|
|
137
|
-
const idx = interactables.indexOf(target);
|
|
138
|
-
|
|
139
|
-
if (idx !== -1) {
|
|
140
|
-
interactables.splice(idx, 1);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/** Mouse is down, touch is pressed, etc */
|
|
145
|
-
|
|
146
|
-
const inputActive = ref(false);
|
|
147
|
-
|
|
148
|
-
// let mouseDownListener: (event: MouseEvent) => void
|
|
149
|
-
// let mouseUpListener: (event: MouseEvent) => void
|
|
150
|
-
|
|
151
|
-
const mousePos = ref({
|
|
152
|
-
x: Infinity,
|
|
153
|
-
y: Infinity
|
|
154
|
-
}); // let autoRaycasterEventsInitialized = false
|
|
155
|
-
// let frameID: number
|
|
156
|
-
// export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
|
|
157
|
-
// const instance = node.instance
|
|
158
|
-
// if (!instance) return
|
|
159
|
-
// // TODO: inject doesn't work here. replace this raycaster with a component so we can
|
|
160
|
-
// // `inject` in `setup`?
|
|
161
|
-
// const appLevelGlobals = { dpr: window.devicePixelRatio } //useGlobals()
|
|
162
|
-
// // add mouse events once renderer is ready
|
|
163
|
-
// let stopWatcher: WatchStopHandle | null = null
|
|
164
|
-
// stopWatcher = watch(
|
|
165
|
-
// () => ensureRenderer.value,
|
|
166
|
-
// (renderer) => {
|
|
167
|
-
// // make sure renderer exists
|
|
168
|
-
// if (!renderer?.instance) return
|
|
169
|
-
// // cancel early if autoraycaster exists
|
|
170
|
-
// if (autoRaycasterEventsInitialized) {
|
|
171
|
-
// if (stopWatcher) stopWatcher()
|
|
172
|
-
// return
|
|
173
|
-
// }
|
|
174
|
-
// // create mouse events
|
|
175
|
-
// mouseMoveListener = (evt) => {
|
|
176
|
-
// const screenWidth =
|
|
177
|
-
// (renderer.instance!.domElement.width ?? 1) /
|
|
178
|
-
// appLevelGlobals.dpr
|
|
179
|
-
// const screenHeight =
|
|
180
|
-
// (renderer.instance!.domElement.height ?? 1) /
|
|
181
|
-
// appLevelGlobals.dpr
|
|
182
|
-
// mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
|
|
183
|
-
// mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
|
|
184
|
-
// }
|
|
185
|
-
// mouseDownListener = () => (inputActive.value = true)
|
|
186
|
-
// mouseUpListener = () => (inputActive.value = false)
|
|
187
|
-
// // add mouse events
|
|
188
|
-
// renderer.instance.domElement.addEventListener(
|
|
189
|
-
// 'mousemove',
|
|
190
|
-
// mouseMoveListener
|
|
191
|
-
// )
|
|
192
|
-
// renderer.instance.domElement.addEventListener(
|
|
193
|
-
// 'mousedown',
|
|
194
|
-
// mouseDownListener
|
|
195
|
-
// )
|
|
196
|
-
// renderer.instance.domElement.addEventListener(
|
|
197
|
-
// 'mouseup',
|
|
198
|
-
// mouseUpListener
|
|
199
|
-
// )
|
|
200
|
-
// // TODO: add touch events
|
|
201
|
-
// // process mouse events asynchronously, whenever the mouse state changes
|
|
202
|
-
// watch(
|
|
203
|
-
// () => [inputActive.value, mousePos.value.x, mousePos.value.y],
|
|
204
|
-
// () => {
|
|
205
|
-
// if (frameID) cancelAnimationFrame(frameID)
|
|
206
|
-
// frameID = requestAnimationFrame(() => {
|
|
207
|
-
// autoRaycasterBeforeRender()
|
|
208
|
-
// })
|
|
209
|
-
// }
|
|
210
|
-
// )
|
|
211
|
-
// // mark complete
|
|
212
|
-
// autoRaycasterEventsInitialized = true
|
|
213
|
-
// // cancel setup watcher
|
|
214
|
-
// if (stopWatcher) {
|
|
215
|
-
// stopWatcher()
|
|
216
|
-
// }
|
|
217
|
-
// },
|
|
218
|
-
// { immediate: true }
|
|
219
|
-
// )
|
|
220
|
-
// }
|
|
221
|
-
// AUTO-RAYCASTER CALLBACK
|
|
222
|
-
// ====================
|
|
223
|
-
|
|
224
|
-
let currentIntersections = []; // const autoRaycasterBeforeRender = () => {
|
|
225
|
-
// // setup
|
|
226
|
-
// const raycaster = ensuredRaycaster.value?.instance
|
|
227
|
-
// const camera = ensuredCamera.value?.instance
|
|
228
|
-
// if (!raycaster || !camera) return
|
|
229
|
-
// raycaster.setFromCamera(globals.mousePos.value, camera)
|
|
230
|
-
// const intersections = raycaster.intersectObjects(
|
|
231
|
-
// interactables.map((v) => v.instance as any as THREE.Object3D)
|
|
232
|
-
// )
|
|
233
|
-
// let enterValues: Array<Intersection<THREE.Object3D>> = [],
|
|
234
|
-
// sameValues: Array<Intersection<THREE.Object3D>> = [],
|
|
235
|
-
// leaveValues: Array<Intersection<THREE.Object3D>> = [],
|
|
236
|
-
// entering: Array<{
|
|
237
|
-
// element: Lunch.Node
|
|
238
|
-
// intersection: Intersection<THREE.Object3D>
|
|
239
|
-
// }> = [],
|
|
240
|
-
// staying: Array<{
|
|
241
|
-
// element: Lunch.Node
|
|
242
|
-
// intersection: Intersection<THREE.Object3D>
|
|
243
|
-
// }> = []
|
|
244
|
-
// // intersection arrays
|
|
245
|
-
// leaveValues = currentIntersections.map((v) => v.intersection)
|
|
246
|
-
// // element arrays
|
|
247
|
-
// intersections?.forEach((intersection) => {
|
|
248
|
-
// const currentIdx = currentIntersections.findIndex(
|
|
249
|
-
// (v) => v.intersection.object === intersection.object
|
|
250
|
-
// )
|
|
251
|
-
// if (currentIdx === -1) {
|
|
252
|
-
// // new intersection
|
|
253
|
-
// enterValues.push(intersection)
|
|
254
|
-
// const found = interactables.find(
|
|
255
|
-
// (v) => v.instance?.uuid === intersection.object.uuid
|
|
256
|
-
// )
|
|
257
|
-
// if (found) {
|
|
258
|
-
// entering.push({ element: found, intersection })
|
|
259
|
-
// }
|
|
260
|
-
// } else {
|
|
261
|
-
// // existing intersection
|
|
262
|
-
// sameValues.push(intersection)
|
|
263
|
-
// const found = interactables.find(
|
|
264
|
-
// (v) => v.instance?.uuid === intersection.object.uuid
|
|
265
|
-
// )
|
|
266
|
-
// if (found) {
|
|
267
|
-
// staying.push({ element: found, intersection })
|
|
268
|
-
// }
|
|
269
|
-
// }
|
|
270
|
-
// // this is a current intersection, so it won't be in our `leave` array
|
|
271
|
-
// const leaveIdx = leaveValues.findIndex(
|
|
272
|
-
// (v) => v.object.uuid === intersection.object.uuid
|
|
273
|
-
// )
|
|
274
|
-
// if (leaveIdx !== -1) {
|
|
275
|
-
// leaveValues.splice(leaveIdx, 1)
|
|
276
|
-
// }
|
|
277
|
-
// })
|
|
278
|
-
// const leaving: Array<{
|
|
279
|
-
// element: Lunch.Node
|
|
280
|
-
// intersection: Intersection<THREE.Object3D>
|
|
281
|
-
// }> = leaveValues.map((intersection) => {
|
|
282
|
-
// return {
|
|
283
|
-
// element: interactables.find(
|
|
284
|
-
// (interactable) =>
|
|
285
|
-
// interactable.instance?.uuid === intersection.object.uuid
|
|
286
|
-
// ) as any as Lunch.Node,
|
|
287
|
-
// intersection,
|
|
288
|
-
// }
|
|
289
|
-
// })
|
|
290
|
-
// // new interactions
|
|
291
|
-
// entering.forEach(({ element, intersection }) => {
|
|
292
|
-
// fireEventsFromIntersections({
|
|
293
|
-
// element,
|
|
294
|
-
// eventKeys: ['onPointerEnter'],
|
|
295
|
-
// intersection,
|
|
296
|
-
// })
|
|
297
|
-
// })
|
|
298
|
-
// // unchanged interactions
|
|
299
|
-
// staying.forEach(({ element, intersection }) => {
|
|
300
|
-
// const eventKeys: Array<Lunch.EventKey> = [
|
|
301
|
-
// 'onPointerOver',
|
|
302
|
-
// 'onPointerMove',
|
|
303
|
-
// ]
|
|
304
|
-
// fireEventsFromIntersections({ element, eventKeys, intersection })
|
|
305
|
-
// })
|
|
306
|
-
// // exited interactions
|
|
307
|
-
// leaving.forEach(({ element, intersection }) => {
|
|
308
|
-
// const eventKeys: Array<Lunch.EventKey> = [
|
|
309
|
-
// 'onPointerLeave',
|
|
310
|
-
// 'onPointerOut',
|
|
311
|
-
// ]
|
|
312
|
-
// fireEventsFromIntersections({ element, eventKeys, intersection })
|
|
313
|
-
// })
|
|
314
|
-
// currentIntersections = ([] as any).concat(entering, staying)
|
|
315
|
-
// }
|
|
316
|
-
// utility function for firing multiple callbacks and multiple events on a Lunchbox.Element
|
|
317
|
-
// const fireEventsFromIntersections = ({
|
|
318
|
-
// element,
|
|
319
|
-
// eventKeys,
|
|
320
|
-
// intersection,
|
|
321
|
-
// }: {
|
|
322
|
-
// element: Lunch.Node
|
|
323
|
-
// eventKeys: Array<Lunch.EventKey>
|
|
324
|
-
// intersection: Intersection<THREE.Object3D>
|
|
325
|
-
// }) => {
|
|
326
|
-
// if (!element) return
|
|
327
|
-
// eventKeys.forEach((eventKey) => {
|
|
328
|
-
// if (element.eventListeners[eventKey]) {
|
|
329
|
-
// element.eventListeners[eventKey].forEach((cb) => {
|
|
330
|
-
// cb({ intersection })
|
|
331
|
-
// })
|
|
332
|
-
// }
|
|
333
|
-
// })
|
|
334
|
-
// }
|
|
335
|
-
|
|
336
105
|
/** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
|
|
337
|
-
|
|
338
106
|
function addEventListener({
|
|
339
107
|
node,
|
|
340
108
|
key,
|
|
109
|
+
interactables,
|
|
341
110
|
value
|
|
342
111
|
}) {
|
|
343
112
|
// create new records for this key if needed
|
|
@@ -353,37 +122,18 @@ function addEventListener({
|
|
|
353
122
|
node.eventListeners[key].push(value); // if we need it, let's get/create the main raycaster
|
|
354
123
|
|
|
355
124
|
if (interactionsRequiringRaycaster.includes(key)) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (key === 'onClick' || key === 'onPointerDown' || key === 'onPointerUp') {
|
|
367
|
-
const stop = watch(() => inputActive.value, isDown => {
|
|
368
|
-
const idx = currentIntersections.map(v => v.element).findIndex(v => v.instance && v.instance.uuid === node.instance?.uuid);
|
|
369
|
-
|
|
370
|
-
if (idx !== -1) {
|
|
371
|
-
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
372
|
-
node.eventListeners[key].forEach(func => {
|
|
373
|
-
func({
|
|
374
|
-
intersection: currentIntersections[idx].intersection
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
} else if (!isDown && key === 'onPointerUp') {
|
|
378
|
-
node.eventListeners[key].forEach(func => {
|
|
379
|
-
func({
|
|
380
|
-
intersection: currentIntersections[idx].intersection
|
|
381
|
-
});
|
|
382
|
-
});
|
|
125
|
+
if (node.instance && !interactables.value.includes(node)) {
|
|
126
|
+
// add to interactables
|
|
127
|
+
interactables.value.push(node);
|
|
128
|
+
node.eventListenerRemoveFunctions[key].push(() => {
|
|
129
|
+
// remove from interactables
|
|
130
|
+
const idx = interactables.value.indexOf(node);
|
|
131
|
+
|
|
132
|
+
if (idx !== -1) {
|
|
133
|
+
interactables.value.splice(idx, 1);
|
|
383
134
|
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
node.eventListenerRemoveFunctions[key].push(stop);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
387
137
|
}
|
|
388
138
|
|
|
389
139
|
return node;
|
|
@@ -477,6 +227,194 @@ const LunchboxScene = defineComponent({
|
|
|
477
227
|
|
|
478
228
|
});
|
|
479
229
|
|
|
230
|
+
const LunchboxEventHandlers = defineComponent({
|
|
231
|
+
name: 'LunchboxEventHandlers',
|
|
232
|
+
|
|
233
|
+
setup() {
|
|
234
|
+
const interactables = useLunchboxInteractables();
|
|
235
|
+
const globals = useGlobals();
|
|
236
|
+
const mousePos = ref({
|
|
237
|
+
x: Infinity,
|
|
238
|
+
y: Infinity
|
|
239
|
+
});
|
|
240
|
+
const inputActive = ref(false);
|
|
241
|
+
let currentIntersections = [];
|
|
242
|
+
const raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, 0, -1));
|
|
243
|
+
|
|
244
|
+
const fireEventsFromIntersections = ({
|
|
245
|
+
element,
|
|
246
|
+
eventKeys,
|
|
247
|
+
intersection
|
|
248
|
+
}) => {
|
|
249
|
+
if (!element) return;
|
|
250
|
+
eventKeys.forEach(eventKey => {
|
|
251
|
+
if (element.eventListeners[eventKey]) {
|
|
252
|
+
element.eventListeners[eventKey].forEach(cb => {
|
|
253
|
+
cb({
|
|
254
|
+
intersection
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}; // add mouse listener to renderer DOM element when the element is ready
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
onRendererReady(v => {
|
|
263
|
+
if (!v?.domElement) return; // we have a DOM element, so let's add mouse listeners
|
|
264
|
+
|
|
265
|
+
const {
|
|
266
|
+
domElement
|
|
267
|
+
} = v;
|
|
268
|
+
|
|
269
|
+
const mouseMoveListener = evt => {
|
|
270
|
+
const screenWidth = (domElement.width ?? 1) / globals.dpr;
|
|
271
|
+
const screenHeight = (domElement.height ?? 1) / globals.dpr;
|
|
272
|
+
mousePos.value.x = evt.offsetX / screenWidth * 2 - 1;
|
|
273
|
+
mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const mouseDownListener = () => inputActive.value = true;
|
|
277
|
+
|
|
278
|
+
const mouseUpListener = () => inputActive.value = false; // add mouse events
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
domElement.addEventListener('pointermove', mouseMoveListener);
|
|
282
|
+
domElement.addEventListener('pointerdown', mouseDownListener);
|
|
283
|
+
domElement.addEventListener('pointerup', mouseUpListener);
|
|
284
|
+
});
|
|
285
|
+
const camera = useCamera();
|
|
286
|
+
|
|
287
|
+
const update = () => {
|
|
288
|
+
const c = camera.value;
|
|
289
|
+
if (!c) return; // console.log(camera.value)
|
|
290
|
+
|
|
291
|
+
raycaster.setFromCamera(mousePos.value, c);
|
|
292
|
+
const intersections = raycaster.intersectObjects(interactables?.value.map(v => v.instance) ?? []);
|
|
293
|
+
let leaveValues = [],
|
|
294
|
+
entering = [],
|
|
295
|
+
staying = []; // intersection arrays
|
|
296
|
+
|
|
297
|
+
leaveValues = currentIntersections.map(v => v.intersection); // element arrays
|
|
298
|
+
|
|
299
|
+
intersections?.forEach(intersection => {
|
|
300
|
+
const currentIdx = currentIntersections.findIndex(v => v.intersection.object === intersection.object);
|
|
301
|
+
|
|
302
|
+
if (currentIdx === -1) {
|
|
303
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
304
|
+
|
|
305
|
+
if (found) {
|
|
306
|
+
entering.push({
|
|
307
|
+
element: found,
|
|
308
|
+
intersection
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
313
|
+
|
|
314
|
+
if (found) {
|
|
315
|
+
staying.push({
|
|
316
|
+
element: found,
|
|
317
|
+
intersection
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
} // this is a current intersection, so it won't be in our `leave` array
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
const leaveIdx = leaveValues.findIndex(v => v.object.uuid === intersection.object.uuid);
|
|
324
|
+
|
|
325
|
+
if (leaveIdx !== -1) {
|
|
326
|
+
leaveValues.splice(leaveIdx, 1);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
const leaving = leaveValues.map(intersection => {
|
|
330
|
+
return {
|
|
331
|
+
element: interactables?.value.find(interactable => interactable.instance?.uuid === intersection.object.uuid),
|
|
332
|
+
intersection
|
|
333
|
+
};
|
|
334
|
+
}); // new interactions
|
|
335
|
+
|
|
336
|
+
entering.forEach(({
|
|
337
|
+
element,
|
|
338
|
+
intersection
|
|
339
|
+
}) => {
|
|
340
|
+
fireEventsFromIntersections({
|
|
341
|
+
element,
|
|
342
|
+
eventKeys: ['onPointerEnter'],
|
|
343
|
+
intersection
|
|
344
|
+
});
|
|
345
|
+
}); // unchanged interactions
|
|
346
|
+
|
|
347
|
+
staying.forEach(({
|
|
348
|
+
element,
|
|
349
|
+
intersection
|
|
350
|
+
}) => {
|
|
351
|
+
const eventKeys = ['onPointerOver', 'onPointerMove'];
|
|
352
|
+
fireEventsFromIntersections({
|
|
353
|
+
element,
|
|
354
|
+
eventKeys,
|
|
355
|
+
intersection
|
|
356
|
+
});
|
|
357
|
+
}); // exited interactions
|
|
358
|
+
|
|
359
|
+
leaving.forEach(({
|
|
360
|
+
element,
|
|
361
|
+
intersection
|
|
362
|
+
}) => {
|
|
363
|
+
const eventKeys = ['onPointerLeave', 'onPointerOut'];
|
|
364
|
+
fireEventsFromIntersections({
|
|
365
|
+
element,
|
|
366
|
+
eventKeys,
|
|
367
|
+
intersection
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
currentIntersections = [].concat(entering, staying);
|
|
371
|
+
}; // update function
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
onBeforeRender(update);
|
|
375
|
+
|
|
376
|
+
const teardown = () => offBeforeRender(update);
|
|
377
|
+
|
|
378
|
+
onBeforeUnmount(teardown);
|
|
379
|
+
const clickEventKeys = ['onClick', 'onPointerDown', 'onPointerUp'];
|
|
380
|
+
watch(inputActive, isDown => {
|
|
381
|
+
// run raycaster on click (necessary when `update` is not automatically called,
|
|
382
|
+
// for example in `updateSource` functions)
|
|
383
|
+
update(); // meshes with multiple intersections receive multiple callbacks by default -
|
|
384
|
+
// let's make it so they only receive one callback of each type per frame.
|
|
385
|
+
// (ie usually when you click on a mesh, you expect only one click event to fire, even
|
|
386
|
+
// if there are technically multiple intersections with that mesh)
|
|
387
|
+
|
|
388
|
+
const uuidsInteractedWithThisFrame = [];
|
|
389
|
+
currentIntersections.forEach(v => {
|
|
390
|
+
clickEventKeys.forEach(key => {
|
|
391
|
+
const id = v.element.uuid + key;
|
|
392
|
+
|
|
393
|
+
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
394
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
395
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
396
|
+
intersection: v.intersection
|
|
397
|
+
}));
|
|
398
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
399
|
+
}
|
|
400
|
+
} else if (!isDown && key === 'onPointerUp') {
|
|
401
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
402
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
403
|
+
intersection: v.intersection
|
|
404
|
+
}));
|
|
405
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}); // return arbitrary object to ensure instantiation
|
|
411
|
+
// TODO: why can't we return a <raycaster/> here?
|
|
412
|
+
|
|
413
|
+
return () => createVNode(resolveComponent("object3D"), null, null);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
});
|
|
417
|
+
|
|
480
418
|
/** fixed & fill styling for container */
|
|
481
419
|
|
|
482
420
|
const fillStyle = position => {
|
|
@@ -529,9 +467,10 @@ const LunchboxWrapper = defineComponent({
|
|
|
529
467
|
|
|
530
468
|
if (props.r3f && THREE?.ColorManagement) {
|
|
531
469
|
THREE.ColorManagement.legacyMode = false;
|
|
532
|
-
}
|
|
533
|
-
// ====================
|
|
470
|
+
}
|
|
534
471
|
|
|
472
|
+
const interactables = useLunchboxInteractables(); // MOUNT
|
|
473
|
+
// ====================
|
|
535
474
|
|
|
536
475
|
onMounted(async () => {
|
|
537
476
|
// canvas needs to exist (or user needs to handle it on their own)
|
|
@@ -567,23 +506,17 @@ const LunchboxWrapper = defineComponent({
|
|
|
567
506
|
updateGlobals?.({
|
|
568
507
|
dpr
|
|
569
508
|
});
|
|
570
|
-
console.log(1);
|
|
571
509
|
|
|
572
510
|
while (!renderer.value?.$el?.instance && // TODO: remove `as any`
|
|
573
511
|
!renderer.value?.component?.ctx.$el?.instance) {
|
|
574
|
-
console.log(2);
|
|
575
512
|
await new Promise(r => requestAnimationFrame(r));
|
|
576
513
|
}
|
|
577
514
|
|
|
578
|
-
console.log(3);
|
|
579
|
-
|
|
580
515
|
while (!scene.value?.$el?.instance && // TODO: remove `as any`
|
|
581
516
|
!scene.value?.component?.ctx.$el?.instance) {
|
|
582
|
-
console.log(4);
|
|
583
517
|
await new Promise(r => requestAnimationFrame(r));
|
|
584
518
|
}
|
|
585
519
|
|
|
586
|
-
console.log(5);
|
|
587
520
|
const normalizedRenderer = renderer.value?.$el?.instance ?? renderer.value?.component?.ctx.$el?.instance;
|
|
588
521
|
normalizedRenderer.setPixelRatio(globals.dpr);
|
|
589
522
|
const normalizedScene = scene.value?.$el?.instance ?? scene.value?.component?.ctx.$el?.instance;
|
|
@@ -653,7 +586,37 @@ const LunchboxWrapper = defineComponent({
|
|
|
653
586
|
// ====================
|
|
654
587
|
|
|
655
588
|
const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
|
|
656
|
-
const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
|
|
589
|
+
const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed'; // REACTIVE CUSTOM CAMERAS
|
|
590
|
+
// ====================
|
|
591
|
+
// find first camera with `type.name` property
|
|
592
|
+
// (which indicates a Lunch.Node)
|
|
593
|
+
|
|
594
|
+
const activeCamera = computed(() => {
|
|
595
|
+
const output = context.slots?.camera?.().find(c => c.type?.name);
|
|
596
|
+
|
|
597
|
+
if (output) {
|
|
598
|
+
return output;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return output;
|
|
602
|
+
}); // TODO: make custom cameras reactive
|
|
603
|
+
|
|
604
|
+
watch(activeCamera, async (newVal, oldVal) => {
|
|
605
|
+
// console.log('got camera', newVal)
|
|
606
|
+
if (newVal && newVal?.props?.key !== oldVal?.props?.key) {
|
|
607
|
+
// TODO: remove cast
|
|
608
|
+
camera.value = newVal; // TODO: why isn't this updating app camera?
|
|
609
|
+
// const el = await waitFor(() => newVal.el)
|
|
610
|
+
// console.log(el)
|
|
611
|
+
// camera.value = el
|
|
612
|
+
// console.log(newVal.uuid)
|
|
613
|
+
// updateGlobals?.({ camera: el })
|
|
614
|
+
}
|
|
615
|
+
}, {
|
|
616
|
+
immediate: true
|
|
617
|
+
}); // RENDER FUNCTION
|
|
618
|
+
// ====================
|
|
619
|
+
|
|
657
620
|
return () => createVNode(Fragment, null, [context.slots?.renderer?.()?.length ? // TODO: remove `as any` cast
|
|
658
621
|
renderer.value = context.slots?.renderer?.()[0] : // ...otherwise, add canvas...
|
|
659
622
|
createVNode(Fragment, null, [createVNode("div", {
|
|
@@ -683,13 +646,13 @@ const LunchboxWrapper = defineComponent({
|
|
|
683
646
|
}, {
|
|
684
647
|
default: () => [context.slots?.default?.()]
|
|
685
648
|
}), context.slots?.camera?.()?.length ? // TODO: remove `any` cast
|
|
686
|
-
camera.value
|
|
649
|
+
camera.value : props.ortho || props.orthographic ? createVNode(resolveComponent("orthographicCamera"), mergeProps({
|
|
687
650
|
"ref": camera,
|
|
688
651
|
"args": props.cameraArgs ?? []
|
|
689
652
|
}, consolidatedCameraProperties), null) : createVNode(resolveComponent("perspectiveCamera"), mergeProps({
|
|
690
653
|
"ref": camera,
|
|
691
654
|
"args": props.cameraArgs ?? [props.r3f ? 75 : 45, 0.5625, 1, 1000]
|
|
692
|
-
}, consolidatedCameraProperties), null)]);
|
|
655
|
+
}, consolidatedCameraProperties), null), interactables?.value.length && createVNode(LunchboxEventHandlers, null, null)]);
|
|
693
656
|
}
|
|
694
657
|
|
|
695
658
|
});
|
|
@@ -704,7 +667,7 @@ const autoGeneratedComponents = [// ThreeJS basics
|
|
|
704
667
|
'light', 'spotLightShadow', 'spotLight', 'pointLight', 'rectAreaLight', 'hemisphereLight', 'directionalLightShadow', 'directionalLight', 'ambientLight', 'lightShadow', 'ambientLightProbe', 'hemisphereLightProbe', 'lightProbe', // textures
|
|
705
668
|
'texture', 'videoTexture', 'dataTexture', 'dataTexture3D', 'compressedTexture', 'cubeTexture', 'canvasTexture', 'depthTexture', // Texture loaders
|
|
706
669
|
'textureLoader', // misc
|
|
707
|
-
'group', 'catmullRomCurve3', 'points', // helpers
|
|
670
|
+
'group', 'catmullRomCurve3', 'points', 'raycaster', // helpers
|
|
708
671
|
'cameraHelper', // cameras
|
|
709
672
|
'camera', 'perspectiveCamera', 'orthographicCamera', 'cubeCamera', 'arrayCamera', // renderers
|
|
710
673
|
'webGLRenderer'
|
|
@@ -739,7 +702,6 @@ planeHelper: PlaneHelperProps
|
|
|
739
702
|
arrowHelper: ArrowHelperProps
|
|
740
703
|
axesHelper: AxesHelperProps
|
|
741
704
|
// misc
|
|
742
|
-
raycaster: RaycasterProps
|
|
743
705
|
vector2: Vector2Props
|
|
744
706
|
vector3: Vector3Props
|
|
745
707
|
vector4: Vector4Props
|
|
@@ -756,7 +718,7 @@ shape: ShapeProps
|
|
|
756
718
|
*/
|
|
757
719
|
];
|
|
758
720
|
|
|
759
|
-
const catalogue = {};
|
|
721
|
+
const catalogue = {}; // component creation utility
|
|
760
722
|
|
|
761
723
|
const createComponent$1 = tag => defineComponent({
|
|
762
724
|
inheritAttrs: false,
|
|
@@ -1152,9 +1114,25 @@ function isMinidomNode(item) {
|
|
|
1152
1114
|
return item?.minidomType === 'RendererNode';
|
|
1153
1115
|
}
|
|
1154
1116
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1117
|
+
const globalsInjectionKey = Symbol();
|
|
1118
|
+
const updateGlobalsInjectionKey = Symbol();
|
|
1119
|
+
const setCustomRenderKey = Symbol();
|
|
1120
|
+
const clearCustomRenderKey = Symbol();
|
|
1121
|
+
const beforeRenderKey = Symbol();
|
|
1122
|
+
const onBeforeRenderKey = Symbol();
|
|
1123
|
+
const offBeforeRenderKey = Symbol();
|
|
1124
|
+
const afterRenderKey = Symbol();
|
|
1125
|
+
const onAfterRenderKey = Symbol();
|
|
1126
|
+
const offAfterRenderKey = Symbol();
|
|
1127
|
+
const frameIdKey = Symbol();
|
|
1128
|
+
const watchStopHandleKey = Symbol();
|
|
1129
|
+
const appRootNodeKey = Symbol();
|
|
1130
|
+
const appKey = Symbol();
|
|
1131
|
+
const appRenderersKey = Symbol();
|
|
1132
|
+
const appSceneKey = Symbol();
|
|
1133
|
+
const appCameraKey = Symbol();
|
|
1134
|
+
const lunchboxInteractables = Symbol();
|
|
1135
|
+
const startCallbackKey = Symbol();
|
|
1158
1136
|
|
|
1159
1137
|
const requestUpdate = opts => {
|
|
1160
1138
|
if (typeof opts.app.config.globalProperties.lunchbox.frameId === 'number') {
|
|
@@ -1189,20 +1167,19 @@ const update = opts => {
|
|
|
1189
1167
|
const {
|
|
1190
1168
|
app,
|
|
1191
1169
|
renderer,
|
|
1192
|
-
scene
|
|
1193
|
-
camera
|
|
1170
|
+
scene
|
|
1194
1171
|
} = opts; // BEFORE RENDER
|
|
1195
1172
|
|
|
1196
1173
|
app.config.globalProperties.lunchbox.beforeRender.forEach(cb => {
|
|
1197
1174
|
cb?.(opts);
|
|
1198
1175
|
}); // RENDER
|
|
1199
1176
|
|
|
1200
|
-
if (renderer && scene && camera) {
|
|
1177
|
+
if (renderer && scene && opts.app.config.globalProperties.lunchbox.camera) {
|
|
1201
1178
|
if (app.customRender) {
|
|
1202
1179
|
app.customRender(opts);
|
|
1203
1180
|
} else {
|
|
1204
|
-
renderer.render(toRaw(scene),
|
|
1205
|
-
|
|
1181
|
+
renderer.render(toRaw(scene), opts.app.config.globalProperties.lunchbox.camera // toRaw(camera)
|
|
1182
|
+
);
|
|
1206
1183
|
}
|
|
1207
1184
|
} // AFTER RENDER
|
|
1208
1185
|
|
|
@@ -1212,97 +1189,92 @@ const update = opts => {
|
|
|
1212
1189
|
});
|
|
1213
1190
|
}; // before render
|
|
1214
1191
|
// ====================
|
|
1215
|
-
|
|
1192
|
+
|
|
1193
|
+
/** Obtain callback methods for `onBeforeRender` and `offBeforeRender`. Usually used internally by Lunchbox. */
|
|
1216
1194
|
|
|
1217
1195
|
const useBeforeRender = () => {
|
|
1218
1196
|
return {
|
|
1219
1197
|
onBeforeRender: inject(onBeforeRenderKey),
|
|
1220
1198
|
offBeforeRender: inject(offBeforeRenderKey)
|
|
1221
1199
|
};
|
|
1222
|
-
};
|
|
1200
|
+
};
|
|
1201
|
+
/** Run a function before every render.
|
|
1202
|
+
*
|
|
1203
|
+
* Note that if `updateSource` is set in the Lunchbox wrapper component, this will **only** run
|
|
1204
|
+
* before a render triggered by that `updateSource`. Normally, the function should run every frame.
|
|
1205
|
+
*/
|
|
1223
1206
|
|
|
1224
1207
|
const onBeforeRender = (cb, index = Infinity) => {
|
|
1225
1208
|
useBeforeRender().onBeforeRender?.(cb, index);
|
|
1226
|
-
};
|
|
1209
|
+
};
|
|
1210
|
+
/** Remove a function from the `beforeRender` callback list. Useful for tearing down functions added
|
|
1211
|
+
* by `onBeforeRender`.
|
|
1212
|
+
*/
|
|
1227
1213
|
|
|
1228
1214
|
const offBeforeRender = cb => {
|
|
1229
1215
|
useBeforeRender().offBeforeRender?.(cb);
|
|
1230
1216
|
}; // after render
|
|
1231
1217
|
// ====================
|
|
1232
|
-
|
|
1218
|
+
|
|
1219
|
+
/** Obtain callback methods for `onAfterRender` and `offAfterRender`. Usually used internally by Lunchbox. */
|
|
1233
1220
|
|
|
1234
1221
|
const useAfterRender = () => {
|
|
1235
1222
|
return {
|
|
1236
1223
|
onAfterRender: inject(onBeforeRenderKey),
|
|
1237
1224
|
offAfterRender: inject(offBeforeRenderKey)
|
|
1238
1225
|
};
|
|
1239
|
-
};
|
|
1226
|
+
};
|
|
1227
|
+
/** Run a function after every render.
|
|
1228
|
+
*
|
|
1229
|
+
* Note that if `updateSource` is set in the Lunchbox wrapper component, this will **only** run
|
|
1230
|
+
* after a render triggered by that `updateSource`. Normally, the function should run every frame.
|
|
1231
|
+
*/
|
|
1240
1232
|
|
|
1241
1233
|
const onAfterRender = (cb, index = Infinity) => {
|
|
1242
1234
|
useBeforeRender().onBeforeRender?.(cb, index);
|
|
1243
|
-
};
|
|
1235
|
+
};
|
|
1236
|
+
/** Remove a function from the `afterRender` callback list. Useful for tearing down functions added
|
|
1237
|
+
* by `onAfterRender`.
|
|
1238
|
+
*/
|
|
1244
1239
|
|
|
1245
1240
|
const offAfterRender = cb => {
|
|
1246
1241
|
useBeforeRender().offBeforeRender?.(cb);
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
// afterRender.splice(index, 0, cb)
|
|
1252
|
-
// }
|
|
1253
|
-
// }
|
|
1254
|
-
// export const offAfterRender = (cb: Lunch.UpdateCallback | number) => {
|
|
1255
|
-
// if (isFinite(cb as number)) {
|
|
1256
|
-
// afterRender.splice(cb as number, 1)
|
|
1257
|
-
// } else {
|
|
1258
|
-
// const idx = afterRender.findIndex((v) => v == cb)
|
|
1259
|
-
// afterRender.splice(idx, 1)
|
|
1260
|
-
// }
|
|
1261
|
-
// }
|
|
1262
|
-
// TODO: document
|
|
1242
|
+
};
|
|
1243
|
+
/** Obtain a function used to cancel the current update frame. Use `cancelUpdate` if you wish
|
|
1244
|
+
* to immediately invoke the cancellation function. Usually used internally by Lunchbox.
|
|
1245
|
+
*/
|
|
1263
1246
|
|
|
1264
1247
|
const useCancelUpdate = () => {
|
|
1265
1248
|
const frameId = inject(frameIdKey);
|
|
1266
1249
|
return () => {
|
|
1267
1250
|
if (frameId !== undefined) cancelAnimationFrame(frameId);
|
|
1268
1251
|
};
|
|
1269
|
-
};
|
|
1252
|
+
};
|
|
1253
|
+
/** Cancel the current update frame. Usually used internally by Lunchbox. */
|
|
1270
1254
|
|
|
1271
1255
|
const cancelUpdate = () => {
|
|
1272
1256
|
useCancelUpdate()?.();
|
|
1273
|
-
};
|
|
1257
|
+
};
|
|
1258
|
+
/** Obtain a function used to cancel an update source. Use `cancelUpdateSource` if you wish to
|
|
1259
|
+
* immediately invoke the cancellation function. Usually used internally by Lunchbox.
|
|
1260
|
+
*/
|
|
1274
1261
|
|
|
1275
1262
|
const useCancelUpdateSource = () => {
|
|
1276
1263
|
const cancel = inject(watchStopHandleKey);
|
|
1277
1264
|
return () => cancel?.();
|
|
1278
|
-
};
|
|
1265
|
+
};
|
|
1266
|
+
/** Cancel an update source. Usually used internally by Lunchbox. */
|
|
1279
1267
|
|
|
1280
1268
|
const cancelUpdateSource = () => {
|
|
1281
1269
|
useCancelUpdateSource()?.();
|
|
1282
1270
|
};
|
|
1283
1271
|
|
|
1284
|
-
/** Update the given node so all of its props are current. */
|
|
1285
|
-
|
|
1286
|
-
function updateAllObjectProps({
|
|
1287
|
-
node
|
|
1288
|
-
}) {
|
|
1289
|
-
// set props
|
|
1290
|
-
const props = node.props || {};
|
|
1291
|
-
let output = node;
|
|
1292
|
-
Object.keys(props).forEach(key => {
|
|
1293
|
-
output = updateObjectProp({
|
|
1294
|
-
node,
|
|
1295
|
-
key,
|
|
1296
|
-
value: props[key]
|
|
1297
|
-
});
|
|
1298
|
-
});
|
|
1299
|
-
return output;
|
|
1300
|
-
}
|
|
1301
1272
|
/** Update a single prop on a given node. */
|
|
1302
1273
|
|
|
1303
1274
|
function updateObjectProp({
|
|
1304
1275
|
node,
|
|
1305
1276
|
key,
|
|
1277
|
+
interactables,
|
|
1306
1278
|
value
|
|
1307
1279
|
}) {
|
|
1308
1280
|
// handle and return early if prop is an event
|
|
@@ -1311,6 +1283,7 @@ function updateObjectProp({
|
|
|
1311
1283
|
return addEventListener({
|
|
1312
1284
|
node,
|
|
1313
1285
|
key,
|
|
1286
|
+
interactables,
|
|
1314
1287
|
value
|
|
1315
1288
|
});
|
|
1316
1289
|
} // update THREE property
|
|
@@ -1341,6 +1314,7 @@ function updateObjectProp({
|
|
|
1341
1314
|
const fullPath = [nestedProperty, finalKey].filter(Boolean).join('.');
|
|
1342
1315
|
liveProperty = liveProperty = get(target, fullPath);
|
|
1343
1316
|
} // change property
|
|
1317
|
+
// first, save as array in case we need to spread it
|
|
1344
1318
|
|
|
1345
1319
|
|
|
1346
1320
|
if (liveProperty && isNumber(value) && liveProperty.setScalar) {
|
|
@@ -1351,14 +1325,24 @@ function updateObjectProp({
|
|
|
1351
1325
|
const nextValueAsArray = Array.isArray(value) ? value : [value];
|
|
1352
1326
|
target[finalKey].set(...nextValueAsArray);
|
|
1353
1327
|
} else if (typeof liveProperty === 'function') {
|
|
1354
|
-
//
|
|
1355
|
-
|
|
1328
|
+
// some function properties are set rather than called, so let's handle them
|
|
1329
|
+
if (finalKey.toLowerCase() === 'onbeforerender' || finalKey.toLowerCase() === 'onafterrender') {
|
|
1330
|
+
target[finalKey] = value;
|
|
1331
|
+
} else {
|
|
1332
|
+
if (!Array.isArray(value)) {
|
|
1333
|
+
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" />');
|
|
1334
|
+
} // if property is a function, let's try calling it
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
liveProperty.bind(node.instance)(...value);
|
|
1338
|
+
} // pass the result to the parent
|
|
1356
1339
|
// const parent = node.parentNode
|
|
1357
1340
|
// if (parent) {
|
|
1358
1341
|
// const parentAsLunchboxNode = parent as Lunchbox.Node
|
|
1359
1342
|
// parentAsLunchboxNode.attached[finalKey] = result
|
|
1360
1343
|
// ; (parentAsLunchboxNode.instance as any)[finalKey] = result
|
|
1361
1344
|
// }
|
|
1345
|
+
|
|
1362
1346
|
} else if (get(target, finalKey, undefined) !== undefined) {
|
|
1363
1347
|
// blank strings evaluate to `true`
|
|
1364
1348
|
// <mesh castShadow receiveShadow /> will work the same as
|
|
@@ -1566,101 +1550,133 @@ const remove = node => {
|
|
|
1566
1550
|
Elements are `create`d from the outside in, then `insert`ed from the inside out.
|
|
1567
1551
|
*/
|
|
1568
1552
|
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
createComment(text) {
|
|
1579
|
-
return createCommentNode({
|
|
1580
|
-
text
|
|
1581
|
-
});
|
|
1582
|
-
},
|
|
1583
|
-
|
|
1584
|
-
insert,
|
|
1553
|
+
const createNodeOps = () => {
|
|
1554
|
+
// APP-LEVEL GLOBALS
|
|
1555
|
+
// ====================
|
|
1556
|
+
// These need to exist at the app level in a place where the node ops can access them.
|
|
1557
|
+
// It'd be better to set these via `app.provide` at app creation, but the node ops need access
|
|
1558
|
+
// to these values before the app is instantiated, so this is the next-best place for them to exist.
|
|
1559
|
+
const interactables = ref([]); // NODE OPS
|
|
1560
|
+
// ====================
|
|
1585
1561
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1562
|
+
const nodeOps = {
|
|
1563
|
+
createElement,
|
|
1588
1564
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const result = node.parentNode;
|
|
1595
|
-
if (!result) return null;
|
|
1596
|
-
return result;
|
|
1597
|
-
},
|
|
1565
|
+
createText(text) {
|
|
1566
|
+
return createTextNode({
|
|
1567
|
+
text
|
|
1568
|
+
});
|
|
1569
|
+
},
|
|
1598
1570
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1571
|
+
createComment(text) {
|
|
1572
|
+
return createCommentNode({
|
|
1573
|
+
text
|
|
1574
|
+
});
|
|
1575
|
+
},
|
|
1576
|
+
|
|
1577
|
+
insert,
|
|
1578
|
+
|
|
1579
|
+
nextSibling(node) {
|
|
1580
|
+
const result = node.nextSibling;
|
|
1581
|
+
if (!result) return null;
|
|
1582
|
+
return result;
|
|
1583
|
+
},
|
|
1584
|
+
|
|
1585
|
+
parentNode(node) {
|
|
1586
|
+
const result = node.parentNode;
|
|
1587
|
+
if (!result) return null;
|
|
1588
|
+
return result;
|
|
1589
|
+
},
|
|
1590
|
+
|
|
1591
|
+
patchProp(node, key, prevValue, nextValue) {
|
|
1592
|
+
if (isLunchboxDomComponent(node)) {
|
|
1593
|
+
// handle DOM node
|
|
1594
|
+
if (key === 'style') {
|
|
1595
|
+
// special handling for style
|
|
1596
|
+
Object.keys(nextValue).forEach(k => {
|
|
1597
|
+
node.domElement.style[k] = nextValue[k];
|
|
1598
|
+
});
|
|
1599
|
+
} else {
|
|
1600
|
+
node.domElement.setAttribute(key, nextValue);
|
|
1601
|
+
}
|
|
1610
1602
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1603
|
+
return;
|
|
1604
|
+
} // ignore if root node, or Lunchbox internal prop
|
|
1613
1605
|
|
|
1614
1606
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1607
|
+
if (isLunchboxRootNode(node) || key.startsWith('$')) {
|
|
1608
|
+
return;
|
|
1609
|
+
} // otherwise, update prop
|
|
1618
1610
|
|
|
1619
1611
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1612
|
+
updateObjectProp({
|
|
1613
|
+
node: node,
|
|
1614
|
+
key,
|
|
1615
|
+
interactables,
|
|
1616
|
+
value: nextValue
|
|
1617
|
+
});
|
|
1618
|
+
},
|
|
1626
1619
|
|
|
1627
|
-
|
|
1620
|
+
remove,
|
|
1628
1621
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1622
|
+
setElementText() {// noop
|
|
1623
|
+
},
|
|
1631
1624
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1625
|
+
setText() {// noop
|
|
1626
|
+
}
|
|
1634
1627
|
|
|
1628
|
+
};
|
|
1629
|
+
return {
|
|
1630
|
+
nodeOps,
|
|
1631
|
+
interactables
|
|
1632
|
+
};
|
|
1635
1633
|
};
|
|
1636
1634
|
|
|
1637
|
-
/** The current camera
|
|
1638
|
-
// TODO: update docs
|
|
1635
|
+
/** The current camera as a computed value. */
|
|
1639
1636
|
|
|
1640
|
-
const
|
|
1637
|
+
const useCamera = () => inject(appCameraKey);
|
|
1638
|
+
/** Run a function using the current camera when it's present. */
|
|
1641
1639
|
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1640
|
+
const onCameraReady = cb => {
|
|
1641
|
+
const stopWatch = watch(useCamera(), newVal => {
|
|
1642
|
+
if (newVal) {
|
|
1643
|
+
cb(newVal);
|
|
1644
|
+
stopWatch();
|
|
1645
|
+
}
|
|
1646
|
+
}, {
|
|
1647
|
+
immediate: true
|
|
1648
|
+
});
|
|
1649
|
+
};
|
|
1650
|
+
/** The current renderer as a computed value. */
|
|
1644
1651
|
|
|
1645
|
-
const
|
|
1652
|
+
const useRenderer = () => inject(appRenderersKey);
|
|
1646
1653
|
/** Run a function using the current renderer when it's present. */
|
|
1647
1654
|
|
|
1648
|
-
const
|
|
1649
|
-
|
|
1650
|
-
|
|
1655
|
+
const onRendererReady = cb => {
|
|
1656
|
+
const stopWatch = watch(useRenderer(), newVal => {
|
|
1657
|
+
if (newVal) {
|
|
1658
|
+
cb(newVal);
|
|
1659
|
+
stopWatch();
|
|
1660
|
+
}
|
|
1661
|
+
}, {
|
|
1662
|
+
immediate: true
|
|
1663
|
+
});
|
|
1664
|
+
};
|
|
1665
|
+
/** The current scene as a computed value. */
|
|
1651
1666
|
|
|
1652
|
-
const
|
|
1667
|
+
const useScene = () => inject(appSceneKey);
|
|
1653
1668
|
/** Run a function using the current scene when it's present. */
|
|
1654
|
-
// TODO: update docs
|
|
1655
1669
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
1670
|
+
const onSceneReady = cb => {
|
|
1671
|
+
const stopWatch = watch(useScene(), newVal => {
|
|
1672
|
+
if (newVal) {
|
|
1673
|
+
cb(newVal);
|
|
1674
|
+
stopWatch();
|
|
1675
|
+
}
|
|
1660
1676
|
}, {
|
|
1661
1677
|
immediate: true
|
|
1662
1678
|
});
|
|
1663
|
-
} // CUSTOM RENDER SUPPORT
|
|
1679
|
+
}; // CUSTOM RENDER SUPPORT
|
|
1664
1680
|
// ====================
|
|
1665
1681
|
|
|
1666
1682
|
/** Set a custom render function, overriding the Lunchbox app's default render function.
|
|
@@ -1717,14 +1733,15 @@ const useUpdateGlobals = () => inject(updateGlobalsInjectionKey);
|
|
|
1717
1733
|
|
|
1718
1734
|
const updateGlobals = newValue => {
|
|
1719
1735
|
useUpdateGlobals()?.(newValue);
|
|
1720
|
-
};
|
|
1721
|
-
|
|
1722
|
-
const useRootNode = () => inject(appRootNodeKey); // TODO: document
|
|
1736
|
+
};
|
|
1737
|
+
/** Use the current Lunchbox app. Usually used internally by Lunchbox. */
|
|
1723
1738
|
|
|
1724
|
-
const useApp = () => inject(appKey);
|
|
1739
|
+
const useApp = () => inject(appKey);
|
|
1740
|
+
/** Obtain a list of the start callback functions. Usually used internally by Lunchbox. */
|
|
1725
1741
|
|
|
1726
|
-
const useStartCallbacks = () => inject(startCallbackKey);
|
|
1727
|
-
|
|
1742
|
+
const useStartCallbacks = () => inject(startCallbackKey);
|
|
1743
|
+
/** Run a given callback once when the Lunchbox app starts. Include an index to
|
|
1744
|
+
* splice the callback at that index in the callback queue. */
|
|
1728
1745
|
|
|
1729
1746
|
const onStart = (cb, index = Infinity) => {
|
|
1730
1747
|
const callbacks = useStartCallbacks();
|
|
@@ -1734,11 +1751,21 @@ const onStart = (cb, index = Infinity) => {
|
|
|
1734
1751
|
} else {
|
|
1735
1752
|
callbacks?.splice(index, 0, cb);
|
|
1736
1753
|
}
|
|
1737
|
-
};
|
|
1754
|
+
};
|
|
1755
|
+
/** Obtain a list of interactable objects (registered via onClick, onHover, etc events). Usually used internally by Lunchbox. */
|
|
1756
|
+
|
|
1757
|
+
const useLunchboxInteractables = () => inject(lunchboxInteractables); // CREATE APP
|
|
1738
1758
|
// ====================
|
|
1739
1759
|
|
|
1740
1760
|
const createApp = root => {
|
|
1741
|
-
const
|
|
1761
|
+
const {
|
|
1762
|
+
nodeOps,
|
|
1763
|
+
interactables
|
|
1764
|
+
} = createNodeOps();
|
|
1765
|
+
const app = createRenderer(nodeOps).createApp(root); // provide Lunchbox interaction handlers flag (modified when user references events via
|
|
1766
|
+
// @click, etc)
|
|
1767
|
+
|
|
1768
|
+
app.provide(lunchboxInteractables, interactables); // register all components
|
|
1742
1769
|
// ====================
|
|
1743
1770
|
|
|
1744
1771
|
Object.keys(components).forEach(key => {
|
|
@@ -1888,4 +1915,4 @@ const createApp = root => {
|
|
|
1888
1915
|
return app;
|
|
1889
1916
|
};
|
|
1890
1917
|
|
|
1891
|
-
export { MiniDom, addEventListener,
|
|
1918
|
+
export { MiniDom, addEventListener, afterRenderKey, appCameraKey, appKey, appRenderersKey, appRootNodeKey, appSceneKey, beforeRenderKey, cancelUpdate, cancelUpdateSource, clearCustomRender, clearCustomRenderKey, createApp, createCommentNode, createDomNode, createNode, createTextNode, extend, find, frameIdKey, globalsInjectionKey, instantiateThreeObject, isMinidomNode, lunchboxInteractables, nestedPropertiesToCheck, offAfterRender, offAfterRenderKey, offBeforeRender, offBeforeRenderKey, onAfterRender, onAfterRenderKey, onBeforeRender, onBeforeRenderKey, onCameraReady, onRendererReady, onSceneReady, onStart, setCustomRender, setCustomRenderKey, startCallbackKey, update, updateGlobals, updateGlobalsInjectionKey, updateObjectProp, useAfterRender, useApp, useBeforeRender, useCamera, useCancelUpdate, useCancelUpdateSource, useCustomRender, useGlobals, useLunchboxInteractables, useRenderer, useScene, useStartCallbacks, useUpdateGlobals, watchStopHandleKey };
|