Feature/task list keyboard project management - #2176
Open
thisisthedave wants to merge 32 commits into
Open
Conversation
- Task-list shortcuts pause while an Obsidian menu or modal is open. - Menu/modal Escape and Backspace events remain owned by the overlay. - Focus returns to the originating task after an overlay closes. - Focus is not stolen if the user intentionally moves to another control. - Escape clears task selection without moving focus to the Bases root. - Escape dismisses search; Backspace dismisses an already-empty search and returns to the focused task. - IME composition and editable controls suppress task-list shortcuts. - All listeners and pending restoration timers are cleaned up with the view.
- Selection/focus visuals now have deterministic precedence:
- Purple selection background overrides gray focus background.
- Gray focus border overrides the primary-selection border.
- Every selected task gets a subdued purple border.
- The primary selected task retains the brighter purple border
when not focused.
- The first rendered task receives keyboard focus when the task view
initially loads, so shortcuts work immediately without clicking a
task first.
- Selection state is rehydrated whenever Bases rebuilds the view
after an edit.
- Batch property, tag, and archive edits no longer explicitly clear
selection. Delete and “Clear selection” still do.
- Task-list views listen for workspace leaf activation and restore
the previously focused card when their tab returns to the
foreground.
- Keyboard shortcuts now remain available when focus is on a non-
interactive part of the foreground task view. Actions use the
remembered focused card as their target.
- Cancelling search restores card focus, and subsequent rerenders
continue restoring it.
- / now creates and opens the search controls when search was
disabled for the Bases view, then focuses the input.
- Existing search controls are simply focused.
Keyboard multi-edit actions now: - Target only selected tasks present in the current filtered view. - Preserve hidden tasks in the selection state. - Do nothing if every selected task is filtered out. - Continue using the focused task when there is no active selection.
- Enter in search preserves the query and returns focus to the remembered task.
- Added customizable actions:
- Clear focus/selection: Escape, Backspace
- Toggle selection: Space
- Copy selected/focused visible task titles: Ctrl/Cmd+C
- Select all visible tasks: Ctrl/Cmd+A
- Toggle archive: Y
- Legacy shortcut settings migrate to the new action names.
- Mixed archived/unarchived selections show a warning instead of toggling unpredictably.
- Shortcut conflict detection and replace/cancel behavior automatically covers the new actions.
- Fixed literal Space key normalization.
The active Task List now pushes a child keyboard scope containing its configured shortcuts. This gives bindings such as Ctrl+B and Ctrl+D precedence over global Bold/Delete Paragraph commands while: - The Task List leaf is active. - Keyboard focus belongs to a non-editable part of that view. - No menu, modal, search input, or editor owns input. The scope is popped when the leaf deactivates, focus enters an editable control, or the view unloads.
- Hotkey recording now pushes a temporary Obsidian Scope that consumes Escape, cancels recording, and
prevents the underlying Settings dialog from closing. Cleanup is idempotent and also runs during
rerenders.
- Escape is now captured as a normal shortcut and forwarded to the confirmation flow without closing
Settings.
- New shortcuts require confirmation before being saved.
- “Add shortcut” is now a circle-plus icon.
- Reset appears before Add.
- Existing bindings display a circle-x icon beside the value; it turns red on hover.
- Added scoped styling and updated regression coverage.
- Moving the mouse over a task updates its visual/remembered focus without stealing DOM focus. - Moving over empty list space leaves focus unchanged. - Selection toggle targets the mouse-focused task. - Navigation shortcuts continue from the hovered task, then switch back to normal keyboard/DOM focus. - Uses one delegated mousemove listener, compatible with rerenders and virtualized cards.
- Mouse hover now moves actual card focus, clearing stale gray focus styling from the previous card.
- Keyboard navigation switches the list to keyboard-cursor mode and suppresses lingering :hover
backgrounds on non-focused cards.
- Selected-task purple styling remains unaffected.
- Edit overlays now schedule focus restoration when opened and continue monitoring until closed.
- After task rerenders, focus returns to the replacement card, reactivating shortcut ownership while
preserving selection.
- Active interactive controls inside cards are protected from hover stealing focus.
- Assigned projects appear in the selector. - Projects can be removed from the active task selection.
- Dragging a task between project groups now replaces all existing project
assignments with the destination project.
- Other list grouping behavior remains unchanged.
- File-link grouping is unaffected.
- Added tests for frontmatter mutation and post-drop task state.
- drag between project groups replaces all projects. - shift+drag adds the destination project while preserving every existing project. - drag between tag groups replaces all tags. - shift-drag adds the destination tag while preserving every existing tag. The Appearance & UI → Task Interaction group now includes “Project and tag drop behavior” with: - Dropping replaces values - Dropping adds values - Dropping replaces; Shift-drop adds — default
- The setting now applies to every list-valued grouping property, including
contexts, aliases, and custom Obsidian list properties.
- Renamed the setting to “List-property drop behavior” and generalized its help
text.
- Replacement mode now replaces the entire list for any list property.
- Additive mode preserves all existing values and adds the destination.
- Changed native drag effects from move-only to allow all effects. Additive
drops advertise copy; replacement drops advertise move. This permits modifier
keys during drag initiation as intended by the HTML drag API. MDN drag
operations
- Project-group drops now resolve the project file and generate the same canonical project reference used by the task modal.
- Respects the useFrontmatterMarkdownLinks setting; otherwise writes wikilinks
such as "[[Project Name]]".
- Dragging a selected task moves all selected tasks that are currently visible.
- Filtered-out selected tasks are not modified.
- Dragging an unselected task moves only that task.
- Every affected task receives the destination group mutation and property-
change side effects.
- The card under the pointer determines the insertion position.
- Selection remains intact after the drop.
- Lexically sorts values when forming list-valued group identities. - Merges equivalent groups before rendering. - Handles projects, tags, contexts, and custom list properties. - Does not modify the order stored in task frontmatter. - Leaves scalar grouping unchanged.
- Navigate down: ArrowDown, j - Navigate up: ArrowUp, k - Toggle selection: Space, x
thisisthedave
commented
Jul 30, 2026
Comment on lines
+77
to
+95
| export function convertBasesListGroupKeyToString(key: unknown): string { | ||
| if (key === null || key === undefined) { | ||
| return convertBasesGroupKeyToString(key); | ||
| } | ||
|
|
||
| const basesKey = key as BasesValueInternals; | ||
| const actualValue = extractBasesGroupKeyValue(basesKey); | ||
| if (!Array.isArray(actualValue)) { | ||
| return formatBasesGroupKeyValue(actualValue); | ||
| } | ||
|
|
||
| const sortedValues = actualValue.map(stringifyUnknown).sort((left, right) => { | ||
| if (left < right) return -1; | ||
| if (left > right) return 1; | ||
| return 0; | ||
| }); | ||
| return sortedValues.length > 0 ? sortedValues.join(", ") : "None"; | ||
| } | ||
|
|
Author
There was a problem hiding this comment.
Normalizing list-value fields by sorting makes the task view groups insensitive to list order. In other words, a task with projects "A, B" is grouped together with a task in projects "B, A".
thisisthedave
commented
Jul 30, 2026
Comment on lines
+122
to
+127
| if (plan.replacesListGroupingValue) { | ||
| if (plan.normalizedTargetGroupKey === null) { | ||
| delete frontmatter[plan.frontmatterKey]; | ||
| } else { | ||
| frontmatter[plan.frontmatterKey] = [plan.normalizedTargetGroupKey]; | ||
| } |
Author
There was a problem hiding this comment.
This implements "move" semantics for drag & drop.
thisisthedave
marked this pull request as ready for review
July 30, 2026 21:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR improves Task List interaction through configurable
keyboard navigation, persistent focus and selection, batch editing, and richer
grouped-property drag-and-drop behavior.
It also adds project-assignment editing, normalizes list-valued group headings,
and introduces settings for controlling whether drops move or copy values
between groups.
New features
Keyboard navigation and focus
Added keyboard navigation through Task List cards.
Default navigation shortcuts now include:
Moving the mouse over a task transfers focus to that card.
Keyboard focus is restored when:
Logical task focus is tracked independently from transient DOM elements,
preventing rerenders from leaving focus on document.body.
Focus and selection have distinct, consistent styling:
focus border remains visible.
Task selection and batch actions
Existing task-editing shortcuts now operate on the active selection.
Batch operations only affect tasks that are both selected and currently
visible. Tasks hidden by search or filtering remain selected but are not
edited.
Selection is preserved across:
Pressing Escape inside a contextual popup or search field no longer
unintentionally clears the Task List selection.
Clearing selection leaves the Task List ready for continued keyboard
navigation.
Configurable Task List shortcuts
action.
Task List shortcuts use an Obsidian child scope while the view is active,
allowing them to work even when the same chord is assigned to a global editor
command.
Added duplicate detection with cancel and replace options.
Shortcut capture now includes an explicit confirm/cancel step.
Escape can be assigned as a shortcut without dismissing the underlying
Settings dialog.
Shortcut bindings can be individually removed using a circle-x action.
New bindings are added using a circle-plus action.
Each action can be reset to its defaults.
Search integration
Project assignment editing
assignments as well as adding new ones.
projectsproperty.Grouped-property drag and drop
Drag and drop between groups now works generically for list-valued properties,
including projects, tags, contexts, and compatible custom list properties.
Three configurable behaviors are available under Appearance & UI → Task
Interaction:
Move between groups — replace the source group values with the destination
values.
Copy between groups — preserve existing values and add the destination values.
Move; Ctrl/Option to copy — move by default, or preserve existing values when
the modifier is held. This is the default.
Additional behavior includes:
move behavior.
Stable grouping for list properties
List-valued group keys are normalized before being submitted to the Bases
renderer.
For example, these project assignments now appear in the same group:
Normalization affects grouping identity only and does not rewrite the order
stored in task frontmatter.
Implementation hotspots
Testing
The PR adds focused unit coverage for: