Problem/Motivation
The only way to add a component, a block or a preset from the library is to drag it onto the canvas. That is slow on a long page, hard on a small screen, and fails WCAG 2.2 2.5.7 Dragging Movements, which asks for a single-pointer alternative.
Proposed resolution
A click on a library item inserts it, as Drupal Canvas, Gutenberg, Webflow and GrapesJS do. Enter or Space on a focused item does the same. Dragging still works.
- With an element selected, the item lands right after it, in the same slot.
- With nothing selected, the item lands at the end of the display.
- The new element opens in the settings sidebar, the next step after adding it. Selected, it also keeps a series of clicks in order. A preset opens nothing: it is a ready-made arrangement, not an element to set up.
- When the slot of the selection is full, the item lands right after the component owning that slot, one level up, and so on up to the root. Drag never sends a drop into a full dropzone, and the click must not either: the server answers it with the generic "refresh the page" error.
- When no dropzone has room, the root included, a click does nothing: the row shows no "+" and the grab hand before the click, since a drag may still find room, and the click is announced as "There is no room left here."
A plain text row does not say that a click inserts it, nor where. So:
- Signifier: a pointer cursor instead of the grab hand, and a "+" at the end of the row under the pointer or the keyboard focus.
- Feedforward: a line on the Canvas where the item will land, as the Gutenberg inserter draws on hover.
- Feedback: the new element is scrolled into view, and announced with
Drupal.announce().
A first draft offered a ⋮ menu per item with four options: at the beginning, at the end, before and after the selection. It was dropped: four lines of text for one click, and a second Tab stop on every library row. "Before" and "at the beginning" are an insert plus a move, which #3624637: Plan: Builder accessibility and UX plans without dragging.
Implementation:
- Library items get
role="button", so a row stays one Tab stop. - The click posts to the existing
api_root_attachorapi_slot_attachroute, with the payload and position a drop sends. The server cannot tell it from a drop. - The selection is resolved with the visible-only lookup of
keyboard.js, moved to a shared helper, so a stale copy in a hidden panel is never the target. - A locked dropzone, full for now, is detected with the check the drag already uses,
isDropzoneLocked()ofdropzone.js, shared rather than copied. Locks still to come,expectedor a lock panel, will hold for the click too.
Remaining tasks
- Review.
User interface changes
A click on a library item inserts it. A pointer cursor and a "+" on the row, and a line on the Canvas, show it before the click. The new element opens in the settings sidebar, except a preset.
API changes
None. The click uses the existing attach routes.
Data model changes
None.
Issue fork display_builder-3628802
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mogtofu33 commentedComment #4
mogtofu33 commentedComment #7
mogtofu33 commented