nimbus-docs 0.1.7 → 0.1.9

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/dist/client.js CHANGED
@@ -111,9 +111,10 @@ const FOCUSABLE = "a[href], button:not([disabled]), input:not([disabled]):not([t
111
111
  * Used by: Tabs.astro, PackageManagers.astro
112
112
  */
113
113
  function initTabs(config) {
114
- const { container, tabSelector, panelSelector, indicator = null, rovingTabindex = true, sync, onActivate } = config;
115
- const tabs = Array.from(container.querySelectorAll(tabSelector));
116
- const panels = Array.from(container.querySelectorAll(panelSelector));
114
+ const { container, tabSelector, panelSelector, boundarySelector, indicator = null, rovingTabindex = true, sync, onActivate } = config;
115
+ const owned = (el) => !boundarySelector || el.closest(boundarySelector) === container;
116
+ const tabs = Array.from(container.querySelectorAll(tabSelector)).filter(owned);
117
+ const panels = Array.from(container.querySelectorAll(panelSelector)).filter(owned);
117
118
  const tablist = container.querySelector("[role=tablist]") ?? container;
118
119
  let currentIndex = -1;
119
120
  let isInitialActivation = true;
@@ -341,6 +342,32 @@ function codeCopy() {
341
342
  //#endregion
342
343
  //#region src/client/heading-anchors.ts
343
344
  /** Add hoverable self-links to markdown headings with ids. */
345
+ let liveRegion = null;
346
+ /**
347
+ * Singleton polite live region for the "copied" announcement.
348
+ * Visually hidden via inline styles so the framework doesn't depend on
349
+ * a starter utility class.
350
+ */
351
+ function announce(message) {
352
+ if (!liveRegion || !liveRegion.isConnected) {
353
+ liveRegion = document.createElement("div");
354
+ liveRegion.setAttribute("aria-live", "polite");
355
+ Object.assign(liveRegion.style, {
356
+ position: "absolute",
357
+ width: "1px",
358
+ height: "1px",
359
+ padding: "0",
360
+ margin: "-1px",
361
+ overflow: "hidden",
362
+ clipPath: "inset(50%)",
363
+ whiteSpace: "nowrap",
364
+ border: "0"
365
+ });
366
+ document.body.appendChild(liveRegion);
367
+ }
368
+ liveRegion.textContent = "";
369
+ liveRegion.textContent = message;
370
+ }
344
371
  function applyHeadingAnchors() {
345
372
  document.querySelectorAll(".docs-content :is(h2, h3, h4)[id]").forEach((heading) => {
346
373
  if (heading.hasAttribute("data-heading-anchor-ready")) return;
@@ -348,15 +375,20 @@ function applyHeadingAnchors() {
348
375
  const link = document.createElement("a");
349
376
  link.href = `#${heading.id}`;
350
377
  link.className = "heading-anchor";
351
- link.setAttribute("aria-label", `Link to ${heading.textContent?.trim() ?? "section"}`);
378
+ link.setAttribute("aria-label", `Copy link to ${heading.textContent?.trim() ?? "section"}`);
352
379
  link.textContent = "#";
380
+ link.addEventListener("click", () => {
381
+ const url = new URL(link.getAttribute("href") ?? `#${heading.id}`, location.href).href;
382
+ navigator.clipboard?.writeText(url).then(() => announce("Link copied to clipboard"), () => {});
383
+ });
353
384
  heading.appendChild(link);
354
385
  });
355
386
  }
356
387
  /**
357
388
  * Add hoverable `#` self-links to all `h2`–`h4` headings in `.docs-content`.
358
- * Re-runs on `astro:page-load` for View Transitions. Call once (e.g. from
359
- * BaseLayout).
389
+ * Clicking one navigates (hash) *and* copies the absolute deep link, with an
390
+ * `aria-live` announcement for screen readers. Re-runs on `astro:page-load`
391
+ * for View Transitions. Call once (e.g. from BaseLayout).
360
392
  */
361
393
  function headingAnchors() {
362
394
  applyHeadingAnchors();
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client/mount.ts","../src/client/ids.ts","../src/client/disclosure.ts","../src/client/dom.ts","../src/client/tabs-controller.ts","../src/client/scroll-lock.ts","../src/client/code-copy.ts","../src/client/heading-anchors.ts"],"sourcesContent":["/**\n * mount.ts — Discover, mount, and unmount component instances.\n *\n * The single entry point used by every `*.client.ts` to wire its component.\n * Handles three concerns:\n *\n * 1. Initial discovery — finds elements matching `selector` and calls `init`\n * on each, storing the returned teardown.\n * 2. View transitions — on `astro:before-swap`, runs every teardown so\n * document/window listeners come down before the DOM is replaced.\n * 3. Re-mount — on `astro:page-load`, re-runs discovery against the new DOM.\n *\n * The init function receives the root element and returns a `destroy()`\n * function. The root element is the keying mechanism — calling mount again\n * against an already-tracked element is a no-op.\n *\n * Usage:\n *\n * mount(\"[data-nb-collapsible]\", (root) => {\n * const disclosure = makeDisclosure({ ... });\n * return () => disclosure.destroy();\n * });\n */\n\ntype Init = (root: HTMLElement) => () => void;\n\nexport function mount(selector: string, init: Init): void {\n const instances = new Map<HTMLElement, () => void>();\n\n function setup() {\n document.querySelectorAll<HTMLElement>(selector).forEach((el) => {\n if (instances.has(el)) return;\n instances.set(el, init(el));\n });\n }\n\n function teardown() {\n instances.forEach((destroy) => destroy());\n instances.clear();\n }\n\n // Module scripts are deferred, so DOM is parsed by the time this runs.\n // Belt-and-braces check anyway.\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", setup, { once: true });\n } else {\n setup();\n }\n\n document.addEventListener(\"astro:before-swap\", teardown);\n document.addEventListener(\"astro:page-load\", setup);\n}\n","/**\n * Generate a unique ID with a prefix, scoped to the page.\n *\n * Used to build ARIA relationships (`aria-controls` / `aria-labelledby`)\n * between elements that don't have a stable author-provided id.\n */\n\nlet counter = 0;\n\nexport function generateId(prefix: string): string {\n counter += 1;\n return `${prefix}-${counter}`;\n}\n","/**\n * disclosure.ts — Open/close state with ARIA wiring.\n *\n * The shared module for any \"click trigger, reveals content\" pattern.\n * Owns:\n * - open/closed state (in-memory + reflected as `data-nb-state` on both\n * trigger and content)\n * - ARIA: `aria-expanded` on trigger, `aria-controls` linking to content\n * - Click handler on the trigger\n *\n * Animation is intentionally out of scope — CSS targets the `data-nb-state`\n * attribute and runs whatever transition the component wants. Returning\n * a teardown means the caller can clean up on unmount.\n *\n * Used by: Collapsible, and any future Accordion / Sidebar group /\n * dismissable Banner that wants the standard disclosure semantics.\n */\n\nimport { generateId } from \"./ids\";\n\nexport interface DisclosureOptions {\n /** The element users click to toggle. Usually a `<button>`. */\n trigger: HTMLElement;\n /** The element that's shown/hidden. Gets `id` + `data-nb-state`. */\n content: HTMLElement;\n /** Initial open state. Default `false`. */\n defaultOpen?: boolean;\n /** Called whenever open changes. */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport interface DisclosureInstance {\n open(): void;\n close(): void;\n toggle(): void;\n isOpen(): boolean;\n destroy(): void;\n}\n\nexport function makeDisclosure(opts: DisclosureOptions): DisclosureInstance {\n const { trigger, content, defaultOpen = false, onOpenChange } = opts;\n\n let open = defaultOpen;\n\n // Ensure ARIA relationship exists.\n if (!content.id) {\n content.id = generateId(\"nb-disclosure\");\n }\n trigger.setAttribute(\"aria-controls\", content.id);\n\n function syncState() {\n const state = open ? \"open\" : \"closed\";\n trigger.setAttribute(\"data-nb-state\", state);\n content.setAttribute(\"data-nb-state\", state);\n trigger.setAttribute(\"aria-expanded\", String(open));\n }\n\n function setOpen(value: boolean) {\n if (open === value) return;\n open = value;\n syncState();\n onOpenChange?.(value);\n }\n\n function handleClick(e: MouseEvent) {\n e.preventDefault();\n setOpen(!open);\n }\n\n syncState();\n trigger.addEventListener(\"click\", handleClick);\n\n return {\n open() {\n setOpen(true);\n },\n close() {\n setOpen(false);\n },\n toggle() {\n setOpen(!open);\n },\n isOpen() {\n return open;\n },\n destroy() {\n trigger.removeEventListener(\"click\", handleClick);\n },\n };\n}\n","/**\n * Shared DOM constants for client-side interaction primitives.\n */\n\n/** CSS selector for focusable elements within a container. */\nexport const FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([disabled]):not([tabindex=\"-1\"])';\n","/**\n * Shared tab activation primitive.\n *\n * Handles aria-selected, panel visibility, roving tabindex,\n * keyboard navigation, sliding indicator, and cross-instance sync.\n *\n * Used by: Tabs.astro, PackageManagers.astro\n */\n\nimport { FOCUSABLE } from \"./dom\";\n\nexport interface TabsConfig {\n /** Root container holding tabs + panels */\n container: Element;\n /** CSS selector for tab trigger buttons */\n tabSelector: string;\n /** CSS selector for tab panels */\n panelSelector: string;\n /** Optional sliding indicator element */\n indicator?: HTMLElement | null;\n /** Enable roving tabindex + arrow key navigation (default: true) */\n rovingTabindex?: boolean;\n /** Cross-instance persistence config */\n sync?: {\n key: string;\n storage?: \"local\" | \"session\";\n /** Extract sync label from a tab element. Default: textContent.trim() */\n getLabel?: (tab: HTMLElement) => string;\n };\n /** Called after a tab is activated */\n onActivate?: (index: number) => void;\n}\n\nexport interface TabsInstance {\n activate(index: number, options?: { emitSync?: boolean }): void;\n readonly currentIndex: number;\n destroy(): void;\n}\n\nexport function initTabs(config: TabsConfig): TabsInstance {\n const { container, tabSelector, panelSelector, indicator = null, rovingTabindex = true, sync, onActivate } = config;\n\n const tabs = Array.from(container.querySelectorAll<HTMLElement>(tabSelector));\n const panels = Array.from(container.querySelectorAll<HTMLElement>(panelSelector));\n const tablist = container.querySelector(\"[role=tablist]\") ?? container;\n\n let currentIndex = -1;\n let isInitialActivation = true;\n\n function getStorage(kind: \"local\" | \"session\"): Storage | null {\n try {\n return kind === \"session\" ? sessionStorage : localStorage;\n } catch {\n return null;\n }\n }\n\n function getLabel(tab: HTMLElement): string {\n return sync?.getLabel?.(tab) ?? tab.textContent?.trim() ?? \"\";\n }\n\n function updateIndicator(index: number) {\n if (!indicator || !tabs[index]) return;\n indicator.style.left = `${tabs[index].offsetLeft}px`;\n indicator.style.width = `${tabs[index].offsetWidth}px`;\n }\n\n function activate(index: number, options?: { emitSync?: boolean }) {\n const emitSync = options?.emitSync ?? true;\n if (index === currentIndex) return;\n\n // Capture scroll position before DOM changes to prevent layout jump\n const rect = container.getBoundingClientRect();\n const scrollBefore = rect.top;\n\n currentIndex = index;\n\n tabs.forEach((tab, i) => {\n const active = i === index;\n tab.setAttribute(\"aria-selected\", String(active));\n if (rovingTabindex) {\n tab.setAttribute(\"tabindex\", active ? \"0\" : \"-1\");\n }\n });\n\n panels.forEach((panel, i) => {\n const visible = i === index;\n panel.hidden = !visible;\n // Panels with no focusable children need tabindex=\"0\" so keyboard\n // users can Tab into the content (WAI-ARIA Tabs pattern).\n if (visible) {\n const hasFocusable = panel.querySelector(FOCUSABLE) !== null;\n if (!hasFocusable) {\n panel.setAttribute(\"tabindex\", \"0\");\n } else {\n panel.removeAttribute(\"tabindex\");\n }\n }\n });\n\n updateIndicator(index);\n onActivate?.(index);\n\n // Compensate scroll position after panel height change (skip on first paint)\n if (emitSync && !isInitialActivation) {\n const scrollAfter = container.getBoundingClientRect().top;\n const delta = scrollAfter - scrollBefore;\n if (Math.abs(delta) > 1) {\n window.scrollTo({\n top: window.scrollY + delta,\n behavior: \"instant\",\n });\n }\n }\n isInitialActivation = false;\n\n if (sync && emitSync) {\n // `tabs[index]!`: `activate(index)` is only called with validated indices.\n const label = getLabel(tabs[index]!);\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n store?.setItem(sync.key, label);\n window.dispatchEvent(\n new CustomEvent(\"ui-tab-sync\", {\n detail: { key: sync.key, label, origin: container },\n }),\n );\n }\n }\n\n // Resolve initial index from sync storage\n let initialIndex = 0;\n if (sync) {\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n const saved = store?.getItem(sync.key);\n if (saved) {\n const idx = tabs.findIndex((t) => getLabel(t) === saved);\n if (idx >= 0) initialIndex = idx;\n }\n }\n\n // Click delegation on tablist\n function handleClick(e: Event) {\n const target = (e.target as HTMLElement).closest(tabSelector);\n if (!target) return;\n const idx = tabs.indexOf(target as HTMLElement);\n if (idx >= 0) {\n activate(idx);\n if (rovingTabindex) (target as HTMLElement).focus();\n }\n }\n\n // Keyboard navigation (roving tabindex)\n function handleKeydown(e: KeyboardEvent) {\n if (!rovingTabindex) return;\n const ci = tabs.indexOf(e.target as HTMLElement);\n if (ci < 0) return;\n\n let next: number;\n switch (e.key) {\n case \"ArrowRight\":\n next = ci + 1;\n break;\n case \"ArrowLeft\":\n next = ci - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = tabs.length - 1;\n break;\n default:\n return;\n }\n // No-wrap: ignore if out of bounds\n if (!tabs[next]) return;\n e.preventDefault();\n activate(next);\n // `tabs[next]!`: guard above proves the assertion.\n tabs[next]!.focus();\n }\n\n // Cross-instance sync via CustomEvent\n function handleSync(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (detail.key === sync?.key && detail.origin !== container) {\n const idx = tabs.findIndex((t) => getLabel(t) === detail.label);\n if (idx >= 0) activate(idx, { emitSync: false });\n }\n }\n\n // Wire events\n tablist.addEventListener(\"click\", handleClick);\n tablist.addEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.addEventListener(\"ui-tab-sync\", handleSync);\n\n // Initial activation — skip indicator transition for first paint\n if (indicator) indicator.style.transition = \"none\";\n activate(initialIndex);\n if (indicator) {\n void indicator.offsetHeight; // force reflow\n indicator.style.transition = \"\";\n }\n\n return {\n activate,\n get currentIndex() {\n return currentIndex;\n },\n destroy() {\n tablist.removeEventListener(\"click\", handleClick);\n tablist.removeEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.removeEventListener(\"ui-tab-sync\", handleSync);\n },\n };\n}\n","/**\n * scroll-lock.ts — Body scroll lock with scrollbar-width compensation.\n *\n * Prevents background scrolling when a modal/overlay is open.\n * Compensates for the scrollbar disappearing to avoid layout shift\n * (visible on Windows/Linux where scrollbars have width).\n *\n * Uses a data attribute + CSS for the overflow lock, and inline\n * paddingRight for the scrollbar compensation.\n *\n * Used by: Dialog (and any future overlay primitive).\n */\n\nconst ATTR = \"data-scroll-locked\";\n\nlet lockCount = 0;\nlet savedPaddingRight = \"\";\n\nexport function lockScroll(): void {\n lockCount++;\n if (lockCount > 1) return;\n\n const scrollbarW = window.innerWidth - document.documentElement.clientWidth;\n savedPaddingRight = document.body.style.paddingRight;\n\n document.body.setAttribute(ATTR, \"\");\n if (scrollbarW > 0) {\n document.body.style.paddingRight = `${scrollbarW}px`;\n }\n}\n\nexport function unlockScroll(): void {\n if (lockCount === 0) return;\n lockCount--;\n if (lockCount > 0) return;\n\n document.body.removeAttribute(ATTR);\n document.body.style.paddingRight = savedPaddingRight;\n}\n","/**\n * code-copy.ts — Injects a Nimbus-styled copy button into every Shiki\n * code block rendered by Astro's built-in `<Code>` and fenced\n * code blocks in MDX.\n *\n * Astro emits `<pre class=\"astro-code shiki ...\">` for syntax-highlighted\n * blocks. We attach a copy button positioned in the top-right corner.\n *\n * Page-wide enhancement, not tied to a single component. Call `codeCopy()`\n * once (e.g. from BaseLayout). Uses `mount` for lifecycle so the buttons\n * are torn down on view transitions and re-added against the new DOM.\n *\n * The original code text comes from the `<code>` element's textContent —\n * Shiki's wrapper spans flatten down to the raw source.\n *\n * Code blocks rendered inside [data-cg-row] (CodeGroup) are skipped\n * because CodeGroup brings its own copy button per panel.\n */\n\nimport { mount } from \"./mount\";\n\nconst COPY_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"/></svg>`;\nconst CHECK_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34Z\"/></svg>`;\n\nfunction getCodeText(pre: HTMLElement): string {\n const codeEl = pre.querySelector<HTMLElement>(\"code\");\n return codeEl?.textContent ?? pre.textContent ?? \"\";\n}\n\nfunction initCodeCopy(pre: HTMLElement): () => void {\n // Skip code blocks owned by CodeGroup. `data-cg-panels-raw` is SSR'd\n // (catches pres before CodeGroup's client script reparents them);\n // `data-cg-row` catches them after reparenting.\n if (pre.closest(\"[data-cg-panels-raw], [data-cg-row]\")) return () => {};\n\n // Append the button to the figure wrapper, not the pre. The pre has\n // `overflow-x: auto`, and absolutely-positioned children of overflow:auto\n // containers slide along with horizontal scroll on iOS Safari. The figure\n // is the non-scrolling wrapper emitted by titleAndLangTransformer.\n const host = (pre.closest(\".nb-code-figure\") as HTMLElement | null) ?? pre;\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"nb-code-copy\";\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n btn.innerHTML = COPY_ICON;\n\n let resetTimer: number | undefined;\n\n async function handleClick() {\n const text = getCodeText(pre);\n if (!text) return;\n\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n return;\n }\n\n btn.innerHTML = CHECK_ICON;\n btn.dataset.state = \"copied\";\n btn.setAttribute(\"aria-label\", \"Copied!\");\n\n if (resetTimer) window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n btn.innerHTML = COPY_ICON;\n delete btn.dataset.state;\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n }, 1500);\n }\n\n btn.addEventListener(\"click\", handleClick);\n\n if (getComputedStyle(host).position === \"static\") {\n host.style.position = \"relative\";\n }\n host.appendChild(btn);\n\n return () => {\n if (resetTimer) window.clearTimeout(resetTimer);\n btn.removeEventListener(\"click\", handleClick);\n btn.remove();\n };\n}\n\n/** Wire copy buttons onto all Shiki code blocks on the page. Call once. */\nexport function codeCopy(): void {\n mount(\"pre.astro-code\", initCodeCopy);\n}\n","/** Add hoverable self-links to markdown headings with ids. */\n\nfunction applyHeadingAnchors() {\n document.querySelectorAll<HTMLElement>(\".docs-content :is(h2, h3, h4)[id]\").forEach((heading) => {\n if (heading.hasAttribute(\"data-heading-anchor-ready\")) return;\n heading.setAttribute(\"data-heading-anchor-ready\", \"true\");\n\n const link = document.createElement(\"a\");\n link.href = `#${heading.id}`;\n link.className = \"heading-anchor\";\n link.setAttribute(\"aria-label\", `Link to ${heading.textContent?.trim() ?? \"section\"}`);\n link.textContent = \"#\";\n heading.appendChild(link);\n });\n}\n\n/**\n * Add hoverable `#` self-links to all `h2`–`h4` headings in `.docs-content`.\n * Re-runs on `astro:page-load` for View Transitions. Call once (e.g. from\n * BaseLayout).\n */\nexport function headingAnchors(): void {\n applyHeadingAnchors();\n document.addEventListener(\"astro:page-load\", applyHeadingAnchors);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/mount.ts","../src/client/ids.ts","../src/client/disclosure.ts","../src/client/dom.ts","../src/client/tabs-controller.ts","../src/client/scroll-lock.ts","../src/client/code-copy.ts","../src/client/heading-anchors.ts"],"sourcesContent":["/**\n * mount.ts — Discover, mount, and unmount component instances.\n *\n * The single entry point used by every `*.client.ts` to wire its component.\n * Handles three concerns:\n *\n * 1. Initial discovery — finds elements matching `selector` and calls `init`\n * on each, storing the returned teardown.\n * 2. View transitions — on `astro:before-swap`, runs every teardown so\n * document/window listeners come down before the DOM is replaced.\n * 3. Re-mount — on `astro:page-load`, re-runs discovery against the new DOM.\n *\n * The init function receives the root element and returns a `destroy()`\n * function. The root element is the keying mechanism — calling mount again\n * against an already-tracked element is a no-op.\n *\n * Usage:\n *\n * mount(\"[data-nb-collapsible]\", (root) => {\n * const disclosure = makeDisclosure({ ... });\n * return () => disclosure.destroy();\n * });\n */\n\ntype Init = (root: HTMLElement) => () => void;\n\nexport function mount(selector: string, init: Init): void {\n const instances = new Map<HTMLElement, () => void>();\n\n function setup() {\n document.querySelectorAll<HTMLElement>(selector).forEach((el) => {\n if (instances.has(el)) return;\n instances.set(el, init(el));\n });\n }\n\n function teardown() {\n instances.forEach((destroy) => destroy());\n instances.clear();\n }\n\n // Module scripts are deferred, so DOM is parsed by the time this runs.\n // Belt-and-braces check anyway.\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", setup, { once: true });\n } else {\n setup();\n }\n\n document.addEventListener(\"astro:before-swap\", teardown);\n document.addEventListener(\"astro:page-load\", setup);\n}\n","/**\n * Generate a unique ID with a prefix, scoped to the page.\n *\n * Used to build ARIA relationships (`aria-controls` / `aria-labelledby`)\n * between elements that don't have a stable author-provided id.\n */\n\nlet counter = 0;\n\nexport function generateId(prefix: string): string {\n counter += 1;\n return `${prefix}-${counter}`;\n}\n","/**\n * disclosure.ts — Open/close state with ARIA wiring.\n *\n * The shared module for any \"click trigger, reveals content\" pattern.\n * Owns:\n * - open/closed state (in-memory + reflected as `data-nb-state` on both\n * trigger and content)\n * - ARIA: `aria-expanded` on trigger, `aria-controls` linking to content\n * - Click handler on the trigger\n *\n * Animation is intentionally out of scope — CSS targets the `data-nb-state`\n * attribute and runs whatever transition the component wants. Returning\n * a teardown means the caller can clean up on unmount.\n *\n * Used by: Collapsible, and any future Accordion / Sidebar group /\n * dismissable Banner that wants the standard disclosure semantics.\n */\n\nimport { generateId } from \"./ids\";\n\nexport interface DisclosureOptions {\n /** The element users click to toggle. Usually a `<button>`. */\n trigger: HTMLElement;\n /** The element that's shown/hidden. Gets `id` + `data-nb-state`. */\n content: HTMLElement;\n /** Initial open state. Default `false`. */\n defaultOpen?: boolean;\n /** Called whenever open changes. */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport interface DisclosureInstance {\n open(): void;\n close(): void;\n toggle(): void;\n isOpen(): boolean;\n destroy(): void;\n}\n\nexport function makeDisclosure(opts: DisclosureOptions): DisclosureInstance {\n const { trigger, content, defaultOpen = false, onOpenChange } = opts;\n\n let open = defaultOpen;\n\n // Ensure ARIA relationship exists.\n if (!content.id) {\n content.id = generateId(\"nb-disclosure\");\n }\n trigger.setAttribute(\"aria-controls\", content.id);\n\n function syncState() {\n const state = open ? \"open\" : \"closed\";\n trigger.setAttribute(\"data-nb-state\", state);\n content.setAttribute(\"data-nb-state\", state);\n trigger.setAttribute(\"aria-expanded\", String(open));\n }\n\n function setOpen(value: boolean) {\n if (open === value) return;\n open = value;\n syncState();\n onOpenChange?.(value);\n }\n\n function handleClick(e: MouseEvent) {\n e.preventDefault();\n setOpen(!open);\n }\n\n syncState();\n trigger.addEventListener(\"click\", handleClick);\n\n return {\n open() {\n setOpen(true);\n },\n close() {\n setOpen(false);\n },\n toggle() {\n setOpen(!open);\n },\n isOpen() {\n return open;\n },\n destroy() {\n trigger.removeEventListener(\"click\", handleClick);\n },\n };\n}\n","/**\n * Shared DOM constants for client-side interaction primitives.\n */\n\n/** CSS selector for focusable elements within a container. */\nexport const FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([disabled]):not([tabindex=\"-1\"])';\n","/**\n * Shared tab activation primitive.\n *\n * Handles aria-selected, panel visibility, roving tabindex,\n * keyboard navigation, sliding indicator, and cross-instance sync.\n *\n * Used by: Tabs.astro, PackageManagers.astro\n */\n\nimport { FOCUSABLE } from \"./dom\";\n\nexport interface TabsConfig {\n /** Root container holding tabs + panels */\n container: Element;\n /** CSS selector for tab trigger buttons */\n tabSelector: string;\n /** CSS selector for tab panels */\n panelSelector: string;\n /**\n * Selector identifying a tab container. When set, tabs/panels are scoped\n * to this container only — a descendant belongs to it iff its nearest\n * ancestor matching `boundarySelector` is `container` itself. This keeps\n * a nested `<Tabs>`'s triggers/panels out of the parent instance. When\n * unset, all descendants match `tabSelector`/`panelSelector` are used\n * (no nesting support — the prior behaviour).\n */\n boundarySelector?: string;\n /** Optional sliding indicator element */\n indicator?: HTMLElement | null;\n /** Enable roving tabindex + arrow key navigation (default: true) */\n rovingTabindex?: boolean;\n /** Cross-instance persistence config */\n sync?: {\n key: string;\n storage?: \"local\" | \"session\";\n /** Extract sync label from a tab element. Default: textContent.trim() */\n getLabel?: (tab: HTMLElement) => string;\n };\n /** Called after a tab is activated */\n onActivate?: (index: number) => void;\n}\n\nexport interface TabsInstance {\n activate(index: number, options?: { emitSync?: boolean }): void;\n readonly currentIndex: number;\n destroy(): void;\n}\n\nexport function initTabs(config: TabsConfig): TabsInstance {\n const { container, tabSelector, panelSelector, boundarySelector, indicator = null, rovingTabindex = true, sync, onActivate } = config;\n\n // A descendant belongs to this container only if no nested tab container\n // sits between it and `container` — i.e. its nearest `boundarySelector`\n // ancestor is `container` itself. Without a boundary selector, every\n // match counts (nesting unsupported).\n const owned = (el: HTMLElement) => !boundarySelector || el.closest(boundarySelector) === container;\n const tabs = Array.from(container.querySelectorAll<HTMLElement>(tabSelector)).filter(owned);\n const panels = Array.from(container.querySelectorAll<HTMLElement>(panelSelector)).filter(owned);\n const tablist = container.querySelector(\"[role=tablist]\") ?? container;\n\n let currentIndex = -1;\n let isInitialActivation = true;\n\n function getStorage(kind: \"local\" | \"session\"): Storage | null {\n try {\n return kind === \"session\" ? sessionStorage : localStorage;\n } catch {\n return null;\n }\n }\n\n function getLabel(tab: HTMLElement): string {\n return sync?.getLabel?.(tab) ?? tab.textContent?.trim() ?? \"\";\n }\n\n function updateIndicator(index: number) {\n if (!indicator || !tabs[index]) return;\n indicator.style.left = `${tabs[index].offsetLeft}px`;\n indicator.style.width = `${tabs[index].offsetWidth}px`;\n }\n\n function activate(index: number, options?: { emitSync?: boolean }) {\n const emitSync = options?.emitSync ?? true;\n if (index === currentIndex) return;\n\n // Capture scroll position before DOM changes to prevent layout jump\n const rect = container.getBoundingClientRect();\n const scrollBefore = rect.top;\n\n currentIndex = index;\n\n tabs.forEach((tab, i) => {\n const active = i === index;\n tab.setAttribute(\"aria-selected\", String(active));\n if (rovingTabindex) {\n tab.setAttribute(\"tabindex\", active ? \"0\" : \"-1\");\n }\n });\n\n panels.forEach((panel, i) => {\n const visible = i === index;\n panel.hidden = !visible;\n // Panels with no focusable children need tabindex=\"0\" so keyboard\n // users can Tab into the content (WAI-ARIA Tabs pattern).\n if (visible) {\n const hasFocusable = panel.querySelector(FOCUSABLE) !== null;\n if (!hasFocusable) {\n panel.setAttribute(\"tabindex\", \"0\");\n } else {\n panel.removeAttribute(\"tabindex\");\n }\n }\n });\n\n updateIndicator(index);\n onActivate?.(index);\n\n // Compensate scroll position after panel height change (skip on first paint)\n if (emitSync && !isInitialActivation) {\n const scrollAfter = container.getBoundingClientRect().top;\n const delta = scrollAfter - scrollBefore;\n if (Math.abs(delta) > 1) {\n window.scrollTo({\n top: window.scrollY + delta,\n behavior: \"instant\",\n });\n }\n }\n isInitialActivation = false;\n\n if (sync && emitSync) {\n // `tabs[index]!`: `activate(index)` is only called with validated indices.\n const label = getLabel(tabs[index]!);\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n store?.setItem(sync.key, label);\n window.dispatchEvent(\n new CustomEvent(\"ui-tab-sync\", {\n detail: { key: sync.key, label, origin: container },\n }),\n );\n }\n }\n\n // Resolve initial index from sync storage\n let initialIndex = 0;\n if (sync) {\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n const saved = store?.getItem(sync.key);\n if (saved) {\n const idx = tabs.findIndex((t) => getLabel(t) === saved);\n if (idx >= 0) initialIndex = idx;\n }\n }\n\n // Click delegation on tablist\n function handleClick(e: Event) {\n const target = (e.target as HTMLElement).closest(tabSelector);\n if (!target) return;\n const idx = tabs.indexOf(target as HTMLElement);\n if (idx >= 0) {\n activate(idx);\n if (rovingTabindex) (target as HTMLElement).focus();\n }\n }\n\n // Keyboard navigation (roving tabindex)\n function handleKeydown(e: KeyboardEvent) {\n if (!rovingTabindex) return;\n const ci = tabs.indexOf(e.target as HTMLElement);\n if (ci < 0) return;\n\n let next: number;\n switch (e.key) {\n case \"ArrowRight\":\n next = ci + 1;\n break;\n case \"ArrowLeft\":\n next = ci - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = tabs.length - 1;\n break;\n default:\n return;\n }\n // No-wrap: ignore if out of bounds\n if (!tabs[next]) return;\n e.preventDefault();\n activate(next);\n // `tabs[next]!`: guard above proves the assertion.\n tabs[next]!.focus();\n }\n\n // Cross-instance sync via CustomEvent\n function handleSync(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (detail.key === sync?.key && detail.origin !== container) {\n const idx = tabs.findIndex((t) => getLabel(t) === detail.label);\n if (idx >= 0) activate(idx, { emitSync: false });\n }\n }\n\n // Wire events\n tablist.addEventListener(\"click\", handleClick);\n tablist.addEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.addEventListener(\"ui-tab-sync\", handleSync);\n\n // Initial activation — skip indicator transition for first paint\n if (indicator) indicator.style.transition = \"none\";\n activate(initialIndex);\n if (indicator) {\n void indicator.offsetHeight; // force reflow\n indicator.style.transition = \"\";\n }\n\n return {\n activate,\n get currentIndex() {\n return currentIndex;\n },\n destroy() {\n tablist.removeEventListener(\"click\", handleClick);\n tablist.removeEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.removeEventListener(\"ui-tab-sync\", handleSync);\n },\n };\n}\n","/**\n * scroll-lock.ts — Body scroll lock with scrollbar-width compensation.\n *\n * Prevents background scrolling when a modal/overlay is open.\n * Compensates for the scrollbar disappearing to avoid layout shift\n * (visible on Windows/Linux where scrollbars have width).\n *\n * Uses a data attribute + CSS for the overflow lock, and inline\n * paddingRight for the scrollbar compensation.\n *\n * Used by: Dialog (and any future overlay primitive).\n */\n\nconst ATTR = \"data-scroll-locked\";\n\nlet lockCount = 0;\nlet savedPaddingRight = \"\";\n\nexport function lockScroll(): void {\n lockCount++;\n if (lockCount > 1) return;\n\n const scrollbarW = window.innerWidth - document.documentElement.clientWidth;\n savedPaddingRight = document.body.style.paddingRight;\n\n document.body.setAttribute(ATTR, \"\");\n if (scrollbarW > 0) {\n document.body.style.paddingRight = `${scrollbarW}px`;\n }\n}\n\nexport function unlockScroll(): void {\n if (lockCount === 0) return;\n lockCount--;\n if (lockCount > 0) return;\n\n document.body.removeAttribute(ATTR);\n document.body.style.paddingRight = savedPaddingRight;\n}\n","/**\n * code-copy.ts — Injects a Nimbus-styled copy button into every Shiki\n * code block rendered by Astro's built-in `<Code>` and fenced\n * code blocks in MDX.\n *\n * Astro emits `<pre class=\"astro-code shiki ...\">` for syntax-highlighted\n * blocks. We attach a copy button positioned in the top-right corner.\n *\n * Page-wide enhancement, not tied to a single component. Call `codeCopy()`\n * once (e.g. from BaseLayout). Uses `mount` for lifecycle so the buttons\n * are torn down on view transitions and re-added against the new DOM.\n *\n * The original code text comes from the `<code>` element's textContent —\n * Shiki's wrapper spans flatten down to the raw source.\n *\n * Code blocks rendered inside [data-cg-row] (CodeGroup) are skipped\n * because CodeGroup brings its own copy button per panel.\n */\n\nimport { mount } from \"./mount\";\n\nconst COPY_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"/></svg>`;\nconst CHECK_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34Z\"/></svg>`;\n\nfunction getCodeText(pre: HTMLElement): string {\n const codeEl = pre.querySelector<HTMLElement>(\"code\");\n return codeEl?.textContent ?? pre.textContent ?? \"\";\n}\n\nfunction initCodeCopy(pre: HTMLElement): () => void {\n // Skip code blocks owned by CodeGroup. `data-cg-panels-raw` is SSR'd\n // (catches pres before CodeGroup's client script reparents them);\n // `data-cg-row` catches them after reparenting.\n if (pre.closest(\"[data-cg-panels-raw], [data-cg-row]\")) return () => {};\n\n // Append the button to the figure wrapper, not the pre. The pre has\n // `overflow-x: auto`, and absolutely-positioned children of overflow:auto\n // containers slide along with horizontal scroll on iOS Safari. The figure\n // is the non-scrolling wrapper emitted by titleAndLangTransformer.\n const host = (pre.closest(\".nb-code-figure\") as HTMLElement | null) ?? pre;\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"nb-code-copy\";\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n btn.innerHTML = COPY_ICON;\n\n let resetTimer: number | undefined;\n\n async function handleClick() {\n const text = getCodeText(pre);\n if (!text) return;\n\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n return;\n }\n\n btn.innerHTML = CHECK_ICON;\n btn.dataset.state = \"copied\";\n btn.setAttribute(\"aria-label\", \"Copied!\");\n\n if (resetTimer) window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n btn.innerHTML = COPY_ICON;\n delete btn.dataset.state;\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n }, 1500);\n }\n\n btn.addEventListener(\"click\", handleClick);\n\n if (getComputedStyle(host).position === \"static\") {\n host.style.position = \"relative\";\n }\n host.appendChild(btn);\n\n return () => {\n if (resetTimer) window.clearTimeout(resetTimer);\n btn.removeEventListener(\"click\", handleClick);\n btn.remove();\n };\n}\n\n/** Wire copy buttons onto all Shiki code blocks on the page. Call once. */\nexport function codeCopy(): void {\n mount(\"pre.astro-code\", initCodeCopy);\n}\n","/** Add hoverable self-links to markdown headings with ids. */\n\nlet liveRegion: HTMLElement | null = null;\n\n/**\n * Singleton polite live region for the \"copied\" announcement.\n * Visually hidden via inline styles so the framework doesn't depend on\n * a starter utility class.\n */\nfunction announce(message: string) {\n if (!liveRegion || !liveRegion.isConnected) {\n liveRegion = document.createElement(\"div\");\n liveRegion.setAttribute(\"aria-live\", \"polite\");\n Object.assign(liveRegion.style, {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n padding: \"0\",\n margin: \"-1px\",\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: \"0\",\n });\n document.body.appendChild(liveRegion);\n }\n // Clear first so repeating the same message re-announces.\n liveRegion.textContent = \"\";\n liveRegion.textContent = message;\n}\n\nfunction applyHeadingAnchors() {\n document.querySelectorAll<HTMLElement>(\".docs-content :is(h2, h3, h4)[id]\").forEach((heading) => {\n if (heading.hasAttribute(\"data-heading-anchor-ready\")) return;\n heading.setAttribute(\"data-heading-anchor-ready\", \"true\");\n\n const link = document.createElement(\"a\");\n link.href = `#${heading.id}`;\n link.className = \"heading-anchor\";\n link.setAttribute(\"aria-label\", `Copy link to ${heading.textContent?.trim() ?? \"section\"}`);\n link.textContent = \"#\";\n\n // Copy the deep link on click. Default navigation is preserved so\n // the URL bar updates; the clipboard write rides alongside. No-op\n // outside secure contexts.\n link.addEventListener(\"click\", () => {\n const url = new URL(link.getAttribute(\"href\") ?? `#${heading.id}`, location.href).href;\n navigator.clipboard?.writeText(url).then(\n () => announce(\"Link copied to clipboard\"),\n () => {},\n );\n });\n\n heading.appendChild(link);\n });\n}\n\n/**\n * Add hoverable `#` self-links to all `h2`–`h4` headings in `.docs-content`.\n * Clicking one navigates (hash) *and* copies the absolute deep link, with an\n * `aria-live` announcement for screen readers. Re-runs on `astro:page-load`\n * for View Transitions. Call once (e.g. from BaseLayout).\n */\nexport function headingAnchors(): void {\n applyHeadingAnchors();\n document.addEventListener(\"astro:page-load\", applyHeadingAnchors);\n}\n"],"mappings":";AA0BA,SAAgB,MAAM,UAAkB,MAAkB;CACxD,MAAM,4BAAY,IAAI,KAA8B;CAEpD,SAAS,QAAQ;AACf,WAAS,iBAA8B,SAAS,CAAC,SAAS,OAAO;AAC/D,OAAI,UAAU,IAAI,GAAG,CAAE;AACvB,aAAU,IAAI,IAAI,KAAK,GAAG,CAAC;IAC3B;;CAGJ,SAAS,WAAW;AAClB,YAAU,SAAS,YAAY,SAAS,CAAC;AACzC,YAAU,OAAO;;AAKnB,KAAI,SAAS,eAAe,UAC1B,UAAS,iBAAiB,oBAAoB,OAAO,EAAE,MAAM,MAAM,CAAC;KAEpE,QAAO;AAGT,UAAS,iBAAiB,qBAAqB,SAAS;AACxD,UAAS,iBAAiB,mBAAmB,MAAM;;;;;;;;;;;AC3CrD,IAAI,UAAU;AAEd,SAAgB,WAAW,QAAwB;AACjD,YAAW;AACX,QAAO,GAAG,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;AC4BtB,SAAgB,eAAe,MAA6C;CAC1E,MAAM,EAAE,SAAS,SAAS,cAAc,OAAO,iBAAiB;CAEhE,IAAI,OAAO;AAGX,KAAI,CAAC,QAAQ,GACX,SAAQ,KAAK,WAAW,gBAAgB;AAE1C,SAAQ,aAAa,iBAAiB,QAAQ,GAAG;CAEjD,SAAS,YAAY;EACnB,MAAM,QAAQ,OAAO,SAAS;AAC9B,UAAQ,aAAa,iBAAiB,MAAM;AAC5C,UAAQ,aAAa,iBAAiB,MAAM;AAC5C,UAAQ,aAAa,iBAAiB,OAAO,KAAK,CAAC;;CAGrD,SAAS,QAAQ,OAAgB;AAC/B,MAAI,SAAS,MAAO;AACpB,SAAO;AACP,aAAW;AACX,iBAAe,MAAM;;CAGvB,SAAS,YAAY,GAAe;AAClC,IAAE,gBAAgB;AAClB,UAAQ,CAAC,KAAK;;AAGhB,YAAW;AACX,SAAQ,iBAAiB,SAAS,YAAY;AAE9C,QAAO;EACL,OAAO;AACL,WAAQ,KAAK;;EAEf,QAAQ;AACN,WAAQ,MAAM;;EAEhB,SAAS;AACP,WAAQ,CAAC,KAAK;;EAEhB,SAAS;AACP,UAAO;;EAET,UAAU;AACR,WAAQ,oBAAoB,SAAS,YAAY;;EAEpD;;;;;;;;;ACnFH,MAAa,YACX;;;;;;;;;;;;AC0CF,SAAgB,SAAS,QAAkC;CACzD,MAAM,EAAE,WAAW,aAAa,eAAe,kBAAkB,YAAY,MAAM,iBAAiB,MAAM,MAAM,eAAe;CAM/H,MAAM,SAAS,OAAoB,CAAC,oBAAoB,GAAG,QAAQ,iBAAiB,KAAK;CACzF,MAAM,OAAO,MAAM,KAAK,UAAU,iBAA8B,YAAY,CAAC,CAAC,OAAO,MAAM;CAC3F,MAAM,SAAS,MAAM,KAAK,UAAU,iBAA8B,cAAc,CAAC,CAAC,OAAO,MAAM;CAC/F,MAAM,UAAU,UAAU,cAAc,iBAAiB,IAAI;CAE7D,IAAI,eAAe;CACnB,IAAI,sBAAsB;CAE1B,SAAS,WAAW,MAA2C;AAC7D,MAAI;AACF,UAAO,SAAS,YAAY,iBAAiB;UACvC;AACN,UAAO;;;CAIX,SAAS,SAAS,KAA0B;AAC1C,SAAO,MAAM,WAAW,IAAI,IAAI,IAAI,aAAa,MAAM,IAAI;;CAG7D,SAAS,gBAAgB,OAAe;AACtC,MAAI,CAAC,aAAa,CAAC,KAAK,OAAQ;AAChC,YAAU,MAAM,OAAO,GAAG,KAAK,OAAO,WAAW;AACjD,YAAU,MAAM,QAAQ,GAAG,KAAK,OAAO,YAAY;;CAGrD,SAAS,SAAS,OAAe,SAAkC;EACjE,MAAM,WAAW,SAAS,YAAY;AACtC,MAAI,UAAU,aAAc;EAI5B,MAAM,eADO,UAAU,uBAAuB,CACpB;AAE1B,iBAAe;AAEf,OAAK,SAAS,KAAK,MAAM;GACvB,MAAM,SAAS,MAAM;AACrB,OAAI,aAAa,iBAAiB,OAAO,OAAO,CAAC;AACjD,OAAI,eACF,KAAI,aAAa,YAAY,SAAS,MAAM,KAAK;IAEnD;AAEF,SAAO,SAAS,OAAO,MAAM;GAC3B,MAAM,UAAU,MAAM;AACtB,SAAM,SAAS,CAAC;AAGhB,OAAI,QAEF,KAAI,EADiB,MAAM,cAAc,UAAU,KAAK,MAEtD,OAAM,aAAa,YAAY,IAAI;OAEnC,OAAM,gBAAgB,WAAW;IAGrC;AAEF,kBAAgB,MAAM;AACtB,eAAa,MAAM;AAGnB,MAAI,YAAY,CAAC,qBAAqB;GAEpC,MAAM,QADc,UAAU,uBAAuB,CAAC,MAC1B;AAC5B,OAAI,KAAK,IAAI,MAAM,GAAG,EACpB,QAAO,SAAS;IACd,KAAK,OAAO,UAAU;IACtB,UAAU;IACX,CAAC;;AAGN,wBAAsB;AAEtB,MAAI,QAAQ,UAAU;GAEpB,MAAM,QAAQ,SAAS,KAAK,OAAQ;AAEpC,GADc,WAAW,KAAK,YAAY,YAAY,YAAY,QAAQ,EACnE,QAAQ,KAAK,KAAK,MAAM;AAC/B,UAAO,cACL,IAAI,YAAY,eAAe,EAC7B,QAAQ;IAAE,KAAK,KAAK;IAAK;IAAO,QAAQ;IAAW,EACpD,CAAC,CACH;;;CAKL,IAAI,eAAe;AACnB,KAAI,MAAM;EAER,MAAM,QADQ,WAAW,KAAK,YAAY,YAAY,YAAY,QAAQ,EACrD,QAAQ,KAAK,IAAI;AACtC,MAAI,OAAO;GACT,MAAM,MAAM,KAAK,WAAW,MAAM,SAAS,EAAE,KAAK,MAAM;AACxD,OAAI,OAAO,EAAG,gBAAe;;;CAKjC,SAAS,YAAY,GAAU;EAC7B,MAAM,SAAU,EAAE,OAAuB,QAAQ,YAAY;AAC7D,MAAI,CAAC,OAAQ;EACb,MAAM,MAAM,KAAK,QAAQ,OAAsB;AAC/C,MAAI,OAAO,GAAG;AACZ,YAAS,IAAI;AACb,OAAI,eAAgB,CAAC,OAAuB,OAAO;;;CAKvD,SAAS,cAAc,GAAkB;AACvC,MAAI,CAAC,eAAgB;EACrB,MAAM,KAAK,KAAK,QAAQ,EAAE,OAAsB;AAChD,MAAI,KAAK,EAAG;EAEZ,IAAI;AACJ,UAAQ,EAAE,KAAV;GACE,KAAK;AACH,WAAO,KAAK;AACZ;GACF,KAAK;AACH,WAAO,KAAK;AACZ;GACF,KAAK;AACH,WAAO;AACP;GACF,KAAK;AACH,WAAO,KAAK,SAAS;AACrB;GACF,QACE;;AAGJ,MAAI,CAAC,KAAK,MAAO;AACjB,IAAE,gBAAgB;AAClB,WAAS,KAAK;AAEd,OAAK,MAAO,OAAO;;CAIrB,SAAS,WAAW,GAAU;EAC5B,MAAM,SAAU,EAAkB;AAClC,MAAI,OAAO,QAAQ,MAAM,OAAO,OAAO,WAAW,WAAW;GAC3D,MAAM,MAAM,KAAK,WAAW,MAAM,SAAS,EAAE,KAAK,OAAO,MAAM;AAC/D,OAAI,OAAO,EAAG,UAAS,KAAK,EAAE,UAAU,OAAO,CAAC;;;AAKpD,SAAQ,iBAAiB,SAAS,YAAY;AAC9C,SAAQ,iBAAiB,WAAW,cAA+B;AACnE,KAAI,KAAM,QAAO,iBAAiB,eAAe,WAAW;AAG5D,KAAI,UAAW,WAAU,MAAM,aAAa;AAC5C,UAAS,aAAa;AACtB,KAAI,WAAW;AACb,EAAK,UAAU;AACf,YAAU,MAAM,aAAa;;AAG/B,QAAO;EACL;EACA,IAAI,eAAe;AACjB,UAAO;;EAET,UAAU;AACR,WAAQ,oBAAoB,SAAS,YAAY;AACjD,WAAQ,oBAAoB,WAAW,cAA+B;AACtE,OAAI,KAAM,QAAO,oBAAoB,eAAe,WAAW;;EAElE;;;;;;;;;;;;;;;;;ACvNH,MAAM,OAAO;AAEb,IAAI,YAAY;AAChB,IAAI,oBAAoB;AAExB,SAAgB,aAAmB;AACjC;AACA,KAAI,YAAY,EAAG;CAEnB,MAAM,aAAa,OAAO,aAAa,SAAS,gBAAgB;AAChE,qBAAoB,SAAS,KAAK,MAAM;AAExC,UAAS,KAAK,aAAa,MAAM,GAAG;AACpC,KAAI,aAAa,EACf,UAAS,KAAK,MAAM,eAAe,GAAG,WAAW;;AAIrD,SAAgB,eAAqB;AACnC,KAAI,cAAc,EAAG;AACrB;AACA,KAAI,YAAY,EAAG;AAEnB,UAAS,KAAK,gBAAgB,KAAK;AACnC,UAAS,KAAK,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;AChBrC,MAAM,YAAY;AAClB,MAAM,aAAa;AAEnB,SAAS,YAAY,KAA0B;AAE7C,QADe,IAAI,cAA2B,OAAO,EACtC,eAAe,IAAI,eAAe;;AAGnD,SAAS,aAAa,KAA8B;AAIlD,KAAI,IAAI,QAAQ,sCAAsC,CAAE,cAAa;CAMrE,MAAM,OAAQ,IAAI,QAAQ,kBAAkB,IAA2B;CAEvE,MAAM,MAAM,SAAS,cAAc,SAAS;AAC5C,KAAI,OAAO;AACX,KAAI,YAAY;AAChB,KAAI,aAAa,cAAc,yBAAyB;AACxD,KAAI,YAAY;CAEhB,IAAI;CAEJ,eAAe,cAAc;EAC3B,MAAM,OAAO,YAAY,IAAI;AAC7B,MAAI,CAAC,KAAM;AAEX,MAAI;AACF,SAAM,UAAU,UAAU,UAAU,KAAK;UACnC;AACN;;AAGF,MAAI,YAAY;AAChB,MAAI,QAAQ,QAAQ;AACpB,MAAI,aAAa,cAAc,UAAU;AAEzC,MAAI,WAAY,QAAO,aAAa,WAAW;AAC/C,eAAa,OAAO,iBAAiB;AACnC,OAAI,YAAY;AAChB,UAAO,IAAI,QAAQ;AACnB,OAAI,aAAa,cAAc,yBAAyB;KACvD,KAAK;;AAGV,KAAI,iBAAiB,SAAS,YAAY;AAE1C,KAAI,iBAAiB,KAAK,CAAC,aAAa,SACtC,MAAK,MAAM,WAAW;AAExB,MAAK,YAAY,IAAI;AAErB,cAAa;AACX,MAAI,WAAY,QAAO,aAAa,WAAW;AAC/C,MAAI,oBAAoB,SAAS,YAAY;AAC7C,MAAI,QAAQ;;;;AAKhB,SAAgB,WAAiB;AAC/B,OAAM,kBAAkB,aAAa;;;;;;ACrFvC,IAAI,aAAiC;;;;;;AAOrC,SAAS,SAAS,SAAiB;AACjC,KAAI,CAAC,cAAc,CAAC,WAAW,aAAa;AAC1C,eAAa,SAAS,cAAc,MAAM;AAC1C,aAAW,aAAa,aAAa,SAAS;AAC9C,SAAO,OAAO,WAAW,OAAO;GAC9B,UAAU;GACV,OAAO;GACP,QAAQ;GACR,SAAS;GACT,QAAQ;GACR,UAAU;GACV,UAAU;GACV,YAAY;GACZ,QAAQ;GACT,CAAC;AACF,WAAS,KAAK,YAAY,WAAW;;AAGvC,YAAW,cAAc;AACzB,YAAW,cAAc;;AAG3B,SAAS,sBAAsB;AAC7B,UAAS,iBAA8B,oCAAoC,CAAC,SAAS,YAAY;AAC/F,MAAI,QAAQ,aAAa,4BAA4B,CAAE;AACvD,UAAQ,aAAa,6BAA6B,OAAO;EAEzD,MAAM,OAAO,SAAS,cAAc,IAAI;AACxC,OAAK,OAAO,IAAI,QAAQ;AACxB,OAAK,YAAY;AACjB,OAAK,aAAa,cAAc,gBAAgB,QAAQ,aAAa,MAAM,IAAI,YAAY;AAC3F,OAAK,cAAc;AAKnB,OAAK,iBAAiB,eAAe;GACnC,MAAM,MAAM,IAAI,IAAI,KAAK,aAAa,OAAO,IAAI,IAAI,QAAQ,MAAM,SAAS,KAAK,CAAC;AAClF,aAAU,WAAW,UAAU,IAAI,CAAC,WAC5B,SAAS,2BAA2B,QACpC,GACP;IACD;AAEF,UAAQ,YAAY,KAAK;GACzB;;;;;;;;AASJ,SAAgB,iBAAuB;AACrC,sBAAqB;AACrB,UAAS,iBAAiB,mBAAmB,oBAAoB"}
package/dist/content.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ComponentProp, DefineSchemaOptions, DocSchemaConfig, componentsSchema, defineDocSchema, defineSchema, docsSchema, partialsSchema } from "./schemas.js";
1
+ import { ComponentProp, DefineSchemaOptions, DocSchemaConfig, componentsSchema, defineDocSchema, definePartialsSchema, defineSchema, docsSchema, partialsSchema } from "./schemas.js";
2
2
  import { z } from "astro/zod";
3
3
  import * as astro_loaders0 from "astro/loaders";
4
4
 
@@ -25,7 +25,7 @@ interface DocsCollectionOptions<TFields extends Record<string, z.ZodTypeAny> = R
25
25
  */
26
26
  schemaFields?: TFields;
27
27
  }
28
- interface PartialsCollectionOptions {
28
+ interface PartialsCollectionOptions<TFields extends Record<string, z.ZodTypeAny> = Record<string, never>> {
29
29
  /**
30
30
  * Directory under `src/content/` to load partials from.
31
31
  * Default: `"partials"`.
@@ -36,6 +36,13 @@ interface PartialsCollectionOptions {
36
36
  * Default: `"** /*.{md,mdx}"`.
37
37
  */
38
38
  pattern?: string;
39
+ /**
40
+ * Extra frontmatter fields merged into the default partials schema.
41
+ * Useful for partials with product-specific metadata (e.g. CF's
42
+ * `inputParameters`). Same generic-preserving shape as
43
+ * `DocsCollectionOptions.schemaFields`.
44
+ */
45
+ schemaFields?: TFields;
39
46
  }
40
47
  /**
41
48
  * Returns an Astro content-collection config (`{ loader, schema }`) for the
@@ -43,7 +50,7 @@ interface PartialsCollectionOptions {
43
50
  */
44
51
  declare function docsCollection<TFields extends Record<string, z.ZodTypeAny> = Record<string, never>>(options?: DocsCollectionOptions<TFields>): {
45
52
  loader: astro_loaders0.Loader;
46
- schema: z.ZodObject<(("prev" | "next" | "title" | "description" | "mode" | "sidebar" | "head" | "banner" | "draft" | "noindex" | "searchable" | "tableOfContents" | "lastUpdated" | "socialImage" | "previousSlug") & keyof TFields extends never ? {
53
+ schema: z.ZodObject<(("prev" | "next" | "title" | "description" | "mode" | "sidebar" | "head" | "banner" | "draft" | "noindex" | "searchable" | "tableOfContents" | "lastUpdated" | "socialImage" | "previousSlug" | "external_link") & keyof TFields extends never ? {
47
54
  title: z.ZodString;
48
55
  description: z.ZodOptional<z.ZodString>;
49
56
  mode: z.ZodDefault<z.ZodEnum<{
@@ -68,13 +75,33 @@ declare function docsCollection<TFields extends Record<string, z.ZodTypeAny> = R
68
75
  }, z.core.$strip>]>>;
69
76
  hidden: z.ZodOptional<z.ZodBoolean>;
70
77
  hideChildren: z.ZodOptional<z.ZodBoolean>;
78
+ group: z.ZodOptional<z.ZodObject<{
79
+ label: z.ZodOptional<z.ZodString>;
80
+ badge: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
81
+ text: z.ZodString;
82
+ variant: z.ZodDefault<z.ZodEnum<{
83
+ success: "success";
84
+ default: "default";
85
+ info: "info";
86
+ note: "note";
87
+ tip: "tip";
88
+ warning: "warning";
89
+ caution: "caution";
90
+ danger: "danger";
91
+ }>>;
92
+ }, z.core.$strip>]>>;
93
+ hideIndex: z.ZodOptional<z.ZodBoolean>;
94
+ }, z.core.$strip>>;
71
95
  }, z.core.$strip>]>>;
72
96
  head: z.ZodDefault<z.ZodArray<z.ZodObject<{
73
97
  tag: z.ZodEnum<{
98
+ title: "title";
74
99
  meta: "meta";
75
100
  link: "link";
76
101
  script: "script";
77
102
  style: "style";
103
+ noscript: "noscript";
104
+ base: "base";
78
105
  }>;
79
106
  attrs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
80
107
  content: z.ZodOptional<z.ZodString>;
@@ -110,6 +137,7 @@ declare function docsCollection<TFields extends Record<string, z.ZodTypeAny> = R
110
137
  label: z.ZodOptional<z.ZodString>;
111
138
  }, z.core.$strip>, z.ZodLiteral<false>]>>;
112
139
  previousSlug: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
140
+ external_link: z.ZodOptional<z.ZodString>;
113
141
  } & { -readonly [P in keyof TFields]: TFields[P] } : ({
114
142
  title: z.ZodString;
115
143
  description: z.ZodOptional<z.ZodString>;
@@ -135,13 +163,33 @@ declare function docsCollection<TFields extends Record<string, z.ZodTypeAny> = R
135
163
  }, z.core.$strip>]>>;
136
164
  hidden: z.ZodOptional<z.ZodBoolean>;
137
165
  hideChildren: z.ZodOptional<z.ZodBoolean>;
166
+ group: z.ZodOptional<z.ZodObject<{
167
+ label: z.ZodOptional<z.ZodString>;
168
+ badge: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
169
+ text: z.ZodString;
170
+ variant: z.ZodDefault<z.ZodEnum<{
171
+ success: "success";
172
+ default: "default";
173
+ info: "info";
174
+ note: "note";
175
+ tip: "tip";
176
+ warning: "warning";
177
+ caution: "caution";
178
+ danger: "danger";
179
+ }>>;
180
+ }, z.core.$strip>]>>;
181
+ hideIndex: z.ZodOptional<z.ZodBoolean>;
182
+ }, z.core.$strip>>;
138
183
  }, z.core.$strip>]>>;
139
184
  head: z.ZodDefault<z.ZodArray<z.ZodObject<{
140
185
  tag: z.ZodEnum<{
186
+ title: "title";
141
187
  meta: "meta";
142
188
  link: "link";
143
189
  script: "script";
144
190
  style: "style";
191
+ noscript: "noscript";
192
+ base: "base";
145
193
  }>;
146
194
  attrs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
147
195
  content: z.ZodOptional<z.ZodString>;
@@ -177,15 +225,23 @@ declare function docsCollection<TFields extends Record<string, z.ZodTypeAny> = R
177
225
  label: z.ZodOptional<z.ZodString>;
178
226
  }, z.core.$strip>, z.ZodLiteral<false>]>>;
179
227
  previousSlug: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
228
+ external_link: z.ZodOptional<z.ZodString>;
180
229
  } extends infer T_1 extends z.core.util.SomeObject ? { [K in keyof T_1 as K extends keyof TFields ? never : K]: T_1[K] } : never) & { [K_1 in keyof { -readonly [P in keyof TFields]: TFields[P] }]: { -readonly [P in keyof TFields]: TFields[P] }[K_1] }) extends infer T ? { [k in keyof T]: T[k] } : never, z.core.$strip>;
181
230
  };
182
231
  /**
183
232
  * Returns an Astro content-collection config (`{ loader, schema }`) for the
184
233
  * partials collection. Pass to `defineCollection()`.
234
+ *
235
+ * `schemaFields` extends the default partials schema with extra
236
+ * frontmatter — same shape as `docsCollection({ schemaFields })`.
185
237
  */
186
- declare function partialsCollection(options?: PartialsCollectionOptions): {
238
+ declare function partialsCollection<TFields extends Record<string, z.ZodTypeAny> = Record<string, never>>(options?: PartialsCollectionOptions<TFields>): {
187
239
  loader: astro_loaders0.Loader;
188
- schema: z.ZodDefault<z.ZodObject<{
240
+ schema: z.ZodObject<("params" & keyof TFields extends never ? {
241
+ params: z.ZodOptional<z.ZodArray<z.ZodString>>;
242
+ } & { -readonly [P in keyof TFields]: TFields[P] } : ({
243
+ params: z.ZodOptional<z.ZodArray<z.ZodString>>;
244
+ } extends infer T_1 extends z.core.util.SomeObject ? { [K in keyof T_1 as K extends keyof TFields ? never : K]: T_1[K] } : never) & { [K_1 in keyof { -readonly [P in keyof TFields]: TFields[P] }]: { -readonly [P in keyof TFields]: TFields[P] }[K_1] }) extends infer T ? { [k in keyof T]: T[k] } : never, z.core.$strip> | z.ZodDefault<z.ZodObject<{
189
245
  params: z.ZodOptional<z.ZodArray<z.ZodString>>;
190
246
  }, z.core.$strip>>;
191
247
  };
@@ -224,5 +280,5 @@ declare function componentsCollection(options?: ComponentsCollectionOptions): {
224
280
  }, z.core.$strip>;
225
281
  };
226
282
  //#endregion
227
- export { type ComponentProp, ComponentsCollectionOptions, type DefineSchemaOptions, type DocSchemaConfig, DocsCollectionOptions, PartialsCollectionOptions, componentsCollection, componentsSchema, defineDocSchema, defineSchema, docsCollection, docsSchema, partialsCollection, partialsSchema };
283
+ export { type ComponentProp, ComponentsCollectionOptions, type DefineSchemaOptions, type DocSchemaConfig, DocsCollectionOptions, PartialsCollectionOptions, componentsCollection, componentsSchema, defineDocSchema, definePartialsSchema, defineSchema, docsCollection, docsSchema, partialsCollection, partialsSchema };
228
284
  //# sourceMappingURL=content.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.ts","names":[],"sources":["../src/content.ts"],"mappings":";;;;;UAoCiB,qBAAA,iBACC,MAAA,SAAe,CAAA,CAAE,UAAA,IAAc,MAAA;EAqBzB;;AAGxB;;EAlBE,IAAA;EAuBA;;AAcF;;EAhCE,OAAA;EAiCiC;;;;;;;;;EAvBjC,YAAA,GAAe,OAAA;AAAA;AAAA,UAGA,yBAAA;;;;;EAKf,IAAA;;;;;EAKA,OAAA;AAAA;;;;;iBASc,cAAA,iBACE,MAAA,SAAe,CAAA,CAAE,UAAA,IAAc,MAAA,gBAAA,CAC/C,OAAA,GAAS,qBAAA,CAAsB,OAAA;UAAD,cAAA,CAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAehB,kBAAA,CAAmB,OAAA,GAAS,yBAAA;UAA8B,cAAA,CAAA,MAAA;;;;;UAUzD,2BAAA;;;;;EAKf,IAAA;;;;;EAKA,OAAA;AAAA;;;;;;;;;iBAWc,oBAAA,CAAqB,OAAA,GAAS,2BAAA;UAAgC,cAAA,CAAA,MAAA"}
1
+ {"version":3,"file":"content.d.ts","names":[],"sources":["../src/content.ts"],"mappings":";;;;;UA0CiB,qBAAA,iBACC,MAAA,SAAe,CAAA,CAAE,UAAA,IAAc,MAAA;EAqBzB;;AAGxB;;EAlBE,IAAA;EAmBiC;;;;EAdjC,OAAA;EAgCsB;;;;;;;;;EAtBtB,YAAA,GAAe,OAAA;AAAA;AAAA,UAGA,yBAAA,iBACC,MAAA,SAAe,CAAA,CAAE,UAAA,IAAc,MAAA;EA2BjC;;;;EArBd,IAAA;EAsB+C;;;;EAjB/C,OAAA;;;;;;;EAOA,YAAA,GAAe,OAAA;AAAA;;;;;iBASD,cAAA,iBACE,MAAA,SAAe,CAAA,CAAE,UAAA,IAAc,MAAA,gBAAA,CAC/C,OAAA,GAAS,qBAAA,CAAsB,OAAA;UAAD,cAAA,CAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBhB,kBAAA,iBACE,MAAA,SAAe,CAAA,CAAE,UAAA,IAAc,MAAA,gBAAA,CAC/C,OAAA,GAAS,yBAAA,CAA0B,OAAA;UAAD,cAAA,CAAA,MAAA;;;;;;;;;UAgBnB,2BAAA;;;;;EAKf,IAAA;;;;;EAKA,OAAA;AAAA;;;;;;;;;iBAWc,oBAAA,CAAqB,OAAA,GAAS,2BAAA;UAAgC,cAAA,CAAA,MAAA"}
package/dist/content.js CHANGED
@@ -1,4 +1,4 @@
1
- import { componentsSchema, defineDocSchema, defineSchema, docsSchema, partialsSchema } from "./schemas.js";
1
+ import { componentsSchema, defineDocSchema, definePartialsSchema, defineSchema, docsSchema, partialsSchema } from "./schemas.js";
2
2
  import { glob } from "astro/loaders";
3
3
 
4
4
  //#region src/content.ts
@@ -41,14 +41,20 @@ function docsCollection(options = {}) {
41
41
  /**
42
42
  * Returns an Astro content-collection config (`{ loader, schema }`) for the
43
43
  * partials collection. Pass to `defineCollection()`.
44
+ *
45
+ * `schemaFields` extends the default partials schema with extra
46
+ * frontmatter — same shape as `docsCollection({ schemaFields })`.
44
47
  */
45
48
  function partialsCollection(options = {}) {
49
+ const base = `./src/content/${options.base ?? "partials"}`;
50
+ const pattern = options.pattern ?? DEFAULT_PATTERN;
51
+ const schema = options.schemaFields ? definePartialsSchema({ fields: options.schemaFields }) : partialsSchema;
46
52
  return {
47
53
  loader: glob({
48
- base: `./src/content/${options.base ?? "partials"}`,
49
- pattern: options.pattern ?? DEFAULT_PATTERN
54
+ base,
55
+ pattern
50
56
  }),
51
- schema: partialsSchema
57
+ schema
52
58
  };
53
59
  }
54
60
  /**
@@ -70,5 +76,5 @@ function componentsCollection(options = {}) {
70
76
  }
71
77
 
72
78
  //#endregion
73
- export { componentsCollection, componentsSchema, defineDocSchema, defineSchema, docsCollection, docsSchema, partialsCollection, partialsSchema };
79
+ export { componentsCollection, componentsSchema, defineDocSchema, definePartialsSchema, defineSchema, docsCollection, docsSchema, partialsCollection, partialsSchema };
74
80
  //# sourceMappingURL=content.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","names":[],"sources":["../src/content.ts"],"sourcesContent":["/**\n * Content collection helpers for `nimbus-docs/content`.\n *\n * Users plug these into their `src/content.config.ts`:\n *\n * import { defineCollection } from \"astro:content\";\n * import { docsCollection, partialsCollection } from \"nimbus-docs/content\";\n *\n * export const collections = {\n * docs: defineCollection(docsCollection()),\n * partials: defineCollection(partialsCollection()),\n * };\n *\n * Extend the docs schema with extra frontmatter fields:\n *\n * docs: defineCollection(docsCollection({\n * schemaFields: { author: z.string(), tags: z.array(z.string()) },\n * })),\n */\n\nimport { glob } from \"astro/loaders\";\nimport type { z } from \"astro/zod\";\n\nimport { componentsSchema, defineDocSchema, partialsSchema } from \"./schemas.js\";\n\n// Re-export the public schema factories from `nimbus-docs/content` so users\n// have a single import for content-config concerns (collections + schemas).\nexport {\n defineDocSchema,\n defineSchema,\n docsSchema,\n partialsSchema,\n componentsSchema,\n} from \"./schemas.js\";\nexport type { DefineSchemaOptions, DocSchemaConfig, ComponentProp } from \"./schemas.js\";\n\nexport interface DocsCollectionOptions<\n TFields extends Record<string, z.ZodTypeAny> = Record<string, never>,\n> {\n /**\n * Directory under `src/content/` to load docs from.\n * Default: `\"docs\"`.\n */\n base?: string;\n /**\n * Glob pattern relative to `base`.\n * Default: `\"** /*.{md,mdx}\"` (space added to avoid breaking this comment).\n */\n pattern?: string;\n /**\n * Extra fields merged into the default docs schema. Lets users add\n * project-specific frontmatter (author, tags, etc.) without rebuilding\n * the whole schema.\n *\n * Generic-typed so the call-site shape (`{ author: z.string() }`) is\n * preserved through to the emitted entry data type — `entry.data.author`\n * resolves to `string`, not `unknown`.\n */\n schemaFields?: TFields;\n}\n\nexport interface PartialsCollectionOptions {\n /**\n * Directory under `src/content/` to load partials from.\n * Default: `\"partials\"`.\n */\n base?: string;\n /**\n * Glob pattern relative to `base`.\n * Default: `\"** /*.{md,mdx}\"`.\n */\n pattern?: string;\n}\n\nconst DEFAULT_PATTERN = \"**/*.{md,mdx}\";\n\n/**\n * Returns an Astro content-collection config (`{ loader, schema }`) for the\n * docs collection. Pass to `defineCollection()`.\n */\nexport function docsCollection<\n TFields extends Record<string, z.ZodTypeAny> = Record<string, never>,\n>(options: DocsCollectionOptions<TFields> = {}) {\n const base = `./src/content/${options.base ?? \"docs\"}`;\n const pattern = options.pattern ?? DEFAULT_PATTERN;\n const schema = defineDocSchema({ fields: options.schemaFields });\n\n return {\n loader: glob({ base, pattern }),\n schema,\n };\n}\n\n/**\n * Returns an Astro content-collection config (`{ loader, schema }`) for the\n * partials collection. Pass to `defineCollection()`.\n */\nexport function partialsCollection(options: PartialsCollectionOptions = {}) {\n const base = `./src/content/${options.base ?? \"partials\"}`;\n const pattern = options.pattern ?? DEFAULT_PATTERN;\n\n return {\n loader: glob({ base, pattern }),\n schema: partialsSchema,\n };\n}\n\nexport interface ComponentsCollectionOptions {\n /**\n * Directory under `src/content/` to load component entries from.\n * Default: `\"components\"`.\n */\n base?: string;\n /**\n * Glob pattern relative to `base`.\n * Default: `\"**\\/*.{md,mdx}\"`.\n */\n pattern?: string;\n}\n\n/**\n * Returns an Astro content-collection config (`{ loader, schema }`) for the\n * components collection — for sites documenting their own UI components.\n *\n * Pairs with the `component-showcase` registry recipe, which installs the\n * matching `<Showcase>` / `<Example>` MDX wrappers and the `/components`\n * route. Frontmatter shape: `{ title, tagline, props }`.\n */\nexport function componentsCollection(options: ComponentsCollectionOptions = {}) {\n const base = `./src/content/${options.base ?? \"components\"}`;\n const pattern = options.pattern ?? DEFAULT_PATTERN;\n\n return {\n loader: glob({ base, pattern }),\n schema: componentsSchema,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0EA,MAAM,kBAAkB;;;;;AAMxB,SAAgB,eAEd,UAA0C,EAAE,EAAE;CAC9C,MAAM,OAAO,iBAAiB,QAAQ,QAAQ;CAC9C,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,gBAAgB,EAAE,QAAQ,QAAQ,cAAc,CAAC;AAEhE,QAAO;EACL,QAAQ,KAAK;GAAE;GAAM;GAAS,CAAC;EAC/B;EACD;;;;;;AAOH,SAAgB,mBAAmB,UAAqC,EAAE,EAAE;AAI1E,QAAO;EACL,QAAQ,KAAK;GAAE,MAJJ,iBAAiB,QAAQ,QAAQ;GAIvB,SAHP,QAAQ,WAAW;GAGH,CAAC;EAC/B,QAAQ;EACT;;;;;;;;;;AAwBH,SAAgB,qBAAqB,UAAuC,EAAE,EAAE;AAI9E,QAAO;EACL,QAAQ,KAAK;GAAE,MAJJ,iBAAiB,QAAQ,QAAQ;GAIvB,SAHP,QAAQ,WAAW;GAGH,CAAC;EAC/B,QAAQ;EACT"}
1
+ {"version":3,"file":"content.js","names":[],"sources":["../src/content.ts"],"sourcesContent":["/**\n * Content collection helpers for `nimbus-docs/content`.\n *\n * Users plug these into their `src/content.config.ts`:\n *\n * import { defineCollection } from \"astro:content\";\n * import { docsCollection, partialsCollection } from \"nimbus-docs/content\";\n *\n * export const collections = {\n * docs: defineCollection(docsCollection()),\n * partials: defineCollection(partialsCollection()),\n * };\n *\n * Extend the docs schema with extra frontmatter fields:\n *\n * docs: defineCollection(docsCollection({\n * schemaFields: { author: z.string(), tags: z.array(z.string()) },\n * })),\n */\n\nimport { glob } from \"astro/loaders\";\nimport type { z } from \"astro/zod\";\n\nimport {\n componentsSchema,\n defineDocSchema,\n definePartialsSchema,\n partialsSchema,\n} from \"./schemas.js\";\n\n// Re-export the public schema factories from `nimbus-docs/content` so users\n// have a single import for content-config concerns (collections + schemas).\nexport {\n defineDocSchema,\n definePartialsSchema,\n defineSchema,\n docsSchema,\n partialsSchema,\n componentsSchema,\n} from \"./schemas.js\";\nexport type { DefineSchemaOptions, DocSchemaConfig, ComponentProp } from \"./schemas.js\";\n\nexport interface DocsCollectionOptions<\n TFields extends Record<string, z.ZodTypeAny> = Record<string, never>,\n> {\n /**\n * Directory under `src/content/` to load docs from.\n * Default: `\"docs\"`.\n */\n base?: string;\n /**\n * Glob pattern relative to `base`.\n * Default: `\"** /*.{md,mdx}\"` (space added to avoid breaking this comment).\n */\n pattern?: string;\n /**\n * Extra fields merged into the default docs schema. Lets users add\n * project-specific frontmatter (author, tags, etc.) without rebuilding\n * the whole schema.\n *\n * Generic-typed so the call-site shape (`{ author: z.string() }`) is\n * preserved through to the emitted entry data type — `entry.data.author`\n * resolves to `string`, not `unknown`.\n */\n schemaFields?: TFields;\n}\n\nexport interface PartialsCollectionOptions<\n TFields extends Record<string, z.ZodTypeAny> = Record<string, never>,\n> {\n /**\n * Directory under `src/content/` to load partials from.\n * Default: `\"partials\"`.\n */\n base?: string;\n /**\n * Glob pattern relative to `base`.\n * Default: `\"** /*.{md,mdx}\"`.\n */\n pattern?: string;\n /**\n * Extra frontmatter fields merged into the default partials schema.\n * Useful for partials with product-specific metadata (e.g. CF's\n * `inputParameters`). Same generic-preserving shape as\n * `DocsCollectionOptions.schemaFields`.\n */\n schemaFields?: TFields;\n}\n\nconst DEFAULT_PATTERN = \"**/*.{md,mdx}\";\n\n/**\n * Returns an Astro content-collection config (`{ loader, schema }`) for the\n * docs collection. Pass to `defineCollection()`.\n */\nexport function docsCollection<\n TFields extends Record<string, z.ZodTypeAny> = Record<string, never>,\n>(options: DocsCollectionOptions<TFields> = {}) {\n const base = `./src/content/${options.base ?? \"docs\"}`;\n const pattern = options.pattern ?? DEFAULT_PATTERN;\n const schema = defineDocSchema({ fields: options.schemaFields });\n\n return {\n loader: glob({ base, pattern }),\n schema,\n };\n}\n\n/**\n * Returns an Astro content-collection config (`{ loader, schema }`) for the\n * partials collection. Pass to `defineCollection()`.\n *\n * `schemaFields` extends the default partials schema with extra\n * frontmatter — same shape as `docsCollection({ schemaFields })`.\n */\nexport function partialsCollection<\n TFields extends Record<string, z.ZodTypeAny> = Record<string, never>,\n>(options: PartialsCollectionOptions<TFields> = {}) {\n const base = `./src/content/${options.base ?? \"partials\"}`;\n const pattern = options.pattern ?? DEFAULT_PATTERN;\n // Avoid re-deriving the schema when no fields were declared — keeps the\n // default behaviour (`partialsSchema` with its `.default({})`) exact for\n // existing users who don't opt in.\n const schema = options.schemaFields\n ? definePartialsSchema({ fields: options.schemaFields })\n : partialsSchema;\n\n return {\n loader: glob({ base, pattern }),\n schema,\n };\n}\n\nexport interface ComponentsCollectionOptions {\n /**\n * Directory under `src/content/` to load component entries from.\n * Default: `\"components\"`.\n */\n base?: string;\n /**\n * Glob pattern relative to `base`.\n * Default: `\"**\\/*.{md,mdx}\"`.\n */\n pattern?: string;\n}\n\n/**\n * Returns an Astro content-collection config (`{ loader, schema }`) for the\n * components collection — for sites documenting their own UI components.\n *\n * Pairs with the `component-showcase` registry recipe, which installs the\n * matching `<Showcase>` / `<Example>` MDX wrappers and the `/components`\n * route. Frontmatter shape: `{ title, tagline, props }`.\n */\nexport function componentsCollection(options: ComponentsCollectionOptions = {}) {\n const base = `./src/content/${options.base ?? \"components\"}`;\n const pattern = options.pattern ?? DEFAULT_PATTERN;\n\n return {\n loader: glob({ base, pattern }),\n schema: componentsSchema,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAyFA,MAAM,kBAAkB;;;;;AAMxB,SAAgB,eAEd,UAA0C,EAAE,EAAE;CAC9C,MAAM,OAAO,iBAAiB,QAAQ,QAAQ;CAC9C,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,gBAAgB,EAAE,QAAQ,QAAQ,cAAc,CAAC;AAEhE,QAAO;EACL,QAAQ,KAAK;GAAE;GAAM;GAAS,CAAC;EAC/B;EACD;;;;;;;;;AAUH,SAAgB,mBAEd,UAA8C,EAAE,EAAE;CAClD,MAAM,OAAO,iBAAiB,QAAQ,QAAQ;CAC9C,MAAM,UAAU,QAAQ,WAAW;CAInC,MAAM,SAAS,QAAQ,eACnB,qBAAqB,EAAE,QAAQ,QAAQ,cAAc,CAAC,GACtD;AAEJ,QAAO;EACL,QAAQ,KAAK;GAAE;GAAM;GAAS,CAAC;EAC/B;EACD;;;;;;;;;;AAwBH,SAAgB,qBAAqB,UAAuC,EAAE,EAAE;AAI9E,QAAO;EACL,QAAQ,KAAK;GAAE,MAJJ,iBAAiB,QAAQ,QAAQ;GAIvB,SAHP,QAAQ,WAAW;GAGH,CAAC;EAC/B,QAAQ;EACT"}
@@ -45,6 +45,9 @@ declare const RULE_CODES: {
45
45
  readonly "nimbus/internal-link": {
46
46
  readonly kind: "authoring";
47
47
  };
48
+ readonly "nimbus/image-ref": {
49
+ readonly kind: "authoring";
50
+ };
48
51
  readonly "nimbus/orphan-page": {
49
52
  readonly kind: "authoring";
50
53
  };
@@ -130,4 +133,4 @@ interface Diagnostic {
130
133
  }
131
134
  //#endregion
132
135
  export { Severity as a, RuleCode as i, Diagnostic as n, SeverityConfig as o, DiagnosticFix as r, AuthoringRuleCode as t };
133
- //# sourceMappingURL=diagnostic-C6OaBe_o.d.ts.map
136
+ //# sourceMappingURL=diagnostic-ewiZxpSO.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostic-ewiZxpSO.d.ts","names":[],"sources":["../src/lint/diagnostic.ts"],"mappings":";;AAyBA;;;;;;;;;;;;;;;;;;;;;;;cAAa,UAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2BD,QAAA,gBAAwB,UAAA;;;;;AAgCpC;;;KAvBY,iBAAA,WACJ,QAAA,WAAmB,UAAA,EAAY,CAAA;EAAa,IAAA;AAAA,IAC9C,CAAA,WAEJ,QAAA;;;KAIU,QAAA;;KAGA,cAAA,GAAiB,QAAA;AAAA,UAEZ,aAAA;EAkBf;EAhBA,WAAA;EAoBA;;;;EAfA,KAAA,EAAO,KAAA;IAAQ,KAAA;IAAyB,IAAA;EAAA;AAAA;AAAA,UAGzB,UAAA;EACf,IAAA,EAAM,QAAA;EACN,QAAA,EAAU,QAAA;;;EAGV,MAAA;EACA,OAAA;;EAEA,IAAA;;EAEA,IAAA;;EAEA,MAAA;EACA,OAAA;EACA,SAAA;EACA,GAAA,GAAM,aAAA;AAAA"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as SeverityConfig, t as AuthoringRuleCode } from "./diagnostic-C6OaBe_o.js";
1
+ import { o as SeverityConfig, t as AuthoringRuleCode } from "./diagnostic-ewiZxpSO.js";
2
2
  import { BadgeVariant, Breadcrumb, NimbusConfig, PrevNext, PrevNextLink, PrevNextOverrides, ResolvedVersions, SearchProvider, SearchResult, SidebarBadge, SidebarConfig, SidebarConfigItem, SidebarExternalLinkItem, SidebarGroupItem, SidebarItem, SidebarLinkItem, SidebarSection, TOCItem, VersionAlternateRecord, VersionAlternatesTable, VersionPageRef, VersionStatus, VersionsConfig } from "./types.js";
3
3
  import mdx from "@astrojs/mdx";
4
4
  import * as astro_content0 from "astro:content";
@@ -85,12 +85,83 @@ interface CollectionLintConfig {
85
85
  */
86
86
  type CollectionsConfig = Record<string, CollectionLintConfig>;
87
87
  //#endregion
88
+ //#region src/_internal/incremental/partial-refs.d.ts
89
+ /**
90
+ * Partial resolver hook. Called for every component opening tag scanner
91
+ * encounters in MDX content. Returns the absolute file path of the partial
92
+ * the component embeds, or null if the component isn't a partial-embedder
93
+ * (Tabs, Aside, etc.) or the props don't match a known pattern.
94
+ *
95
+ * Pattern borrowed from mvvmm's cloudflare-docs PR — supports the
96
+ * multi-prop case (`<Render file="setup" product="workers" />` →
97
+ * `partials/workers/setup.mdx`) that single-prop string regex can't
98
+ * capture.
99
+ */
100
+ type PartialResolverHook = (componentName: string, props: Record<string, string>) => string | null;
101
+ //#endregion
102
+ //#region src/_internal/incremental/sitemap.d.ts
103
+ /** Mirror of `@astrojs/sitemap`'s `SitemapItem` shape. */
104
+ interface SitemapItem {
105
+ url: string;
106
+ lastmod?: string;
107
+ changefreq?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
108
+ priority?: number;
109
+ links?: {
110
+ lang: string;
111
+ url: string;
112
+ }[];
113
+ }
114
+ type SitemapSerialize = (item: SitemapItem) => SitemapItem | undefined | null | Promise<SitemapItem | undefined | null>;
115
+ //#endregion
88
116
  //#region src/integration.d.ts
117
+ interface SitemapOptions {
118
+ serialize?: SitemapSerialize;
119
+ customPages?: string[];
120
+ }
89
121
  interface NimbusIntegrationOptions {
90
122
  /** MDX options forwarded to `@astrojs/mdx`. */
91
123
  mdx?: Parameters<typeof mdx>[0];
92
- /** Skip sitemap integration. Default: enabled when `site.url` is set. */
93
- sitemap?: boolean;
124
+ /**
125
+ * Sitemap behavior. Defaults: enabled when `site.url` is set, default
126
+ * `@astrojs/sitemap` output. `false` disables it. Pass an object to
127
+ * customise — currently `serialize` and `customPages` are supported, and
128
+ * they apply both when incremental builds are on (we emit the sitemap
129
+ * ourselves so cached routes appear) and when incremental is off (we
130
+ * forward them to `@astrojs/sitemap`).
131
+ *
132
+ * The `serialize` callback runs once per URL and may return modified
133
+ * fields (e.g. `lastmod` from git) or `null`/`undefined` to drop the
134
+ * URL. Cloudflare-docs's pattern of git-sourced `lastmod` is the
135
+ * motivating case.
136
+ */
137
+ sitemap?: boolean | SitemapOptions;
138
+ /**
139
+ * Override the markdown processor Nimbus wires into Astro's
140
+ * `markdown.processor`. Default is Sätteri (Rust-based, fast).
141
+ *
142
+ * Pass a different processor when you need remark/rehype plugin
143
+ * extensibility — Sätteri disables `mdx({ remarkPlugins })` because it
144
+ * replaces unified's pipeline. The escape hatch:
145
+ *
146
+ * ```ts
147
+ * import { unified } from "@astrojs/markdown-remark";
148
+ * import remarkToc from "remark-toc";
149
+ *
150
+ * nimbus(config, {
151
+ * markdown: {
152
+ * processor: unified({ remarkPlugins: [remarkToc] }),
153
+ * },
154
+ * });
155
+ * ```
156
+ *
157
+ * Trade-off: the Sätteri performance win goes away. Worth it for sites
158
+ * that depend on unified-ecosystem plugins (CF docs uses seven).
159
+ *
160
+ * @default `satteri()`
161
+ */
162
+ markdown?: {
163
+ /** Custom Astro `markdown.processor`. Imported from `@astrojs/markdown-remark` (unified), `@astrojs/markdown-satteri` (default), or any compatible processor. */processor?: unknown;
164
+ };
94
165
  /**
95
166
  * Build-time MDX PascalCase tag validation.
96
167
  *
@@ -115,6 +186,30 @@ interface NimbusIntegrationOptions {
115
186
  contentDirs?: string[];
116
187
  skip?: (filePath: string) => boolean;
117
188
  };
189
+ /**
190
+ * Rewrite `:::type[title]` fenced directives to `<Aside>` components
191
+ * in MDX/MD source before the markdown compiler sees them. Built-in
192
+ * types: `note`, `info`, `tip`, `caution`, `warning`, `important`,
193
+ * `danger` (mapped to Nimbus's 4 Aside slots).
194
+ *
195
+ * - `true` (default): rewrite against `src/content/**\/*.{md,mdx}`.
196
+ * - `false`: skip the transform; `:::` syntax renders as literal text.
197
+ * - `{ typeAliases }`: extra type → Aside mappings for product
198
+ * synonyms (`{ heads: "tip" }`).
199
+ * - `{ contentDirs }`: override the scanned directories.
200
+ * - `{ skip }`: per-file opt-out.
201
+ *
202
+ * Runs as a Vite plugin (content pass) so it survives the
203
+ * `markdown.processor` swap that disables remark plugins under
204
+ * Sätteri. Aside must be in the user's `src/components.ts` globals
205
+ * registry — the default starter exports it; if your registry doesn't,
206
+ * the MDX validator surfaces a clean build error.
207
+ */
208
+ admonitions?: boolean | {
209
+ typeAliases?: Record<string, "note" | "tip" | "caution" | "danger">;
210
+ contentDirs?: string[];
211
+ skip?: (filePath: string) => boolean;
212
+ };
118
213
  /**
119
214
  * Authoring-lint severity overrides for `nimbus-docs lint`. Maps a rule
120
215
  * code to `"error" | "warn" | "off"` or a `[severity, options]` tuple.
@@ -138,6 +233,46 @@ interface NimbusIntegrationOptions {
138
233
  * }
139
234
  */
140
235
  collections?: CollectionsConfig;
236
+ /**
237
+ * Opt into per-page build caching. When `true`, Nimbus wraps Astro's
238
+ * prerenderer and short-circuits cache hits with previously-rendered HTML.
239
+ *
240
+ * Phase 2 MVP — preview-quality:
241
+ * - Per-page cache keyed on file bytes + a global hash of tracked
242
+ * sources (config, components, layouts, lockfile).
243
+ * - No partial-dependency tracking yet — editing a partial triggers a
244
+ * full rebuild (because it doesn't change the page's bytes but it
245
+ * does need to invalidate dependents). Phase 3 closes this gap.
246
+ * - No cache provenance / namespace isolation / trust boundary.
247
+ * Manual `rm -rf .nimbus/cache` between framework upgrades.
248
+ *
249
+ * Default: `false`. The bench (`apps/incremental-bench`) is the only
250
+ * site that should set this today.
251
+ */
252
+ incrementalBuilds?: boolean;
253
+ /**
254
+ * Custom partial resolver for incremental builds. Called for every
255
+ * PascalCase component opening tag found in MDX content with string-
256
+ * literal props. Return the absolute file path of the partial the
257
+ * component embeds, or `null` to indicate this component isn't a
258
+ * partial-embedder.
259
+ *
260
+ * The default resolver covers the standard `<Render file="topic/slug" />`
261
+ * pattern shipping with Nimbus's starter. Sites with multi-prop
262
+ * conventions need their own — cloudflare-docs is the motivating case:
263
+ *
264
+ * @example
265
+ * partialResolver: (name, props) => {
266
+ * if (name !== "Render" || !props.file) return null;
267
+ * if (props.product) {
268
+ * return resolve(projectRoot, `src/content/partials/${props.product}/${props.file}.mdx`);
269
+ * }
270
+ * return resolve(projectRoot, `src/content/partials/${props.file}.mdx`);
271
+ * }
272
+ *
273
+ * Required only when `incrementalBuilds: true`. Ignored otherwise.
274
+ */
275
+ partialResolver?: PartialResolverHook;
141
276
  }
142
277
  declare function nimbus(rawConfig: NimbusConfig, options?: NimbusIntegrationOptions): AstroIntegration;
143
278
  //#endregion