mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-10-08 20:57:51 +00:00
fix: preserve platform shortcuts and IME input on the board (#1550)
Co-authored-by: bradgroux <brad@digitalmeld.io>
This commit is contained in:
parent
9733e817a2
commit
46e0709f28
3 changed files with 65 additions and 2 deletions
|
|
@ -113,6 +113,62 @@ describe('KeyboardProvider', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it.each(['metaKey', 'ctrlKey', 'altKey', 'shiftKey'] as const)(
|
||||
'leaves %s combinations to the platform',
|
||||
(modifier) => {
|
||||
const create = vi.fn();
|
||||
const move = vi.fn();
|
||||
function RegisterCreate() {
|
||||
const { setOpenCreateDialog } = useKeyboard();
|
||||
React.useEffect(() => setOpenCreateDialog(create), [setOpenCreateDialog]);
|
||||
return null;
|
||||
}
|
||||
render(
|
||||
<KeyboardProvider>
|
||||
<RegisterCreate />
|
||||
<TestConsumer tasks={[createMockTask({ id: 'task_one' })]} onMoveTask={move} />
|
||||
</KeyboardProvider>
|
||||
);
|
||||
for (const key of ['c', 'j', 'k', '1', 'ArrowDown', 'ArrowUp', 'Enter']) {
|
||||
const event = new KeyboardEvent('keydown', { key, [modifier]: true, cancelable: true });
|
||||
fireEvent(window, event);
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
}
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
expect(move).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('selected').textContent).toBe('none');
|
||||
fireEvent.keyDown(window, { key: 'c' });
|
||||
expect(create).toHaveBeenCalledOnce();
|
||||
}
|
||||
);
|
||||
|
||||
it('preserves the intentional chat chord and ignores IME composition', () => {
|
||||
const chat = vi.fn();
|
||||
const create = vi.fn();
|
||||
function RegisterCommands() {
|
||||
const { setOpenChatPanel, setOpenCreateDialog } = useKeyboard();
|
||||
React.useEffect(() => {
|
||||
setOpenChatPanel(chat);
|
||||
setOpenCreateDialog(create);
|
||||
}, [setOpenChatPanel, setOpenCreateDialog]);
|
||||
return null;
|
||||
}
|
||||
render(
|
||||
<KeyboardProvider>
|
||||
<RegisterCommands />
|
||||
</KeyboardProvider>
|
||||
);
|
||||
fireEvent.keyDown(window, { key: 'C', metaKey: true, shiftKey: true });
|
||||
fireEvent.keyDown(window, { key: 'C', ctrlKey: true, shiftKey: true });
|
||||
expect(chat).toHaveBeenCalledTimes(2);
|
||||
fireEvent.keyDown(window, { key: 'C', ctrlKey: true, shiftKey: true, altKey: true });
|
||||
fireEvent.keyDown(window, { key: 'C', metaKey: true, shiftKey: true, isComposing: true });
|
||||
fireEvent.keyDown(window, { key: 'c', isComposing: true });
|
||||
fireEvent.keyDown(window, { key: 'c', keyCode: 229 });
|
||||
expect(chat).toHaveBeenCalledTimes(2);
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('starts with no selected task and help closed', () => {
|
||||
renderWithProvider();
|
||||
expect(screen.getByTestId('selected').textContent).toBe('none');
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@ export function KanbanBoard() {
|
|||
// Register filtered tasks with keyboard context
|
||||
useEffect(() => {
|
||||
setTasks(filteredTasks);
|
||||
return () => setTasks([]);
|
||||
}, [filteredTasks, setTasks]);
|
||||
|
||||
// Handler for opening a task
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ export function KeyboardProvider({ children }: { children: ReactNode }) {
|
|||
const target = e.target instanceof HTMLElement ? e.target : null;
|
||||
if (
|
||||
e.defaultPrevented ||
|
||||
e.isComposing ||
|
||||
e.keyCode === 229 ||
|
||||
target?.isContentEditable ||
|
||||
target?.closest(
|
||||
'input, textarea, select, button, a[href], summary, [role="button"], [role="link"], [role="switch"], [role="checkbox"], [role="radio"], [role="combobox"], [role="listbox"], [role="option"], [role="slider"], [role="spinbutton"], [role="tab"], [role="menuitem"]'
|
||||
|
|
@ -143,12 +145,16 @@ export function KeyboardProvider({ children }: { children: ReactNode }) {
|
|||
const currentIndex = selectedTaskId ? taskList.findIndex((t) => t.id === selectedTaskId) : -1;
|
||||
|
||||
// Cmd+Shift+C (or Ctrl+Shift+C on Windows/Linux) - Toggle chat panel
|
||||
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'C') {
|
||||
if (e.metaKey !== e.ctrlKey && !e.altKey && e.shiftKey && e.key.toLowerCase() === 'c') {
|
||||
e.preventDefault();
|
||||
openChatPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Unregistered modifiers belong to the platform or focused application.
|
||||
// Shift is permitted only for '?' so shortcut help remains reachable.
|
||||
if (e.metaKey || e.ctrlKey || e.altKey || (e.shiftKey && e.key !== '?')) return;
|
||||
|
||||
switch (e.key) {
|
||||
case 'c':
|
||||
e.preventDefault();
|
||||
|
|
@ -215,7 +221,7 @@ export function KeyboardProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (selectedTaskId && onMoveTaskRef.current) {
|
||||
if (selectedTaskId && currentIndex >= 0 && onMoveTaskRef.current) {
|
||||
onMoveTaskRef.current(selectedTaskId, newStatus);
|
||||
} else {
|
||||
// Show toast when no task is selected
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue