@tenphi/renderer 0.20.4 → 0.21.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.
Files changed (44) hide show
  1. package/README.md +2 -2
  2. package/dist/client/navigation.js +2 -6
  3. package/dist/client/navigation.js.map +1 -1
  4. package/dist/client/popover-motion.d.ts +6 -0
  5. package/dist/client/popover-motion.d.ts.map +1 -0
  6. package/dist/client/popover-motion.js +14 -0
  7. package/dist/client/popover-motion.js.map +1 -0
  8. package/dist/client/search.js +55 -14
  9. package/dist/client/search.js.map +1 -1
  10. package/dist/components/GlobalStyles.js +97 -48
  11. package/dist/components/HeaderLinks.astro +14 -3
  12. package/dist/components/Icon.astro +1 -1
  13. package/dist/components/LayoutComponents.js +31 -40
  14. package/dist/components/Preview.astro +1 -1
  15. package/dist/components/SelectPopover.astro +64 -0
  16. package/dist/components/TastyComponents.js +16 -2
  17. package/dist/components/VersionSwitcher.astro +11 -18
  18. package/dist/components/component-styles.test.ts +4 -20
  19. package/dist/components/component-styles.ts +2 -22
  20. package/dist/components/select-popover-styles.js +107 -0
  21. package/dist/eslint-plugin.js +2 -2
  22. package/dist/eslint-plugin.js.map +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -10
  25. package/dist/index.js.map +1 -1
  26. package/dist/localization.d.ts.map +1 -1
  27. package/dist/localization.js +19 -5
  28. package/dist/localization.js.map +1 -1
  29. package/dist/overrides/Head.astro +3 -0
  30. package/dist/overrides/Header.astro +8 -0
  31. package/dist/overrides/LanguageSelect.astro +29 -35
  32. package/dist/overrides/MobileMenuFooter.astro +1 -1
  33. package/dist/overrides/Search.astro +25 -1
  34. package/dist/overrides/ThemeSelect.astro +2 -0
  35. package/dist/routes/Page.astro +1 -0
  36. package/dist/styling.d.ts.map +1 -1
  37. package/dist/styling.js +1 -1
  38. package/dist/{tasty-states-BDKIbfw2.js → tasty-states-rTLFy26t.js} +5 -13
  39. package/dist/tasty-states-rTLFy26t.js.map +1 -0
  40. package/dist/ui.d.ts.map +1 -1
  41. package/dist/ui.js +2 -0
  42. package/dist/ui.js.map +1 -1
  43. package/package.json +2 -2
  44. package/dist/tasty-states-BDKIbfw2.js.map +0 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ collections remain independent.
10
10
 
11
11
  `@tenphi/renderer/styling` exports `tasty`, `useGlobalStyles`, `mergeStyles`,
12
12
  the `Styles` type, and the `defineComponent` and `resolveComponentStyles`
13
- helpers for merging `theme.styles` into custom components. The facade exposes
13
+ helpers for merging `theme.customStyles` into custom components. The facade exposes
14
14
  the same API through `@tenphi/cookbook/styling`.
15
15
 
16
16
  `@tenphi/renderer/eslint-plugin` exports the default Tasty ESLint plugin,
@@ -19,5 +19,5 @@ the same API through `@tenphi/cookbook/styling`.
19
19
  Use `extends: "@tenphi/renderer"` in `tasty.config.ts` to inherit this preset
20
20
  and list only your additional tokens, states, or presets.
21
21
  It matches `@tenphi/cookbook/eslint-plugin`; see
