Skip to content

Commit dfc6ae8

Browse files
committed
Fixes tree virtualizer rendering crash
- Prevents stale DOM node references in the virtualized tree view. - Uses keyed directive to force `lit-virtualizer` re-creation when the underlying tree model changes
1 parent 75a3dfa commit dfc6ae8

File tree

1 file changed

+27
-9
lines changed

1 file changed

+27
-9
lines changed

src/webviews/apps/shared/components/tree/tree-generator.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { flow } from '@lit-labs/virtualizer/layouts/flow.js';
22
import { css, html, nothing } from 'lit';
33
import { customElement, property, state } from 'lit/decorators.js';
4+
import { keyed } from 'lit/directives/keyed.js';
45
import type { Ref } from 'lit/directives/ref.js';
56
import { createRef, ref } from 'lit/directives/ref.js';
67
import { when } from 'lit/directives/when.js';
@@ -71,6 +72,9 @@ export class GlTreeGenerator extends GlElement {
7172
@state()
7273
treeItems?: TreeModelFlat[] = undefined;
7374

75+
@state()
76+
private _virtualizerKey = 0;
77+
7478
@property({ reflect: true })
7579
guides?: 'none' | 'onHover' | 'always';
7680

@@ -143,6 +147,10 @@ export class GlTreeGenerator extends GlElement {
143147
// This prevents stale node references when switching commits or toggling filters
144148
this._nodeMap.clear();
145149

150+
// Increment virtualizer key to force complete re-render
151+
// This prevents stale DOM node references in the repeat directive
152+
this._virtualizerKey++;
153+
146154
// Build both maps during tree flattening (single traversal)
147155
let treeItems: TreeModelFlat[] | undefined;
148156
if (this._model != null) {
@@ -271,6 +279,8 @@ export class GlTreeGenerator extends GlElement {
271279
// Use aria-activedescendant to indicate which tree item is active for screen readers
272280
const activeDescendant = this._focusedItemPath ? `tree-item-${this._focusedItemPath}` : undefined;
273281

282+
// Use keyed directive to force virtualizer re-creation when model changes
283+
// This prevents stale DOM node references in the repeat directive
274284
return html`
275285
<div
276286
${ref(this.scrollableRef)}
@@ -284,14 +294,18 @@ export class GlTreeGenerator extends GlElement {
284294
@focus=${this.handleContainerFocus}
285295
@blur=${this.handleContainerBlur}
286296
>
287-
<lit-virtualizer
288-
${ref(this.virtualizerRef)}
289-
.items=${this.treeItems}
290-
.keyFunction=${(item: TreeModelFlat) => item.path}
291-
.layout=${flow({ direction: 'vertical' })}
292-
.renderItem=${(node: TreeModelFlat) => this.renderTreeItem(node)}
293-
.scroller=${Boolean(this.scrollableRef.value)}
294-
></lit-virtualizer>
297+
${keyed(
298+
this._virtualizerKey,
299+
html`<lit-virtualizer
300+
class="scrollable"
301+
${ref(this.virtualizerRef)}
302+
.items=${this.treeItems}
303+
.keyFunction=${(item: TreeModelFlat) => item.path}
304+
.layout=${flow({ direction: 'vertical' })}
305+
.renderItem=${(node: TreeModelFlat) => this.renderTreeItem(node)}
306+
scroller
307+
></lit-virtualizer>`,
308+
)}
295309
</div>
296310
`;
297311
}
@@ -317,9 +331,13 @@ export class GlTreeGenerator extends GlElement {
317331
private rebuildFlattenedTree() {
318332
if (!this._model) return;
319333

334+
// Clear stale node map before processing new model
335+
// This prevents stale node references when expanding/collapsing nodes
336+
this._nodeMap.clear();
337+
320338
const size = this._model.length;
321339
const newTreeItems = this._model.reduce<TreeModelFlat[]>((acc, node, index) => {
322-
acc.push(...flattenTree(node, size, index + 1));
340+
acc.push(...flattenTree(node, size, index + 1, undefined, this._nodeMap));
323341
return acc;
324342
}, []);
325343

0 commit comments

Comments
 (0)