Give a scrolling chip set a scroll button at each edge
M3 § Chips, Accessibility asks that a row which overflows horizontally carry a visible affordance; `<x-chip-set scroll>` faded its edges and left it there (plan step 24, audit docs/audits/m3-alignment/inputs.md § Missing). Where the pointer is fine, and so there is no swipe to reach for, a button now sits over each fading edge and scrolls the row by most of its width. They are pointer affordances only — no tab stops, since the arrow keys already walk every chip — and the row's scroll padding grows on a fine pointer so a chip the keyboard reaches clears the buttons as well as the fade. The scroller became a ref, because the buttons stand outside it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
549d96b0dc
commit
c8357cfd4d
+18
-5
@@ -7,8 +7,11 @@
|
||||
* after it (Delete), because a focused element that disappears leaves focus on the body.
|
||||
*
|
||||
* A scrolling set marks the edges it can still scroll towards (`data-scroll-start`,
|
||||
* `data-scroll-end`), and the set fades those edges. Its row carries `wire:ignore.self`, so a
|
||||
* Livewire morph keeps the marks.
|
||||
* `data-scroll-end`), and the set fades those edges and — where the pointer is fine, so there is no
|
||||
* swipe — puts a button over each of them, which is the visible affordance M3's chips accessibility
|
||||
* page asks a scrolling row for. Its row carries `wire:ignore.self`, so a Livewire morph keeps the
|
||||
* marks. Wrapping, the row is the element with `x-data`; scrolling, it is that element's `row` ref,
|
||||
* because the buttons stand outside the scroller.
|
||||
*
|
||||
* A set is also one tab stop with a roving tabindex, and the arrow keys walk its controls, which is
|
||||
* M3's chip keyboard table ("Arrows: moves focus between chips"; "only one chip can be in focus
|
||||
@@ -126,9 +129,11 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
window.Alpine.data('materialChipSet', () => ({
|
||||
observers: [],
|
||||
row: null,
|
||||
|
||||
init() {
|
||||
const row = this.$el
|
||||
// A scrolling set hangs its buttons outside the scroller, so the row is a ref there.
|
||||
const row = (this.row = this.$refs.row ?? this.$el)
|
||||
|
||||
const mark = () => {
|
||||
// scrollLeft runs negative towards the end in a right-to-left row.
|
||||
@@ -167,7 +172,15 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
/** Everything in the set the keyboard can reach, in the order it is written. */
|
||||
controls() {
|
||||
return [...this.$el.querySelectorAll(CONTROLS)]
|
||||
return [...this.row.querySelectorAll(CONTROLS)]
|
||||
},
|
||||
|
||||
/** A scroll button: most of a row's width towards one of its ends. */
|
||||
nudge(towards) {
|
||||
const rtl = getComputedStyle(this.row).direction === 'rtl'
|
||||
const step = Math.max(this.row.clientWidth * 0.8, 120)
|
||||
|
||||
this.row.scrollBy({ left: (towards === 'start' ? -1 : 1) * (rtl ? -1 : 1) * step, behavior: 'smooth' })
|
||||
},
|
||||
|
||||
/** One tab stop: the control focus is on, or the first one, is the only one Tab reaches. */
|
||||
@@ -203,7 +216,7 @@ document.addEventListener('alpine:init', () => {
|
||||
return
|
||||
}
|
||||
|
||||
const forwards = getComputedStyle(this.$el).direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'
|
||||
const forwards = getComputedStyle(this.row).direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'
|
||||
const backwards = forwards === 'ArrowRight' ? 'ArrowLeft' : 'ArrowRight'
|
||||
|
||||
let next = null
|
||||
|
||||
Reference in New Issue
Block a user