diff --git a/README.md b/README.md
index 96a215e..94b4407 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@ Built with Svelte for speed and simplicity. No overwhelming features, no endless
- ↩️ **Undo** - `Cmd/Ctrl+Z` brings back a deleted task, in its original position
- 📅 **Today & Tomorrow lists** - Plan ahead with separate task lists
- 🔄 **Unfinished work carries over** - When a new day begins, tasks you didn't finish move to Today; completed ones are cleared away
+- ✏️ **Edit a task** - Fix a typo without deleting and retyping it
- ➡️ **Push a task to tomorrow** - Didn't get to it? Move it across without retyping it
- 🎯 **Reorder by drag or keyboard** - Drag with a mouse, long-press and drag on touch, or use `Alt+↑`/`Alt+↓`
- 💾 **Persistent storage** - All tasks saved locally in your browser
@@ -89,6 +90,7 @@ The date, storage, rollover, task and undo logic lives in `src/lib/` as plain mo
### Managing Tasks
- **Add a task**: Type in the input field and press Enter
- **Complete a task**: Click the checkbox next to the task
+- **Edit a task**: Click its text. Enter saves, Escape cancels, and clicking away saves too
- **Delete a task**: Click the delete icon on the task (it shows on hover, and is always visible on touch), or press `Delete` with the task focused
- **Undo a delete**: Press `Cmd/Ctrl+Z`. The task returns to where it was
- **Move a task to Tomorrow**: Click the arrow on the task, or press `Alt+→`. From Tomorrow, the arrow points back and `Alt+←` returns it to Today
diff --git a/src/App.svelte b/src/App.svelte
index 80e9591..246e3ee 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -78,9 +78,53 @@
const viewingToday = $derived(selectedKey === todayKey);
+ let editingId = $state(null);
+ let editingText = $state('');
+ // A drag ends with a click event, which would otherwise drop the row you
+ // just moved straight into edit mode.
+ let suppressClick = false;
+
+ function startEditing(task) {
+ if (suppressClick || draggedId) return;
+ editingId = task.id;
+ editingText = task.text;
+ }
+
+ function commitEdit() {
+ if (editingId === null) return;
+ // renameTask refuses blank input, so an accidental select-all and enter
+ // leaves the task as it was rather than wiping it.
+ setTasks(taskOps.renameTask(tasks, editingId, editingText));
+ editingId = null;
+ }
+
+ function cancelEdit() {
+ editingId = null;
+ }
+
+ function handleEditKeydown(event) {
+ // Stop these reaching the row, which would delete or reorder the task
+ // being typed into.
+ event.stopPropagation();
+
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ commitEdit();
+ } else if (event.key === 'Escape') {
+ event.preventDefault();
+ cancelEdit();
+ }
+ }
+
+ function focusOnMount(node) {
+ node.focus();
+ node.select();
+ }
+
/** "Not today, tomorrow" and its reverse. Which direction depends only on
* which day you are looking at, so one control covers both. */
function deferTask(taskId) {
+ cancelEdit();
const destination = viewingToday ? tomorrowKey() : todayKey;
tasks = moveTaskToDay(storage, selectedKey, destination, taskId);
}
@@ -305,6 +349,7 @@
if (event.pointerType === 'mouse' && event.button !== 0) return;
// Let the checkbox and delete button have their clicks.
if (event.target.closest('button')) return;
+ if (event.target.closest('.task-edit')) return;
drag = {
id: tasks[index].id,
@@ -369,6 +414,10 @@
if (!wasActive) return;
+ // Swallow the click the browser fires after this drag.
+ suppressClick = true;
+ setTimeout(() => { suppressClick = false; }, 0);
+
// Clearing the inline transform while the settling class supplies a
// transition eases the card into its slot. Nothing else on the list moves,
// because nothing else changed.
@@ -398,6 +447,7 @@
}
function switchDate() {
+ cancelEdit();
selectedKey = selectedKey === todayKey ? tomorrowKey() : todayKey;
tasks = storage.loadTasks(selectedKey);
}
@@ -411,6 +461,7 @@
* they follow it there, which preserves the existing mental model.
*/
function runRollover() {
+ cancelEdit();
const now = new Date();
const wasViewingToday = selectedKey === todayKey;
@@ -613,7 +664,21 @@
{/if}
- {task.text}
+ {#if task.id === editingId}
+
+ {:else}
+
+ {/if}