fix: correct piano-roll header seek when horizontally scrolled

This commit is contained in:
Xiaohan-Tian
2026-07-06 18:23:34 -07:00
parent be15983e12
commit 3c93dd76fc
2 changed files with 32 additions and 6 deletions
@@ -28,10 +28,12 @@ function renderHeader({
hasPianoKeys = true,
scrollLeft = 0,
paddingLeft = hasPianoKeys ? '60px' : '0px',
scrollContainerLeft = 100,
}: {
hasPianoKeys?: boolean;
scrollLeft?: number;
paddingLeft?: string;
scrollContainerLeft?: number;
} = {}) {
const view = render(
<div className="piano-roll-note-scroll">
@@ -48,18 +50,33 @@ function renderHeader({
writable: true,
});
Object.defineProperty(scrollContainer, 'getBoundingClientRect', {
configurable: true,
value: () => ({
left: scrollContainerLeft,
top: 0,
right: scrollContainerLeft + 500,
bottom: 20,
width: 500,
height: 20,
x: scrollContainerLeft,
y: 0,
toJSON: () => ({}),
}),
});
header.style.paddingLeft = paddingLeft;
Object.defineProperty(header, 'getBoundingClientRect', {
configurable: true,
value: () => ({
left: 100,
left: scrollContainerLeft - scrollLeft,
top: 0,
right: 600,
right: scrollContainerLeft - scrollLeft + 500,
bottom: 20,
width: 500,
height: 20,
x: 100,
x: scrollContainerLeft - scrollLeft,
y: 0,
toJSON: () => ({}),
}),
@@ -88,7 +105,7 @@ describe('PianoGridHeader', () => {
expect(requestMainContentScroll).toHaveBeenCalledWith(3);
});
it('includes the note scroll offset when seeking after horizontal scroll', () => {
it('seeks to the same beat after horizontal scroll when the header rect already shifts with content', () => {
const { header } = renderHeader({ scrollLeft: 160 });
fireEvent.click(header, { clientX: 280 });
@@ -118,4 +135,13 @@ describe('PianoGridHeader', () => {
expect(setPlayheadPosition).toHaveBeenCalledWith(5);
expect(requestMainContentScroll).not.toHaveBeenCalled();
});
it('ignores clicks inside the visible piano-key gutter before it fully scrolls out of view', () => {
const { header } = renderHeader({ scrollLeft: 20 });
fireEvent.click(header, { clientX: 110 });
expect(setPlayheadPosition).not.toHaveBeenCalled();
expect(requestMainContentScroll).not.toHaveBeenCalled();
});
});
@@ -28,9 +28,9 @@ const PianoGridHeader: React.FC<PianoGridHeaderProps> = ({
if (!headerElementRef.current) return null;
const headerElement = headerElementRef.current;
const rect = headerElement.getBoundingClientRect();
const relativeX = clientX - rect.left;
const scrollContainer = headerElement.closest('.piano-roll-note-scroll') as HTMLElement | null;
const referenceRect = scrollContainer?.getBoundingClientRect() ?? headerElement.getBoundingClientRect();
const relativeX = clientX - referenceRect.left;
const scrollLeft = scrollContainer?.scrollLeft ?? 0;
const leftGutter = parseFloat(getComputedStyle(headerElement).paddingLeft) || 0;
const adjustedX = relativeX + scrollLeft - leftGutter;