@tenphi/renderer 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import "./navigation-router.js";
@@ -0,0 +1,41 @@
1
+ //#region src/client/client-navigation.ts
2
+ let pageLoadNotified = false;
3
+ document.addEventListener("astro:page-load", () => {
4
+ pageLoadNotified = true;
5
+ }, { once: true });
6
+ window.addEventListener("load", () => {
7
+ window.setTimeout(() => {
8
+ if (!pageLoadNotified) document.dispatchEvent(new Event("astro:page-load"));
9
+ }, 0);
10
+ }, { once: true });
11
+ let router;
12
+ function loadRouter() {
13
+ return router ??= import("./navigation-router.js").catch((error) => {
14
+ router = void 0;
15
+ throw error;
16
+ });
17
+ }
18
+ function navigationLink(target) {
19
+ const link = target instanceof Element ? target.closest("a[href]") : null;
20
+ if (!(link instanceof HTMLAnchorElement) || link.hasAttribute("download") || link.hasAttribute("data-astro-reload") || link.target && link.target !== "_self") return;
21
+ const url = new URL(link.href);
22
+ if (url.origin !== location.origin || !router && url.pathname === location.pathname && url.search === location.search && url.hash) return;
23
+ return link;
24
+ }
25
+ for (const name of ["pointerover", "focusin"]) document.addEventListener(name, (event) => {
26
+ if (navigationLink(event.composedPath()[0])) loadRouter().catch(() => {});
27
+ });
28
+ document.addEventListener("click", (event) => {
29
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
30
+ const link = navigationLink(event.composedPath()[0]);
31
+ if (!link) return;
32
+ event.preventDefault();
33
+ loadRouter().then(({ navigate }) => navigate(link.href, {
34
+ sourceElement: link,
35
+ history: link.dataset.astroHistory === "replace" ? "replace" : "auto"
36
+ })).catch(() => location.assign(link.href));
37
+ });
38
+ //#endregion
39
+ export {};
40
+
41
+ //# sourceMappingURL=client-navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-navigation.js","names":[],"sources":["../../src/client/client-navigation.ts"],"sourcesContent":["// Use Astro's public router without ClientRouter's vendor stylesheet. Cookbook\n// owns link interception and extracted accessibility styles; Astro owns fetch,\n// stylesheet readiness, script execution, history, and route announcements.\n// Prefetch expects an initial page-load event before later route swaps. When\n// the router is still lazy, supply it after all native load listeners finish.\nlet pageLoadNotified = false;\ndocument.addEventListener(\n \"astro:page-load\",\n () => {\n pageLoadNotified = true;\n },\n { once: true },\n);\nwindow.addEventListener(\n \"load\",\n () => {\n window.setTimeout(() => {\n if (!pageLoadNotified)\n document.dispatchEvent(new Event(\"astro:page-load\"));\n }, 0);\n },\n { once: true },\n);\n\nlet router: Promise<typeof import(\"./navigation-router.js\")> | undefined;\nfunction loadRouter() {\n return (router ??= import(\"./navigation-router.js\").catch(\n (error: unknown) => {\n router = undefined;\n throw error;\n },\n ));\n}\n\nfunction navigationLink(target: EventTarget | undefined) {\n const link = target instanceof Element ? target.closest(\"a[href]\") : null;\n if (\n !(link instanceof HTMLAnchorElement) ||\n link.hasAttribute(\"download\") ||\n link.hasAttribute(\"data-astro-reload\") ||\n (link.target && link.target !== \"_self\")\n )\n return;\n const url = new URL(link.href);\n if (\n url.origin !== location.origin ||\n (!router &&\n url.pathname === location.pathname &&\n url.search === location.search &&\n url.hash)\n )\n return;\n // Heading links stay native until a page-navigation intent loads the router.\n return link;\n}\n\n// Download the router on navigation intent, rather than for every reader.\nfor (const name of [\"pointerover\", \"focusin\"]) {\n document.addEventListener(name, (event) => {\n if (navigationLink(event.composedPath()[0]))\n void loadRouter().catch(() => {\n // A later click can retry; native navigation remains the fallback.\n });\n });\n}\n\ndocument.addEventListener(\"click\", (event) => {\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n )\n return;\n const link = navigationLink(event.composedPath()[0]);\n if (!link) return;\n event.preventDefault();\n void loadRouter()\n .then(({ navigate }) =>\n navigate(link.href, {\n sourceElement: link,\n history: link.dataset.astroHistory === \"replace\" ? \"replace\" : \"auto\",\n }),\n )\n .catch(() => location.assign(link.href));\n});\n"],"mappings":";AAKA,IAAI,mBAAmB;AACvB,SAAS,iBACP,yBACM;CACJ,mBAAmB;AACrB,GACA,EAAE,MAAM,KAAK,CACf;AACA,OAAO,iBACL,cACM;CACJ,OAAO,iBAAiB;EACtB,IAAI,CAAC,kBACH,SAAS,cAAc,IAAI,MAAM,iBAAiB,CAAC;CACvD,GAAG,CAAC;AACN,GACA,EAAE,MAAM,KAAK,CACf;AAEA,IAAI;AACJ,SAAS,aAAa;CACpB,OAAQ,WAAW,OAAO,yBAAyB,CAAC,OACjD,UAAmB;EAClB,SAAS,KAAA;EACT,MAAM;CACR,CACF;AACF;AAEA,SAAS,eAAe,QAAiC;CACvD,MAAM,OAAO,kBAAkB,UAAU,OAAO,QAAQ,SAAS,IAAI;CACrE,IACE,EAAE,gBAAgB,sBAClB,KAAK,aAAa,UAAU,KAC5B,KAAK,aAAa,mBAAmB,KACpC,KAAK,UAAU,KAAK,WAAW,SAEhC;CACF,MAAM,MAAM,IAAI,IAAI,KAAK,IAAI;CAC7B,IACE,IAAI,WAAW,SAAS,UACvB,CAAC,UACA,IAAI,aAAa,SAAS,YAC1B,IAAI,WAAW,SAAS,UACxB,IAAI,MAEN;CAEF,OAAO;AACT;AAGA,KAAK,MAAM,QAAQ,CAAC,eAAe,SAAS,GAC1C,SAAS,iBAAiB,OAAO,UAAU;CACzC,IAAI,eAAe,MAAM,aAAa,CAAC,CAAC,EAAE,GACxC,WAAgB,CAAC,CAAC,YAAY,CAE9B,CAAC;AACL,CAAC;AAGH,SAAS,iBAAiB,UAAU,UAAU;CAC5C,IACE,MAAM,oBACN,MAAM,WAAW,KACjB,MAAM,WACN,MAAM,WACN,MAAM,YACN,MAAM,QAEN;CACF,MAAM,OAAO,eAAe,MAAM,aAAa,CAAC,CAAC,EAAE;CACnD,IAAI,CAAC,MAAM;CACX,MAAM,eAAe;CACrB,WAAgB,CAAC,CACd,MAAM,EAAE,eACP,SAAS,KAAK,MAAM;EAClB,eAAe;EACf,SAAS,KAAK,QAAQ,iBAAiB,YAAY,YAAY;CACjE,CAAC,CACH,CAAC,CACA,YAAY,SAAS,OAAO,KAAK,IAAI,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { navigate } from "astro:transitions/client";
2
+ export { navigate };
@@ -0,0 +1,36 @@
1
+ import { initializeTabs } from "./tabs.js";
2
+ import { navigate } from "astro:transitions/client";
3
+ //#region src/client/navigation-router.ts
4
+ document.addEventListener("astro:before-swap", (event) => {
5
+ const transition = event;
6
+ transition.viewTransition.skipTransition();
7
+ for (const name of ["data-theme", "data-contrast"]) {
8
+ const value = document.documentElement.getAttribute(name);
9
+ if (value === null) transition.newDocument.documentElement.removeAttribute(name);
10
+ else transition.newDocument.documentElement.setAttribute(name, value);
11
+ }
12
+ const sidebar = document.querySelector("#cookbook__sidebar");
13
+ const key = sidebar?.querySelector("cookbook-sidebar")?.dataset.storageKey;
14
+ const scroll = sidebar?.scrollTop ?? 0;
15
+ const swap = transition.swap;
16
+ transition.swap = () => {
17
+ swap();
18
+ const next = document.querySelector("#cookbook__sidebar");
19
+ if (next && key && next.querySelector("cookbook-sidebar")?.dataset.storageKey === key && matchMedia("(min-width: 50rem)").matches) next.scrollTop = scroll;
20
+ };
21
+ });
22
+ document.addEventListener("astro:after-swap", () => {
23
+ initializeTabs();
24
+ if (!location.hash) {
25
+ window.scrollTo({
26
+ top: 0,
27
+ left: 0,
28
+ behavior: "instant"
29
+ });
30
+ document.getElementById("main-content")?.focus({ preventScroll: true });
31
+ }
32
+ });
33
+ //#endregion
34
+ export { navigate };
35
+
36
+ //# sourceMappingURL=navigation-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation-router.js","names":[],"sources":["../../src/client/navigation-router.ts"],"sourcesContent":["import type { TransitionBeforeSwapEvent } from \"astro:transitions/client\";\nimport { initializeTabs } from \"./tabs.js\";\nexport { navigate } from \"astro:transitions/client\";\n\n// Load swap behavior with the router, before the first client navigation.\ndocument.addEventListener(\"astro:before-swap\", (event) => {\n const transition = event as TransitionBeforeSwapEvent;\n // Instant swaps avoid the browser's default full-page crossfade.\n transition.viewTransition.skipTransition();\n for (const name of [\"data-theme\", \"data-contrast\"]) {\n const value = document.documentElement.getAttribute(name);\n if (value === null)\n transition.newDocument.documentElement.removeAttribute(name);\n else transition.newDocument.documentElement.setAttribute(name, value);\n }\n const sidebar = document.querySelector<HTMLElement>(\"#cookbook__sidebar\");\n const key =\n sidebar?.querySelector<HTMLElement>(\"cookbook-sidebar\")?.dataset.storageKey;\n const scroll = sidebar?.scrollTop ?? 0;\n const swap = transition.swap;\n transition.swap = () => {\n swap();\n const next = document.querySelector<HTMLElement>(\"#cookbook__sidebar\");\n if (\n next &&\n key &&\n next.querySelector<HTMLElement>(\"cookbook-sidebar\")?.dataset\n .storageKey === key &&\n matchMedia(\"(min-width: 50rem)\").matches\n )\n next.scrollTop = scroll;\n };\n});\n\ndocument.addEventListener(\"astro:after-swap\", () => {\n // Enhance incoming panels synchronously, before a frame can show every\n // no-JavaScript panel and then jump when page-load scripts hide them.\n initializeTabs();\n // Keep fragment navigation intact, but reset article scroll on page changes,\n // including Back/Forward. Sidebar scroll is independent of the document.\n if (!location.hash) {\n window.scrollTo({ top: 0, left: 0, behavior: \"instant\" });\n document.getElementById(\"main-content\")?.focus({ preventScroll: true });\n }\n});\n"],"mappings":";;;AAKA,SAAS,iBAAiB,sBAAsB,UAAU;CACxD,MAAM,aAAa;CAEnB,WAAW,eAAe,eAAe;CACzC,KAAK,MAAM,QAAQ,CAAC,cAAc,eAAe,GAAG;EAClD,MAAM,QAAQ,SAAS,gBAAgB,aAAa,IAAI;EACxD,IAAI,UAAU,MACZ,WAAW,YAAY,gBAAgB,gBAAgB,IAAI;OACxD,WAAW,YAAY,gBAAgB,aAAa,MAAM,KAAK;CACtE;CACA,MAAM,UAAU,SAAS,cAA2B,oBAAoB;CACxE,MAAM,MACJ,SAAS,cAA2B,kBAAkB,CAAC,EAAE,QAAQ;CACnE,MAAM,SAAS,SAAS,aAAa;CACrC,MAAM,OAAO,WAAW;CACxB,WAAW,aAAa;EACtB,KAAK;EACL,MAAM,OAAO,SAAS,cAA2B,oBAAoB;EACrE,IACE,QACA,OACA,KAAK,cAA2B,kBAAkB,CAAC,EAAE,QAClD,eAAe,OAClB,WAAW,oBAAoB,CAAC,CAAC,SAEjC,KAAK,YAAY;CACrB;AACF,CAAC;AAED,SAAS,iBAAiB,0BAA0B;CAGlD,eAAe;CAGf,IAAI,CAAC,SAAS,MAAM;EAClB,OAAO,SAAS;GAAE,KAAK;GAAG,MAAM;GAAG,UAAU;EAAU,CAAC;EACxD,SAAS,eAAe,cAAc,CAAC,EAAE,MAAM,EAAE,eAAe,KAAK,CAAC;CACxE;AACF,CAAC"}
@@ -0,0 +1,9 @@
1
+ //#region src/client/prefetch-focus.d.ts
2
+ /** Keep Astro's hover strategy working for keyboard focus after body swaps. */
3
+ declare function initializePrefetchFocus(options: {
4
+ prefetchAll?: boolean;
5
+ defaultStrategy?: string;
6
+ }): void;
7
+ //#endregion
8
+ export { initializePrefetchFocus };
9
+ //# sourceMappingURL=prefetch-focus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefetch-focus.d.ts","names":[],"sources":["../../src/client/prefetch-focus.ts"],"mappings":";;iBAGgB,wBAAwB;EACtC;EACA"}
@@ -0,0 +1,26 @@
1
+ import { prefetch } from "astro:prefetch";
2
+ //#region src/client/prefetch-focus.ts
3
+ /** Keep Astro's hover strategy working for keyboard focus after body swaps. */
4
+ function initializePrefetchFocus(options) {
5
+ let swapped = false;
6
+ document.addEventListener("astro:after-swap", () => {
7
+ swapped = true;
8
+ }, { once: true });
9
+ let timeout;
10
+ document.addEventListener("focusin", (event) => {
11
+ if (!swapped) return;
12
+ const link = event.target instanceof Element ? event.target.closest("a") : null;
13
+ if (!(link instanceof HTMLAnchorElement)) return;
14
+ const strategy = link.dataset.astroPrefetch;
15
+ if (!(strategy === "hover" || (strategy === "" || strategy === void 0 && options.prefetchAll) && (options.defaultStrategy ?? "hover") === "hover")) return;
16
+ window.clearTimeout(timeout);
17
+ timeout = window.setTimeout(() => {
18
+ if (link.isConnected) prefetch(link.href);
19
+ }, 80);
20
+ });
21
+ document.addEventListener("focusout", () => window.clearTimeout(timeout));
22
+ }
23
+ //#endregion
24
+ export { initializePrefetchFocus };
25
+
26
+ //# sourceMappingURL=prefetch-focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefetch-focus.js","names":[],"sources":["../../src/client/prefetch-focus.ts"],"sourcesContent":["import { prefetch } from \"astro:prefetch\";\n\n/** Keep Astro's hover strategy working for keyboard focus after body swaps. */\nexport function initializePrefetchFocus(options: {\n prefetchAll?: boolean;\n defaultStrategy?: string;\n}): void {\n // Astro 7.3 binds focus listeners to the initial body. Its page-load refresh\n // handles mouse listeners on new anchors, but does not rebind keyboard focus.\n // Use the public prefetch API only after swaps; Astro still owns\n // requests, cache reuse, deduplication, and connection checks.\n let swapped = false;\n document.addEventListener(\n \"astro:after-swap\",\n () => {\n swapped = true;\n },\n { once: true },\n );\n let timeout: number | undefined;\n document.addEventListener(\"focusin\", (event) => {\n if (!swapped) return;\n const link =\n event.target instanceof Element ? event.target.closest(\"a\") : null;\n if (!(link instanceof HTMLAnchorElement)) return;\n const strategy = link.dataset.astroPrefetch;\n const hover =\n strategy === \"hover\" ||\n ((strategy === \"\" || (strategy === undefined && options.prefetchAll)) &&\n (options.defaultStrategy ?? \"hover\") === \"hover\");\n if (!hover) return;\n window.clearTimeout(timeout);\n timeout = window.setTimeout(() => {\n if (link.isConnected) prefetch(link.href);\n }, 80);\n });\n document.addEventListener(\"focusout\", () => window.clearTimeout(timeout));\n}\n"],"mappings":";;;AAGA,SAAgB,wBAAwB,SAG/B;CAKP,IAAI,UAAU;CACd,SAAS,iBACP,0BACM;EACJ,UAAU;CACZ,GACA,EAAE,MAAM,KAAK,CACf;CACA,IAAI;CACJ,SAAS,iBAAiB,YAAY,UAAU;EAC9C,IAAI,CAAC,SAAS;EACd,MAAM,OACJ,MAAM,kBAAkB,UAAU,MAAM,OAAO,QAAQ,GAAG,IAAI;EAChE,IAAI,EAAE,gBAAgB,oBAAoB;EAC1C,MAAM,WAAW,KAAK,QAAQ;EAK9B,IAAI,EAHF,aAAa,YACX,aAAa,MAAO,aAAa,KAAA,KAAa,QAAQ,iBACrD,QAAQ,mBAAmB,aAAa,UACjC;EACZ,OAAO,aAAa,OAAO;EAC3B,UAAU,OAAO,iBAAiB;GAChC,IAAI,KAAK,aAAa,SAAS,KAAK,IAAI;EAC1C,GAAG,EAAE;CACP,CAAC;CACD,SAAS,iBAAiB,kBAAkB,OAAO,aAAa,OAAO,CAAC;AAC1E"}
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","names":[],"sources":["../../src/client/search.ts"],"mappings":";KAAK;EAAiB;EAAa,cAAc;;KAC5C,kBAAkB;EACrB,iBAAiB,QAAQ,iBAAiB;;;iBAI5B,iBACd,SAAS,aACT,SAAS,iBACT,sBACA"}
1
+ {"version":3,"file":"search.d.ts","names":[],"sources":["../../src/client/search.ts"],"mappings":";KAAK;EAAiB;EAAa,cAAc;;KAE5C,kBAAkB;EACrB,iBAAiB,QAAQ,iBAAiB;;;iBAI5B,iBACd,SAAS,aACT,SAAS,iBACT,sBACA"}
@@ -3,6 +3,9 @@
3
3
  function initializeSearch(element, options, development, base) {
4
4
  if (element.dataset.initialized) return;
5
5
  element.dataset.initialized = "true";
6
+ const listeners = new AbortController();
7
+ const persistenceKey = element.dataset.searchKey;
8
+ if (persistenceKey) element.setAttribute("data-astro-transition-persist", persistenceKey);
6
9
  const open = element.querySelector("[data-open-modal]");
7
10
  const close = element.querySelector("[data-close-modal]");
8
11
  const dialog = element.querySelector("dialog");
@@ -118,13 +121,24 @@ function initializeSearch(element, options, development, base) {
118
121
  document.body.removeAttribute("data-search-modal-open");
119
122
  returnFocus?.focus();
120
123
  });