22
- [Linting custom styles](https://cookbook.tenphi.me/theme-and-components/#linting-custom-styles)
22
+ [Linting custom styles](https://cookbook.tenphi.me/custom-components/#linting-custom-styles)
23
23
  for ESLint and oxlint setup.
@@ -1,3 +1,4 @@
1
+ import { initializePopoverMotion } from "./popover-motion.js";
1
2
  //#region src/client/navigation.ts
2
3
  var CookbookSidebarPane = class extends HTMLElement {
3
4
  #listeners;
@@ -93,12 +94,7 @@ var CookbookHeaderLinks = class extends HTMLElement {
93
94
  const close = () => {
94
95
  if (panel?.matches(":popover-open")) panel.hidePopover();
95
96
  };
96
- panel?.addEventListener("toggle", () => {
97
- if (panel.matches(":popover-open")) {
98
- panel.getBoundingClientRect();
99
- panel.setAttribute("data-open", "");
100
- } else panel.removeAttribute("data-open");
101
- }, { signal });
97
+ if (panel) initializePopoverMotion(panel, signal);
102
98
  desktop.addEventListener("change", () => {
103
99
  if (desktop.matches) close();
104
100
  }, { signal });
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.js","names":[],"sources":["../../src/client/navigation.ts"],"sourcesContent":["export {};\n\nclass CookbookSidebarPane extends HTMLElement {\n #listeners?: AbortController;\n #inert = new Map<HTMLElement, boolean>();\n #trigger: HTMLButtonElement | undefined;\n\n connectedCallback() {\n this.#listeners?.abort();\n this.#listeners = new AbortController();\n const { signal } = this.#listeners;\n this.#trigger =\n document.querySelector<HTMLButtonElement>(\n '.td-menu-button[popovertarget=\"cookbook__sidebar\"]',\n ) ?? undefined;\n const desktop = matchMedia(\"(min-width: 50rem)\");\n const syncViewport = () => {\n if (desktop.matches) {\n if (this.matches(\":popover-open\")) this.hidePopover();\n this.#release();\n this.popover = null;\n this.removeAttribute(\"role\");\n this.removeAttribute(\"aria-modal\");\n } else {\n this.popover = \"auto\";\n this.setAttribute(\"role\", \"dialog\");\n this.setAttribute(\"aria-modal\", \"true\");\n }\n };\n syncViewport();\n desktop.addEventListener(\"change\", syncViewport, { signal });\n this.addEventListener(\n \"toggle\",\n () => {\n const open = !desktop.matches && this.matches(\":popover-open\");\n this.#trigger?.setAttribute(\"aria-expanded\", String(open));\n if (open) {\n // Establish the closed position after the popover enters the top\n // layer so its attribute change transitions from an actual layout.\n // Tasty's global SSR renderer does not preserve @starting-style.\n this.getBoundingClientRect();\n this.setAttribute(\"data-open\", \"\");\n for (const element of document.querySelectorAll<HTMLElement>(\n \".header, .main-frame, .cookbook-skip-link\",\n )) {\n this.#inert.set(element, element.inert);\n element.inert = true;\n }\n this.querySelector<HTMLElement>(\"[autofocus]\")?.focus({\n preventScroll: true,\n });\n } else {\n const restoreFocus =\n this.contains(document.activeElement) ||\n document.activeElement === document.body;\n this.#release();\n if (restoreFocus && !desktop.matches) this.#trigger?.focus();\n }\n },\n { signal },\n );\n this.addEventListener(\n \"keydown\",\n (event) => {\n if (desktop.matches || !this.matches(\":popover-open\")) return;\n const sections = this.querySelector<HTMLDetailsElement>(\n \".td-mobile-tabs details[open]\",\n );\n if (event.key === \"Escape\" && sections) {\n event.preventDefault();\n sections.open = false;\n sections.querySelector(\"summary\")?.focus();\n return;\n }\n if (event.key !== \"Tab\") return;\n const focusable = Array.from(\n this.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), select:not([disabled]), input:not([disabled]), summary, [tabindex=\"0\"]',\n ),\n ).filter(\n (element) =>\n element.tabIndex >= 0 && element.getClientRects().length > 0,\n );\n const first = focusable[0];\n const last = focusable.at(-1);\n if (!first || !last) {\n event.preventDefault();\n this.focus();\n return;\n }\n if (\n event.shiftKey &&\n (document.activeElement === first || document.activeElement === this)\n ) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n },\n { signal },\n );\n this.addEventListener(\n \"click\",\n (event) => {\n if (\n !event.defaultPrevented &&\n event.target instanceof Element &&\n event.target.closest(\"a[href]\") &&\n this.matches(\":popover-open\")\n )\n this.hidePopover();\n },\n { signal },\n );\n }\n\n #release() {\n this.removeAttribute(\"data-open\");\n for (const [element, inert] of this.#inert) element.inert = inert;\n this.#inert.clear();\n this.#trigger?.setAttribute(\"aria-expanded\", \"false\");\n }\n\n disconnectedCallback() {\n this.#listeners?.abort();\n this.#release();\n }\n}\n\nclass CookbookHeaderLinks extends HTMLElement {\n #listeners?: AbortController;\n connectedCallback() {\n this.#listeners?.abort();\n this.#listeners = new AbortController();\n const { signal } = this.#listeners;\n const panel = this.querySelector<HTMLElement>(\"[popover]\");\n const desktop = matchMedia(\"(min-width: 50rem)\");\n const close = () => {\n if (panel?.matches(\":popover-open\")) panel.hidePopover();\n };\n panel?.addEventListener(\n \"toggle\",\n () => {\n if (panel.matches(\":popover-open\")) {\n panel.getBoundingClientRect();\n panel.setAttribute(\"data-open\", \"\");\n } else {\n panel.removeAttribute(\"data-open\");\n }\n },\n { signal },\n );\n desktop.addEventListener(\n \"change\",\n () => {\n if (desktop.matches) close();\n },\n { signal },\n );\n panel?.addEventListener(\n \"click\",\n (event) => {\n if (event.target instanceof Element && event.target.closest(\"a[href]\"))\n close();\n },\n { signal },\n );\n }\n disconnectedCallback() {\n this.#listeners?.abort();\n }\n}\n\nif (!customElements.get(\"cookbook-sidebar-pane\"))\n customElements.define(\"cookbook-sidebar-pane\", CookbookSidebarPane);\nif (!customElements.get(\"cookbook-header-links\"))\n customElements.define(\"cookbook-header-links\", CookbookHeaderLinks);\n"],"mappings":"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"}
1
+ {"version":3,"file":"navigation.js","names":[],"sources":["../../src/client/navigation.ts"],"sourcesContent":["import { initializePopoverMotion } from \"./popover-motion.js\";\n\nclass CookbookSidebarPane extends HTMLElement {\n #listeners?: AbortController;\n #inert = new Map<HTMLElement, boolean>();\n #trigger: HTMLButtonElement | undefined;\n\n connectedCallback() {\n this.#listeners?.abort();\n this.#listeners = new AbortController();\n const { signal } = this.#listeners;\n this.#trigger =\n document.querySelector<HTMLButtonElement>(\n '.td-menu-button[popovertarget=\"cookbook__sidebar\"]',\n ) ?? undefined;\n const desktop = matchMedia(\"(min-width: 50rem)\");\n const syncViewport = () => {\n if (desktop.matches) {\n if (this.matches(\":popover-open\")) this.hidePopover();\n this.#release();\n this.popover = null;\n this.removeAttribute(\"role\");\n this.removeAttribute(\"aria-modal\");\n } else {\n this.popover = \"auto\";\n this.setAttribute(\"role\", \"dialog\");\n this.setAttribute(\"aria-modal\", \"true\");\n }\n };\n syncViewport();\n desktop.addEventListener(\"change\", syncViewport, { signal });\n this.addEventListener(\n \"toggle\",\n () => {\n const open = !desktop.matches && this.matches(\":popover-open\");\n this.#trigger?.setAttribute(\"aria-expanded\", String(open));\n if (open) {\n // Establish the closed position after the popover enters the top\n // layer so its attribute change transitions from an actual layout.\n // Tasty's global SSR renderer does not preserve @starting-style.\n this.getBoundingClientRect();\n this.setAttribute(\"data-open\", \"\");\n for (const element of document.querySelectorAll<HTMLElement>(\n \".header, .main-frame, .cookbook-skip-link\",\n )) {\n this.#inert.set(element, element.inert);\n element.inert = true;\n }\n this.querySelector<HTMLElement>(\"[autofocus]\")?.focus({\n preventScroll: true,\n });\n } else {\n const restoreFocus =\n this.contains(document.activeElement) ||\n document.activeElement === document.body;\n this.#release();\n if (restoreFocus && !desktop.matches) this.#trigger?.focus();\n }\n },\n { signal },\n );\n this.addEventListener(\n \"keydown\",\n (event) => {\n if (desktop.matches || !this.matches(\":popover-open\")) return;\n const sections = this.querySelector<HTMLDetailsElement>(\n \".td-mobile-tabs details[open]\",\n );\n if (event.key === \"Escape\" && sections) {\n event.preventDefault();\n sections.open = false;\n sections.querySelector(\"summary\")?.focus();\n return;\n }\n if (event.key !== \"Tab\") return;\n const focusable = Array.from(\n this.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), select:not([disabled]), input:not([disabled]), summary, [tabindex=\"0\"]',\n ),\n ).filter(\n (element) =>\n element.tabIndex >= 0 && element.getClientRects().length > 0,\n );\n const first = focusable[0];\n const last = focusable.at(-1);\n if (!first || !last) {\n event.preventDefault();\n this.focus();\n return;\n }\n if (\n event.shiftKey &&\n (document.activeElement === first || document.activeElement === this)\n ) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n },\n { signal },\n );\n this.addEventListener(\n \"click\",\n (event) => {\n if (\n !event.defaultPrevented &&\n event.target instanceof Element &&\n event.target.closest(\"a[href]\") &&\n this.matches(\":popover-open\")\n )\n this.hidePopover();\n },\n { signal },\n );\n }\n\n #release() {\n this.removeAttribute(\"data-open\");\n for (const [element, inert] of this.#inert) element.inert = inert;\n this.#inert.clear();\n this.#trigger?.setAttribute(\"aria-expanded\", \"false\");\n }\n\n disconnectedCallback() {\n this.#listeners?.abort();\n this.#release();\n }\n}\n\nclass CookbookHeaderLinks extends HTMLElement {\n #listeners?: AbortController;\n connectedCallback() {\n this.#listeners?.abort();\n this.#listeners = new AbortController();\n const { signal } = this.#listeners;\n const panel = this.querySelector<HTMLElement>(\"[popover]\");\n const desktop = matchMedia(\"(min-width: 50rem)\");\n const close = () => {\n if (panel?.matches(\":popover-open\")) panel.hidePopover();\n };\n if (panel) initializePopoverMotion(panel, signal);\n desktop.addEventListener(\n \"change\",\n () => {\n if (desktop.matches) close();\n },\n { signal },\n );\n panel?.addEventListener(\n \"click\",\n (event) => {\n if (event.target instanceof Element && event.target.closest(\"a[href]\"))\n close();\n },\n { signal },\n );\n }\n disconnectedCallback() {\n this.#listeners?.abort();\n }\n}\n\nif (!customElements.get(\"cookbook-sidebar-pane\"))\n customElements.define(\"cookbook-sidebar-pane\", CookbookSidebarPane);\nif (!customElements.get(\"cookbook-header-links\"))\n customElements.define(\"cookbook-header-links\", CookbookHeaderLinks);\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ //#region src/client/popover-motion.d.ts
2
+ /** Animate popovers after they have entered the top layer. */
3
+ declare function initializePopoverMotion(panel: HTMLElement, signal?: AbortSignal): void;
4
+ //#endregion
5
+ export { initializePopoverMotion };
6
+ //# sourceMappingURL=popover-motion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popover-motion.d.ts","names":[],"sources":["../../src/client/popover-motion.ts"],"mappings":";;iBACgB,wBACd,OAAO,aACP,SAAS"}
@@ -0,0 +1,14 @@
1
+ //#region src/client/popover-motion.ts
2
+ /** Animate popovers after they have entered the top layer. */
3
+ function initializePopoverMotion(panel, signal) {
4
+ panel.addEventListener("toggle", () => {
5
+ if (panel.matches(":popover-open")) {
6
+ panel.getBoundingClientRect();
7
+ panel.setAttribute("data-open", "");
8
+ } else panel.removeAttribute("data-open");
9
+ }, signal ? { signal } : void 0);
10
+ }
11
+ //#endregion
12
+ export { initializePopoverMotion };
13
+
14
+ //# sourceMappingURL=popover-motion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popover-motion.js","names":[],"sources":["../../src/client/popover-motion.ts"],"sourcesContent":["/** Animate popovers after they have entered the top layer. */\nexport function initializePopoverMotion(\n panel: HTMLElement,\n signal?: AbortSignal,\n): void {\n panel.addEventListener(\n \"toggle\",\n () => {\n if (panel.matches(\":popover-open\")) {\n panel.getBoundingClientRect();\n panel.setAttribute(\"data-open\", \"\");\n } else {\n panel.removeAttribute(\"data-open\");\n }\n },\n signal ? { signal } : undefined,\n );\n}\n"],"mappings":";;AACA,SAAgB,wBACd,OACA,QACM;CACN,MAAM,iBACJ,gBACM;EACJ,IAAI,MAAM,QAAQ,eAAe,GAAG;GAClC,MAAM,sBAAsB;GAC5B,MAAM,aAAa,aAAa,EAAE;EACpC,OACE,MAAM,gBAAgB,WAAW;CAErC,GACA,SAAS,EAAE,OAAO,IAAI,KAAA,CACxB;AACF"}
@@ -8,6 +8,8 @@ function initializeSearch(element, options, development, base) {
8
8
  const dialog = element.querySelector("dialog");
9
9
  const frame = element.querySelector(".dialog-frame");
10
10
  const status = element.querySelector("[data-search-status]");
11
+ const input = element.querySelector("[data-search-input]");
12
+ const clear = element.querySelector("[data-clear-search]");
11
13
  const shortcut = open.querySelector("kbd");
12
14
  if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {
13
15
  shortcut.querySelector("kbd").textContent = "⌘";
@@ -15,7 +17,24 @@ function initializeSearch(element, options, development, base) {
15
17
  }
16
18
  delete shortcut.dataset.pending;
17
19
  let initialized;
20
+ let engineInput = null;
18
21
  let returnFocus = null;
22
+ let closing = false;
23
+ let generation = 0;
24
+ const updateSearch = () => {
25
+ if (clear) clear.hidden = !input?.value;
26
+ if (engineInput) {
27
+ engineInput.value = input?.value ?? "";
28
+ engineInput.dispatchEvent(new Event("input", { bubbles: true }));
29
+ }
30
+ };
31
+ input?.addEventListener("input", updateSearch);
32
+ clear?.addEventListener("click", () => {
33
+ if (!input) return;
34
+ input.value = "";
35
+ updateSearch();
36
+ input.focus();
37
+ });
19
38
  const initialize = () => {
20
39
  if (development) return Promise.resolve();
21
40
  return initialized ??= (async () => {
@@ -24,7 +43,7 @@ function initializeSearch(element, options, development, base) {
24
43
  const format = (url) => element.hasAttribute("data-strip-trailing-slash") ? url.replace(/(.)\/(#.*)?$/, "$1$2") : url;
25
44
  new PagefindUI({
26
45
  ...options,
27
- element: "#cookbook__search",
46
+ element: element.querySelector("[data-search-results]"),
28
47
  baseUrl: base,
29
48
  bundlePath: `${base.replace(/\/$/, "")}/pagefind/`,
30
49
  showImages: false,
@@ -39,6 +58,13 @@ function initializeSearch(element, options, development, base) {
39
58
  return result;
40
59
  }
41
60
  });
61
+ engineInput = element.querySelector(".pagefind-ui__search-input");
62
+ element.querySelector(".pagefind-ui__form")?.removeAttribute("role");
63
+ element.querySelectorAll(".pagefind-ui__search-input, .pagefind-ui__search-clear").forEach((control) => {
64
+ control.hidden = true;
65
+ control.tabIndex = -1;
66
+ });
67
+ updateSearch();
42
68
  })().catch((error) => {
43
69
  initialized = void 0;
44
70
  if (status) {
@@ -49,38 +75,53 @@ function initializeSearch(element, options, development, base) {
49
75
  throw error;
50
76
  });
51
77
  };
52
- const show = async () => {
53
- if (dialog.open) return;
54
- returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : open;
55
- dialog.removeAttribute("data-open");
56
- dialog.showModal();
78
+ const show = (trigger) => {
79
+ if (dialog.open && !closing) return;
80
+ generation += 1;
81
+ closing = false;
82
+ if (!dialog.open) {
83
+ returnFocus = trigger ?? (document.activeElement instanceof HTMLElement && document.activeElement !== document.body ? document.activeElement : open);
84
+ dialog.removeAttribute("data-open");
85
+ dialog.showModal();
86
+ }
87
+ input?.focus({ preventScroll: true });
57
88
  dialog.getBoundingClientRect();
58
89
  dialog.setAttribute("data-open", "");
59
90
  document.body.setAttribute("data-search-modal-open", "");
60
91
  if (!development && status) status.hidden = true;
61
- try {
62
- await initialize();
63
- if (dialog.open) element.querySelector("input")?.focus();
64
- } catch {}
92
+ initialize().catch(() => {});
93
+ };
94
+ const hide = async () => {
95
+ if (!dialog.open || closing) return;
96
+ closing = true;
97
+ const current = ++generation;
98
+ dialog.removeAttribute("data-open");
99
+ await Promise.allSettled(dialog.getAnimations().filter((animation) => Number.isFinite(animation.effect?.getComputedTiming().endTime)).map((animation) => animation.finished));
100
+ if (current === generation && dialog.open) dialog.close();
65
101
  };
66
102
  open.addEventListener("click", () => {
67
- show();
103
+ show(open);
68
104
  });
69
105
  open.disabled = false;
70
- close.addEventListener("click", () => dialog.close());
106
+ close.addEventListener("click", () => void hide());
107
+ dialog.addEventListener("cancel", (event) => {
108
+ event.preventDefault();
109
+ hide();
110
+ });
71
111
  dialog.addEventListener("click", (event) => {
72
112
  if (!(event.target instanceof Element)) return;
73
- if (event.target.closest("a[href]") || !frame.contains(event.target)) dialog.close();
113
+ if (event.target.closest("a[href]") || !frame.contains(event.target)) hide();
74
114
  });
75
115
  dialog.addEventListener("close", () => {
76
116
  dialog.removeAttribute("data-open");
117
+ closing = false;
77
118
  document.body.removeAttribute("data-search-modal-open");
78
119
  returnFocus?.focus();
79
120
  });
80
121
  window.addEventListener("keydown", (event) => {
81
122
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
82
123
  event.preventDefault();
83
- if (dialog.open) dialog.close();
124
+ if (dialog.open && !closing) hide();
84
125
  else show();
85
126
  }
86
127
  });
@@ -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(\".dialog-frame\")!;\n const status = element.querySelector<HTMLElement>(\"[data-search-status]\");\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 returnFocus: HTMLElement | null = null;\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: \"#cookbook__search\",\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 })().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 = async () => {\n if (dialog.open) return;\n returnFocus =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : open;\n dialog.removeAttribute(\"data-open\");\n dialog.showModal();\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 try {\n await initialize();\n // Pagefind mounts synchronously. Only focus if the user has not closed it.\n if (dialog.open) element.querySelector(\"input\")?.focus();\n } catch {\n /* Keep the close control usable when the search bundle fails. */\n }\n };\n open.addEventListener(\"click\", () => {\n void show();\n });\n open.disabled = false;\n close.addEventListener(\"click\", () => dialog.close());\n dialog.addEventListener(\"click\", (event) => {\n if (!(event.target instanceof Element)) return;\n if (event.target.closest(\"a[href]\") || !frame.contains(event.target))\n dialog.close();\n });\n dialog.addEventListener(\"close\", () => {\n dialog.removeAttribute(\"data-open\");\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) dialog.close();\n else void show();\n }\n });\n}\n"],"mappings":";;AAMA,SAAgB,iBACd,SACA,SACA,aACA,MACM;CACN,IAAI,QAAQ,QAAQ,aAAa;CACjC,QAAQ,QAAQ,cAAc;CAC9B,MAAM,OAAO,QAAQ,cAAiC,mBAAmB;CACzE,MAAM,QAAQ,QAAQ,cAAiC,oBAAoB;CAC3E,MAAM,SAAS,QAAQ,cAAc,QAAQ;CAC7C,MAAM,QAAQ,QAAQ,cAAc,eAAe;CACnD,MAAM,SAAS,QAAQ,cAA2B,sBAAsB;CACxE,MAAM,WAAW,KAAK,cAAc,KAAK;CACzC,IAAI,0BAA0B,KAAK,UAAU,QAAQ,GAAG;EACtD,SAAS,cAAc,KAAK,CAAC,CAAE,cAAc;EAC7C,KAAK,aAAa,qBAAqB,QAAQ;CACjD;CACA,OAAO,SAAS,QAAQ;CACxB,IAAI;CACJ,IAAI,cAAkC;CACtC,MAAM,mBAAmB;EACvB,IAAI,aAAa,OAAO,QAAQ,QAAQ;EACxC,OAAQ,iBAAiB,YAAY;GAEnC,MAAM,EAAE,eAAe,MAAM,OAAO;GACpC,MAAM,eAAe,KAAK,MAAM,QAAQ,QAAQ,gBAAgB,IAAI;GACpE,MAAM,UAAU,QACd,QAAQ,aAAa,2BAA2B,IAC5C,IAAI,QAAQ,gBAAgB,MAAM,IAClC;GACN,IAAI,WAAW;IACb,GAAG;IACH,SAAS;IACT,SAAS;IACT,YAAY,GAAG,KAAK,QAAQ,OAAO,EAAE,EAAE;IACvC,YAAY;IACZ,gBAAgB;IAChB;IACA,cAAc,QAAsB;KAClC,SAAS,QAAQ,gBAAgB,MAAM,KAAK;KAC5C,OAAO,MAAM,OAAO,OAAO,GAAG;KAC9B,OAAO,aAAa,SAAS,UAAU;MACrC,MAAM,MAAM,OAAO,MAAM,GAAG;KAC9B,CAAC;KACD,OAAO;IACT;GACF,CAAC;EACH,EAAA,CAAG,CAAC,CAAC,OAAO,UAAmB;GAC7B,cAAc,KAAA;GACd,IAAI,QAAQ;IACV,OAAO,SAAS;IAEhB,MAAM,eAAe,KAAK,MAAM,QAAQ,QAAQ,gBAAgB,IAAI;IACpE,OAAO,cACL,aAAa,SAAS;GAC1B;GACA,MAAM;EACR,CAAC;CACH;CACA,MAAM,OAAO,YAAY;EACvB,IAAI,OAAO,MAAM;EACjB,cACE,SAAS,yBAAyB,cAC9B,SAAS,gBACT;EACN,OAAO,gBAAgB,WAAW;EAClC,OAAO,UAAU;EAGjB,OAAO,sBAAsB;EAC7B,OAAO,aAAa,aAAa,EAAE;EACnC,SAAS,KAAK,aAAa,0BAA0B,EAAE;EACvD,IAAI,CAAC,eAAe,QAAQ,OAAO,SAAS;EAC5C,IAAI;GACF,MAAM,WAAW;GAEjB,IAAI,OAAO,MAAM,QAAQ,cAAc,OAAO,CAAC,EAAE,MAAM;EACzD,QAAQ,CAER;CACF;CACA,KAAK,iBAAiB,eAAe;EACnC,KAAU;CACZ,CAAC;CACD,KAAK,WAAW;CAChB,MAAM,iBAAiB,eAAe,OAAO,MAAM,CAAC;CACpD,OAAO,iBAAiB,UAAU,UAAU;EAC1C,IAAI,EAAE,MAAM,kBAAkB,UAAU;EACxC,IAAI,MAAM,OAAO,QAAQ,SAAS,KAAK,CAAC,MAAM,SAAS,MAAM,MAAM,GACjE,OAAO,MAAM;CACjB,CAAC;CACD,OAAO,iBAAiB,eAAe;EACrC,OAAO,gBAAgB,WAAW;EAClC,SAAS,KAAK,gBAAgB,wBAAwB;EACtD,aAAa,MAAM;CACrB,CAAC;CACD,OAAO,iBAAiB,YAAY,UAAU;EAC5C,KAAK,MAAM,WAAW,MAAM,YAAY,MAAM,IAAI,YAAY,MAAM,KAAK;GACvE,MAAM,eAAe;GACrB,IAAI,OAAO,MAAM,OAAO,MAAM;QACzB,KAAU;EACjB;CACF,CAAC;AACH"}
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(\".dialog-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 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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"}
@@ -9,6 +9,7 @@ import closeIcon from "../icons/close.svg?raw";
9
9
  import copyIcon from "../icons/copy.svg?raw";
10
10
  import searchIcon from "../icons/search.svg?raw";
11
11
  import { resolveComponentStyles } from "./component-styles.js";
12
+ import { selectPopoverStyles } from "./select-popover-styles.js";
12
13
  import { svgIconUrl } from "./svg-icon.js";
13
14
  import { configureCookbookStates } from "./tasty-states.js";
14
15
  import { getDefaultFontUsage, getFontFaces } from "../theme/fonts.js";
@@ -362,18 +363,18 @@ export default function GlobalStyles() {
362
363
  },
363
364
  Backdrop: {
364
365
  $: "dialog::backdrop",
365
- fill: "#overlay",
366
- backdropFilter: "blur(0.25rem)",
366
+ fill: { "": "#overlay", "@mobile": "#clear" },
367
+ backdropFilter: { "": "blur(0.25rem)", "@mobile": "none" },
367
368
  opacity: "0",
368
369
  transition: {
369
370
  "": "none",
370
- "!@reduced-motion":
371
+ "!@reduced-motion & !@mobile":
371
372
  "opacity $dialog-transition ease-out, display $dialog-transition allow-discrete, overlay $dialog-transition allow-discrete",
372
373
  },
373
374
  },
374
375
  EnteredBackdrop: {
375
376
  $: "dialog[open][data-open]::backdrop",
376
- opacity: "1",
377
+ opacity: { "": "1", "@mobile": "0" },
377
378
  },
378
379
  Frame: {
379
380
  $: ".dialog-frame",
@@ -935,8 +936,9 @@ export default function GlobalStyles() {
935
936
  marginBlockStart: "0",
936
937
  marginBlockEnd: "0",
937
938
  },
939
+ // Linked Cards are block content even though their root is an anchor.
938
940
  BlockSpacing: {
939
- $: ":not(a):not(strong):not(em):not(del):not(span):not(input):not(code):not(br) + :not(a):not(strong):not(em):not(del):not(span):not(input):not(code):not(br):not(li):not(dt):not(dd):not(.not-content *)",
941
+ $: ":not(a):not(strong):not(em):not(del):not(span):not(input):not(code):not(br) + :not(a):not(strong):not(em):not(del):not(span):not(input):not(code):not(br):not(li):not(dt):not(dd):not(.not-content *), :not(a):not(strong):not(em):not(del):not(span):not(input):not(code):not(br) + a.td-card, a.td-card + :not(a):not(strong):not(em):not(del):not(span):not(input):not(code):not(br):not(li):not(dt):not(dd):not(.not-content *), a.td-card + a.td-card",
940
942
  marginBlockStart: "($gap * 3)",
941
943
  },
942
944
  HeadingSpacing: {
@@ -1173,21 +1175,21 @@ export default function GlobalStyles() {
1173
1175
  lineHeight: "0",
1174
1176
  },
1175
1177
  DiffLine: {
1176
- $: "pre.td-diff .line",
1178
+ $: "pre.td-diff > code > .line",
1177
1179
  display: "block",
1178
1180
  inlinePadding: "1rem",
1179
1181
  preset: "code",
1180
1182
  },
1181
1183
  EmptyDiffLine: {
1182
- $: "pre.td-diff .line:empty::before",
1184
+ $: "pre.td-diff > code > .line:empty::before",
1183
1185
  content: '"\\200b"',
1184
1186
  },
1185
1187
  InsertedLine: {
1186
- $: "pre.td-diff .td-diff-line--inserted",
1188
+ $: "pre.td-diff > code > .td-diff-line--inserted",
1187
1189
  fill: "#green-surface",
1188
1190
  },
1189
1191
  DeletedLine: {
1190
- $: "pre.td-diff .td-diff-line--deleted",
1192
+ $: "pre.td-diff > code > .td-diff-line--deleted",
1191
1193
  fill: "#red-surface",
1192
1194
  },
1193
1195
  }),
@@ -1431,47 +1433,85 @@ export default function GlobalStyles() {
1431
1433
  }),
1432
1434
  );
1433
1435
 
1436
+ const languageSelectStyles = selectPopoverStyles();
1434
1437
  useGlobalStyles(
1435
1438
  "cookbook-language-select",
1436
1439
  resolveComponentStyles("LanguageSelect", {
1437
- display: "block",
1438
- Label: {
1439
- $: "label",
1440
- position: "relative",
1441
- display: "flex",
1442
- alignItems: "center",
1443
- color: "#text-soft",
1444
- },
1445
- HoverLabel: { $: "label:hover", color: "#text" },
1446
- LabelIcon: {
1447
- $: ".label-icon",
1448
- position: "absolute",
1449
- zIndex: "1",
1450
- insetInlineStart: "$gap",
1451
- pointerEvents: "none",
1440
+ display: "flex",
1441
+ flexShrink: "0",
1442
+ "$popover-transition": languageSelectStyles["$popover-transition"],
1443
+ Compact: {
1444
+ $: "&[data-compact]",
1452
1445
  },
1453
- Select: {
1454
- $: "select",
1455
- appearance: "none",
1446
+ Trigger: {
1447
+ ...languageSelectStyles.Trigger,
1456
1448
  inlineSize: "8rem",
1457
- minBlockSize: "$control-height",
1458
- paddingInlineStart: "($gap * 3.5)",
1459
- paddingInlineEnd: "($gap * 3.5)",
1460
- color: "#text-soft",
1461
- border: "0",
1462
- radius: "$header-control-radius",
1449
+ blockSize: "$control-height",
1450
+ padding: "0 $gap",
1463
1451
  fill: "#clear",
1464
- preset: "small",
1465
- cursor: "pointer",
1466
- textOverflow: "ellipsis",
1452
+ border: "0",
1453
+ transition: "color $transition, fill $transition",
1467
1454
  },
1468
- Caret: {
1469
- $: ".caret",
1470
- position: "absolute",
1471
- insetInlineEnd: "$gap",
1472
- pointerEvents: "none",
1455
+ HoverTrigger: languageSelectStyles.HoverTrigger,
1456
+ ActiveTrigger: {
1457
+ $: "> button:active",
1458
+ color: "#text",
1459
+ fill: "#surface-2-pressed",
1460
+ },
1461
+ LabelIcon: {
1462
+ $: "> button > .label-icon",
1463
+ display: "block",
1464
+ flexShrink: "0",
1465
+ inlineSize: { "": "1.25rem", "@mobile": "1.125rem" },
1466
+ blockSize: { "": "1.25rem", "@mobile": "1.125rem" },
1473
1467
  },
1474
- Option: { $: "option", color: "#text", fill: "#surface-2" },
1468
+ TriggerLabel: languageSelectStyles.TriggerLabel,
1469
+ Caret: languageSelectStyles.Caret,
1470
+ CompactTrigger: {
1471
+ $: "&[data-compact] > button",
1472
+ inlineSize: "$docs-menu-button-size",
1473
+ blockSize: "$docs-menu-button-size",
1474
+ padding: "0",
1475
+ },
1476
+ CompactLabel: {
1477
+ $: "&[data-compact] > button > .trigger-label",
1478
+ hide: true,
1479
+ },
1480
+ CompactCaret: { $: "&[data-compact] > button > .caret", hide: true },
1481
+ Label: { $: "> button" },
1482
+ HoverLabel: { $: "> button:hover" },
1483
+ Select: { $: "> button" },
1484
+ CompactSelect: { $: "&[data-compact] > button" },
1485
+ CompactLabelIcon: { $: "&[data-compact] > button > .label-icon" },
1486
+ SidebarTrigger: {
1487
+ $: "&[data-sidebar] > button",
1488
+ anchorName: "--language-trigger",
1489
+ },
1490
+ Panel: {
1491
+ ...languageSelectStyles.Panel,
1492
+ blockInset: { "": "4rem start", "@mobile": "3.5rem start" },
1493
+ inlineInset:
1494
+ "max($docs-nav-pad-x, ((100vw - $layout-width) / 2 + $docs-sidebar-pad-x)) end",
1495
+ },
1496
+ SidebarPanel: {
1497
+ $: "&[data-sidebar] > [popover]",
1498
+ positionAnchor: "--language-trigger",
1499
+ blockInset: "auto start, anchor(top) end",
1500
+ blockMargin: "$gap end",
1501
+ inlineInset: "$gap start, auto end",
1502
+ inlineSize: "min(20rem, calc(100vw - 4rem))",
1503
+ blockSize: "max (100dvh - 8rem)",
1504
+ transformOrigin: "bottom",
1505
+ },
1506
+ OpenPanel: languageSelectStyles.OpenPanel,
1507
+ PanelTitle: languageSelectStyles.PanelTitle,
1508
+ Options: languageSelectStyles.Options,
1509
+ Option: languageSelectStyles.Option,
1510
+ HoverOption: languageSelectStyles.HoverOption,
1511
+ CurrentOption: languageSelectStyles.CurrentOption,
1512
+ Fallback: { $: ".fallback-label", color: "#text-muted" },
1513
+ Checkmark: languageSelectStyles.Checkmark,
1514
+ SelectedCheckmark: languageSelectStyles.SelectedCheckmark,
1475
1515
  }),
1476
1516
  );
1477
1517
 
@@ -1490,7 +1530,7 @@ export default function GlobalStyles() {
1490
1530
  "$pagefind-ui-tag": "#surface-3",
1491
1531
  preset: "body",
1492
1532
  UI: {
1493
- $: ".pagefind-ui",
1533
+ $: ".pagefind-ui, [data-search-results]",
1494
1534
  display: "flex",
1495
1535
  flow: "column",
1496
1536
  flexGrow: "1",
@@ -1504,6 +1544,15 @@ export default function GlobalStyles() {
1504
1544
  flexGrow: "1",
1505
1545
  minBlockSize: "0",
1506
1546
  },
1547
+ Field: {
1548
+ $: ".cookbook-search-field",
1549
+ position: "relative",
1550
+ flexShrink: "0",
1551
+ },
1552
+ EngineControls: {
1553
+ $: ".pagefind-ui__search-input, .pagefind-ui__search-clear",
1554
+ display: "none",
1555
+ },
1507
1556
  Drawer: {
1508
1557
  $: ".pagefind-ui__drawer",
1509
1558
  display: "flex",
@@ -1512,7 +1561,7 @@ export default function GlobalStyles() {
1512
1561
  minBlockSize: "0",
1513
1562
  },
1514
1563
  Input: {
1515
- $: ".pagefind-ui__search-input",
1564
+ $: ".cookbook-search-input",
1516
1565
  flexShrink: "0",
1517
1566
  inlineSize: "100%",
1518
1567
  minBlockSize: "3rem",
@@ -1526,7 +1575,7 @@ export default function GlobalStyles() {
1526
1575
  boxShadow: "none",
1527
1576
  },
1528
1577
  Clear: {
1529
- $: ".pagefind-ui__search-clear",
1578
+ $: ".cookbook-search-clear",
1530
1579
  position: "absolute",
1531
1580
  insetBlockStart: "0",
1532
1581
  insetInlineEnd: "0",
@@ -1614,7 +1663,7 @@ export default function GlobalStyles() {
1614
1663
  },
1615
1664
  HoverMore: { $: ".pagefind-ui__button:hover", fill: "#surface-2-hover" },
1616
1665
  SearchIcon: {
1617
- $: ".pagefind-ui__form::before",
1666
+ $: ".cookbook-search-field::before",
1618
1667
  content: '""',
1619
1668
  position: "absolute",
1620
1669
  zIndex: "1",
@@ -1628,7 +1677,7 @@ export default function GlobalStyles() {
1628
1677
  pointerEvents: "none",
1629
1678
  },
1630
1679
  ClearIcon: {
1631
- $: ".pagefind-ui__search-clear::before",
1680
+ $: ".cookbook-search-clear::before",
1632
1681
  content: '""',
1633
1682
  display: "block",
1634
1683
  inlineSize: "1rem",
@@ -1637,7 +1686,7 @@ export default function GlobalStyles() {
1637
1686
  mask: `url("${svgIconUrl(closeIcon)}") center / 1rem no-repeat`,
1638
1687
  },
1639
1688
  SuppressedClear: {
1640
- $: ".pagefind-ui__search-clear.pagefind-ui__suppressed",
1689
+ $: ".cookbook-search-clear[hidden]",
1641
1690
  display: "none",
1642
1691
  },
1643
1692
  }),
@@ -1,11 +1,22 @@
1
1
  ---
2
2
  import { messages } from "../localization.js";
3
+ import { localeHref, routeLocale } from "../localization.js";
4
+ import { navigationPath } from "../navigation.js";
3
5
  import { content } from "virtual:cookbook/config";
4
6
  import { HeaderLinksRoot } from "./LayoutComponents.js";
5
7
 
6
8
  const links = content.site.headerLinks ?? [];
7
9
  const base = import.meta.env.BASE_URL.replace(/\/$/, "");
8
- const href = (link: string) => (link.startsWith("/") ? `${base}${link}` : link);
10
+ const currentPath = navigationPath(Astro.url.pathname, base || "/");
11
+ const href = (link: string) =>
12
+ localeHref(link, currentPath, content.routes, content, base || "/");
13
+ const locale = routeLocale(currentPath, content);
14
+ const language =
15
+ content.locales?.[locale]?.lang ?? (locale === "root" ? "en" : locale);
16
+ const label = (value: string) =>
17
+ content.translations?.[language]?.[`navigation.${value}`] ??
18
+ content.translations?.[language.split("-")[0]!]?.[`navigation.${value}`] ??
19
+ value;
9
20
  const ui = messages(Astro.locals.cookbookRoute?.lang ?? "en", content);
10
21
  ---
11
22
 
@@ -20,7 +31,7 @@ const ui = messages(Astro.locals.cookbookRoute?.lang ?? "en", content);
20
31
  target={link.newTab ? "_blank" : undefined}
21
32
  rel={link.newTab ? "noopener noreferrer" : undefined}
22
33
  >
23
- {link.label}
34
+ {label(link.label)}
24
35
  </a>
25
36
  ))}
26
37
  </nav>
@@ -74,7 +85,7 @@ const ui = messages(Astro.locals.cookbookRoute?.lang ?? "en", content);
74
85
  target={link.newTab ? "_blank" : undefined}
75
86
  rel={link.newTab ? "noopener noreferrer" : undefined}
76
87
  >
77
- {link.label}
88
+ {label(link.label)}
78
89
  </a>
79
90
  ))}
80
91
  </nav>
@@ -23,7 +23,7 @@ const originals: Record<string, string> = {
23
23
  laptop: desktop,
24
24
  "approve-check": check,
25
25
  translate:
26
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18"/></svg>',
26
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/></svg>',
27
27
  github:
28
28
  '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48v-1.7c-2.77.6-3.36-1.18-3.36-1.18-.45-1.14-1.1-1.44-1.1-1.44-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.89 1.52 2.33 1.08 2.9.83.09-.65.35-1.08.63-1.33-2.21-.25-4.53-1.1-4.53-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02A9.6 9.6 0 0 1 12 6.66c.85 0 1.7.11 2.5.34 1.9-1.29 2.74-1.02 2.74-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.85-2.33 4.68-4.55 4.93.36.31.68.92.68 1.86v2.91c0 .27.18.58.69.48A10 10 0 0 0 12 2Z"/></svg>',
29
29
  gitlab: