(tNode: TNode, newElement: HTMLElement, newLView?: LView)
| 176 | * `TNode`) from the same logical view being re-rendered into a new DOM parent. |
| 177 | */ |
| 178 | export function cancelLeavingNodes(tNode: TNode, newElement: HTMLElement, newLView?: LView): void { |
| 179 | const nodes = leavingNodes.get(tNode); |
| 180 | if (!nodes || nodes.length === 0) return; |
| 181 | |
| 182 | const newParent = newElement.parentNode; |
| 183 | const prevSibling = newElement.previousSibling; |
| 184 | const newDeclarationView = getDeclarationView(newLView); |
| 185 | |
| 186 | for (let i = nodes.length - 1; i >= 0; i--) { |
| 187 | const {el: leavingEl, declarationView: leavingDeclarationView} = nodes[i]; |
| 188 | const leavingParent = leavingEl.parentNode; |
| 189 | // Cancel if the leaving element is: |
| 190 | // - The direct previousSibling of the new element. This is reliable |
| 191 | // because Angular inserts new elements at the same position (before |
| 192 | // the container anchor) where the leaving element was, making them |
| 193 | // always adjacent. Covers @if toggling and same-VCR toggling. |
| 194 | // - The leaving element IS the new element. This happens when a node is moved |
| 195 | // (e.g., drag-and-drop reordering). We must cancel its pending leave animation |
| 196 | // and ensure it's not physically removed from the DOM by marking it as reused. |
| 197 | if (leavingEl === newElement) { |
| 198 | nodes.splice(i, 1); |
| 199 | reusedNodes.add(leavingEl); |
| 200 | leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}})); |
| 201 | } else if (prevSibling && leavingEl === prevSibling) { |
| 202 | nodes.splice(i, 1); |
| 203 | leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}})); |
| 204 | leavingEl.parentNode?.removeChild(leavingEl); |
| 205 | } else if (leavingParent && newParent && leavingParent !== newParent) { |
| 206 | // The leaving element is in a different DOM parent than the entering one. |
| 207 | // This is ambiguous: it can be the same logical view re-rendered into a new |
| 208 | // container (e.g. a dynamic component re-created in a fresh CDK overlay |
| 209 | // pane), which must be de-duplicated by removing it immediately; or it can |
| 210 | // be a *distinct* instance of the same template (accordions, exclusive- |
| 211 | // expansion menus, master/detail nav) that merely shares this `TNode` and |
| 212 | // is legitimately leaving in its own parent. Only force-remove when the |
| 213 | // entering element belongs to the SAME declaration view as the leaving one |
| 214 | // — i.e. a true re-render of the same logical view. Distinct instances are |
| 215 | // left alone so their `animate.leave` runs to completion. |
| 216 | const sameLogicalView = |
| 217 | newDeclarationView === null || |
| 218 | leavingDeclarationView === null || |
| 219 | newDeclarationView === leavingDeclarationView; |
| 220 | if (sameLogicalView) { |
| 221 | nodes.splice(i, 1); |
| 222 | leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}})); |
| 223 | leavingEl.parentNode?.removeChild(leavingEl); |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /** |
| 230 | * Tracks the nodes list of nodes that are leaving the DOM so we can cancel any leave animations |
no test coverage detected
searching dependent graphs…