Merge pull request 'fix(ui): the phone's context sheet says when it scrolls' (#209) from fix/207-sheet-scroll-affordance into main
This commit was merged in pull request #209.
This commit is contained in:
@@ -155,10 +155,55 @@ export class MenuSurface extends LitElement {
|
||||
bottom was at y=452 on a 439px screen -- the one row a
|
||||
destructive action is most likely to be. The cap has to stay
|
||||
(a sheet covering the whole screen is a page, not a sheet),
|
||||
so the body is what gives. */
|
||||
so the body is what gives.
|
||||
|
||||
**And a body that scrolls says so** (#207). Scrolling was the
|
||||
whole of the fix above, which left the last item reachable
|
||||
and nothing on screen admitting it was there -- measured at
|
||||
424x439, eight items ending at y=470 with the fold at 439,
|
||||
and worse when the cut lands on a row boundary, where the
|
||||
sheet ends in a clean edge that reads as the end of the list.
|
||||
|
||||
Two layers, and the *order* is what asks the question: a
|
||||
shadow pinned to the bottom of the box (attachment scroll),
|
||||
and over it a cover of the sheet's own colour painted at the
|
||||
end of the *content* (attachment local), which therefore
|
||||
scrolls up over the shadow and hides it exactly when there is
|
||||
nothing more to see. So the affordance is absent on a menu
|
||||
that fits, present the moment one does not, and gone again at
|
||||
the end of the list -- with no scroll listener, no
|
||||
measurement, and nothing reaching into wa-dialog's shadow
|
||||
root for the scroller. background-attachment is Chrome 4;
|
||||
the reference device is Chrome 113.
|
||||
|
||||
**The curve is steep because the rows under it stay live.**
|
||||
A scrim over a menu item is that item's text surface, and
|
||||
this app's rule is that text clears 4.5:1 on every surface it
|
||||
can sit on -- which the light ramp, whose bgElevated is
|
||||
#e9ecef, is what makes non-theoretical. A row is 48px with
|
||||
its label centred, so 32px of scrim that is already down to
|
||||
a quarter strength at 14px reaches y-centre at about 0.06 and
|
||||
spends its weight on the strip below the last legible label.
|
||||
Measured on the dark ramp at x=300, flat 52,58,64 throughout
|
||||
before: 50,56,62 at y=330, 33,37,40 at y=350 and 22,24,27 at
|
||||
the bottom edge, and flat again at the end of the list. The
|
||||
light ramp puts 9.9:1 on the last label. */
|
||||
wa-dialog::part(body) {
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
background:
|
||||
linear-gradient(
|
||||
var(--yj-bg-elevated, #343a40),
|
||||
var(--yj-bg-elevated, #343a40)
|
||||
)
|
||||
bottom / 100% 32px no-repeat local,
|
||||
linear-gradient(
|
||||
to top,
|
||||
rgba(0, 0, 0, 0.6) 0%,
|
||||
rgba(0, 0, 0, 0.25) 45%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
)
|
||||
bottom / 100% 32px no-repeat scroll;
|
||||
}
|
||||
|
||||
/* A sheet is dragged at with a thumb, so it says where its top
|
||||
|
||||
@@ -206,6 +206,56 @@ describe('menu-surface', () => {
|
||||
expect(dismissed, 'no menu-dismiss reached the document').toBe(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* The scroll affordance (#207), and this is the mechanism again
|
||||
* rather than the symptom.
|
||||
*
|
||||
* The sheet's body has scrolled since #60 and said nothing about
|
||||
* it: measured at 424x439, eight items ended at y=470 with the
|
||||
* fold at 439, and where the cut lands on a row boundary the sheet
|
||||
* ends in a clean edge that reads as the end of the list.
|
||||
*
|
||||
* What makes the fade *conditional* — absent on a menu that fits,
|
||||
* present the moment one does not, gone again at the end of the
|
||||
* list — is `background-attachment`, not a scroll listener: a cover
|
||||
* of the sheet's own colour is painted at the end of the content
|
||||
* and attached `local`, over a shadow pinned to the box and
|
||||
* attached `scroll`. So the pair of attachments *is* the feature,
|
||||
* and it is what this asserts. The rendered result was measured in
|
||||
* the harness (dark ramp 52,58,64 flat before; 52,57,63 at the last
|
||||
* label and 22,24,27 at the bottom edge with more below; flat again
|
||||
* at the end of the list) and is on the PR.
|
||||
*/
|
||||
it('paints the fade only while there is more below', async () => {
|
||||
const el = await surfaceWithPanel();
|
||||
|
||||
const wrapper = el.shadowRoot?.querySelector('wa-dialog');
|
||||
|
||||
await (wrapper as HTMLElement & { updateComplete: Promise<unknown> })
|
||||
.updateComplete;
|
||||
|
||||
const body = wrapper?.shadowRoot?.querySelector('[part~="body"]');
|
||||
|
||||
expect(body, 'no body part to scroll').not.toBeNull();
|
||||
|
||||
const style = getComputedStyle(body as Element);
|
||||
|
||||
expect(style.overflowY, 'the body is what gives, not the cap').toBe(
|
||||
'auto',
|
||||
);
|
||||
|
||||
// The cover scrolls with the content; the shadow does not. Either
|
||||
// one alone is a fade that is always there or never there.
|
||||
expect(
|
||||
style.backgroundAttachment,
|
||||
'the cover must be local and the shadow must not',
|
||||
).toBe('local, scroll');
|
||||
|
||||
// Both sit at the bottom, or the cover hides nothing.
|
||||
expect(style.backgroundPosition).toBe('50% 100%, 50% 100%');
|
||||
expect(style.backgroundSize).toBe('100% 32px, 100% 32px');
|
||||
});
|
||||
|
||||
/**
|
||||
* A dialog with no accessible name is what `utils/name-dialog.ts`
|
||||
* exists for; here the name is already written on the panel, so no
|
||||
|
||||
Reference in New Issue
Block a user