add vertical shake; apply shake animation when nav/push/pull fails
Hunter
Saturday, March 7th, 2026 at 6:52 PM
216 .shake {
217 animation: shake 0.25s ease-out;
218 }
219 + @keyframes shake-vertical {
220 + 0% { transform: translateY(0); }
221 + 20% { transform: translateY(3px); }
222 + 40% { transform: translateY(-3px); }
223 + 60% { transform: translateY(3px); }
224 + 80% { transform: translateY(-3px); }
225 + 100% { transform: translateY(0); }
226 + }
227 + .shake-vertical {
228 + animation: shake-vertical 0.25s ease-out;
229 + }
230 html {
231 scroll-behavior: smooth;
232 overflow-y: scroll;
598 keyHandler.enter.blocked = true;
599 addNewSubtask(currentTask, task);
600 }
- } else if (e.key === 'ArrowDown' && !e.shiftKey) {
601 + } else if (e.key === 'ArrowDown' && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
602 if (keyHandler.arrowDown.blocked) {
603 e.preventDefault();
604 return;
734 }
735 }
736
- function applyShakeAnimation(taskId) {
737 + function applyShakeAnimation(taskId, direction = 'horizontal') {
738 const checkbox = document.querySelector(`.task-container[data-id="${taskId}"] .checkbox-label`);
739 if (checkbox) {
- checkbox.classList.add('shake');
740 + const className = direction === 'vertical' ? 'shake-vertical' : 'shake'
741 + checkbox.classList.add(className);
742 setTimeout(() => {
- checkbox.classList.remove('shake');
- }, 250); // Remove class after animation completes
743 + checkbox.classList.remove(className);
744 + }, 250);
745 }
746 }
747
882 }
883
884 function handleKeyDown(e, task) {
885 + const cmd = e.metaKey || e.ctrlKey;
886 +
887 if (e.key === 'Enter' && e.shiftKey) {
888 e.preventDefault();
889 if (!keyHandler.shiftEnter.pressed) {
890 keyHandler.shiftEnter.pressed = true;
891 toggleTaskState(task);
892 }
- } else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
893 + } else if (e.key === 'ArrowUp' && cmd && !e.shiftKey) {
894 + e.preventDefault();
895 + if (!e.repeat && !pushSubtaskIntoTarget(task, 'up')) {
896 + applyShakeAnimation(task.id, 'vertical');
897 + }
898 + } else if (e.key === 'ArrowDown' && cmd && !e.shiftKey) {
899 e.preventDefault();
- if (!e.repeat) pushSubtaskIntoTarget(task, 'up');
- } else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
900 + if (!e.repeat && !pushSubtaskIntoTarget(task, 'down')) {
901 + applyShakeAnimation(task.id, 'vertical');
902 + }
903 + } else if (e.key === 'ArrowRight' && cmd && !e.shiftKey) {
904 + e.preventDefault();
905 + applyShakeAnimation(task.id);
906 + } else if (e.key === 'ArrowLeft' && cmd && !e.shiftKey) {
907 + e.preventDefault();
908 + if (!e.repeat && !pullSubtaskOutLayer(task)) {
909 + applyShakeAnimation(task.id);
910 + }
911 + } else if (e.key === 'ArrowUp' && cmd && e.shiftKey) {
912 + e.preventDefault();
913 + if (!e.repeat && !pushSubtaskIntoTarget(task, 'up', true)) {
914 + applyShakeAnimation(task.id, 'vertical');
915 + }
916 + } else if (e.key === 'ArrowDown' && cmd && e.shiftKey) {
917 + e.preventDefault();
918 + if (!e.repeat && !pushSubtaskIntoTarget(task, 'down', true)) {
919 + applyShakeAnimation(task.id, 'vertical');
920 + }
921 + } else if (e.key === 'ArrowLeft' && cmd && e.shiftKey) {
922 + e.preventDefault();
923 + if (!e.repeat && !pullSubtaskOutLayer(task, true)) {
924 + applyShakeAnimation(task.id);
925 + }
926 + } else if (e.key === 'ArrowRight' && cmd && e.shiftKey) {
927 e.preventDefault();
- if (!e.repeat) pushSubtaskIntoTarget(task, 'down');
928 + applyShakeAnimation(task.id);
929 } else if (e.key === 'ArrowUp' && !e.shiftKey) {
930 e.preventDefault();
931 navigateTasks('up');
932 } else if (e.key === 'ArrowDown' && !e.shiftKey) {
933 e.preventDefault();
934 navigateTasks('down');
- } else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
- e.preventDefault();
- if (!e.repeat) pullSubtaskOutLayer(task, true);
- } else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
- e.preventDefault();
- if (!e.repeat) pushSubtaskIntoTarget(task, 'up', true);
- } else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
- e.preventDefault();
- if (!e.repeat) pushSubtaskIntoTarget(task, 'down', true);
935 } else if (e.key === 'ArrowUp' && e.shiftKey) {
936 e.preventDefault();
- moveSubtask(task, 'up');
937 + if (!moveSubtask(task, 'up')) {
938 + applyShakeAnimation(task.id, 'vertical');
939 + }
940 } else if (e.key === 'ArrowDown' && e.shiftKey) {
941 e.preventDefault();
- moveSubtask(task, 'down');
942 + if (!moveSubtask(task, 'down')) {
943 + applyShakeAnimation(task.id, 'vertical');
944 + }
945 } else if (e.key === 'ArrowRight' && e.shiftKey) {
946 e.preventDefault();
947 if (!keyHandler.shiftRight.pressed) {
949 if (task !== currentTask) {
950 navigateIntoSubtask(task);
951 } else {
- // Apply shake animation when trying to navigate into the parent task
952 applyShakeAnimation(task.id);
953 }
954 }
- } else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
- e.preventDefault();
- if (!e.repeat) pullSubtaskOutLayer(task);
955 } else if (e.key === 'ArrowLeft' && e.shiftKey) {
956 e.preventDefault();
957 if (!keyHandler.shiftLeft.pressed) {
962 }
963
964 function moveSubtask(subtask, direction) {
965 + if (subtask === currentTask) return false;
966 +
967 const parentTask = findParentTask(subtask);
- if (!parentTask) return;
968 + if (!parentTask) return false;
969
970 const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
- if (index === -1) return;
971 + if (index === -1) return false;
972
973 if (direction === 'up' && index > 0) {
974 [parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]];
975 } else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
976 [parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]];
977 + } else {
978 + return false;
979 }
980
981 renderCurrentView();
982 selectAndFocusTask(subtask);
983 scheduleSave();
984 + return true;
985 }
986
987 function pushSubtaskIntoTarget(subtask, direction, navigate = false) {
988 // Don't allow pushing the current parent task
- if (subtask === currentTask) {
- return;
- }
989 + if (subtask === currentTask) return false;
990
991 if (navigate) {
992 document.documentElement.style.scrollBehavior = 'auto';
993 }
994
995 const parentTask = findParentTask(subtask);
- if (!parentTask) return;
996 + if (!parentTask) return false;
997
998 const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
- if (index === -1) return;
999 + if (index === -1) return false;
1000
1001 let targetTask = null;
1002 if (direction === 'up' && index > 0) {
1005 targetTask = parentTask.subtasks[index + 1];
1006 }
1007
- if (!targetTask) return;
1008 + if (!targetTask) return false;
1009
1010 // Remove the subtask from its current parent
1011 parentTask.subtasks.splice(index, 1);
1033 }
1034
1035 scheduleSave();
1036 + return true;
1037 }
1038
1039 function pullSubtaskOutLayer(subtask, navigate = false) {
1040 // Don't allow pulling the current parent task
- if (subtask === currentTask) {
- return;
- }
1041 + if (subtask === currentTask) return false;
1042
1043 // Don't allow pulling when at root level
- if (taskPath.length <= 1) {
- return;
- }
1044 + if (taskPath.length <= 1) return false;
1045
1046 if (navigate) {
1047 document.documentElement.style.scrollBehavior = 'auto';
1091 }
1092
1093 scheduleSave();
1094 + return true;
1095 }
1096
1097