fix(timeline): stabilize popovers on internal scroll + expand test coverage

B-1: useViewportPopover — ignoreScrollContainers option; scroll events
originating inside the timeline canvas no longer close point-anchor popovers
B-2: AllocationPopover, DemandPopover, NewAllocationPopover — thread
scrollContainerRef through so horizontal timeline scroll is ignored
B-3: AllocationPopover — staleTime 0 so SSE reconnect triggers immediate refetch
B-4: useViewportPopover.test.ts — 6 new tests (scroll close, ignore container,
resize close, style clamping)
B-5: AllocationPopover.test.tsx — loading state + happy-path tests added

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-04-02 20:49:08 +02:00
parent d4641e27aa
commit 8d9e26872b
7 changed files with 326 additions and 4 deletions
+16 -2
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState, type CSSProperties } from "react";
import React, { useEffect, useRef, useState, type CSSProperties } from "react";
type PopoverAnchor =
| { kind: "point"; x: number; y: number }
@@ -18,6 +18,7 @@ interface UseViewportPopoverOptions {
viewportPadding?: number;
ignoreElements?: Array<HTMLElement | null>;
ignoreSelectors?: string[];
ignoreScrollContainers?: React.RefObject<HTMLElement | null>[];
zIndex?: number;
}
@@ -32,6 +33,7 @@ export function useViewportPopover({
viewportPadding = 16,
ignoreElements = [],
ignoreSelectors = [],
ignoreScrollContainers,
zIndex = 9998,
}: UseViewportPopoverOptions) {
const ref = useRef<HTMLDivElement>(null);
@@ -182,8 +184,19 @@ export function useViewportPopover({
updateOrClose();
const handleScroll = () => {
const handleScroll = (event: Event) => {
if (closeOnViewportChange) {
const scrollTarget = (event as Event).target;
if (
ignoreScrollContainers?.some(
(r) =>
r.current != null &&
scrollTarget instanceof Node &&
r.current.contains(scrollTarget),
)
) {
return;
}
cancelScheduledFrame();
onClose();
return;
@@ -215,6 +228,7 @@ export function useViewportPopover({
align,
anchor,
estimatedHeight,
ignoreScrollContainers,
offset,
onClose,
side,