paste transform followup (#17624)

Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
Jacob Richman
2026-01-27 06:19:54 -08:00
committed by GitHub
parent 0dc69bd364
commit 362384112e
12 changed files with 539 additions and 440 deletions

View File

@@ -229,4 +229,88 @@ describe('MouseContext', () => {
},
);
});
it('should emit a double-click event when two left-presses occur quickly at the same position', () => {
const handler = vi.fn();
const { result } = renderHook(() => useMouseContext(), { wrapper });
act(() => {
result.current.subscribe(handler);
});
// First click
act(() => {
stdin.write('\x1b[<0;10;20M');
});
expect(handler).toHaveBeenCalledTimes(1);
expect(handler).toHaveBeenLastCalledWith(
expect.objectContaining({ name: 'left-press', col: 10, row: 20 }),
);
// Second click (within threshold)
act(() => {
stdin.write('\x1b[<0;10;20M');
});
// Should have called for the second left-press AND the double-click
expect(handler).toHaveBeenCalledTimes(3);
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({ name: 'double-click', col: 10, row: 20 }),
);
});
it('should NOT emit a double-click event if clicks are too far apart', () => {
const handler = vi.fn();
const { result } = renderHook(() => useMouseContext(), { wrapper });
act(() => {
result.current.subscribe(handler);
});
// First click
act(() => {
stdin.write('\x1b[<0;10;20M');
});
// Second click (too far)
act(() => {
stdin.write('\x1b[<0;15;25M');
});
expect(handler).toHaveBeenCalledTimes(2);
expect(handler).not.toHaveBeenCalledWith(
expect.objectContaining({ name: 'double-click' }),
);
});
it('should NOT emit a double-click event if too much time passes', async () => {
vi.useFakeTimers();
const handler = vi.fn();
const { result } = renderHook(() => useMouseContext(), { wrapper });
act(() => {
result.current.subscribe(handler);
});
// First click
act(() => {
stdin.write('\x1b[<0;10;20M');
});
await act(async () => {
vi.advanceTimersByTime(500); // Threshold is 400ms
});
// Second click
act(() => {
stdin.write('\x1b[<0;10;20M');
});
expect(handler).toHaveBeenCalledTimes(2);
expect(handler).not.toHaveBeenCalledWith(
expect.objectContaining({ name: 'double-click' }),
);
vi.useRealTimers();
});
});

View File

@@ -22,6 +22,8 @@ import {
type MouseEvent,
type MouseEventName,
type MouseHandler,
DOUBLE_CLICK_THRESHOLD_MS,
DOUBLE_CLICK_DISTANCE_TOLERANCE,
} from '../utils/mouse.js';
export type { MouseEvent, MouseEventName, MouseHandler };
@@ -67,6 +69,11 @@ export function MouseProvider({
}) {
const { stdin } = useStdin();
const subscribers = useRef<Set<MouseHandler>>(new Set()).current;
const lastClickRef = useRef<{
time: number;
col: number;
row: number;
} | null>(null);
const subscribe = useCallback(
(handler: MouseHandler) => {
@@ -96,6 +103,30 @@ export function MouseProvider({
handled = true;
}
}
if (event.name === 'left-press') {
const now = Date.now();
const lastClick = lastClickRef.current;
if (
lastClick &&
now - lastClick.time < DOUBLE_CLICK_THRESHOLD_MS &&
Math.abs(event.col - lastClick.col) <=
DOUBLE_CLICK_DISTANCE_TOLERANCE &&
Math.abs(event.row - lastClick.row) <= DOUBLE_CLICK_DISTANCE_TOLERANCE
) {
const doubleClickEvent: MouseEvent = {
...event,
name: 'double-click',
};
for (const handler of subscribers) {
handler(doubleClickEvent);
}
lastClickRef.current = null;
} else {
lastClickRef.current = { time: now, col: event.col, row: event.row };
}
}
if (
!handled &&
event.name === 'move' &&