app/enhance/theme.ts
149 lines and 17 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| enhanceThemeToggle | 7 to 22 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:8> | 8 to 21 | 5 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| toSearch | 39 to 42 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| openPalette | 44 to 104 | 6 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:73> | 73 to 76 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:74> | 74 to 74 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:75> | 75 to 75 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:83> | 83 to 86 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:84> | 84 to 84 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:91> | 91 to 94 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:95> | 95 to 98 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| isMac | 110 to 114 | 3 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| enhanceSearchTrigger | 116 to 152 | 8 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:136> | 136 to 140 | 5 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:143> | 143 to 151 | 4 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| apply | 154 to 173 | 4 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
| <anonymous:169> | 169 to 169 | 1 | No data: no coverage report in this map | No data: no coverage report in this map | No data: no mutants in this code |
Imports
It imports app/lib/enhance-url.mjs, app/lib/theme.ts. No module of this repo imports it.
Source
// No "apply the stored theme on load" step: the server already wrote the attribute, so a script
// that re-applied it could only agree, or race.
import { ENHANCE_URL_PREFIX } from "~/lib/enhance-url.mjs";
import { serializeThemeCookie, isWritableTheme, type WritableTheme } from "~/lib/theme";
function enhanceThemeToggle() {
document.addEventListener("submit", (event) => {
const form = event.target;
if (!(form instanceof HTMLFormElement)) return;
if (!form.matches("[data-theme-toggle]")) return;
const submitter = (event as SubmitEvent).submitter;
if (!(submitter instanceof HTMLButtonElement)) return;
const choice = submitter.value;
if (!isWritableTheme(choice)) return;
event.preventDefault();
apply(choice, form);
});
}
enhanceThemeToggle();
enhanceSearchTrigger();
/**
* A custom event rather than `import()`, because vite wraps every `import()` in a preload helper that
* would be a large share of this bundle. 'self' allows the inserted script with no nonce.
*/
const PALETTE_OPEN = "palette:open";
let requested = false;
/**
* The caller has already prevented the anchor's default, so a refusal or a load failure must fall
* back to `/search`, never to nothing, and says why in the console first.
*/
function toSearch(reason: unknown) {
console.error("palette:", reason);
location.assign("/search");
}
function openPalette() {
const trigger = document.querySelector<HTMLElement>("[data-palette]");
// The URL is hashed by the app build; the `?url` import in `search-trigger.tsx` is its one statement.
// Held to the loader's rule, because 'self' trusts whatever the loader inserts from this origin. The
// element resolves and normalizes the URL, so `..` is gone before the check reads it, and the
// origin is followed by the prefix's own `/`, so a longer host cannot match. Inline rather than a
// shared function: every public page loads this bundle, and its budget has no room for one.
const raw = trigger?.dataset.palette;
const script = document.createElement("script");
if (raw) script.src = raw;
if (!script.src.startsWith(location.origin + ENHANCE_URL_PREFIX)) {
toSearch(raw);
return;
}
if (!requested) {
requested = true;
trigger?.setAttribute("aria-busy", "true");
/*
* Stylesheets are awaited because the bundle calls `showModal` as soon as it runs. A failed
* stylesheet resolves: an unstyled dialog is better than none.
*/
const pending = [];
for (const href of (trigger?.dataset.paletteCss ?? "").split(",").filter(Boolean)) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
pending.push(
new Promise((resolve) => {
link.addEventListener("load", () => resolve(null));
link.addEventListener("error", () => resolve(null));
}),
);
document.head.appendChild(link);
}
script.type = "module";
pending.push(
new Promise((resolve, reject) => {
script.addEventListener("load", () => resolve(null));
script.addEventListener("error", reject);
}),
);
document.head.appendChild(script);
void Promise.all(pending).then(
() => {
trigger?.removeAttribute("aria-busy");
document.dispatchEvent(new Event(PALETTE_OPEN));
},
(reason) => {
trigger?.removeAttribute("aria-busy");
toSearch(reason);
},
);
return;
}
document.dispatchEvent(new Event(PALETTE_OPEN));
}
/**
* The hint is set only from script, so a reader whose script did not run is never told about a key
* that would do nothing. Platform detection is best-effort: the handler accepts meta or ctrl either way.
*/
function isMac(): boolean {
const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
const platform = nav.userAgentData?.platform ?? navigator.platform ?? navigator.userAgent;
return /mac/i.test(platform);
}
function enhanceSearchTrigger() {
const mac = isMac();
const chord = mac ? "Command-K" : "Control-K";
// Text before unhide, so the stale server placeholder is never the announced description.
for (const hint of document.querySelectorAll<HTMLElement>("[data-search-hint]")) {
hint.textContent = `Press ${chord} to search`;
hint.hidden = false;
}
for (const trigger of document.querySelectorAll<HTMLElement>("[data-search-trigger]")) {
/* The header tip names the keys as key caps (Capsomer's tooltip), only now that they do something. */
const tip = trigger.querySelector(".cap-site-header-tip");
if (tip && !tip.querySelector(".cap-keys")) {
const keys = document.createElement("span");
keys.className = "cap-keys";
for (const key of [mac ? "\u2318" : "Ctrl", "K"]) keys.appendChild(document.createElement("kbd")).textContent = key;
tip.appendChild(keys);
}
trigger.dataset.shortcutHint = "shown";
trigger.addEventListener("click", (event) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
event.preventDefault();
openPalette();
});
}
document.addEventListener("keydown", (event) => {
const meta = event.metaKey || event.ctrlKey;
if (meta && event.key.toLowerCase() === "k") {
event.preventDefault();
openPalette();
return;
}
// Deliberately no bare-slash shortcut: it collides with find-in-page.
});
}
function apply(choice: WritableTheme, form: HTMLFormElement) {
const root = document.documentElement;
root.setAttribute("data-theme", choice);
document.cookie = serializeThemeCookie(choice);
/*
* Activating a button hides it via the cascade, and `display: none` drops focus to `<body>`, so move
* focus to its twin, only when the hidden one held it. `preventScroll` because Chrome scrolls a
* focused sticky-header descendant toward its un-stuck position.
*/
const active = document.activeElement;
if (active instanceof HTMLElement && form.contains(active) && active.offsetParent === null) {
const shown = [...form.querySelectorAll<HTMLElement>("button[value]")].find(
(button) => button.offsetParent !== null,
);
shown?.focus({ preventScroll: true });
}
}