7.8 KiB
Keyboard Shortcuts System
Overview
Constellation Analyzer now features a centralized keyboard shortcut management system that prevents conflicts, provides priority-based handling, and offers built-in documentation through a help modal.
Architecture
Core Components
-
useKeyboardShortcutManager (
src/hooks/useKeyboardShortcutManager.ts)- Core hook that manages shortcut registration and event handling
- Provides conflict detection
- Supports priority-based execution
- Platform-aware (Mac vs Windows/Linux)
-
KeyboardShortcutContext (
src/contexts/KeyboardShortcutContext.tsx)- React context provider making the shortcut manager available throughout the app
- Ensures single global event listener for all shortcuts
-
useGlobalShortcuts (
src/hooks/useGlobalShortcuts.ts)- Centralized registration of all application-wide shortcuts
- Single source of truth for what shortcuts exist
-
KeyboardShortcutsHelp (
src/components/Common/KeyboardShortcutsHelp.tsx)- Modal component displaying all available shortcuts
- Automatically generated from registered shortcuts
- Grouped by category
Available Shortcuts
Document Management
- Ctrl+N - New Document
- Ctrl+O - Open Document Manager
- Ctrl+S - Export Document
- Ctrl+W - Close Current Document
Graph Editing
- Ctrl+Z - Undo
- Ctrl+Y or Ctrl+Shift+Z - Redo
- Delete or Backspace - Delete selected nodes/edges (handled by React Flow)
Selection
- Ctrl+A - Select All (placeholder for future implementation)
- Escape - Deselect All (handled by React Flow)
View
- F - Fit View to Content
Navigation
- Ctrl+Tab - Next Document
- Ctrl+Shift+Tab - Previous Document
- ? - Show Keyboard Shortcuts Help
Implementation Details
Shortcut Definition
Shortcuts are defined using the KeyboardShortcut interface:
interface KeyboardShortcut {
id: string; // Unique identifier
description: string; // Shown in help UI
key: string; // Key to press
ctrl?: boolean; // Requires Ctrl/Cmd modifier
shift?: boolean; // Requires Shift modifier
alt?: boolean; // Requires Alt/Option modifier
handler: () => void; // Function to execute
priority?: number; // Higher = executed first (default: 0)
category: ShortcutCategory; // For grouping in help
enabled?: boolean; // Can be disabled (default: true)
}
Platform Detection
The system automatically detects the platform:
- Mac: Uses
Cmdkey (metaKey) - Windows/Linux: Uses
Ctrlkey (ctrlKey)
Display strings adapt accordingly:
- Mac: "Cmd+N"
- Windows/Linux: "Ctrl+N"
Conflict Detection
When registering a shortcut, the system checks for conflicts:
- Same key combination
- Same modifiers
- Different ID
Conflicts are logged to console as warnings but don't prevent registration.
Priority Handling
If multiple shortcuts match the same key combination:
- Sort by priority (higher number = higher priority)
- Execute only the highest priority handler
- Default priority is 0
Example: Ctrl+Shift+Z has lower priority than Ctrl+Y for redo, so Ctrl+Y is preferred.
Adding New Shortcuts
Global Shortcuts
Add to src/hooks/useGlobalShortcuts.ts:
const shortcutDefinitions: KeyboardShortcut[] = [
// ... existing shortcuts
{
id: 'my-new-shortcut',
description: 'Do Something',
key: 'k',
ctrl: true,
handler: () => doSomething(),
category: 'Graph Editing',
},
];
Component-Specific Shortcuts
Use the context in any component:
import { useKeyboardShortcuts } from '../contexts/KeyboardShortcutContext';
function MyComponent() {
const { shortcuts } = useKeyboardShortcuts();
useEffect(() => {
shortcuts.register({
id: 'component-specific',
description: 'Component Action',
key: 'x',
ctrl: true,
handler: () => handleAction(),
category: 'Graph Editing',
});
return () => shortcuts.unregister('component-specific');
}, [shortcuts]);
}
Adding Menu Items
When adding a new shortcut that should appear in the menu, update MenuBar.tsx:
<button
onClick={() => {
myAction();
closeMenu();
}}
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center justify-between"
>
<span>My Action</span>
<span className="text-xs text-gray-400">Ctrl+K</span>
</button>
Design Decisions
Why Not Use ? as a Regular Character
The ? key doesn't require Shift in the shortcut definition because:
- It's simpler for users to press just
? - Consistent with industry standards (VS Code, GitHub, etc.)
- The key value is already
?when Shift is pressed
Why Centralized vs Distributed
Advantages of centralized system:
- Single source of truth for all shortcuts
- Conflict detection
- Automatic help documentation
- Easier to maintain and audit
- Priority-based resolution
Disadvantages:
- Slightly more complex initial setup
- All shortcuts must be registered centrally or cleanup properly
Why Context vs Global Singleton
Using React Context provides:
- Better integration with React lifecycle
- Automatic cleanup
- Testability
- Type safety
Migration from Old System
The old useKeyboardShortcuts hook has been replaced with useGlobalShortcuts. The migration involved:
- Before: Event listeners scattered across components
- After: Centralized registration with automatic documentation
The old hook has been preserved for reference but should not be used for new shortcuts.
Future Enhancements
Possible Additions
-
User-Configurable Shortcuts
- Allow users to customize key bindings
- Store in localStorage
- UI for rebinding
-
Shortcut Contexts
- Different shortcuts active in different app modes
- Disable/enable groups of shortcuts
-
Chord Shortcuts
- Multi-key sequences (e.g., "Ctrl+K, Ctrl+S")
- Inspired by VS Code
-
Shortcut Recording
- Let users record custom shortcuts
- Visual feedback during recording
-
Platform-Specific Overrides
- Different shortcuts for Mac vs Windows
- Better ergonomics per platform
Excluded from Current Implementation
Node Type Creation Shortcuts (e.g., P for Person, O for Organization)
- Reason: User-configurable node types make fixed shortcuts inappropriate
- Alternative: Context menu (right-click) or toolbar remain the recommended methods
- Users can have custom types like "Department", "Resource", etc., so hardcoded letters wouldn't make sense
Testing
To test the keyboard shortcut system:
- Build the application:
npm run build - Start the dev server:
npm run dev - Test shortcuts:
- Press
?to see all available shortcuts - Try Ctrl+N for new document
- Try Ctrl+Z/Ctrl+Y for undo/redo
- Try F to fit view
- Press
- Check conflict detection:
- Look at browser console during startup
- Verify no conflict warnings appear
Troubleshooting
Shortcut Not Working
- Check browser console for conflict warnings
- Verify shortcut is registered in
useGlobalShortcuts - Check if handler is properly passed (not undefined)
- Verify
enabledis not set to false - Check if another shortcut has higher priority
Shortcut Not Appearing in Help
- Verify
enabledis not set to false - Check the category is correct
- Ensure shortcut is registered before help modal opens
Conflicts
If you see conflict warnings:
- Change one of the conflicting shortcuts
- Or use priority to determine which should win
- Or disable one of the shortcuts conditionally
References
- UX Analysis:
UX_ANALYSIS.md(lines 58-104) - Implementation docs: Inline comments in source files
- React Flow keyboard handling: https://reactflow.dev/learn/advanced-use/accessibility