editor: add core hotkeys (copy/paste/select-all/esc)
This commit is contained in:
parent
e4b25a66a0
commit
4e4b0e428d
@ -1,9 +1,35 @@
|
|||||||
import type { EditorAction } from './store';
|
import type { EditorAction } from './store';
|
||||||
|
|
||||||
|
function isEditableTarget(target: EventTarget | null): boolean {
|
||||||
|
if (!target) return false;
|
||||||
|
if (!(target instanceof HTMLElement)) return false;
|
||||||
|
|
||||||
|
// If the focus is inside inputs/textareas or contenteditable, let the browser handle hotkeys.
|
||||||
|
const el = target;
|
||||||
|
if (el.isContentEditable) return true;
|
||||||
|
|
||||||
|
const tag = el.tagName.toLowerCase();
|
||||||
|
if (tag === 'input' || tag === 'textarea' || tag === 'select') return true;
|
||||||
|
|
||||||
|
// Some components nest the real input.
|
||||||
|
if (el.closest('input, textarea, select, [contenteditable="true"]')) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function bindEditorHotkeys(getShift: () => boolean, dispatch: (a: EditorAction) => void) {
|
export function bindEditorHotkeys(getShift: () => boolean, dispatch: (a: EditorAction) => void) {
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
const ctrl = e.ctrlKey || e.metaKey;
|
const ctrl = e.ctrlKey || e.metaKey;
|
||||||
|
|
||||||
|
// Esc: clear selection (and closes context menu via selection parity effect).
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
dispatch({ type: 'selectSingle', id: undefined });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not steal common hotkeys while typing in an input/editor.
|
||||||
|
if (isEditableTarget(e.target)) return;
|
||||||
|
|
||||||
// Undo/redo
|
// Undo/redo
|
||||||
if (ctrl && e.key.toLowerCase() === 'z') {
|
if (ctrl && e.key.toLowerCase() === 'z') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -16,8 +42,28 @@ export function bindEditorHotkeys(getShift: () => boolean, dispatch: (a: EditorA
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Select all
|
||||||
|
if (ctrl && e.key.toLowerCase() === 'a') {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch({ type: 'selectAll' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy/paste
|
||||||
|
if (ctrl && e.key.toLowerCase() === 'c') {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch({ type: 'copySelected' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctrl && e.key.toLowerCase() === 'v') {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch({ type: 'pasteClipboard' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||||
|
e.preventDefault();
|
||||||
dispatch({ type: 'deleteSelected' });
|
dispatch({ type: 'deleteSelected' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user