Skip to content

Commit 75cd15c

Browse files
committed
fix(studio): every remaining shortcut asks the shared typing check
1 parent c5cd070 commit 75cd15c

6 files changed

Lines changed: 39 additions & 61 deletions

File tree

‎packages/studio/src/components/panels/SlideshowPanel.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// @vitest-environment happy-dom
2+
13
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
24
import {
35
toggleMainLineSlide,
@@ -14,6 +16,7 @@ import {
1416
removeHotspot,
1517
safeParseManifest,
1618
makeSlideshowNotesController,
19+
isPanelUndoKey,
1720
} from "./SlideshowPanel";
1821
import type { SlideshowManifest } from "@hyperframes/core/slideshow";
1922

@@ -518,3 +521,27 @@ describe("makeSlideshowNotesController", () => {
518521
expect(persist).not.toHaveBeenCalled();
519522
});
520523
});
524+
525+
describe("isPanelUndoKey", () => {
526+
function undoFrom(html: string) {
527+
document.body.innerHTML = `<div>${html}</div>`;
528+
const target = document.body.firstElementChild!.firstElementChild!;
529+
return { metaKey: true, ctrlKey: false, shiftKey: false, key: "z", target };
530+
}
531+
532+
it("undoes the panel from a button or a slider", () => {
533+
expect(isPanelUndoKey(undoFrom("<button>Add</button>"))).toBe(true);
534+
expect(isPanelUndoKey(undoFrom('<div role="slider" tabindex="0"></div>'))).toBe(true);
535+
});
536+
537+
it("leaves Cmd+Z to a text field, contenteditable and select included", () => {
538+
expect(isPanelUndoKey(undoFrom("<input />"))).toBe(false);
539+
expect(isPanelUndoKey(undoFrom('<p contenteditable="plaintext-only">Note</p>'))).toBe(false);
540+
expect(isPanelUndoKey(undoFrom("<select><option>a</option></select>"))).toBe(false);
541+
});
542+
543+
it("ignores Cmd+Shift+Z and a bare z", () => {
544+
expect(isPanelUndoKey({ ...undoFrom("<button />"), shiftKey: true })).toBe(false);
545+
expect(isPanelUndoKey({ ...undoFrom("<button />"), metaKey: false })).toBe(false);
546+
});
547+
});

‎packages/studio/src/components/panels/SlideshowPanel.tsx‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { usePlayerStore } from "../../player";
2121
import { useDomEditSelectionContext } from "../../contexts/DomEditContext";
2222
import { useFileManagerContext } from "../../contexts/FileManagerContext";
2323
import { generateId } from "../../utils/generateId";
24+
import { isTypingTarget } from "../../utils/typingTarget";
2425
import {
2526
SectionHeader,
2627
SlideList,
@@ -153,6 +154,13 @@ export function makeSlideshowNotesController(): NotesController {
153154
};
154155
}
155156

157+
export function isPanelUndoKey(
158+
e: Pick<KeyboardEvent, "metaKey" | "ctrlKey" | "shiftKey" | "key" | "target">,
159+
): boolean {
160+
if (!(e.metaKey || e.ctrlKey) || e.shiftKey || e.key.toLowerCase() !== "z") return false;
161+
return !isTypingTarget(e.target);
162+
}
163+
156164
// ── Component ─────────────────────────────────────────────────────────────
157165

158166
export interface SlideshowPanelProps {
@@ -416,13 +424,10 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
416424
className="flex flex-col h-full overflow-y-auto text-text-0"
417425
onKeyDown={(e) => {
418426
// In-panel undo — scoped so it never fights the app-level file undo.
419-
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z") {
420-
const target = e.target instanceof HTMLElement ? e.target.tagName : "";
421-
if (target === "TEXTAREA" || target === "INPUT") return;
422-
e.preventDefault();
423-
e.stopPropagation();
424-
handleUndo();
425-
}
427+
if (!isPanelUndoKey(e)) return;
428+
e.preventDefault();
429+
e.stopPropagation();
430+
handleUndo();
426431
}}
427432
>
428433
{persistError && (

‎packages/studio/src/player/components/Timeline.test.ts‎

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
getTimelineScrollLeftForZoomAnchor,
1717
getTimelineScrollLeftForZoomTransition,
1818
shouldShowTimelineShortcutHint,
19-
shouldHandleTimelineDeleteKey,
2019
shouldAutoScrollTimeline,
2120
getTimelineVisibleTimeRange,
2221
getTimelineScrollTopForGeometryChange,
@@ -1248,26 +1247,6 @@ describe("shouldShowTimelineShortcutHint", () => {
12481247
});
12491248
});
12501249

