Skip to content

Commit f117074

Browse files
committed
Fixes sidebar focus loss when there are no results
1 parent 9ff4dff commit f117074

1 file changed

Lines changed: 52 additions & 49 deletions

File tree

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

Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -534,63 +534,66 @@ export class GlTreeGenerator extends GlElement {
534534
}
535535

536536
override render(): unknown {
537-
if (!this.treeItems?.length) {
538-
if (this._filterText && this._model?.length) {
539-
return html`${this.renderFilterBar()}
540-
<div class="no-results">No results found</div>`;
541-
}
542-
return nothing;
543-
}
537+
const hasItems = Boolean(this.treeItems?.length);
538+
const showNoResults = !hasItems && this._filterText && this._model?.length;
539+
540+
if (!hasItems && !showNoResults) return nothing;
544541

545542
// Container-focused approach: the scrollable div is the focusable element
546543
// Use aria-activedescendant to indicate which tree item is active for screen readers
547544
const activeDescendant = this._focusedItemPath ? `tree-item-${this._focusedItemPath}` : undefined;
548545

549-
// Use keyed directive to force virtualizer re-creation when model changes
550-
// This prevents stale DOM node references in the repeat directive
546+
// Keep a single top-level template across the has-results/no-results transition so Lit
547+
// patches in place — swapping top-level templates would tear down the filter input and
548+
// lose focus while the user is still typing.
551549
return html`
552550
${this.renderFilterBar()}
553-
<div
554-
${ref(this.scrollableRef)}
555-
class="scrollable"
556-
tabindex="0"
557-
role="tree"
558-
aria-label=${this.ariaLabel}
559-
aria-multiselectable="false"
560-
aria-activedescendant=${activeDescendant || nothing}
561-
@keydown=${this.handleContainerKeydown}
562-
@focus=${this.handleContainerFocus}
563-
@blur=${this.handleContainerBlur}
564-
>
565-
${keyed(
566-
this._virtualizerKey,
567-
html`<lit-virtualizer
568-
class="scrollable"
569-
${ref(this.virtualizerRef)}
570-
.items=${this.treeItems}
571-
.keyFunction=${(item: TreeModelFlat) => item.path}
572-
.layout=${flow({ direction: 'vertical' })}
573-
.renderItem=${(node: TreeModelFlat) => this.renderTreeItem(node)}
574-
scroller
575-
></lit-virtualizer>`,
576-
)}
577-
</div>
578-
${this._hoverOpen && this._hoveredTooltip
579-
? html`<gl-popover
580-
?open=${this._hoverOpen}
581-
.anchor=${this._hoveredAnchor}
582-
.placement=${this.tooltipAnchorRight ? 'right-start' : 'bottom'}
583-
trigger="manual"
584-
hoist
585-
.distance=${4}
586-
@mouseenter=${() => clearTimeout(this._unhoverTimer)}
587-
@mouseleave=${() => this.onTreeItemUnhover()}
588-
>
589-
<div slot="content" class="hover-content">
590-
<gl-markdown density="compact" .markdown=${this._hoveredTooltip ?? ''}></gl-markdown>
551+
${showNoResults
552+
? html`<div class="no-results">No results found</div>`
553+
: html`<div
554+
${ref(this.scrollableRef)}
555+
class="scrollable"
556+
tabindex="0"
557+
role="tree"
558+
aria-label=${this.ariaLabel}
559+
aria-multiselectable="false"
560+
aria-activedescendant=${activeDescendant || nothing}
561+
@keydown=${this.handleContainerKeydown}
562+
@focus=${this.handleContainerFocus}
563+
@blur=${this.handleContainerBlur}
564+
>
565+
${keyed(
566+
this._virtualizerKey,
567+
html`<lit-virtualizer
568+
class="scrollable"
569+
${ref(this.virtualizerRef)}
570+
.items=${this.treeItems}
571+
.keyFunction=${(item: TreeModelFlat) => item.path}
572+
.layout=${flow({ direction: 'vertical' })}
573+
.renderItem=${(node: TreeModelFlat) => this.renderTreeItem(node)}
574+
scroller
575+
></lit-virtualizer>`,
576+
)}
591577
</div>
592-
</gl-popover>`
593-
: nothing}
578+
${this._hoverOpen && this._hoveredTooltip
579+
? html`<gl-popover
580+
?open=${this._hoverOpen}
581+
.anchor=${this._hoveredAnchor}
582+
.placement=${this.tooltipAnchorRight ? 'right-start' : 'bottom'}
583+
trigger="manual"
584+
hoist
585+
.distance=${4}
586+
@mouseenter=${() => clearTimeout(this._unhoverTimer)}
587+
@mouseleave=${() => this.onTreeItemUnhover()}
588+
>
589+
<div slot="content" class="hover-content">
590+
<gl-markdown
591+
density="compact"
592+
.markdown=${this._hoveredTooltip ?? ''}
593+
></gl-markdown>
594+
</div>
595+
</gl-popover>`
596+
: nothing}`}
594597
`;
595598
}
596599

0 commit comments

Comments
 (0)