124
+ document.addEventListener("astro:before-swap", (event) => {
125
+ generation += 1;
126
+ returnFocus = null;
127
+ if (dialog.open) dialog.close();
128
+ dialog.removeAttribute("data-open");
129
+ document.body.removeAttribute("data-search-modal-open");
130
+ const next = event.newDocument;
131
+ const nextRoot = persistenceKey ? [...next.querySelectorAll("site-search")].find((root) => root.dataset.searchKey === persistenceKey) : void 0;
132
+ if (nextRoot) nextRoot.setAttribute("data-astro-transition-persist", persistenceKey);
133
+ else listeners.abort();
134
+ }, { signal: listeners.signal });
121
135
  window.addEventListener("keydown", (event) => {
122
136
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
123
137
  event.preventDefault();
124
138
  if (dialog.open && !closing) hide();
125
139
  else show();
126
140
  }
127
- });
141
+ }, { signal: listeners.signal });
128
142
  }
129
143
  //#endregion
130
144
  export { initializeSearch };
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../src/client/search.ts"],"sourcesContent":["type SearchResult = { url: string; sub_results?: SearchResult[] };\ntype PagefindOptions = Record<string, unknown> & {\n processResult?: (result: SearchResult) => SearchResult | void;\n};\n\n/** Own the dialog lifecycle; Pagefind owns indexing, filtering and result rendering. */\nexport function initializeSearch(\n element: HTMLElement,\n options: PagefindOptions,\n development: boolean,\n base: string,\n): void {\n if (element.dataset.initialized) return;\n element.dataset.initialized = \"true\";\n const open = element.querySelector<HTMLButtonElement>(\"[data-open-modal]\")!;\n const close = element.querySelector<HTMLButtonElement>(\"[data-close-modal]\")!;\n const dialog = element.querySelector(\"dialog\")!;\n const frame = element.querySelector(\"[data-element=Frame]\")!;\n const status = element.querySelector<HTMLElement>(\"[data-search-status]\");\n const input = element.querySelector<HTMLInputElement>(\"[data-search-input]\");\n const clear = element.querySelector<HTMLButtonElement>(\"[data-clear-search]\");\n const shortcut = open.querySelector(\"kbd\")!;\n if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {\n shortcut.querySelector(\"kbd\")!.textContent = \"⌘\";\n open.setAttribute(\"aria-keyshortcuts\", \"Meta+K\");\n }\n delete shortcut.dataset.pending;\n let initialized: Promise<void> | undefined;\n let engineInput: HTMLInputElement | null = null;\n let returnFocus: HTMLElement | null = null;\n let closing = false;\n let generation = 0;\n const updateSearch = () => {\n if (clear) clear.hidden = !input?.value;\n if (engineInput) {\n engineInput.value = input?.value ?? \"\";\n engineInput.dispatchEvent(new Event(\"input\", { bubbles: true }));\n }\n };\n input?.addEventListener(\"input\", updateSearch);\n clear?.addEventListener(\"click\", () => {\n if (!input) return;\n input.value = \"\";\n updateSearch();\n input.focus();\n });\n const initialize = () => {\n if (development) return Promise.resolve();\n return (initialized ??= (async () => {\n // @ts-expect-error Pagefind publishes no declarations for its default UI.\n const { PagefindUI } = await import(\"@pagefind/default-ui\");\n const translations = JSON.parse(element.dataset.translations ?? \"{}\");\n const format = (url: string) =>\n element.hasAttribute(\"data-strip-trailing-slash\")\n ? url.replace(/(.)\\/(#.*)?$/, \"$1$2\")\n : url;\n new PagefindUI({\n ...options,\n element: element.querySelector(\"[data-search-results]\"),\n baseUrl: base,\n bundlePath: `${base.replace(/\\/$/, \"\")}/pagefind/`,\n showImages: false,\n showSubResults: true,\n translations,\n processResult(result: SearchResult) {\n result = options.processResult?.(result) ?? result;\n result.url = format(result.url);\n result.sub_results?.forEach((child) => {\n child.url = format(child.url);\n });\n return result;\n },\n });\n // Pagefind generates and controls these nodes; its UI API exposes only classes.\n engineInput = element.querySelector(\".pagefind-ui__search-input\");\n element.querySelector(\".pagefind-ui__form\")?.removeAttribute(\"role\");\n // Cookbook owns the field so focus stays within the opening tap on mobile.\n // Pagefind still owns query processing and its lazily rendered results.\n element\n .querySelectorAll<HTMLElement>(\n \".pagefind-ui__search-input, .pagefind-ui__search-clear\",\n )\n .forEach((control) => {\n control.hidden = true;\n control.tabIndex = -1;\n });\n updateSearch();\n })().catch((error: unknown) => {\n initialized = undefined;\n if (status) {\n status.hidden = false;\n // Reuse the localized Pagefind error, with a plain fallback.\n const translations = JSON.parse(element.dataset.translations ?? \"{}\");\n status.textContent =\n translations.error ?? \"Search could not load. Close and try again.\";\n }\n throw error;\n }));\n };\n const show = (trigger?: HTMLElement) => {\n if (dialog.open && !closing) return;\n generation += 1;\n closing = false;\n if (!dialog.open) {\n returnFocus =\n trigger ??\n (document.activeElement instanceof HTMLElement &&\n document.activeElement !== document.body\n ? document.activeElement\n : open);\n dialog.removeAttribute(\"data-open\");\n dialog.showModal();\n }\n // Do this synchronously, before the lazy import releases user activation.\n input?.focus({ preventScroll: true });\n // Establish the initial state after entering the top layer. The global\n // style renderer does not preserve @starting-style in server output.\n dialog.getBoundingClientRect();\n dialog.setAttribute(\"data-open\", \"\");\n document.body.setAttribute(\"data-search-modal-open\", \"\");\n if (!development && status) status.hidden = true;\n void initialize().catch(() => {\n /* Keep the close control usable when the search bundle fails. */\n });\n };\n const hide = async () => {\n if (!dialog.open || closing) return;\n closing = true;\n const current = ++generation;\n dialog.removeAttribute(\"data-open\");\n // Keep the modal in the top layer until its exit transition has finished.\n // This also works in browsers without discrete display/overlay transitions.\n await Promise.allSettled(\n dialog\n .getAnimations()\n .filter((animation) =>\n Number.isFinite(animation.effect?.getComputedTiming().endTime),\n )\n .map((animation) => animation.finished),\n );\n if (current === generation && dialog.open) dialog.close();\n };\n open.addEventListener(\"click\", () => {\n show(open);\n });\n open.disabled = false;\n close.addEventListener(\"click\", () => void hide());\n dialog.addEventListener(\"cancel\", (event) => {\n event.preventDefault();\n void hide();\n });\n dialog.addEventListener(\"click\", (event) => {\n if (!(event.target instanceof Element)) return;\n if (event.target.closest(\"a[href]\") || !frame.contains(event.target))\n void hide();\n });\n dialog.addEventListener(\"close\", () => {\n dialog.removeAttribute(\"data-open\");\n closing = false;\n document.body.removeAttribute(\"data-search-modal-open\");\n returnFocus?.focus();\n });\n window.addEventListener(\"keydown\", (event) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === \"k\") {\n event.preventDefault();\n if (dialog.open && !closing) void hide();\n else void show();\n }\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../src/client/search.ts"],"sourcesContent":["type SearchResult = { url: string; sub_results?: SearchResult[] };\nimport type { TransitionBeforeSwapEvent } from \"astro:transitions/client\";\ntype PagefindOptions = Record<string, unknown> & {\n processResult?: (result: SearchResult) => SearchResult | void;\n};\n\n/** Own the dialog lifecycle; Pagefind owns indexing, filtering and result rendering. */\nexport function initializeSearch(\n element: HTMLElement,\n options: PagefindOptions,\n development: boolean,\n base: string,\n): void {\n if (element.dataset.initialized) return;\n element.dataset.initialized = \"true\";\n const listeners = new AbortController();\n // Astro strips transition attributes from unhydrated framework roots. Apply\n // the public persistence hook here and to the incoming matching search root.\n const persistenceKey = element.dataset.searchKey;\n if (persistenceKey)\n element.setAttribute(\"data-astro-transition-persist\", persistenceKey);\n const open = element.querySelector<HTMLButtonElement>(\"[data-open-modal]\")!;\n const close = element.querySelector<HTMLButtonElement>(\"[data-close-modal]\")!;\n const dialog = element.querySelector(\"dialog\")!;\n const frame = element.querySelector(\"[data-element=Frame]\")!;\n const status = element.querySelector<HTMLElement>(\"[data-search-status]\");\n const input = element.querySelector<HTMLInputElement>(\"[data-search-input]\");\n const clear = element.querySelector<HTMLButtonElement>(\"[data-clear-search]\");\n const shortcut = open.querySelector(\"kbd\")!;\n if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {\n shortcut.querySelector(\"kbd\")!.textContent = \"⌘\";\n open.setAttribute(\"aria-keyshortcuts\", \"Meta+K\");\n }\n delete shortcut.dataset.pending;\n let initialized: Promise<void> | undefined;\n let engineInput: HTMLInputElement | null = null;\n let returnFocus: HTMLElement | null = null;\n let closing = false;\n let generation = 0;\n const updateSearch = () => {\n if (clear) clear.hidden = !input?.value;\n if (engineInput) {\n engineInput.value = input?.value ?? \"\";\n engineInput.dispatchEvent(new Event(\"input\", { bubbles: true }));\n }\n };\n input?.addEventListener(\"input\", updateSearch);\n clear?.addEventListener(\"click\", () => {\n if (!input) return;\n input.value = \"\";\n updateSearch();\n input.focus();\n });\n const initialize = () => {\n if (development) return Promise.resolve();\n return (initialized ??= (async () => {\n // @ts-expect-error Pagefind publishes no declarations for its default UI.\n const { PagefindUI } = await import(\"@pagefind/default-ui\");\n const translations = JSON.parse(element.dataset.translations ?? \"{}\");\n const format = (url: string) =>\n element.hasAttribute(\"data-strip-trailing-slash\")\n ? url.replace(/(.)\\/(#.*)?$/, \"$1$2\")\n : url;\n new PagefindUI({\n ...options,\n element: element.querySelector(\"[data-search-results]\"),\n baseUrl: base,\n bundlePath: `${base.replace(/\\/$/, \"\")}/pagefind/`,\n showImages: false,\n showSubResults: true,\n translations,\n processResult(result: SearchResult) {\n result = options.processResult?.(result) ?? result;\n result.url = format(result.url);\n result.sub_results?.forEach((child) => {\n child.url = format(child.url);\n });\n return result;\n },\n });\n // Pagefind generates and controls these nodes; its UI API exposes only classes.\n engineInput = element.querySelector(\".pagefind-ui__search-input\");\n element.querySelector(\".pagefind-ui__form\")?.removeAttribute(\"role\");\n // Cookbook owns the field so focus stays within the opening tap on mobile.\n // Pagefind still owns query processing and its lazily rendered results.\n element\n .querySelectorAll<HTMLElement>(\n \".pagefind-ui__search-input, .pagefind-ui__search-clear\",\n )\n .forEach((control) => {\n control.hidden = true;\n control.tabIndex = -1;\n });\n updateSearch();\n })().catch((error: unknown) => {\n initialized = undefined;\n if (status) {\n status.hidden = false;\n // Reuse the localized Pagefind error, with a plain fallback.\n const translations = JSON.parse(element.dataset.translations ?? \"{}\");\n status.textContent =\n translations.error ?? \"Search could not load. Close and try again.\";\n }\n throw error;\n }));\n };\n const show = (trigger?: HTMLElement) => {\n if (dialog.open && !closing) return;\n generation += 1;\n closing = false;\n if (!dialog.open) {\n returnFocus =\n trigger ??\n (document.activeElement instanceof HTMLElement &&\n document.activeElement !== document.body\n ? document.activeElement\n : open);\n dialog.removeAttribute(\"data-open\");\n dialog.showModal();\n }\n // Do this synchronously, before the lazy import releases user activation.\n input?.focus({ preventScroll: true });\n // Establish the initial state after entering the top layer. The global\n // style renderer does not preserve @starting-style in server output.\n dialog.getBoundingClientRect();\n dialog.setAttribute(\"data-open\", \"\");\n document.body.setAttribute(\"data-search-modal-open\", \"\");\n if (!development && status) status.hidden = true;\n void initialize().catch(() => {\n /* Keep the close control usable when the search bundle fails. */\n });\n };\n const hide = async () => {\n if (!dialog.open || closing) return;\n closing = true;\n const current = ++generation;\n dialog.removeAttribute(\"data-open\");\n // Keep the modal in the top layer until its exit transition has finished.\n // This also works in browsers without discrete display/overlay transitions.\n await Promise.allSettled(\n dialog\n .getAnimations()\n .filter((animation) =>\n Number.isFinite(animation.effect?.getComputedTiming().endTime),\n )\n .map((animation) => animation.finished),\n );\n if (current === generation && dialog.open) dialog.close();\n };\n open.addEventListener(\"click\", () => {\n show(open);\n });\n open.disabled = false;\n close.addEventListener(\"click\", () => void hide());\n dialog.addEventListener(\"cancel\", (event) => {\n event.preventDefault();\n void hide();\n });\n dialog.addEventListener(\"click\", (event) => {\n if (!(event.target instanceof Element)) return;\n if (event.target.closest(\"a[href]\") || !frame.contains(event.target))\n void hide();\n });\n dialog.addEventListener(\"close\", () => {\n dialog.removeAttribute(\"data-open\");\n closing = false;\n document.body.removeAttribute(\"data-search-modal-open\");\n returnFocus?.focus();\n });\n document.addEventListener(\n \"astro:before-swap\",\n (event) => {\n // Keep the query and lazy engine for matching localized search shells,\n // while dismissing the modal before the destination content is displayed.\n generation += 1;\n returnFocus = null;\n if (dialog.open) dialog.close();\n dialog.removeAttribute(\"data-open\");\n document.body.removeAttribute(\"data-search-modal-open\");\n const next = (event as TransitionBeforeSwapEvent).newDocument;\n const nextRoot = persistenceKey\n ? [...next.querySelectorAll<HTMLElement>(\"site-search\")].find(\n (root) => root.dataset.searchKey === persistenceKey,\n )\n : undefined;\n if (nextRoot)\n nextRoot.setAttribute(\"data-astro-transition-persist\", persistenceKey!);\n else listeners.abort();\n },\n { signal: listeners.signal },\n );\n window.addEventListener(\n \"keydown\",\n (event) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === \"k\") {\n event.preventDefault();\n if (dialog.open && !closing) void hide();\n else void show();\n }\n },\n { signal: listeners.signal },\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ ---
2
+ import bootstrap from "./appearance-bootstrap.js?raw";
3
+ ---
4
+
5
+ <!-- Apply stored appearance before the body can paint, including with a replaced Head. -->
6
+ <script is:inline set:html={bootstrap} />
@@ -183,6 +183,19 @@ export default function DocumentStyles() {
183
183
  whiteSpace: { "": null, "@own(sr-only)": "nowrap" },
184
184
  border: { "": null, "@own(sr-only)": "0" },
185
185
  },
186
+ RouteAnnouncer: {
187
+ // Astro's public router creates this class; its generated markup has
188
+ // no owned data-element hook. Keep the bridge scoped to its announcer.
189
+ $: ".astro-route-announcer",
190
+ position: "absolute",
191
+ inset: "0 top, 0 left",
192
+ "$visually-hidden-size": "1px",
193
+ inlineSize: "$visually-hidden-size",
194
+ blockSize: "$visually-hidden-size",
195
+ overflow: "hidden",
196
+ clip: "rect(0, 0, 0, 0)",
197
+ whiteSpace: "nowrap",
198
+ },
186
199
  Strong: { $: ":where(strong), :where(b)", preset: "strong" },
187
200
  Link: { $: "a", color: "#accent-text" },
188
201
  NarrowBlock: {
@@ -59,7 +59,16 @@ const id = `select-${crypto.randomUUID()}`;
59
59
  <script>
60
60
  import { initializePopoverMotion } from "../client/popover-motion.js";
61
61
 
62
- document
63
- .querySelectorAll<HTMLElement>("[data-select-popover-panel]")
64
- .forEach((panel) => initializePopoverMotion(panel));
62
+ const initialized = new WeakSet<HTMLElement>();
63
+ function initialize(): void {
64
+ document
65
+ .querySelectorAll<HTMLElement>("[data-select-popover-panel]")
66
+ .forEach((panel) => {
67
+ if (initialized.has(panel)) return;
68
+ initialized.add(panel);
69
+ initializePopoverMotion(panel);
70
+ });
71
+ }
72
+ initialize();
73
+ document.addEventListener("astro:page-load", initialize);
65
74
  </script>
@@ -0,0 +1,21 @@
1
+ // Kept dependency-free for the page shell's synchronous, pre-paint script.
2
+ (() => {
3
+ const root = globalThis.document.documentElement;
4
+ try {
5
+ const scheme = globalThis.localStorage.getItem("cookbook-theme");
6
+ if (scheme === "light" || scheme === "dark") root.dataset.theme = scheme;
7
+ else delete root.dataset.theme;
8
+ } catch {
9
+ // Without storage, the document's system appearance rules remain active.
10
+ }
11
+ try {
12
+ const appearance = JSON.parse(
13
+ globalThis.localStorage.getItem("cookbook-appearance") ?? "{}",
14
+ );
15
+ if (appearance?.contrast === "normal" || appearance?.contrast === "more")
16
+ root.dataset.contrast = appearance.contrast;
17
+ else delete root.dataset.contrast;
18
+ } catch {
19
+ // Malformed or unavailable storage must not prevent the first paint.
20
+ }
21
+ })();
package/dist/index.js CHANGED
@@ -1273,9 +1273,18 @@ function configuredCookbook(options) {
1273
1273
  srcDir: context.config.srcDir
1274
1274
  };
1275
1275
  let markdownRenderer;
1276
+ const prefetch = context.config.prefetch ?? {
1277
+ prefetchAll: true,
1278
+ defaultStrategy: "hover"
1279
+ };
1280
+ if (prefetch !== false) {
1281
+ const focusScript = fileURLToPath(new URL("./client/prefetch-focus.js", import.meta.url));
1282
+ context.injectScript("page", `import { initializePrefetchFocus } from ${JSON.stringify(focusScript)}; initializePrefetchFocus(${JSON.stringify(typeof prefetch === "object" ? prefetch : {})});`);
1283
+ }
1276
1284
  context.updateConfig({
1277
1285
  ...configuredSite && !context.config.site ? { site: configuredSite } : {},
1278
1286
  base,
1287
+ prefetch,
1279
1288
  markdown: {
1280
1289
  syntaxHighlight: "shiki",
1281
1290
  shikiConfig: cookbookShikiConfig(context.config.markdown.shikiConfig)