mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
Alt-click focus behavior (#3872)
* fix(web-shared): keep Alt+hover working after selecting a span
Chrome's Alt menu-bar handling blurred the page and cleared the overlay
flag, which also killed span hover until the window was refocused.
* test(web-shared): drop useAltHeld unit tests
* refactor(web-shared): let useAltHeld own Alt tracking
Drop the event union, reducer, and timeline mouse-move graft. The hook
listens for keys, blur, and window pointermove itself and returns only
altHeld.
* style(web-shared): trim useAltHeld comment
* fix(web-shared): keep Alt gap overlay when nothing is selected
pointermove was copying e.altKey, so Chrome's post-preventDefault
pointermove with altKey false cleared the flag on Alt press.
* Revert "fix(web-shared): keep Alt gap overlay when nothing is selected"
This reverts commit ba142906f7.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@workflow/web-shared': patch
|
||||
---
|
||||
|
||||
Keep the trace viewer Alt+hover delta overlay working after a span click by stopping Chrome from stealing Alt.
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function isAltModifierKey(key: string): boolean {
|
||||
return key === 'Alt' || key === 'AltGraph';
|
||||
}
|
||||
|
||||
function isTypingTarget(target: EventTarget | null): boolean {
|
||||
return (
|
||||
target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
(target instanceof HTMLElement && target.isContentEditable)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Alt/Option is held, for the timeline delta overlay.
|
||||
*
|
||||
* preventDefault stops Chrome from taking the menu bar (which kills hover).
|
||||
* Capture-phase keys; pointermove recovers if keydown was missed; blur
|
||||
* clears after Alt+Tab.
|
||||
*/
|
||||
export function useAltHeld(): { altHeld: boolean } {
|
||||
const [altHeld, setAltHeld] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent): void => {
|
||||
if (!isAltModifierKey(e.key)) return;
|
||||
if (!isTypingTarget(e.target)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
setAltHeld(true);
|
||||
};
|
||||
const onKeyUp = (e: KeyboardEvent): void => {
|
||||
if (isAltModifierKey(e.key)) setAltHeld(false);
|
||||
};
|
||||
const onBlur = (): void => setAltHeld(false);
|
||||
const onPointerMove = (e: PointerEvent): void => setAltHeld(e.altKey);
|
||||
|
||||
window.addEventListener('keydown', onKeyDown, true);
|
||||
window.addEventListener('keyup', onKeyUp, true);
|
||||
window.addEventListener('blur', onBlur);
|
||||
window.addEventListener('pointermove', onPointerMove, true);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown, true);
|
||||
window.removeEventListener('keyup', onKeyUp, true);
|
||||
window.removeEventListener('blur', onBlur);
|
||||
window.removeEventListener('pointermove', onPointerMove, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { altHeld };
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type TimelineHover,
|
||||
} from './components/timeline';
|
||||
import { TraceShortcutHelper } from './components/trace-shortcut-helper';
|
||||
import { useAltHeld } from './components/use-alt-held';
|
||||
import { ROW_HEIGHT_PX, scrollRowIntoView } from './components/use-row-window';
|
||||
import { ActiveSpanProvider, useActiveSpan } from './context';
|
||||
import { searchSpans } from './search';
|
||||
@@ -370,29 +371,16 @@ function TraceViewerContent({
|
||||
[setActiveSpan, scrollSpanIntoView, cancelPendingZoom, focusViewportOnSpan]
|
||||
);
|
||||
|
||||
const [altHeld, setAltHeld] = useState(false);
|
||||
const { altHeld } = useAltHeld();
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent): void => {
|
||||
if (e.key === 'Escape') {
|
||||
handleClearActiveSpan();
|
||||
} else if (e.key === 'Alt') {
|
||||
setAltHeld(true);
|
||||
}
|
||||
};
|
||||
const onKeyUp = (e: KeyboardEvent): void => {
|
||||
if (e.key === 'Alt') setAltHeld(false);
|
||||
};
|
||||
const onBlur = (): void => setAltHeld(false);
|
||||
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
window.addEventListener('keyup', onKeyUp);
|
||||
window.addEventListener('blur', onBlur);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
window.removeEventListener('blur', onBlur);
|
||||
};
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [handleClearActiveSpan]);
|
||||
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
Reference in New Issue
Block a user