Skip to content

Commit 8f70c3b

Browse files
thomasahleclaude
andcommitted
Fix transition toolbar buttons via synthetic keyboard events
postMessage (for WCP command files) is async so the FocusItem message races with the iframe blur event, causing transition_next/prev WCP commands to execute before Surfer's focused-variable state is restored. Switch transition_next/prev to dispatch synthetic ArrowRight/ArrowLeft keydown events directly to the Surfer iframe canvas (same-origin). This mirrors pressing the arrow keys while the iframe is focused (known working path) and avoids the async fetch race entirely. FocusItem is still re-sent 50 ms before the key event so ordering is guaranteed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0a5742c commit 8f70c3b

1 file changed

Lines changed: 30 additions & 6 deletions

File tree

src/lib/components/WaveformViewer.svelte

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,37 @@
114114
export function sendCmd(cmd) {
115115
const cw = iframeEl?.contentWindow;
116116
if (!cw) return;
117-
// Clicking a toolbar button in the parent page blurs the Surfer iframe, which
118-
// causes Surfer to lose its focused-variable state. Re-send FocusItem before
119-
// any command that depends on it so transition_next/prev have a signal to work on.
120-
if ((cmd === 'transition_next' || cmd === 'transition_previous') &&
121-
_focusedVisibleIdx !== undefined) {
122-
surferMsg(cw, { FocusItem: _focusedVisibleIdx });
117+
118+
if (cmd === 'transition_next' || cmd === 'transition_previous') {
119+
// Dispatch a synthetic arrow-key event directly to the Surfer iframe
120+
// (same-origin access). This mirrors what happens when the user presses
121+
// ← / → with the iframe focused, and avoids the async race between the
122+
// WCP command-file fetch and the iframe-blur event clearing Surfer's
123+
// focused-variable state.
124+
//
125+
// We also re-send FocusItem first so Surfer's focused variable is correct
126+
// even if it was cleared by the blur event. postMessage is async, so we
127+
// delay the key dispatch by 50 ms to ensure FocusItem is processed by
128+
// Surfer before the keyboard event fires.
129+
if (_focusedVisibleIdx !== undefined) {
130+
surferMsg(cw, { FocusItem: _focusedVisibleIdx });
131+
}
132+
const key = cmd === 'transition_next' ? 'ArrowRight' : 'ArrowLeft';
133+
setTimeout(() => {
134+
const el = iframeEl;
135+
if (!el) return;
136+
const canvas = el.contentDocument?.getElementById('the_canvas_id');
137+
const target = canvas ?? el.contentWindow;
138+
if (!target) return;
139+
// Create the event in the iframe's realm to avoid cross-realm prototype issues.
140+
const IframeKBEvent = el.contentWindow?.KeyboardEvent ?? KeyboardEvent;
141+
target.dispatchEvent(
142+
new IframeKBEvent('keydown', { key, code: key, bubbles: true, cancelable: true })
143+
);
144+
}, 50);
145+
return;
123146
}
147+
124148
const url = URL.createObjectURL(new Blob([cmd + '\n'], { type: 'text/plain' }));
125149
surferMsg(cw, { LoadCommandFileFromUrl: url });
126150
setTimeout(() => URL.revokeObjectURL(url), 5000);

0 commit comments

Comments
 (0)