Android: context menus should be a bottom sheet, not a popover that gets clipped #60
Notifications
Due Date
No due date set.
Blocks
#63 Android: touch gestures for track lists — swipe to queue, tap to play, long-press to multi-select, double-tap for the menu
yonlu/yellowjacket
#67 Android: move inline entity links into the item's context menu
yonlu/yellowjacket
#71 Android: the "More" navigation should not be a side drawer
yonlu/yellowjacket
Reference: yonlu/yellowjacket#60
Reference in New Issue
Block a user
Report
The long-press context menu on a track can end up behind other UI, and its buttons are too small for touch. Make the mobile context menu a bottom drawer with large, obvious options.
Findings
wa-popup/wa-dropdown-itemanchored to the touch point, withutils/context-menu-controller.tsandMenuKeyboardproviding one keyboard model. A popup near the bottom of a 439 CSS px viewport has nowhere to go.popover="manual", which the reference device's Chrome 113 does not support (Popover API is Chrome 114) — so top-layer promotion is not available there and clipping is expected, not incidental.wa-dialogand there is deliberately no sixth pattern; a bottom sheet should be awa-dialogvariant (focus trap, Escape, accessible name viautils/name-dialog.ts) rather than a new hand-rolled overlay.Direction
A
<action-sheet>built onwa-dialog, taking the same item model the popup menus use, selected by viewport width so every existing menu call site gains it without changing. Large rows (48px+), swipe-down and backdrop tap to dismiss. Keep the keyboard model working — it is shared.#69 has added a sixth menu call site, and deliberately did not build a
bottom sheet for it — flagging it here so this issue inherits it
rather than meeting it.
#69's Direction said "a bottom sheet on phone — see the context-menu
issue". Deciding it the other way, and the argument is this issue's own:
your Direction is an
<action-sheet>onwa-dialog"taking the sameitem model the popup menus use, selected by viewport width so every
existing menu call site gains it without changing". A phone-specific
surface built inside
page-headeris therefore either thrown away whenthat lands, or becomes exactly the second pattern this issue exists to
prevent. Blocking #69 on a Phase 4 issue to avoid that would have left a
live
Priority/Highdefect open — every Playlists action unreachable ona phone — for the sake of a surface that is going to be replaced from
one place anyway.
So
page-header's overflow menu iswa-popup+wa-dropdown-item+the shared
MenuKeyboard, the same as the other five. What that meansfor you:
<button aria-haspopup="menu" aria-expanded aria-controls>; the panelis a
role="menu"witharia-label="More actions", renderedunconditionally so
aria-controlsnames something real, and hidden bywa-popup's own.popup:not(.popup-active) { display: none }.PageAction[]is{id, label, icon, onSelect, priority, drop?}. Ifthe
<action-sheet>takes an item model, this call site can hand itone directly — it is the cheapest of the six to convert and probably
the one to prototype against.
is worth knowing so it is not used as evidence either way: this menu
opens downward from the top of a full-height view, so it has
somewhere to go even without top-layer promotion. Your Chrome 113
finding (
popover="manual"is Chrome 114) is untouched — a menuanchored to a touch point near the bottom of a 439px viewport still
has nowhere to go, and that is still the reason this issue exists.
wa-dropdown-itemdefaults, not the 48px+ this issue asks for, and ona phone every Playlists action is in that menu — so this call site
is now one of the places where the size complaint is most visible.
Nothing here blocks you and nothing here needs undoing first.
Why this happens, from the Web Awesome source — it is sharper than
"113 has no Popover API", and it explains why #62's popover was fine.
wa-popuprenders<div popover="manual" part="popup">and featuredetects:
So it already has a fallback and does not simply break on 113: with
no Popover API it positions with
strategy: "fixed"instead of relyingon the top layer.
position: fixedis not enough here, and the reason is in our ownstylesheet.
contain: paintmakes an element a containing block forfixed-position descendants and clips them.
index.csshas:Every context menu in this list is rendered from a component inside
.main-panel—track-list,cover-grid,artists-view,genres-view, the two Explore detail views. On a browser with thePopover API the menu is promoted to the top layer and escapes all of
that; on 113 it is a fixed-position box contained and clipped by the
main panel. That is a deterministic, structural clip, not a
z-index race, and it is why the report says "behind other UI".
The contrast is the useful part. While doing #62 I measured
job-indicator's popover at the device's own 424x439 and it wasneither clipped nor covered at any width. That is not luck: it lives in
.top-bar, and no ancestor of the top bar has containment, so eventhe fixed fallback lands correctly. Same component, same browser, two
different outcomes decided by which side of
contain: paintthe menuis on.
Two consequences for this issue:
wa-dialoguses the native<dialog>top layer, which 113 doeshave (
showModal()is Chrome 37) — so the sheet escapes containmentfor the same reason the drawer and every dialog in the app already
do. That is a stronger argument for the Direction than "the buttons
are small".
both support the Popover API, so the menus are top-layered and
correct; a spec asserting "the menu is not clipped" passes on the
broken build. If you want a regression guard, assert the mechanism
— that the menu is a dialog rather than a popup at phone widths —
rather than the symptom.
Not claiming this; filed because whoever takes it should not have to
re-derive it.
Taking this, branch
60-context-menu-action-sheet. It opens Phase 4 in#73's order and is the item I flagged on #174 as the one to take next.
Why now, specifically: a device is attached. Your second comment
says it outright — "No tier here can reproduce the clip", because CI's
Chromium and WebKit both have the Popover API. So today this issue rests
on reading the Web Awesome source, which is good evidence and is not a
measurement. A Light Phone III (Android 14, arm64, Chrome 113, 424x439)
is plugged in, so the first thing I will do is reproduce the clip on
the real engine and post the numbers, before writing any code. If it
does not reproduce, that is worth knowing more than the fix is.
The plan, and the one thing I would do differently from the
Direction.
Your Direction asks for an
<action-sheet>onwa-dialog"selected byviewport width so every existing menu call site gains it without
changing". The mechanism is right and I am keeping it; the "without
changing" is not quite reachable, and I would rather say so up front
than quietly do twelve edits.
The controller does not own the markup — each host renders its own
<wa-popup>with a.context-menu-panelinside it. So a zero-editversion would have to move those nodes at open time, and Lit owns
them. What is reachable is a one-line change per call site: a
<menu-surface>element with the same.active/.anchorAPIContextMenuControlleralready drives, which renders awa-popupabove600px and a
wa-dialogsheet below it, with the host's existing panelslotted into either. Twelve tag swaps, no other change at any call site,
and the decision lives in one component.
contextMenuStylesis what makes the rest free, and it is the pieceworth knowing before starting: it is already a shared stylesheet every
one of these hosts includes, and it already styles
.context-menu-panel wa-dropdown-item. So the 48px rows go there oncerather than twelve times, which is the "gains it without changing" your
Direction was reaching for, one layer down from where it was expected.
The element existence is
matchMedia, not a media query —job-bandand
player-controls' rule, since this decides whether a<dialog>isin the tree at all.
Deliberately not in this pass, and I will say so on the issue rather
than leave it implied: the playlist submenu. It is a hover-anchored
flyout; inside a top-layer dialog it stops being clipped, which is this
issue's actual complaint, but a nested flyout is not a sheet. I will
measure it on the device and file what I find rather than growing this
change to cover it.
The regression guard will assert the mechanism — that the surface is
a native
<dialog>and not a popup at phone width — for the reason yourcomment gives and the reason
queue-as-a-screen.spec.tsdoes the same:a spec asserting "not clipped" passes on the broken build in every tier
that can run it.