/** * Small pieces shared by more than one component, in the pattern of breakpoints.js: a plain * function or two, not a framework. */ /** * The index an Arrow/Home/End key moves to from `index` (which may be -1, nothing chosen yet), * wrapping around the ends; null when the key means nothing here. `vertical` picks Up/Down over * Left/Right; `rtl` mirrors Left/Right (Up/Down never mirror). A caller that answers to both axes * at once — a chip set's row, which also takes Up/Down — calls this twice and takes whichever * answers. */ export function ringIndex(key, index, length, { rtl = false, vertical = false } = {}) { if (key === 'Home') { return 0 } if (key === 'End') { return length - 1 } const forward = vertical ? 'ArrowDown' : rtl ? 'ArrowLeft' : 'ArrowRight' const backward = vertical ? 'ArrowUp' : rtl ? 'ArrowRight' : 'ArrowLeft' if (key === forward) { return (index + 1 + length) % length } if (key === backward) { return index < 0 ? length - 1 : (index - 1 + length) % length } return null } /** A CSS custom property's time value in milliseconds, whichever unit it is written in; null when unset. */ export function ms(element, token) { const value = getComputedStyle(element).getPropertyValue(token).trim() const number = parseFloat(value) if (Number.isNaN(number)) { return null } return value.endsWith('ms') ? number : number * 1000 } /** Writes to localStorage, quietly: a blocked or full store still lets the page work, it just won't remember. */ export function remember(key, value) { try { localStorage.setItem(key, value) } catch { // Blocked storage: the page still works, it just will not remember. } } const REOPEN_GUARD_MS = 250 /** * A `popover="auto"` closes on the press that lands on its trigger, and the click that follows * would open it again. A close this recent is taken as that press. It is timed from * `beforetoggle`, which fires as the popover closes: `toggle` is queued, and arrives after that * click. Only a close the browser made on its own (light dismiss) counts — `closing()` marks one * this script makes as explicit, so a keyboard command that shuts it is never mistaken for that * race, only the browser's own light dismiss racing the click it precedes is. */ export function reopenGuard() { let closedAt = -Infinity let closingExplicitly = false return { beforeToggle(event) { if (event.newState === 'closed' && !closingExplicitly) { closedAt = performance.now() } closingExplicitly = false }, closing() { closingExplicitly = true }, canOpen() { return performance.now() - closedAt > REOPEN_GUARD_MS }, } }