浏览代码

feat: pass all provides down to custom renderer (#806)

* feat: pass all provides down to custom renderer

* chore: eslint auto import

* fix: solve precedence issue and added dedicated playground

* chore: disable eslint console rule in playground

* feat: provide inject reactivity seems to work now

* chore: remove unsued import

* feature: just removed console log after testing in another package

* feat: opt-out flag for disabling provide bridge

* feat: correct the precedence of provide keys

* chore: fix lint

* feat: changed flag to positive

* docs: correct back the docs
Alvaro Saburido 7 月之前
父节点
当前提交
b4a3866f69

+ 1 - 0
docs/api/tres-canvas.md

@@ -92,6 +92,7 @@ renderer.shadowMap.type = PCFSoftShadowMap
 | **toneMappingExposure** | Exposure level of tone mapping. | `1` |
 | **useLegacyLights** | Whether to use the legacy lighting mode or not | `true` |
 | **windowSize** | Whether to use the window size as the canvas size or the parent element. | `false` |
+| **enableProvideBridge** | Enables the provide/inject bridge between Vue context and TresCanvas. | `true` |
 
 ### Defaults
 

+ 67 - 0
playground/.eslintrc-auto-import.json

@@ -0,0 +1,67 @@
+{
+  "globals": {
+    "Component": true,
+    "ComponentPublicInstance": true,
+    "ComputedRef": true,
+    "EffectScope": true,
+    "ExtractDefaultPropTypes": true,
+    "ExtractPropTypes": true,
+    "ExtractPublicPropTypes": true,
+    "InjectionKey": true,
+    "PropType": true,
+    "Ref": true,
+    "VNode": true,
+    "WritableComputedRef": true,
+    "computed": true,
+    "createApp": true,
+    "customRef": true,
+    "defineAsyncComponent": true,
+    "defineComponent": true,
+    "effectScope": true,
+    "getCurrentInstance": true,
+    "getCurrentScope": true,
+    "h": true,
+    "inject": true,
+    "isProxy": true,
+    "isReactive": true,
+    "isReadonly": true,
+    "isRef": true,
+    "markRaw": true,
+    "nextTick": true,
+    "onActivated": true,
+    "onBeforeMount": true,
+    "onBeforeUnmount": true,
+    "onBeforeUpdate": true,
+    "onDeactivated": true,
+    "onErrorCaptured": true,
+    "onMounted": true,
+    "onRenderTracked": true,
+    "onRenderTriggered": true,
+    "onScopeDispose": true,
+    "onServerPrefetch": true,
+    "onUnmounted": true,
+    "onUpdated": true,
+    "provide": true,
+    "reactive": true,
+    "readonly": true,
+    "ref": true,
+    "resolveComponent": true,
+    "shallowReactive": true,
+    "shallowReadonly": true,
+    "shallowRef": true,
+    "toRaw": true,
+    "toRef": true,
+    "toRefs": true,
+    "toValue": true,
+    "triggerRef": true,
+    "unref": true,
+    "useAttrs": true,
+    "useCssModule": true,
+    "useCssVars": true,
+    "useSlots": true,
+    "watch": true,
+    "watchEffect": true,
+    "watchPostEffect": true,
+    "watchSyncEffect": true
+  }
+}

+ 1 - 0
playground/vue/components.d.ts

@@ -19,6 +19,7 @@ declare module 'vue' {
     Overlay: typeof import('./src/components/Overlay.vue')['default']
     OverlayInfo: typeof import('./src/components/OverlayInfo.vue')['default']
     PbrSphere: typeof import('./src/components/PbrSphere.vue')['default']
+    ProvideBridge: typeof import('./src/components/ProvideBridge.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     TakeOverLoopExperience: typeof import('./src/components/TakeOverLoopExperience.vue')['default']

+ 4 - 0
playground/vue/src/App.vue

@@ -8,6 +8,10 @@ function setBodyClass(routeName: string) {
   document.body.className = routeName
 }
 watch([route], () => setBodyClass(route.name?.toString() ?? ''))
+provide('v-route', route)
+provide('useTres', {
+  message: `Im not the real useTres, but I can provide you with some data!`,
+})
 </script>
 
 <template>

+ 20 - 0
playground/vue/src/components/ProvideBridge.vue

@@ -0,0 +1,20 @@
+<!-- ProvideBridge.vue -->
+<script setup lang="ts">
+import { provide } from 'vue'
+
+interface Props {
+  keysValues: Record<string, any>
+}
+const props = withDefaults(defineProps<Props>(), {
+  keysValues: () => ({}),
+})
+for (const [key, value] of Object.entries(props.keysValues)) {
+  provide(key, value)
+}
+</script>
+
+<template>
+  <TresGroup>
+    <slot></slot>
+  </TresGroup>
+</template>

+ 1 - 1
playground/vue/src/pages/basic/index.vue

@@ -55,7 +55,7 @@ const toonTealMaterial = new MeshToonMaterial({
     v-bind="state"
   >
     <TresPerspectiveCamera
-      :position="[0, 8, 8]"
+      :position="{ x: 0, y: 8, z: 8 }"
       :fov="45"
       :near="0.1"
       :far="1000"

+ 15 - 0
playground/vue/src/pages/issues/732/ProvideInjectExperience.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import { TresCanvas } from '@tresjs/core'
+import SubComponentWithInject from './SubComponentWithInject.vue'
+
+provide('test', '✅ Precedence is correct')
+</script>
+
+<template>
+  <TresCanvas clear-color="#82DBC5">
+    <TresPerspectiveCamera :position="[11, 11, 11]" :look-at="[0, 0, 0]" />
+    <SubComponentWithInject />
+    <TresGridHelper />
+    <TresAmbientLight :intensity="1" />
+  </TresCanvas>
+</template>

+ 24 - 0
playground/vue/src/pages/issues/732/SubComponentWithInject.vue

@@ -0,0 +1,24 @@
+<!-- eslint-disable no-console -->
+<script setup lang="ts">
+import { inject, watchEffect } from 'vue'
+
+const awiwi = inject('awiwi')
+const bululu = inject('bululu')
+const vRoute = inject('v-route')
+console.log(inject('test'))
+
+watchEffect(() => console.log('tres:awiwi', awiwi?.a))
+
+watchEffect(() => console.log('tres:bululu', bululu?.value))
+
+watchEffect(() => console.log('tres:v-route', vRoute))
+
+watchEffect(() => console.log('tres:useTres', inject('useTres')))
+</script>
+
+<template>
+  <TresMesh>
+    <TresBoxGeometry :args="[1, 1, 1]" />
+    <TresMeshNormalMaterial />
+  </TresMesh>
+</template>

+ 20 - 0
playground/vue/src/pages/issues/732/SubVueComponentWithInject.vue

@@ -0,0 +1,20 @@
+<script setup lang="ts">
+import { inject, watchEffect } from 'vue'
+
+const awiwi = inject('awiwi')
+const bululu = inject('bululu')
+// eslint-disable-next-line no-console
+watchEffect(() => console.log('vue:awiwi', awiwi?.a))
+// eslint-disable-next-line no-console
+watchEffect(() => console.log('vue:bululu', bululu?.value))
+// eslint-disable-next-line no-console
+watchEffect(() => console.log('vue:v-route', inject('v-route')))
+// eslint-disable-next-line no-console
+watchEffect(() => console.log('vue:useTres', inject('useTres')))
+</script>
+
+<template>
+  <p>awiwi: {{ awiwi }}</p>
+  <p>bululu: {{ bululu }}</p>
+  <p>v-route: {{ inject('v-route') }}</p>
+</template>

+ 24 - 0
playground/vue/src/pages/issues/732/index.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import { provide, reactive } from 'vue'
+
+import ProvideInjectExperience from './ProvideInjectExperience.vue'
+import SubVueComponentWithInject from './SubVueComponentWithInject.vue'
+
+const obj = reactive({
+  a: 1,
+})
+const bululu = ref(1)
+provide('awiwi', obj)
+provide('bululu', bululu)
+provide('test', '❌ Precendence is incorrect')
+function onClick() {
+  obj.a++
+  bululu.value++
+}
+</script>
+
+<template>
+  <button @click="onClick">Click me </button>
+  <SubVueComponentWithInject />
+  <ProvideInjectExperience />
+</template>

+ 5 - 1
playground/vue/src/router/routes/issues.ts

@@ -24,5 +24,9 @@ export const issuesRoutes = [
     name: '#796: unmounted',
     component: () => import('../../pages/issues/796/index.vue'),
   },
-
+  {
+    path: '/issues/732',
+    name: '#732: provide/inject',
+    component: () => import('../../pages/issues/732/index.vue'),
+  },
 ]

+ 32 - 2
src/components/TresCanvas.vue

@@ -54,6 +54,9 @@ export interface TresCanvasProps
   camera?: TresCamera
   preset?: RendererPresetsType
   windowSize?: boolean
+
+  // Misc opt-out flags
+  enableProvideBridge?: boolean
 }
 
 const props = withDefaults(defineProps<TresCanvasProps>(), {
@@ -68,6 +71,7 @@ const props = withDefaults(defineProps<TresCanvasProps>(), {
   logarithmicDepthBuffer: undefined,
   failIfMajorPerformanceCaveat: undefined,
   renderMode: 'always',
+  enableProvideBridge: true,
 })
 
 // Define emits for Pointer events, pass `emit` into useTresEventManager so we can emit events off of TresCanvas
@@ -104,14 +108,40 @@ const canvas = ref<HTMLCanvasElement>()
 */
 const scene = shallowRef<TresScene | Scene>(new Scene())
 
-const instance = getCurrentInstance()?.appContext.app
+const instance = getCurrentInstance()
 extend(THREE)
 
 const createInternalComponent = (context: TresContext, empty = false) =>
   defineComponent({
     setup() {
       const ctx = getCurrentInstance()?.appContext
-      if (ctx) { ctx.app = instance as App }
+      if (ctx) { ctx.app = instance?.appContext.app as App }
+      const provides = {}
+
+      // Helper function to recursively merge provides from parents
+      function mergeProvides(currentInstance: any) {
+        if (!currentInstance) { return }
+
+        // Recursively process the parent instance
+        if (currentInstance.parent) {
+          mergeProvides(currentInstance.parent)
+        }
+        // Extract provides from the current instance and merge them
+        if (currentInstance.provides) {
+          Object.assign(provides, currentInstance.provides)
+        }
+      }
+
+      // Start the recursion from the initial instance
+      if (instance?.parent && props.enableProvideBridge) {
+        mergeProvides(instance.parent)
+
+        Object.entries(provides)
+          .forEach(([key, value]) => {
+            provide(key, value)
+          })
+      }
+
       provide('useTres', context)
       provide('extend', extend)