diff --git a/packages/studio/src/components/pointerTargetSize.test.tsx b/packages/studio/src/components/pointerTargetSize.test.tsx index 54f7519f8..fd1923533 100644 --- a/packages/studio/src/components/pointerTargetSize.test.tsx +++ b/packages/studio/src/components/pointerTargetSize.test.tsx @@ -33,7 +33,7 @@ function mount(element: React.ReactNode) { return host; } -describe("pointer target size (WCAG 2.5.8)", () => { +describe("pointer target sizing classes (proxy for WCAG 2.5.8, not a geometry check)", () => { it("gives the timeline zoom slider a 24px box without changing its 2px track or 10px thumb", () => { const host = mount(); const slider = host.querySelector('input[aria-label="Timeline zoom"]'); diff --git a/packages/studio/src/player/components/TimelineClipDiamonds.test.tsx b/packages/studio/src/player/components/TimelineClipDiamonds.test.tsx index aff08c082..fc00b55c0 100644 --- a/packages/studio/src/player/components/TimelineClipDiamonds.test.tsx +++ b/packages/studio/src/player/components/TimelineClipDiamonds.test.tsx @@ -641,7 +641,7 @@ describe("TimelineClipDiamonds", () => { act(() => root.unmount()); }); - const renderSegmentLane = (lastAmbiguous: boolean) => { + const renderSegmentLane = (lastAmbiguous: boolean, clipWidthPx = 200) => { const host = document.createElement("div"); document.body.append(host); const root = createRoot(host); @@ -660,7 +660,7 @@ describe("TimelineClipDiamonds", () => { format: "percentage", keyframes: [kf(0), kf(50), kf(100, { easeAmbiguous: lastAmbiguous })], }} - clipWidthPx={200} + clipWidthPx={clipWidthPx} clipHeightPx={48} accentColor="#4ba3d2" isSelected @@ -698,6 +698,21 @@ describe("TimelineClipDiamonds", () => { expect(segment?.style.pointerEvents).toBe("none"); expect(ease?.style.pointerEvents).toBe("auto"); expect(Number(segment?.style.zIndex)).toBeGreaterThan(Number(diamond?.style.zIndex)); + // Room to spare here, so the button carries the 24x24 WCAG 2.5.8 overlay. + expect(ease?.className).toContain("before:h-6"); + act(() => root.unmount()); + }); + + it("drops the 24x24 ease overlay when the segment is too narrow to hold it", () => { + // The segment wrapper outranks the diamonds, so an overlay wider than the + // clear span between them would steal their clicks at fit zoom. 40px of clip + // across three keyframes leaves well under 24px of clear span per segment. + const { host, root } = renderSegmentLane(false, 40); + const ease = host.querySelector("[data-keyframe-ease-button]"); + + expect(ease).not.toBeNull(); + expect(ease?.className).not.toContain("before:h-6"); + expect(ease?.style.width).toBe("16px"); act(() => root.unmount()); }); diff --git a/packages/studio/src/player/components/TimelineDiamondConnectors.tsx b/packages/studio/src/player/components/TimelineDiamondConnectors.tsx index 6298dc62c..4b155ef71 100644 --- a/packages/studio/src/player/components/TimelineDiamondConnectors.tsx +++ b/packages/studio/src/player/components/TimelineDiamondConnectors.tsx @@ -62,6 +62,12 @@ export function TimelineDiamondConnectors({ // no source animation id (runtime-scanned) so there is no tween to target. const target = keyframeTarget(kf); const ease = kf.ease ?? globalEase; + // connectorWidth is the clear span between the two diamonds' edges, so a + // 24x24 target centred in it overhangs a diamond as soon as the span is + // narrower than 24. The segment wrapper sits at z-index 3, above the + // diamonds, so that overhang would win the hit test and steal their + // clicks at fit zoom. Grow the target only where the room exists. + const roomForFullTarget = connectorWidth >= 24; return (