Browser tab: no focus highlight on the page
Every key on the app's keyboard takes focus from the page for a moment and gives it back, so the site's focus ring (a 2px blue outline on the search box, and a brighter border) flashed with each letter. Outline rings are turned off with the page's clean-up CSS, and the guest preload strips outline, border and box-shadow from the site's own :focus / :focus-within / :focus-visible rules, keeping anything else those rules do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -167,6 +167,71 @@ document.addEventListener('focusout', (e) => {
|
||||
}, 0);
|
||||
}, true);
|
||||
|
||||
// ------------------------------------------------------------------ no focus highlight
|
||||
/*
|
||||
* Nothing on the page shows that it has focus. The app's keyboard is outside the page, so every key press
|
||||
* takes focus from the page for a moment and gives it straight back: a site's focus style would flash
|
||||
* with each letter — and on stage it is clutter anyway. Outline rings (the browser's own and any site's)
|
||||
* go with one rule in browser-tab.js's CLEAN_CSS; this takes the rest — a border that brightens, a glow —
|
||||
* out of the site's own :focus / :focus-within / :focus-visible rules, and leaves everything else those
|
||||
* rules do: a menu that opens on focus still opens. Sites add rules as they go (CSS-in-JS, late
|
||||
* stylesheets), so this runs again just before anything can take focus. Another origin's stylesheets
|
||||
* cannot be read and keep theirs; the outline rule still covers their rings.
|
||||
*/
|
||||
const FOCUS_LOOK = /^(?:outline|box-shadow|border)(?:-|$)/;
|
||||
const seenRules = new WeakSet();
|
||||
|
||||
// Split at commas that are not inside brackets or quotes: "a:hover, b:is(c, d)" -> 2; "x:1; y:f(a;b)" -> 2.
|
||||
function splitTop(text, sep) {
|
||||
const out = [];
|
||||
let depth = 0, quote = '', start = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
if (quote) { if (c === '\\') i++; else if (c === quote) quote = ''; continue; }
|
||||
if (c === '"' || c === "'") quote = c;
|
||||
else if (c === '(' || c === '[') depth++;
|
||||
else if (c === ')' || c === ']') depth--;
|
||||
else if (c === sep && depth === 0) { out.push(text.slice(start, i).trim()); start = i + 1; }
|
||||
}
|
||||
out.push(text.slice(start).trim());
|
||||
return out.filter(Boolean);
|
||||
}
|
||||
// Styled BECAUSE it has focus. ":not(:focus)" styles a field at rest, so it does not count.
|
||||
function focusDriven(sel) {
|
||||
return /:focus(?:-within|-visible)?(?![\w-])/.test(sel.replace(/:not\((?:[^()]|\([^()]*\))*\)/g, ''));
|
||||
}
|
||||
function isLook(decl) { return FOCUS_LOOK.test(decl.slice(0, decl.indexOf(':')).trim().toLowerCase()); }
|
||||
|
||||
function stripFocusLook(list, owner) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const r = list[i];
|
||||
if (r.cssRules && r.cssRules.length) stripFocusLook(r.cssRules, r); // @media, @supports, @layer, nesting
|
||||
if (!r.selectorText || !r.style || seenRules.has(r)) continue;
|
||||
seenRules.add(r);
|
||||
const sels = splitTop(r.selectorText, ',');
|
||||
const focus = sels.filter(focusDriven);
|
||||
if (!focus.length) continue;
|
||||
// Declarations as written (cssText), so a shorthand built on var() survives intact.
|
||||
const decls = splitTop(r.style.cssText, ';');
|
||||
const keep = decls.filter((d) => !isLook(d));
|
||||
if (keep.length === decls.length) continue;
|
||||
if (focus.length === sels.length) { r.style.cssText = keep.join('; '); continue; }
|
||||
// Shared with selectors that are not about focus ("a:hover, a:focus"): those keep the whole rule, and
|
||||
// the focus ones move to a copy without the look.
|
||||
r.selectorText = sels.filter((s) => !focusDriven(s)).join(', ');
|
||||
if (!keep.length) continue;
|
||||
try { owner.insertRule(focus.join(', ') + ' { ' + keep.join('; ') + ' }', i + 1); i++; seenRules.add(list[i]); } catch (_) {}
|
||||
}
|
||||
}
|
||||
function hideFocusLook() {
|
||||
const sheets = [...document.styleSheets, ...(document.adoptedStyleSheets || [])];
|
||||
for (const sh of sheets) { try { stripFocusLook(sh.cssRules, sh); } catch (_) {} } // another origin's: unreadable
|
||||
}
|
||||
window.addEventListener('DOMContentLoaded', hideFocusLook);
|
||||
window.addEventListener('load', hideFocusLook);
|
||||
window.addEventListener('pointerdown', hideFocusLook, true);
|
||||
document.addEventListener('focusin', hideFocusLook, true);
|
||||
|
||||
// ------------------------------------------------------------------ activity + five fingers
|
||||
let lastActivity = 0;
|
||||
function activity() {
|
||||
|
||||
Reference in New Issue
Block a user