Dragndrop /matching keyboard updates#1315
Conversation
There was a problem hiding this comment.
Pull request overview
This PR follows up on prior keyboard-accessibility work for Runestone’s DragNDrop and Matching interactives, adding NVDA-friendly click handling, arrow-key navigation, and stricter focus/tab behavior to reduce invalid interactions.
Changes:
- Matching: adds keyboard navigation helpers (Tab trapping, arrow navigation), click-to-activate support, and disables MathJax/tabbable nested math inside boxes.
- DragNDrop: adds click-based selection/placement, improves arrow navigation across “columns”, and disables MathJax/tabbable nested math inside premises.
- Removes the CSS pointer-events hack for MathJax in drop targets, relying instead on event handling using
currentTarget.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| bases/rsptx/interactives/runestone/matching/test/matching.test.js | Adds keyboard/mouse interaction tests for Matching (tab trapping, arrows, click behavior, math tab stops). |
| bases/rsptx/interactives/runestone/matching/js/matching.js | Implements Matching keyboard navigation, click activation, and MathJax tab-stop disabling. |
| bases/rsptx/interactives/runestone/dragndrop/test/dragndrop.test.js | Expands DragNDrop coverage for arrow navigation, click activation, and math tab stops. |
| bases/rsptx/interactives/runestone/dragndrop/js/dragndrop.js | Adds click interactions, improves focus movement logic, and hardens drop handling via currentTarget. |
| bases/rsptx/interactives/runestone/dragndrop/css/dragndrop.css | Removes pointer-events CSS workaround for MathJax inside drop zones. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| if (!this.selectedBox) { | ||
| this.selectedBox = box; | ||
| box.classList.add("selected"); | ||
| } else { | ||
| if (box !== this.selectedBox) | ||
| this.createPermanentLine(this.selectedBox, box); | ||
| this.selectedBox.classList.remove("selected"); | ||
| this.selectedBox = null; | ||
| const currentIndex = this.allBoxes.indexOf(box); | ||
| const next = this.allBoxes[currentIndex + 1]; | ||
| if (next) next.focus(); | ||
| else this.allBoxes[0].focus(); | ||
| } | ||
| this.activateBox(box); | ||
| } else if (this.selectedBox && e.key === "Tab") { |
| if (!this.selectedPremise || ev.target.closest(".premise")) { | ||
| return; | ||
| } | ||
| ev.preventDefault(); | ||
| this.placeSelectedPremise(dpSpan); |
|
Now I can't select anything in dragndrop. It is still tabbing into the math instead of stopping the top level. |
ec5be45 to
b144415
Compare
|
Just did a fresh build of the JS. I can't reproduce your failure to reproduce. :) Just force pushed with a change to also make response Mathjax untabbable. Now the only way to reach them is when placing a premise. Looks like the two new Copilot suggestions are existing code that is working. Will let you decide whether to adopt those two suggestions. If you still can't reproduce I'll loop back in a week. |
| activateBox(box) { | ||
| if (!this.selectedBox) { | ||
| this.setSelectedBox(box); | ||
| const firstOppositeBox = this.getTabbableBoxes()[0]; | ||
| firstOppositeBox?.focus(); | ||
| return; | ||
| } | ||
|
|
||
| if (box !== this.selectedBox) { | ||
| this.createPermanentLine(this.selectedBox, box); | ||
| } | ||
| this.setSelectedBox(null); | ||
| box.focus(); | ||
| } |
| dgSpan.addEventListener("click", (ev) => { | ||
| ev.preventDefault(); | ||
| if (this.selectedPremise === dgSpan) { | ||
| this.deselectPremise(); | ||
| } else { | ||
| this.selectPremise(dgSpan); | ||
| } | ||
| }); |
|
Found a quirk when NVDA is engaged. Marking as draft until I'm back from Colorado. |
Follow up to address issues in #1308
Also fixes another issue: when NVDA is active it consumes Enter/Space and generates click events, so we need to allow for those as if they were enter
Also implements changes to matching questions: