Improve attachment accessibility#1053
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves accessibility and keyboard ergonomics for attachments (including galleries) by moving destructive controls out of the contenteditable, improving screen-reader announcements for NodeSelections, and adding keyboard-based attachment reordering while aiming to keep serialized HTML stable.
Changes:
- Introduces a contextual
<lexxy-attachment-toolbar>(Alt+F10 / Esc) and removes the inline delete button element. - Adds a live region +
announce()API and uses it to announce keyboard reordering actions (Alt+Shift+Arrow…). - Improves caret/selection behavior around decorator nodes (atomic caret stepping + “fake” DOM selection anchoring) and enhances caption accessibility/keyboard exit.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/javascript/unit/helpers/direction.test.js | Adds unit coverage for the new Direction helper. |
| test/browser/tests/prompts/mention_navigation.test.js | Adds browser coverage for keyboard navigation/select behavior around mentions. |
| test/browser/tests/formatting/horizontal_divider.test.js | Updates divider deletion to use the new contextual toolbar. |
| test/browser/tests/attachments/gallery.test.js | Refactors duplicated gallery helpers into shared helpers. |
| test/browser/tests/attachments/gallery_navigation.test.js | Adds keyboard navigation tests for moving selection within galleries. |
| test/browser/tests/attachments/drop_in_gallery.test.js | Uses shared gallery helpers instead of local duplicates. |
| test/browser/tests/attachments/attachments.test.js | Updates attachment deletion to use the new contextual toolbar. |
| test/browser/tests/attachments/attachment_toolbar.test.js | Adds coverage for toolbar visibility, shortcut focusing, and delete action. |
| test/browser/tests/attachments/attachment_keyboard_move.test.js | Adds coverage for Alt+Shift+Arrow attachment move/reorder + announcements. |
| test/browser/tests/attachments/attachment_fake_selection.test.js | Adds coverage that the fake selection span is attached and labeled. |
| test/browser/tests/attachments/attachment_caption.test.js | Adds coverage for Tab-to-caption and Esc-to-exit caption editing. |
| test/browser/helpers/gallery_test_helpers.js | Centralizes gallery helper utilities used by multiple tests. |
| test/browser/helpers/attachment_helpers.js | Adds shared helpers for selecting attachments and building attachment HTML. |
| src/nodes/horizontal_divider_node.js | Removes inline delete UI and adds a label for accessibility tooling. |
| src/nodes/custom_action_text_attachment_node.js | Removes inline delete UI, adds decorative alt="" for inner images, adds label. |
| src/nodes/action_text_attachment_node.js | Adds label, caption focus helper, caption label mirroring, Esc-to-exit caption behavior. |
| src/helpers/lexical_helper.js | Adds selection helpers for labelled decorator nodes + editor announce helper. |
| src/helpers/direction.js | Introduces a forward/backward direction abstraction used by caret + reorder logic. |
| src/extensions/attachments_extension.js | Registers new attachment accessibility behaviors (tab-to-caption, fake selection, caret, keyboard move). |
| src/elements/node_delete_button.js | Removes the old inline delete button custom element. |
| src/elements/live_region.js | Adds a debounced polite live region element for announcements. |
| src/elements/index.js | Registers new custom elements (live region + attachment toolbar) and removes old one. |
| src/elements/editor.js | Installs live region and attachment toolbar; exposes announce(). |
| src/elements/attachment_toolbar.js | Adds contextual toolbar UI for removing the currently selected labelled decorator node. |
| src/editor/attachments/keyboard_move.js | Implements Alt+Shift+Arrow attachment moves + gallery creation/extraction + announcements. |
| src/editor/attachments/fake_selection.js | Implements the hidden span + DOM-range parking for SR focus-mode announcements. |
| src/editor/attachments/decorator_node_caret.js | Adds atomic caret behavior around decorator nodes and NodeSelection drop-back logic. |
| app/assets/stylesheets/lexxy-editor.css | Updates floating-controls styling for the new attachment toolbar and visually-hidden helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e992e22 to
0f64cdd
Compare
0f64cdd to
69193a2
Compare
| connectedCallback() { | ||
| this.classList.add("lexxy-floating-controls") | ||
| this.role = "toolbar" | ||
| this.ariaLabel = "Attachment actions" | ||
| this.hidden = true | ||
|
|
||
| if (this.#editor) { | ||
| this.#setUpButtons() | ||
| this.#monitorSelection() | ||
| this.#registerKeyboardShortcut() | ||
| } |
69193a2 to
d2a0007
Compare
There was a problem hiding this comment.
@brunoprietog I've made a few comments to start threads on the various workarounds proposed. I do want to note the high quality of the execution of the workarounds; my questions are on overall approach.
There was a problem hiding this comment.
Is the Alt+F10 shortcut tied to a floating delete button? Could the same shortcut be mapped to jumping on/off an individual DecoratorNode's deletion button?
I ask as floating buttons are more difficult to style onto the node and can end up mis-aligned with the node. The best positional code is the one we don't have to write. I think it's better to have more complexity jumping onto/off each node's button rather than moving a singleton button around.
There was a problem hiding this comment.
Alt+F10 is used to focus the toolbar, which could have any button actually. The problem is that we can't have those buttons inside the contenteditable element, which is why I left them as siblings. I improved the code there by using a ResizeObserver.
There was a problem hiding this comment.
This is the change which gives me the most pause and seems like it will need the most continuing maintenance to keep working.
What's the viability of manually announcing what's in the NodeSelection accessibility-wise?
There was a problem hiding this comment.
This was the trickiest part for sure. The reason for this fake selection is that we need to make the screen reader believe that something has changed in the cursor itself. If we use a live region or anything else, even if we can announce the correct information, the cursor will also be read, with incorrect information about the cursor, such as the first letter of the previous paragraph. In other words, the live region is just an additional announcement, but it doesn’t fix the incorrect announcements about the cursor.
d2a0007 to
ac3d093
Compare
ac3d093 to
2899401
Compare
2899401 to
fb8b659
Compare
fb8b659 to
91a131f
Compare
91a131f to
16fa22d
Compare
Make attachments and galleries usable by keyboard and screen-reader users while keeping the existing markup and serialized HTML unchanged. Selection announcements - Move the per-attachment Remove control out of the contenteditable into a contextual <lexxy-attachment-toolbar> sibling, reachable with Alt+F10 and dismissed with Esc. The inline <lexxy-node-delete-button> is gone. - Park the DOM range on a visually hidden span inside the figure when an attachment receives a NodeSelection, so the screen reader announces the widget label in focus mode. Lexical would otherwise clear the DOM range and leave nothing to read. Caret behaviour around decorator nodes - One DecoratorNodeCaret class keeps every figure atomic for the caret: arrow keys collapse the visually-identical offset adjacent to the decorator node into its NodeSelection (including cross-block entry into galleries and mention-leading paragraphs), stepping off lands the caret on the inline neighbour or the adjacent block (and otherwise stays on the figure rather than letting Chromium drop the caret inside), and any non-arrow key drops the NodeSelection back to a normal RangeSelection so typing, Ctrl+Home / Ctrl+End, Escape and the rest of the keyboard keep working on a familiar selection. - Decorative <img>s inside a custom attachment carry an empty alt so screen readers don't read them as a graphic on top of the decorator node's own accessible label. Caption - Label the <textarea> with aria-label and let Esc exit caption editing by re-selecting the attachment. Live region - Add a polite aria-live region on <lexxy-editor> with a debounced announce() helper. Used to announce keyboard reorder moves. Keyboard reordering (issue #877) - Alt+Shift+Arrow moves the selected attachment up/down and creates or extracts galleries when adjacent images line up. When the attachment is already at the start or end of its container, the screen reader is told instead of the move silently failing.
16fa22d to
1314191
Compare
Make attachments and galleries usable by keyboard and screen-reader users while keeping the existing markup and serialized HTML unchanged.
Selection announcements
Caret behaviour around decorator nodes
<img>s inside a custom attachment carry an empty alt so screen readers don't read them as a graphic on top of the decorator node's own accessible label.Caption
<textarea>with aria-label and let Esc exit caption editing by re-selecting the attachment.Live region
aria-liveregion on<lexxy-editor>with a debouncedannounce()helper. Used to announce keyboard reorder moves.Keyboard reordering (issue #877)