@@ -9,18 +9,18 @@ import {FocusTrap} from '@angular/cdk/a11y';
99import { OverlayRef , OverlaySizeConfig , PositionStrategy } from '@angular/cdk/overlay' ;
1010import { TemplatePortal } from '@angular/cdk/portal' ;
1111import {
12- afterRender ,
12+ afterNextRender ,
1313 AfterViewInit ,
1414 Directive ,
1515 ElementRef ,
1616 EmbeddedViewRef ,
17+ inject ,
18+ ListenerOptions ,
1719 NgZone ,
1820 OnDestroy ,
21+ Renderer2 ,
1922 TemplateRef ,
2023 ViewContainerRef ,
21- inject ,
22- Renderer2 ,
23- ListenerOptions ,
2424} from '@angular/core' ;
2525import { merge , Observable , Subject } from 'rxjs' ;
2626import {
@@ -35,8 +35,10 @@ import {
3535 withLatestFrom ,
3636} from 'rxjs/operators' ;
3737
38+ import { _bindEventWithOptions } from '@angular/cdk/platform' ;
3839import { CELL_SELECTOR , EDIT_PANE_CLASS , EDIT_PANE_SELECTOR , ROW_SELECTOR } from './constants' ;
3940import { EditEventDispatcher , HoverContentState } from './edit-event-dispatcher' ;
41+ import { EditRef } from './edit-ref' ;
4042import { EditServices } from './edit-services' ;
4143import { FocusDispatcher } from './focus-dispatcher' ;
4244import {
@@ -45,8 +47,6 @@ import {
4547 FocusEscapeNotifierFactory ,
4648} from './focus-escape-notifier' ;
4749import { closest } from './polyfill' ;
48- import { EditRef } from './edit-ref' ;
49- import { _bindEventWithOptions } from '@angular/cdk/platform' ;
5050
5151/**
5252 * Describes the number of columns before and after the originating cell that the
@@ -61,6 +61,23 @@ export interface CdkPopoverEditColspan {
6161/** Used for rate-limiting mousemove events. */
6262const MOUSE_MOVE_THROTTLE_TIME_MS = 10 ;
6363
64+ function hasRowElement ( nl : NodeList ) {
65+ for ( let i = 0 ; i < nl . length ; i ++ ) {
66+ const el = nl [ i ] ;
67+ if ( ! ( el instanceof HTMLElement ) ) {
68+ continue ;
69+ }
70+ if ( el . matches ( ROW_SELECTOR ) ) {
71+ return true ;
72+ }
73+ }
74+ return false ;
75+ }
76+
77+ function isRowMutation ( mutation : MutationRecord ) : boolean {
78+ return hasRowElement ( mutation . addedNodes ) || hasRowElement ( mutation . removedNodes ) ;
79+ }
80+
6481/**
6582 * A directive that must be attached to enable editability on a table.
6683 * It is responsible for setting up delegated event handlers and providing the
@@ -80,11 +97,25 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
8097
8198 protected readonly destroyed = new Subject < void > ( ) ;
8299
83- private _rendered = new Subject ( ) ;
100+ private _rowsRendered = new Subject ( ) ;
101+
102+ private _rowMutationObserver = globalThis . MutationObserver
103+ ? new globalThis . MutationObserver ( mutations => {
104+ if ( mutations . some ( isRowMutation ) ) {
105+ this . _rowsRendered . next ( ) ;
106+ }
107+ } )
108+ : null ;
84109
85110 constructor ( ) {
86- afterRender ( ( ) => {
87- this . _rendered . next ( ) ;
111+ // TODO: consider a design where instead of polling for row changes we just use
112+ // afterRenderEffect + a signal of the rows.
113+ afterNextRender ( ( ) => {
114+ this . _rowsRendered . next ( ) ;
115+ this . _rowMutationObserver ?. observe ( this . elementRef . nativeElement , {
116+ childList : true ,
117+ subtree : true ,
118+ } ) ;
88119 } ) ;
89120 }
90121
@@ -95,7 +126,7 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
95126 ngOnDestroy ( ) : void {
96127 this . destroyed . next ( ) ;
97128 this . destroyed . complete ( ) ;
98- this . _rendered . complete ( ) ;
129+ this . _rowMutationObserver ?. disconnect ( ) ;
99130 }
100131
101132 private _observableFromEvent < T extends Event > (
@@ -153,9 +184,10 @@ export class CdkEditable implements AfterViewInit, OnDestroy {
153184 // Keep track of rows within the table. This is used to know which rows with hover content
154185 // are first or last in the table. They are kept focusable in case focus enters from above
155186 // or below the table.
156- this . _rendered
187+ this . _rowsRendered
157188 . pipe (
158189 // Avoid some timing inconsistencies since Angular v19.
190+ // TODO: see if we can remove this now that we're using MutationObserver.
159191 debounceTime ( 0 ) ,
160192 // Optimization: ignore dom changes while focus is within the table as we already
161193 // ensure that rows above and below the focused/active row are tabbable.
0 commit comments