Skip to content

Commit ecfec4d

Browse files
Copilothuangyiirene
andcommitted
Refactor: Extract keyboard shortcuts into reusable hook
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
1 parent 59dda83 commit ecfec4d

File tree

5 files changed

+133
-162
lines changed

5 files changed

+133
-162
lines changed

packages/designer/src/components/FormDesigner.tsx

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
* - Field property configuration
99
*/
1010

11-
import React, { useEffect } from 'react';
11+
import React from 'react';
1212
import { DesignerProvider } from '../context/DesignerContext';
1313
import { Canvas } from './Canvas';
1414
import { PropertyPanel } from './PropertyPanel';
1515
import { FilteredComponentPalette } from './FilteredComponentPalette';
1616
import { ComponentTree } from './ComponentTree';
1717
import { useDesigner } from '../context/DesignerContext';
18+
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
1819
import type { SchemaNode } from '@object-ui/core';
1920
import type { FormDesignerConfig } from '../types/designer-modes';
2021

@@ -58,59 +59,19 @@ export const FormDesignerContent: React.FC = () => {
5859
canRedo
5960
} = useDesigner();
6061

61-
// Keyboard shortcuts
62-
useEffect(() => {
63-
const handleKeyDown = (e: KeyboardEvent) => {
64-
// Check if we're in an editable element
65-
const target = e.target as HTMLElement;
66-
const isEditing =
67-
target.tagName === 'INPUT' ||
68-
target.tagName === 'TEXTAREA' ||
69-
target.tagName === 'SELECT' ||
70-
target.isContentEditable;
71-
72-
// Undo: Ctrl+Z / Cmd+Z
73-
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) {
74-
e.preventDefault();
75-
undo();
76-
}
77-
// Redo: Ctrl+Y / Cmd+Shift+Z
78-
else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) {
79-
if (canRedo) {
80-
e.preventDefault();
81-
redo();
82-
}
83-
}
84-
// Copy: Ctrl+C / Cmd+C (only when not editing)
85-
else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) {
86-
e.preventDefault();
87-
copyNode(selectedNodeId);
88-
}
89-
// Cut: Ctrl+X / Cmd+X (only when not editing)
90-
else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) {
91-
e.preventDefault();
92-
cutNode(selectedNodeId);
93-
}
94-
// Paste: Ctrl+V / Cmd+V (only when not editing)
95-
else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) {
96-
e.preventDefault();
97-
pasteNode(selectedNodeId);
98-
}
99-
// Duplicate: Ctrl+D / Cmd+D (only when not editing)
100-
else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) {
101-
e.preventDefault();
102-
duplicateNode(selectedNodeId);
103-
}
104-
// Delete: Delete / Backspace (only when not editing)
105-
else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) {
106-
e.preventDefault();
107-
removeNode(selectedNodeId);
108-
}
109-
};
110-
111-
window.addEventListener('keydown', handleKeyDown);
112-
return () => window.removeEventListener('keydown', handleKeyDown);
113-
}, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]);
62+
// Use shared keyboard shortcuts hook
63+
useKeyboardShortcuts({
64+
undo,
65+
redo,
66+
copyNode,
67+
cutNode,
68+
duplicateNode,
69+
pasteNode,
70+
removeNode,
71+
selectedNodeId,
72+
canUndo,
73+
canRedo,
74+
});
11475

11576
return (
11677
<div className="h-full flex flex-col bg-white text-gray-900 font-sans">

packages/designer/src/components/GeneralDesigner.tsx

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useEffect } from 'react';
1+
import React from 'react';
22
import { DesignerProvider } from '../context/DesignerContext';
33
import { LeftSidebar } from './LeftSidebar';
44
import { Canvas } from './Canvas';
55
import { PropertyPanel } from './PropertyPanel';
66
import { useDesigner } from '../context/DesignerContext';
7+
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
78
import type { SchemaNode } from '@object-ui/core';
89

