keyboard.js (5.5 KB)
1 // Keyboard: key handler state and handleKeyDown dispatch 2 3 var keyHandler = { 4 backspace: { 5 canDelete: true, 6 blocked: false 7 }, 8 enter: { 9 canAdd: true, 10 blocked: false 11 }, 12 arrowDown: { 13 canAdd: true, 14 blocked: false 15 }, 16 shiftArrowDown: { 17 blocked: false 18 }, 19 shiftEnter: { 20 pressed: false 21 }, 22 shiftRight: { 23 pressed: false 24 }, 25 shiftLeft: { 26 pressed: false 27 } 28 }; 29 30 function handleKeyDown(e, task) { 31 var cmd = e.metaKey || e.ctrlKey; 32 var hasMultiSelect = state.multiSelectedIds.length > 1; 33 34 // Alt+Up/Down: extend multi-selection 35 if (e.key === 'ArrowUp' && e.altKey && !cmd && !e.shiftKey) { 36 e.preventDefault(); 37 extendMultiSelect(task, 'up'); 38 return; 39 } else if (e.key === 'ArrowDown' && e.altKey && !cmd && !e.shiftKey) { 40 e.preventDefault(); 41 extendMultiSelect(task, 'down'); 42 return; 43 } 44 45 // Shift+Enter: toggle task state (bulk if multi-selected) 46 if (e.key === 'Enter' && e.shiftKey) { 47 e.preventDefault(); 48 if (!keyHandler.shiftEnter.pressed) { 49 keyHandler.shiftEnter.pressed = true; 50 if (hasMultiSelect) { 51 bulkToggleTaskState(); 52 } else { 53 toggleTaskState(task); 54 } 55 } 56 // Cmd+Up: push into task above 57 } else if (e.key === 'ArrowUp' && cmd && !e.shiftKey && !e.altKey) { 58 e.preventDefault(); 59 if (!e.repeat) { 60 var success = hasMultiSelect ? pushMultiSelectedIntoTarget('up') : pushSubtaskIntoTarget(task, 'up'); 61 if (!success) { 62 hasMultiSelect ? shakeAllSelected('vertical') : applyShakeAnimation(task.id, 'vertical'); 63 } 64 } 65 // Cmd+Down: push into task below 66 } else if (e.key === 'ArrowDown' && cmd && !e.shiftKey && !e.altKey) { 67 e.preventDefault(); 68 if (!e.repeat) { 69 var success = hasMultiSelect ? pushMultiSelectedIntoTarget('down') : pushSubtaskIntoTarget(task, 'down'); 70 if (!success) { 71 hasMultiSelect ? shakeAllSelected('vertical') : applyShakeAnimation(task.id, 'vertical'); 72 } 73 } 74 } else if (e.key === 'ArrowRight' && cmd && !e.shiftKey && !e.altKey) { 75 e.preventDefault(); 76 hasMultiSelect ? shakeAllSelected() : applyShakeAnimation(task.id); 77 // Cmd+Left: pull out one level 78 } else if (e.key === 'ArrowLeft' && cmd && !e.shiftKey && !e.altKey) { 79 e.preventDefault(); 80 if (!e.repeat) { 81 var success = hasMultiSelect ? pullMultiSelectedOutLayer() : pullSubtaskOutLayer(task); 82 if (!success) { 83 hasMultiSelect ? shakeAllSelected() : applyShakeAnimation(task.id); 84 } 85 } 86 // Cmd+Shift+Up: push and navigate 87 } else if (e.key === 'ArrowUp' && cmd && e.shiftKey && !e.altKey) { 88 e.preventDefault(); 89 if (!e.repeat) { 90 var success = hasMultiSelect ? pushMultiSelectedIntoTarget('up', true) : pushSubtaskIntoTarget(task, 'up', true); 91 if (!success) { 92 hasMultiSelect ? shakeAllSelected('vertical') : applyShakeAnimation(task.id, 'vertical'); 93 } 94 } 95 // Cmd+Shift+Down: push and navigate 96 } else if (e.key === 'ArrowDown' && cmd && e.shiftKey && !e.altKey) { 97 e.preventDefault(); 98 if (!e.repeat) { 99 var success = hasMultiSelect ? pushMultiSelectedIntoTarget('down', true) : pushSubtaskIntoTarget(task, 'down', true); 100 if (!success) { 101 hasMultiSelect ? shakeAllSelected('vertical') : applyShakeAnimation(task.id, 'vertical'); 102 } 103 } 104 // Cmd+Shift+Left: pull and navigate 105 } else if (e.key === 'ArrowLeft' && cmd && e.shiftKey && !e.altKey) { 106 e.preventDefault(); 107 if (!e.repeat) { 108 var success = hasMultiSelect ? pullMultiSelectedOutLayer(true) : pullSubtaskOutLayer(task, true); 109 if (!success) { 110 hasMultiSelect ? shakeAllSelected() : applyShakeAnimation(task.id); 111 } 112 } 113 } else if (e.key === 'ArrowRight' && cmd && e.shiftKey && !e.altKey) { 114 e.preventDefault(); 115 hasMultiSelect ? shakeAllSelected() : applyShakeAnimation(task.id); 116 // Plain Up/Down: navigate (clears multi-select) 117 } else if (e.key === 'ArrowUp' && !e.shiftKey && !e.altKey) { 118 e.preventDefault(); 119 if (hasMultiSelect) clearMultiSelect(); 120 navigateTasks('up'); 121 } else if (e.key === 'ArrowDown' && !e.shiftKey && !e.altKey) { 122 e.preventDefault(); 123 if (hasMultiSelect) clearMultiSelect(); 124 navigateTasks('down'); 125 // Shift+Up/Down: move task 126 } else if (e.key === 'ArrowUp' && e.shiftKey && !e.altKey) { 127 e.preventDefault(); 128 if (hasMultiSelect) { 129 if (!moveMultiSelected('up')) shakeAllSelected('vertical'); 130 } else { 131 if (!moveSubtask(task, 'up')) applyShakeAnimation(task.id, 'vertical'); 132 } 133 } else if (e.key === 'ArrowDown' && e.shiftKey && !e.altKey) { 134 e.preventDefault(); 135 if (hasMultiSelect) { 136 if (!moveMultiSelected('down')) shakeAllSelected('vertical'); 137 } else { 138 if (!moveSubtask(task, 'down')) applyShakeAnimation(task.id, 'vertical'); 139 } 140 // Shift+Right: navigate into subtask (blocked during multi-select) 141 } else if (e.key === 'ArrowRight' && e.shiftKey && !e.altKey) { 142 e.preventDefault(); 143 if (hasMultiSelect) { 144 if (!keyHandler.shiftRight.pressed) { 145 keyHandler.shiftRight.pressed = true; 146 shakeAllSelected(); 147 } 148 } else if (!keyHandler.shiftRight.pressed) { 149 keyHandler.shiftRight.pressed = true; 150 if (task !== state.currentTask) { 151 navigateIntoSubtask(task); 152 } else { 153 applyShakeAnimation(task.id); 154 } 155 } 156 // Shift+Left: navigate to parent 157 } else if (e.key === 'ArrowLeft' && e.shiftKey && !e.altKey) { 158 e.preventDefault(); 159 if (hasMultiSelect && state.currentTask.id === 'root') { 160 if (!keyHandler.shiftLeft.pressed) { 161 keyHandler.shiftLeft.pressed = true; 162 shakeAllSelected(); 163 } 164 } else if (hasMultiSelect) { 165 clearMultiSelect(); 166 if (!keyHandler.shiftLeft.pressed) { 167 keyHandler.shiftLeft.pressed = true; 168 navigateToParentTask(); 169 } 170 } else if (!keyHandler.shiftLeft.pressed) { 171 keyHandler.shiftLeft.pressed = true; 172 navigateToParentTask(); 173 } 174 } 175 }