@@ -12,100 +12,32 @@ import {
12
12
afterRenderEffect ,
13
13
booleanAttribute ,
14
14
computed ,
15
- contentChildren ,
16
- forwardRef ,
17
15
inject ,
18
16
input ,
19
17
model ,
20
18
signal ,
21
19
Signal ,
20
+ OnInit ,
21
+ OnDestroy ,
22
22
} from '@angular/core' ;
23
23
import { _IdGenerator } from '@angular/cdk/a11y' ;
24
24
import { Directionality } from '@angular/cdk/bidi' ;
25
25
import { DeferredContent , DeferredContentAware } from '@angular/cdk-experimental/deferred-content' ;
26
26
import { TreeItemPattern , TreePattern } from '../ui-patterns/tree/tree' ;
27
27
28
- /**
29
- * Base class to make a Cdk item groupable.
30
- *
31
- * Also need to add the following to the `@Directive` configuration:
32
- * ```
33
- * providers: [
34
- * { provide: BaseGroupable, useExisting: forwardRef(() => CdkSomeItem) },
35
- * ],
36
- * ```
37
- *
38
- * TODO(ok7sai): Move it to a shared place.
39
- */
40
- export class BaseGroupable {
41
- /** The parent CdkGroup, if any. */
42
- groupParent = inject ( CdkGroup , { optional : true } ) ;
28
+ interface HasElement {
29
+ element : Signal < HTMLElement > ;
43
30
}
44
31
45
32
/**
46
- * Generic container that designates content as a group.
47
- *
48
- * TODO(ok7sai): Move it to a shared place.
33
+ * Sort directives by their document order.
49
34
*/
50
- @Directive ( {
51
- selector : '[cdkGroup]' ,
52
- exportAs : 'cdkGroup' ,
53
- hostDirectives : [
54
- {
55
- directive : DeferredContentAware ,
56
- inputs : [ 'preserveContent' ] ,
57
- } ,
58
- ] ,
59
- host : {
60
- 'class' : 'cdk-group' ,
61
- 'role' : 'group' ,
62
- '[id]' : 'id' ,
63
- '[attr.inert]' : 'visible() ? null : true' ,
64
- } ,
65
- } )
66
- export class CdkGroup < V > {
67
- /** The DeferredContentAware host directive. */
68
- private readonly _deferredContentAware = inject ( DeferredContentAware ) ;
69
-
70
- /** All groupable items that are descendants of the group. */
71
- private readonly _items = contentChildren ( BaseGroupable , { descendants : true } ) ;
72
-
73
- /** Identifier for matching the group owner. */
74
- readonly value = input . required < V > ( ) ;
75
-
76
- /** Whether the group is visible. */
77
- readonly visible = signal ( true ) ;
78
-
79
- /** Unique ID for the group. */
80
- readonly id = inject ( _IdGenerator ) . getId ( 'cdk-group-' ) ;
81
-
82
- /** Child items within this group. */
83
- readonly children = signal < BaseGroupable [ ] > ( [ ] ) ;
84
-
85
- constructor ( ) {
86
- afterRenderEffect ( ( ) => {
87
- this . children . set ( this . _items ( ) . filter ( item => item . groupParent === this ) ) ;
88
- } ) ;
89
-
90
- // Connect the group's hidden state to the DeferredContentAware's visibility.
91
- afterRenderEffect ( ( ) => {
92
- this . _deferredContentAware . contentVisible . set ( this . visible ( ) ) ;
93
- } ) ;
94
- }
35
+ function sortDirectives ( a : HasElement , b : HasElement ) {
36
+ return ( a . element ( ) ?. compareDocumentPosition ( b . element ( ) ) & Node . DOCUMENT_POSITION_PRECEDING ) > 0
37
+ ? 1
38
+ : - 1 ;
95
39
}
96
40
97
- /**
98
- * A structural directive that marks the `ng-template` to be used as the content
99
- * for a `CdkGroup`. This content can be lazily loaded.
100
- *
101
- * TODO(ok7sai): Move it to a shared place.
102
- */
103
- @Directive ( {
104
- selector : 'ng-template[cdkGroupContent]' ,
105
- hostDirectives : [ DeferredContent ] ,
106
- } )
107
- export class CdkGroupContent { }
108
-
109
41
/**
110
42
* Makes an element a tree and manages state (focus, selection, keyboard navigation).
111
43
*/
@@ -126,15 +58,10 @@ export class CdkGroupContent {}
126
58
} )
127
59
export class CdkTree < V > {
128
60
/** All CdkTreeItem instances within this tree. */
129
- private readonly _cdkTreeItems = contentChildren < CdkTreeItem < V > > ( CdkTreeItem , {
130
- descendants : true ,
131
- } ) ;
132
-
133
- /** All TreeItemPattern instances within this tree. */
134
- private readonly _itemPatterns = computed ( ( ) => this . _cdkTreeItems ( ) . map ( item => item . pattern ) ) ;
61
+ private readonly _unorderedItems = signal ( new Set < CdkTreeItem < V > > ( ) ) ;
135
62
136
63
/** All CdkGroup instances within this tree. */
137
- private readonly _cdkGroups = contentChildren ( CdkGroup , { descendants : true } ) ;
64
+ readonly unorderedGroups = signal ( new Set < CdkTreeGroup < V > > ( ) ) ;
138
65
139
66
/** Orientation of the tree. */
140
67
readonly orientation = input < 'vertical' | 'horizontal' > ( 'vertical' ) ;
@@ -169,20 +96,34 @@ export class CdkTree<V> {
169
96
/** The UI pattern for the tree. */
170
97
pattern : TreePattern < V > = new TreePattern < V > ( {
171
98
...this ,
172
- allItems : this . _itemPatterns ,
99
+ allItems : computed ( ( ) =>
100
+ [ ...this . _unorderedItems ( ) ] . sort ( sortDirectives ) . map ( item => item . pattern ) ,
101
+ ) ,
173
102
activeIndex : signal ( 0 ) ,
174
103
} ) ;
175
104
176
- constructor ( ) {
177
- // Binds groups to tree items.
178
- afterRenderEffect ( ( ) => {
179
- const groups = this . _cdkGroups ( ) ;
180
- const treeItems = this . _cdkTreeItems ( ) ;
181
- for ( const group of groups ) {
182
- const treeItem = treeItems . find ( item => item . value ( ) === group . value ( ) ) ;
183
- treeItem ?. group . set ( group ) ;
184
- }
185
- } ) ;
105
+ register ( child : CdkTreeGroup < V > | CdkTreeItem < V > ) {
106
+ if ( child instanceof CdkTreeGroup ) {
107
+ this . unorderedGroups ( ) . add ( child ) ;
108
+ this . unorderedGroups . set ( new Set ( this . unorderedGroups ( ) ) ) ;
109
+ }
110
+
111
+ if ( child instanceof CdkTreeItem ) {
112
+ this . _unorderedItems ( ) . add ( child ) ;
113
+ this . _unorderedItems . set ( new Set ( this . _unorderedItems ( ) ) ) ;
114
+ }
115
+ }
116
+
117
+ deregister ( child : CdkTreeGroup < V > | CdkTreeItem < V > ) {
118
+ if ( child instanceof CdkTreeGroup ) {
119
+ this . unorderedGroups ( ) . delete ( child ) ;
120
+ this . unorderedGroups . set ( new Set ( this . unorderedGroups ( ) ) ) ;
121
+ }
122
+
123
+ if ( child instanceof CdkTreeItem ) {
124
+ this . _unorderedItems ( ) . delete ( child ) ;
125
+ this . _unorderedItems . set ( new Set ( this . _unorderedItems ( ) ) ) ;
126
+ }
186
127
}
187
128
}
188
129
@@ -204,30 +145,32 @@ export class CdkTree<V> {
204
145
'[attr.aria-posinset]' : 'pattern.posinset()' ,
205
146
'[attr.tabindex]' : 'pattern.tabindex()' ,
206
147
} ,
207
- providers : [ { provide : BaseGroupable , useExisting : forwardRef ( ( ) => CdkTreeItem ) } ] ,
208
148
} )
209
- export class CdkTreeItem < V > extends BaseGroupable {
149
+ export class CdkTreeItem < V > implements OnInit , OnDestroy , HasElement {
210
150
/** A reference to the tree item element. */
211
151
private readonly _elementRef = inject ( ElementRef ) ;
212
152
213
- /** The host native element. */
214
- private readonly _element = computed ( ( ) => this . _elementRef . nativeElement ) ;
215
-
216
153
/** A unique identifier for the tree item. */
217
154
private readonly _id = inject ( _IdGenerator ) . getId ( 'cdk-tree-item-' ) ;
218
155
219
156
/** The top level CdkTree. */
220
- private readonly _cdkTree = inject ( CdkTree < V > , { optional : true } ) ;
157
+ private readonly _tree = inject ( CdkTree < V > ) ;
221
158
222
159
/** The parent CdkTreeItem. */
223
- private readonly _cdkTreeItem = inject ( CdkTreeItem < V > , { optional : true , skipSelf : true } ) ;
160
+ private readonly _treeItem = inject ( CdkTreeItem < V > , { optional : true , skipSelf : true } ) ;
161
+
162
+ /** The parent CdkGroup, if any. */
163
+ private readonly _parentGroup = inject ( CdkTreeGroup < V > , { optional : true } ) ;
224
164
225
165
/** The top lavel TreePattern. */
226
- private readonly _treePattern = computed ( ( ) => this . _cdkTree ?. pattern ) ;
166
+ private readonly _treePattern = computed ( ( ) => this . _tree ?. pattern ) ;
227
167
228
168
/** The parent TreeItemPattern. */
229
169
private readonly _parentPattern : Signal < TreeItemPattern < V > | TreePattern < V > | undefined > =
230
- computed ( ( ) => this . _cdkTreeItem ?. pattern ?? this . _treePattern ( ) ) ;
170
+ computed ( ( ) => this . _treeItem ?. pattern ?? this . _treePattern ( ) ) ;
171
+
172
+ /** The host native element. */
173
+ readonly element = computed ( ( ) => this . _elementRef . nativeElement ) ;
231
174
232
175
/** The value of the tree item. */
233
176
readonly value = input . required < V > ( ) ;
@@ -239,16 +182,15 @@ export class CdkTreeItem<V> extends BaseGroupable {
239
182
readonly label = input < string > ( ) ;
240
183
241
184
/** Search term for typeahead. */
242
- readonly searchTerm = computed ( ( ) => this . label ( ) ?? this . _element ( ) . textContent ) ;
185
+ readonly searchTerm = computed ( ( ) => this . label ( ) ?? this . element ( ) . textContent ) ;
243
186
244
187
/** Manual group assignment. */
245
- readonly group = signal < CdkGroup < V > | undefined > ( undefined ) ;
188
+ readonly group = signal < CdkTreeGroup < V > | undefined > ( undefined ) ;
246
189
247
190
/** The UI pattern for this item. */
248
191
pattern : TreeItemPattern < V > = new TreeItemPattern < V > ( {
249
192
...this ,
250
193
id : ( ) => this . _id ,
251
- element : this . _element ,
252
194
tree : this . _treePattern ,
253
195
parent : this . _parentPattern ,
254
196
children : computed (
@@ -261,11 +203,109 @@ export class CdkTreeItem<V> extends BaseGroupable {
261
203
} ) ;
262
204
263
205
constructor ( ) {
264
- super ( ) ;
206
+ afterRenderEffect ( ( ) => {
207
+ const group = [ ...this . _tree . unorderedGroups ( ) ] . find ( group => group . value ( ) === this . value ( ) ) ;
208
+ if ( group ) {
209
+ this . group . set ( group ) ;
210
+ }
211
+ } ) ;
265
212
266
213
// Updates the visibility of the owned group.
267
214
afterRenderEffect ( ( ) => {
268
215
this . group ( ) ?. visible . set ( this . pattern . expanded ( ) ) ;
269
216
} ) ;
270
217
}
218
+
219
+ ngOnInit ( ) {
220
+ this . _tree . register ( this ) ;
221
+ this . _parentGroup ?. register ( this ) ;
222
+ }
223
+
224
+ ngOnDestroy ( ) {
225
+ this . _tree . deregister ( this ) ;
226
+ this . _parentGroup ?. deregister ( this ) ;
227
+ }
271
228
}
229
+
230
+ /**
231
+ * Container that designates content as a group.
232
+ */
233
+ @Directive ( {
234
+ selector : '[cdkTreeGroup]' ,
235
+ exportAs : 'cdkTreeGroup' ,
236
+ hostDirectives : [
237
+ {
238
+ directive : DeferredContentAware ,
239
+ inputs : [ 'preserveContent' ] ,
240
+ } ,
241
+ ] ,
242
+ host : {
243
+ 'class' : 'cdk-tree-group' ,
244
+ 'role' : 'group' ,
245
+ '[id]' : 'id' ,
246
+ '[attr.inert]' : 'visible() ? null : true' ,
247
+ } ,
248
+ } )
249
+ export class CdkTreeGroup < V > implements OnInit , OnDestroy , HasElement {
250
+ /** A reference to the group element. */
251
+ private readonly _elementRef = inject ( ElementRef ) ;
252
+
253
+ /** The DeferredContentAware host directive. */
254
+ private readonly _deferredContentAware = inject ( DeferredContentAware ) ;
255
+
256
+ /** The top level CdkTree. */
257
+ private readonly _tree = inject ( CdkTree < V > ) ;
258
+
259
+ /** All groupable items that are descendants of the group. */
260
+ private readonly _unorderedItems = signal ( new Set < CdkTreeItem < V > > ( ) ) ;
261
+
262
+ /** The host native element. */
263
+ readonly element = computed ( ( ) => this . _elementRef . nativeElement ) ;
264
+
265
+ /** Unique ID for the group. */
266
+ readonly id = inject ( _IdGenerator ) . getId ( 'cdk-tree-group-' ) ;
267
+
268
+ /** Whether the group is visible. */
269
+ readonly visible = signal ( true ) ;
270
+
271
+ /** Child items within this group. */
272
+ readonly children = computed ( ( ) => [ ...this . _unorderedItems ( ) ] . sort ( sortDirectives ) ) ;
273
+
274
+ /** Identifier for matching the group owner. */
275
+ readonly value = input . required < V > ( ) ;
276
+
277
+ constructor ( ) {
278
+ // Connect the group's hidden state to the DeferredContentAware's visibility.
279
+ afterRenderEffect ( ( ) => {
280
+ this . _deferredContentAware . contentVisible . set ( this . visible ( ) ) ;
281
+ } ) ;
282
+ }
283
+
284
+ ngOnInit ( ) {
285
+ this . _tree . register ( this ) ;
286
+ }
287
+
288
+ ngOnDestroy ( ) {
289
+ this . _tree . deregister ( this ) ;
290
+ }
291
+
292
+ register ( child : CdkTreeItem < V > ) {
293
+ this . _unorderedItems ( ) . add ( child ) ;
294
+ this . _unorderedItems . set ( new Set ( this . _unorderedItems ( ) ) ) ;
295
+ }
296
+
297
+ deregister ( child : CdkTreeItem < V > ) {
298
+ this . _unorderedItems ( ) . delete ( child ) ;
299
+ this . _unorderedItems . set ( new Set ( this . _unorderedItems ( ) ) ) ;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * A structural directive that marks the `ng-template` to be used as the content
305
+ * for a `CdkTreeGroup`. This content can be lazily loaded.
306
+ */
307
+ @Directive ( {
308
+ selector : 'ng-template[cdkTreeGroupContent]' ,
309
+ hostDirectives : [ DeferredContent ] ,
310
+ } )
311
+ export class CdkTreeGroupContent { }
0 commit comments