910
interface GeneralDesignerProps {
@@ -26,59 +27,19 @@ export const GeneralDesignerContent: React.FC = () => {
2627
canRedo
2728
} = useDesigner();
2829

29-
// Keyboard shortcuts
30-
useEffect(() => {
31-
const handleKeyDown = (e: KeyboardEvent) => {
32-
// Check if we're in an editable element
33-
const target = e.target as HTMLElement;
34-
const isEditing =
35-
target.tagName === 'INPUT' ||
36-
target.tagName === 'TEXTAREA' ||
37-
target.tagName === 'SELECT' ||
38-
target.isContentEditable;
39-
40-
// Undo: Ctrl+Z / Cmd+Z
41-
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) {
42-
e.preventDefault();
43-
undo();
44-
}
45-
// Redo: Ctrl+Y / Cmd+Shift+Z
46-
else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) {
47-
if (canRedo) {
48-
e.preventDefault();
49-
redo();
50-
}
51-
}
52-
// Copy: Ctrl+C / Cmd+C (only when not editing)
53-
else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) {
54-
e.preventDefault();
55-
copyNode(selectedNodeId);
56-
}
57-
// Cut: Ctrl+X / Cmd+X (only when not editing)
58-
else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) {
59-
e.preventDefault();
60-
cutNode(selectedNodeId);
61-
}
62-
// Paste: Ctrl+V / Cmd+V (only when not editing)
63-
else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) {
64-
e.preventDefault();
65-
pasteNode(selectedNodeId);
66-
}
67-
// Duplicate: Ctrl+D / Cmd+D (only when not editing)
68-
else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) {
69-
e.preventDefault();
70-
duplicateNode(selectedNodeId);
71-
}
72-
// Delete: Delete / Backspace (only when not editing)
73-
else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) {
74-
e.preventDefault();
75-
removeNode(selectedNodeId);
76-
}
77-
};
78-
79-
window.addEventListener('keydown', handleKeyDown);
80-
return () => window.removeEventListener('keydown', handleKeyDown);
81-
}, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]);
30+
// Use shared keyboard shortcuts hook
31+
useKeyboardShortcuts({
32+
undo,
33+
redo,
34+
copyNode,
35+
cutNode,
36+
duplicateNode,
37+
pasteNode,
38+
removeNode,
39+
selectedNodeId,
40+
canUndo,
41+
canRedo,
42+
});
8243

8344
return (
8445
<div className="h-full flex flex-col bg-white text-gray-900 font-sans">

packages/designer/src/components/LayoutDesigner.tsx

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
* - Spacing and alignment helpers
99
*/
1010

11-
import React, { useEffect } from 'react';
11+
import React from 'react';
1212
import { DesignerProvider } from '../context/DesignerContext';
1313
import { Canvas } from './Canvas';
1414
import { PropertyPanel } from './PropertyPanel';
1515
import { FilteredComponentPalette } from './FilteredComponentPalette';
1616
import { ComponentTree } from './ComponentTree';
1717
import { useDesigner } from '../context/DesignerContext';
18+
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
1819
import type { SchemaNode } from '@object-ui/core';
1920
import type { LayoutDesignerConfig } from '../types/designer-modes';
2021

@@ -61,59 +62,19 @@ export const LayoutDesignerContent: React.FC = () => {
6162
canRedo
6263
} = useDesigner();
6364

64-
// Keyboard shortcuts
65-
useEffect(() => {
66-
const handleKeyDown = (e: KeyboardEvent) => {
67-
// Check if we're in an editable element
68-
const target = e.target as HTMLElement;
69-
const isEditing =
70-
target.tagName === 'INPUT' ||
71-
target.tagName === 'TEXTAREA' ||
72-
target.tagName === 'SELECT' ||
73-
target.isContentEditable;
74-
75-
// Undo: Ctrl+Z / Cmd+Z
76-
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) {
77-
e.preventDefault();
78-
undo();
79-
}
80-
// Redo: Ctrl+Y / Cmd+Shift+Z
81-
else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) {
82-
if (canRedo) {
83-
e.preventDefault();
84-
redo();
85-
}
86-
}
87-
// Copy: Ctrl+C / Cmd+C (only when not editing)
88-
else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) {
89-
e.preventDefault();
90-
copyNode(selectedNodeId);
91-
}
92-
// Cut: Ctrl+X / Cmd+X (only when not editing)
93-
else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) {
94-
e.preventDefault();
95-
cutNode(selectedNodeId);
96-
}
97-
// Paste: Ctrl+V / Cmd+V (only when not editing)
98-
else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) {
99-
e.preventDefault();
100-
pasteNode(selectedNodeId);
101-
}
102-
// Duplicate: Ctrl+D / Cmd+D (only when not editing)
103-
else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) {
104-
e.preventDefault();
105-
duplicateNode(selectedNodeId);
106-
}
107-
// Delete: Delete / Backspace (only when not editing)
108-
else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) {
109-
e.preventDefault();
110-
removeNode(selectedNodeId);
111-
}
112-
};
113-
114-
window.addEventListener('keydown', handleKeyDown);
115-
return () => window.removeEventListener('keydown', handleKeyDown);
116-
}, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]);
65+
// Use shared keyboard shortcuts hook
66+
useKeyboardShortcuts({
67+
undo,
68+
redo,
69+
copyNode,
70+
cutNode,
71+
duplicateNode,
72+
pasteNode,
73+
removeNode,
74+
selectedNodeId,
75+
canUndo,
76+
canRedo,
77+
});
11778

