mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(player): re-place the timeline label when the player resizes
The label's measured pixel left only re-ran when its anchor or text changed, so a paused player whose slider the keyboard holds kept a stale position after the window narrowed, past the player's edge. A ResizeObserver on the bar and the player now feeds the placement effect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7aca654408
commit
9c0278079c
3 files changed
+52
-4
No files matched your search
@@ -407,7 +407,9 @@ stylesheet centres it on its anchor; once laid out,
|
||||
`PlayerTimelineComponent` moves it by its measured width
|
||||
(`clampTimelineLabelLeft`, an `afterRenderEffect`) so it stays 8px inside
|
||||
the player (`.player-controls-host`) — past the bar's ends when needed, as
|
||||
the bar is narrower than the player. `xtream-catchup-timeline-label.e2e.ts`
|
||||
the bar is narrower than the player. A `ResizeObserver` on the bar and the
|
||||
player re-places it when only the size changes (a paused player whose
|
||||
slider the keyboard holds, in a narrowed window). `xtream-catchup-timeline-label.e2e.ts`
|
||||
tabs to the bar with a 120-character programme title at 1280 and 800px, in
|
||||
both themes and in de and ru.
|
||||
|
||||
|
||||
@@ -466,7 +466,10 @@ describe('PlayerControlsComponent dock', () => {
|
||||
|
||||
try {
|
||||
bar.dispatchEvent(
|
||||
new MouseEvent('pointermove', { clientX: 70, bubbles: true })
|
||||
new MouseEvent('pointermove', {
|
||||
clientX: 70,
|
||||
bubbles: true,
|
||||
})
|
||||
);
|
||||
fixture.detectChanges();
|
||||
const label = query(
|
||||
@@ -477,11 +480,29 @@ describe('PlayerControlsComponent dock', () => {
|
||||
expect(label.style.transform).toBe('none');
|
||||
|
||||
bar.dispatchEvent(
|
||||
new MouseEvent('pointermove', { clientX: 730, bubbles: true })
|
||||
new MouseEvent('pointermove', {
|
||||
clientX: 730,
|
||||
bubbles: true,
|
||||
})
|
||||
);
|
||||
fixture.detectChanges();
|
||||
// Its right edge 8px in from the player's right edge.
|
||||
expect(label.style.left).toBe(`${800 - 8 - 390 - 70}px`);
|
||||
|
||||
// The player narrows with the anchor and text unchanged (a
|
||||
// paused player, the slider held): the label is re-placed.
|
||||
host.getBoundingClientRect = () =>
|
||||
({ left: 0, right: 600, width: 600 }) as DOMRect;
|
||||
bar.getBoundingClientRect = () =>
|
||||
({ left: 70, width: 460 }) as DOMRect;
|
||||
resizeTo(600);
|
||||
expect(
|
||||
(
|
||||
query(
|
||||
'[data-test-id="player-controls-timeline-label"]'
|
||||
) as HTMLElement
|
||||
).style.left
|
||||
).toBe(`${600 - 8 - 390 - 70}px`);
|
||||
} finally {
|
||||
if (offsetWidth) {
|
||||
Object.defineProperty(
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
Component,
|
||||
ElementRef,
|
||||
afterRenderEffect,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -54,16 +56,39 @@ export class PlayerTimelineComponent {
|
||||
private readonly bar = viewChild<ElementRef<HTMLElement>>('bar');
|
||||
private readonly labelElement =
|
||||
viewChild<ElementRef<HTMLElement>>('labelElement');
|
||||
/**
|
||||
* Bumped when the bar or the player resizes: the label's pixel `left`
|
||||
* is stale then, even with its anchor and text unchanged (a paused
|
||||
* player, the keyboard holding the slider, a narrowed window).
|
||||
*/
|
||||
private readonly resized = signal(0);
|
||||
|
||||
constructor() {
|
||||
effect((onCleanup) => {
|
||||
const bar = this.bar()?.nativeElement;
|
||||
if (!bar || typeof ResizeObserver === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const observer = new ResizeObserver(() =>
|
||||
this.resized.update((count) => count + 1)
|
||||
);
|
||||
observer.observe(bar);
|
||||
const player = this.host.closest('.player-controls-host');
|
||||
if (player) {
|
||||
observer.observe(player);
|
||||
}
|
||||
onCleanup(() => observer.disconnect());
|
||||
});
|
||||
// The stylesheet centres the label on its anchor; once laid out, it
|
||||
// is moved by its measured width to stay inside the player, which a
|
||||
// long programme or chapter title would otherwise overflow.
|
||||
afterRenderEffect({
|
||||
earlyRead: () => {
|
||||
const percent = this.label().percent();
|
||||
// A new title or time changes the width to clamp.
|
||||
// A new title or time changes the width to clamp, and a
|
||||
// resize the room it is clamped into.
|
||||
this.label().text();
|
||||
this.resized();
|
||||
const label = this.labelElement()?.nativeElement;
|
||||
const bar = this.bar()?.nativeElement;
|
||||
if (percent === null || !label || !bar) {
|
||||
|
||||
Reference in new issue
Block a user