From 883ed2fb80c55a2d36f826e283f69bd9afc3cc3d Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Fri, 18 Apr 2025 16:55:25 -0400 Subject: [PATCH] fixing the auto focus issue with search --- .../vault-header/vault-header-v2.component.ts | 6 ++++++ .../vault-search/vault-v2-search.component.ts | 9 +++++++-- .../components/vault-v2/vault-v2.component.ts | 14 ++++++++++++++ libs/components/src/index.ts | 1 + libs/components/src/search/search.component.ts | 4 ++++ 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts index bcea2e76190..ed611d0a263 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -34,6 +34,12 @@ import { VaultV2SearchComponent } from "../vault-search/vault-v2-search.componen export class VaultHeaderV2Component { @ViewChild(DisclosureComponent) disclosure: DisclosureComponent; + @ViewChild(VaultV2SearchComponent) searchComponent!: VaultV2SearchComponent; + + focusSearch(): void { + this.searchComponent?.focus(); + } + /** Emits the visibility status of the disclosure component. */ protected isDisclosureShown$ = this.vaultPopupListFiltersService.filterVisibilityState$.pipe( runInsideAngular(inject(NgZone)), // Browser state updates can happen outside of `ngZone` diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index 32f5611f436..a9a0d0cdaf7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -1,11 +1,11 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { Subject, Subscription, debounceTime, filter } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { SearchModule } from "@bitwarden/components"; +import { SearchModule, SearchComponent } from "@bitwarden/components"; import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; @@ -21,6 +21,11 @@ export class VaultV2SearchComponent { searchText: string = ""; private searchText$ = new Subject(); + @ViewChild(SearchComponent) bitSearchRef!: SearchComponent; + + focus(): void { + this.bitSearchRef?.focus(); + } constructor(private vaultPopupItemsService: VaultPopupItemsService) { this.subscribeToLatestSearchText(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 7f5242dcf18..b5698ef0a95 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -79,6 +79,7 @@ enum VaultState { }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; + @ViewChild(VaultHeaderV2Component) vaultHeaderRef!: VaultHeaderV2Component; cipherType = CipherType; @@ -166,6 +167,19 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { this.vaultScrollPositionService.start(this.virtualScrollElement!); }); } + + // 🔍 Focus the search input after loading completes and the vault is not empty + combineLatest([this.loading$, this.vaultPopupItemsService.emptyVault$]) + .pipe( + filter(([loading, isEmpty]) => !loading && !isEmpty), + take(1), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(() => { + setTimeout(() => { + this.vaultHeaderRef?.focusSearch(); + }); + }); } async ngOnInit() { diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 319b60e6435..7987adef586 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -20,6 +20,7 @@ export * from "./drawer"; export * from "./form-field"; export * from "./icon-button"; export * from "./icon"; +export * from "./search/search.component"; export * from "./input"; export * from "./item"; export * from "./layout"; diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 7f1bd781e9d..64c809fe8b2 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -39,6 +39,10 @@ export class SearchComponent implements ControlValueAccessor, FocusableElement { @ViewChild("input") private input: ElementRef; + focus(): void { + this.getFocusTarget()?.focus(); + } + protected id = `search-id-${nextId++}`; protected searchText: string; // Use `type="text"` for Safari to improve rendering performance