1251-
describe("shouldHandleTimelineDeleteKey", () => {
1252-
it("handles Delete and Backspace when focus is not in an editor", () => {
1253-
expect(shouldHandleTimelineDeleteKey({ key: "Delete" })).toBe(true);
1254-
expect(shouldHandleTimelineDeleteKey({ key: "Backspace" })).toBe(true);
1255-
});
1256-
1257-
it("ignores modifier shortcuts", () => {
1258-
expect(shouldHandleTimelineDeleteKey({ key: "Delete", metaKey: true })).toBe(false);
1259-
expect(shouldHandleTimelineDeleteKey({ key: "Backspace", ctrlKey: true })).toBe(false);
1260-
});
1261-
1262-
it("ignores input and editable targets", () => {
1263-
const input = { tagName: "INPUT", isContentEditable: false };
1264-
const editable = { tagName: "DIV", isContentEditable: true };
1265-
1266-
expect(shouldHandleTimelineDeleteKey({ key: "Delete", target: input })).toBe(false);
1267-
expect(shouldHandleTimelineDeleteKey({ key: "Delete", target: editable })).toBe(false);
1268-
});
1269-
});
1270-
12711250
describe("getDefaultDroppedTrack", () => {
12721251
it("defaults to track 0 when there are no rows yet", () => {
12731252
expect(getDefaultDroppedTrack([])).toBe(0);

‎packages/studio/src/player/components/Timeline.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export {
2626
getTimelineCanvasHeight,
2727
shouldShowTimelineShortcutHint,
2828
resolveTimelineAssetDrop,
29-
shouldHandleTimelineDeleteKey,
3029
getDefaultDroppedTrack,
3130
} from "./timelineLayout";
3231
export { formatTimelineTickLabel, generateTicks } from "./timelineRulerGeometry";

‎packages/studio/src/player/components/timelineLayout.ts‎

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -506,34 +506,6 @@ export function shouldShowTimelineShortcutHint(
506506
return scrollHeight - clientHeight <= 1;
507507
}
508508

509-
export function shouldHandleTimelineDeleteKey(input: {
510-
key: string;
511-
metaKey?: boolean;
512-
ctrlKey?: boolean;
513-
altKey?: boolean;
514-
target?: EventTarget | null;
515-
}): boolean {
516-
if (input.key !== "Delete" && input.key !== "Backspace") return false;
517-
if (input.metaKey || input.ctrlKey || input.altKey) return false;
518-
const target =
519-
input.target && typeof input.target === "object"
520-
? (input.target as {
521-
tagName?: string;
522-
isContentEditable?: boolean;
523-
closest?: (selector: string) => Element | null;
524-
})
525-
: null;
526-
if (target) {
527-
const tag = target.tagName?.toLowerCase() ?? "";
528-
if (target.isContentEditable) return false;
529-
if (["input", "textarea", "select"].includes(tag)) return false;
530-
if (typeof target.closest === "function" && target.closest("[contenteditable='true']")) {
531-
return false;
532-
}
533-
}
534-
return true;
535-
}
536-
537509
/* ── Asset drop ───────────────────────────────────────────────────── */
538510
export function getDefaultDroppedTrack(trackOrder: number[], rowIndex?: number): number {
539511
if (trackOrder.length === 0) return 0;

‎packages/studio/src/player/lib/playbackShortcuts.ts‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,11 @@ const PLAYBACK_SHORTCUT_IGNORED_SELECTOR = [
1515
"a[href]",
1616
"[role='button']",
1717
"[role='checkbox']",
18-
"[role='combobox']",
1918
"[role='menuitem']",
2019
// Base UI's menu radio item is a `<div>`, so `button` above no longer catches it.
2120
"[role='menuitemradio']",
2221
"[role='radio']",
23-
"[role='slider']",
2422
"[role='spinbutton']",
25-
"[role='switch']",
26-
"[role='textbox']",
2723
].join(",");
2824

2925
export function shouldIgnorePlaybackShortcutTarget(target: EventTarget | null): boolean {

0 commit comments

Comments
 (0)