1
1
import { DOCUMENT } from '@angular/common' ;
2
2
import {
3
3
DebugNode ,
4
+ ElementRef ,
4
5
Injectable ,
5
6
Renderer2 ,
6
7
RendererFactory2 ,
7
8
RendererType2 ,
9
+ Type ,
8
10
inject ,
9
11
makeEnvironmentProviders ,
10
12
untracked ,
11
13
} from '@angular/core' ;
14
+ import { Object3D } from 'three' ;
12
15
import { NgtArgs } from '../directives/args' ;
16
+ import { NgtParent } from '../directives/parent' ;
13
17
import { getLocalState , prepare } from '../instance' ;
14
18
import { NGT_STORE , injectStore , provideStore } from '../store' ;
15
19
import { NgtAnyRecord , NgtLocalState , NgtState } from '../types' ;
@@ -24,6 +28,7 @@ import {
24
28
ROUTED_SCENE ,
25
29
SPECIAL_DOM_TAG ,
26
30
SPECIAL_INTERNAL_ADD_COMMENT ,
31
+ SPECIAL_INTERNAL_SET_PARENT_COMMENT ,
27
32
SPECIAL_PROPERTIES ,
28
33
} from './constants' ;
29
34
import {
@@ -87,6 +92,7 @@ export class NgtRendererFactory implements RendererFactory2 {
87
92
88
93
export class NgtRenderer implements Renderer2 {
89
94
private argsCommentNodes : Array < NgtRendererNode > = [ ] ;
95
+ private parentCommentNodes : Array < NgtRendererNode > = [ ] ;
90
96
91
97
constructor (
92
98
private delegate : Renderer2 ,
@@ -130,17 +136,31 @@ export class NgtRenderer implements Renderer2 {
130
136
) ;
131
137
}
132
138
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
+ ] ;
134
143
135
144
if ( name === SPECIAL_DOM_TAG . NGT_PRIMITIVE ) {
136
145
if ( ! injectedArgs [ 0 ] ) throw new Error ( `[NGT] ngt-primitive without args is invalid` ) ;
137
146
const object = injectedArgs [ 0 ] ;
138
- const localState = getLocalState ( object ) ;
147
+ let localState = getLocalState ( object ) ;
139
148
if ( ! localState ) {
140
149
// NOTE: if an object isn't already "prepared", we prepare it
141
150
prepare ( object , { store : this . rootStore , primitive : true } ) ;
151
+ localState = getLocalState ( object ) ;
142
152
}
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 ;
144
164
}
145
165
146
166
const threeName = kebabToPascal ( name . startsWith ( 'ngt-' ) ? name . slice ( 4 ) : name ) ;
@@ -159,6 +179,13 @@ export class NgtRenderer implements Renderer2 {
159
179
localState . attach = [ 'material' ] ;
160
180
}
161
181
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
+
162
189
return node ;
163
190
}
164
191
@@ -167,19 +194,28 @@ export class NgtRenderer implements Renderer2 {
167
194
168
195
createComment ( value : string ) {
169
196
const comment = this . delegate . createComment ( value ) ;
197
+ const commentNode = createNode ( 'comment' , comment , this . document ) ;
170
198
171
199
// NOTE: we attach an arrow function to the Comment node
172
200
// In our directives, we can call this function to then start tracking the RendererNode
173
201
// 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' ) => {
175
203
if ( node === 'args' ) {
176
204
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
+ } ;
177
213
} else if ( typeof node === 'object' ) {
178
214
this . portalCommentsNodes . push ( node ) ;
179
215
}
180
216
} ;
181
217
182
- return createNode ( 'comment' , comment , this . document ) ;
218
+ return commentNode ;
183
219
}
184
220
185
221
appendChild ( parent : NgtRendererNode , newChild : NgtRendererNode ) : void {
@@ -627,6 +663,97 @@ export class NgtRenderer implements Renderer2 {
627
663
return isDanglingThreeChild || ( isParentStillDOM && isChildStillDOM ) || isParentStillDOM ;
628
664
}
629
665
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
+
630
757
private getNgtArgs ( ) {
631
758
let directive : NgtArgs | undefined ;
632
759
0 commit comments