@tenphi/renderer 0.24.0 → 0.25.1

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.
@@ -1,4 +1,13 @@
1
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 });
2
11
  let router;
3
12
  function loadRouter() {
4
13
  return router ??= import("./navigation-router.js").catch((error) => {
@@ -1 +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.\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":";AAGA,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"}
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,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"}
@@ -43,7 +43,12 @@ function initializeSearch(element, options, development, base) {
43
43
  return initialized ??= (async () => {
44
44
  const { PagefindUI } = await import("@pagefind/default-ui");
45
45
  const translations = JSON.parse(element.dataset.translations ?? "{}");
46
- const format = (url) => element.hasAttribute("data-strip-trailing-slash") ? url.replace(/(.)\/(#.*)?$/, "$1$2") : url;
46
+ const format = (url) => {
47
+ const trailingSlash = element.dataset.trailingSlash;
48
+ if (trailingSlash === "always") return url;
49
+ const page = url.replace(/\.html(?=#|$)/, "");
50
+ return trailingSlash === "never" ? page.replace(/(.)\/(#.*)?$/, "$1$2") : page;
51
+ };
47
52
  new PagefindUI({
48
53
  ...options,
49
54
  element: element.querySelector("[data-search-results]"),
@@ -1 +1 @@
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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"}
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 // Pagefind names pages by their files: x/ for x/index.html, x.html otherwise.\n const format = (url: string) => {\n const trailingSlash = element.dataset.trailingSlash;\n if (trailingSlash === \"always\") return url;\n const page = url.replace(/\\.html(?=#|$)/, \"\");\n return trailingSlash === \"never\"\n ? page.replace(/(.)\\/(#.*)?$/, \"$1$2\")\n : page;\n };\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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"}
@@ -22,6 +22,12 @@ export function MarkdownTableStyles() {
22
22
  $: "th, td",
23
23
  padding: "($gap * 1.5) ($gap * 2)",
24
24
  verticalAlign: "top",
25
+ textAlign: {
26
+ "": null,
27
+ "@own(align=left)": "left",
28
+ "@own(align=center)": "center",
29
+ "@own(align=right)": "right",
30
+ },
25
31
  // Change only color; border widths and styles belong to other rules or theme overrides.
26
32
  // eslint-disable-next-line tasty/prefer-shorthand-property
27
33
  borderColor: "#border",
@@ -36,7 +42,7 @@ export function MarkdownTableStyles() {
36
42
  $: "th",
37
43
  color: "#text",
38
44
  fill: "#surface-2",
39
- textAlign: "start",
45
+ textAlign: { "": "start", "@own(align)": null },
40
46
  radius: {
41
47
  "@own(:is(thead:first-child tr:first-child >) & :first-child & !:last-child)":
42
48
  "($card-radius - $border-width) top-left",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin-contract.ts","../src/integration.ts"],"mappings":";;;;;KAEY;EAEN,WAAW,iBAAiB,QAAQ;;EAGpC,MAAM,iBAAiB;;;;UCmGZ;EACf,SAAS;EACT;EACA;;EAEA,oBAAoB;;iBAGE,SACtB,UAAS,kBACR"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin-contract.ts","../src/integration.ts"],"mappings":";;;;;KAEY;EAEN,WAAW,iBAAiB,QAAQ;;EAGpC,MAAM,iBAAiB;;;;UCyGZ;EACf,SAAS;EACT;EACA;;EAEA,oBAAoB;;iBAGE,SACtB,UAAS,kBACR"}
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { createRequire } from "node:module";
10
10
  import { cp, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
11
11
  import { dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
12
12
  import sharp from "sharp";
13
- import { DOCS_FRONTMATTER_KEYS, assertValidDocs, createDocsGraph, renderAgentMarkdown, resolveColorTheme, resolveDocsProject } from "@tenphi/docs";
13
+ import { DOCS_FRONTMATTER_KEYS, assertValidDocs, createDocsGraph, renderAgentMarkdown, resolveColorTheme, resolveDocsProject, routePath, routeUrl } from "@tenphi/docs";
14
14
  import { createHash, randomUUID } from "node:crypto";
15
15
  import { fromHtml } from "hast-util-from-html";
16
16
  import { existsSync } from "node:fs";
@@ -304,7 +304,7 @@ const alerts = {
304
304
  };
305
305
  /** Keep GitHub alert syntax useful in repository Markdown and MDX. */
306
306
  function rehypeAlerts() {
307
- return (tree) => visit(tree);
307
+ return (tree) => visit$1(tree);
308
308
  }
309
309
  const satteriAlerts = {
310
310
  name: "cookbook:alerts",
@@ -316,12 +316,12 @@ const satteriAlerts = {
316
316
  }
317
317
  }
318
318
  };
319
- function visit(parent) {
319
+ function visit$1(parent) {
320
320
  if (!parent.children) return;
321
321
  for (const [index, child] of parent.children.entries()) {
322
322
  const replacement = alert(child);
323
323
  if (replacement) parent.children[index] = replacement;
324
- else visit(child);
324
+ else visit$1(child);
325
325
  }
326
326
  }
327
327
  function alert(blockquote) {
@@ -367,6 +367,53 @@ function alert(blockquote) {
367
367
  };
368
368
  }
369
369
  //#endregion
370
+ //#region src/markdown/rehype-table-alignment.ts
371
+ const alignments = /* @__PURE__ */ new Set([
372
+ "left",
373
+ "center",
374
+ "right"
375
+ ]);
376
+ /**
377
+ * Move GFM column alignment into a `data-align` modifier styled by
378
+ * `MarkdownTable`. Unified emits the obsolete `align` attribute and Sätteri an
379
+ * inline `text-align` style, which the Tasty-only output check rejects.
380
+ */
381
+ function rehypeTableAlignment() {
382
+ return (tree) => visit(tree);
383
+ }
384
+ /** Sätteri adapter for Astro's default Markdown processor. */
385
+ const satteriTableAlignment = {
386
+ name: "cookbook:table-alignment",
387
+ element: {
388
+ filter: ["th", "td"],
389
+ visit(node, context) {
390
+ const replacement = alignedCell(node);
391
+ if (replacement) context.replaceNode(node, replacement);
392
+ }
393
+ }
394
+ };
395
+ function visit(parent) {
396
+ if (!parent.children) return;
397
+ for (const [index, child] of parent.children.entries()) {
398
+ const replacement = alignedCell(child);
399
+ if (replacement) parent.children[index] = replacement;
400
+ else visit(child);
401
+ }
402
+ }
403
+ function alignedCell(cell) {
404
+ if (cell.type !== "element" || cell.tagName !== "th" && cell.tagName !== "td") return;
405
+ const { align, style, ...properties } = cell.properties ?? {};
406
+ const alignment = (typeof align === "string" ? align : typeof style === "string" ? /^\s*text-align:\s*([a-z]+)\s*;?\s*$/i.exec(style)?.[1] : void 0)?.toLowerCase();
407
+ if (!alignment || !alignments.has(alignment)) return;
408
+ return {
409
+ ...cell,
410
+ properties: {
411
+ ...properties,
412
+ dataAlign: alignment
413
+ }
414
+ };
415
+ }
416
+ //#endregion
370
417
  //#region src/markdown/rehype-table-scroll.ts
371
418
  const containerElement = "Scroll";
372
419
  /** Wrap Markdown tables in a dedicated horizontal scroll container. */
@@ -1002,9 +1049,6 @@ function outputPathForPublicAsset(publicPath, base) {
1002
1049
  }
1003
1050
  //#endregion
1004
1051
  //#region src/agent-discovery.ts
1005
- function sitePath(base, route) {
1006
- return `${base === "/" ? "" : `/${base.replace(/^\/+|\/+$/g, "")}`}${route === "/" ? "/" : `${route}/`}`;
1007
- }
1008
1052
  function pageUrl(site, path) {
1009
1053
  return site ? new URL(path, site).href : path;
1010
1054
  }
@@ -1022,7 +1066,7 @@ function renderLlmsTxt(graph) {
1022
1066
  const discoverable = new Set(graph.routes.filter((route) => route.discoverable !== false && route.sitemap !== false).map((route) => route.route));
1023
1067
  for (const entry of graph.entries) {
1024
1068
  if (!discoverable.has(entry.route)) continue;
1025
- const url = pageUrl(site.url, sitePath(build.base, entry.route));
1069
+ const url = routeUrl(entry.route, build.base, site.url, build.trailingSlash);
1026
1070
  lines.push(`- [${markdownLabel(entry.title)}](${url})${entry.description ? `: ${singleLine(entry.description)}` : ""}${site.seo?.copyPage === false ? "" : ` ([Markdown](${pageUrl(site.url, agentPagePath(entry.route, build.base))}))`}`);
1027
1071
  }
1028
1072
  return `${lines.join("\n")}\n`;
@@ -1049,7 +1093,7 @@ async function writeAgentDiscovery(output, graph) {
1049
1093
  if (markdown) await writeFile(join(output, outputPathForPublicAsset(markdown, graph.config.build.base)), renderAgentMarkdown(entry, graph.config));
1050
1094
  pages.push({
1051
1095
  route: entry.route,
1052
- url: pageUrl(graph.config.site.url, sitePath(graph.config.build.base, entry.route)),
1096
+ url: routeUrl(entry.route, graph.config.build.base, graph.config.site.url, graph.config.build.trailingSlash),
1053
1097
  canonical: route.canonical,
1054
1098
  index: route.indexable !== false,
1055
1099
  discoverable: route.discoverable !== false,
@@ -1160,6 +1204,7 @@ function configuredCookbook(options) {
1160
1204
  let projectRoot = options.root;
1161
1205
  let graphConfig = options.config;
1162
1206
  let graphBase = "/";
1207
+ let graphTrailingSlash = "always";
1163
1208
  let graph;
1164
1209
  let siteIconBase = "/";
1165
1210
  let siteIcons;
@@ -1171,7 +1216,8 @@ function configuredCookbook(options) {
1171
1216
  const candidate = await createDocsGraph({
1172
1217
  ...projectRoot ? { root: projectRoot } : {},
1173
1218
  ...graphConfig ? { config: graphConfig } : {},
1174
- base: graphBase
1219
+ base: graphBase,
1220
+ trailingSlash: graphTrailingSlash
1175
1221
  });
1176
1222
  assertValidDocs(candidate);
1177
1223
  await validatePluginFrontmatter(candidate.entries, options.frontmatterSchema);
@@ -1236,6 +1282,7 @@ function configuredCookbook(options) {
1236
1282
  };
1237
1283
  siteIconBase = base;
1238
1284
  graphBase = base;
1285
+ graphTrailingSlash = context.config.trailingSlash !== "ignore" ? context.config.trailingSlash : context.config.build.format === "directory" ? "always" : "index";
1239
1286
  context.config.integrations.push(sitemap({
1240
1287
  serialize: (item) => {
1241
1288
  if (!graph?.config.locales) return item;
@@ -1244,7 +1291,7 @@ function configuredCookbook(options) {
1244
1291
  ...item,
1245
1292
  links: localeAlternates(route, graph.routes.filter((route) => route.sitemap !== false), graph.config).map(({ lang, route }) => ({
1246
1293
  lang,
1247
- url: new URL(`${graphBase.replace(/\/$/, "")}${route === "/" ? "/" : `${route}/`}`, item.url).href
1294
+ url: new URL(routePath(route, graphBase, graphTrailingSlash), item.url).href
1248
1295
  }))
1249
1296
  };
1250
1297
  },
@@ -1273,9 +1320,18 @@ function configuredCookbook(options) {
1273
1320
  srcDir: context.config.srcDir
1274
1321
  };
1275
1322
  let markdownRenderer;
1323
+ const prefetch = context.config.prefetch ?? {
1324
+ prefetchAll: true,
1325
+ defaultStrategy: "hover"
1326
+ };
1327
+ if (prefetch !== false) {
1328
+ const focusScript = fileURLToPath(new URL("./client/prefetch-focus.js", import.meta.url));
1329
+ context.injectScript("page", `import { initializePrefetchFocus } from ${JSON.stringify(focusScript)}; initializePrefetchFocus(${JSON.stringify(typeof prefetch === "object" ? prefetch : {})});`);
1330
+ }
1276
1331
  context.updateConfig({
1277
1332
  ...configuredSite && !context.config.site ? { site: configuredSite } : {},
1278
1333
  base,
1334
+ prefetch,
1279
1335
  markdown: {
1280
1336
  syntaxHighlight: "shiki",
1281
1337
  shikiConfig: cookbookShikiConfig(context.config.markdown.shikiConfig)
@@ -1364,6 +1420,7 @@ function configuredCookbook(options) {
1364
1420
  }
1365
1421
  } : documentedSite(loaded),
1366
1422
  base: loaded.config.build.base,
1423
+ trailingSlash: loaded.config.build.trailingSlash,
1367
1424
  search: loaded.config.search.enabled || typeof options.config?.components?.overrides?.Search === "string",
1368
1425
  logo: siteLogo?.logo,
1369
1426
  locales: loaded.config.locales,
@@ -1401,7 +1458,8 @@ function configuredCookbook(options) {
1401
1458
  graph = await createDocsGraph({
1402
1459
  root: projectRoot,
1403
1460
  ...graphConfig ? { config: graphConfig } : {},
1404
- base: graphBase
1461
+ base: graphBase,
1462
+ trailingSlash: graphTrailingSlash
1405
1463
  });
1406
1464
  assertValidDocs(graph);
1407
1465
  context.injectRoute({
@@ -1598,6 +1656,7 @@ function registerCookbookMarkdownPlugins(processor) {
1598
1656
  const plugins = Array.isArray(options.rehypePlugins) ? options.rehypePlugins : [];
1599
1657
  if (!plugins.includes(rehypeMermaid)) plugins.push(rehypeMermaid);
1600
1658
  if (!plugins.includes(rehypeAlerts)) plugins.push(rehypeAlerts);
1659
+ if (!plugins.includes(rehypeTableAlignment)) plugins.push(rehypeTableAlignment);
1601
1660
  if (!plugins.includes(rehypeTableScroll)) plugins.push(rehypeTableScroll);
1602
1661
  if (!plugins.includes(rehypePageAffordances)) plugins.push(rehypePageAffordances);
1603
1662
  options.rehypePlugins = plugins;
@@ -1606,6 +1665,7 @@ function registerCookbookMarkdownPlugins(processor) {
1606
1665
  const plugins = Array.isArray(options.hastPlugins) ? options.hastPlugins : [];
1607
1666
  if (!plugins.includes(satteriMermaid)) plugins.push(satteriMermaid);
1608
1667
  if (!plugins.includes(satteriAlerts)) plugins.push(satteriAlerts);
1668
+ if (!plugins.includes(satteriTableAlignment)) plugins.push(satteriTableAlignment);
1609
1669
  if (!plugins.includes(satteriTableScroll)) plugins.push(satteriTableScroll);
1610
1670
  if (!plugins.includes(satteriPageAffordances)) plugins.push(satteriPageAffordances);
1611
1671
  options.hastPlugins = plugins;