@tenphi/renderer 0.20.4 → 0.20.5

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.
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"}
@@ -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";
@@ -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" },
1467
+ },
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,
1473
1479
  },
1474
- Option: { $: "option", color: "#text", fill: "#surface-2" },
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
 
@@ -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:
@@ -1,5 +1,6 @@
1
1
  import { tasty } from "@tenphi/tasty";
2
2
  import { customizeComponent } from "./customize-component.js";
3
+ import { selectPopoverStyles } from "./select-popover-styles.js";
3
4
  import { configureCookbookStates } from "./tasty-states.js";
4
5
 
5
6
  configureCookbookStates();
@@ -117,56 +118,37 @@ export const PackageVersionRoot = customizeComponent(
117
118
  }),
118
119
  );
119
120
 
121
+ const versionSelectStyles = selectPopoverStyles({
122
+ option: "Link",
123
+ hoverOption: "HoverLink",
124
+ currentOption: "CurrentLink",
125
+ });
126
+
120
127
  export const VersionSwitcherRoot = customizeComponent(
121
128
  "VersionSwitcher",
122
129
  tasty({
123
- as: "details",
130
+ as: "div",
124
131
  styles: {
132
+ ...versionSelectStyles,
125
133
  display: "inline-flex",
126
- position: "relative",
127
134
  flexShrink: "0",
128
- color: "#text-soft",
129
135
  Trigger: {
130
- $: "> summary",
131
- display: "flex",
132
- alignItems: "center",
136
+ ...versionSelectStyles.Trigger,
133
137
  blockSize: "min $control-height",
134
138
  inlinePadding: "$gap",
135
139
  fill: "#surface-2",
136
140
  border: true,
137
- radius: "$header-control-radius",
138
141
  preset: "small / strong",
139
- cursor: "pointer",
140
- whiteSpace: "nowrap",
142
+ anchorName: "--version-trigger",
141
143
  },
142
144
  Panel: {
143
- $: "> nav",
144
- position: "absolute",
145
- inset: { "": "100% auto auto 0", "@mobile": "100% 0 auto auto" },
146
- zIndex: "10",
147
- inlineSize: "min 8rem",
148
- padding: "($gap * 0.5)",
149
- fill: "#surface",
150
- border: true,
151
- radius: "$radius",
152
- shadow: "0 0.75rem 2rem #shadow",
153
- },
154
- OpenPanel: {
155
- $: "&[open] > nav",
156
- display: "grid",
157
- },
158
- Link: {
159
- $: "> nav > a",
160
- display: "block",
161
- padding: "($gap * 0.5) $gap",
162
- color: "#text-soft",
163
- preset: "small",
164
- textDecoration: "none",
165
- },
166
- CurrentLink: {
167
- $: '> nav > a[aria-current="page"]',
168
- color: "#accent-text",
169
- preset: "small / strong",
145
+ ...versionSelectStyles.Panel,
146
+ positionAnchor: "--version-trigger",
147
+ blockInset: "(anchor(bottom) + $gap) start, auto end",
148
+ inlineInset: {
149
+ "": "anchor(left) start, auto end",
150
+ "@mobile": "auto start, anchor(right) end",
151
+ },
170
152
  },
171
153
  },
172
154
  }),
@@ -320,6 +302,15 @@ export const HeaderRoot = customizeComponent(
320
302
  inlineSize: "$docs-menu-button-size",
321
303
  blockSize: "$docs-menu-button-size",
322
304
  },
305
+ MobileLanguage: {
306
+ $: ".td-header__mobile-language",
307
+ display: "grid",
308
+ hide: { "": true, "@mobile": false },
309
+ flexShrink: "0",
310
+ order: "1",
311
+ inlineSize: "$docs-menu-button-size",
312
+ blockSize: "$docs-menu-button-size",
313
+ },
323
314
  },
324
315
  }),
325
316
  );
@@ -394,9 +385,9 @@ export const ThemeSelectRoot = customizeComponent(
394
385
  },
395
386
  },
396
387
  OpenPanel: {
397
- $: "[popover]:popover-open",
398
- opacity: { "": "1", "@starting": "0" },
399
- scale: { "": "1", "@starting": "1 0.96" },
388
+ $: "[popover]:popover-open[data-open]",
389
+ opacity: "1",
390
+ scale: "1",
400
391
  },
401
392
  Section: {
402
393
  $: "fieldset",
@@ -745,7 +736,7 @@ export const HeaderLinksRoot = customizeComponent(
745
736
  shadow: "0 0.75rem 2rem #shadow",
746
737
  opacity: "0",
747
738
  scale: "1 0.96",
748
- transformOrigin: "top right",
739
+ transformOrigin: "top",
749
740
  transition: {
750
741
  "": "none",
751
742
  "!@reduced-motion":
@@ -14,7 +14,7 @@ const ui = messages(
14
14
  ---
15
15
 
16
16
  <PreviewRoot
17
- className="td-preview"
17
+ className="td-preview not-content"
18
18
  aria-label={title}
19
19
  data-tasty-anatomy="Preview"
20
20
  >
@@ -0,0 +1,64 @@
1
+ ---
2
+ import Icon from "./Icon.astro";
3
+
4
+ interface Option {
5
+ href: string;
6
+ label: string;
7
+ lang?: string;
8
+ hint?: string;
9
+ current?: boolean;
10
+ }
11
+
12
+ interface Props {
13
+ triggerLabel: string;
14
+ selectedLabel: string;
15
+ title: string;
16
+ options: Option[];
17
+ icon?: string;
18
+ }
19
+
20
+ const { triggerLabel, selectedLabel, title, options, icon } = Astro.props;
21
+ const id = `select-${crypto.randomUUID()}`;
22
+ ---
23
+
24
+ <button
25
+ type="button"
26
+ popovertarget={id}
27
+ aria-label={triggerLabel}
28
+ title={title}
29
+ >
30
+ {icon && <Icon name={icon} class="label-icon" />}
31
+ <span class="trigger-label">{selectedLabel}</span>
32
+ <Icon name="down-caret" class="caret" />
33
+ </button>
34
+ <div
35
+ id={id}
36
+ popover="auto"
37
+ role="group"
38
+ aria-label={title}
39
+ data-select-popover-panel
40
+ >
41
+ <div class="panel-title">{title}</div>
42
+ <nav aria-label={title}>
43
+ {
44
+ options.map((option) => (
45
+ <a
46
+ href={option.href}
47
+ aria-current={option.current ? "page" : undefined}
48
+ >
49
+ <span lang={option.lang}>{option.label}</span>
50
+ {option.hint && <span class="fallback-label">({option.hint})</span>}
51
+ <Icon name="approve-check" class="checkmark" />
52
+ </a>
53
+ ))
54
+ }
55
+ </nav>
56
+ </div>
57
+
58
+ <script>
59
+ import { initializePopoverMotion } from "../client/popover-motion.js";
60
+
61
+ document
62
+ .querySelectorAll<HTMLElement>("[data-select-popover-panel]")
63
+ .forEach((panel) => initializePopoverMotion(panel));
64
+ </script>
@@ -84,13 +84,13 @@ export const PreviewRoot = customizeComponent(
84
84
  fill: "#surface-2",
85
85
  },
86
86
  Stage: {
87
- padding: "3x",
87
+ padding: "2x",
88
88
  fill: "#surface",
89
89
  },
90
90
  Frame: {
91
91
  display: "block",
92
92
  width: "100%",
93
- height: "min 18rem",
93
+ height: "min 10rem",
94
94
  border: "0",
95
95
  fill: "#surface",
96
96
  },
@@ -102,9 +102,23 @@ export const PreviewRoot = customizeComponent(
102
102
  preset: "small",
103
103
  fill: "#surface-2",
104
104
  cursor: "pointer",
105
+ transition: "fill $transition",
106
+ },
107
+ HoverSummary: {
108
+ $: "> details > summary:hover",
109
+ fill: "#surface-2-hover",
110
+ },
111
+ ActiveSummary: {
112
+ $: "> details > summary:active",
113
+ fill: "#surface-2-pressed",
105
114
  },
106
115
  Pre: {
107
116
  margin: "0",
117
+ padding: "2x",
118
+ overflowX: "auto",
119
+ color: "#syntax-text",
120
+ fill: "#syntax-bg",
121
+ preset: "code",
108
122
  radius: "0",
109
123
  },
110
124
  },
@@ -4,6 +4,7 @@ import { content } from "virtual:cookbook/config";
4
4
  import type { DocsRoute, SiteVersion } from "@tenphi/docs";
5
5
  import { versionLinks } from "../versioning.js";
6
6
  import { VersionSwitcherRoot } from "./LayoutComponents.js";
7
+ import SelectPopover from "./SelectPopover.astro";
7
8
 
8
9
  interface Props {
9
10
  versions: SiteVersion[];
@@ -23,22 +24,14 @@ const ui = messages(Astro.locals.cookbookRoute?.lang ?? "en", content);
23
24
  ---
24
25
 
25
26
  <VersionSwitcherRoot data-tasty-anatomy="VersionSwitcher">
26
- <summary
27
- aria-label={`${ui.documentationVersion}: ${current?.label ?? ui.versions}`}
28
- >{current?.label ?? ui.versions}</summary
29
- >
30
- <nav aria-label={ui.documentationVersions}>
31
- {
32
- links.map((version) => (
33
- <a
34
- href={version.href}
35
- aria-current={
36
- version.routeBase === current?.routeBase ? "page" : undefined
37
- }
38
- >
39
- {version.label}
40
- </a>
41
- ))
42
- }
43
- </nav>
27
+ <SelectPopover
28
+ triggerLabel={`${ui.documentationVersion}: ${current?.label ?? ui.versions}`}
29
+ selectedLabel={current?.label ?? ui.versions}
30
+ title={ui.documentationVersions}
31
+ options={links.map((version) => ({
32
+ href: version.href,
33
+ label: version.label,
34
+ current: version.routeBase === current?.routeBase,
35
+ }))}
36
+ />
44
37
  </VersionSwitcherRoot>
@@ -0,0 +1,107 @@
1
+ /** Shared server-side styles for the language and version selectors. */
2
+ export function selectPopoverStyles({
3
+ option = "Option",
4
+ hoverOption = "HoverOption",
5
+ currentOption = "CurrentOption",
6
+ } = {}) {
7
+ return {
8
+ "$popover-transition": "120ms",
9
+ Trigger: {
10
+ $: "> button",
11
+ display: "flex",
12
+ alignItems: "center",
13
+ justifyContent: "center",
14
+ gap: "($gap * 0.5)",
15
+ color: "#text-soft",
16
+ radius: "$header-control-radius",
17
+ preset: "small",
18
+ cursor: "pointer",
19
+ },
20
+ HoverTrigger: {
21
+ $: "> button:hover, &:has([popover]:popover-open) > button",
22
+ color: "#text",
23
+ fill: "#surface-2-hover",
24
+ },
25
+ TriggerLabel: {
26
+ $: ".trigger-label",
27
+ overflow: "hidden",
28
+ textOverflow: "ellipsis",
29
+ whiteSpace: "nowrap",
30
+ },
31
+ Caret: {
32
+ $: "> button > .caret",
33
+ flexShrink: "0",
34
+ inlineSize: "0.875rem",
35
+ blockSize: "0.875rem",
36
+ },
37
+ Panel: {
38
+ $: "> [popover]",
39
+ position: "fixed",
40
+ inset: "auto",
41
+ inlineSize: "min(15rem, calc(100vw - 2 * $docs-nav-pad-x))",
42
+ blockSize: "max (100dvh - $docs-nav-height - $gap)",
43
+ overflowY: "auto",
44
+ margin: "0",
45
+ padding: "$gap",
46
+ color: "#text",
47
+ fill: "#surface-2",
48
+ border: true,
49
+ radius: "$card-radius",
50
+ shadow: "0 0.75rem 2rem #shadow",
51
+ opacity: "0",
52
+ scale: "1 0.96",
53
+ transformOrigin: "top",
54
+ transition: {
55
+ "": "none",
56
+ "!@reduced-motion":
57
+ "opacity $popover-transition ease-out, scale $popover-transition ease-out, display $popover-transition allow-discrete, overlay $popover-transition allow-discrete",
58
+ },
59
+ },
60
+ OpenPanel: {
61
+ $: "> [popover]:popover-open[data-open]",
62
+ opacity: "1",
63
+ scale: "1",
64
+ },
65
+ PanelTitle: {
66
+ $: ".panel-title",
67
+ padding: "($gap * 0.75) $gap",
68
+ color: "#text-muted",
69
+ preset: "small / strong",
70
+ },
71
+ Options: { $: "nav", display: "grid", gap: "($gap * 0.25)" },
72
+ [option]: {
73
+ $: "nav a",
74
+ display: "flex",
75
+ alignItems: "center",
76
+ gap: "$gap",
77
+ minBlockSize: "2.25rem",
78
+ padding: "($gap * 0.75) $gap",
79
+ color: "#text-soft",
80
+ preset: "small",
81
+ textDecoration: "none",
82
+ radius: "$radius",
83
+ },
84
+ [hoverOption]: {
85
+ $: "nav a:hover",
86
+ color: "#text",
87
+ fill: "#surface-2-hover",
88
+ },
89
+ [currentOption]: {
90
+ $: 'nav a[aria-current="page"]',
91
+ color: "#accent-text",
92
+ fill: "#accent-surface-2-subtle",
93
+ },
94
+ Checkmark: {
95
+ $: "nav a > .checkmark",
96
+ flexShrink: "0",
97
+ inlineMargin: "auto start",
98
+ inlineSize: "1rem",
99
+ blockSize: "1rem",
100
+ visibility: "hidden",
101
+ },
102
+ SelectedCheckmark: {
103
+ $: 'nav a[aria-current="page"] > .checkmark',
104
+ visibility: "visible",
105
+ },
106
+ };
107
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"localization.d.ts","names":[],"sources":["../src/localization.ts"],"mappings":";;;KAMK;EACH,UAAU;EACV,gBAAgB;EAChB,eAAe;;iBAGD,YAAY,cAAc,SAAS;;iBAMnC,gBACd,kBACA,cACA,SAAS;iBAWK,WAAW,cAAc,SAAS;iBAQlC,eACd,cACA,gBACA,SAAS;;iBASK,aACd,cACA,gBACA,QAAQ,aACR,SAAS;iBAoBK,WACd,cACA,qBACA,QAAQ,aACR,SAAS,eACT;;iBAec,oBACd,QAAQ,0BACR,QAAQ,aACR,qBACA,SAAS;EACN,QAAQ;EAA0B,QAAQ;;cAiElC;WACX;WACA;WACA;WACA;WACA;WACA;WAEA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;KAEU,0BAA0B;iBA0CtB,SACd,cACA,UAAS,gBACR,OAAO;iBAUM,iBACd,cACA,QAAQ,aACR,SAAS,gBACR;EAAQ;EAAc"}
1
+ {"version":3,"file":"localization.d.ts","names":[],"sources":["../src/localization.ts"],"mappings":";;;KAMK;EACH,UAAU;EACV,gBAAgB;EAChB,eAAe;;iBAGD,YAAY,cAAc,SAAS;;iBAMnC,gBACd,kBACA,cACA,SAAS;iBAWK,WAAW,cAAc,SAAS;iBAQlC,eACd,cACA,gBACA,SAAS;;iBASK,aACd,cACA,gBACA,QAAQ,aACR,SAAS;iBAoBK,WACd,cACA,qBACA,QAAQ,aACR,SAAS,eACT;;iBAec,oBACd,QAAQ,0BACR,QAAQ,aACR,qBACA,SAAS;EACN,QAAQ;EAA0B,QAAQ;;cA8FlC;WACX;WACA;WACA;WACA;WACA;WACA;WAEA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;KAEU,0BAA0B;iBA0CtB,SACd,cACA,UAAS,gBACR,OAAO;iBAUM,iBACd,cACA,QAAQ,aACR,SAAS,gBACR;EAAQ;EAAc"}
@@ -51,24 +51,38 @@ function localizedNavigation(layout, routes, currentPath, options) {
51
51
  route: localePath(route.route, options)
52
52
  }));
53
53
  const available = new Set(localRoutes.filter((route) => route.discoverable !== false).map((route) => route.route));
54
+ const visibleRoutes = new Map(routes.filter((route) => route.discoverable !== false).map((route) => [route.route, route]));
55
+ const fallbackLocale = options.defaultLocale ?? (options.locales.root ? "root" : Object.keys(options.locales)[0]);
54
56
  const convert = (items) => items.flatMap((item) => {
55
57
  const link = typeof item === "string" ? item : item.link;
56
58
  const internal = link?.startsWith("/") && !link.startsWith("//");
57
59
  const translated = internal ? localePath(link, options) : link;
58
- if (typeof item === "string") return !internal || available.has(translated) ? [translated] : [];
60
+ const fallback = internal ? localizedRoute(link, fallbackLocale, options) : void 0;
61
+ const target = !internal || available.has(translated) ? translated : fallback && visibleRoutes.has(fallback) ? fallback : void 0;
62
+ if (typeof item === "string") {
63
+ if (!target) return [];
64
+ if (!internal || available.has(translated)) return [target];
65
+ const route = visibleRoutes.get(target);
66
+ return [{
67
+ label: typeof route.sidebar === "object" && route.sidebar.label ? route.sidebar.label : route.title,
68
+ link: target
69
+ }];
70
+ }
59
71
  if ("items" in item || "autogenerate" in item) {
60
72
  const { link: _link, ...group } = item;
73
+ const children = "items" in item ? convert(item.items) : void 0;
74
+ if (children?.length === 0 && !target) return [];
61
75
  return [{
62
76
  ...group,
63
77
  label: label(group.label),
64
- ...translated && (!internal || available.has(translated)) ? { link: translated } : {},
65
- ..."items" in item ? { items: convert(item.items) } : { autogenerate: { directory: localePath(item.autogenerate.directory, options) } }
78
+ ...target ? { link: target } : {},
79
+ ..."items" in item ? { items: children } : { autogenerate: { directory: localePath(item.autogenerate.directory, options) } }
66
80
  }];
67
81
  }
68
- return !internal || available.has(translated) ? [{
82
+ return target ? [{
69
83
  ...item,
70
84
  label: label(item.label),
71
- link: translated
85
+ link: target
72
86
  }] : [];
73
87
  });
74
88
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"localization.js","names":[],"sources":["../src/localization.ts"],"sourcesContent":["import type { DocsConfig, DocsRoute, NavigationItem } from \"@tenphi/docs\";\nimport {\n normalizeNavigationPath,\n type ResolvedNavigationLayout,\n} from \"./navigation.js\";\n\ntype LocaleOptions = {\n locales?: DocsConfig[\"locales\"] | undefined;\n defaultLocale?: DocsConfig[\"defaultLocale\"] | undefined;\n translations?: DocsConfig[\"translations\"] | undefined;\n};\n\nexport function routeLocale(path: string, options: LocaleOptions): string {\n const first = normalizeNavigationPath(path).split(\"/\")[1];\n return first && first !== \"root\" && options.locales?.[first] ? first : \"root\";\n}\n\n/** Author components can render before the page initializes route locals. */\nexport function requestLanguage(\n pathname: string,\n base: string,\n options: LocaleOptions,\n): string {\n const prefix = base.replace(/\\/$/, \"\");\n const path =\n prefix && (pathname === prefix || pathname.startsWith(`${prefix}/`))\n ? pathname.slice(prefix.length)\n : pathname;\n const locale = routeLocale(path, options);\n return options.locales?.[locale]?.lang ?? (locale === \"root\" ? \"en\" : locale);\n}\n\nexport function localePath(path: string, options: LocaleOptions): string {\n const route = normalizeNavigationPath(path);\n const locale = routeLocale(route, options);\n return locale === \"root\"\n ? route\n : normalizeNavigationPath(route.slice(locale.length + 1));\n}\n\nexport function localizedRoute(\n path: string,\n locale: string,\n options: LocaleOptions,\n): string {\n const suffix = localePath(path, options);\n return normalizeNavigationPath(\n `${locale === \"root\" ? \"\" : `/${locale}`}${suffix}`,\n );\n}\n\n/** Missing translations use a real default-language page, then a real locale home. */\nexport function localeTarget(\n path: string,\n locale: string,\n routes: DocsRoute[],\n options: LocaleOptions,\n): string | undefined {\n const visible = new Set(\n routes\n .filter((route) => route.discoverable !== false)\n .map((route) => route.route),\n );\n const fallback =\n options.defaultLocale ??\n (options.locales?.root\n ? \"root\"\n : (Object.keys(options.locales ?? {})[0] ?? \"root\"));\n return [\n localizedRoute(path, locale, options),\n localizedRoute(path, fallback, options),\n localizedRoute(\"/\", locale, options),\n localizedRoute(\"/\", fallback, options),\n ].find((route) => visible.has(route));\n}\n\nexport function localeHref(\n link: string,\n currentPath: string,\n routes: DocsRoute[],\n options: LocaleOptions,\n base = \"/\",\n): string {\n if (!link.startsWith(\"/\") || link.startsWith(\"//\")) return link;\n const suffix = /[?#].*$/.exec(link)?.[0] ?? \"\";\n const path = suffix ? link.slice(0, -suffix.length) : link;\n const target = options.locales\n ? localeTarget(path, routeLocale(currentPath, options), routes, options)\n : normalizeNavigationPath(path);\n return (\n `${base.replace(/\\/$/, \"\")}${target ?? normalizeNavigationPath(path)}${suffix}` ||\n \"/\"\n );\n}\n\n/** Resolve navigation against the current locale's available routes. */\nexport function localizedNavigation(\n layout: ResolvedNavigationLayout,\n routes: DocsRoute[],\n currentPath: string,\n options: LocaleOptions,\n): { layout: ResolvedNavigationLayout; routes: DocsRoute[] } {\n if (!options.locales) return { layout, routes };\n const locale = routeLocale(currentPath, options);\n const language =\n options.locales[locale]?.lang ?? (locale === \"root\" ? \"en\" : locale);\n const label = (value: string) =>\n options.translations?.[language]?.[`navigation.${value}`] ??\n options.translations?.[language.split(\"-\")[0]!]?.[`navigation.${value}`] ??\n value;\n const localRoutes = routes\n .filter((route) => routeLocale(route.route, options) === locale)\n .map((route) => ({ ...route, route: localePath(route.route, options) }));\n const available = new Set(\n localRoutes\n .filter((route) => route.discoverable !== false)\n .map((route) => route.route),\n );\n const convert = (items: NavigationItem[]): NavigationItem[] =>\n items.flatMap((item): NavigationItem[] => {\n const link = typeof item === \"string\" ? item : item.link;\n const internal = link?.startsWith(\"/\") && !link.startsWith(\"//\");\n const translated = internal ? localePath(link!, options) : link;\n if (typeof item === \"string\")\n return !internal || available.has(translated!) ? [translated!] : [];\n if (\"items\" in item || \"autogenerate\" in item) {\n const { link: _link, ...group } = item;\n return [\n {\n ...group,\n label: label(group.label),\n ...(translated && (!internal || available.has(translated))\n ? { link: translated }\n : {}),\n ...(\"items\" in item\n ? { items: convert(item.items) }\n : {\n autogenerate: {\n directory: localePath(item.autogenerate.directory, options),\n },\n }),\n },\n ];\n }\n return !internal || available.has(translated!)\n ? [{ ...item, label: label(item.label), link: translated! }]\n : [];\n });\n return {\n layout: {\n ...layout,\n ...(layout.items ? { items: convert(layout.items) } : {}),\n tabs: layout.tabs.map((tab) => ({\n ...tab,\n label: label(tab.label),\n link:\n tab.link.startsWith(\"/\") && !tab.link.startsWith(\"//\")\n ? localePath(tab.link, options)\n : tab.link,\n ...(tab.items ? { items: convert(tab.items) } : {}),\n })),\n },\n routes: localRoutes,\n };\n}\n\nexport const ENGLISH_MESSAGES = {\n copyPage: \"Copy page\",\n viewMarkdown: \"Download Markdown\",\n pageCopied: \"Page copied\",\n pageCopyError: \"Could not copy. Download Markdown to read or copy the text.\",\n draftTitle: \"Draft\",\n draftNotice:\n \"This page is available by direct link and excluded from navigation and indexes.\",\n searchError: \"Search could not load. Close and try again.\",\n appearance: \"Appearance\",\n colorScheme: \"Color scheme\",\n contrast: \"Contrast\",\n normalContrast: \"Normal\",\n highContrast: \"High\",\n auto: \"Auto\",\n closeNavigation: \"Close navigation\",\n primary: \"Primary\",\n sections: \"Sections\",\n chooseSection: \"Choose section\",\n headerLinks: \"Header links\",\n more: \"More\",\n closeMore: \"Close more menu\",\n moreLinks: \"More links\",\n versions: \"Versions\",\n documentationVersion: \"Documentation version\",\n documentationVersions: \"Documentation versions\",\n version: \"Version\",\n home: \"Home\",\n generatedWith: \"Generated with\",\n viewSource: \"View source\",\n copyCode: \"Copy code\",\n codeCopied: \"Code copied\",\n codeCopyError: \"Could not copy code\",\n copyHeading: \"Copy link to “{heading}”\",\n linkCopied: \"Link copied\",\n linkCopyError: \"Could not copy link\",\n notFound: \"Page not found\",\n notFoundDescription: \"The page you requested could not be found.\",\n fallback: \"fallback\",\n} as const;\nexport type MessageKey = keyof typeof ENGLISH_MESSAGES;\nconst FRENCH_MESSAGES: Record<MessageKey, string> = {\n copyPage: \"Copier la page\",\n viewMarkdown: \"Télécharger le Markdown\",\n pageCopied: \"Page copiée\",\n pageCopyError:\n \"Copie impossible. Téléchargez le Markdown pour lire ou copier le texte.\",\n draftTitle: \"Brouillon\",\n draftNotice:\n \"Cette page est accessible par son lien direct et exclue de la navigation et des index.\",\n searchError: \"La recherche n’a pas pu se charger. Fermez et réessayez.\",\n appearance: \"Apparence\",\n colorScheme: \"Thème de couleur\",\n contrast: \"Contraste\",\n normalContrast: \"Normal\",\n highContrast: \"Élevé\",\n auto: \"Auto\",\n closeNavigation: \"Fermer la navigation\",\n primary: \"Navigation principale\",\n sections: \"Sections\",\n chooseSection: \"Choisir une section\",\n headerLinks: \"Liens d’en-tête\",\n more: \"Plus\",\n closeMore: \"Fermer le menu\",\n moreLinks: \"Autres liens\",\n versions: \"Versions\",\n documentationVersion: \"Version de la documentation\",\n documentationVersions: \"Versions de la documentation\",\n version: \"Version\",\n home: \"Accueil\",\n generatedWith: \"Créé avec\",\n viewSource: \"Voir le code source\",\n copyCode: \"Copier le code\",\n codeCopied: \"Code copié\",\n codeCopyError: \"Impossible de copier le code\",\n copyHeading: \"Copier le lien vers « {heading} »\",\n linkCopied: \"Lien copié\",\n linkCopyError: \"Impossible de copier le lien\",\n notFound: \"Page introuvable\",\n notFoundDescription: \"La page demandée est introuvable.\",\n fallback: \"langue de secours\",\n};\nexport function messages(\n lang: string,\n options: LocaleOptions = {},\n): Record<MessageKey, string> {\n const language = lang.split(\"-\")[0] ?? \"en\";\n return {\n ...ENGLISH_MESSAGES,\n ...(language === \"fr\" ? FRENCH_MESSAGES : {}),\n ...options.translations?.[language],\n ...options.translations?.[lang],\n };\n}\n\nexport function localeAlternates(\n path: string,\n routes: DocsRoute[],\n options: LocaleOptions,\n): Array<{ lang: string; route: string }> {\n const available = new Set(\n routes\n .filter((route) => route.discoverable !== false)\n .map((route) => route.route),\n );\n if (!options.locales || !available.has(path)) return [];\n const alternates = Object.entries(options.locales).flatMap(\n ([locale, config]) => {\n const route = localizedRoute(path, locale, options);\n return available.has(route)\n ? [{ lang: config.lang ?? locale, route }]\n : [];\n },\n );\n const fallback = localizedRoute(\n path,\n options.defaultLocale ??\n (options.locales.root ? \"root\" : Object.keys(options.locales)[0]!),\n options,\n );\n if (available.has(fallback))\n alternates.push({ lang: \"x-default\", route: fallback });\n return alternates;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"localization.js","names":[],"sources":["../src/localization.ts"],"sourcesContent":["import type { DocsConfig, DocsRoute, NavigationItem } from \"@tenphi/docs\";\nimport {\n normalizeNavigationPath,\n type ResolvedNavigationLayout,\n} from \"./navigation.js\";\n\ntype LocaleOptions = {\n locales?: DocsConfig[\"locales\"] | undefined;\n defaultLocale?: DocsConfig[\"defaultLocale\"] | undefined;\n translations?: DocsConfig[\"translations\"] | undefined;\n};\n\nexport function routeLocale(path: string, options: LocaleOptions): string {\n const first = normalizeNavigationPath(path).split(\"/\")[1];\n return first && first !== \"root\" && options.locales?.[first] ? first : \"root\";\n}\n\n/** Author components can render before the page initializes route locals. */\nexport function requestLanguage(\n pathname: string,\n base: string,\n options: LocaleOptions,\n): string {\n const prefix = base.replace(/\\/$/, \"\");\n const path =\n prefix && (pathname === prefix || pathname.startsWith(`${prefix}/`))\n ? pathname.slice(prefix.length)\n : pathname;\n const locale = routeLocale(path, options);\n return options.locales?.[locale]?.lang ?? (locale === \"root\" ? \"en\" : locale);\n}\n\nexport function localePath(path: string, options: LocaleOptions): string {\n const route = normalizeNavigationPath(path);\n const locale = routeLocale(route, options);\n return locale === \"root\"\n ? route\n : normalizeNavigationPath(route.slice(locale.length + 1));\n}\n\nexport function localizedRoute(\n path: string,\n locale: string,\n options: LocaleOptions,\n): string {\n const suffix = localePath(path, options);\n return normalizeNavigationPath(\n `${locale === \"root\" ? \"\" : `/${locale}`}${suffix}`,\n );\n}\n\n/** Missing translations use a real default-language page, then a real locale home. */\nexport function localeTarget(\n path: string,\n locale: string,\n routes: DocsRoute[],\n options: LocaleOptions,\n): string | undefined {\n const visible = new Set(\n routes\n .filter((route) => route.discoverable !== false)\n .map((route) => route.route),\n );\n const fallback =\n options.defaultLocale ??\n (options.locales?.root\n ? \"root\"\n : (Object.keys(options.locales ?? {})[0] ?? \"root\"));\n return [\n localizedRoute(path, locale, options),\n localizedRoute(path, fallback, options),\n localizedRoute(\"/\", locale, options),\n localizedRoute(\"/\", fallback, options),\n ].find((route) => visible.has(route));\n}\n\nexport function localeHref(\n link: string,\n currentPath: string,\n routes: DocsRoute[],\n options: LocaleOptions,\n base = \"/\",\n): string {\n if (!link.startsWith(\"/\") || link.startsWith(\"//\")) return link;\n const suffix = /[?#].*$/.exec(link)?.[0] ?? \"\";\n const path = suffix ? link.slice(0, -suffix.length) : link;\n const target = options.locales\n ? localeTarget(path, routeLocale(currentPath, options), routes, options)\n : normalizeNavigationPath(path);\n return (\n `${base.replace(/\\/$/, \"\")}${target ?? normalizeNavigationPath(path)}${suffix}` ||\n \"/\"\n );\n}\n\n/** Resolve navigation against the current locale's available routes. */\nexport function localizedNavigation(\n layout: ResolvedNavigationLayout,\n routes: DocsRoute[],\n currentPath: string,\n options: LocaleOptions,\n): { layout: ResolvedNavigationLayout; routes: DocsRoute[] } {\n if (!options.locales) return { layout, routes };\n const locale = routeLocale(currentPath, options);\n const language =\n options.locales[locale]?.lang ?? (locale === \"root\" ? \"en\" : locale);\n const label = (value: string) =>\n options.translations?.[language]?.[`navigation.${value}`] ??\n options.translations?.[language.split(\"-\")[0]!]?.[`navigation.${value}`] ??\n value;\n const localRoutes = routes\n .filter((route) => routeLocale(route.route, options) === locale)\n .map((route) => ({ ...route, route: localePath(route.route, options) }));\n const available = new Set(\n localRoutes\n .filter((route) => route.discoverable !== false)\n .map((route) => route.route),\n );\n const visibleRoutes = new Map(\n routes\n .filter((route) => route.discoverable !== false)\n .map((route) => [route.route, route]),\n );\n const fallbackLocale =\n options.defaultLocale ??\n (options.locales.root ? \"root\" : Object.keys(options.locales)[0]!);\n const convert = (items: NavigationItem[]): NavigationItem[] =>\n items.flatMap((item): NavigationItem[] => {\n const link = typeof item === \"string\" ? item : item.link;\n const internal = link?.startsWith(\"/\") && !link.startsWith(\"//\");\n const translated = internal ? localePath(link!, options) : link;\n const fallback = internal\n ? localizedRoute(link!, fallbackLocale, options)\n : undefined;\n const target =\n !internal || available.has(translated!)\n ? translated\n : fallback && visibleRoutes.has(fallback)\n ? fallback\n : undefined;\n if (typeof item === \"string\") {\n if (!target) return [];\n if (!internal || available.has(translated!)) return [target];\n const route = visibleRoutes.get(target)!;\n return [\n {\n label:\n typeof route.sidebar === \"object\" && route.sidebar.label\n ? route.sidebar.label\n : route.title,\n link: target,\n },\n ];\n }\n if (\"items\" in item || \"autogenerate\" in item) {\n const { link: _link, ...group } = item;\n const children = \"items\" in item ? convert(item.items) : undefined;\n if (children?.length === 0 && !target) return [];\n return [\n {\n ...group,\n label: label(group.label),\n ...(target ? { link: target } : {}),\n ...(\"items\" in item\n ? { items: children! }\n : {\n autogenerate: {\n directory: localePath(item.autogenerate.directory, options),\n },\n }),\n },\n ];\n }\n return target\n ? [{ ...item, label: label(item.label), link: target }]\n : [];\n });\n return {\n layout: {\n ...layout,\n ...(layout.items ? { items: convert(layout.items) } : {}),\n tabs: layout.tabs.map((tab) => ({\n ...tab,\n label: label(tab.label),\n link:\n tab.link.startsWith(\"/\") && !tab.link.startsWith(\"//\")\n ? localePath(tab.link, options)\n : tab.link,\n ...(tab.items ? { items: convert(tab.items) } : {}),\n })),\n },\n routes: localRoutes,\n };\n}\n\nexport const ENGLISH_MESSAGES = {\n copyPage: \"Copy page\",\n viewMarkdown: \"Download Markdown\",\n pageCopied: \"Page copied\",\n pageCopyError: \"Could not copy. Download Markdown to read or copy the text.\",\n draftTitle: \"Draft\",\n draftNotice:\n \"This page is available by direct link and excluded from navigation and indexes.\",\n searchError: \"Search could not load. Close and try again.\",\n appearance: \"Appearance\",\n colorScheme: \"Color scheme\",\n contrast: \"Contrast\",\n normalContrast: \"Normal\",\n highContrast: \"High\",\n auto: \"Auto\",\n closeNavigation: \"Close navigation\",\n primary: \"Primary\",\n sections: \"Sections\",\n chooseSection: \"Choose section\",\n headerLinks: \"Header links\",\n more: \"More\",\n closeMore: \"Close more menu\",\n moreLinks: \"More links\",\n versions: \"Versions\",\n documentationVersion: \"Documentation version\",\n documentationVersions: \"Documentation versions\",\n version: \"Version\",\n home: \"Home\",\n generatedWith: \"Generated with\",\n viewSource: \"View source\",\n copyCode: \"Copy code\",\n codeCopied: \"Code copied\",\n codeCopyError: \"Could not copy code\",\n copyHeading: \"Copy link to “{heading}”\",\n linkCopied: \"Link copied\",\n linkCopyError: \"Could not copy link\",\n notFound: \"Page not found\",\n notFoundDescription: \"The page you requested could not be found.\",\n fallback: \"fallback\",\n} as const;\nexport type MessageKey = keyof typeof ENGLISH_MESSAGES;\nconst FRENCH_MESSAGES: Record<MessageKey, string> = {\n copyPage: \"Copier la page\",\n viewMarkdown: \"Télécharger le Markdown\",\n pageCopied: \"Page copiée\",\n pageCopyError:\n \"Copie impossible. Téléchargez le Markdown pour lire ou copier le texte.\",\n draftTitle: \"Brouillon\",\n draftNotice:\n \"Cette page est accessible par son lien direct et exclue de la navigation et des index.\",\n searchError: \"La recherche n’a pas pu se charger. Fermez et réessayez.\",\n appearance: \"Apparence\",\n colorScheme: \"Thème de couleur\",\n contrast: \"Contraste\",\n normalContrast: \"Normal\",\n highContrast: \"Élevé\",\n auto: \"Auto\",\n closeNavigation: \"Fermer la navigation\",\n primary: \"Navigation principale\",\n sections: \"Sections\",\n chooseSection: \"Choisir une section\",\n headerLinks: \"Liens d’en-tête\",\n more: \"Plus\",\n closeMore: \"Fermer le menu\",\n moreLinks: \"Autres liens\",\n versions: \"Versions\",\n documentationVersion: \"Version de la documentation\",\n documentationVersions: \"Versions de la documentation\",\n version: \"Version\",\n home: \"Accueil\",\n generatedWith: \"Créé avec\",\n viewSource: \"Voir le code source\",\n copyCode: \"Copier le code\",\n codeCopied: \"Code copié\",\n codeCopyError: \"Impossible de copier le code\",\n copyHeading: \"Copier le lien vers « {heading} »\",\n linkCopied: \"Lien copié\",\n linkCopyError: \"Impossible de copier le lien\",\n notFound: \"Page introuvable\",\n notFoundDescription: \"La page demandée est introuvable.\",\n fallback: \"langue de secours\",\n};\nexport function messages(\n lang: string,\n options: LocaleOptions = {},\n): Record<MessageKey, string> {\n const language = lang.split(\"-\")[0] ?? \"en\";\n return {\n ...ENGLISH_MESSAGES,\n ...(language === \"fr\" ? FRENCH_MESSAGES : {}),\n ...options.translations?.[language],\n ...options.translations?.[lang],\n };\n}\n\nexport function localeAlternates(\n path: string,\n routes: DocsRoute[],\n options: LocaleOptions,\n): Array<{ lang: string; route: string }> {\n const available = new Set(\n routes\n .filter((route) => route.discoverable !== false)\n .map((route) => route.route),\n );\n if (!options.locales || !available.has(path)) return [];\n const alternates = Object.entries(options.locales).flatMap(\n ([locale, config]) => {\n const route = localizedRoute(path, locale, options);\n return available.has(route)\n ? [{ lang: config.lang ?? locale, route }]\n : [];\n },\n );\n const fallback = localizedRoute(\n path,\n options.defaultLocale ??\n (options.locales.root ? \"root\" : Object.keys(options.locales)[0]!),\n options,\n );\n if (available.has(fallback))\n alternates.push({ lang: \"x-default\", route: fallback });\n return alternates;\n}\n"],"mappings":"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"}
@@ -107,6 +107,14 @@ const ui = messages(Astro.locals.cookbookRoute?.lang ?? "en", content);
107
107
  >
108
108
  <ThemeSelect />
109
109
  </div>
110
+ {
111
+ !Astro.locals.cookbookRoute.hasSidebar &&
112
+ Object.keys(content.locales ?? {}).length > 1 && (
113
+ <div class="td-header__mobile-language cookbook-print-hidden">
114
+ <LanguageSelect compact />
115
+ </div>
116
+ )
117
+ }
110
118
  <div
111
119
  class="td-header__tools cookbook-hidden md:cookbook-flex cookbook-print-hidden"
112
120
  >
@@ -2,10 +2,16 @@
2
2
  import { content } from "virtual:cookbook/config";
3
3
  import { localeTarget, routeLocale, messages } from "../localization.js";
4
4
  import { navigationPath } from "../navigation.js";
5
- import Icon from "../components/Icon.astro";
5
+ import SelectPopover from "../components/SelectPopover.astro";
6
+ interface Props {
7
+ compact?: boolean;
8
+ sidebar?: boolean;
9
+ }
10
+ const { compact = false, sidebar = false } = Astro.props;
6
11
  const current = navigationPath(Astro.url.pathname, content.base);
7
12
  const selected = routeLocale(current, content);
8
13
  const ui = messages(Astro.locals.cookbookRoute.lang, content);
14
+ const accessibleLabel = Astro.locals.t("languageSelect.accessibleLabel");
9
15
  const options = Object.entries(content.locales ?? {}).flatMap(
10
16
  ([locale, config]) => {
11
17
  const target = localeTarget(current, locale, content.routes, content);
@@ -14,50 +20,38 @@ const options = Object.entries(content.locales ?? {}).flatMap(
14
20
  ? [
15
21
  {
16
22
  locale,
17
- label: `${config.label}${fallback ? ` (${ui.fallback})` : ""}`,
23
+ label: config.label,
24
+ lang: config.lang ?? (locale === "root" ? "en" : locale),
25
+ fallback,
18
26
  href: `${content.base.replace(/\/$/, "")}${target === "/" ? "/" : `${target}/`}`,
19
27
  },
20
28
  ]
21
29
  : [];
22
30
  },
23
31
  );
32
+ const selectedOption = options.find((option) => option.locale === selected);
24
33
  ---
25
34
 
26
35
  {
27
36
  options.length > 1 && (
28
- <cookbook-language-select>
29
- <label>
30
- <Icon name="translate" class="label-icon" />
31
- <span class="sr-only">
32
- {Astro.locals.t("languageSelect.accessibleLabel")}
33
- </span>
34
- <select>
35
- {options.map((option) => (
36
- <option value={option.href} selected={option.locale === selected}>
37
- {option.label}
38
- </option>
39
- ))}
40
- </select>
41
- <Icon name="down-caret" class="caret" />
42
- </label>
37
+ <cookbook-language-select
38
+ data-compact={compact ? "" : undefined}
39
+ data-sidebar={sidebar ? "" : undefined}
40
+ data-tasty-anatomy="LanguageSelect"
41
+ >
42
+ <SelectPopover
43
+ triggerLabel={`${accessibleLabel}: ${selectedOption?.label ?? ""}`}
44
+ selectedLabel={selectedOption?.label ?? ""}
45
+ title={accessibleLabel}
46
+ icon="translate"
47
+ options={options.map((option) => ({
48
+ href: option.href,
49
+ label: option.label,
50
+ lang: option.lang,
51
+ hint: option.fallback ? ui.fallback : undefined,
52
+ current: option.locale === selected,
53
+ }))}
54
+ />
43
55
  </cookbook-language-select>
44
56
  )
45
57
  }
46
-
47
- <script>
48
- class CookbookLanguageSelect extends HTMLElement {
49
- connectedCallback() {
50
- const select = this.querySelector("select");
51
- select?.addEventListener("change", () => {
52
- window.location.href = select.value;
53
- });
54
- window.addEventListener("pageshow", () => {
55
- if (select)
56
- select.selectedIndex =
57
- select.querySelector("option[selected]")?.index ?? 0;
58
- });
59
- }
60
- }
61
- if (!customElements.get("cookbook-language-select"))
62
- customElements.define("cookbook-language-select", CookbookLanguageSelect);
63
- </script>
@@ -11,5 +11,5 @@ import { MobileMenuFooterRoot } from "../components/LayoutComponents.js";
11
11
  <div class="td-mobile-preferences__social">
12
12
  <SocialIcons />
13
13
  </div>
14
- <LanguageSelect />
14
+ <LanguageSelect sidebar />
15
15
  </MobileMenuFooterRoot>
@@ -82,6 +82,7 @@ const contrasts = [
82
82
  parseScheme,
83
83
  parseContrast,
84
84
  } from "../components/appearance-preferences.js";
85
+ import { initializePopoverMotion } from "../client/popover-motion.js";
85
86
 
86
87
  let current = loadPreferences();
87
88
  const systemScheme = matchMedia("(prefers-color-scheme: light)");
@@ -127,6 +128,7 @@ const contrasts = [
127
128
  connectedCallback(): void {
128
129
  if (this.dataset.ready) return;
129
130
  this.dataset.ready = "true";
131
+ initializePopoverMotion(this.querySelector<HTMLElement>("[popover]")!);
130
132
  this.addEventListener("change", (event) => {
131
133
  const input = event.target;
132
134
  if (!(input instanceof HTMLInputElement)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/renderer",
3
- "version": "0.20.4",
3
+ "version": "0.20.5",
4
4
  "description": "Astro renderer and Tasty theme for Cookbook",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -61,7 +61,7 @@
61
61
  "@fontsource-variable/jetbrains-mono": "^5.3.0",
62
62
  "@fontsource-variable/onest": "^5.3.0",
63
63
  "@pagefind/default-ui": "1.5.2",
64
- "@tenphi/docs": "0.20.4",
64
+ "@tenphi/docs": "0.20.5",
65
65
  "@tenphi/eslint-plugin-tasty": "1.1.0",
66
66
  "@tenphi/glaze": "2.0.0",
67
67
  "@tenphi/tasty": "3.8.0",