tree.mjs 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795
  1. import { S as SelectionModel } from './selection-model-CeeHVIcP.mjs';
  2. import { isObservable, Subject, BehaviorSubject, of, combineLatest, EMPTY, concat } from 'rxjs';
  3. import { take, filter, takeUntil, startWith, tap, switchMap, map, reduce, concatMap, distinctUntilChanged } from 'rxjs/operators';
  4. import * as i0 from '@angular/core';
  5. import { InjectionToken, inject, ViewContainerRef, Directive, TemplateRef, IterableDiffers, ChangeDetectorRef, ElementRef, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, ViewChild, ContentChildren, EventEmitter, booleanAttribute, Output, numberAttribute, NgModule } from '@angular/core';
  6. import { T as TREE_KEY_MANAGER } from './tree-key-manager-KnCoIkIC.mjs';
  7. import { D as Directionality } from './directionality-CBXD4hga.mjs';
  8. import { i as isDataSource } from './data-source-D34wiQZj.mjs';
  9. import { coerceObservable } from './coercion/private.mjs';
  10. import './typeahead-9ZW4Dtsf.mjs';
  11. import './keycodes-CpHkExLC.mjs';
  12. import '@angular/common';
  13. /**
  14. * Base tree control. It has basic toggle/expand/collapse operations on a single data node.
  15. *
  16. * @deprecated Use one of levelAccessor or childrenAccessor. To be removed in a future version.
  17. * @breaking-change 21.0.0
  18. */
  19. class BaseTreeControl {
  20. /** Saved data node for `expandAll` action. */
  21. dataNodes;
  22. /** A selection model with multi-selection to track expansion status. */
  23. expansionModel = new SelectionModel(true);
  24. /**
  25. * Returns the identifier by which a dataNode should be tracked, should its
  26. * reference change.
  27. *
  28. * Similar to trackBy for *ngFor
  29. */
  30. trackBy;
  31. /** Get depth of a given data node, return the level number. This is for flat tree node. */
  32. getLevel;
  33. /**
  34. * Whether the data node is expandable. Returns true if expandable.
  35. * This is for flat tree node.
  36. */
  37. isExpandable;
  38. /** Gets a stream that emits whenever the given data node's children change. */
  39. getChildren;
  40. /** Toggles one single data node's expanded/collapsed state. */
  41. toggle(dataNode) {
  42. this.expansionModel.toggle(this._trackByValue(dataNode));
  43. }
  44. /** Expands one single data node. */
  45. expand(dataNode) {
  46. this.expansionModel.select(this._trackByValue(dataNode));
  47. }
  48. /** Collapses one single data node. */
  49. collapse(dataNode) {
  50. this.expansionModel.deselect(this._trackByValue(dataNode));
  51. }
  52. /** Whether a given data node is expanded or not. Returns true if the data node is expanded. */
  53. isExpanded(dataNode) {
  54. return this.expansionModel.isSelected(this._trackByValue(dataNode));
  55. }
  56. /** Toggles a subtree rooted at `node` recursively. */
  57. toggleDescendants(dataNode) {
  58. this.expansionModel.isSelected(this._trackByValue(dataNode))
  59. ? this.collapseDescendants(dataNode)
  60. : this.expandDescendants(dataNode);
  61. }
  62. /** Collapse all dataNodes in the tree. */
  63. collapseAll() {
  64. this.expansionModel.clear();
  65. }
  66. /** Expands a subtree rooted at given data node recursively. */
  67. expandDescendants(dataNode) {
  68. let toBeProcessed = [dataNode];
  69. toBeProcessed.push(...this.getDescendants(dataNode));
  70. this.expansionModel.select(...toBeProcessed.map(value => this._trackByValue(value)));
  71. }
  72. /** Collapses a subtree rooted at given data node recursively. */
  73. collapseDescendants(dataNode) {
  74. let toBeProcessed = [dataNode];
  75. toBeProcessed.push(...this.getDescendants(dataNode));
  76. this.expansionModel.deselect(...toBeProcessed.map(value => this._trackByValue(value)));
  77. }
  78. _trackByValue(value) {
  79. return this.trackBy ? this.trackBy(value) : value;
  80. }
  81. }
  82. /**
  83. * Flat tree control. Able to expand/collapse a subtree recursively for flattened tree.
  84. *
  85. * @deprecated Use one of levelAccessor or childrenAccessor instead. To be removed in a future
  86. * version.
  87. * @breaking-change 21.0.0
  88. */
  89. class FlatTreeControl extends BaseTreeControl {
  90. getLevel;
  91. isExpandable;
  92. options;
  93. /** Construct with flat tree data node functions getLevel and isExpandable. */
  94. constructor(getLevel, isExpandable, options) {
  95. super();
  96. this.getLevel = getLevel;
  97. this.isExpandable = isExpandable;
  98. this.options = options;
  99. if (this.options) {
  100. this.trackBy = this.options.trackBy;
  101. }
  102. }
  103. /**
  104. * Gets a list of the data node's subtree of descendent data nodes.
  105. *
  106. * To make this working, the `dataNodes` of the TreeControl must be flattened tree nodes
  107. * with correct levels.
  108. */
  109. getDescendants(dataNode) {
  110. const startIndex = this.dataNodes.indexOf(dataNode);
  111. const results = [];
  112. // Goes through flattened tree nodes in the `dataNodes` array, and get all descendants.
  113. // The level of descendants of a tree node must be greater than the level of the given
  114. // tree node.
  115. // If we reach a node whose level is equal to the level of the tree node, we hit a sibling.
  116. // If we reach a node whose level is greater than the level of the tree node, we hit a
  117. // sibling of an ancestor.
  118. for (let i = startIndex + 1; i < this.dataNodes.length && this.getLevel(dataNode) < this.getLevel(this.dataNodes[i]); i++) {
  119. results.push(this.dataNodes[i]);
  120. }
  121. return results;
  122. }
  123. /**
  124. * Expands all data nodes in the tree.
  125. *
  126. * To make this working, the `dataNodes` variable of the TreeControl must be set to all flattened
  127. * data nodes of the tree.
  128. */
  129. expandAll() {
  130. this.expansionModel.select(...this.dataNodes.map(node => this._trackByValue(node)));
  131. }
  132. }
  133. /**
  134. * Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type.
  135. *
  136. * @deprecated Use one of levelAccessor or childrenAccessor instead. To be removed in a future
  137. * version.
  138. * @breaking-change 21.0.0
  139. */
  140. class NestedTreeControl extends BaseTreeControl {
  141. getChildren;
  142. options;
  143. /** Construct with nested tree function getChildren. */
  144. constructor(getChildren, options) {
  145. super();
  146. this.getChildren = getChildren;
  147. this.options = options;
  148. if (this.options) {
  149. this.trackBy = this.options.trackBy;
  150. }
  151. if (this.options?.isExpandable) {
  152. this.isExpandable = this.options.isExpandable;
  153. }
  154. }
  155. /**
  156. * Expands all dataNodes in the tree.
  157. *
  158. * To make this working, the `dataNodes` variable of the TreeControl must be set to all root level
  159. * data nodes of the tree.
  160. */
  161. expandAll() {
  162. this.expansionModel.clear();
  163. const allNodes = this.dataNodes.reduce((accumulator, dataNode) => [...accumulator, ...this.getDescendants(dataNode), dataNode], []);
  164. this.expansionModel.select(...allNodes.map(node => this._trackByValue(node)));
  165. }
  166. /** Gets a list of descendant dataNodes of a subtree rooted at given data node recursively. */
  167. getDescendants(dataNode) {
  168. const descendants = [];
  169. this._getDescendants(descendants, dataNode);
  170. // Remove the node itself
  171. return descendants.splice(1);
  172. }
  173. /** A helper function to get descendants recursively. */
  174. _getDescendants(descendants, dataNode) {
  175. descendants.push(dataNode);
  176. const childrenNodes = this.getChildren(dataNode);
  177. if (Array.isArray(childrenNodes)) {
  178. childrenNodes.forEach((child) => this._getDescendants(descendants, child));
  179. }
  180. else if (isObservable(childrenNodes)) {
  181. // TypeScript as of version 3.5 doesn't seem to treat `Boolean` like a function that
  182. // returns a `boolean` specifically in the context of `filter`, so we manually clarify that.
  183. childrenNodes.pipe(take(1), filter(Boolean)).subscribe(children => {
  184. for (const child of children) {
  185. this._getDescendants(descendants, child);
  186. }
  187. });
  188. }
  189. }
  190. }
  191. /**
  192. * Injection token used to provide a `CdkTreeNode` to its outlet.
  193. * Used primarily to avoid circular imports.
  194. * @docs-private
  195. */
  196. const CDK_TREE_NODE_OUTLET_NODE = new InjectionToken('CDK_TREE_NODE_OUTLET_NODE');
  197. /**
  198. * Outlet for nested CdkNode. Put `[cdkTreeNodeOutlet]` on a tag to place children dataNodes
  199. * inside the outlet.
  200. */
  201. class CdkTreeNodeOutlet {
  202. viewContainer = inject(ViewContainerRef);
  203. _node = inject(CDK_TREE_NODE_OUTLET_NODE, { optional: true });
  204. constructor() { }
  205. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodeOutlet, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  206. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkTreeNodeOutlet, isStandalone: true, selector: "[cdkTreeNodeOutlet]", ngImport: i0 });
  207. }
  208. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodeOutlet, decorators: [{
  209. type: Directive,
  210. args: [{
  211. selector: '[cdkTreeNodeOutlet]',
  212. }]
  213. }], ctorParameters: () => [] });
  214. /** Context provided to the tree node component. */
  215. class CdkTreeNodeOutletContext {
  216. /** Data for the node. */
  217. $implicit;
  218. /** Depth of the node. */
  219. level;
  220. /** Index location of the node. */
  221. index;
  222. /** Length of the number of total dataNodes. */
  223. count;
  224. constructor(data) {
  225. this.$implicit = data;
  226. }
  227. }
  228. /**
  229. * Data node definition for the CdkTree.
  230. * Captures the node's template and a when predicate that describes when this node should be used.
  231. */
  232. class CdkTreeNodeDef {
  233. /** @docs-private */
  234. template = inject(TemplateRef);
  235. /**
  236. * Function that should return true if this node template should be used for the provided node
  237. * data and index. If left undefined, this node will be considered the default node template to
  238. * use when no other when functions return true for the data.
  239. * For every node, there must be at least one when function that passes or an undefined to
  240. * default.
  241. */
  242. when;
  243. constructor() { }
  244. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodeDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  245. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkTreeNodeDef, isStandalone: true, selector: "[cdkTreeNodeDef]", inputs: { when: ["cdkTreeNodeDefWhen", "when"] }, ngImport: i0 });
  246. }
  247. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodeDef, decorators: [{
  248. type: Directive,
  249. args: [{
  250. selector: '[cdkTreeNodeDef]',
  251. inputs: [{ name: 'when', alias: 'cdkTreeNodeDefWhen' }],
  252. }]
  253. }], ctorParameters: () => [] });
  254. /**
  255. * Returns an error to be thrown when there is no usable data.
  256. * @docs-private
  257. */
  258. function getTreeNoValidDataSourceError() {
  259. return Error(`A valid data source must be provided.`);
  260. }
  261. /**
  262. * Returns an error to be thrown when there are multiple nodes that are missing a when function.
  263. * @docs-private
  264. */
  265. function getTreeMultipleDefaultNodeDefsError() {
  266. return Error(`There can only be one default row without a when predicate function.`);
  267. }
  268. /**
  269. * Returns an error to be thrown when there are no matching node defs for a particular set of data.
  270. * @docs-private
  271. */
  272. function getTreeMissingMatchingNodeDefError() {
  273. return Error(`Could not find a matching node definition for the provided node data.`);
  274. }
  275. /**
  276. * Returns an error to be thrown when there is no tree control.
  277. * @docs-private
  278. */
  279. function getTreeControlMissingError() {
  280. return Error(`Could not find a tree control, levelAccessor, or childrenAccessor for the tree.`);
  281. }
  282. /**
  283. * Returns an error to be thrown when there are multiple ways of specifying children or level
  284. * provided to the tree.
  285. * @docs-private
  286. */
  287. function getMultipleTreeControlsError() {
  288. return Error(`More than one of tree control, levelAccessor, or childrenAccessor were provided.`);
  289. }
  290. /**
  291. * CDK tree component that connects with a data source to retrieve data of type `T` and renders
  292. * dataNodes with hierarchy. Updates the dataNodes when new data is provided by the data source.
  293. */
  294. class CdkTree {
  295. _differs = inject(IterableDiffers);
  296. _changeDetectorRef = inject(ChangeDetectorRef);
  297. _elementRef = inject(ElementRef);
  298. _dir = inject(Directionality);
  299. /** Subject that emits when the component has been destroyed. */
  300. _onDestroy = new Subject();
  301. /** Differ used to find the changes in the data provided by the data source. */
  302. _dataDiffer;
  303. /** Stores the node definition that does not have a when predicate. */
  304. _defaultNodeDef;
  305. /** Data subscription */
  306. _dataSubscription;
  307. /** Level of nodes */
  308. _levels = new Map();
  309. /** The immediate parents for a node. This is `null` if there is no parent. */
  310. _parents = new Map();
  311. /**
  312. * Nodes grouped into each set, which is a list of nodes displayed together in the DOM.
  313. *
  314. * Lookup key is the parent of a set. Root nodes have key of null.
  315. *
  316. * Values is a 'set' of tree nodes. Each tree node maps to a treeitem element. Sets are in the
  317. * order that it is rendered. Each set maps directly to aria-posinset and aria-setsize attributes.
  318. */
  319. _ariaSets = new Map();
  320. /**
  321. * Provides a stream containing the latest data array to render. Influenced by the tree's
  322. * stream of view window (what dataNodes are currently on screen).
  323. * Data source can be an observable of data array, or a data array to render.
  324. */
  325. get dataSource() {
  326. return this._dataSource;
  327. }
  328. set dataSource(dataSource) {
  329. if (this._dataSource !== dataSource) {
  330. this._switchDataSource(dataSource);
  331. }
  332. }
  333. _dataSource;
  334. /**
  335. * The tree controller
  336. *
  337. * @deprecated Use one of `levelAccessor` or `childrenAccessor` instead. To be removed in a
  338. * future version.
  339. * @breaking-change 21.0.0
  340. */
  341. treeControl;
  342. /**
  343. * Given a data node, determines what tree level the node is at.
  344. *
  345. * One of levelAccessor or childrenAccessor must be specified, not both.
  346. * This is enforced at run-time.
  347. */
  348. levelAccessor;
  349. /**
  350. * Given a data node, determines what the children of that node are.
  351. *
  352. * One of levelAccessor or childrenAccessor must be specified, not both.
  353. * This is enforced at run-time.
  354. */
  355. childrenAccessor;
  356. /**
  357. * Tracking function that will be used to check the differences in data changes. Used similarly
  358. * to `ngFor` `trackBy` function. Optimize node operations by identifying a node based on its data
  359. * relative to the function to know if a node should be added/removed/moved.
  360. * Accepts a function that takes two parameters, `index` and `item`.
  361. */
  362. trackBy;
  363. /**
  364. * Given a data node, determines the key by which we determine whether or not this node is expanded.
  365. */
  366. expansionKey;
  367. // Outlets within the tree's template where the dataNodes will be inserted.
  368. _nodeOutlet;
  369. /** The tree node template for the tree */
  370. _nodeDefs;
  371. // TODO(tinayuangao): Setup a listener for scrolling, emit the calculated view to viewChange.
  372. // Remove the MAX_VALUE in viewChange
  373. /**
  374. * Stream containing the latest information on what rows are being displayed on screen.
  375. * Can be used by the data source to as a heuristic of what data should be provided.
  376. */
  377. viewChange = new BehaviorSubject({
  378. start: 0,
  379. end: Number.MAX_VALUE,
  380. });
  381. /** Keep track of which nodes are expanded. */
  382. _expansionModel;
  383. /**
  384. * Maintain a synchronous cache of flattened data nodes. This will only be
  385. * populated after initial render, and in certain cases, will be delayed due to
  386. * relying on Observable `getChildren` calls.
  387. */
  388. _flattenedNodes = new BehaviorSubject([]);
  389. /** The automatically determined node type for the tree. */
  390. _nodeType = new BehaviorSubject(null);
  391. /** The mapping between data and the node that is rendered. */
  392. _nodes = new BehaviorSubject(new Map());
  393. /**
  394. * Synchronous cache of nodes for the `TreeKeyManager`. This is separate
  395. * from `_flattenedNodes` so they can be independently updated at different
  396. * times.
  397. */
  398. _keyManagerNodes = new BehaviorSubject([]);
  399. _keyManagerFactory = inject(TREE_KEY_MANAGER);
  400. /** The key manager for this tree. Handles focus and activation based on user keyboard input. */
  401. _keyManager;
  402. _viewInit = false;
  403. constructor() { }
  404. ngAfterContentInit() {
  405. this._initializeKeyManager();
  406. }
  407. ngAfterContentChecked() {
  408. this._updateDefaultNodeDefinition();
  409. this._subscribeToDataChanges();
  410. }
  411. ngOnDestroy() {
  412. this._nodeOutlet.viewContainer.clear();
  413. this.viewChange.complete();
  414. this._onDestroy.next();
  415. this._onDestroy.complete();
  416. if (this._dataSource && typeof this._dataSource.disconnect === 'function') {
  417. this.dataSource.disconnect(this);
  418. }
  419. if (this._dataSubscription) {
  420. this._dataSubscription.unsubscribe();
  421. this._dataSubscription = null;
  422. }
  423. // In certain tests, the tree might be destroyed before this is initialized
  424. // in `ngAfterContentInit`.
  425. this._keyManager?.destroy();
  426. }
  427. ngOnInit() {
  428. this._checkTreeControlUsage();
  429. this._initializeDataDiffer();
  430. }
  431. ngAfterViewInit() {
  432. this._viewInit = true;
  433. }
  434. _updateDefaultNodeDefinition() {
  435. const defaultNodeDefs = this._nodeDefs.filter(def => !def.when);
  436. if (defaultNodeDefs.length > 1 && (typeof ngDevMode === 'undefined' || ngDevMode)) {
  437. throw getTreeMultipleDefaultNodeDefsError();
  438. }
  439. this._defaultNodeDef = defaultNodeDefs[0];
  440. }
  441. /**
  442. * Sets the node type for the tree, if it hasn't been set yet.
  443. *
  444. * This will be called by the first node that's rendered in order for the tree
  445. * to determine what data transformations are required.
  446. */
  447. _setNodeTypeIfUnset(newType) {
  448. const currentType = this._nodeType.value;
  449. if (currentType === null) {
  450. this._nodeType.next(newType);
  451. }
  452. else if ((typeof ngDevMode === 'undefined' || ngDevMode) && currentType !== newType) {
  453. console.warn(`Tree is using conflicting node types which can cause unexpected behavior. ` +
  454. `Please use tree nodes of the same type (e.g. only flat or only nested). ` +
  455. `Current node type: "${currentType}", new node type "${newType}".`);
  456. }
  457. }
  458. /**
  459. * Switch to the provided data source by resetting the data and unsubscribing from the current
  460. * render change subscription if one exists. If the data source is null, interpret this by
  461. * clearing the node outlet. Otherwise start listening for new data.
  462. */
  463. _switchDataSource(dataSource) {
  464. if (this._dataSource && typeof this._dataSource.disconnect === 'function') {
  465. this.dataSource.disconnect(this);
  466. }
  467. if (this._dataSubscription) {
  468. this._dataSubscription.unsubscribe();
  469. this._dataSubscription = null;
  470. }
  471. // Remove the all dataNodes if there is now no data source
  472. if (!dataSource) {
  473. this._nodeOutlet.viewContainer.clear();
  474. }
  475. this._dataSource = dataSource;
  476. if (this._nodeDefs) {
  477. this._subscribeToDataChanges();
  478. }
  479. }
  480. _getExpansionModel() {
  481. if (!this.treeControl) {
  482. this._expansionModel ??= new SelectionModel(true);
  483. return this._expansionModel;
  484. }
  485. return this.treeControl.expansionModel;
  486. }
  487. /** Set up a subscription for the data provided by the data source. */
  488. _subscribeToDataChanges() {
  489. if (this._dataSubscription) {
  490. return;
  491. }
  492. let dataStream;
  493. if (isDataSource(this._dataSource)) {
  494. dataStream = this._dataSource.connect(this);
  495. }
  496. else if (isObservable(this._dataSource)) {
  497. dataStream = this._dataSource;
  498. }
  499. else if (Array.isArray(this._dataSource)) {
  500. dataStream = of(this._dataSource);
  501. }
  502. if (!dataStream) {
  503. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  504. throw getTreeNoValidDataSourceError();
  505. }
  506. return;
  507. }
  508. this._dataSubscription = this._getRenderData(dataStream)
  509. .pipe(takeUntil(this._onDestroy))
  510. .subscribe(renderingData => {
  511. this._renderDataChanges(renderingData);
  512. });
  513. }
  514. /** Given an Observable containing a stream of the raw data, returns an Observable containing the RenderingData */
  515. _getRenderData(dataStream) {
  516. const expansionModel = this._getExpansionModel();
  517. return combineLatest([
  518. dataStream,
  519. this._nodeType,
  520. // We don't use the expansion data directly, however we add it here to essentially
  521. // trigger data rendering when expansion changes occur.
  522. expansionModel.changed.pipe(startWith(null), tap(expansionChanges => {
  523. this._emitExpansionChanges(expansionChanges);
  524. })),
  525. ]).pipe(switchMap(([data, nodeType]) => {
  526. if (nodeType === null) {
  527. return of({ renderNodes: data, flattenedNodes: null, nodeType });
  528. }
  529. // If we're here, then we know what our node type is, and therefore can
  530. // perform our usual rendering pipeline, which necessitates converting the data
  531. return this._computeRenderingData(data, nodeType).pipe(map(convertedData => ({ ...convertedData, nodeType })));
  532. }));
  533. }
  534. _renderDataChanges(data) {
  535. if (data.nodeType === null) {
  536. this.renderNodeChanges(data.renderNodes);
  537. return;
  538. }
  539. // If we're here, then we know what our node type is, and therefore can
  540. // perform our usual rendering pipeline.
  541. this._updateCachedData(data.flattenedNodes);
  542. this.renderNodeChanges(data.renderNodes);
  543. this._updateKeyManagerItems(data.flattenedNodes);
  544. }
  545. _emitExpansionChanges(expansionChanges) {
  546. if (!expansionChanges) {
  547. return;
  548. }
  549. const nodes = this._nodes.value;
  550. for (const added of expansionChanges.added) {
  551. const node = nodes.get(added);
  552. node?._emitExpansionState(true);
  553. }
  554. for (const removed of expansionChanges.removed) {
  555. const node = nodes.get(removed);
  556. node?._emitExpansionState(false);
  557. }
  558. }
  559. _initializeKeyManager() {
  560. const items = combineLatest([this._keyManagerNodes, this._nodes]).pipe(map(([keyManagerNodes, renderNodes]) => keyManagerNodes.reduce((items, data) => {
  561. const node = renderNodes.get(this._getExpansionKey(data));
  562. if (node) {
  563. items.push(node);
  564. }
  565. return items;
  566. }, [])));
  567. const keyManagerOptions = {
  568. trackBy: node => this._getExpansionKey(node.data),
  569. skipPredicate: node => !!node.isDisabled,
  570. typeAheadDebounceInterval: true,
  571. horizontalOrientation: this._dir.value,
  572. };
  573. this._keyManager = this._keyManagerFactory(items, keyManagerOptions);
  574. }
  575. _initializeDataDiffer() {
  576. // Provide a default trackBy based on `_getExpansionKey` if one isn't provided.
  577. const trackBy = this.trackBy ?? ((_index, item) => this._getExpansionKey(item));
  578. this._dataDiffer = this._differs.find([]).create(trackBy);
  579. }
  580. _checkTreeControlUsage() {
  581. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  582. // Verify that Tree follows API contract of using one of TreeControl, levelAccessor or
  583. // childrenAccessor. Throw an appropriate error if contract is not met.
  584. let numTreeControls = 0;
  585. if (this.treeControl) {
  586. numTreeControls++;
  587. }
  588. if (this.levelAccessor) {
  589. numTreeControls++;
  590. }
  591. if (this.childrenAccessor) {
  592. numTreeControls++;
  593. }
  594. if (!numTreeControls) {
  595. throw getTreeControlMissingError();
  596. }
  597. else if (numTreeControls > 1) {
  598. throw getMultipleTreeControlsError();
  599. }
  600. }
  601. }
  602. /** Check for changes made in the data and render each change (node added/removed/moved). */
  603. renderNodeChanges(data, dataDiffer = this._dataDiffer, viewContainer = this._nodeOutlet.viewContainer, parentData) {
  604. const changes = dataDiffer.diff(data);
  605. // Some tree consumers expect change detection to propagate to nodes
  606. // even when the array itself hasn't changed; we explicitly detect changes
  607. // anyways in order for nodes to update their data.
  608. //
  609. // However, if change detection is called while the component's view is
  610. // still initing, then the order of child views initing will be incorrect;
  611. // to prevent this, we only exit early if the view hasn't initialized yet.
  612. if (!changes && !this._viewInit) {
  613. return;
  614. }
  615. changes?.forEachOperation((item, adjustedPreviousIndex, currentIndex) => {
  616. if (item.previousIndex == null) {
  617. this.insertNode(data[currentIndex], currentIndex, viewContainer, parentData);
  618. }
  619. else if (currentIndex == null) {
  620. viewContainer.remove(adjustedPreviousIndex);
  621. }
  622. else {
  623. const view = viewContainer.get(adjustedPreviousIndex);
  624. viewContainer.move(view, currentIndex);
  625. }
  626. });
  627. // If the data itself changes, but keeps the same trackBy, we need to update the templates'
  628. // context to reflect the new object.
  629. changes?.forEachIdentityChange((record) => {
  630. const newData = record.item;
  631. if (record.currentIndex != undefined) {
  632. const view = viewContainer.get(record.currentIndex);
  633. view.context.$implicit = newData;
  634. }
  635. });
  636. // Note: we only `detectChanges` from a top-level call, otherwise we risk overflowing
  637. // the call stack since this method is called recursively (see #29733.)
  638. // TODO: change to `this._changeDetectorRef.markForCheck()`,
  639. // or just switch this component to use signals.
  640. if (parentData) {
  641. this._changeDetectorRef.markForCheck();
  642. }
  643. else {
  644. this._changeDetectorRef.detectChanges();
  645. }
  646. }
  647. /**
  648. * Finds the matching node definition that should be used for this node data. If there is only
  649. * one node definition, it is returned. Otherwise, find the node definition that has a when
  650. * predicate that returns true with the data. If none return true, return the default node
  651. * definition.
  652. */
  653. _getNodeDef(data, i) {
  654. if (this._nodeDefs.length === 1) {
  655. return this._nodeDefs.first;
  656. }
  657. const nodeDef = this._nodeDefs.find(def => def.when && def.when(i, data)) || this._defaultNodeDef;
  658. if (!nodeDef && (typeof ngDevMode === 'undefined' || ngDevMode)) {
  659. throw getTreeMissingMatchingNodeDefError();
  660. }
  661. return nodeDef;
  662. }
  663. /**
  664. * Create the embedded view for the data node template and place it in the correct index location
  665. * within the data node view container.
  666. */
  667. insertNode(nodeData, index, viewContainer, parentData) {
  668. const levelAccessor = this._getLevelAccessor();
  669. const node = this._getNodeDef(nodeData, index);
  670. const key = this._getExpansionKey(nodeData);
  671. // Node context that will be provided to created embedded view
  672. const context = new CdkTreeNodeOutletContext(nodeData);
  673. parentData ??= this._parents.get(key) ?? undefined;
  674. // If the tree is flat tree, then use the `getLevel` function in flat tree control
  675. // Otherwise, use the level of parent node.
  676. if (levelAccessor) {
  677. context.level = levelAccessor(nodeData);
  678. }
  679. else if (parentData !== undefined && this._levels.has(this._getExpansionKey(parentData))) {
  680. context.level = this._levels.get(this._getExpansionKey(parentData)) + 1;
  681. }
  682. else {
  683. context.level = 0;
  684. }
  685. this._levels.set(key, context.level);
  686. // Use default tree nodeOutlet, or nested node's nodeOutlet
  687. const container = viewContainer ? viewContainer : this._nodeOutlet.viewContainer;
  688. container.createEmbeddedView(node.template, context, index);
  689. // Set the data to just created `CdkTreeNode`.
  690. // The `CdkTreeNode` created from `createEmbeddedView` will be saved in static variable
  691. // `mostRecentTreeNode`. We get it from static variable and pass the node data to it.
  692. if (CdkTreeNode.mostRecentTreeNode) {
  693. CdkTreeNode.mostRecentTreeNode.data = nodeData;
  694. }
  695. }
  696. /** Whether the data node is expanded or collapsed. Returns true if it's expanded. */
  697. isExpanded(dataNode) {
  698. return !!(this.treeControl?.isExpanded(dataNode) ||
  699. this._expansionModel?.isSelected(this._getExpansionKey(dataNode)));
  700. }
  701. /** If the data node is currently expanded, collapse it. Otherwise, expand it. */
  702. toggle(dataNode) {
  703. if (this.treeControl) {
  704. this.treeControl.toggle(dataNode);
  705. }
  706. else if (this._expansionModel) {
  707. this._expansionModel.toggle(this._getExpansionKey(dataNode));
  708. }
  709. }
  710. /** Expand the data node. If it is already expanded, does nothing. */
  711. expand(dataNode) {
  712. if (this.treeControl) {
  713. this.treeControl.expand(dataNode);
  714. }
  715. else if (this._expansionModel) {
  716. this._expansionModel.select(this._getExpansionKey(dataNode));
  717. }
  718. }
  719. /** Collapse the data node. If it is already collapsed, does nothing. */
  720. collapse(dataNode) {
  721. if (this.treeControl) {
  722. this.treeControl.collapse(dataNode);
  723. }
  724. else if (this._expansionModel) {
  725. this._expansionModel.deselect(this._getExpansionKey(dataNode));
  726. }
  727. }
  728. /**
  729. * If the data node is currently expanded, collapse it and all its descendants.
  730. * Otherwise, expand it and all its descendants.
  731. */
  732. toggleDescendants(dataNode) {
  733. if (this.treeControl) {
  734. this.treeControl.toggleDescendants(dataNode);
  735. }
  736. else if (this._expansionModel) {
  737. if (this.isExpanded(dataNode)) {
  738. this.collapseDescendants(dataNode);
  739. }
  740. else {
  741. this.expandDescendants(dataNode);
  742. }
  743. }
  744. }
  745. /**
  746. * Expand the data node and all its descendants. If they are already expanded, does nothing.
  747. */
  748. expandDescendants(dataNode) {
  749. if (this.treeControl) {
  750. this.treeControl.expandDescendants(dataNode);
  751. }
  752. else if (this._expansionModel) {
  753. const expansionModel = this._expansionModel;
  754. expansionModel.select(this._getExpansionKey(dataNode));
  755. this._getDescendants(dataNode)
  756. .pipe(take(1), takeUntil(this._onDestroy))
  757. .subscribe(children => {
  758. expansionModel.select(...children.map(child => this._getExpansionKey(child)));
  759. });
  760. }
  761. }
  762. /** Collapse the data node and all its descendants. If it is already collapsed, does nothing. */
  763. collapseDescendants(dataNode) {
  764. if (this.treeControl) {
  765. this.treeControl.collapseDescendants(dataNode);
  766. }
  767. else if (this._expansionModel) {
  768. const expansionModel = this._expansionModel;
  769. expansionModel.deselect(this._getExpansionKey(dataNode));
  770. this._getDescendants(dataNode)
  771. .pipe(take(1), takeUntil(this._onDestroy))
  772. .subscribe(children => {
  773. expansionModel.deselect(...children.map(child => this._getExpansionKey(child)));
  774. });
  775. }
  776. }
  777. /** Expands all data nodes in the tree. */
  778. expandAll() {
  779. if (this.treeControl) {
  780. this.treeControl.expandAll();
  781. }
  782. else if (this._expansionModel) {
  783. this._forEachExpansionKey(keys => this._expansionModel?.select(...keys));
  784. }
  785. }
  786. /** Collapse all data nodes in the tree. */
  787. collapseAll() {
  788. if (this.treeControl) {
  789. this.treeControl.collapseAll();
  790. }
  791. else if (this._expansionModel) {
  792. this._forEachExpansionKey(keys => this._expansionModel?.deselect(...keys));
  793. }
  794. }
  795. /** Level accessor, used for compatibility between the old Tree and new Tree */
  796. _getLevelAccessor() {
  797. return this.treeControl?.getLevel?.bind(this.treeControl) ?? this.levelAccessor;
  798. }
  799. /** Children accessor, used for compatibility between the old Tree and new Tree */
  800. _getChildrenAccessor() {
  801. return this.treeControl?.getChildren?.bind(this.treeControl) ?? this.childrenAccessor;
  802. }
  803. /**
  804. * Gets the direct children of a node; used for compatibility between the old tree and the
  805. * new tree.
  806. */
  807. _getDirectChildren(dataNode) {
  808. const levelAccessor = this._getLevelAccessor();
  809. const expansionModel = this._expansionModel ?? this.treeControl?.expansionModel;
  810. if (!expansionModel) {
  811. return of([]);
  812. }
  813. const key = this._getExpansionKey(dataNode);
  814. const isExpanded = expansionModel.changed.pipe(switchMap(changes => {
  815. if (changes.added.includes(key)) {
  816. return of(true);
  817. }
  818. else if (changes.removed.includes(key)) {
  819. return of(false);
  820. }
  821. return EMPTY;
  822. }), startWith(this.isExpanded(dataNode)));
  823. if (levelAccessor) {
  824. return combineLatest([isExpanded, this._flattenedNodes]).pipe(map(([expanded, flattenedNodes]) => {
  825. if (!expanded) {
  826. return [];
  827. }
  828. return this._findChildrenByLevel(levelAccessor, flattenedNodes, dataNode, 1);
  829. }));
  830. }
  831. const childrenAccessor = this._getChildrenAccessor();
  832. if (childrenAccessor) {
  833. return coerceObservable(childrenAccessor(dataNode) ?? []);
  834. }
  835. throw getTreeControlMissingError();
  836. }
  837. /**
  838. * Given the list of flattened nodes, the level accessor, and the level range within
  839. * which to consider children, finds the children for a given node.
  840. *
  841. * For example, for direct children, `levelDelta` would be 1. For all descendants,
  842. * `levelDelta` would be Infinity.
  843. */
  844. _findChildrenByLevel(levelAccessor, flattenedNodes, dataNode, levelDelta) {
  845. const key = this._getExpansionKey(dataNode);
  846. const startIndex = flattenedNodes.findIndex(node => this._getExpansionKey(node) === key);
  847. const dataNodeLevel = levelAccessor(dataNode);
  848. const expectedLevel = dataNodeLevel + levelDelta;
  849. const results = [];
  850. // Goes through flattened tree nodes in the `flattenedNodes` array, and get all
  851. // descendants within a certain level range.
  852. //
  853. // If we reach a node whose level is equal to or less than the level of the tree node,
  854. // we hit a sibling or parent's sibling, and should stop.
  855. for (let i = startIndex + 1; i < flattenedNodes.length; i++) {
  856. const currentLevel = levelAccessor(flattenedNodes[i]);
  857. if (currentLevel <= dataNodeLevel) {
  858. break;
  859. }
  860. if (currentLevel <= expectedLevel) {
  861. results.push(flattenedNodes[i]);
  862. }
  863. }
  864. return results;
  865. }
  866. /**
  867. * Adds the specified node component to the tree's internal registry.
  868. *
  869. * This primarily facilitates keyboard navigation.
  870. */
  871. _registerNode(node) {
  872. this._nodes.value.set(this._getExpansionKey(node.data), node);
  873. this._nodes.next(this._nodes.value);
  874. }
  875. /** Removes the specified node component from the tree's internal registry. */
  876. _unregisterNode(node) {
  877. this._nodes.value.delete(this._getExpansionKey(node.data));
  878. this._nodes.next(this._nodes.value);
  879. }
  880. /**
  881. * For the given node, determine the level where this node appears in the tree.
  882. *
  883. * This is intended to be used for `aria-level` but is 0-indexed.
  884. */
  885. _getLevel(node) {
  886. return this._levels.get(this._getExpansionKey(node));
  887. }
  888. /**
  889. * For the given node, determine the size of the parent's child set.
  890. *
  891. * This is intended to be used for `aria-setsize`.
  892. */
  893. _getSetSize(dataNode) {
  894. const set = this._getAriaSet(dataNode);
  895. return set.length;
  896. }
  897. /**
  898. * For the given node, determine the index (starting from 1) of the node in its parent's child set.
  899. *
  900. * This is intended to be used for `aria-posinset`.
  901. */
  902. _getPositionInSet(dataNode) {
  903. const set = this._getAriaSet(dataNode);
  904. const key = this._getExpansionKey(dataNode);
  905. return set.findIndex(node => this._getExpansionKey(node) === key) + 1;
  906. }
  907. /** Given a CdkTreeNode, gets the node that renders that node's parent's data. */
  908. _getNodeParent(node) {
  909. const parent = this._parents.get(this._getExpansionKey(node.data));
  910. return parent && this._nodes.value.get(this._getExpansionKey(parent));
  911. }
  912. /** Given a CdkTreeNode, gets the nodes that renders that node's child data. */
  913. _getNodeChildren(node) {
  914. return this._getDirectChildren(node.data).pipe(map(children => children.reduce((nodes, child) => {
  915. const value = this._nodes.value.get(this._getExpansionKey(child));
  916. if (value) {
  917. nodes.push(value);
  918. }
  919. return nodes;
  920. }, [])));
  921. }
  922. /** `keydown` event handler; this just passes the event to the `TreeKeyManager`. */
  923. _sendKeydownToKeyManager(event) {
  924. // Only handle events directly on the tree or directly on one of the nodes, otherwise
  925. // we risk interfering with events in the projected content (see #29828).
  926. if (event.target === this._elementRef.nativeElement) {
  927. this._keyManager.onKeydown(event);
  928. }
  929. else {
  930. const nodes = this._nodes.getValue();
  931. for (const [, node] of nodes) {
  932. if (event.target === node._elementRef.nativeElement) {
  933. this._keyManager.onKeydown(event);
  934. break;
  935. }
  936. }
  937. }
  938. }
  939. /** Gets all nested descendants of a given node. */
  940. _getDescendants(dataNode) {
  941. if (this.treeControl) {
  942. return of(this.treeControl.getDescendants(dataNode));
  943. }
  944. if (this.levelAccessor) {
  945. const results = this._findChildrenByLevel(this.levelAccessor, this._flattenedNodes.value, dataNode, Infinity);
  946. return of(results);
  947. }
  948. if (this.childrenAccessor) {
  949. return this._getAllChildrenRecursively(dataNode).pipe(reduce((allChildren, nextChildren) => {
  950. allChildren.push(...nextChildren);
  951. return allChildren;
  952. }, []));
  953. }
  954. throw getTreeControlMissingError();
  955. }
  956. /**
  957. * Gets all children and sub-children of the provided node.
  958. *
  959. * This will emit multiple times, in the order that the children will appear
  960. * in the tree, and can be combined with a `reduce` operator.
  961. */
  962. _getAllChildrenRecursively(dataNode) {
  963. if (!this.childrenAccessor) {
  964. return of([]);
  965. }
  966. return coerceObservable(this.childrenAccessor(dataNode)).pipe(take(1), switchMap(children => {
  967. // Here, we cache the parents of a particular child so that we can compute the levels.
  968. for (const child of children) {
  969. this._parents.set(this._getExpansionKey(child), dataNode);
  970. }
  971. return of(...children).pipe(concatMap(child => concat(of([child]), this._getAllChildrenRecursively(child))));
  972. }));
  973. }
  974. _getExpansionKey(dataNode) {
  975. // In the case that a key accessor function was not provided by the
  976. // tree user, we'll default to using the node object itself as the key.
  977. //
  978. // This cast is safe since:
  979. // - if an expansionKey is provided, TS will infer the type of K to be
  980. // the return type.
  981. // - if it's not, then K will be defaulted to T.
  982. return this.expansionKey?.(dataNode) ?? dataNode;
  983. }
  984. _getAriaSet(node) {
  985. const key = this._getExpansionKey(node);
  986. const parent = this._parents.get(key);
  987. const parentKey = parent ? this._getExpansionKey(parent) : null;
  988. const set = this._ariaSets.get(parentKey);
  989. return set ?? [node];
  990. }
  991. /**
  992. * Finds the parent for the given node. If this is a root node, this
  993. * returns null. If we're unable to determine the parent, for example,
  994. * if we don't have cached node data, this returns undefined.
  995. */
  996. _findParentForNode(node, index, cachedNodes) {
  997. // In all cases, we have a mapping from node to level; all we need to do here is backtrack in
  998. // our flattened list of nodes to determine the first node that's of a level lower than the
  999. // provided node.
  1000. if (!cachedNodes.length) {
  1001. return null;
  1002. }
  1003. const currentLevel = this._levels.get(this._getExpansionKey(node)) ?? 0;
  1004. for (let parentIndex = index - 1; parentIndex >= 0; parentIndex--) {
  1005. const parentNode = cachedNodes[parentIndex];
  1006. const parentLevel = this._levels.get(this._getExpansionKey(parentNode)) ?? 0;
  1007. if (parentLevel < currentLevel) {
  1008. return parentNode;
  1009. }
  1010. }
  1011. return null;
  1012. }
  1013. /**
  1014. * Given a set of root nodes and the current node level, flattens any nested
  1015. * nodes into a single array.
  1016. *
  1017. * If any nodes are not expanded, then their children will not be added into the array.
  1018. * This will still traverse all nested children in order to build up our internal data
  1019. * models, but will not include them in the returned array.
  1020. */
  1021. _flattenNestedNodesWithExpansion(nodes, level = 0) {
  1022. const childrenAccessor = this._getChildrenAccessor();
  1023. // If we're using a level accessor, we don't need to flatten anything.
  1024. if (!childrenAccessor) {
  1025. return of([...nodes]);
  1026. }
  1027. return of(...nodes).pipe(concatMap(node => {
  1028. const parentKey = this._getExpansionKey(node);
  1029. if (!this._parents.has(parentKey)) {
  1030. this._parents.set(parentKey, null);
  1031. }
  1032. this._levels.set(parentKey, level);
  1033. const children = coerceObservable(childrenAccessor(node));
  1034. return concat(of([node]), children.pipe(take(1), tap(childNodes => {
  1035. this._ariaSets.set(parentKey, [...(childNodes ?? [])]);
  1036. for (const child of childNodes ?? []) {
  1037. const childKey = this._getExpansionKey(child);
  1038. this._parents.set(childKey, node);
  1039. this._levels.set(childKey, level + 1);
  1040. }
  1041. }), switchMap(childNodes => {
  1042. if (!childNodes) {
  1043. return of([]);
  1044. }
  1045. return this._flattenNestedNodesWithExpansion(childNodes, level + 1).pipe(map(nestedNodes => (this.isExpanded(node) ? nestedNodes : [])));
  1046. })));
  1047. }), reduce((results, children) => {
  1048. results.push(...children);
  1049. return results;
  1050. }, []));
  1051. }
  1052. /**
  1053. * Converts children for certain tree configurations.
  1054. *
  1055. * This also computes parent, level, and group data.
  1056. */
  1057. _computeRenderingData(nodes, nodeType) {
  1058. // The only situations where we have to convert children types is when
  1059. // they're mismatched; i.e. if the tree is using a childrenAccessor and the
  1060. // nodes are flat, or if the tree is using a levelAccessor and the nodes are
  1061. // nested.
  1062. if (this.childrenAccessor && nodeType === 'flat') {
  1063. // clear previously generated data so we don't keep end up retaining data overtime causing
  1064. // memory leaks.
  1065. this._clearPreviousCache();
  1066. // This flattens children into a single array.
  1067. this._ariaSets.set(null, [...nodes]);
  1068. return this._flattenNestedNodesWithExpansion(nodes).pipe(map(flattenedNodes => ({
  1069. renderNodes: flattenedNodes,
  1070. flattenedNodes,
  1071. })));
  1072. }
  1073. else if (this.levelAccessor && nodeType === 'nested') {
  1074. // In the nested case, we only look for root nodes. The CdkNestedNode
  1075. // itself will handle rendering each individual node's children.
  1076. const levelAccessor = this.levelAccessor;
  1077. return of(nodes.filter(node => levelAccessor(node) === 0)).pipe(map(rootNodes => ({
  1078. renderNodes: rootNodes,
  1079. flattenedNodes: nodes,
  1080. })), tap(({ flattenedNodes }) => {
  1081. this._calculateParents(flattenedNodes);
  1082. }));
  1083. }
  1084. else if (nodeType === 'flat') {
  1085. // In the case of a TreeControl, we know that the node type matches up
  1086. // with the TreeControl, and so no conversions are necessary. Otherwise,
  1087. // we've already confirmed that the data model matches up with the
  1088. // desired node type here.
  1089. return of({ renderNodes: nodes, flattenedNodes: nodes }).pipe(tap(({ flattenedNodes }) => {
  1090. this._calculateParents(flattenedNodes);
  1091. }));
  1092. }
  1093. else {
  1094. // clear previously generated data so we don't keep end up retaining data overtime causing
  1095. // memory leaks.
  1096. this._clearPreviousCache();
  1097. // For nested nodes, we still need to perform the node flattening in order
  1098. // to maintain our caches for various tree operations.
  1099. this._ariaSets.set(null, [...nodes]);
  1100. return this._flattenNestedNodesWithExpansion(nodes).pipe(map(flattenedNodes => ({
  1101. renderNodes: nodes,
  1102. flattenedNodes,
  1103. })));
  1104. }
  1105. }
  1106. _updateCachedData(flattenedNodes) {
  1107. this._flattenedNodes.next(flattenedNodes);
  1108. }
  1109. _updateKeyManagerItems(flattenedNodes) {
  1110. this._keyManagerNodes.next(flattenedNodes);
  1111. }
  1112. /** Traverse the flattened node data and compute parents, levels, and group data. */
  1113. _calculateParents(flattenedNodes) {
  1114. const levelAccessor = this._getLevelAccessor();
  1115. if (!levelAccessor) {
  1116. return;
  1117. }
  1118. // clear previously generated data so we don't keep end up retaining data overtime causing
  1119. // memory leaks.
  1120. this._clearPreviousCache();
  1121. for (let index = 0; index < flattenedNodes.length; index++) {
  1122. const dataNode = flattenedNodes[index];
  1123. const key = this._getExpansionKey(dataNode);
  1124. this._levels.set(key, levelAccessor(dataNode));
  1125. const parent = this._findParentForNode(dataNode, index, flattenedNodes);
  1126. this._parents.set(key, parent);
  1127. const parentKey = parent ? this._getExpansionKey(parent) : null;
  1128. const group = this._ariaSets.get(parentKey) ?? [];
  1129. group.splice(index, 0, dataNode);
  1130. this._ariaSets.set(parentKey, group);
  1131. }
  1132. }
  1133. /** Invokes a callback with all node expansion keys. */
  1134. _forEachExpansionKey(callback) {
  1135. const toToggle = [];
  1136. const observables = [];
  1137. this._nodes.value.forEach(node => {
  1138. toToggle.push(this._getExpansionKey(node.data));
  1139. observables.push(this._getDescendants(node.data));
  1140. });
  1141. if (observables.length > 0) {
  1142. combineLatest(observables)
  1143. .pipe(take(1), takeUntil(this._onDestroy))
  1144. .subscribe(results => {
  1145. results.forEach(inner => inner.forEach(r => toToggle.push(this._getExpansionKey(r))));
  1146. callback(toToggle);
  1147. });
  1148. }
  1149. else {
  1150. callback(toToggle);
  1151. }
  1152. }
  1153. /** Clears the maps we use to store parents, level & aria-sets in. */
  1154. _clearPreviousCache() {
  1155. this._parents.clear();
  1156. this._levels.clear();
  1157. this._ariaSets.clear();
  1158. }
  1159. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
  1160. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: CdkTree, isStandalone: true, selector: "cdk-tree", inputs: { dataSource: "dataSource", treeControl: "treeControl", levelAccessor: "levelAccessor", childrenAccessor: "childrenAccessor", trackBy: "trackBy", expansionKey: "expansionKey" }, host: { attributes: { "role": "tree" }, listeners: { "keydown": "_sendKeydownToKeyManager($event)" }, classAttribute: "cdk-tree" }, queries: [{ propertyName: "_nodeDefs", predicate: CdkTreeNodeDef, descendants: true }], viewQueries: [{ propertyName: "_nodeOutlet", first: true, predicate: CdkTreeNodeOutlet, descendants: true, static: true }], exportAs: ["cdkTree"], ngImport: i0, template: `<ng-container cdkTreeNodeOutlet></ng-container>`, isInline: true, dependencies: [{ kind: "directive", type: CdkTreeNodeOutlet, selector: "[cdkTreeNodeOutlet]" }], changeDetection: i0.ChangeDetectionStrategy.Default, encapsulation: i0.ViewEncapsulation.None });
  1161. }
  1162. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTree, decorators: [{
  1163. type: Component,
  1164. args: [{
  1165. selector: 'cdk-tree',
  1166. exportAs: 'cdkTree',
  1167. template: `<ng-container cdkTreeNodeOutlet></ng-container>`,
  1168. host: {
  1169. 'class': 'cdk-tree',
  1170. 'role': 'tree',
  1171. '(keydown)': '_sendKeydownToKeyManager($event)',
  1172. },
  1173. encapsulation: ViewEncapsulation.None,
  1174. // The "OnPush" status for the `CdkTree` component is effectively a noop, so we are removing it.
  1175. // The view for `CdkTree` consists entirely of templates declared in other views. As they are
  1176. // declared elsewhere, they are checked when their declaration points are checked.
  1177. // tslint:disable-next-line:validate-decorators
  1178. changeDetection: ChangeDetectionStrategy.Default,
  1179. imports: [CdkTreeNodeOutlet],
  1180. }]
  1181. }], ctorParameters: () => [], propDecorators: { dataSource: [{
  1182. type: Input
  1183. }], treeControl: [{
  1184. type: Input
  1185. }], levelAccessor: [{
  1186. type: Input
  1187. }], childrenAccessor: [{
  1188. type: Input
  1189. }], trackBy: [{
  1190. type: Input
  1191. }], expansionKey: [{
  1192. type: Input
  1193. }], _nodeOutlet: [{
  1194. type: ViewChild,
  1195. args: [CdkTreeNodeOutlet, { static: true }]
  1196. }], _nodeDefs: [{
  1197. type: ContentChildren,
  1198. args: [CdkTreeNodeDef, {
  1199. // We need to use `descendants: true`, because Ivy will no longer match
  1200. // indirect descendants if it's left as false.
  1201. descendants: true,
  1202. }]
  1203. }] } });
  1204. /**
  1205. * Tree node for CdkTree. It contains the data in the tree node.
  1206. */
  1207. class CdkTreeNode {
  1208. _elementRef = inject(ElementRef);
  1209. _tree = inject(CdkTree);
  1210. _tabindex = -1;
  1211. _type = 'flat';
  1212. /**
  1213. * The role of the tree node.
  1214. *
  1215. * @deprecated This will be ignored; the tree will automatically determine the appropriate role
  1216. * for tree node. This input will be removed in a future version.
  1217. * @breaking-change 21.0.0
  1218. */
  1219. get role() {
  1220. return 'treeitem';
  1221. }
  1222. set role(_role) {
  1223. // ignore any role setting, we handle this internally.
  1224. }
  1225. /**
  1226. * Whether or not this node is expandable.
  1227. *
  1228. * If not using `FlatTreeControl`, or if `isExpandable` is not provided to
  1229. * `NestedTreeControl`, this should be provided for correct node a11y.
  1230. */
  1231. get isExpandable() {
  1232. return this._isExpandable();
  1233. }
  1234. set isExpandable(isExpandable) {
  1235. this._inputIsExpandable = isExpandable;
  1236. if ((this.data && !this._isExpandable) || !this._inputIsExpandable) {
  1237. return;
  1238. }
  1239. // If the node is being set to expandable, ensure that the status of the
  1240. // node is propagated
  1241. if (this._inputIsExpanded) {
  1242. this.expand();
  1243. }
  1244. else if (this._inputIsExpanded === false) {
  1245. this.collapse();
  1246. }
  1247. }
  1248. get isExpanded() {
  1249. return this._tree.isExpanded(this._data);
  1250. }
  1251. set isExpanded(isExpanded) {
  1252. this._inputIsExpanded = isExpanded;
  1253. if (isExpanded) {
  1254. this.expand();
  1255. }
  1256. else {
  1257. this.collapse();
  1258. }
  1259. }
  1260. /**
  1261. * Whether or not this node is disabled. If it's disabled, then the user won't be able to focus
  1262. * or activate this node.
  1263. */
  1264. isDisabled;
  1265. /**
  1266. * The text used to locate this item during typeahead. If not specified, the `textContent` will
  1267. * will be used.
  1268. */
  1269. typeaheadLabel;
  1270. getLabel() {
  1271. return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || '';
  1272. }
  1273. /** This emits when the node has been programatically activated or activated by keyboard. */
  1274. activation = new EventEmitter();
  1275. /** This emits when the node's expansion status has been changed. */
  1276. expandedChange = new EventEmitter();
  1277. /**
  1278. * The most recently created `CdkTreeNode`. We save it in static variable so we can retrieve it
  1279. * in `CdkTree` and set the data to it.
  1280. */
  1281. static mostRecentTreeNode = null;
  1282. /** Subject that emits when the component has been destroyed. */
  1283. _destroyed = new Subject();
  1284. /** Emits when the node's data has changed. */
  1285. _dataChanges = new Subject();
  1286. _inputIsExpandable = false;
  1287. _inputIsExpanded = undefined;
  1288. /**
  1289. * Flag used to determine whether or not we should be focusing the actual element based on
  1290. * some user interaction (click or focus). On click, we don't forcibly focus the element
  1291. * since the click could trigger some other component that wants to grab its own focus
  1292. * (e.g. menu, dialog).
  1293. */
  1294. _shouldFocus = true;
  1295. _parentNodeAriaLevel;
  1296. /** The tree node's data. */
  1297. get data() {
  1298. return this._data;
  1299. }
  1300. set data(value) {
  1301. if (value !== this._data) {
  1302. this._data = value;
  1303. this._dataChanges.next();
  1304. }
  1305. }
  1306. _data;
  1307. /* If leaf node, return true to not assign aria-expanded attribute */
  1308. get isLeafNode() {
  1309. // If flat tree node data returns false for expandable property, it's a leaf node
  1310. if (this._tree.treeControl?.isExpandable !== undefined &&
  1311. !this._tree.treeControl.isExpandable(this._data)) {
  1312. return true;
  1313. // If nested tree node data returns 0 descendants, it's a leaf node
  1314. }
  1315. else if (this._tree.treeControl?.isExpandable === undefined &&
  1316. this._tree.treeControl?.getDescendants(this._data).length === 0) {
  1317. return true;
  1318. }
  1319. return false;
  1320. }
  1321. get level() {
  1322. // If the tree has a levelAccessor, use it to get the level. Otherwise read the
  1323. // aria-level off the parent node and use it as the level for this node (note aria-level is
  1324. // 1-indexed, while this property is 0-indexed, so we don't need to increment).
  1325. return this._tree._getLevel(this._data) ?? this._parentNodeAriaLevel;
  1326. }
  1327. /** Determines if the tree node is expandable. */
  1328. _isExpandable() {
  1329. if (this._tree.treeControl) {
  1330. if (this.isLeafNode) {
  1331. return false;
  1332. }
  1333. // For compatibility with trees created using TreeControl before we added
  1334. // CdkTreeNode#isExpandable.
  1335. return true;
  1336. }
  1337. return this._inputIsExpandable;
  1338. }
  1339. /**
  1340. * Determines the value for `aria-expanded`.
  1341. *
  1342. * For non-expandable nodes, this is `null`.
  1343. */
  1344. _getAriaExpanded() {
  1345. if (!this._isExpandable()) {
  1346. return null;
  1347. }
  1348. return String(this.isExpanded);
  1349. }
  1350. /**
  1351. * Determines the size of this node's parent's child set.
  1352. *
  1353. * This is intended to be used for `aria-setsize`.
  1354. */
  1355. _getSetSize() {
  1356. return this._tree._getSetSize(this._data);
  1357. }
  1358. /**
  1359. * Determines the index (starting from 1) of this node in its parent's child set.
  1360. *
  1361. * This is intended to be used for `aria-posinset`.
  1362. */
  1363. _getPositionInSet() {
  1364. return this._tree._getPositionInSet(this._data);
  1365. }
  1366. _changeDetectorRef = inject(ChangeDetectorRef);
  1367. constructor() {
  1368. CdkTreeNode.mostRecentTreeNode = this;
  1369. }
  1370. ngOnInit() {
  1371. this._parentNodeAriaLevel = getParentNodeAriaLevel(this._elementRef.nativeElement);
  1372. this._tree
  1373. ._getExpansionModel()
  1374. .changed.pipe(map(() => this.isExpanded), distinctUntilChanged())
  1375. .subscribe(() => this._changeDetectorRef.markForCheck());
  1376. this._tree._setNodeTypeIfUnset(this._type);
  1377. this._tree._registerNode(this);
  1378. }
  1379. ngOnDestroy() {
  1380. // If this is the last tree node being destroyed,
  1381. // clear out the reference to avoid leaking memory.
  1382. if (CdkTreeNode.mostRecentTreeNode === this) {
  1383. CdkTreeNode.mostRecentTreeNode = null;
  1384. }
  1385. this._dataChanges.complete();
  1386. this._destroyed.next();
  1387. this._destroyed.complete();
  1388. }
  1389. getParent() {
  1390. return this._tree._getNodeParent(this) ?? null;
  1391. }
  1392. getChildren() {
  1393. return this._tree._getNodeChildren(this);
  1394. }
  1395. /** Focuses this data node. Implemented for TreeKeyManagerItem. */
  1396. focus() {
  1397. this._tabindex = 0;
  1398. if (this._shouldFocus) {
  1399. this._elementRef.nativeElement.focus();
  1400. }
  1401. this._changeDetectorRef.markForCheck();
  1402. }
  1403. /** Defocus this data node. */
  1404. unfocus() {
  1405. this._tabindex = -1;
  1406. this._changeDetectorRef.markForCheck();
  1407. }
  1408. /** Emits an activation event. Implemented for TreeKeyManagerItem. */
  1409. activate() {
  1410. if (this.isDisabled) {
  1411. return;
  1412. }
  1413. this.activation.next(this._data);
  1414. }
  1415. /** Collapses this data node. Implemented for TreeKeyManagerItem. */
  1416. collapse() {
  1417. if (this.isExpandable) {
  1418. this._tree.collapse(this._data);
  1419. }
  1420. }
  1421. /** Expands this data node. Implemented for TreeKeyManagerItem. */
  1422. expand() {
  1423. if (this.isExpandable) {
  1424. this._tree.expand(this._data);
  1425. }
  1426. }
  1427. /** Makes the node focusable. Implemented for TreeKeyManagerItem. */
  1428. makeFocusable() {
  1429. this._tabindex = 0;
  1430. this._changeDetectorRef.markForCheck();
  1431. }
  1432. _focusItem() {
  1433. if (this.isDisabled) {
  1434. return;
  1435. }
  1436. this._tree._keyManager.focusItem(this);
  1437. }
  1438. _setActiveItem() {
  1439. if (this.isDisabled) {
  1440. return;
  1441. }
  1442. this._shouldFocus = false;
  1443. this._tree._keyManager.focusItem(this);
  1444. this._shouldFocus = true;
  1445. }
  1446. _emitExpansionState(expanded) {
  1447. this.expandedChange.emit(expanded);
  1448. }
  1449. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNode, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1450. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkTreeNode, isStandalone: true, selector: "cdk-tree-node", inputs: { role: "role", isExpandable: ["isExpandable", "isExpandable", booleanAttribute], isExpanded: "isExpanded", isDisabled: ["isDisabled", "isDisabled", booleanAttribute], typeaheadLabel: ["cdkTreeNodeTypeaheadLabel", "typeaheadLabel"] }, outputs: { activation: "activation", expandedChange: "expandedChange" }, host: { attributes: { "role": "treeitem" }, listeners: { "click": "_setActiveItem()", "focus": "_focusItem()" }, properties: { "attr.aria-expanded": "_getAriaExpanded()", "attr.aria-level": "level + 1", "attr.aria-posinset": "_getPositionInSet()", "attr.aria-setsize": "_getSetSize()", "tabindex": "_tabindex" }, classAttribute: "cdk-tree-node" }, exportAs: ["cdkTreeNode"], ngImport: i0 });
  1451. }
  1452. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNode, decorators: [{
  1453. type: Directive,
  1454. args: [{
  1455. selector: 'cdk-tree-node',
  1456. exportAs: 'cdkTreeNode',
  1457. host: {
  1458. 'class': 'cdk-tree-node',
  1459. '[attr.aria-expanded]': '_getAriaExpanded()',
  1460. '[attr.aria-level]': 'level + 1',
  1461. '[attr.aria-posinset]': '_getPositionInSet()',
  1462. '[attr.aria-setsize]': '_getSetSize()',
  1463. '[tabindex]': '_tabindex',
  1464. 'role': 'treeitem',
  1465. '(click)': '_setActiveItem()',
  1466. '(focus)': '_focusItem()',
  1467. },
  1468. }]
  1469. }], ctorParameters: () => [], propDecorators: { role: [{
  1470. type: Input
  1471. }], isExpandable: [{
  1472. type: Input,
  1473. args: [{ transform: booleanAttribute }]
  1474. }], isExpanded: [{
  1475. type: Input
  1476. }], isDisabled: [{
  1477. type: Input,
  1478. args: [{ transform: booleanAttribute }]
  1479. }], typeaheadLabel: [{
  1480. type: Input,
  1481. args: ['cdkTreeNodeTypeaheadLabel']
  1482. }], activation: [{
  1483. type: Output
  1484. }], expandedChange: [{
  1485. type: Output
  1486. }] } });
  1487. function getParentNodeAriaLevel(nodeElement) {
  1488. let parent = nodeElement.parentElement;
  1489. while (parent && !isNodeElement(parent)) {
  1490. parent = parent.parentElement;
  1491. }
  1492. if (!parent) {
  1493. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  1494. throw Error('Incorrect tree structure containing detached node.');
  1495. }
  1496. else {
  1497. return -1;
  1498. }
  1499. }
  1500. else if (parent.classList.contains('cdk-nested-tree-node')) {
  1501. return numberAttribute(parent.getAttribute('aria-level'));
  1502. }
  1503. else {
  1504. // The ancestor element is the cdk-tree itself
  1505. return 0;
  1506. }
  1507. }
  1508. function isNodeElement(element) {
  1509. const classList = element.classList;
  1510. return !!(classList?.contains('cdk-nested-tree-node') || classList?.contains('cdk-tree'));
  1511. }
  1512. /**
  1513. * Nested node is a child of `<cdk-tree>`. It works with nested tree.
  1514. * By using `cdk-nested-tree-node` component in tree node template, children of the parent node will
  1515. * be added in the `cdkTreeNodeOutlet` in tree node template.
  1516. * The children of node will be automatically added to `cdkTreeNodeOutlet`.
  1517. */
  1518. class CdkNestedTreeNode extends CdkTreeNode {
  1519. _type = 'nested';
  1520. _differs = inject(IterableDiffers);
  1521. /** Differ used to find the changes in the data provided by the data source. */
  1522. _dataDiffer;
  1523. /** The children data dataNodes of current node. They will be placed in `CdkTreeNodeOutlet`. */
  1524. _children;
  1525. /** The children node placeholder. */
  1526. nodeOutlet;
  1527. constructor() {
  1528. super();
  1529. }
  1530. ngAfterContentInit() {
  1531. this._dataDiffer = this._differs.find([]).create(this._tree.trackBy);
  1532. this._tree
  1533. ._getDirectChildren(this.data)
  1534. .pipe(takeUntil(this._destroyed))
  1535. .subscribe(result => this.updateChildrenNodes(result));
  1536. this.nodeOutlet.changes
  1537. .pipe(takeUntil(this._destroyed))
  1538. .subscribe(() => this.updateChildrenNodes());
  1539. }
  1540. ngOnDestroy() {
  1541. this._clear();
  1542. super.ngOnDestroy();
  1543. }
  1544. /** Add children dataNodes to the NodeOutlet */
  1545. updateChildrenNodes(children) {
  1546. const outlet = this._getNodeOutlet();
  1547. if (children) {
  1548. this._children = children;
  1549. }
  1550. if (outlet && this._children) {
  1551. const viewContainer = outlet.viewContainer;
  1552. this._tree.renderNodeChanges(this._children, this._dataDiffer, viewContainer, this._data);
  1553. }
  1554. else {
  1555. // Reset the data differ if there's no children nodes displayed
  1556. this._dataDiffer.diff([]);
  1557. }
  1558. }
  1559. /** Clear the children dataNodes. */
  1560. _clear() {
  1561. const outlet = this._getNodeOutlet();
  1562. if (outlet) {
  1563. outlet.viewContainer.clear();
  1564. this._dataDiffer.diff([]);
  1565. }
  1566. }
  1567. /** Gets the outlet for the current node. */
  1568. _getNodeOutlet() {
  1569. const outlets = this.nodeOutlet;
  1570. // Note that since we use `descendants: true` on the query, we have to ensure
  1571. // that we don't pick up the outlet of a child node by accident.
  1572. return outlets && outlets.find(outlet => !outlet._node || outlet._node === this);
  1573. }
  1574. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkNestedTreeNode, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1575. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: CdkNestedTreeNode, isStandalone: true, selector: "cdk-nested-tree-node", host: { classAttribute: "cdk-nested-tree-node" }, providers: [
  1576. { provide: CdkTreeNode, useExisting: CdkNestedTreeNode },
  1577. { provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: CdkNestedTreeNode },
  1578. ], queries: [{ propertyName: "nodeOutlet", predicate: CdkTreeNodeOutlet, descendants: true }], exportAs: ["cdkNestedTreeNode"], usesInheritance: true, ngImport: i0 });
  1579. }
  1580. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkNestedTreeNode, decorators: [{
  1581. type: Directive,
  1582. args: [{
  1583. selector: 'cdk-nested-tree-node',
  1584. exportAs: 'cdkNestedTreeNode',
  1585. providers: [
  1586. { provide: CdkTreeNode, useExisting: CdkNestedTreeNode },
  1587. { provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: CdkNestedTreeNode },
  1588. ],
  1589. host: {
  1590. 'class': 'cdk-nested-tree-node',
  1591. },
  1592. }]
  1593. }], ctorParameters: () => [], propDecorators: { nodeOutlet: [{
  1594. type: ContentChildren,
  1595. args: [CdkTreeNodeOutlet, {
  1596. // We need to use `descendants: true`, because Ivy will no longer match
  1597. // indirect descendants if it's left as false.
  1598. descendants: true,
  1599. }]
  1600. }] } });
  1601. /** Regex used to split a string on its CSS units. */
  1602. const cssUnitPattern = /([A-Za-z%]+)$/;
  1603. /**
  1604. * Indent for the children tree dataNodes.
  1605. * This directive will add left-padding to the node to show hierarchy.
  1606. */
  1607. class CdkTreeNodePadding {
  1608. _treeNode = inject(CdkTreeNode);
  1609. _tree = inject(CdkTree);
  1610. _element = inject(ElementRef);
  1611. _dir = inject(Directionality, { optional: true });
  1612. /** Current padding value applied to the element. Used to avoid unnecessarily hitting the DOM. */
  1613. _currentPadding;
  1614. /** Subject that emits when the component has been destroyed. */
  1615. _destroyed = new Subject();
  1616. /** CSS units used for the indentation value. */
  1617. indentUnits = 'px';
  1618. /** The level of depth of the tree node. The padding will be `level * indent` pixels. */
  1619. get level() {
  1620. return this._level;
  1621. }
  1622. set level(value) {
  1623. this._setLevelInput(value);
  1624. }
  1625. _level;
  1626. /**
  1627. * The indent for each level. Can be a number or a CSS string.
  1628. * Default number 40px from material design menu sub-menu spec.
  1629. */
  1630. get indent() {
  1631. return this._indent;
  1632. }
  1633. set indent(indent) {
  1634. this._setIndentInput(indent);
  1635. }
  1636. _indent = 40;
  1637. constructor() {
  1638. this._setPadding();
  1639. this._dir?.change.pipe(takeUntil(this._destroyed)).subscribe(() => this._setPadding(true));
  1640. // In Ivy the indentation binding might be set before the tree node's data has been added,
  1641. // which means that we'll miss the first render. We have to subscribe to changes in the
  1642. // data to ensure that everything is up to date.
  1643. this._treeNode._dataChanges.subscribe(() => this._setPadding());
  1644. }
  1645. ngOnDestroy() {
  1646. this._destroyed.next();
  1647. this._destroyed.complete();
  1648. }
  1649. /** The padding indent value for the tree node. Returns a string with px numbers if not null. */
  1650. _paddingIndent() {
  1651. const nodeLevel = (this._treeNode.data && this._tree._getLevel(this._treeNode.data)) ?? null;
  1652. const level = this._level == null ? nodeLevel : this._level;
  1653. return typeof level === 'number' ? `${level * this._indent}${this.indentUnits}` : null;
  1654. }
  1655. _setPadding(forceChange = false) {
  1656. const padding = this._paddingIndent();
  1657. if (padding !== this._currentPadding || forceChange) {
  1658. const element = this._element.nativeElement;
  1659. const paddingProp = this._dir && this._dir.value === 'rtl' ? 'paddingRight' : 'paddingLeft';
  1660. const resetProp = paddingProp === 'paddingLeft' ? 'paddingRight' : 'paddingLeft';
  1661. element.style[paddingProp] = padding || '';
  1662. element.style[resetProp] = '';
  1663. this._currentPadding = padding;
  1664. }
  1665. }
  1666. /**
  1667. * This has been extracted to a util because of TS 4 and VE.
  1668. * View Engine doesn't support property rename inheritance.
  1669. * TS 4.0 doesn't allow properties to override accessors or vice-versa.
  1670. * @docs-private
  1671. */
  1672. _setLevelInput(value) {
  1673. // Set to null as the fallback value so that _setPadding can fall back to the node level if the
  1674. // consumer set the directive as `cdkTreeNodePadding=""`. We still want to take this value if
  1675. // they set 0 explicitly.
  1676. this._level = isNaN(value) ? null : value;
  1677. this._setPadding();
  1678. }
  1679. /**
  1680. * This has been extracted to a util because of TS 4 and VE.
  1681. * View Engine doesn't support property rename inheritance.
  1682. * TS 4.0 doesn't allow properties to override accessors or vice-versa.
  1683. * @docs-private
  1684. */
  1685. _setIndentInput(indent) {
  1686. let value = indent;
  1687. let units = 'px';
  1688. if (typeof indent === 'string') {
  1689. const parts = indent.split(cssUnitPattern);
  1690. value = parts[0];
  1691. units = parts[1] || units;
  1692. }
  1693. this.indentUnits = units;
  1694. this._indent = numberAttribute(value);
  1695. this._setPadding();
  1696. }
  1697. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodePadding, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1698. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkTreeNodePadding, isStandalone: true, selector: "[cdkTreeNodePadding]", inputs: { level: ["cdkTreeNodePadding", "level", numberAttribute], indent: ["cdkTreeNodePaddingIndent", "indent"] }, ngImport: i0 });
  1699. }
  1700. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodePadding, decorators: [{
  1701. type: Directive,
  1702. args: [{
  1703. selector: '[cdkTreeNodePadding]',
  1704. }]
  1705. }], ctorParameters: () => [], propDecorators: { level: [{
  1706. type: Input,
  1707. args: [{ alias: 'cdkTreeNodePadding', transform: numberAttribute }]
  1708. }], indent: [{
  1709. type: Input,
  1710. args: ['cdkTreeNodePaddingIndent']
  1711. }] } });
  1712. /**
  1713. * Node toggle to expand and collapse the node.
  1714. */
  1715. class CdkTreeNodeToggle {
  1716. _tree = inject(CdkTree);
  1717. _treeNode = inject(CdkTreeNode);
  1718. /** Whether expand/collapse the node recursively. */
  1719. recursive = false;
  1720. constructor() { }
  1721. // Toggle the expanded or collapsed state of this node.
  1722. //
  1723. // Focus this node with expanding or collapsing it. This ensures that the active node will always
  1724. // be visible when expanding and collapsing.
  1725. _toggle() {
  1726. this.recursive
  1727. ? this._tree.toggleDescendants(this._treeNode.data)
  1728. : this._tree.toggle(this._treeNode.data);
  1729. this._tree._keyManager.focusItem(this._treeNode);
  1730. }
  1731. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodeToggle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  1732. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkTreeNodeToggle, isStandalone: true, selector: "[cdkTreeNodeToggle]", inputs: { recursive: ["cdkTreeNodeToggleRecursive", "recursive", booleanAttribute] }, host: { attributes: { "tabindex": "-1" }, listeners: { "click": "_toggle(); $event.stopPropagation();", "keydown.Enter": "_toggle(); $event.preventDefault();", "keydown.Space": "_toggle(); $event.preventDefault();" } }, ngImport: i0 });
  1733. }
  1734. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeNodeToggle, decorators: [{
  1735. type: Directive,
  1736. args: [{
  1737. selector: '[cdkTreeNodeToggle]',
  1738. host: {
  1739. '(click)': '_toggle(); $event.stopPropagation();',
  1740. '(keydown.Enter)': '_toggle(); $event.preventDefault();',
  1741. '(keydown.Space)': '_toggle(); $event.preventDefault();',
  1742. 'tabindex': '-1',
  1743. },
  1744. }]
  1745. }], ctorParameters: () => [], propDecorators: { recursive: [{
  1746. type: Input,
  1747. args: [{ alias: 'cdkTreeNodeToggleRecursive', transform: booleanAttribute }]
  1748. }] } });
  1749. const EXPORTED_DECLARATIONS = [
  1750. CdkNestedTreeNode,
  1751. CdkTreeNodeDef,
  1752. CdkTreeNodePadding,
  1753. CdkTreeNodeToggle,
  1754. CdkTree,
  1755. CdkTreeNode,
  1756. CdkTreeNodeOutlet,
  1757. ];
  1758. class CdkTreeModule {
  1759. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  1760. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeModule, imports: [CdkNestedTreeNode,
  1761. CdkTreeNodeDef,
  1762. CdkTreeNodePadding,
  1763. CdkTreeNodeToggle,
  1764. CdkTree,
  1765. CdkTreeNode,
  1766. CdkTreeNodeOutlet], exports: [CdkNestedTreeNode,
  1767. CdkTreeNodeDef,
  1768. CdkTreeNodePadding,
  1769. CdkTreeNodeToggle,
  1770. CdkTree,
  1771. CdkTreeNode,
  1772. CdkTreeNodeOutlet] });
  1773. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeModule });
  1774. }
  1775. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkTreeModule, decorators: [{
  1776. type: NgModule,
  1777. args: [{
  1778. imports: EXPORTED_DECLARATIONS,
  1779. exports: EXPORTED_DECLARATIONS,
  1780. }]
  1781. }] });
  1782. export { BaseTreeControl, CDK_TREE_NODE_OUTLET_NODE, CdkNestedTreeNode, CdkTree, CdkTreeModule, CdkTreeNode, CdkTreeNodeDef, CdkTreeNodeOutlet, CdkTreeNodeOutletContext, CdkTreeNodePadding, CdkTreeNodeToggle, FlatTreeControl, NestedTreeControl, getMultipleTreeControlsError, getTreeControlMissingError, getTreeMissingMatchingNodeDefError, getTreeMultipleDefaultNodeDefsError, getTreeNoValidDataSourceError };
  1783. //# sourceMappingURL=tree.mjs.map