@yaakdoc/react 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,170 @@
1
+ import { n as createIsland, t as IslandComponent } from "./island-BWyuL4_6.js";
2
+ import { i as EnvironmentOption, n as ColorMode, r as ColorModePreference, s as LlmProvider } from "./types-BY2lOvlK.js";
3
+ import { $ as useTabs, J as TabList, Q as tabPanelProps, X as tabElementIds, Y as TabListProps, Z as tabGroupIds, et as visibleTabPanelSelector, q as TAB_STORAGE_KEY } from "./index-CoQ7pnuB.js";
4
+ //#region src/islands/browser.d.ts
5
+ /** Browser helpers shared by the islands. */
6
+ /** `localStorage` access that never throws (private mode, blocked storage...). */
7
+ export declare const storage: {
8
+ get(key: string): string | null;
9
+ set(key: string, value: string): void;
10
+ };
11
+ /** Resolves a (possibly relative) URL against the current page. */
12
+ export declare const resolveUrl: (url: string) => string;
13
+ //#endregion
14
+ //#region src/islands/color-mode.d.ts
15
+ export declare const COLOR_MODE_STORAGE_KEY = "yaakdoc-color-mode";
16
+ /** Applies a preference to <html data-theme> and persists it. */
17
+ export declare function applyColorMode(preference: ColorModePreference): void;
18
+ /**
19
+ * Headless color mode state. Hydration-safe: renders `fallback` on the server and during hydration,
20
+ * then switches to the real value.
21
+ */
22
+ export declare function useColorMode(fallback?: ColorModePreference): {
23
+ preference: "dark" | "light" | "system";
24
+ resolved: ColorMode;
25
+ setPreference: (next: ColorModePreference) => void;
26
+ toggle: () => void;
27
+ };
28
+ /**
29
+ * Inline script for <head> that applies the stored/system color mode before first paint, and keeps
30
+ * following the system setting while the preference is "system".
31
+ */
32
+ export declare function colorModeScript(defaultMode: ColorModePreference): string;
33
+ type ColorModeToggleProps = {
34
+ className?: string;
35
+ label?: string;
36
+ };
37
+ /** Unstyled light/dark toggle. Both icons are rendered; show one via `[data-theme]` CSS. */
38
+ export declare const ColorModeToggle: IslandComponent<ColorModeToggleProps>;
39
+ //#endregion
40
+ //#region src/islands/copy.d.ts
41
+ type CopySource = {
42
+ /** Literal text to copy. */
43
+ text?: string;
44
+ /** CSS selector of an element whose text content is copied. */
45
+ target?: string;
46
+ /** URL whose response body is copied (e.g. a page's Markdown twin). */
47
+ url?: string;
48
+ };
49
+ /** Headless copy-to-clipboard state. `copied` resets after `timeout` ms. */
50
+ export declare function useCopy(timeout?: number): {
51
+ state: "copied" | "error" | "idle";
52
+ copied: boolean;
53
+ copy: (source: CopySource) => Promise<void>;
54
+ };
55
+ type CopyButtonProps = CopySource & {
56
+ label?: string;
57
+ copiedLabel?: string;
58
+ className?: string;
59
+ };
60
+ /** Unstyled copy button. Copies `text`, the text of `target`, or the body of `url`. */
61
+ export declare const CopyButton: IslandComponent<CopyButtonProps>;
62
+ //#endregion
63
+ //#region src/islands/environment.d.ts
64
+ export declare const ENVIRONMENT_STORAGE_KEY = "yaakdoc-environment";
65
+ /**
66
+ * Shows the content variants of one environment. Variants are elements with
67
+ * `data-yd-env="<environment id>"`; `<html data-environment>` drives the CSS (see
68
+ * {@link environmentCss}) and the `hidden` attributes are kept in sync for accessibility.
69
+ */
70
+ export declare function applyEnvironment(id: string): void;
71
+ /** Headless environment switcher state, synced across switchers and remembered. */
72
+ export declare function useEnvironment(environments: readonly EnvironmentOption[], defaultId: string): {
73
+ selected: string;
74
+ select: (id: string) => void;
75
+ current: import("@yaakdoc/core").EnvironmentRef | undefined;
76
+ };
77
+ /** Inline <head> script: applies the remembered environment before first paint. */
78
+ export declare function environmentScript(ids: readonly string[]): string;
79
+ /** CSS that shows the variants of the environment set on <html> (falls back to `hidden`). */
80
+ export declare function environmentCss(ids: readonly string[]): string;
81
+ type EnvironmentSwitcherProps = {
82
+ environments: EnvironmentOption[];
83
+ defaultId: string;
84
+ className?: string;
85
+ label?: string;
86
+ };
87
+ /** Unstyled environment switcher (native select). */
88
+ export declare const EnvironmentSwitcher: IslandComponent<EnvironmentSwitcherProps>;
89
+ //#endregion
90
+ //#region src/islands/llm.d.ts
91
+ export declare const LLM_PROVIDERS: Record<LlmProvider, {
92
+ label: string;
93
+ url: (prompt: string) => string;
94
+ }>;
95
+ export declare const llmPrompt: (markdownUrl: string) => string;
96
+ /** URL that opens an assistant with a prompt pointing at the (absolute) Markdown URL. */
97
+ export declare function openInUrl(provider: LlmProvider, markdownUrl: string): string;
98
+ type OpenInLinkProps = {
99
+ provider: LlmProvider;
100
+ /** Markdown URL; relative URLs are made absolute on click using the current location. */
101
+ markdownUrl: string;
102
+ className?: string;
103
+ label?: string;
104
+ };
105
+ /** Link that opens ChatGPT/Claude/... with the page's Markdown as context. */
106
+ export declare const OpenInLink: IslandComponent<OpenInLinkProps>;
107
+ //#endregion
108
+ //#region src/islands/nav.d.ts
109
+ /** Headless open/close state for a navigation element (`data-open` is set on the target). */
110
+ export declare function useNavToggle(target: string): {
111
+ open: boolean;
112
+ setOpen: (next: boolean) => void;
113
+ toggle: () => void;
114
+ };
115
+ type NavToggleProps = {
116
+ target: string;
117
+ label?: string;
118
+ className?: string;
119
+ };
120
+ /** Unstyled menu button for small screens. */
121
+ export declare const NavToggle: IslandComponent<NavToggleProps>;
122
+ /**
123
+ * Filters `[data-yd-nav-item]` elements inside `target` by their text. Folders
124
+ * (`[data-yd-nav-folder]`) without visible matches are hidden; matching folders are expanded.
125
+ */
126
+ export declare function filterNav(target: string, query: string): number;
127
+ type NavFilterProps = {
128
+ target: string;
129
+ placeholder?: string;
130
+ className?: string;
131
+ shortcut?: string;
132
+ };
133
+ /** Unstyled filter input for the navigation. Press `/` to focus. */
134
+ export declare const NavFilter: IslandComponent<NavFilterProps>;
135
+ /**
136
+ * Highlights the navigation link of the section currently in view (single-page mode). Sections are
137
+ * elements with `data-yd-section`; links are `a[href="#<id>"]` inside `target`.
138
+ */
139
+ export declare function useScrollSpy(target: string): void;
140
+ /** Renders nothing; marks the active nav link while scrolling. */
141
+ export declare const ScrollSpy: IslandComponent<{
142
+ target: string;
143
+ }>;
144
+ //#endregion
145
+ //#region src/islands/run-in-yaak.d.ts
146
+ /** Official "Run in Yaak" badge image. */
147
+ export declare const RUN_IN_YAAK_BADGE = "https://yaak.app/static/button.svg";
148
+ /** Link that opens Yaak and prompts to import the export/spec at `dataUrl` (must be absolute). */
149
+ export declare function runInYaakUrl(name: string, dataUrl: string): string;
150
+ /**
151
+ * "Run in Yaak" link for an export URL. Relative URLs (sites without a configured public URL) are
152
+ * made absolute on the client, after hydration.
153
+ */
154
+ export declare function useRunInYaakHref(name: string, dataUrl: string): string;
155
+ type RunInYaakLinkProps = {
156
+ name: string;
157
+ dataUrl: string;
158
+ /** Render the official badge image instead of text. */
159
+ badge?: boolean;
160
+ label?: string;
161
+ className?: string;
162
+ };
163
+ /** Unstyled "Run in Yaak" link (or the official badge with `badge`). */
164
+ export declare const RunInYaakLink: IslandComponent<RunInYaakLinkProps>;
165
+ //#endregion
166
+ //#region src/islands/index.d.ts
167
+ /** All built-in islands. Pass to `hydrateIslands()` (alongside your own) in a theme's client entry. */
168
+ export declare const builtinIslands: ReadonlyArray<IslandComponent<any>>;
169
+ //#endregion
170
+ export { type ColorMode, type ColorModePreference, type ColorModeToggleProps, type CopyButtonProps, type CopySource, type EnvironmentOption, type EnvironmentSwitcherProps, type IslandComponent, type LlmProvider, type NavFilterProps, type NavToggleProps, type OpenInLinkProps, type RunInYaakLinkProps, TAB_STORAGE_KEY, TabList, type TabListProps, createIsland, tabElementIds, tabGroupIds, tabPanelProps, useTabs, visibleTabPanelSelector };
@@ -0,0 +1,152 @@
1
+ import { a as ENVIRONMENT_STORAGE_KEY, c as environmentCss, d as resolveUrl, f as storage, i as useRunInYaakHref, l as environmentScript, m as createIsland, n as RunInYaakLink, o as EnvironmentSwitcher, r as runInYaakUrl, s as applyEnvironment, t as RUN_IN_YAAK_BADGE, u as useEnvironment } from "./run-in-yaak-CMMcDS7S.js";
2
+ import { a as CopyButton, c as TabList, d as tabPanelProps, f as useTabs, i as openInUrl, l as tabElementIds, n as OpenInLink, o as useCopy, p as visibleTabPanelSelector, r as llmPrompt, s as TAB_STORAGE_KEY, t as LLM_PROVIDERS, u as tabGroupIds } from "./llm-Ddn5hWnS.js";
3
+ import { a as useColorMode, i as colorModeScript, n as ColorModeToggle, r as applyColorMode, t as COLOR_MODE_STORAGE_KEY } from "./color-mode-B-vJCVkP.js";
4
+ import { useCallback, useEffect, useState } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/islands/nav.tsx
7
+ /** Headless open/close state for a navigation element (`data-open` is set on the target). */
8
+ function useNavToggle(target) {
9
+ const [open, setOpen] = useState(false);
10
+ const set = useCallback((next) => {
11
+ setOpen(next);
12
+ const element = document.querySelector(target);
13
+ if (element) element.dataset.open = String(next);
14
+ document.documentElement.dataset.navOpen = String(next);
15
+ }, [target]);
16
+ useEffect(() => {
17
+ const element = document.querySelector(target);
18
+ const onKey = (event) => event.key === "Escape" && set(false);
19
+ const onClick = (event) => {
20
+ if (event.target.closest("a")) set(false);
21
+ };
22
+ window.addEventListener("keydown", onKey);
23
+ element?.addEventListener("click", onClick);
24
+ return () => {
25
+ window.removeEventListener("keydown", onKey);
26
+ element?.removeEventListener("click", onClick);
27
+ };
28
+ }, [target, set]);
29
+ return {
30
+ open,
31
+ setOpen: set,
32
+ toggle: () => set(!open)
33
+ };
34
+ }
35
+ function NavToggleImpl({ target, label = "Toggle navigation", className }) {
36
+ const { open, toggle } = useNavToggle(target);
37
+ return /* @__PURE__ */ jsx("button", {
38
+ type: "button",
39
+ "data-yd": "nav-toggle",
40
+ "data-state": open ? "open" : "closed",
41
+ "aria-expanded": open,
42
+ "aria-label": label,
43
+ className,
44
+ onClick: toggle,
45
+ children: /* @__PURE__ */ jsx("span", {
46
+ "aria-hidden": "true",
47
+ children: "☰"
48
+ })
49
+ });
50
+ }
51
+ /** Unstyled menu button for small screens. */
52
+ const NavToggle = createIsland("yd:nav-toggle", NavToggleImpl);
53
+ /**
54
+ * Filters `[data-yd-nav-item]` elements inside `target` by their text. Folders
55
+ * (`[data-yd-nav-folder]`) without visible matches are hidden; matching folders are expanded.
56
+ */
57
+ function filterNav(target, query) {
58
+ const root = document.querySelector(target);
59
+ if (!root) return 0;
60
+ const needle = query.trim().toLowerCase();
61
+ let visible = 0;
62
+ for (const item of root.querySelectorAll("[data-yd-nav-item]")) {
63
+ const text = (item.dataset.ydNavLabel ?? item.textContent ?? "").toLowerCase();
64
+ const match = !needle || text.includes(needle);
65
+ item.hidden = !match;
66
+ if (match) visible++;
67
+ }
68
+ const folders = [...root.querySelectorAll("[data-yd-nav-folder]")].reverse();
69
+ for (const folder of folders) {
70
+ const label = (folder.dataset.ydNavLabel ?? "").toLowerCase();
71
+ const hasMatch = !needle || label.includes(needle) || folder.querySelector("[data-yd-nav-item]:not([hidden])") !== null;
72
+ folder.hidden = !hasMatch;
73
+ if (needle && hasMatch) {
74
+ folder.querySelector("details")?.setAttribute("open", "");
75
+ if (label.includes(needle)) for (const child of folder.querySelectorAll("[hidden]")) child.hidden = false;
76
+ }
77
+ }
78
+ return visible;
79
+ }
80
+ function NavFilterImpl({ target, placeholder = "Filter...", className, shortcut = "/" }) {
81
+ const [query, setQuery] = useState("");
82
+ useEffect(() => {
83
+ const onKey = (event) => {
84
+ const active = document.activeElement;
85
+ if (event.key !== shortcut || active?.matches("input, textarea, [contenteditable]")) return;
86
+ event.preventDefault();
87
+ document.querySelector("[data-yd=\"nav-filter\"]")?.focus();
88
+ };
89
+ window.addEventListener("keydown", onKey);
90
+ return () => window.removeEventListener("keydown", onKey);
91
+ }, [shortcut]);
92
+ return /* @__PURE__ */ jsx("input", {
93
+ type: "search",
94
+ "data-yd": "nav-filter",
95
+ className,
96
+ placeholder,
97
+ "aria-label": placeholder,
98
+ value: query,
99
+ onChange: (event) => {
100
+ setQuery(event.target.value);
101
+ filterNav(target, event.target.value);
102
+ }
103
+ });
104
+ }
105
+ /** Unstyled filter input for the navigation. Press `/` to focus. */
106
+ const NavFilter = createIsland("yd:nav-filter", NavFilterImpl);
107
+ /**
108
+ * Highlights the navigation link of the section currently in view (single-page mode). Sections are
109
+ * elements with `data-yd-section`; links are `a[href="#<id>"]` inside `target`.
110
+ */
111
+ function useScrollSpy(target) {
112
+ useEffect(() => {
113
+ const nav = document.querySelector(target);
114
+ const sections = document.querySelectorAll("[data-yd-section]");
115
+ if (!nav || sections.length === 0 || typeof IntersectionObserver === "undefined") return;
116
+ let current = null;
117
+ const observer = new IntersectionObserver((entries) => {
118
+ const top = entries.filter((entry) => entry.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
119
+ if (!top) return;
120
+ const link = nav.querySelector(`a[href$="#${CSS.escape(top.target.id)}"]`);
121
+ if (!link || link === current) return;
122
+ current?.removeAttribute("aria-current");
123
+ link.setAttribute("aria-current", "location");
124
+ current = link;
125
+ for (let el = link.parentElement; el && el !== nav; el = el.parentElement) if (el instanceof HTMLDetailsElement && !el.open) el.open = true;
126
+ }, { rootMargin: "-10% 0px -70% 0px" });
127
+ for (const section of sections) observer.observe(section);
128
+ return () => observer.disconnect();
129
+ }, [target]);
130
+ }
131
+ function ScrollSpyImpl({ target }) {
132
+ useScrollSpy(target);
133
+ return null;
134
+ }
135
+ /** Renders nothing; marks the active nav link while scrolling. */
136
+ const ScrollSpy = createIsland("yd:scroll-spy", ScrollSpyImpl);
137
+ //#endregion
138
+ //#region src/islands/index.ts
139
+ /** All built-in islands. Pass to `hydrateIslands()` (alongside your own) in a theme's client entry. */
140
+ const builtinIslands = [
141
+ ColorModeToggle,
142
+ CopyButton,
143
+ EnvironmentSwitcher,
144
+ TabList,
145
+ NavToggle,
146
+ NavFilter,
147
+ ScrollSpy,
148
+ OpenInLink,
149
+ RunInYaakLink
150
+ ];
151
+ //#endregion
152
+ export { COLOR_MODE_STORAGE_KEY, ColorModeToggle, CopyButton, ENVIRONMENT_STORAGE_KEY, EnvironmentSwitcher, LLM_PROVIDERS, NavFilter, NavToggle, OpenInLink, RUN_IN_YAAK_BADGE, RunInYaakLink, ScrollSpy, TAB_STORAGE_KEY, TabList, applyColorMode, applyEnvironment, builtinIslands, colorModeScript, createIsland, environmentCss, environmentScript, filterNav, llmPrompt, openInUrl, resolveUrl, runInYaakUrl, storage, tabElementIds, tabGroupIds, tabPanelProps, useColorMode, useCopy, useEnvironment, useNavToggle, useRunInYaakHref, useScrollSpy, useTabs, visibleTabPanelSelector };
@@ -0,0 +1,205 @@
1
+ import { d as resolveUrl, f as storage, m as createIsland } from "./run-in-yaak-CMMcDS7S.js";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/islands/tabs.tsx
5
+ const TAB_STORAGE_KEY = "yaakdoc-tab";
6
+ /** Tab group names of a request's code examples and example responses. */
7
+ const tabGroupIds = {
8
+ examples: (anchor) => `ex-${anchor}`,
9
+ responses: (anchor) => `res-${anchor}`
10
+ };
11
+ /** Ids linking a tab to its panel. `variant` tells apart copies of a panel (one per environment). */
12
+ function tabElementIds(group, id, variant = 0) {
13
+ return {
14
+ tab: `${group}-tab-${id}`,
15
+ panel: `${group}-panel-${id}${variant ? `-${variant}` : ""}`
16
+ };
17
+ }
18
+ /** Props for a server-rendered panel controlled by {@link TabList}. */
19
+ function tabPanelProps(group, id, options) {
20
+ const ids = tabElementIds(group, id, options.variant);
21
+ return {
22
+ role: "tabpanel",
23
+ id: ids.panel,
24
+ "aria-labelledby": ids.tab,
25
+ "data-yd-tab-panel": id,
26
+ hidden: !options.selected
27
+ };
28
+ }
29
+ /** Selector of an element (default: the code) inside the visible panel of a tab group. */
30
+ const visibleTabPanelSelector = (group, inner = "pre") => `[data-yd-tab-group="${group}"] [data-yd-tab-panel]:not([hidden]) ${inner}`;
31
+ const TAB_EVENT = "yaakdoc:tab";
32
+ function showPanel(group, id) {
33
+ const container = document.querySelector(`[data-yd-tab-group="${CSS.escape(group)}"]`);
34
+ if (!container) return;
35
+ for (const panel of container.querySelectorAll("[data-yd-tab-panel]")) panel.hidden = panel.dataset.ydTabPanel !== id;
36
+ }
37
+ /**
38
+ * Headless tab state for a panel group rendered on the server. Panels are elements with
39
+ * `data-yd-tab-panel="<id>"` inside `[data-yd-tab-group="<group>"]`. The selection is synced across
40
+ * all groups on the page (pick "Python" once, see Python everywhere) and remembered.
41
+ */
42
+ function useTabs(group, ids, sync = true) {
43
+ const [selected, setSelected] = useState(ids[0] ?? "");
44
+ const select = useCallback((id, broadcast = true) => {
45
+ if (!ids.includes(id)) return;
46
+ setSelected(id);
47
+ showPanel(group, id);
48
+ if (sync && broadcast) {
49
+ storage.set(TAB_STORAGE_KEY, id);
50
+ window.dispatchEvent(new CustomEvent(TAB_EVENT, { detail: {
51
+ id,
52
+ group
53
+ } }));
54
+ }
55
+ }, [
56
+ group,
57
+ ids,
58
+ sync
59
+ ]);
60
+ useEffect(() => {
61
+ if (!sync) return;
62
+ const stored = storage.get(TAB_STORAGE_KEY);
63
+ if (stored) select(stored, false);
64
+ const onTab = (event) => {
65
+ const detail = event.detail;
66
+ if (detail.group !== group) select(detail.id, false);
67
+ };
68
+ window.addEventListener(TAB_EVENT, onTab);
69
+ return () => window.removeEventListener(TAB_EVENT, onTab);
70
+ }, [
71
+ group,
72
+ select,
73
+ sync
74
+ ]);
75
+ return {
76
+ selected,
77
+ select
78
+ };
79
+ }
80
+ function TabListImpl({ group, tabs, sync = true, className, tabClassName, label = "Code examples" }) {
81
+ const { selected, select } = useTabs(group, tabs.map((tab) => tab.id), sync);
82
+ const onKeyDown = (event, index) => {
83
+ const delta = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
84
+ if (!delta) return;
85
+ event.preventDefault();
86
+ const next = tabs[(index + delta + tabs.length) % tabs.length];
87
+ select(next.id);
88
+ (event.currentTarget.parentElement?.children[tabs.indexOf(next)])?.focus();
89
+ };
90
+ return /* @__PURE__ */ jsx("div", {
91
+ role: "tablist",
92
+ "aria-label": label,
93
+ "data-yd": "tab-list",
94
+ className,
95
+ children: tabs.map((tab, index) => /* @__PURE__ */ jsx("button", {
96
+ type: "button",
97
+ role: "tab",
98
+ id: tabElementIds(group, tab.id).tab,
99
+ "aria-selected": selected === tab.id,
100
+ "aria-controls": tabElementIds(group, tab.id).panel,
101
+ tabIndex: selected === tab.id ? 0 : -1,
102
+ "data-state": selected === tab.id ? "active" : "inactive",
103
+ className: [tabClassName, tab.className].filter(Boolean).join(" ") || void 0,
104
+ onClick: () => select(tab.id),
105
+ onKeyDown: (event) => onKeyDown(event, index),
106
+ children: tab.label
107
+ }, tab.id))
108
+ });
109
+ }
110
+ /** Accessible tab list controlling server-rendered panels (see {@link useTabs}). */
111
+ const TabList = createIsland("yd:tab-list", TabListImpl);
112
+ //#endregion
113
+ //#region src/islands/copy.tsx
114
+ async function resolveCopyText(source) {
115
+ if (source.text !== void 0) return source.text;
116
+ if (source.url) {
117
+ const response = await fetch(source.url);
118
+ if (!response.ok) throw new Error(`Failed to fetch ${source.url}: ${response.status}`);
119
+ return response.text();
120
+ }
121
+ if (source.target) {
122
+ const matches = [...document.querySelectorAll(source.target)];
123
+ return (matches.find((el) => el.getClientRects().length > 0) ?? matches[0])?.textContent ?? "";
124
+ }
125
+ return "";
126
+ }
127
+ /** Headless copy-to-clipboard state. `copied` resets after `timeout` ms. */
128
+ function useCopy(timeout = 2e3) {
129
+ const [state, setState] = useState("idle");
130
+ const timer = useRef(void 0);
131
+ useEffect(() => () => clearTimeout(timer.current), []);
132
+ const copy = useCallback(async (source) => {
133
+ try {
134
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) await navigator.clipboard.write([new ClipboardItem({ "text/plain": resolveCopyText(source).then((text) => new Blob([text], { type: "text/plain" })) })]);
135
+ else await navigator.clipboard.writeText(await resolveCopyText(source));
136
+ setState("copied");
137
+ } catch (error) {
138
+ console.error("[yaakdoc] Copy failed", error);
139
+ setState("error");
140
+ }
141
+ clearTimeout(timer.current);
142
+ timer.current = setTimeout(() => setState("idle"), timeout);
143
+ }, [timeout]);
144
+ return {
145
+ state,
146
+ copied: state === "copied",
147
+ copy
148
+ };
149
+ }
150
+ function CopyButtonImpl({ label = "Copy", copiedLabel = "Copied!", className, ...source }) {
151
+ const { state, copy } = useCopy();
152
+ return /* @__PURE__ */ jsx("button", {
153
+ type: "button",
154
+ "data-yd": "copy-button",
155
+ "data-state": state,
156
+ className,
157
+ onClick: () => copy(source),
158
+ "aria-live": "polite",
159
+ children: state === "copied" ? copiedLabel : label
160
+ });
161
+ }
162
+ /** Unstyled copy button. Copies `text`, the text of `target`, or the body of `url`. */
163
+ const CopyButton = createIsland("yd:copy-button", CopyButtonImpl);
164
+ //#endregion
165
+ //#region src/islands/llm.tsx
166
+ const LLM_PROVIDERS = {
167
+ chatgpt: {
168
+ label: "ChatGPT",
169
+ url: (q) => `https://chatgpt.com/?hints=search&q=${encodeURIComponent(q)}`
170
+ },
171
+ claude: {
172
+ label: "Claude",
173
+ url: (q) => `https://claude.ai/new?q=${encodeURIComponent(q)}`
174
+ },
175
+ perplexity: {
176
+ label: "Perplexity",
177
+ url: (q) => `https://www.perplexity.ai/search?q=${encodeURIComponent(q)}`
178
+ }
179
+ };
180
+ const llmPrompt = (markdownUrl) => `Read ${markdownUrl} so I can ask questions about this API documentation.`;
181
+ /** URL that opens an assistant with a prompt pointing at the (absolute) Markdown URL. */
182
+ function openInUrl(provider, markdownUrl) {
183
+ return LLM_PROVIDERS[provider].url(llmPrompt(markdownUrl));
184
+ }
185
+ function OpenInLinkImpl({ provider, markdownUrl, className, label }) {
186
+ return /* @__PURE__ */ jsx("a", {
187
+ "data-yd": "open-in",
188
+ "data-provider": provider,
189
+ className,
190
+ href: openInUrl(provider, markdownUrl),
191
+ target: "_blank",
192
+ rel: "noopener noreferrer",
193
+ onClick: (event) => {
194
+ const absolute = resolveUrl(markdownUrl);
195
+ if (absolute === markdownUrl) return;
196
+ event.preventDefault();
197
+ window.open(openInUrl(provider, absolute), "_blank", "noopener,noreferrer");
198
+ },
199
+ children: label ?? `Open in ${LLM_PROVIDERS[provider].label}`
200
+ });
201
+ }
202
+ /** Link that opens ChatGPT/Claude/... with the page's Markdown as context. */
203
+ const OpenInLink = createIsland("yd:open-in", OpenInLinkImpl);
204
+ //#endregion
205
+ export { CopyButton as a, TabList as c, tabPanelProps as d, useTabs as f, openInUrl as i, tabElementIds as l, OpenInLink as n, useCopy as o, visibleTabPanelSelector as p, llmPrompt as r, TAB_STORAGE_KEY as s, LLM_PROVIDERS as t, tabGroupIds as u };