diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index 2361f8873..61b3351b7 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -1,6 +1,6 @@
import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener, ElementRef } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
-import { Router } from '@angular/router';
+import { EventType, NavigationStart, Router } from '@angular/router';
import { AssetsService } from '../../services/assets.service';
import { StateService } from '../../services/state.service';
import { Observable, of, Subject, zip, BehaviorSubject, combineLatest } from 'rxjs';
@@ -47,6 +47,8 @@ export class SearchFormComponent implements OnInit {
this.handleKeyDown($event);
}
+ @ViewChild('searchInput') searchInput: ElementRef;
+
constructor(
private formBuilder: UntypedFormBuilder,
private router: Router,
@@ -55,11 +57,26 @@ export class SearchFormComponent implements OnInit {
private electrsApiService: ElectrsApiService,
private apiService: ApiService,
private relativeUrlPipe: RelativeUrlPipe,
- private elementRef: ElementRef,
- ) { }
+ private elementRef: ElementRef
+ ) {
+ }
ngOnInit(): void {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
+
+ this.router.events.subscribe((e: NavigationStart) => { // Reset search focus when changing page
+ if (this.searchInput && e.type === EventType.NavigationStart) {
+ this.searchInput.nativeElement.blur();
+ }
+ });
+
+ this.stateService.searchFocus$.subscribe(() => {
+ if (!this.searchInput) { // Try again a bit later once the view is properly initialized
+ setTimeout(() => this.searchInput.nativeElement.focus(), 100);
+ } else if (this.searchInput) {
+ this.searchInput.nativeElement.focus();
+ }
+ });
this.searchForm = this.formBuilder.group({
searchText: ['', Validators.required],
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts
index aca3593d7..05381453d 100644
--- a/frontend/src/app/dashboard/dashboard.component.ts
+++ b/frontend/src/app/dashboard/dashboard.component.ts
@@ -1,7 +1,7 @@
-import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { combineLatest, merge, Observable, of, Subscription } from 'rxjs';
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
-import { BlockExtended, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
+import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface';
import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
@@ -31,7 +31,7 @@ interface MempoolStatsData {
styleUrls: ['./dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class DashboardComponent implements OnInit, OnDestroy {
+export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
featuredAssets$: Observable
;
network$: Observable;
mempoolBlocksData$: Observable;
@@ -57,6 +57,10 @@ export class DashboardComponent implements OnInit, OnDestroy {
private seoService: SeoService
) { }
+ ngAfterViewInit(): void {
+ this.stateService.focusSearchInputDesktop();
+ }
+
ngOnDestroy(): void {
this.currencySubscription.unsubscribe();
this.websocketService.stopTrackRbfSummary();
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts
index 6fa4b454c..e58d5f124 100644
--- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts
+++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { share } from 'rxjs/operators';
import { INodesRanking } from '../../interfaces/node-api.interface';
@@ -12,7 +12,7 @@ import { LightningApiService } from '../lightning-api.service';
styleUrls: ['./lightning-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class LightningDashboardComponent implements OnInit {
+export class LightningDashboardComponent implements OnInit, AfterViewInit {
statistics$: Observable;
nodesRanking$: Observable;
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
@@ -30,4 +30,7 @@ export class LightningDashboardComponent implements OnInit {
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());
}
+ ngAfterViewInit(): void {
+ this.stateService.focusSearchInputDesktop();
+ }
}
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index ea00f12ab..9ab8a7e93 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -7,6 +7,7 @@ import { Router, NavigationStart } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
import { filter, map, scan, shareReplay } from 'rxjs/operators';
import { StorageService } from './storage.service';
+import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
export interface MarkBlockState {
blockHeight?: number;
@@ -139,6 +140,8 @@ export class StateService {
fiatCurrency$: BehaviorSubject;
rateUnits$: BehaviorSubject;
+ searchFocus$: Subject = new Subject();
+
constructor(
@Inject(PLATFORM_ID) private platformId: any,
@Inject(LOCALE_ID) private locale: string,
@@ -356,4 +359,10 @@ export class StateService {
this.blocks = this.blocks.slice(0, this.env.KEEP_BLOCKS_AMOUNT);
this.blocksSubject$.next(this.blocks);
}
+
+ focusSearchInputDesktop() {
+ if (!hasTouchScreen()) {
+ this.searchFocus$.next(true);
+ }
+ }
}
diff --git a/frontend/src/app/shared/pipes/bytes-pipe/utils.ts b/frontend/src/app/shared/pipes/bytes-pipe/utils.ts
index fc8c2b08f..2700be45d 100644
--- a/frontend/src/app/shared/pipes/bytes-pipe/utils.ts
+++ b/frontend/src/app/shared/pipes/bytes-pipe/utils.ts
@@ -309,3 +309,28 @@ export function takeWhile(input: any[], predicate: CollectionPredicate) {
return takeUntil(input, (item: any, index: number | undefined, collection: any[] | undefined) =>
!predicate(item, index, collection));
}
+
+// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
+export function hasTouchScreen(): boolean {
+ let hasTouchScreen = false;
+ if ('maxTouchPoints' in navigator) {
+ hasTouchScreen = navigator.maxTouchPoints > 0;
+ } else if ('msMaxTouchPoints' in navigator) {
+ // @ts-ignore
+ hasTouchScreen = navigator.msMaxTouchPoints > 0;
+ } else {
+ const mQ = matchMedia?.('(pointer:coarse)');
+ if (mQ?.media === '(pointer:coarse)') {
+ hasTouchScreen = !!mQ.matches;
+ } else if ('orientation' in window) {
+ hasTouchScreen = true; // deprecated, but good fallback
+ } else {
+ // @ts-ignore - Only as a last resort, fall back to user agent sniffing
+ const UA = navigator.userAgent;
+ hasTouchScreen =
+ /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
+ /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA);
+ }
+ }
+ return hasTouchScreen;
+}
\ No newline at end of file