Build a graphical "select your seats" picker for a seated slot, the interactive equivalent of the current per-section list at /seating/{slot}. It draws the hall from the venue geometry landed in the seating foundation (issue #3611780 and its parent): seat x/y/angle, the venue map-background SVG and the seat sprites. A visitor picks seats by clicking the map, with full keyboard and screen-reader support.

Rendering. The venue map-background SVG is drawn once as an inert backdrop; each bookable seat is a real button positioned by its x/y and turned by its angle, in the background's own coordinate space, so seats overlay the hall with no transform. Seats are colored by their grade, drawn with a seat sprite, and carry a state: available, selected or taken. A legend lists the grades present, never color alone.

Data. The controller builds one compact payload per slot: the seats (id, x, y, angle, grade color and label, section, row, place, position, availability, accessible) plus the background SVG and sprite ids. Availability comes from SeatAvailability (free seats for the slot); every other venue seat renders as taken. A price is shown per grade where the slot categories resolve one.

Interaction. Click a seat to select or deselect it. Zoom and pan: wheel to cursor, pinch, plus and minus and fit buttons, with panning clamped to the hall. A "best places" helper offers three modes: together (a run of adjacent seats in one row), nearby (a compact cluster) and any (the best individual seats), reusing the server adjacency logic for the together mode.

Keyboard and screen reader. One tab stop per section; arrow keys move seat to seat within a section (left and right along the row by position, up and down between rows), Enter selects, N jumps to the next free seat. Each seat button has a full accessible name (section, row, place, state, grade, price); the hover tooltip is decorative and aria-hidden, so nothing depends on it. Selection changes are announced in an aria-live region.

Progressive enhancement and fallback. The graphical map is layered over the existing picker: with no JavaScript, or on a venue with no geometry, the current grouped section and row list with the single-seat and party selectors remain the working fallback. Selecting on the map fills the same submission the form already handles, so booking still goes through the engine under the slot lock (a single hold, or a group hold for several seats).

Delivery. A seat-map render element plus a JavaScript behavior and stylesheet in yoyaku_seating, self-contained with no external assets, theme aware in light and dark and honoring reduced motion. Covered by a kernel test for the payload builder and a functional JavaScript test for selecting and booking on the map; the existing list fallback keeps its test.

Out of scope. Editing seat geometry on the map (that stays in the admin seat generator), real payment, and mixed general-admission sections.

Issue fork yoyaku-3611792

Command icon 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

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review

Opened the merge request with slice 1: the graphical picker (payload builder, JS/CSS renderer, progressive-enhancement of the booking form) plus a kernel test. Verified selecting and booking on the map. The reseat "change my seat" mode follows as a separate slice.

  • mably committed fcfc4b63 on 1.x
    feat: #3611792 Graphical seat-map selector: pick seats by clicking the...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.