11import { DOCUMENT } from '@angular/common' ;
22import {
33 DebugNode ,
4+ ElementRef ,
45 Injectable ,
56 Renderer2 ,
67 RendererFactory2 ,
78 RendererType2 ,
9+ Type ,
810 inject ,
911 makeEnvironmentProviders ,
1012 untracked ,
1113} from '@angular/core' ;
14+ import { Object3D } from 'three' ;
1215import { NgtArgs } from '../directives/args' ;
16+ import { NgtParent } from '../directives/parent' ;
1317import { getLocalState , prepare } from '../instance' ;
1418import { NGT_STORE , injectStore , provideStore } from '../store' ;
1519import { NgtAnyRecord , NgtLocalState , NgtState } from '../types' ;
@@ -24,6 +28,7 @@ import {
2428 ROUTED_SCENE ,
2529 SPECIAL_DOM_TAG ,
2630 SPECIAL_INTERNAL_ADD_COMMENT ,
31+ SPECIAL_INTERNAL_SET_PARENT_COMMENT ,
2732 SPECIAL_PROPERTIES ,
2833} from './constants' ;
2934import {
@@ -87,6 +92,7 @@ export class NgtRendererFactory implements RendererFactory2 {
8792
8893export class NgtRenderer implements Renderer2 {
8994 private argsCommentNodes : Array < NgtRendererNode > = [ ] ;
95+ private parentCommentNodes : Array < NgtRendererNode > = [ ] ;
9096
9197 constructor (
9298 private delegate : Renderer2 ,
@@ -130,17 +136,31 @@ export class NgtRenderer implements Renderer2 {
130136 ) ;
131137 }
132138
133- const [ injectedArgs ] = [ this . getNgtArgs ( ) ?. value || [ ] ] ;
139+ const [ injectedArgs , injectedParent ] = [
140+ this . getNgtDirective ( NgtArgs , this . argsCommentNodes ) ?. value || [ ] ,
141+ this . getNgtDirective ( NgtParent , this . parentCommentNodes ) ?. value ,
142+ ] ;
134143
135144 if ( name === SPECIAL_DOM_TAG . NGT_PRIMITIVE ) {
136145 if ( ! injectedArgs [ 0 ] ) throw new Error ( `[NGT] ngt-primitive without args is invalid` ) ;
137146 const object = injectedArgs [ 0 ] ;
138- const localState = getLocalState ( object ) ;
147+ let localState = getLocalState ( object ) ;
139148 if ( ! localState ) {
140149 // NOTE: if an object isn't already "prepared", we prepare it
141150 prepare ( object , { store : this . rootStore , primitive : true } ) ;
151+ localState = getLocalState ( object ) ;
142152 }
143- return createNode ( 'three' , object , this . document ) ;
153+
154+ const primitiveNode = createNode ( 'three' , object , this . document ) ;
155+
156+ if ( injectedParent ) {
157+ const resolvedParent = this . getParentFromNgtParent ( injectedParent , this . rootStore ) ;
158+ if ( resolvedParent ) {
159+ primitiveNode . __ngt_renderer__ [ NgtRendererClassId . parent ] = resolvedParent as unknown as NgtRendererNode ;
160+ }
161+ }
162+
163+ return primitiveNode ;
144164 }
145165
146166 const threeName = kebabToPascal ( name . startsWith ( 'ngt-' ) ? name . slice ( 4 ) : name ) ;
@@ -159,6 +179,13 @@ export class NgtRenderer implements Renderer2 {
159179 localState . attach = [ 'material' ] ;
160180 }
161181
182+ if ( injectedParent ) {
183+ const resolvedParent = this . getParentFromNgtParent ( injectedParent , this . rootStore ) ;
184+ if ( resolvedParent ) {
185+ node . __ngt_renderer__ [ NgtRendererClassId . parent ] = resolvedParent as unknown as NgtRendererNode ;
186+ }
187+ }
188+
162189 return node ;
163190 }
164191
@@ -167,19 +194,28 @@ export class NgtRenderer implements Renderer2 {
167194
168195 createComment ( value : string ) {
169196 const comment = this . delegate . createComment ( value ) ;
197+ const commentNode = createNode ( 'comment' , comment , this . document ) ;
170198
171199 // NOTE: we attach an arrow function to the Comment node
172200 // In our directives, we can call this function to then start tracking the RendererNode
173201 // this is done to limit the amount of Nodes we need to process for getCreationState
174- comment [ SPECIAL_INTERNAL_ADD_COMMENT ] = ( node : NgtRendererNode | 'args' ) => {
202+ comment [ SPECIAL_INTERNAL_ADD_COMMENT ] = ( node : NgtRendererNode | 'args' | 'parent' ) => {
175203 if ( node === 'args' ) {
176204 this . argsCommentNodes . push ( comment ) ;
205+ } else if ( node === 'parent' ) {
206+ this . parentCommentNodes . push ( comment ) ;
207+ comment [ SPECIAL_INTERNAL_SET_PARENT_COMMENT ] = ( ngtParent : Object3D | ElementRef < Object3D > | string ) => {
208+ commentNode . __ngt_renderer__ [ NgtRendererClassId . parent ] = this . getParentFromNgtParent (
209+ ngtParent ,
210+ this . rootStore ,
211+ ) as unknown as NgtRendererNode ;
212+ } ;
177213 } else if ( typeof node === 'object' ) {
178214 this . portalCommentsNodes . push ( node ) ;
179215 }
180216 } ;
181217
182- return createNode ( 'comment' , comment , this . document ) ;
218+ return commentNode ;
183219 }
184220
185221 appendChild ( parent : NgtRendererNode , newChild : NgtRendererNode ) : void {
@@ -627,6 +663,97 @@ export class NgtRenderer implements Renderer2 {
627663 return isDanglingThreeChild || ( isParentStillDOM && isChildStillDOM ) || isParentStillDOM ;
628664 }
629665
666+ private getNgtDirective < TDirective > ( directive : Type < TDirective > , commentNodes : Array < NgtRendererNode > ) {
667+ let directiveInstance : TDirective | undefined ;
668+
669+ const destroyed = [ ] ;
670+
671+ let i = commentNodes . length - 1 ;
672+ while ( i >= 0 ) {
673+ const comment = commentNodes [ i ] ;
674+ if ( comment . __ngt_renderer__ [ NgtRendererClassId . destroyed ] ) {
675+ destroyed . push ( i ) ;
676+ i -- ;
677+ continue ;
678+ }
679+ const injector = comment . __ngt_renderer__ [ NgtRendererClassId . debugNodeFactory ] ?.( ) ?. injector ;
680+ if ( ! injector ) {
681+ i -- ;
682+ continue ;
683+ }
684+ const instance = injector . get ( directive , null ) ;
685+ if (
686+ instance &&
687+ typeof instance === 'object' &&
688+ 'validate' in instance &&
689+ typeof instance . validate === 'function' &&
690+ instance . validate ( )
691+ ) {
692+ directiveInstance = instance ;
693+ break ;
694+ }
695+ i -- ;
696+ }
697+ destroyed . forEach ( ( index ) => {
698+ commentNodes . splice ( index , 1 ) ;
699+ } ) ;
700+ return directiveInstance ;
701+ }
702+
703+ private getParentFromNgtParent ( ngtParent : Object3D | ElementRef < Object3D > | string , store : NgtSignalStore < NgtState > ) {
704+ let topMostStore = store ;
705+
706+ while ( topMostStore . snapshot . previousRoot ) {
707+ topMostStore = topMostStore . snapshot . previousRoot ;
708+ }
709+
710+ const scene = topMostStore . snapshot . scene ;
711+
712+ if ( typeof ngtParent === 'string' ) {
713+ return scene . getObjectByName ( ngtParent ) ;
714+ }
715+
716+ if ( 'nativeElement' in ngtParent ) {
717+ return ngtParent . nativeElement ;
718+ }
719+
720+ return ngtParent ;
721+ }
722+
723+ private getNgtParent ( ) {
724+ let directive : NgtParent | undefined ;
725+
726+ const destroyed = [ ] ;
727+
728+ let i = this . parentCommentNodes . length - 1 ;
729+ while ( i >= 0 ) {
730+ const comment = this . parentCommentNodes [ i ] ;
731+ if ( comment . __ngt_renderer__ [ NgtRendererClassId . destroyed ] ) {
732+ destroyed . push ( i ) ;
733+ i -- ;
734+ continue ;
735+ }
736+ const injector = comment . __ngt_renderer__ [ NgtRendererClassId . debugNodeFactory ] ?.( ) ?. injector ;
737+ if ( ! injector ) {
738+ i -- ;
739+ continue ;
740+ }
741+ const instance = injector . get ( NgtParent , null ) ;
742+ if ( instance && instance . validate ( ) ) {
743+ directive = instance ;
744+ break ;
745+ }
746+
747+ i -- ;
748+ }
749+
750+ destroyed . forEach ( ( index ) => {
751+ this . parentCommentNodes . splice ( index , 1 ) ;
752+ } ) ;
753+
754+ return directive ;
755+ }
756+
630757 private getNgtArgs ( ) {
631758 let directive : NgtArgs | undefined ;
632759
0 commit comments