11879
return (
11980
<div className="h-full flex flex-col bg-white text-gray-900 font-sans">
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Custom hook for handling keyboard shortcuts in designer components
3+
*/
4+
import { useEffect } from 'react';
5+
6+
interface UseKeyboardShortcutsOptions {
7+
undo: () => void;
8+
redo: () => void;
9+
copyNode: (id: string) => void;
10+
cutNode: (id: string) => void;
11+
duplicateNode: (id: string) => void;
12+
pasteNode: (parentId: string | null) => void;
13+
removeNode: (id: string) => void;
14+
selectedNodeId: string | null;
15+
canUndo: boolean;
16+
canRedo: boolean;
17+
}
18+
19+
/**
20+
* Hook that sets up keyboard shortcuts for designer operations
21+
* Handles Undo/Redo, Copy/Cut/Paste, Duplicate, and Delete
22+
*/
23+
export const useKeyboardShortcuts = ({
24+
undo,
25+
redo,
26+
copyNode,
27+
cutNode,
28+
duplicateNode,
29+
pasteNode,
30+
removeNode,
31+
selectedNodeId,
32+
canUndo,
33+
canRedo,
34+
}: UseKeyboardShortcutsOptions) => {
35+
useEffect(() => {
36+
const handleKeyDown = (e: KeyboardEvent) => {
37+
// Check if we're in an editable element
38+
const target = e.target as HTMLElement;
39+
const isEditing =
40+
target.tagName === 'INPUT' ||
41+
target.tagName === 'TEXTAREA' ||
42+
target.tagName === 'SELECT' ||
43+
target.isContentEditable;
44+
45+
// Undo: Ctrl+Z / Cmd+Z
46+
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && canUndo) {
47+
e.preventDefault();
48+
undo();
49+
}
50+
// Redo: Ctrl+Y / Cmd+Shift+Z
51+
else if (((e.ctrlKey || e.metaKey) && e.key === 'y') || ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')) {
52+
if (canRedo) {
53+
e.preventDefault();
54+
redo();
55+
}
56+
}
57+
// Copy: Ctrl+C / Cmd+C (only when not editing)
58+
else if ((e.ctrlKey || e.metaKey) && e.key === 'c' && !isEditing && selectedNodeId) {
59+
e.preventDefault();
60+
copyNode(selectedNodeId);
61+
}
62+
// Cut: Ctrl+X / Cmd+X (only when not editing)
63+
else if ((e.ctrlKey || e.metaKey) && e.key === 'x' && !isEditing && selectedNodeId) {
64+
e.preventDefault();
65+
cutNode(selectedNodeId);
66+
}
67+
// Paste: Ctrl+V / Cmd+V (only when not editing)
68+
else if ((e.ctrlKey || e.metaKey) && e.key === 'v' && !isEditing) {
69+
e.preventDefault();
70+
pasteNode(selectedNodeId);
71+
}
72+
// Duplicate: Ctrl+D / Cmd+D (only when not editing)
73+
else if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !isEditing && selectedNodeId) {
74+
e.preventDefault();
75+
duplicateNode(selectedNodeId);
76+
}
77+
// Delete: Delete / Backspace (only when not editing)
78+
else if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing && selectedNodeId) {
79+
e.preventDefault();
80+
removeNode(selectedNodeId);
81+
}
82+
};
83+
84+
window.addEventListener('keydown', handleKeyDown);
85+
return () => window.removeEventListener('keydown', handleKeyDown);
86+
}, [undo, redo, copyNode, cutNode, duplicateNode, pasteNode, removeNode, selectedNodeId, canUndo, canRedo]);
87+
};

packages/designer/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export { GeneralDesigner, GeneralDesignerContent } from './components/GeneralDes
99
// Context and Hooks
1010
export { DesignerProvider, useDesigner } from './context/DesignerContext';
1111
export type { DesignerContextValue, ViewportMode, ResizingState } from './context/DesignerContext';
12+
export { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';
1213

1314
// Designer Mode Types
1415
export type { DesignerMode, DesignerConfig, FormDesignerConfig, LayoutDesignerConfig } from './types/designer-modes';

0 commit comments

Comments
 (0)