mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(dashboard): tell pointer focus apart without touching the DOM
FocusMonitor toggles cdk-*-focused classes on the monitored track, so a mouse press on a source card mutated the DOM before the click. The J2 "open a source" performance journey rejects iterations with DOM activity between its settle snapshot and the click. Read the input modality from the CDK InputModalityDetector in a focusin handler instead: it only listens, so the rail stays untouched until the click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d9427ef9c4
commit
58e6c3a853
2 files changed
+17
-18
No files matched your search
@@ -50,6 +50,7 @@
|
||||
class="rail__track"
|
||||
[attr.data-layout]="layout()"
|
||||
(scroll)="onScroll()"
|
||||
(focusin)="onTrackFocusIn($event)"
|
||||
role="list"
|
||||
>
|
||||
@for (card of items(); track card.id) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
viewChild,
|
||||
viewChildren,
|
||||
} from '@angular/core';
|
||||
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||
import { InputModalityDetector } from '@angular/cdk/a11y';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
@@ -135,7 +135,7 @@ export interface DashboardRailActionSelection {
|
||||
})
|
||||
export class DashboardRailComponent implements AfterViewInit, OnDestroy {
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
private readonly focusMonitor = inject(FocusMonitor);
|
||||
private readonly inputModality = inject(InputModalityDetector);
|
||||
|
||||
readonly label = input.required<string>();
|
||||
readonly items = input.required<DashboardRailCard[]>();
|
||||
@@ -210,19 +210,10 @@ export class DashboardRailComponent implements AfterViewInit, OnDestroy {
|
||||
this.updateScrollState()
|
||||
);
|
||||
this.resizeObserver.observe(this.track().nativeElement);
|
||||
// Every focus inside the track, with where it came from. A mouse or
|
||||
// touch press focuses the card it lands on too, but scrolling then
|
||||
// would slide the card from under the pointer and lose the click.
|
||||
this.focusMonitor.monitor(this.track(), true).subscribe((origin) => {
|
||||
if (origin === 'keyboard' || origin === 'program') {
|
||||
this.revealFocusedCard();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.resizeObserver?.disconnect();
|
||||
this.focusMonitor.stopMonitoring(this.track());
|
||||
this.intersectionObserver?.disconnect();
|
||||
this.cancelPendingReset();
|
||||
}
|
||||
@@ -296,7 +287,8 @@ export class DashboardRailComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
/**
|
||||
* Brings a card that receives keyboard or programmatic focus (Tab or
|
||||
* `focus()`) fully into view.
|
||||
* `focus()`) fully into view; focus that follows a mouse or touch press
|
||||
* leaves the rail where it is.
|
||||
* Chromium skips its own focus scroll when 32px or more of the element
|
||||
* already shows, which left a card partly hidden under the edge fade
|
||||
* when the rail overflows by less than a card. A plain "nearest" scroll
|
||||
@@ -305,13 +297,19 @@ export class DashboardRailComponent implements AfterViewInit, OnDestroy {
|
||||
* snap position that reveals the whole card. The visible area is the
|
||||
* viewport, not the track, whose padding bleeds under the fades.
|
||||
*/
|
||||
private revealFocusedCard(): void {
|
||||
const track = this.track().nativeElement;
|
||||
onTrackFocusIn(event: FocusEvent): void {
|
||||
// A mouse or touch press focuses the card it lands on too, but
|
||||
// scrolling then would slide the card from under the pointer and
|
||||
// lose the click. The detector only listens, so nothing in the DOM
|
||||
// changes before the click itself.
|
||||
const modality = this.inputModality.mostRecentModality;
|
||||
if (modality === 'mouse' || modality === 'touch') return;
|
||||
const card =
|
||||
track.ownerDocument.activeElement?.closest<HTMLElement>(
|
||||
'.rail__card'
|
||||
);
|
||||
if (!card || !track.contains(card)) return;
|
||||
event.target instanceof Element
|
||||
? event.target.closest<HTMLElement>('.rail__card')
|
||||
: null;
|
||||
if (!card) return;
|
||||
const track = this.track().nativeElement;
|
||||
const visible = this.viewport().nativeElement.getBoundingClientRect();
|
||||
const rect = card.getBoundingClientRect();
|
||||
const hiddenLeft = rect.left < visible.left - 1;
|
||||
|
||||
Reference in new issue
Block a user