From 6f0b202e309267341b044466183f172b489e2fd1 Mon Sep 17 00:00:00 2001 From: OscarG673 Date: Wed, 18 Feb 2026 15:35:48 -0600 Subject: [PATCH 1/3] fix: transaction details tooltip --- .../block-overview-tooltip.component.ts | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index 08f7bc7d8..a98a0733a 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -36,6 +36,8 @@ export class BlockOverviewTooltipComponent implements OnChanges { tooltipPosition: Position = { x: 0, y: 0 }; + positionRafId: number | null = null; + @ViewChild('tooltip') tooltipElement: ElementRef; constructor( @@ -44,19 +46,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { ngOnChanges(changes): void { if (changes.cursorPosition && changes.cursorPosition.currentValue) { - let x = changes.cursorPosition.currentValue.x + 10; - let y = changes.cursorPosition.currentValue.y + 10; - if (this.tooltipElement) { - const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); - const parentBounds = this.tooltipElement.nativeElement.offsetParent.getBoundingClientRect(); - if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { - x = Math.max(0, parentBounds.width - elementBounds.width - 10); - } - if (y + elementBounds.height > parentBounds.height) { - y = y - elementBounds.height - 20; - } - } - this.tooltipPosition = { x, y }; + this.updatePosition(); } if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) { @@ -94,10 +84,42 @@ export class BlockOverviewTooltipComponent implements OnChanges { } } + if (this.cursorPosition) { + this.tooltipPosition = { x: this.cursorPosition.x + 10, y: this.cursorPosition.y + 10 }; + } + this.schedulePositionAfterLayout(); this.cd.markForCheck(); } } + updatePosition(): void { + if (!this.cursorPosition) return; + let x = this.cursorPosition.x + 10; + let y = this.cursorPosition.y + 10; + if (this.tooltipElement?.nativeElement?.offsetParent) { + const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); + const parentBounds = this.tooltipElement.nativeElement.offsetParent.getBoundingClientRect(); + if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { + x = Math.max(0, parentBounds.width - elementBounds.width - 10); + } + if (y + elementBounds.height > parentBounds.height) { + y = y - elementBounds.height - 20; + } + } + this.tooltipPosition = { x, y }; + } + + schedulePositionAfterLayout(): void { + if (this.positionRafId != null) cancelAnimationFrame(this.positionRafId); + this.positionRafId = requestAnimationFrame(() => { + this.positionRafId = null; + if (this.tx && this.cursorPosition && this.tooltipElement?.nativeElement?.offsetParent) { + this.updatePosition(); + this.cd.markForCheck(); + } + }); + } + getTooltipLeftPosition(): string { return window.innerWidth < 392 ? '-50px' : this.tooltipPosition.x + 'px'; } From 02290cddb37eb349fcca65a285466e1f28805290 Mon Sep 17 00:00:00 2001 From: OscarG673 Date: Tue, 24 Feb 2026 21:07:44 -0600 Subject: [PATCH 2/3] fix: tooltip shake --- .../block-overview-tooltip.component.ts | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index a98a0733a..a03d3174a 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -1,17 +1,18 @@ -import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { Component, ElementRef, ViewChild, Input, OnChanges, AfterViewChecked, ChangeDetectorRef, ApplicationRef } from '@angular/core'; import { Position } from '@components/block-overview-graph/sprite-types.js'; import { Price } from '@app/services/price.service'; import { TransactionStripped } from '@interfaces/node-api.interface.js'; import { Filter, FilterMode, TransactionFlags, toFilters } from '@app/shared/filters.utils'; import { Block } from '@interfaces/electrs.interface.js'; + @Component({ selector: 'app-block-overview-tooltip', templateUrl: './block-overview-tooltip.component.html', styleUrls: ['./block-overview-tooltip.component.scss'], standalone: false, }) -export class BlockOverviewTooltipComponent implements OnChanges { +export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecked { @Input() tx: TransactionStripped | void; @Input() relativeTime?: number; @Input() cursorPosition: Position; @@ -21,6 +22,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { @Input() filterFlags: bigint | null = null; @Input() filterMode: FilterMode = 'and'; + txid = ''; time: number = 0; fee = 0; @@ -34,21 +36,30 @@ export class BlockOverviewTooltipComponent implements OnChanges { filters: Filter[] = []; activeFilters: { [key: string]: boolean } = {}; - tooltipPosition: Position = { x: 0, y: 0 }; - positionRafId: number | null = null; + + + tooltipPosition: Position = { x: 0, y: 0 }; + private pendingPositionRecalc = false; + + + @ViewChild('tooltip') tooltipElement: ElementRef; + constructor( private cd: ChangeDetectorRef, - ) {} + private appRef: ApplicationRef, + ) { } + ngOnChanges(changes): void { if (changes.cursorPosition && changes.cursorPosition.currentValue) { - this.updatePosition(); + this.updateTooltipPosition(changes.cursorPosition.currentValue, false); } + if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) { this.txid = this.tx.txid || ''; this.time = this.tx.time || 0; @@ -69,6 +80,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { } } + if (!this.relativeTime) { this.timeMode = 'mempool'; } else { @@ -84,41 +96,63 @@ export class BlockOverviewTooltipComponent implements OnChanges { } } - if (this.cursorPosition) { - this.tooltipPosition = { x: this.cursorPosition.x + 10, y: this.cursorPosition.y + 10 }; - } - this.schedulePositionAfterLayout(); + this.cd.markForCheck(); + if (this.cursorPosition) { + this.pendingPositionRecalc = true; + } } } - updatePosition(): void { - if (!this.cursorPosition) return; - let x = this.cursorPosition.x + 10; - let y = this.cursorPosition.y + 10; + + ngAfterViewChecked(): void { + if (this.pendingPositionRecalc && this.cursorPosition && this.tx) { + this.pendingPositionRecalc = false; + requestAnimationFrame(() => { + this.updateTooltipPosition(this.cursorPosition, true); + this.appRef.tick(); + }); + } + } + + + private updateTooltipPosition(cursor: Position, yOnly: boolean = false): void { + const baseX = cursor.x; + const baseY = cursor.y; + let x = baseX + 10; + let y = baseY + 10; if (this.tooltipElement?.nativeElement?.offsetParent) { const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); const parentBounds = this.tooltipElement.nativeElement.offsetParent.getBoundingClientRect(); - if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { - x = Math.max(0, parentBounds.width - elementBounds.width - 10); + const belowY = baseY + 10; + const aboveY = baseY - elementBounds.height - 20; + const maxYInParent = parentBounds.height - elementBounds.height; + + + if (!yOnly) { + if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { + x = Math.max(0, parentBounds.width - elementBounds.width - 10); + } } - if (y + elementBounds.height > parentBounds.height) { - y = y - elementBounds.height - 20; + + + if (belowY <= maxYInParent) { + y = belowY; + } else if (aboveY <= maxYInParent) { + y = aboveY; + } else { + y = maxYInParent; } + y = Math.min(y, maxYInParent); } - this.tooltipPosition = { x, y }; + if (yOnly) { + this.tooltipPosition = { ...this.tooltipPosition, y }; + } else { + this.tooltipPosition = { x, y }; + } + this.cd.markForCheck(); } - schedulePositionAfterLayout(): void { - if (this.positionRafId != null) cancelAnimationFrame(this.positionRafId); - this.positionRafId = requestAnimationFrame(() => { - this.positionRafId = null; - if (this.tx && this.cursorPosition && this.tooltipElement?.nativeElement?.offsetParent) { - this.updatePosition(); - this.cd.markForCheck(); - } - }); - } getTooltipLeftPosition(): string { return window.innerWidth < 392 ? '-50px' : this.tooltipPosition.x + 'px'; From 056e358584664dc4eb38cd3a97f27a82d211c2c3 Mon Sep 17 00:00:00 2001 From: OscarG673 Date: Fri, 27 Feb 2026 22:34:54 -0600 Subject: [PATCH 3/3] fix: review comments --- .../block-overview-tooltip.component.ts | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index a03d3174a..d1f9d92c8 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -5,14 +5,13 @@ import { TransactionStripped } from '@interfaces/node-api.interface.js'; import { Filter, FilterMode, TransactionFlags, toFilters } from '@app/shared/filters.utils'; import { Block } from '@interfaces/electrs.interface.js'; - @Component({ selector: 'app-block-overview-tooltip', templateUrl: './block-overview-tooltip.component.html', styleUrls: ['./block-overview-tooltip.component.scss'], standalone: false, }) -export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecked { +export class BlockOverviewTooltipComponent implements OnChanges { @Input() tx: TransactionStripped | void; @Input() relativeTime?: number; @Input() cursorPosition: Position; @@ -22,7 +21,6 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke @Input() filterFlags: bigint | null = null; @Input() filterMode: FilterMode = 'and'; - txid = ''; time: number = 0; fee = 0; @@ -36,30 +34,20 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke filters: Filter[] = []; activeFilters: { [key: string]: boolean } = {}; - - - tooltipPosition: Position = { x: 0, y: 0 }; - private pendingPositionRecalc = false; - - - @ViewChild('tooltip') tooltipElement: ElementRef; - constructor( private cd: ChangeDetectorRef, private appRef: ApplicationRef, ) { } - ngOnChanges(changes): void { if (changes.cursorPosition && changes.cursorPosition.currentValue) { this.updateTooltipPosition(changes.cursorPosition.currentValue, false); } - if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) { this.txid = this.tx.txid || ''; this.time = this.tx.time || 0; @@ -79,8 +67,6 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke this.activeFilters[filter.key] = true; } } - - if (!this.relativeTime) { this.timeMode = 'mempool'; } else { @@ -95,27 +81,20 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke } } } - - this.cd.markForCheck(); if (this.cursorPosition) { - this.pendingPositionRecalc = true; + this.schedulePositionRecalc(); } } } - - ngAfterViewChecked(): void { - if (this.pendingPositionRecalc && this.cursorPosition && this.tx) { - this.pendingPositionRecalc = false; - requestAnimationFrame(() => { - this.updateTooltipPosition(this.cursorPosition, true); - this.appRef.tick(); - }); - } + private schedulePositionRecalc(): void { + requestAnimationFrame(() => { + this.updateTooltipPosition(this.cursorPosition, true); + this.appRef.tick(); + }); } - private updateTooltipPosition(cursor: Position, yOnly: boolean = false): void { const baseX = cursor.x; const baseY = cursor.y; @@ -128,14 +107,12 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke const aboveY = baseY - elementBounds.height - 20; const maxYInParent = parentBounds.height - elementBounds.height; - if (!yOnly) { if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { x = Math.max(0, parentBounds.width - elementBounds.width - 10); } } - if (belowY <= maxYInParent) { y = belowY; } else if (aboveY <= maxYInParent) { @@ -143,8 +120,8 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke } else { y = maxYInParent; } - y = Math.min(y, maxYInParent); } + if (yOnly) { this.tooltipPosition = { ...this.tooltipPosition, y }; } else { @@ -153,7 +130,6 @@ export class BlockOverviewTooltipComponent implements OnChanges, AfterViewChecke this.cd.markForCheck(); } - getTooltipLeftPosition(): string { return window.innerWidth < 392 ? '-50px' : this.tooltipPosition.x + 'px'; }