- social: likes/comments rejected on a closed event (e2e proven). - admin: patch_config validates-then-writes atomically; char-based length. - hashtag/comment models: atomic tag writes in edit + comment paths. - Modal: inert the background (modal-inert action) for screen readers. - sse.ts: idempotent visibilitychange listener (no duplicate reconnects). - diashow: videos advance on `ended` (transitions + page wiring). - docs/hygiene: README clone-case fix; removed stale committed .env.test and gitignored it; docker-compose.dev.yml tidy. Left documented as accepted-risk per plan: PIN-persist-after-logout, UUID-reachable hidden uploads, DECISION-media-auth.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
30 lines
1020 B
TypeScript
30 lines
1020 B
TypeScript
// Make everything *outside* a modal's subtree inert while it's open, so screen
|
|
// readers and tab order can't reach the background (focus-trap only covers
|
|
// keyboard focus; `inert` also hides the content from assistive tech).
|
|
//
|
|
// Walks from the node up to <body> and marks every sibling along the path as
|
|
// inert, then restores exactly those it changed on teardown. Applying it to a
|
|
// `display: contents` wrapper keeps the modal's own backdrop + dialog interactive.
|
|
export function modalInert(node: HTMLElement) {
|
|
const changed: HTMLElement[] = [];
|
|
|
|
let el: HTMLElement | null = node;
|
|
while (el && el !== document.body) {
|
|
const parent: HTMLElement | null = el.parentElement;
|
|
if (!parent) break;
|
|
for (const sib of Array.from(parent.children)) {
|
|
if (sib !== el && sib instanceof HTMLElement && !sib.hasAttribute('inert')) {
|
|
sib.setAttribute('inert', '');
|
|
changed.push(sib);
|
|
}
|
|
}
|
|
el = parent;
|
|
}
|
|
|
|
return {
|
|
destroy() {
|
|
for (const sib of changed) sib.removeAttribute('inert');
|
|
}
|
|
};
|
|
}
|