Repository navigation
[DREAM-894] Keyboard selection, context actions and selection feedback in the work package table - #26294
Open
myabc wants to merge 8 commits into
Open
[DREAM-894] Keyboard selection, context actions and selection feedback in the work package table#26294myabc wants to merge 8 commits into
myabc wants to merge 8 commits into
Conversation
myabc
marked this pull request as ready for review
October 24, 2026 21:58
Deploying openproject with ⚡ PullPreview
|
myabc
force-pushed
the
feature/dream-894-keyboard-selection
branch
2 times, most recently
from
October 24, 2026 22:57
e021aef to
c78bfec
Compare
This was referenced Oct 25, 2026
myabc
force-pushed
the
feature/dream-894-keyboard-selection
branch
2 times, most recently
from
October 26, 2026 02:35
811be99 to
e64b6c0
Compare
myabc
force-pushed
the
feature/dream-938-row-positions
branch
from
October 26, 2026 03:52
6ec3530 to
ed11e52
Compare
myabc
force-pushed
the
feature/dream-894-keyboard-selection
branch
from
October 26, 2026 03:52
e64b6c0 to
ee4c332
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
myabc
force-pushed
the
feature/dream-894-keyboard-selection
branch
from
October 26, 2026 11:13
ee4c332 to
7b5358d
Compare
Extracts the orchestrator's announcement policy into a shared SelectionAnnouncements class in core-common, so other selection consumers can reuse the gesture rules and the render baseline. Moves the navigation/selection speak rules and the baseline tracking out of renderSelection. The orchestrator now delegates to the engine and drops its own selectionMessage helper. Its spec is unchanged. https://community.openproject.org/wp/DREAM-894
Ctrl/Cmd+Shift+A is not Select All. The shared predicate isSelectAllShortcut now returns false when Shift is held, so the shortcut falls through to the browser instead of selecting every row (OP-20202). This applies to every caller of the shared predicate: the work package table, the card view, and Backlogs. https://community.openproject.org/wp/DREAM-894
Adds a null anchor mode to selectAll that selects every selectable occurrence and clears any existing anchor, so a following Shift gesture falls back to a plain replace. Omitting the argument keeps the first-selectable fallback. Adds a selectionKeys getter for callers that compare membership across a gesture. https://community.openproject.org/wp/DREAM-894
Adds TableSelectionFeedback, created by the selection transformer only for tables with keyboard navigation enabled. Gesture sites (row click, context menu, drag collapse and Escape) run their mutation through selectionGesture, which announces via the shared SelectionAnnouncements engine. Capability-off tables run the mutation unchanged. The feedback records the membership each gesture commits as owned by the table; any other publication with a different membership drops that ownership. Pruning after a render announces only while the membership is still gesture-owned, so route, reload and programmatic selections are pruned silently. Adds the js.work_packages.selection locale keys. The card view is covered by a zero-announce test in its click handler spec; the shared selection services stay unhooked. https://community.openproject.org/wp/DREAM-894
Adds the Shift+Space, Shift+ArrowUp/Down and Ctrl/Cmd+A chords to the capability-on work package table. A new KeyboardSelection collaborator resolves the eligible rendered occurrence at the roving position and runs toggle, range and select-all through the announcement feedback. KeyboardNavigationTransformer dispatches to it on the roving stop before navigation. Shift+Arrow moves focus first and extends the range only when focus landed on a selectable row, so group headers and sums rows leave the range unchanged. Select All anchors the occurrence under focus, or none from a structural row. The legacy capture-phase select-all registration is now skipped for capability-on tables, so Ctrl/Cmd+A is handled exactly once. https://community.openproject.org/wp/DREAM-894
Opens the work package row context menu from the keyboard with Shift+F10 and the Menu (ContextMenu) key, in addition to the existing Shift+Alt+F10. Cell focus resolves to its row, and group headers, the column header and totals stay inert. Repeat keydowns of a held chord are swallowed, so holding the key opens the menu once. Browsers fire a companion contextmenu event after these keys. The shared ContextMenuHandler records each keyboard open per table with its row and swallows the first contextmenu on that row within 500 ms, so a keyboard invocation opens the menu exactly once. A repeat keydown on the same row refreshes the record, so the companion after a long hold is still swallowed. A pointerdown on the table or the table's teardown forgets the record, and a right-click on another row, or after the window, opens normally. Inside an editable control (input, textarea, select, contenteditable other than contenteditable="false", or an active edit field) both the chords and right-click are left to the browser, so the native menu shows instead of the row menu. Both handlers share one predicate for this. The work package id link keeps its native link menu for the chords as it already does for right-click. The new chords, the editor refusal and the id link exemption apply only to tables with keyboard navigation. Other tables keep their previous behaviour: only Shift+Alt+F10 opens the row menu, also on the id link and inside an editor, and right-click inside an editor opens the row menu. https://community.openproject.org/wp/DREAM-894
Focus in the body-portal context menu marks focus as having left the table, so neither recovery nor returnFocus restored it after a menu was dismissed with Escape or closed by an action. OPContextMenuService.close now carries a reason (escape, action, outside or scroll) through to the handler's onClose. The row and column context menus capture their origin position at open and return focus through it only for Escape and action closes; a click outside or a scroll closes the menu as before without moving focus. Tables without keyboard navigation keep the previous behaviour. Adds returnFocusFromMenu to KeyboardNavigation: it runs the returnFocus logic with its userPosition guard while ignoring the left-table flag, and clears the flag and claims ownership for a pending render only when focus was actually returned. It never takes focus from a control outside the table and is a no-op once the table is destroyed. Lets Escape cancel a context menu that is still opening, so the keypress closes it instead of being refused; a click outside during the opening stays refused. The context menu overlay prevents the browser's own menu for any contextmenu inside it, so a keyboard companion contextmenu that lands on the focused menu item does not show the native menu over ours. Closing a row menu for any reason forgets the table's keyboard-open record, so a contextmenu on the same row right after the close opens the menu again instead of being swallowed as a companion. The record is noted after the menu is shown, so closing a menu it replaces does not forget it. https://community.openproject.org/wp/DREAM-894
Adds Components::LiveRegion, a Capybara support component that reads Primer's polite live region through its shadow root and offers a synchronized announcement expectation. Adds a feature spec for the work package table covering Shift+Space toggling, Shift+Arrow ranges, Select All, Escape, Shift+F10 context menus with focus return, mixed pointer and keyboard gestures and the selection announcements, in flat, grouped and hierarchy tables. https://community.openproject.org/wp/DREAM-894
myabc
force-pushed
the
feature/dream-894-keyboard-selection
branch
from
October 26, 2026 14:32
7b5358d to
ae9a7ef
Compare
myabc
force-pushed
the
feature/dream-938-row-positions
branch
from
October 26, 2026 14:32
ed11e52 to
866679a
Compare
This branch has not been deployed
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.
Note
This PR is stacked on #26291 (DREAM-938, global row positions). Please review/merge that PR first.
Ticket
https://community.openproject.org/wp/DREAM-894
What are you trying to accomplish?
tl;dr for humans: Keyboard users can now select work packages and open their context menu without a mouse, and screen readers announce how many are selected.
What approach did you choose and why?
The table's keyboard owner handles selection chords before navigation, only on the focused roving stop, so editors and inner controls keep their keys. Selection still goes through
WorkPackageViewSelectionService, which gains a no-anchor Select All.Announcements use a small engine extracted from the Backlogs sortable-lists orchestrator into
core-common. Only gesture sites on the table call it, so card view, boards, the calendar and programmatic changes stay silent.Context-menu chords stay in
ContextMenuKeyboardHandler; the browser's companioncontextmenuevent is matched to the keyboard-opened row, and anypointerdowncancels the match. Focus returns only on Escape or an action, never on scroll or click-away.Watch areas
isSelectAllShortcutnow rejects Shift for every caller (table, card view, Backlogs), implementing OP-20202.OPContextMenuService.closepassing a close reason to every menu'sonClose(seventh).Out of scope: speech verification (DREAM-897), embedded tables (DREAM-896), APG-strict Escape (DREAM-940).
AI involvement
Collaborative – AI generated a substantial part of the code; I reviewed and understand every line.
Merge checklist
contextmenuon keyup; release the key only after the menu has taken focus), macOS Safari/Firefox, and VoiceOver VO+Shift+M — menu opens once, on the right target set, focus returns on Escape