diff --git a/src/cdk/tree/tree.spec.ts b/src/cdk/tree/tree.spec.ts index 92d4356e0cf4..c71312c00815 100644 --- a/src/cdk/tree/tree.spec.ts +++ b/src/cdk/tree/tree.spec.ts @@ -380,6 +380,28 @@ describe('CdkTree', () => { .withContext(`Expect node collapsed`) .toBe(0); }); + + it('should not handle events coming from a descendant of a node', () => { + expect(dataSource.data.length).toBe(3); + + expect(getExpandedNodes(component.dataSource?.getRecursiveData(), component.tree).length) + .withContext('Expect no expanded node on init') + .toBe(0); + + const node = getNodes(treeElement)[2] as HTMLElement; + const input = document.createElement('input'); + node.appendChild(input); + + const event = createKeyboardEvent('keydown', undefined, 'ArrowRight'); + spyOn(event, 'preventDefault').and.callThrough(); + input.dispatchEvent(event); + fixture.detectChanges(); + + expect(getExpandedNodes(component.dataSource?.getRecursiveData(), component.tree).length) + .withContext('Expect no expanded node after event') + .toBe(0); + expect(event.preventDefault).not.toHaveBeenCalled(); + }); }); describe('with when node template', () => { diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 9d52bc521ffd..1e23605f8fb7 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -125,6 +125,7 @@ export class CdkTree OnDestroy, OnInit { + private _elementRef = inject(ElementRef); private _dir = inject(Directionality); /** Subject that emits when the component has been destroyed. */ @@ -889,8 +890,20 @@ export class CdkTree } /** `keydown` event handler; this just passes the event to the `TreeKeyManager`. */ - _sendKeydownToKeyManager(event: KeyboardEvent) { - this._keyManager.onKeydown(event); + protected _sendKeydownToKeyManager(event: KeyboardEvent): void { + // Only handle events directly on the tree or directly on one of the nodes, otherwise + // we risk interfering with events in the projected content (see #29828). + if (event.target === this._elementRef.nativeElement) { + this._keyManager.onKeydown(event); + } else { + const nodes = this._nodes.getValue(); + for (const [, node] of nodes) { + if (event.target === node._elementRef.nativeElement) { + this._keyManager.onKeydown(event); + break; + } + } + } } /** Gets all nested descendants of a given node. */ @@ -1341,7 +1354,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI private _changeDetectorRef = inject(ChangeDetectorRef); constructor( - protected _elementRef: ElementRef, + public _elementRef: ElementRef, protected _tree: CdkTree, ) { CdkTreeNode.mostRecentTreeNode = this as CdkTreeNode; diff --git a/tools/public_api_guard/cdk/tree.md b/tools/public_api_guard/cdk/tree.md index 73c45c1929a7..e808bc335cd9 100644 --- a/tools/public_api_guard/cdk/tree.md +++ b/tools/public_api_guard/cdk/tree.md @@ -119,7 +119,7 @@ export class CdkTree implements AfterContentChecked, AfterContentInit, _nodeOutlet: CdkTreeNodeOutlet; _registerNode(node: CdkTreeNode): void; renderNodeChanges(data: readonly T[], dataDiffer?: IterableDiffer, viewContainer?: ViewContainerRef, parentData?: T): void; - _sendKeydownToKeyManager(event: KeyboardEvent): void; + protected _sendKeydownToKeyManager(event: KeyboardEvent): void; _setNodeTypeIfUnset(nodeType: 'flat' | 'nested'): void; toggle(dataNode: T): void; toggleDescendants(dataNode: T): void; @@ -160,7 +160,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI readonly _dataChanges: Subject; protected readonly _destroyed: Subject; // (undocumented) - protected _elementRef: ElementRef; + _elementRef: ElementRef; // (undocumented) _emitExpansionState(expanded: boolean): void; expand(): void;