Merge branch 'worktree-agent-a41c756369d016a29'
This commit is contained in:
+16
-16
@@ -6,8 +6,8 @@
|
||||
* chip taking itself off the page. Before that, focus moves to the chip before it (Backspace) or
|
||||
* 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 and — where the pointer is fine, so there is no
|
||||
* A scrolling set marks the edges it can still scroll towards (`data-md-scroll-start`,
|
||||
* `data-md-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,
|
||||
@@ -25,23 +25,23 @@ document.addEventListener('alpine:init', () => {
|
||||
window.Alpine.data('materialChip', () => ({
|
||||
init() {
|
||||
const button = this.removeButton()
|
||||
const label = this.$root.querySelector('[data-chip-label]')
|
||||
const label = this.$root.querySelector('[data-md-chip-label]')
|
||||
|
||||
// A chip rendered by x-for has no label on the server: its button carries the translated
|
||||
// words with a `:label` placeholder, filled in once Alpine has written the label.
|
||||
if (button?.dataset.chipRemove && label) {
|
||||
if (button?.dataset.mdChipRemove && label) {
|
||||
this.$nextTick(() => {
|
||||
const text = label.textContent.trim()
|
||||
|
||||
if (text !== '' && !button.hasAttribute('aria-label')) {
|
||||
button.setAttribute('aria-label', button.dataset.chipRemove.replace(':label', text))
|
||||
button.setAttribute('aria-label', button.dataset.mdChipRemove.replace(':label', text))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
removeButton() {
|
||||
return this.$root.querySelector('[data-chip-remove]')
|
||||
return this.$root.querySelector('[data-md-chip-remove]')
|
||||
},
|
||||
|
||||
removeOnKey(event) {
|
||||
@@ -68,15 +68,15 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
handOffFocus(backwards) {
|
||||
const chip = this.$root
|
||||
const set = chip.closest('[data-chip-set]') ?? chip.parentElement
|
||||
const chips = [...(set?.querySelectorAll('[data-chip]') ?? [])]
|
||||
const set = chip.closest('[data-md-chip-set-row]') ?? chip.parentElement
|
||||
const chips = [...(set?.querySelectorAll('[data-md-chip]') ?? [])]
|
||||
const index = chips.indexOf(chip)
|
||||
const before = chips.slice(0, index).reverse()
|
||||
const after = chips.slice(index + 1)
|
||||
const onRemove = document.activeElement?.hasAttribute('data-chip-remove')
|
||||
const onRemove = document.activeElement?.hasAttribute('data-md-chip-remove')
|
||||
|
||||
for (const other of backwards ? [...before, ...after] : [...after, ...before]) {
|
||||
const target = (onRemove && other.querySelector('[data-chip-remove]:not(:disabled)')) || (other.matches(CONTROLS) ? other : other.querySelector(CONTROLS))
|
||||
const target = (onRemove && other.querySelector('[data-md-chip-remove]:not(:disabled)')) || (other.matches(CONTROLS) ? other : other.querySelector(CONTROLS))
|
||||
|
||||
if (target) {
|
||||
target.focus()
|
||||
@@ -92,17 +92,17 @@ document.addEventListener('alpine:init', () => {
|
||||
// seconds pass), whenever the set changes and focus has fallen away, focus the same
|
||||
// control again — the element itself, or the chip with its `wire:key` after a morph.
|
||||
holdFocus(target, set) {
|
||||
const key = target.closest('[data-chip]')?.getAttribute('wire:key')
|
||||
const onRemove = target.hasAttribute('data-chip-remove')
|
||||
const key = target.closest('[data-md-chip]')?.getAttribute('wire:key')
|
||||
const onRemove = target.hasAttribute('data-md-chip-remove')
|
||||
|
||||
const current = () => {
|
||||
if (target.isConnected || !key) {
|
||||
return target.isConnected ? target : null
|
||||
}
|
||||
|
||||
const chip = [...set.querySelectorAll('[data-chip]')].find((other) => other.getAttribute('wire:key') === key)
|
||||
const chip = [...set.querySelectorAll('[data-md-chip]')].find((other) => other.getAttribute('wire:key') === key)
|
||||
|
||||
return chip && (onRemove ? chip.querySelector('[data-chip-remove]') : chip.matches(CONTROLS) ? chip : chip.querySelector(CONTROLS))
|
||||
return chip && (onRemove ? chip.querySelector('[data-md-chip-remove]') : chip.matches(CONTROLS) ? chip : chip.querySelector(CONTROLS))
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
@@ -140,8 +140,8 @@ document.addEventListener('alpine:init', () => {
|
||||
const travelled = Math.abs(row.scrollLeft)
|
||||
const room = row.scrollWidth - row.clientWidth
|
||||
|
||||
row.toggleAttribute('data-scroll-start', travelled > 1)
|
||||
row.toggleAttribute('data-scroll-end', room - travelled > 1)
|
||||
row.toggleAttribute('data-md-scroll-start', travelled > 1)
|
||||
row.toggleAttribute('data-md-scroll-end', room - travelled > 1)
|
||||
}
|
||||
|
||||
// A filter chip's check changes its width without resizing the row.
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
* in.
|
||||
*
|
||||
* `<x-checkbox indeterminate>`: HTML has no attribute for a checkbox's mixed state, only the
|
||||
* `indeterminate` property, so the component marks the input `data-indeterminate` and this keeps
|
||||
* `indeterminate` property, so the component marks the input `data-md-indeterminate` and this keeps
|
||||
* the property in step with the mark — when the page loads, and when a render adds or removes the
|
||||
* mark. A person pressing the box clears the property as the browser always does; the mark stays
|
||||
* until the server decides again.
|
||||
*/
|
||||
const GROWING = 'textarea[data-autogrow]'
|
||||
const MIXED = 'input[type="checkbox"][data-indeterminate]'
|
||||
const GROWING = 'textarea[data-md-autogrow]'
|
||||
const MIXED = 'input[type="checkbox"][data-md-indeterminate]'
|
||||
|
||||
const growsByItself = window.CSS?.supports?.('field-sizing', 'content') ?? false
|
||||
|
||||
@@ -49,8 +49,8 @@ if (! growsByItself) {
|
||||
new MutationObserver((records) => {
|
||||
for (const record of records) {
|
||||
if (record.type === 'attributes') {
|
||||
if (record.attributeName === 'data-indeterminate' && record.target instanceof HTMLInputElement) {
|
||||
record.target.indeterminate = record.target.hasAttribute('data-indeterminate')
|
||||
if (record.attributeName === 'data-md-indeterminate' && record.target instanceof HTMLInputElement) {
|
||||
record.target.indeterminate = record.target.hasAttribute('data-md-indeterminate')
|
||||
} else if (! growsByItself && record.target.matches(GROWING)) {
|
||||
grow(record.target)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ new MutationObserver((records) => {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['data-indeterminate', 'data-autogrow'],
|
||||
attributeFilter: ['data-md-indeterminate', 'data-md-autogrow'],
|
||||
})
|
||||
|
||||
markAll(document)
|
||||
|
||||
@@ -78,11 +78,11 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
|
||||
// Suggestions and results never show together; count whichever one is on screen.
|
||||
const list = [...this.$refs.view.querySelectorAll('[data-search-results], [data-search-suggestions]')]
|
||||
const list = [...this.$refs.view.querySelectorAll('[data-md-search-results], [data-md-search-suggestions]')]
|
||||
.find((element) => element.getClientRects().length > 0)
|
||||
const items = list ? list.querySelectorAll('[role="listitem"], li') : []
|
||||
const total = items.length || (list ? this.results().length : 0)
|
||||
const suggesting = Boolean(list?.hasAttribute('data-search-suggestions'))
|
||||
const suggesting = Boolean(list?.hasAttribute('data-md-search-suggestions'))
|
||||
|
||||
this.announcement = total === 0
|
||||
? (announce.none ?? '')
|
||||
|
||||
+20
-18
@@ -200,8 +200,10 @@ function sliderGeometry({ width, size, fractions, centered = false, range = fals
|
||||
* effect, and anything reactive read or written on the way to the drawing would re-run it.
|
||||
*/
|
||||
function slider(root) {
|
||||
// The component's root carries the props; `root` is the control inside it the pointer presses.
|
||||
const host = root.closest('[data-md-slider]') ?? root
|
||||
const inputs = [...root.querySelectorAll(':scope > input[type="range"]')]
|
||||
const drawing = root.querySelector('[data-slider-drawing]')
|
||||
const drawing = root.querySelector('[data-md-slider-drawing]')
|
||||
const range = inputs.length > 1
|
||||
const cleanups = []
|
||||
let queued = false
|
||||
@@ -228,24 +230,24 @@ function slider(root) {
|
||||
}
|
||||
|
||||
/** M3 Expressive's second orientation: the drawing is turned a quarter, the geometry is not. */
|
||||
const vertical = () => root.dataset.orientation === 'vertical'
|
||||
const vertical = () => host.dataset.mdOrientation === 'vertical'
|
||||
|
||||
/** The track's length: the slider's height while it stands up, its width while it lies down. */
|
||||
const trackLength = () => Math.max((vertical() ? root.clientHeight : root.clientWidth) - HANDLE_WIDTH, 0)
|
||||
|
||||
const values = () => inputs.map((input) => Number.parseFloat(input.value))
|
||||
const thumbOf = (input) => drawing?.querySelector(`[data-handle="${inputs.indexOf(input) === 1 ? 'end' : 'start'}"]`)
|
||||
const thumbOf = (input) => drawing?.querySelector(`[data-md-slider-handle="${inputs.indexOf(input) === 1 ? 'end' : 'start'}"]`)
|
||||
|
||||
const tickFractions = () => [...(drawing?.querySelectorAll('[data-tick]') ?? [])].map((tick) => Number.parseFloat(tick.dataset.tick))
|
||||
const tickFractions = () => [...(drawing?.querySelectorAll('[data-md-slider-tick]') ?? [])].map((tick) => Number.parseFloat(tick.dataset.mdSliderTick))
|
||||
|
||||
const geometry = (width) => {
|
||||
const { min, max } = bounds()
|
||||
|
||||
return sliderGeometry({
|
||||
width,
|
||||
size: root.dataset.size,
|
||||
size: host.dataset.mdSize,
|
||||
fractions: values().map((value) => (Number.isFinite(value) ? clamp((value - min) / (max - min), 0, 1) : 0)),
|
||||
centered: root.hasAttribute('data-centered'),
|
||||
centered: host.hasAttribute('data-md-centered'),
|
||||
range,
|
||||
tickFractions: tickFractions(),
|
||||
})
|
||||
@@ -257,13 +259,13 @@ function slider(root) {
|
||||
}
|
||||
|
||||
const width = trackLength()
|
||||
const size = SIZES[root.dataset.size] ?? SIZES.xs
|
||||
const size = SIZES[host.dataset.mdSize] ?? SIZES.xs
|
||||
const { segments, stops, ticks, handles, activeTrack, endTrackStart } = geometry(width)
|
||||
const part = (selector) => drawing.querySelector(selector)
|
||||
const px = (number) => `${Math.round(number * 100) / 100}px`
|
||||
|
||||
for (const name of ['start', 'active', 'end']) {
|
||||
const element = part(`[data-segment="${name}"]`)
|
||||
const element = part(`[data-md-slider-segment="${name}"]`)
|
||||
const segment = segments[name]
|
||||
|
||||
if (element) {
|
||||
@@ -277,7 +279,7 @@ function slider(root) {
|
||||
}
|
||||
|
||||
for (const name of ['start', 'end']) {
|
||||
const element = part(`[data-stop="${name}"]`)
|
||||
const element = part(`[data-md-slider-stop="${name}"]`)
|
||||
|
||||
if (element) {
|
||||
element.hidden = stops[name] === undefined
|
||||
@@ -288,20 +290,20 @@ function slider(root) {
|
||||
const fractions = tickFractions()
|
||||
const spacing = fractions.length > 1 ? (width - size.corner * 2) * Math.abs(fractions[1] - fractions[0]) : Infinity
|
||||
|
||||
drawing.querySelectorAll('[data-tick]').forEach((element, i) => {
|
||||
drawing.querySelectorAll('[data-md-slider-tick]').forEach((element, i) => {
|
||||
const tick = ticks[i]
|
||||
|
||||
element.hidden = !tick || spacing < TICK_MIN_SPACING
|
||||
|
||||
if (tick) {
|
||||
element.style.left = px(tick.x)
|
||||
element.toggleAttribute('data-active', tick.active)
|
||||
element.toggleAttribute('data-md-active', tick.active)
|
||||
}
|
||||
})
|
||||
|
||||
handles.forEach((x, i) => {
|
||||
const thumb = part(`[data-handle="${i === 1 ? 'end' : 'start'}"]`)
|
||||
const label = thumb?.querySelector('[data-value-label]')
|
||||
const thumb = part(`[data-md-slider-handle="${i === 1 ? 'end' : 'start'}"]`)
|
||||
const label = thumb?.querySelector('[data-md-slider-value]')
|
||||
|
||||
if (thumb) {
|
||||
thumb.style.left = px(x)
|
||||
@@ -313,7 +315,7 @@ function slider(root) {
|
||||
})
|
||||
|
||||
// The inset icon: at the start of the active track while it fits, else of the inactive one.
|
||||
const icon = part('[data-track-icon]')
|
||||
const icon = part('[data-md-slider-icon]')
|
||||
|
||||
if (icon) {
|
||||
const room = size.icon + ICON_PADDING * 2
|
||||
@@ -321,7 +323,7 @@ function slider(root) {
|
||||
const inInactive = !inActive && endTrackStart !== null && width - endTrackStart >= room
|
||||
|
||||
icon.hidden = !inActive && !inInactive
|
||||
icon.toggleAttribute('data-active', inActive)
|
||||
icon.toggleAttribute('data-md-active', inActive)
|
||||
icon.style.left = px((inActive ? activeTrack[0] : (endTrackStart ?? 0)) + ICON_PADDING)
|
||||
}
|
||||
}
|
||||
@@ -371,10 +373,10 @@ function slider(root) {
|
||||
}
|
||||
|
||||
/** Whether a handle shows keyboard focus: narrowed, ringed and labelled. */
|
||||
const showFocus = (input, shown) => thumbOf(input)?.toggleAttribute('data-focused', shown)
|
||||
const showFocus = (input, shown) => thumbOf(input)?.toggleAttribute('data-md-focused', shown)
|
||||
|
||||
/** Compose narrows a handle and shows its label while it is pressed or dragged. */
|
||||
const pressed = (index) => drawing?.querySelectorAll('[data-handle]').forEach((thumb, i) => thumb.toggleAttribute('data-pressed', i === index))
|
||||
const pressed = (index) => drawing?.querySelectorAll('[data-md-slider-handle]').forEach((thumb, i) => thumb.toggleAttribute('data-md-pressed', i === index))
|
||||
|
||||
inputs.forEach((input) => {
|
||||
// A value written by script fires no event: catch it on the input's own setters.
|
||||
@@ -493,7 +495,7 @@ function slider(root) {
|
||||
|
||||
const mutations = new MutationObserver(schedule)
|
||||
inputs.forEach((input) => mutations.observe(input, { attributes: true, attributeFilter: ['min', 'max', 'step', 'value', 'disabled'] }))
|
||||
mutations.observe(root, { attributes: true, attributeFilter: ['data-size', 'data-centered', 'data-orientation', 'dir'] })
|
||||
mutations.observe(host, { attributes: true, attributeFilter: ['data-md-size', 'data-md-centered', 'data-md-orientation', 'dir'] })
|
||||
|
||||
const resizes = new ResizeObserver(schedule)
|
||||
resizes.observe(root)
|
||||
|
||||
Reference in New Issue
Block a user