mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(epg): re-measure the ribbon window when the axis origin moves
Review follow-up (Codex): a time offset that carries the first programme across midnight moves the axis origin and every track position while scrollLeft and the ribbon stay put, so no scroll event fires and the window kept its epoch-time centre at a different pixel position. The window identity now includes the axis start; when only that changes, the viewport is measured from the ribbon instead of re-estimated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d9f95ec1ca
commit
226ac7ee14
2 files changed
+54
-4
No files matched your search
@@ -345,6 +345,26 @@ describe('EpgTimelineComponent ribbon windowing', () => {
|
||||
expect(renderedTitles()).toContain('Slot 239');
|
||||
});
|
||||
|
||||
// A time offset can carry the first programme across midnight: the axis
|
||||
// origin and every track position move under an unchanged scrollLeft,
|
||||
// and no scroll event follows.
|
||||
it('re-measures the ribbon when the axis origin moves', () => {
|
||||
render(midnightSchedule());
|
||||
const startBefore = component.axis().startMs;
|
||||
const left = ribbon().scrollLeft;
|
||||
|
||||
fixture.componentRef.setInput('offsetMinutes', -720);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.axis().startMs).not.toBe(startBefore);
|
||||
expect(ribbon().scrollLeft).toBe(left);
|
||||
const visible = itemsIn(left, ribbonWidth);
|
||||
expect(visible.length).toBeGreaterThan(0);
|
||||
expect(component.ribbonWindow.items().map((e) => e.key)).toEqual(
|
||||
expect.arrayContaining(visible)
|
||||
);
|
||||
});
|
||||
|
||||
it('windows an expanded group before the ribbon scrolls to it', () => {
|
||||
render(mockSchedule());
|
||||
const first = component
|
||||
|
||||
@@ -149,20 +149,36 @@ export class TimelineWindowController {
|
||||
() => ({
|
||||
ribbon: this.scroller(),
|
||||
key: programsFocusKey(this.ctx.programs()),
|
||||
axisStartMs: this.ctx.axis().startMs,
|
||||
}),
|
||||
{
|
||||
equal: (left, right) =>
|
||||
left.ribbon === right.ribbon && left.key === right.key,
|
||||
left.ribbon === right.ribbon &&
|
||||
left.key === right.key &&
|
||||
left.axisStartMs === right.axisStartMs,
|
||||
}
|
||||
);
|
||||
/**
|
||||
* Estimated once per channel or ribbon mount, then moved only by the
|
||||
* scroll and resize measurements: a live estimate would follow the 30 s
|
||||
* now tick and the centred day away from what the ribbon shows.
|
||||
* now tick and the centred day away from what the ribbon shows. A moved
|
||||
* axis origin (a time offset carrying the first programme across
|
||||
* midnight) shifts every track position under an unchanged `scrollLeft`,
|
||||
* with no scroll event, so the viewport is measured from the ribbon again.
|
||||
*/
|
||||
private readonly viewport = linkedSignal<unknown, TimelineViewport>({
|
||||
private readonly viewport = linkedSignal<
|
||||
{ ribbon: HTMLElement | undefined; key: string; axisStartMs: number },
|
||||
TimelineViewport
|
||||
>({
|
||||
source: this.identity,
|
||||
computation: () => untracked(() => this.initialViewport()),
|
||||
computation: (source, previous) =>
|
||||
untracked(() =>
|
||||
previous &&
|
||||
previous.source.ribbon === source.ribbon &&
|
||||
previous.source.key === source.key
|
||||
? (this.measuredViewport() ?? previous.value)
|
||||
: this.initialViewport()
|
||||
),
|
||||
});
|
||||
private readonly range = computed(() =>
|
||||
timelineWindowRange(this.viewport(), this.ctx.axis(), this.ctx.scale())
|
||||
@@ -260,6 +276,20 @@ export class TimelineWindowController {
|
||||
}
|
||||
}
|
||||
|
||||
/** The viewport the ribbon shows now; null before it is laid out. */
|
||||
private measuredViewport(): TimelineViewport | null {
|
||||
const scroller = this.scroller();
|
||||
if (!scroller || scroller.clientWidth <= 0) {
|
||||
return null;
|
||||
}
|
||||
return viewportFromScroller(
|
||||
scroller.scrollLeft,
|
||||
scroller.clientWidth,
|
||||
this.ctx.axis(),
|
||||
this.ctx.scale()
|
||||
);
|
||||
}
|
||||
|
||||
private scroller(): HTMLElement | undefined {
|
||||
return this.ctx.ribbon()?.nativeElement;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user