@@ -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,33 @@ 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
- private readonly _parentPattern : Signal < TreeItemPattern < V > | TreePattern < V > | undefined > =
230
- computed ( ( ) => this . _cdkTreeItem ?. pattern ?? this . _treePattern ( ) ) ;
169
+ private readonly _parentPattern : Signal < TreeItemPattern < V > | TreePattern < V > > = computed (
170
+ ( ) => this . _treeItem ?. pattern ?? this . _treePattern ( ) ,
171
+ ) ;
172
+
173
+ /** The host native element. */
174
+ readonly element = computed ( ( ) => this . _elementRef . nativeElement ) ;
231
175
232
176
/** The value of the tree item. */
233
177
readonly value = input . required < V > ( ) ;
@@ -239,16 +183,15 @@ export class CdkTreeItem<V> extends BaseGroupable {
239
183
readonly label = input < string > ( ) ;
240
184
241
185
/** Search term for typeahead. */
242
- readonly searchTerm = computed ( ( ) => this . label ( ) ?? this . _element ( ) . textContent ) ;
186
+ readonly searchTerm = computed ( ( ) => this . label ( ) ?? this . element ( ) . textContent ) ;
243
187
244
188
/** Manual group assignment. */
245
- readonly group = signal < CdkGroup < V > | undefined > ( undefined ) ;
189
+ readonly group = signal < CdkTreeGroup < V > | undefined > ( undefined ) ;
246
190
247
191
/** The UI pattern for this item. */
248
192
pattern : TreeItemPattern < V > = new TreeItemPattern < V > ( {
249
193
...this ,
250
194
id : ( ) => this . _id ,
251
- element : this . _element ,
252
195
tree : this . _treePattern ,
253
196
parent : this . _parentPattern ,
254
197
children : computed (
@@ -261,11 +204,109 @@ export class CdkTreeItem<V> extends BaseGroupable {
261
204
} ) ;
262
205
263
206
constructor ( ) {
264
- super ( ) ;
207
+ afterRenderEffect ( ( ) => {
208
+ const group = [ ...this . _tree . unorderedGroups ( ) ] . find ( group => group . value ( ) === this . value ( ) ) ;
209
+ if ( group ) {
210
+ this . group . set ( group ) ;
211
+ }
212
+ } ) ;
265
213
266
214
// Updates the visibility of the owned group.
267
215
afterRenderEffect ( ( ) => {
268
216
this . group ( ) ?. visible . set ( this . pattern . expanded ( ) ) ;
269
217
} ) ;
270
218
}
219
+
220
+ ngOnInit ( ) {
221
+ this . _tree . register ( this ) ;
222
+ this . _parentGroup ?. register ( this ) ;
223
+ }
224
+
225
+ ngOnDestroy ( ) {
226
+ this . _tree . deregister ( this ) ;
227
+ this . _parentGroup ?. deregister ( this ) ;
228
+ }
271
229
}
230
+
231
+ /**
232
+ * Container that designates content as a group.
233
+ */
234
+ @Directive ( {
235
+ selector : '[cdkTreeGroup]' ,
236
+ exportAs : 'cdkTreeGroup' ,
237
+ hostDirectives : [
238
+ {
239
+ directive : DeferredContentAware ,
240
+ inputs : [ 'preserveContent' ] ,
241
+ } ,
242
+ ] ,
243
+ host : {
244
+ 'class' : 'cdk-tree-group' ,
245
+ 'role' : 'group' ,
246
+ '[id]' : 'id' ,
247
+ '[attr.inert]' : 'visible() ? null : true' ,
248
+ } ,
249
+ } )
250
+ export class CdkTreeGroup < V > implements OnInit , OnDestroy , HasElement {
251
+ /** A reference to the group element. */
252
+ private readonly _elementRef = inject ( ElementRef ) ;
253
+
254
+ /** The DeferredContentAware host directive. */
255
+ private readonly _deferredContentAware = inject ( DeferredContentAware ) ;
256
+
257
+ /** The top level CdkTree. */
258
+ private readonly _tree = inject ( CdkTree < V > ) ;
259
+
260
+ /** All groupable items that are descendants of the group. */
261
+ private readonly _unorderedItems = signal ( new Set < CdkTreeItem < V > > ( ) ) ;
262
+
263
+ /** The host native element. */
264
+ readonly element = computed ( ( ) => this . _elementRef . nativeElement ) ;
265
+
266
+ /** Unique ID for the group. */
267
+ readonly id = inject ( _IdGenerator ) . getId ( 'cdk-tree-group-' ) ;
268
+
269
+ /** Whether the group is visible. */
270
+ readonly visible = signal ( true ) ;
271
+
272
+ /** Child items within this group. */
273
+ readonly children = computed ( ( ) => [ ...this . _unorderedItems ( ) ] . sort ( sortDirectives ) ) ;
274
+
275
+ /** Identifier for matching the group owner. */
276
+ readonly value = input . required < V > ( ) ;
277
+
278
+ constructor ( ) {
279
+ // Connect the group's hidden state to the DeferredContentAware's visibility.
280
+ afterRenderEffect ( ( ) => {
281
+ this . _deferredContentAware . contentVisible . set ( this . visible ( ) ) ;
282
+ } ) ;
283
+ }
284
+
285
+ ngOnInit ( ) {
286
+ this . _tree . register ( this ) ;
287
+ }
288
+
289
+ ngOnDestroy ( ) {
290
+ this . _tree . deregister ( this ) ;
291
+ }
292
+
293
+ register ( child : CdkTreeItem < V > ) {
294
+ this . _unorderedItems ( ) . add ( child ) ;
295
+ this . _unorderedItems . set ( new Set ( this . _unorderedItems ( ) ) ) ;
296
+ }
297
+
298
+ deregister ( child : CdkTreeItem < V > ) {
299
+ this . _unorderedItems ( ) . delete ( child ) ;
300
+ this . _unorderedItems . set ( new Set ( this . _unorderedItems ( ) ) ) ;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * A structural directive that marks the `ng-template` to be used as the content
306
+ * for a `CdkTreeGroup`. This content can be lazily loaded.
307
+ */
308
+ @Directive ( {
309
+ selector : 'ng-template[cdkTreeGroupContent]' ,
310
+ hostDirectives : [ DeferredContent ] ,
311
+ } )
312
+ export class CdkTreeGroupContent { }
0 commit comments