diff --git a/src/cdk/tree/tree.spec.ts b/src/cdk/tree/tree.spec.ts index 3941db8e423f..57c17383c1fe 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 4eb0779056f5..a8eb66c1e1f4 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -127,6 +127,7 @@ export class CdkTree { private _differs = inject(IterableDiffers); private _changeDetectorRef = inject(ChangeDetectorRef); + private _elementRef = inject(ElementRef); private _dir = inject(Directionality); @@ -890,8 +891,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. */ @@ -1155,7 +1168,7 @@ export class CdkTree standalone: true, }) export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerItem { - protected _elementRef = inject>(ElementRef); + _elementRef = inject>(ElementRef); protected _tree = inject>(CdkTree); protected _tabindex: number | null = -1; diff --git a/tools/public_api_guard/cdk/tree.md b/tools/public_api_guard/cdk/tree.md index b875f809c71c..655f0f2c5456 100644 --- a/tools/public_api_guard/cdk/tree.md +++ b/tools/public_api_guard/cdk/tree.md @@ -117,7 +117,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; @@ -158,7 +158,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;