@djangocfg/ui-tools 2.1.287 → 2.1.290

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +14 -3
  2. package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
  3. package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
  4. package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
  5. package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
  6. package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
  7. package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
  9. package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
  10. package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
  11. package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
  12. package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
  13. package/dist/chunk-EFWOJPA6.mjs.map +1 -0
  14. package/dist/index.cjs +10 -10
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +34 -0
  17. package/dist/index.d.ts +34 -0
  18. package/dist/index.mjs +5 -5
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +21 -14
  21. package/src/components/markdown/MarkdownMessage.tsx +46 -0
  22. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
  23. package/src/tools/OpenapiViewer/README.md +114 -6
  24. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
  25. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
  26. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
  27. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
  39. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
  40. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
  41. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
  42. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
  43. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
  44. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
  45. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
  46. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
  47. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
  48. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
  49. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
  50. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
  51. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
  52. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
  53. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
  54. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
  55. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
  56. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
  57. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
  58. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
  59. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
  60. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
  61. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
  62. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
  63. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
  64. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
  65. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
  66. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
  67. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
  68. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
  69. package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
  70. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
  71. package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
  72. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
  73. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
  74. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
  75. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
  76. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
  77. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
  78. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
  79. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
  80. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
  81. package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
  82. package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
  83. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
  84. package/src/tools/OpenapiViewer/types.ts +46 -1
  85. package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
  86. package/src/tools/OpenapiViewer/utils/index.ts +3 -0
  87. package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
  88. package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
  89. package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
  90. package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
  91. package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
  92. package/src/tools/PrettyCode/index.tsx +13 -0
  93. package/src/tools/PrettyCode/lazy.tsx +5 -0
  94. package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
  95. package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
  96. package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
  97. package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
  98. package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
  99. package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
  100. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
  101. package/dist/chunk-IULI4XII.cjs.map +0 -1
  102. package/dist/chunk-VZGQC3NG.mjs.map +0 -1
  103. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
  104. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
  105. package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
@@ -0,0 +1,3457 @@
1
+ import { deduplicateEndpoints, dereferenceSchema, resolveBaseUrl, usePlaygroundContext, toMarkdown, toCompactJson, toRawJson, formatBytes, MarkdownMessage, CODE_SAMPLE_TARGETS, buildHarRequest, resolveAbsolute, renderSnippet, PrettyCode_default, relativePath, endpointToMarkdown, isValidJson, findApiKeyById, parseRequestHeaders, UrlBuilder, sampleSchemaJson, joinUrl } from './chunk-EFWOJPA6.mjs';
2
+ import { JsonTree_default } from './chunk-LFWQ36LJ.mjs';
3
+ import './chunk-SSUOENAZ.mjs';
4
+ import { __name } from './chunk-CGILA3WO.mjs';
5
+ import React12, { createContext, useRef, useCallback, useEffect, useMemo, useState, useContext } from 'react';
6
+ import { groupBy, orderBy, partition, sortBy, keyBy } from 'lodash-es';
7
+ import { Tooltip, TooltipTrigger, TooltipContent, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Input, Combobox, SafeTooltipProvider, CopyButton, Switch, Textarea, SidePanel, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, Skeleton, TooltipProvider } from '@djangocfg/ui-core/components';
8
+ import { toast, useMediaQuery } from '@djangocfg/ui-core/hooks';
9
+ import consola from 'consola';
10
+ import { ChevronRight, Sparkles, ChevronDown, Check, Search, X, Link2, FileCode2, ChevronsDownUp, ChevronsUpDown, Play, Minus, Plus, RotateCcw, Send, Key, Terminal, Info, ShieldCheck, Loader2, WifiOff, AlertCircle } from 'lucide-react';
11
+ import { cn } from '@djangocfg/ui-core/lib';
12
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
+ import { create } from 'zustand';
14
+ import { persist, createJSONStorage } from 'zustand/middleware';
15
+
16
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
17
+ var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl, schemaId, specRoot) => {
18
+ const endpoints = [];
19
+ if (!schema.paths) return [];
20
+ for (const [path, methods] of Object.entries(schema.paths)) {
21
+ for (const method of HTTP_METHODS) {
22
+ const op = methods[method];
23
+ if (!op) continue;
24
+ const methodUpper = method.toUpperCase();
25
+ const summary = (op.summary || "").trim();
26
+ const description = op.description || summary || `${methodUpper} ${path}`;
27
+ const category = op.tags?.[0] || "Other";
28
+ const parameters = [];
29
+ const allParams = [...methods.parameters || [], ...op.parameters || []];
30
+ for (const param of allParams) {
31
+ parameters.push({
32
+ name: param.name,
33
+ type: param.schema?.type || "string",
34
+ required: param.required || false,
35
+ description: param.description
36
+ });
37
+ }
38
+ const responses = [];
39
+ if (op.responses) {
40
+ for (const [code, response] of Object.entries(op.responses)) {
41
+ const respContent = response.content;
42
+ const contentKeys = respContent ? Object.keys(respContent) : [];
43
+ const chosenContentType = respContent?.["application/json"] ? "application/json" : contentKeys[0];
44
+ const chosen = chosenContentType ? respContent?.[chosenContentType] : void 0;
45
+ const respSchema = chosen?.schema;
46
+ responses.push({
47
+ code,
48
+ description: response.description || `Response ${code}`,
49
+ contentType: chosenContentType,
50
+ schema: respSchema,
51
+ example: respSchema ? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot) : void 0
52
+ });
53
+ }
54
+ }
55
+ let requestBody;
56
+ if (op.requestBody) {
57
+ const content = op.requestBody.content;
58
+ const mediaType = content?.["application/json"] || content?.[Object.keys(content || {})[0]];
59
+ const rawSchema = mediaType?.schema;
60
+ requestBody = {
61
+ type: rawSchema?.type || "object",
62
+ description: op.requestBody.description,
63
+ schema: rawSchema,
64
+ example: rawSchema ? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot) : void 0
65
+ };
66
+ }
67
+ const endpoint = {
68
+ name: path.split("/").pop() || path,
69
+ method: methodUpper,
70
+ path: baseUrl ? joinUrl(baseUrl, path) : path,
71
+ summary,
72
+ description,
73
+ category,
74
+ parameters: parameters.length > 0 ? parameters : void 0,
75
+ requestBody,
76
+ responses: responses.length > 0 ? responses : void 0,
77
+ schemaId
78
+ };
79
+ endpoints.push(endpoint);
80
+ }
81
+ }
82
+ return endpoints;
83
+ }, "extractEndpoints");
84
+ var getCategories = /* @__PURE__ */ __name((endpoints) => {
85
+ const categories = /* @__PURE__ */ new Set();
86
+ endpoints.forEach((endpoint) => categories.add(endpoint.category));
87
+ return Array.from(categories).sort();
88
+ }, "getCategories");
89
+ var fetchSchema = /* @__PURE__ */ __name(async (url) => {
90
+ const response = await fetch(url, {
91
+ headers: {
92
+ "Accept": "application/json"
93
+ }
94
+ });
95
+ if (!response.ok) {
96
+ throw new Error(`Failed to fetch schema: ${response.statusText}`);
97
+ }
98
+ return response.json();
99
+ }, "fetchSchema");
100
+ function useOpenApiSchema({
101
+ schemas,
102
+ defaultSchemaId,
103
+ baseUrl: configBaseUrl,
104
+ preloadAll = false
105
+ }) {
106
+ const [loading, setLoading] = useState(true);
107
+ const [error, setError] = useState(null);
108
+ const [currentSchemaId, setCurrentSchemaId] = useState(
109
+ defaultSchemaId || schemas[0]?.id
110
+ );
111
+ const [loadedSchemas, setLoadedSchemas] = useState(
112
+ /* @__PURE__ */ new Map()
113
+ );
114
+ const [loadStates, setLoadStates] = useState(/* @__PURE__ */ new Map());
115
+ const currentSchema = useMemo(
116
+ () => schemas.find((s) => s.id === currentSchemaId) || null,
117
+ [schemas, currentSchemaId]
118
+ );
119
+ const currentOpenApiSchema = useMemo(
120
+ () => currentSchemaId ? loadedSchemas.get(currentSchemaId) : null,
121
+ [loadedSchemas, currentSchemaId]
122
+ );
123
+ const dereferencedSchema = useMemo(
124
+ () => currentOpenApiSchema ? dereferenceSchema(currentOpenApiSchema) : null,
125
+ [currentOpenApiSchema]
126
+ );
127
+ const resolvedBaseUrl = useMemo(
128
+ () => resolveBaseUrl({
129
+ schemaSource: currentSchema?.baseUrl,
130
+ config: configBaseUrl,
131
+ fromServers: currentOpenApiSchema?.servers?.[0]?.url
132
+ }),
133
+ [currentSchema?.baseUrl, configBaseUrl, currentOpenApiSchema]
134
+ );
135
+ const endpoints = useMemo(
136
+ () => dereferencedSchema ? extractEndpoints(dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema ?? void 0) : [],
137
+ [dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema]
138
+ );
139
+ const categories = useMemo(() => getCategories(endpoints), [endpoints]);
140
+ const schemaInfo = useMemo(() => {
141
+ if (!currentOpenApiSchema?.info) return null;
142
+ const { title, version, description } = currentOpenApiSchema.info;
143
+ return {
144
+ title,
145
+ version,
146
+ description,
147
+ servers: currentOpenApiSchema.servers
148
+ };
149
+ }, [currentOpenApiSchema]);
150
+ useEffect(() => {
151
+ if (preloadAll) return;
152
+ if (!currentSchema) return;
153
+ if (loadedSchemas.has(currentSchema.id)) {
154
+ setLoading(false);
155
+ return;
156
+ }
157
+ setLoading(true);
158
+ setError(null);
159
+ fetchSchema(currentSchema.url).then((schema) => {
160
+ setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
161
+ consola.success(`Schema loaded: ${currentSchema.name}`);
162
+ setLoading(false);
163
+ }).catch((err) => {
164
+ consola.error(`Error loading schema from ${currentSchema.url}:`, err);
165
+ setError(err instanceof Error ? err.message : "Failed to load schema");
166
+ setLoading(false);
167
+ });
168
+ }, [currentSchema, loadedSchemas, preloadAll]);
169
+ useEffect(() => {
170
+ if (!preloadAll) return;
171
+ if (schemas.length === 0) {
172
+ setLoading(false);
173
+ return;
174
+ }
175
+ let cancelled = false;
176
+ const pending = schemas.filter((s) => !loadedSchemas.has(s.id));
177
+ if (pending.length === 0) {
178
+ setLoading(false);
179
+ return;
180
+ }
181
+ setLoading(true);
182
+ setLoadStates((prev) => {
183
+ const next = new Map(prev);
184
+ for (const s of pending) next.set(s.id, { loading: true, error: null });
185
+ return next;
186
+ });
187
+ Promise.allSettled(
188
+ pending.map(
189
+ (s) => fetchSchema(s.url).then((schema) => ({ id: s.id, name: s.name, schema }))
190
+ )
191
+ ).then((results) => {
192
+ if (cancelled) return;
193
+ setLoadedSchemas((prev) => {
194
+ const next = new Map(prev);
195
+ for (const r of results) {
196
+ if (r.status === "fulfilled") {
197
+ next.set(r.value.id, r.value.schema);
198
+ consola.success(`Schema loaded: ${r.value.name}`);
199
+ }
200
+ }
201
+ return next;
202
+ });
203
+ setLoadStates((prev) => {
204
+ const next = new Map(prev);
205
+ results.forEach((r, i) => {
206
+ const src = pending[i];
207
+ if (r.status === "fulfilled") {
208
+ next.set(src.id, { loading: false, error: null });
209
+ } else {
210
+ const msg = r.reason instanceof Error ? r.reason.message : "Failed to load schema";
211
+ consola.error(`Error loading schema from ${src.url}:`, r.reason);
212
+ next.set(src.id, { loading: false, error: msg });
213
+ }
214
+ });
215
+ return next;
216
+ });
217
+ setLoading(false);
218
+ });
219
+ return () => {
220
+ cancelled = true;
221
+ };
222
+ }, [preloadAll, schemas, loadedSchemas]);
223
+ const schemasData = useMemo(() => {
224
+ if (!preloadAll) return [];
225
+ return schemas.map((src) => {
226
+ const raw = loadedSchemas.get(src.id) ?? null;
227
+ const deref = raw ? dereferenceSchema(raw) : null;
228
+ const resolved = resolveBaseUrl({
229
+ schemaSource: src.baseUrl,
230
+ config: configBaseUrl,
231
+ fromServers: raw?.servers?.[0]?.url
232
+ });
233
+ const info = raw?.info ? {
234
+ title: raw.info.title,
235
+ version: raw.info.version,
236
+ description: raw.info.description,
237
+ servers: raw.servers
238
+ } : null;
239
+ const eps = deref ? extractEndpoints(deref, resolved, src.id, raw ?? void 0) : [];
240
+ const state = loadStates.get(src.id) ?? { loading: !raw, error: null };
241
+ return {
242
+ source: src,
243
+ info,
244
+ rawSchema: raw,
245
+ endpoints: eps,
246
+ resolvedBaseUrl: resolved || void 0,
247
+ loading: state.loading,
248
+ error: state.error
249
+ };
250
+ });
251
+ }, [preloadAll, schemas, loadedSchemas, loadStates, configBaseUrl]);
252
+ const setCurrentSchema = useCallback((schemaId) => {
253
+ setCurrentSchemaId(schemaId);
254
+ }, []);
255
+ const refresh = useCallback(() => {
256
+ if (!currentSchema) return;
257
+ setLoading(true);
258
+ setError(null);
259
+ setLoadedSchemas((prev) => {
260
+ const next = new Map(prev);
261
+ next.delete(currentSchema.id);
262
+ return next;
263
+ });
264
+ fetchSchema(currentSchema.url).then((schema) => {
265
+ setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
266
+ consola.success(`Schema refreshed: ${currentSchema.name}`);
267
+ setLoading(false);
268
+ }).catch((err) => {
269
+ consola.error(`Error refreshing schema from ${currentSchema.url}:`, err);
270
+ setError(err instanceof Error ? err.message : "Failed to refresh schema");
271
+ setLoading(false);
272
+ });
273
+ }, [currentSchema]);
274
+ return {
275
+ loading,
276
+ error,
277
+ endpoints,
278
+ categories,
279
+ schemas,
280
+ currentSchema,
281
+ schemaInfo,
282
+ rawSchema: currentOpenApiSchema ?? null,
283
+ // Consumers expect ``undefined`` when no base URL was resolved (for
284
+ // conditional ``{ baseUrl?: … }`` plumbing). Turn the empty-string
285
+ // convention from the resolver into undefined at the API boundary.
286
+ resolvedBaseUrl: resolvedBaseUrl || void 0,
287
+ setCurrentSchema,
288
+ refresh,
289
+ schemasData
290
+ };
291
+ }
292
+ __name(useOpenApiSchema, "useOpenApiSchema");
293
+ function parseDocsHash(hash) {
294
+ const raw = hash.startsWith("#") ? hash.slice(1) : hash;
295
+ if (!raw) return { schemaId: null, anchor: null };
296
+ const [schemaId = null, ...rest] = raw.split("/");
297
+ const anchor = rest.length > 0 ? rest.join("/") : null;
298
+ return {
299
+ schemaId: schemaId || null,
300
+ anchor: anchor || null
301
+ };
302
+ }
303
+ __name(parseDocsHash, "parseDocsHash");
304
+ function buildDocsHash(schemaId, anchor) {
305
+ if (!schemaId && !anchor) return "";
306
+ if (schemaId && anchor) return `#${schemaId}/${anchor}`;
307
+ if (schemaId) return `#${schemaId}`;
308
+ return anchor ? `#${anchor}` : "";
309
+ }
310
+ __name(buildDocsHash, "buildDocsHash");
311
+ function useDocsUrlSync({
312
+ enabled,
313
+ currentSchemaId,
314
+ activeAnchor,
315
+ onHashTarget
316
+ }) {
317
+ const primedRef = useRef(false);
318
+ const onHashTargetRef = useRef(onHashTarget);
319
+ useEffect(() => {
320
+ onHashTargetRef.current = onHashTarget;
321
+ }, [onHashTarget]);
322
+ useEffect(() => {
323
+ if (!enabled || typeof window === "undefined") return;
324
+ const apply = /* @__PURE__ */ __name(() => {
325
+ onHashTargetRef.current(parseDocsHash(window.location.hash));
326
+ }, "apply");
327
+ apply();
328
+ primedRef.current = true;
329
+ window.addEventListener("hashchange", apply);
330
+ window.addEventListener("popstate", apply);
331
+ return () => {
332
+ window.removeEventListener("hashchange", apply);
333
+ window.removeEventListener("popstate", apply);
334
+ };
335
+ }, [enabled]);
336
+ useEffect(() => {
337
+ if (!enabled || typeof window === "undefined") return;
338
+ if (!primedRef.current) return;
339
+ const next = buildDocsHash(currentSchemaId, activeAnchor);
340
+ const current = window.location.hash;
341
+ if (next === current) return;
342
+ const url = next ? `${window.location.pathname}${window.location.search}${next}` : `${window.location.pathname}${window.location.search}`;
343
+ window.history.replaceState(window.history.state, "", url);
344
+ }, [enabled, currentSchemaId, activeAnchor]);
345
+ const pushTarget = useCallback(
346
+ (schemaId, anchor) => {
347
+ if (!enabled || typeof window === "undefined") return;
348
+ const next = buildDocsHash(schemaId, anchor);
349
+ const url = next ? `${window.location.pathname}${window.location.search}${next}` : `${window.location.pathname}${window.location.search}`;
350
+ window.history.pushState(window.history.state, "", url);
351
+ },
352
+ [enabled]
353
+ );
354
+ return { pushTarget };
355
+ }
356
+ __name(useDocsUrlSync, "useDocsUrlSync");
357
+ var EMPTY_DRAFT = { parameters: {}, requestBody: "" };
358
+ function storageKey(schemaId, ep) {
359
+ if (!schemaId || !ep) return null;
360
+ return `openapi-playground:draft:${schemaId}:${ep.method}:${ep.path}`;
361
+ }
362
+ __name(storageKey, "storageKey");
363
+ function readDraft(key) {
364
+ if (!key || typeof window === "undefined") return EMPTY_DRAFT;
365
+ try {
366
+ const raw = window.localStorage.getItem(key);
367
+ if (!raw) return EMPTY_DRAFT;
368
+ const parsed = JSON.parse(raw);
369
+ return {
370
+ parameters: parsed?.parameters ?? {},
371
+ requestBody: typeof parsed?.requestBody === "string" ? parsed.requestBody : ""
372
+ };
373
+ } catch {
374
+ return EMPTY_DRAFT;
375
+ }
376
+ }
377
+ __name(readDraft, "readDraft");
378
+ function writeDraft(key, value) {
379
+ if (!key || typeof window === "undefined") return;
380
+ try {
381
+ if (Object.keys(value.parameters).length === 0 && !value.requestBody) {
382
+ window.localStorage.removeItem(key);
383
+ return;
384
+ }
385
+ window.localStorage.setItem(key, JSON.stringify(value));
386
+ } catch {
387
+ }
388
+ }
389
+ __name(writeDraft, "writeDraft");
390
+ function useEndpointDraft(schemaId, endpoint) {
391
+ const key = storageKey(schemaId, endpoint);
392
+ const [draft, setDraftSnapshot] = useState(() => readDraft(key));
393
+ const loadedKeyRef = useRef(key);
394
+ useEffect(() => {
395
+ if (loadedKeyRef.current === key) return;
396
+ loadedKeyRef.current = key;
397
+ setDraftSnapshot(readDraft(key));
398
+ }, [key]);
399
+ const keyRef = useRef(key);
400
+ useEffect(() => {
401
+ keyRef.current = key;
402
+ }, [key]);
403
+ const latestRef = useRef(draft);
404
+ useEffect(() => {
405
+ latestRef.current = draft;
406
+ }, [draft]);
407
+ const setParameters = useCallback((params) => {
408
+ const next = {
409
+ parameters: params,
410
+ requestBody: latestRef.current.requestBody
411
+ };
412
+ latestRef.current = next;
413
+ writeDraft(keyRef.current, next);
414
+ }, []);
415
+ const setRequestBody = useCallback((body) => {
416
+ const next = {
417
+ parameters: latestRef.current.parameters,
418
+ requestBody: body
419
+ };
420
+ latestRef.current = next;
421
+ writeDraft(keyRef.current, next);
422
+ }, []);
423
+ const reset = useCallback(() => {
424
+ latestRef.current = EMPTY_DRAFT;
425
+ if (keyRef.current && typeof window !== "undefined") {
426
+ try {
427
+ window.localStorage.removeItem(keyRef.current);
428
+ } catch {
429
+ }
430
+ }
431
+ setDraftSnapshot(EMPTY_DRAFT);
432
+ }, []);
433
+ return { draft, setParameters, setRequestBody, reset };
434
+ }
435
+ __name(useEndpointDraft, "useEndpointDraft");
436
+
437
+ // src/tools/OpenapiViewer/components/shared/EndpointDraftSync.tsx
438
+ function EndpointDraftSync({ schemaId }) {
439
+ const { state, setParameters, setRequestBody, setActiveSchemaId } = usePlaygroundContext();
440
+ const ep = state.selectedEndpoint;
441
+ useEffect(() => {
442
+ setActiveSchemaId(schemaId);
443
+ }, [schemaId, setActiveSchemaId]);
444
+ const { draft, setParameters: persistParams, setRequestBody: persistBody } = useEndpointDraft(schemaId, ep);
445
+ const lastLoadedKeyRef = useRef(null);
446
+ const lastPersistedParamsRef = useRef("");
447
+ const lastPersistedBodyRef = useRef("");
448
+ const currentKey = ep ? `${ep.method}|${ep.path}` : null;
449
+ useEffect(() => {
450
+ if (!ep || !currentKey) {
451
+ lastLoadedKeyRef.current = null;
452
+ return;
453
+ }
454
+ if (lastLoadedKeyRef.current === currentKey) return;
455
+ lastLoadedKeyRef.current = currentKey;
456
+ const hasStoredParams = draft.parameters && Object.keys(draft.parameters).length > 0;
457
+ const hasStoredBody = typeof draft.requestBody === "string" && draft.requestBody !== "";
458
+ if (hasStoredParams) {
459
+ setParameters(draft.parameters);
460
+ lastPersistedParamsRef.current = JSON.stringify(draft.parameters);
461
+ } else {
462
+ lastPersistedParamsRef.current = JSON.stringify(state.parameters);
463
+ }
464
+ if (hasStoredBody) {
465
+ setRequestBody(draft.requestBody);
466
+ lastPersistedBodyRef.current = draft.requestBody;
467
+ } else {
468
+ lastPersistedBodyRef.current = state.requestBody;
469
+ }
470
+ }, [currentKey]);
471
+ useEffect(() => {
472
+ if (!ep || lastLoadedKeyRef.current !== currentKey) return;
473
+ const serialised = JSON.stringify(state.parameters);
474
+ if (serialised === lastPersistedParamsRef.current) return;
475
+ lastPersistedParamsRef.current = serialised;
476
+ persistParams(state.parameters);
477
+ }, [state.parameters, ep, currentKey, persistParams]);
478
+ useEffect(() => {
479
+ if (!ep || lastLoadedKeyRef.current !== currentKey) return;
480
+ if (state.requestBody === lastPersistedBodyRef.current) return;
481
+ lastPersistedBodyRef.current = state.requestBody;
482
+ persistBody(state.requestBody);
483
+ }, [state.requestBody, ep, currentKey, persistBody]);
484
+ return null;
485
+ }
486
+ __name(EndpointDraftSync, "EndpointDraftSync");
487
+
488
+ // src/tools/OpenapiViewer/components/DocsLayout/anchor.ts
489
+ function endpointAnchor(ep, schemaId) {
490
+ const slug = ep.path.replace(/^https?:\/\/[^/]+/, "").replace(/[{}]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
491
+ const schemaSlug = schemaId ? `${slugifySchemaId(schemaId)}-` : "";
492
+ return `ep-${schemaSlug}${ep.method.toLowerCase()}-${slug}`;
493
+ }
494
+ __name(endpointAnchor, "endpointAnchor");
495
+ function slugifySchemaId(id) {
496
+ return id.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
497
+ }
498
+ __name(slugifySchemaId, "slugifySchemaId");
499
+ var METHOD_STYLES = {
500
+ GET: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25",
501
+ POST: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25",
502
+ PUT: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
503
+ PATCH: "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25",
504
+ DELETE: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
505
+ };
506
+ var METHOD_FALLBACK = "bg-muted text-muted-foreground border-border";
507
+ function getMethodStyle(method) {
508
+ return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
509
+ }
510
+ __name(getMethodStyle, "getMethodStyle");
511
+ function getStatusStyle(status) {
512
+ if (status >= 500) return "bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25";
513
+ if (status >= 400) return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25";
514
+ if (status >= 300) return "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25";
515
+ return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
516
+ }
517
+ __name(getStatusStyle, "getStatusStyle");
518
+ function MethodBadge({ method }) {
519
+ return /* @__PURE__ */ jsx("span", { className: cn(
520
+ "inline-flex shrink-0 items-center rounded border px-1.5 py-px",
521
+ "font-mono text-[10px] font-bold uppercase tracking-wider leading-none",
522
+ getMethodStyle(method)
523
+ ), children: method });
524
+ }
525
+ __name(MethodBadge, "MethodBadge");
526
+ function StatusBadge({ status }) {
527
+ return /* @__PURE__ */ jsx("span", { className: cn(
528
+ "inline-flex items-center rounded border px-1.5 py-px",
529
+ "font-mono text-[11px] font-bold leading-none",
530
+ getStatusStyle(status)
531
+ ), children: status });
532
+ }
533
+ __name(StatusBadge, "StatusBadge");
534
+ function SectionLabel({ children }) {
535
+ return /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 select-none", children });
536
+ }
537
+ __name(SectionLabel, "SectionLabel");
538
+ function Panel({ children, className }) {
539
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col min-h-0 overflow-hidden", className), children });
540
+ }
541
+ __name(Panel, "Panel");
542
+ function ScrollArea({ children, className }) {
543
+ return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto min-h-0", className), children });
544
+ }
545
+ __name(ScrollArea, "ScrollArea");
546
+ function EmptyState({
547
+ icon: Icon,
548
+ text,
549
+ className
550
+ }) {
551
+ return /* @__PURE__ */ jsxs(
552
+ "div",
553
+ {
554
+ className: cn(
555
+ "flex flex-col items-center justify-center h-full gap-3 px-6 text-center",
556
+ className
557
+ ),
558
+ children: [
559
+ /* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground/25" }),
560
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: text })
561
+ ]
562
+ }
563
+ );
564
+ }
565
+ __name(EmptyState, "EmptyState");
566
+ function CollapsibleSection({
567
+ label,
568
+ action,
569
+ children,
570
+ defaultOpen = false
571
+ }) {
572
+ const [open, setOpen] = React12.useState(defaultOpen);
573
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
574
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
575
+ /* @__PURE__ */ jsxs(
576
+ "button",
577
+ {
578
+ type: "button",
579
+ onClick: () => setOpen((v) => !v),
580
+ className: "flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 hover:text-muted-foreground transition-colors py-1",
581
+ children: [
582
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3 w-3 transition-transform", open && "rotate-90") }),
583
+ label
584
+ ]
585
+ }
586
+ ),
587
+ action && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: action })
588
+ ] }),
589
+ open && /* @__PURE__ */ jsx("div", { children })
590
+ ] });
591
+ }
592
+ __name(CollapsibleSection, "CollapsibleSection");
593
+ var FLAVOUR_LABELS = {
594
+ markdown: {
595
+ title: "Markdown for LLM",
596
+ hint: "Endpoints + params as prose. Smallest."
597
+ },
598
+ compact: {
599
+ title: "Compact JSON",
600
+ hint: "Dereferenced, no examples, minified."
601
+ },
602
+ raw: {
603
+ title: "Raw JSON",
604
+ hint: "Full OpenAPI document with $refs."
605
+ }
606
+ };
607
+ function SchemaCopyMenu({ schema, endpoints, baseUrl, variant = "button" }) {
608
+ const [sizeCache, setSizeCache] = useState({});
609
+ const [justCopied, setJustCopied] = useState(null);
610
+ const [open, setOpen] = useState(false);
611
+ const isReady = schema !== null && endpoints.length > 0;
612
+ const build = useCallback(
613
+ (flavour) => {
614
+ if (!schema) return "";
615
+ if (flavour === "markdown") return toMarkdown(schema, endpoints, baseUrl);
616
+ if (flavour === "compact") return toCompactJson(schema, baseUrl);
617
+ return toRawJson(schema, baseUrl);
618
+ },
619
+ [schema, endpoints, baseUrl]
620
+ );
621
+ const handleCopy = useCallback(
622
+ async (flavour) => {
623
+ if (!isReady) return;
624
+ const text = build(flavour);
625
+ const label = FLAVOUR_LABELS[flavour].title;
626
+ try {
627
+ await navigator.clipboard.writeText(text);
628
+ const size = formatBytes(text);
629
+ setSizeCache((prev) => ({ ...prev, [flavour]: size }));
630
+ setJustCopied(flavour);
631
+ setTimeout(() => setJustCopied(null), 1500);
632
+ setOpen(false);
633
+ toast.success(`Copied ${label}`, { description: size });
634
+ } catch (err) {
635
+ const message = err instanceof Error ? err.message : "Clipboard permission denied";
636
+ toast.error("Copy failed", { description: message });
637
+ }
638
+ },
639
+ [build, isReady]
640
+ );
641
+ const flavours = useMemo(() => ["markdown", "compact", "raw"], []);
642
+ return /* @__PURE__ */ jsxs(DropdownMenu, { open, onOpenChange: setOpen, children: [
643
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: variant === "icon" ? /* @__PURE__ */ jsx(
644
+ Button,
645
+ {
646
+ variant: "ghost",
647
+ size: "icon",
648
+ className: "h-7 w-7 shrink-0",
649
+ disabled: !isReady,
650
+ title: "Copy schema for AI",
651
+ "aria-label": "Copy schema for AI",
652
+ children: /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" })
653
+ }
654
+ ) : /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: !isReady, children: [
655
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
656
+ "Copy for AI",
657
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 opacity-60" })
658
+ ] }) }),
659
+ /* @__PURE__ */ jsxs(
660
+ DropdownMenuContent,
661
+ {
662
+ side: "right",
663
+ align: "start",
664
+ sideOffset: 6,
665
+ collisionPadding: 8,
666
+ className: "w-60 max-w-[calc(100vw-16px)]",
667
+ children: [
668
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-[10px] uppercase tracking-wider text-muted-foreground/70", children: "Copy schema" }),
669
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
670
+ flavours.map((f) => {
671
+ const label = FLAVOUR_LABELS[f];
672
+ const size = sizeCache[f];
673
+ const isDone = justCopied === f;
674
+ return /* @__PURE__ */ jsxs(
675
+ DropdownMenuItem,
676
+ {
677
+ onClick: (e) => {
678
+ e.preventDefault();
679
+ void handleCopy(f);
680
+ },
681
+ className: "flex flex-col items-start gap-0.5 py-2 cursor-pointer",
682
+ children: [
683
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
684
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium flex-1", children: label.title }),
685
+ isDone ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-emerald-500", children: [
686
+ /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
687
+ " Copied"
688
+ ] }) : size ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground/70 tabular-nums", children: size }) : null
689
+ ] }),
690
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/70 leading-snug line-clamp-1", children: label.hint })
691
+ ]
692
+ },
693
+ f
694
+ );
695
+ })
696
+ ]
697
+ }
698
+ )
699
+ ] });
700
+ }
701
+ __name(SchemaCopyMenu, "SchemaCopyMenu");
702
+ function BrandHeader({ info, endpoints, rawSchema, resolvedBaseUrl }) {
703
+ const apiTitle = info?.title ?? "API Reference";
704
+ const copyReady = rawSchema !== null && rawSchema !== void 0 && endpoints.length > 0;
705
+ return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-2.5 flex items-start gap-2", children: [
706
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
707
+ /* @__PURE__ */ jsx(
708
+ "div",
709
+ {
710
+ className: "text-[13px] font-semibold text-foreground leading-tight truncate",
711
+ title: apiTitle,
712
+ children: apiTitle
713
+ }
714
+ ),
715
+ info?.version && /* @__PURE__ */ jsxs("div", { className: "font-mono text-[10px] text-muted-foreground/60 leading-tight mt-0.5", children: [
716
+ "v",
717
+ info.version
718
+ ] })
719
+ ] }),
720
+ copyReady && /* @__PURE__ */ jsx(
721
+ SchemaCopyMenu,
722
+ {
723
+ schema: rawSchema ?? null,
724
+ endpoints,
725
+ baseUrl: resolvedBaseUrl,
726
+ variant: "icon"
727
+ }
728
+ )
729
+ ] });
730
+ }
731
+ __name(BrandHeader, "BrandHeader");
732
+
733
+ // src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts
734
+ function longestCommonPrefix(paths) {
735
+ if (paths.length === 0) return "";
736
+ if (paths.length === 1) return "";
737
+ const segments = paths.map((p) => p.split("/"));
738
+ const minLen = Math.min(...segments.map((s) => s.length));
739
+ const shared = [];
740
+ for (let i = 0; i < minLen; i++) {
741
+ const first = segments[0][i];
742
+ if (segments.every((s) => s[i] === first)) {
743
+ shared.push(first);
744
+ } else {
745
+ break;
746
+ }
747
+ }
748
+ const joined = shared.join("/");
749
+ return joined;
750
+ }
751
+ __name(longestCommonPrefix, "longestCommonPrefix");
752
+ function sidebarLabel(ep, groupCommonPrefix) {
753
+ if (ep.summary) return ep.summary;
754
+ if (groupCommonPrefix && ep.path.startsWith(groupCommonPrefix)) {
755
+ const tail = ep.path.slice(groupCommonPrefix.length) || "/";
756
+ return tail;
757
+ }
758
+ return relativePath2(ep.path);
759
+ }
760
+ __name(sidebarLabel, "sidebarLabel");
761
+ function relativePath2(full) {
762
+ try {
763
+ return new URL(full).pathname;
764
+ } catch {
765
+ return full;
766
+ }
767
+ }
768
+ __name(relativePath2, "relativePath");
769
+ function sidebarTooltip(ep) {
770
+ return `${ep.method} ${relativePath2(ep.path)}`;
771
+ }
772
+ __name(sidebarTooltip, "sidebarTooltip");
773
+
774
+ // src/tools/OpenapiViewer/components/DocsLayout/grouping.ts
775
+ var METHOD_ORDER = {
776
+ GET: 0,
777
+ POST: 1,
778
+ PUT: 2,
779
+ PATCH: 3,
780
+ DELETE: 4
781
+ };
782
+ var methodRank = /* @__PURE__ */ __name((ep) => METHOD_ORDER[ep.method] ?? 99, "methodRank");
783
+ function groupEndpoints(list) {
784
+ const byCategory = groupBy(list, "category");
785
+ const all = Object.entries(byCategory).map(([category, endpoints]) => ({
786
+ category,
787
+ endpoints: orderBy(endpoints, ["path", methodRank], ["asc", "asc"]),
788
+ commonPrefix: longestCommonPrefix(endpoints.map((e) => e.path))
789
+ }));
790
+ const [other, named] = partition(all, (g) => g.category === "Other");
791
+ return [...sortBy(named, (g) => g.category.toLowerCase()), ...other];
792
+ }
793
+ __name(groupEndpoints, "groupEndpoints");
794
+ function buildSchemaSections(sources, endpointsBySchema) {
795
+ return sources.map((source) => ({
796
+ source,
797
+ groups: groupEndpoints(endpointsBySchema[source.id] ?? [])
798
+ }));
799
+ }
800
+ __name(buildSchemaSections, "buildSchemaSections");
801
+
802
+ // src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts
803
+ function filterEndpoints(list, query, method) {
804
+ let out = list;
805
+ if (method !== "ALL") {
806
+ out = out.filter((e) => e.method === method);
807
+ }
808
+ if (query) {
809
+ const q = query.toLowerCase();
810
+ out = out.filter(
811
+ (e) => e.summary.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
812
+ );
813
+ }
814
+ return out;
815
+ }
816
+ __name(filterEndpoints, "filterEndpoints");
817
+ function buildCategory(group, activeEndpointId, schemaId, keyPrefix) {
818
+ const rows = group.endpoints.map((ep) => {
819
+ const anchor = endpointAnchor(ep, schemaId ?? ep.schemaId ?? null);
820
+ return {
821
+ key: `${ep.method}-${ep.path}`,
822
+ anchor,
823
+ schemaId: schemaId ?? ep.schemaId ?? null,
824
+ label: sidebarLabel(ep, group.commonPrefix),
825
+ tooltip: sidebarTooltip(ep),
826
+ method: ep.method,
827
+ useMono: !ep.summary,
828
+ isActive: activeEndpointId === anchor
829
+ };
830
+ });
831
+ return {
832
+ key: `${keyPrefix}${group.category}`,
833
+ category: group.category,
834
+ rows
835
+ };
836
+ }
837
+ __name(buildCategory, "buildCategory");
838
+ function emptyTextFor(query, method, defaultText) {
839
+ if (query && method !== "ALL") return `No ${method} endpoints match "${query}"`;
840
+ if (query) return `No endpoints match "${query}"`;
841
+ if (method !== "ALL") return `No ${method} endpoints`;
842
+ return defaultText;
843
+ }
844
+ __name(emptyTextFor, "emptyTextFor");
845
+ function buildFlatVM(endpoints, selectedVersion, query, method, activeEndpointId) {
846
+ const filtered = filterEndpoints(
847
+ deduplicateEndpoints(endpoints, selectedVersion),
848
+ query,
849
+ method
850
+ );
851
+ const groups = groupEndpoints(filtered);
852
+ return {
853
+ kind: "flat",
854
+ categories: groups.map((g) => buildCategory(g, activeEndpointId, null, "")),
855
+ emptyText: emptyTextFor(query, method, "No endpoints in this schema")
856
+ };
857
+ }
858
+ __name(buildFlatVM, "buildFlatVM");
859
+ function buildSectionsVM(schemas, endpointsBySchema, selectedVersion, query, method, activeEndpointId) {
860
+ const filteredMap = {};
861
+ for (const src of schemas) {
862
+ const raw = endpointsBySchema[src.id] ?? [];
863
+ filteredMap[src.id] = filterEndpoints(
864
+ deduplicateEndpoints(raw, selectedVersion),
865
+ query,
866
+ method
867
+ );
868
+ }
869
+ const rawSections = buildSchemaSections(schemas, filteredMap);
870
+ const sections = rawSections.filter((s) => s.groups.length > 0).map((s) => ({
871
+ sourceId: s.source.id,
872
+ sourceName: s.source.name,
873
+ categories: s.groups.map(
874
+ (g) => buildCategory(g, activeEndpointId, s.source.id, `${s.source.id}-`)
875
+ )
876
+ }));
877
+ return {
878
+ kind: "sections",
879
+ sections,
880
+ emptyText: emptyTextFor(query, method, "No endpoints in any schema")
881
+ };
882
+ }
883
+ __name(buildSectionsVM, "buildSectionsVM");
884
+ var EndpointRow = React12.memo(/* @__PURE__ */ __name(function EndpointRow2({
885
+ row,
886
+ onNavigate
887
+ }) {
888
+ const displayLabel = row.label.replace(/\.$/, "");
889
+ return /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 350, children: [
890
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
891
+ "button",
892
+ {
893
+ onClick: () => onNavigate(row.anchor, row.schemaId),
894
+ "aria-current": row.isActive ? "location" : void 0,
895
+ className: cn(
896
+ "relative w-full text-left grid grid-cols-[52px_minmax(0,1fr)] items-baseline gap-2 pl-3 pr-3 py-1 transition-colors",
897
+ row.isActive ? "bg-primary/10 text-foreground" : "hover:bg-muted/40 text-foreground/75 hover:text-foreground"
898
+ ),
899
+ children: [
900
+ row.isActive && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded-r bg-primary" }),
901
+ /* @__PURE__ */ jsx("span", { className: "justify-self-start", children: /* @__PURE__ */ jsx(MethodBadge, { method: row.method }) }),
902
+ /* @__PURE__ */ jsx(
903
+ "span",
904
+ {
905
+ className: cn(
906
+ "line-clamp-2 leading-snug min-w-0",
907
+ row.useMono ? "font-mono text-[11px] break-all" : "text-[12px]",
908
+ row.isActive && "text-foreground font-medium"
909
+ ),
910
+ children: displayLabel
911
+ }
912
+ )
913
+ ]
914
+ }
915
+ ) }),
916
+ /* @__PURE__ */ jsx(TooltipContent, { side: "right", align: "center", className: "font-mono text-[11px]", children: row.tooltip })
917
+ ] });
918
+ }, "EndpointRow"));
919
+ var CategoryBlock = React12.memo(/* @__PURE__ */ __name(function CategoryBlock2({
920
+ category,
921
+ onNavigate
922
+ }) {
923
+ return /* @__PURE__ */ jsxs("div", { className: "mb-2.5 last:mb-1", children: [
924
+ /* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/50 select-none", children: category.category }),
925
+ /* @__PURE__ */ jsx("div", { children: category.rows.map((row) => /* @__PURE__ */ jsx(EndpointRow, { row, onNavigate }, row.key)) })
926
+ ] });
927
+ }, "CategoryBlock"));
928
+ function SchemaSection({ section, onNavigate }) {
929
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-2", children: [
930
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 sticky top-0 z-[1] bg-background/95 backdrop-blur-[2px] border-b border-border/40", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold uppercase tracking-[0.12em] text-foreground/80", children: section.sourceName }) }),
931
+ section.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key))
932
+ ] });
933
+ }
934
+ __name(SchemaSection, "SchemaSection");
935
+ function SidebarBody({ body, onNavigate }) {
936
+ if (body.kind === "flat") {
937
+ if (body.categories.length === 0) {
938
+ return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
939
+ }
940
+ return /* @__PURE__ */ jsx("nav", { className: "py-1.5", children: body.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key)) });
941
+ }
942
+ if (body.sections.length === 0) {
943
+ return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
944
+ }
945
+ return /* @__PURE__ */ jsx("nav", { className: "py-1.5", children: body.sections.map((section) => /* @__PURE__ */ jsx(SchemaSection, { section, onNavigate }, section.sourceId)) });
946
+ }
947
+ __name(SidebarBody, "SidebarBody");
948
+
949
+ // src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts
950
+ var METHOD_FILTERS = ["ALL", "GET", "POST", "PUT", "PATCH", "DELETE"];
951
+ function MethodChips({ value, onChange }) {
952
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto -mx-1 px-1 pb-px", children: METHOD_FILTERS.map((m) => {
953
+ const active = value === m;
954
+ return /* @__PURE__ */ jsx(
955
+ "button",
956
+ {
957
+ type: "button",
958
+ onClick: () => onChange(m),
959
+ "aria-pressed": active,
960
+ className: cn(
961
+ "shrink-0 px-2 h-6 rounded font-mono text-[10px] font-semibold tracking-wide uppercase transition-colors",
962
+ active ? "bg-foreground text-background" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted"
963
+ ),
964
+ children: m
965
+ },
966
+ m
967
+ );
968
+ }) });
969
+ }
970
+ __name(MethodChips, "MethodChips");
971
+ function SearchInput({ value, onChange, placeholder }) {
972
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
973
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" }),
974
+ /* @__PURE__ */ jsx(
975
+ Input,
976
+ {
977
+ placeholder: placeholder ?? "Search endpoints\u2026",
978
+ value,
979
+ onChange: (e) => onChange(e.target.value),
980
+ className: "pl-8 pr-7 h-8 text-xs"
981
+ }
982
+ ),
983
+ value && /* @__PURE__ */ jsx(
984
+ "button",
985
+ {
986
+ type: "button",
987
+ onClick: () => onChange(""),
988
+ "aria-label": "Clear search",
989
+ className: cn(
990
+ "absolute right-1.5 top-1/2 -translate-y-1/2 h-5 w-5 rounded",
991
+ "inline-flex items-center justify-center",
992
+ "text-muted-foreground/50 hover:text-foreground hover:bg-muted transition-colors"
993
+ ),
994
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
995
+ }
996
+ )
997
+ ] });
998
+ }
999
+ __name(SearchInput, "SearchInput");
1000
+ function Toolbar({
1001
+ schemas,
1002
+ currentSchemaId,
1003
+ onSchemaChange,
1004
+ showSchemaSelector,
1005
+ search,
1006
+ onSearchChange,
1007
+ methodFilter,
1008
+ onMethodFilterChange
1009
+ }) {
1010
+ const schemaOptions = React12.useMemo(
1011
+ () => schemas.map((s) => ({ value: s.id, label: s.name })),
1012
+ [schemas]
1013
+ );
1014
+ return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-2.5 space-y-2", children: [
1015
+ showSchemaSelector && /* @__PURE__ */ jsx(
1016
+ Combobox,
1017
+ {
1018
+ options: schemaOptions,
1019
+ value: currentSchemaId ?? "",
1020
+ onValueChange: (id) => id && onSchemaChange(id),
1021
+ placeholder: "Select API",
1022
+ searchPlaceholder: "Search APIs\u2026",
1023
+ emptyText: "No APIs found",
1024
+ className: "w-full h-8 text-xs"
1025
+ }
1026
+ ),
1027
+ /* @__PURE__ */ jsx(SearchInput, { value: search, onChange: onSearchChange }),
1028
+ /* @__PURE__ */ jsx(MethodChips, { value: methodFilter, onChange: onMethodFilterChange })
1029
+ ] });
1030
+ }
1031
+ __name(Toolbar, "Toolbar");
1032
+ function useDebouncedValue(value, delayMs = 120) {
1033
+ const [debounced, setDebounced] = useState(value);
1034
+ useEffect(() => {
1035
+ const id = setTimeout(() => setDebounced(value), delayMs);
1036
+ return () => clearTimeout(id);
1037
+ }, [value, delayMs]);
1038
+ return debounced;
1039
+ }
1040
+ __name(useDebouncedValue, "useDebouncedValue");
1041
+ function DocsSidebar({
1042
+ info,
1043
+ endpoints,
1044
+ schemas,
1045
+ currentSchemaId,
1046
+ onSchemaChange,
1047
+ activeEndpointId,
1048
+ selectedVersion,
1049
+ onNavigate,
1050
+ grouping = "selector",
1051
+ endpointsBySchema,
1052
+ rawSchema,
1053
+ resolvedBaseUrl
1054
+ }) {
1055
+ const [search, setSearch] = useState("");
1056
+ const [methodFilter, setMethodFilter] = useState("ALL");
1057
+ const debouncedSearch = useDebouncedValue(search);
1058
+ const body = useMemo(() => {
1059
+ if (grouping === "sections") {
1060
+ return buildSectionsVM(
1061
+ schemas,
1062
+ endpointsBySchema ?? {},
1063
+ selectedVersion,
1064
+ debouncedSearch,
1065
+ methodFilter,
1066
+ activeEndpointId
1067
+ );
1068
+ }
1069
+ return buildFlatVM(
1070
+ endpoints,
1071
+ selectedVersion,
1072
+ debouncedSearch,
1073
+ methodFilter,
1074
+ activeEndpointId
1075
+ );
1076
+ }, [
1077
+ grouping,
1078
+ schemas,
1079
+ endpointsBySchema,
1080
+ endpoints,
1081
+ selectedVersion,
1082
+ debouncedSearch,
1083
+ methodFilter,
1084
+ activeEndpointId
1085
+ ]);
1086
+ const hasMultipleSchemas = schemas.length > 1;
1087
+ const showSchemaSelector = grouping === "selector" && hasMultipleSchemas;
1088
+ return /* @__PURE__ */ jsxs("aside", { className: "flex flex-col h-full min-h-0 border-r bg-muted/10", children: [
1089
+ /* @__PURE__ */ jsx(
1090
+ BrandHeader,
1091
+ {
1092
+ info,
1093
+ endpoints,
1094
+ rawSchema,
1095
+ resolvedBaseUrl
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ jsx(
1099
+ Toolbar,
1100
+ {
1101
+ schemas,
1102
+ currentSchemaId,
1103
+ onSchemaChange,
1104
+ showSchemaSelector,
1105
+ search,
1106
+ onSearchChange: setSearch,
1107
+ methodFilter,
1108
+ onMethodFilterChange: setMethodFilter
1109
+ }
1110
+ ),
1111
+ /* @__PURE__ */ jsx(ScrollArea, { children: /* @__PURE__ */ jsx(SidebarBody, { body, onNavigate }) })
1112
+ ] });
1113
+ }
1114
+ __name(DocsSidebar, "DocsSidebar");
1115
+
1116
+ // src/tools/OpenapiViewer/utils/scrollParent.ts
1117
+ function getScrollParent(el) {
1118
+ if (typeof window === "undefined") return null;
1119
+ if (!el) return window;
1120
+ let cur = el.parentElement;
1121
+ while (cur && cur !== document.body && cur !== document.documentElement) {
1122
+ const style = getComputedStyle(cur);
1123
+ const overflowY = style.overflowY;
1124
+ const canScroll = (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && cur.scrollHeight > cur.clientHeight;
1125
+ if (canScroll) return cur;
1126
+ cur = cur.parentElement;
1127
+ }
1128
+ return window;
1129
+ }
1130
+ __name(getScrollParent, "getScrollParent");
1131
+ function getScrollTop(target) {
1132
+ return target === window ? window.scrollY : target.scrollTop;
1133
+ }
1134
+ __name(getScrollTop, "getScrollTop");
1135
+ function getViewportHeight(target) {
1136
+ return target === window ? window.innerHeight : target.clientHeight;
1137
+ }
1138
+ __name(getViewportHeight, "getViewportHeight");
1139
+ function getTargetTop(target) {
1140
+ return target === window ? 0 : target.getBoundingClientRect().top;
1141
+ }
1142
+ __name(getTargetTop, "getTargetTop");
1143
+ function scrollTargetTo(target, top) {
1144
+ if (target === window) {
1145
+ window.scrollTo({ top, behavior: "smooth" });
1146
+ return;
1147
+ }
1148
+ target.scrollTop = top;
1149
+ }
1150
+ __name(scrollTargetTo, "scrollTargetTo");
1151
+ function ApiIntroSection({ info, schema, endpoints, resolvedBaseUrl }) {
1152
+ const baseUrlRows = resolvedBaseUrl ? [{ url: resolvedBaseUrl, description: info.servers?.[0]?.description }] : (info.servers ?? []).map((s) => ({ url: s.url, description: s.description }));
1153
+ return /* @__PURE__ */ jsxs("section", { className: "pb-10 mb-10 border-b", children: [
1154
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [
1155
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap min-w-0", children: [
1156
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold tracking-tight text-foreground leading-tight", children: info.title }),
1157
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 font-mono text-[11px] text-muted-foreground", children: [
1158
+ "v",
1159
+ info.version
1160
+ ] })
1161
+ ] }),
1162
+ /* @__PURE__ */ jsx(
1163
+ SchemaCopyMenu,
1164
+ {
1165
+ schema,
1166
+ endpoints,
1167
+ baseUrl: resolvedBaseUrl
1168
+ }
1169
+ )
1170
+ ] }),
1171
+ info.description && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: info.description }) }),
1172
+ baseUrlRows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
1173
+ /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: "Base URL" }),
1174
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: baseUrlRows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1175
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs px-2 py-1 rounded bg-muted border", children: row.url }),
1176
+ row.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: row.description })
1177
+ ] }, `${row.url}-${i}`)) })
1178
+ ] })
1179
+ ] });
1180
+ }
1181
+ __name(ApiIntroSection, "ApiIntroSection");
1182
+ var EndpointDocContext = createContext(null);
1183
+ function EndpointDocProvider({ endpointId, method, children }) {
1184
+ const value = useMemo(() => ({ endpointId, method }), [endpointId, method]);
1185
+ return /* @__PURE__ */ jsx(EndpointDocContext.Provider, { value, children });
1186
+ }
1187
+ __name(EndpointDocProvider, "EndpointDocProvider");
1188
+ function useEndpointDocContext() {
1189
+ const ctx = useContext(EndpointDocContext);
1190
+ if (!ctx) {
1191
+ throw new Error(
1192
+ "[OpenapiViewer] useEndpointDocContext must be used inside <EndpointDocProvider>."
1193
+ );
1194
+ }
1195
+ return ctx;
1196
+ }
1197
+ __name(useEndpointDocContext, "useEndpointDocContext");
1198
+ var sectionKey = /* @__PURE__ */ __name((endpointId, sectionId) => `${endpointId}:${sectionId}`, "sectionKey");
1199
+ var initialState = {
1200
+ openSections: {},
1201
+ activeCodeTab: {}
1202
+ };
1203
+ var useEndpointDocStore = create()(
1204
+ persist(
1205
+ (set) => ({
1206
+ ...initialState,
1207
+ toggleSection: /* @__PURE__ */ __name((endpointId, sectionId) => set((state) => {
1208
+ const key = sectionKey(endpointId, sectionId);
1209
+ const current = state.openSections[key];
1210
+ return {
1211
+ openSections: {
1212
+ ...state.openSections,
1213
+ // If there's no explicit override yet, the user's
1214
+ // first click means "flip from the default". We
1215
+ // assume the default was ``true`` for the most
1216
+ // common case (bodies/responses) and ``false``
1217
+ // otherwise; the Section component tracks this
1218
+ // via its ``defaultOpen`` prop and never calls
1219
+ // toggle on sections whose defaults match the
1220
+ // next state.
1221
+ [key]: current === void 0 ? false : !current
1222
+ }
1223
+ };
1224
+ }), "toggleSection"),
1225
+ setSectionOpen: /* @__PURE__ */ __name((endpointId, sectionId, open) => set((state) => ({
1226
+ openSections: {
1227
+ ...state.openSections,
1228
+ [sectionKey(endpointId, sectionId)]: open
1229
+ }
1230
+ })), "setSectionOpen"),
1231
+ setCodeTab: /* @__PURE__ */ __name((endpointId, tab) => set((state) => ({
1232
+ activeCodeTab: {
1233
+ ...state.activeCodeTab,
1234
+ [endpointId]: tab
1235
+ }
1236
+ })), "setCodeTab"),
1237
+ expandAll: /* @__PURE__ */ __name((endpointId, sectionIds) => set((state) => {
1238
+ const next = { ...state.openSections };
1239
+ for (const sid of sectionIds) {
1240
+ next[sectionKey(endpointId, sid)] = true;
1241
+ }
1242
+ return { openSections: next };
1243
+ }), "expandAll"),
1244
+ collapseAll: /* @__PURE__ */ __name((endpointId, sectionIds) => set((state) => {
1245
+ const next = { ...state.openSections };
1246
+ for (const sid of sectionIds) {
1247
+ next[sectionKey(endpointId, sid)] = false;
1248
+ }
1249
+ return { openSections: next };
1250
+ }), "collapseAll")
1251
+ }),
1252
+ {
1253
+ name: "openapi-viewer:endpoint-doc",
1254
+ storage: createJSONStorage(() => {
1255
+ if (typeof window === "undefined") {
1256
+ return {
1257
+ getItem: /* @__PURE__ */ __name(() => null, "getItem"),
1258
+ setItem: /* @__PURE__ */ __name(() => {
1259
+ }, "setItem"),
1260
+ removeItem: /* @__PURE__ */ __name(() => {
1261
+ }, "removeItem")
1262
+ };
1263
+ }
1264
+ return window.sessionStorage;
1265
+ }),
1266
+ // Only persist user overrides, not the functions. Zustand
1267
+ // serialises everything by default and logs a warning on
1268
+ // non-serialisable values; partialize keeps the payload lean.
1269
+ partialize: /* @__PURE__ */ __name((state) => ({
1270
+ openSections: state.openSections,
1271
+ activeCodeTab: state.activeCodeTab
1272
+ }), "partialize")
1273
+ }
1274
+ )
1275
+ );
1276
+
1277
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts
1278
+ function useIsSectionOpen(endpointId, sectionId, defaultOpen) {
1279
+ return useEndpointDocStore((s) => {
1280
+ const explicit = s.openSections[sectionKey(endpointId, sectionId)];
1281
+ return explicit === void 0 ? defaultOpen : explicit;
1282
+ });
1283
+ }
1284
+ __name(useIsSectionOpen, "useIsSectionOpen");
1285
+ function useActiveCodeTab(endpointId, fallback = "curl") {
1286
+ return useEndpointDocStore((s) => s.activeCodeTab[endpointId] ?? fallback);
1287
+ }
1288
+ __name(useActiveCodeTab, "useActiveCodeTab");
1289
+ function LanguageTabs({ activeId, onChange }) {
1290
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto -mx-1 px-1", children: CODE_SAMPLE_TARGETS.map((t) => /* @__PURE__ */ jsx(
1291
+ "button",
1292
+ {
1293
+ type: "button",
1294
+ onClick: () => onChange(t.id),
1295
+ className: cn(
1296
+ "shrink-0 h-7 px-2.5 rounded text-xs font-medium transition-colors",
1297
+ activeId === t.id ? "bg-muted text-foreground" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted/50"
1298
+ ),
1299
+ children: t.label
1300
+ },
1301
+ t.id
1302
+ )) });
1303
+ }
1304
+ __name(LanguageTabs, "LanguageTabs");
1305
+ function useCodeSnippet({
1306
+ endpoint,
1307
+ body,
1308
+ parameters,
1309
+ headers,
1310
+ baseUrl,
1311
+ activeId
1312
+ }) {
1313
+ const effectiveBody = body ?? endpoint.requestBody?.example;
1314
+ const har = useMemo(() => {
1315
+ const h = buildHarRequest({
1316
+ endpoint,
1317
+ body: effectiveBody,
1318
+ parameters,
1319
+ headers,
1320
+ baseUrl
1321
+ });
1322
+ return baseUrl ? h : { ...h, url: resolveAbsolute(h.url) };
1323
+ }, [endpoint, effectiveBody, parameters, headers, baseUrl]);
1324
+ return useMemo(() => {
1325
+ const target = CODE_SAMPLE_TARGETS.find((t) => t.id === activeId);
1326
+ const code = renderSnippet(har, activeId);
1327
+ return {
1328
+ snippet: code ?? `// Snippet for ${target.label} is unavailable for this request.`,
1329
+ prism: target.prism
1330
+ };
1331
+ }, [har, activeId]);
1332
+ }
1333
+ __name(useCodeSnippet, "useCodeSnippet");
1334
+ function CodeSamples({ endpoint, body, parameters, headers, baseUrl }) {
1335
+ const { endpointId } = useEndpointDocContext();
1336
+ const activeId = useActiveCodeTab(endpointId);
1337
+ const setCodeTab = useEndpointDocStore((s) => s.setCodeTab);
1338
+ const { snippet, prism } = useCodeSnippet({
1339
+ endpoint,
1340
+ body,
1341
+ parameters,
1342
+ headers,
1343
+ baseUrl,
1344
+ activeId
1345
+ });
1346
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
1347
+ /* @__PURE__ */ jsx(LanguageTabs, { activeId, onChange: (id) => setCodeTab(endpointId, id) }),
1348
+ /* @__PURE__ */ jsx(
1349
+ PrettyCode_default,
1350
+ {
1351
+ data: snippet,
1352
+ language: prism,
1353
+ isCompact: true,
1354
+ maxLines: 20
1355
+ }
1356
+ )
1357
+ ] });
1358
+ }
1359
+ __name(CodeSamples, "CodeSamples");
1360
+ function IconButton({ label, onClick, children, active }) {
1361
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
1362
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1363
+ "button",
1364
+ {
1365
+ type: "button",
1366
+ onClick,
1367
+ "aria-label": label,
1368
+ className: cn(
1369
+ "shrink-0 h-6 w-6 inline-flex items-center justify-center rounded",
1370
+ "text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors",
1371
+ active && "text-emerald-500 hover:text-emerald-500"
1372
+ ),
1373
+ children
1374
+ }
1375
+ ) }),
1376
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", className: "text-[11px]", children: label })
1377
+ ] });
1378
+ }
1379
+ __name(IconButton, "IconButton");
1380
+ function MetaActions({ anchor, endpointMarkdown, presentSections }) {
1381
+ const { endpointId } = useEndpointDocContext();
1382
+ const expandAll = useEndpointDocStore((s) => s.expandAll);
1383
+ const collapseAll = useEndpointDocStore((s) => s.collapseAll);
1384
+ const openSections = useEndpointDocStore((s) => s.openSections);
1385
+ const [justCopied, setJustCopied] = useState(null);
1386
+ const flash = useCallback((which) => {
1387
+ setJustCopied(which);
1388
+ setTimeout(() => setJustCopied(null), 1200);
1389
+ }, []);
1390
+ const mostlyOpen = useMemo(() => {
1391
+ if (presentSections.length === 0) return false;
1392
+ let openCount = 0;
1393
+ for (const sid of presentSections) {
1394
+ if (openSections[sectionKey(endpointId, sid)]) openCount += 1;
1395
+ }
1396
+ return openCount > presentSections.length / 2;
1397
+ }, [openSections, presentSections, endpointId]);
1398
+ const copyLink = useCallback(() => {
1399
+ if (typeof window === "undefined") return;
1400
+ const url = `${window.location.origin}${window.location.pathname}#${anchor}`;
1401
+ void navigator.clipboard?.writeText(url).then(() => flash("link"));
1402
+ }, [anchor, flash]);
1403
+ const copyMarkdown = useCallback(() => {
1404
+ if (typeof window === "undefined") return;
1405
+ void navigator.clipboard?.writeText(endpointMarkdown).then(() => flash("md"));
1406
+ }, [endpointMarkdown, flash]);
1407
+ const toggleAll = useCallback(() => {
1408
+ if (mostlyOpen) collapseAll(endpointId, presentSections);
1409
+ else expandAll(endpointId, presentSections);
1410
+ }, [mostlyOpen, collapseAll, expandAll, endpointId, presentSections]);
1411
+ return /* @__PURE__ */ jsx(SafeTooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
1412
+ /* @__PURE__ */ jsx(
1413
+ IconButton,
1414
+ {
1415
+ label: justCopied === "link" ? "Copied!" : "Copy link to endpoint",
1416
+ onClick: copyLink,
1417
+ active: justCopied === "link",
1418
+ children: justCopied === "link" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" })
1419
+ }
1420
+ ),
1421
+ /* @__PURE__ */ jsx(
1422
+ IconButton,
1423
+ {
1424
+ label: justCopied === "md" ? "Copied!" : "Copy as Markdown (for AI)",
1425
+ onClick: copyMarkdown,
1426
+ active: justCopied === "md",
1427
+ children: justCopied === "md" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(FileCode2, { className: "h-3.5 w-3.5" })
1428
+ }
1429
+ ),
1430
+ presentSections.length >= 2 && /* @__PURE__ */ jsx(
1431
+ IconButton,
1432
+ {
1433
+ label: mostlyOpen ? "Collapse all sections" : "Expand all sections",
1434
+ onClick: toggleAll,
1435
+ children: mostlyOpen ? /* @__PURE__ */ jsx(ChevronsDownUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-3.5 w-3.5" })
1436
+ }
1437
+ )
1438
+ ] }) });
1439
+ }
1440
+ __name(MetaActions, "MetaActions");
1441
+ function PathDisplay({ path }) {
1442
+ return /* @__PURE__ */ jsx(
1443
+ "code",
1444
+ {
1445
+ className: "block font-mono text-lg md:text-xl font-semibold text-foreground leading-tight",
1446
+ style: { overflowWrap: "anywhere", wordBreak: "break-word" },
1447
+ children: relativePath(path)
1448
+ }
1449
+ );
1450
+ }
1451
+ __name(PathDisplay, "PathDisplay");
1452
+ function EndpointHeader({
1453
+ endpoint,
1454
+ anchor,
1455
+ isLoadedInPlayground,
1456
+ onTryIt,
1457
+ presentSections
1458
+ }) {
1459
+ const endpointMd = useMemo(() => endpointToMarkdown(endpoint), [endpoint]);
1460
+ return /* @__PURE__ */ jsxs("header", { className: "space-y-3", children: [
1461
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
1462
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
1463
+ /* @__PURE__ */ jsx(MethodBadge, { method: endpoint.method }),
1464
+ /* @__PURE__ */ jsx(
1465
+ MetaActions,
1466
+ {
1467
+ anchor,
1468
+ endpointMarkdown: endpointMd,
1469
+ presentSections
1470
+ }
1471
+ )
1472
+ ] }),
1473
+ /* @__PURE__ */ jsxs(
1474
+ Button,
1475
+ {
1476
+ size: "sm",
1477
+ variant: isLoadedInPlayground ? "secondary" : "default",
1478
+ onClick: onTryIt,
1479
+ className: "ml-auto h-7 text-xs gap-1.5 px-2.5",
1480
+ children: [
1481
+ /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
1482
+ isLoadedInPlayground ? "Loaded" : "Try it"
1483
+ ]
1484
+ }
1485
+ )
1486
+ ] }),
1487
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx(PathDisplay, { path: endpoint.path }) }),
1488
+ endpoint.description && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-sm", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: endpoint.description }) })
1489
+ ] });
1490
+ }
1491
+ __name(EndpointHeader, "EndpointHeader");
1492
+ function ParamRow({ param }) {
1493
+ return /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
1494
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1495
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: param.name }),
1496
+ param.required && /* @__PURE__ */ jsx(
1497
+ "span",
1498
+ {
1499
+ title: "Required",
1500
+ className: "text-[9px] text-destructive font-bold leading-none",
1501
+ children: "*"
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: param.type })
1505
+ ] }),
1506
+ param.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: param.description })
1507
+ ] });
1508
+ }
1509
+ __name(ParamRow, "ParamRow");
1510
+ function ParamGroup({ label, params }) {
1511
+ if (params.length === 0) return null;
1512
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1513
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-muted-foreground/60 px-1", children: label }),
1514
+ /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: params.map((p) => /* @__PURE__ */ jsx(ParamRow, { param: p }, `${label}-${p.name}`)) })
1515
+ ] });
1516
+ }
1517
+ __name(ParamGroup, "ParamGroup");
1518
+ function Parameters({ pathParams, queryParams }) {
1519
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1520
+ /* @__PURE__ */ jsx(ParamGroup, { label: "Path", params: pathParams }),
1521
+ /* @__PURE__ */ jsx(ParamGroup, { label: "Query", params: queryParams })
1522
+ ] });
1523
+ }
1524
+ __name(Parameters, "Parameters");
1525
+
1526
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts
1527
+ var MAX_DEPTH = 5;
1528
+ function mergeAllOf(branches) {
1529
+ const properties = {};
1530
+ const required = [];
1531
+ for (const b of branches) {
1532
+ if (b.properties) Object.assign(properties, b.properties);
1533
+ if (Array.isArray(b.required)) required.push(...b.required);
1534
+ }
1535
+ return { type: "object", properties, required };
1536
+ }
1537
+ __name(mergeAllOf, "mergeAllOf");
1538
+ function describeType(node) {
1539
+ if (node.type === "array") {
1540
+ const itemLabel = node.items ? describeType(node.items).label : "any";
1541
+ return { label: `array<${itemLabel}>`, kind: "array" };
1542
+ }
1543
+ if (node.type === "object" || node.properties) {
1544
+ return { label: "object", kind: "object" };
1545
+ }
1546
+ const base = node.type || "any";
1547
+ if (Array.isArray(node.enum) && node.enum.length > 0) {
1548
+ return { label: `${base} enum`, kind: "primitive" };
1549
+ }
1550
+ if (node.format) {
1551
+ return { label: `${base} (${node.format})`, kind: "primitive" };
1552
+ }
1553
+ return { label: base, kind: "primitive" };
1554
+ }
1555
+ __name(describeType, "describeType");
1556
+ function resolveCombinators(node) {
1557
+ if (Array.isArray(node.allOf) && node.allOf.length > 0) {
1558
+ return { ...mergeAllOf(node.allOf), description: node.description };
1559
+ }
1560
+ if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
1561
+ return { ...node.oneOf[0], description: node.description ?? node.oneOf[0].description };
1562
+ }
1563
+ if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
1564
+ return { ...node.anyOf[0], description: node.description ?? node.anyOf[0].description };
1565
+ }
1566
+ return node;
1567
+ }
1568
+ __name(resolveCombinators, "resolveCombinators");
1569
+ function buildNode(name, schema, isRequired, depth) {
1570
+ const resolved = resolveCombinators(schema);
1571
+ const { label, kind } = describeType(resolved);
1572
+ const node = {
1573
+ name,
1574
+ type: label,
1575
+ kind,
1576
+ required: isRequired,
1577
+ description: resolved.description
1578
+ };
1579
+ if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
1580
+ node.enumValues = resolved.enum.map((v) => String(v));
1581
+ }
1582
+ if (depth >= MAX_DEPTH) return node;
1583
+ if (kind === "object" && resolved.properties) {
1584
+ const required = new Set(resolved.required ?? []);
1585
+ node.children = Object.entries(resolved.properties).map(
1586
+ ([key, child]) => buildNode(key, child, required.has(key), depth + 1)
1587
+ );
1588
+ } else if (kind === "array" && resolved.items) {
1589
+ node.children = [buildNode("[]", resolved.items, false, depth + 1)];
1590
+ }
1591
+ return node;
1592
+ }
1593
+ __name(buildNode, "buildNode");
1594
+ function buildSchemaTree(schema) {
1595
+ if (!schema) return [];
1596
+ const root = buildNode("", schema, false, 0);
1597
+ if (root.children && root.children.length > 0) return root.children;
1598
+ if (root.kind === "primitive" || !root.children && root.name === "") {
1599
+ return [{ ...root, name: root.name || "(body)" }];
1600
+ }
1601
+ return [];
1602
+ }
1603
+ __name(buildSchemaTree, "buildSchemaTree");
1604
+ function FieldRow({ field, depth, showTreeLine = true }) {
1605
+ const isExpandable = (field.kind === "object" || field.kind === "array") && Array.isArray(field.children) && field.children.length > 0;
1606
+ const [open, setOpen] = useState(depth < 2);
1607
+ const padLeft = showTreeLine ? depth * 14 : 0;
1608
+ return /* @__PURE__ */ jsxs("div", { className: "bg-background", children: [
1609
+ /* @__PURE__ */ jsxs(
1610
+ "div",
1611
+ {
1612
+ className: cn(
1613
+ "grid grid-cols-[16px_minmax(0,1fr)] items-baseline gap-2 px-3 py-2",
1614
+ isExpandable && "cursor-pointer hover:bg-muted/30"
1615
+ ),
1616
+ style: { paddingLeft: 12 + padLeft },
1617
+ onClick: () => isExpandable && setOpen((v) => !v),
1618
+ role: isExpandable ? "button" : void 0,
1619
+ "aria-expanded": isExpandable ? open : void 0,
1620
+ children: [
1621
+ /* @__PURE__ */ jsx(
1622
+ ChevronRight,
1623
+ {
1624
+ className: cn(
1625
+ "h-3.5 w-3.5 text-muted-foreground/50 shrink-0 transition-transform",
1626
+ !isExpandable && "opacity-0",
1627
+ open && "rotate-90"
1628
+ )
1629
+ }
1630
+ ),
1631
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
1632
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1633
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: field.name }),
1634
+ field.required && /* @__PURE__ */ jsx(
1635
+ "span",
1636
+ {
1637
+ title: "Required",
1638
+ className: "text-[9px] text-destructive font-bold leading-none",
1639
+ children: "*"
1640
+ }
1641
+ ),
1642
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: field.type })
1643
+ ] }),
1644
+ field.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: field.description }),
1645
+ field.enumValues && field.enumValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 pt-0.5", children: field.enumValues.map((v) => /* @__PURE__ */ jsx(
1646
+ "code",
1647
+ {
1648
+ className: "inline-flex items-center rounded border border-border/60 bg-muted/40 px-1.5 py-px font-mono text-[10px] text-muted-foreground",
1649
+ children: v
1650
+ },
1651
+ v
1652
+ )) })
1653
+ ] })
1654
+ ]
1655
+ }
1656
+ ),
1657
+ isExpandable && open && /* @__PURE__ */ jsx("div", { children: field.children.map((child, i) => /* @__PURE__ */ jsx(
1658
+ FieldRow,
1659
+ {
1660
+ field: child,
1661
+ depth: depth + 1
1662
+ },
1663
+ `${child.name}-${i}`
1664
+ )) })
1665
+ ] });
1666
+ }
1667
+ __name(FieldRow, "FieldRow");
1668
+ function SchemaFields({ schema }) {
1669
+ const tree = useMemo(() => buildSchemaTree(schema), [schema]);
1670
+ if (tree.length === 0) return null;
1671
+ return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: tree.map((node, i) => /* @__PURE__ */ jsx(FieldRow, { field: node, depth: 0 }, `${node.name}-${i}`)) });
1672
+ }
1673
+ __name(SchemaFields, "SchemaFields");
1674
+ function RequestBody({ body }) {
1675
+ const typeLabel = body.schema ? body.type === "array" ? `array<${body.schema.items?.type ?? "object"}>` : body.type : body.type;
1676
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1677
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1678
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/80", children: typeLabel }),
1679
+ body.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: body.description })
1680
+ ] }),
1681
+ body.schema && /* @__PURE__ */ jsx(SchemaFields, { schema: body.schema })
1682
+ ] });
1683
+ }
1684
+ __name(RequestBody, "RequestBody");
1685
+ var EXAMPLE_JSON_TREE_CONFIG = {
1686
+ maxAutoExpandDepth: 2,
1687
+ maxAutoExpandArrayItems: 5,
1688
+ maxAutoExpandObjectKeys: 8,
1689
+ maxStringLength: 160,
1690
+ collectionLimit: 25,
1691
+ showCollectionInfo: true,
1692
+ showExpandControls: false,
1693
+ showActionButtons: false,
1694
+ preserveKeyOrder: true,
1695
+ className: "border-0 rounded-none"
1696
+ };
1697
+ function ResponseBody({ example, contentType }) {
1698
+ const parsed = useMemo(() => {
1699
+ try {
1700
+ return JSON.parse(example);
1701
+ } catch {
1702
+ return null;
1703
+ }
1704
+ }, [example]);
1705
+ return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/20", children: [
1706
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50", children: [
1707
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: contentType ?? "application/json" }),
1708
+ /* @__PURE__ */ jsx(
1709
+ CopyButton,
1710
+ {
1711
+ value: example,
1712
+ variant: "ghost",
1713
+ size: "sm",
1714
+ className: "h-6 px-2 text-[10px] text-muted-foreground",
1715
+ children: "Copy"
1716
+ }
1717
+ )
1718
+ ] }),
1719
+ parsed != null ? /* @__PURE__ */ jsx(
1720
+ JsonTree_default,
1721
+ {
1722
+ title: "",
1723
+ data: parsed,
1724
+ mode: "compact",
1725
+ config: EXAMPLE_JSON_TREE_CONFIG
1726
+ }
1727
+ ) : /* @__PURE__ */ jsx("pre", { className: "p-3 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: example })
1728
+ ] });
1729
+ }
1730
+ __name(ResponseBody, "ResponseBody");
1731
+ function StatusTag({ code }) {
1732
+ const numeric = Number.parseInt(code, 10);
1733
+ const cls = !Number.isFinite(numeric) ? "bg-muted text-muted-foreground border-border" : numeric >= 500 ? "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25" : numeric >= 400 ? "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25" : numeric >= 300 ? "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25" : "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
1734
+ return /* @__PURE__ */ jsx("span", { className: cn(
1735
+ "inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-[11px] font-bold leading-none shrink-0 tabular-nums",
1736
+ cls
1737
+ ), children: code });
1738
+ }
1739
+ __name(StatusTag, "StatusTag");
1740
+ function ResponseRow({ response }) {
1741
+ const hasExample = Boolean(response.example);
1742
+ const numeric = Number.parseInt(response.code, 10);
1743
+ const isSuccess = Number.isFinite(numeric) && numeric >= 200 && numeric < 300;
1744
+ const [open, setOpen] = useState(hasExample && isSuccess);
1745
+ if (!hasExample) {
1746
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2 bg-background", children: [
1747
+ /* @__PURE__ */ jsx("div", { className: "w-12 shrink-0 flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: response.code }) }),
1748
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words min-w-0", children: response.description })
1749
+ ] });
1750
+ }
1751
+ return /* @__PURE__ */ jsxs("div", { className: "bg-background", children: [
1752
+ /* @__PURE__ */ jsxs(
1753
+ "button",
1754
+ {
1755
+ type: "button",
1756
+ onClick: () => setOpen((v) => !v),
1757
+ className: "w-full flex items-center gap-3 px-3 py-2 text-left hover:bg-muted/40 cursor-pointer transition-colors",
1758
+ "aria-expanded": open,
1759
+ children: [
1760
+ /* @__PURE__ */ jsx(
1761
+ ChevronRight,
1762
+ {
1763
+ className: cn(
1764
+ "h-3.5 w-3.5 text-muted-foreground/60 transition-transform shrink-0",
1765
+ open && "rotate-90"
1766
+ )
1767
+ }
1768
+ ),
1769
+ /* @__PURE__ */ jsx("div", { className: "w-12 shrink-0 flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: response.code }) }),
1770
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words min-w-0 flex-1", children: response.description })
1771
+ ]
1772
+ }
1773
+ ),
1774
+ open && /* @__PURE__ */ jsx(
1775
+ ResponseBody,
1776
+ {
1777
+ example: response.example,
1778
+ contentType: response.contentType
1779
+ }
1780
+ )
1781
+ ] });
1782
+ }
1783
+ __name(ResponseRow, "ResponseRow");
1784
+ function Responses({ responses }) {
1785
+ return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: responses.map((r) => /* @__PURE__ */ jsx(ResponseRow, { response: r }, r.code)) });
1786
+ }
1787
+ __name(Responses, "Responses");
1788
+
1789
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts
1790
+ var DEFAULTS_BY_METHOD = {
1791
+ GET: { parameters: true, responses: true },
1792
+ DELETE: { parameters: true, responses: true },
1793
+ POST: { requestBody: true, responses: true },
1794
+ PUT: { requestBody: true, responses: true },
1795
+ PATCH: { requestBody: true, responses: true }
1796
+ };
1797
+ function defaultSectionOpen(method, sectionId) {
1798
+ return DEFAULTS_BY_METHOD[method.toUpperCase()]?.[sectionId] ?? false;
1799
+ }
1800
+ __name(defaultSectionOpen, "defaultSectionOpen");
1801
+
1802
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts
1803
+ var ALL_SECTION_IDS = [
1804
+ "parameters",
1805
+ "requestBody",
1806
+ "responses",
1807
+ "codeSamples"
1808
+ ];
1809
+
1810
+ // src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts
1811
+ function parseSectionHash(hash) {
1812
+ const raw = hash.startsWith("#") ? hash.slice(1) : hash;
1813
+ if (!raw.startsWith("section=")) return null;
1814
+ const value = raw.slice("section=".length);
1815
+ const dot = value.lastIndexOf(".");
1816
+ if (dot <= 0 || dot === value.length - 1) return null;
1817
+ const endpointId = value.slice(0, dot);
1818
+ const sectionIdCandidate = value.slice(dot + 1);
1819
+ if (!ALL_SECTION_IDS.includes(sectionIdCandidate)) return null;
1820
+ return { endpointId, sectionId: sectionIdCandidate };
1821
+ }
1822
+ __name(parseSectionHash, "parseSectionHash");
1823
+ function buildSectionHash(endpointId, sectionId) {
1824
+ return `section=${endpointId}.${sectionId}`;
1825
+ }
1826
+ __name(buildSectionHash, "buildSectionHash");
1827
+ function useSectionHashRouter() {
1828
+ const setSectionOpen = useEndpointDocStore((s) => s.setSectionOpen);
1829
+ useEffect(() => {
1830
+ if (typeof window === "undefined") return;
1831
+ function apply() {
1832
+ const parsed = parseSectionHash(window.location.hash);
1833
+ if (!parsed) return;
1834
+ setSectionOpen(parsed.endpointId, parsed.sectionId, true);
1835
+ requestAnimationFrame(() => {
1836
+ const el = document.getElementById(parsed.endpointId);
1837
+ el?.scrollIntoView({ behavior: "smooth", block: "start" });
1838
+ });
1839
+ }
1840
+ __name(apply, "apply");
1841
+ apply();
1842
+ window.addEventListener("hashchange", apply);
1843
+ return () => window.removeEventListener("hashchange", apply);
1844
+ }, [setSectionOpen]);
1845
+ }
1846
+ __name(useSectionHashRouter, "useSectionHashRouter");
1847
+ function SectionHeader({ sectionId, title, badge, open, onToggle }) {
1848
+ const { endpointId } = useEndpointDocContext();
1849
+ const [copied, setCopied] = useState(false);
1850
+ const copyHash = /* @__PURE__ */ __name((e) => {
1851
+ e.stopPropagation();
1852
+ if (typeof window === "undefined") return;
1853
+ const hash = buildSectionHash(endpointId, sectionId);
1854
+ const url = `${window.location.origin}${window.location.pathname}#${hash}`;
1855
+ void navigator.clipboard?.writeText(url).then(() => {
1856
+ setCopied(true);
1857
+ setTimeout(() => setCopied(false), 1200);
1858
+ });
1859
+ }, "copyHash");
1860
+ return /* @__PURE__ */ jsxs(
1861
+ "div",
1862
+ {
1863
+ className: cn(
1864
+ "group/section w-full flex items-center gap-2 py-1.5 -ml-1 px-1 rounded cursor-pointer",
1865
+ "hover:bg-muted/30 transition-colors"
1866
+ ),
1867
+ onClick: onToggle,
1868
+ role: "button",
1869
+ "aria-expanded": open,
1870
+ tabIndex: 0,
1871
+ onKeyDown: (e) => {
1872
+ if (e.key === "Enter" || e.key === " ") {
1873
+ e.preventDefault();
1874
+ onToggle();
1875
+ }
1876
+ },
1877
+ children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ ChevronDown,
1880
+ {
1881
+ className: cn(
1882
+ "h-3.5 w-3.5 text-muted-foreground/50 transition-transform shrink-0",
1883
+ !open && "-rotate-90"
1884
+ )
1885
+ }
1886
+ ),
1887
+ /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground/80", children: title }),
1888
+ typeof badge === "number" && badge > 0 && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50 tabular-nums", children: badge }),
1889
+ /* @__PURE__ */ jsx(
1890
+ "button",
1891
+ {
1892
+ type: "button",
1893
+ onClick: copyHash,
1894
+ title: "Copy link to this section",
1895
+ className: cn(
1896
+ "ml-auto shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all",
1897
+ "opacity-0 group-hover/section:opacity-100 focus-visible:opacity-100",
1898
+ copied && "opacity-100 text-emerald-500"
1899
+ ),
1900
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" })
1901
+ }
1902
+ )
1903
+ ]
1904
+ }
1905
+ );
1906
+ }
1907
+ __name(SectionHeader, "SectionHeader");
1908
+ function Section({ id, title, badge, children }) {
1909
+ const { endpointId, method } = useEndpointDocContext();
1910
+ const defaultOpen = defaultSectionOpen(method, id);
1911
+ const open = useIsSectionOpen(endpointId, id, defaultOpen);
1912
+ const toggleSection = useEndpointDocStore((s) => s.toggleSection);
1913
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
1914
+ /* @__PURE__ */ jsx(
1915
+ SectionHeader,
1916
+ {
1917
+ sectionId: id,
1918
+ title,
1919
+ badge,
1920
+ open,
1921
+ onToggle: () => toggleSection(endpointId, id)
1922
+ }
1923
+ ),
1924
+ open && /* @__PURE__ */ jsx("div", { children })
1925
+ ] });
1926
+ }
1927
+ __name(Section, "Section");
1928
+ function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt, schemaId }) {
1929
+ const scopedSchemaId = schemaId ?? endpoint.schemaId ?? null;
1930
+ const anchor = endpointAnchor(endpoint, scopedSchemaId);
1931
+ const pathParams = endpoint.parameters?.filter((p) => endpoint.path.includes(`{${p.name}}`)) ?? [];
1932
+ const queryParams = endpoint.parameters?.filter((p) => !endpoint.path.includes(`{${p.name}}`)) ?? [];
1933
+ const hasParameters = pathParams.length > 0 || queryParams.length > 0;
1934
+ const hasResponses = (endpoint.responses?.length ?? 0) > 0;
1935
+ const presentSections = [];
1936
+ if (hasParameters) presentSections.push("parameters");
1937
+ if (endpoint.requestBody) presentSections.push("requestBody");
1938
+ presentSections.push("codeSamples");
1939
+ if (hasResponses) presentSections.push("responses");
1940
+ return /* @__PURE__ */ jsx(EndpointDocProvider, { endpointId: anchor, method: endpoint.method, children: /* @__PURE__ */ jsxs(
1941
+ "section",
1942
+ {
1943
+ id: anchor,
1944
+ "data-endpoint-anchor": anchor,
1945
+ "data-schema-id": scopedSchemaId ?? "",
1946
+ className: "scroll-mt-24 py-10 first:pt-0",
1947
+ children: [
1948
+ /* @__PURE__ */ jsx(
1949
+ EndpointHeader,
1950
+ {
1951
+ endpoint,
1952
+ anchor,
1953
+ isLoadedInPlayground,
1954
+ onTryIt,
1955
+ presentSections
1956
+ }
1957
+ ),
1958
+ /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-5", children: [
1959
+ hasParameters && /* @__PURE__ */ jsx(
1960
+ Section,
1961
+ {
1962
+ id: "parameters",
1963
+ title: "Parameters",
1964
+ badge: pathParams.length + queryParams.length,
1965
+ children: /* @__PURE__ */ jsx(Parameters, { pathParams, queryParams })
1966
+ }
1967
+ ),
1968
+ endpoint.requestBody && /* @__PURE__ */ jsx(Section, { id: "requestBody", title: "Request body", children: /* @__PURE__ */ jsx(RequestBody, { body: endpoint.requestBody }) }),
1969
+ /* @__PURE__ */ jsx(Section, { id: "codeSamples", title: "Code samples", children: /* @__PURE__ */ jsx(CodeSamples, { endpoint }) }),
1970
+ hasResponses && /* @__PURE__ */ jsx(
1971
+ Section,
1972
+ {
1973
+ id: "responses",
1974
+ title: "Responses",
1975
+ badge: endpoint.responses.length,
1976
+ children: /* @__PURE__ */ jsx(Responses, { responses: endpoint.responses })
1977
+ }
1978
+ )
1979
+ ] })
1980
+ ]
1981
+ }
1982
+ ) });
1983
+ }
1984
+ __name(EndpointDoc, "EndpointDoc");
1985
+ var readNavbarOffset = /* @__PURE__ */ __name(() => {
1986
+ if (typeof document === "undefined") return 0;
1987
+ const raw = getComputedStyle(document.documentElement).getPropertyValue("--navbar-height");
1988
+ const parsed = parseInt(raw || "", 10);
1989
+ return Number.isFinite(parsed) ? parsed : 0;
1990
+ }, "readNavbarOffset");
1991
+ var isSameEndpoint = /* @__PURE__ */ __name((a, b) => a !== null && a.method === b.method && a.path === b.path, "isSameEndpoint");
1992
+ function buildEndpointRow(ep, loadedEndpoint, schemaId) {
1993
+ const keySchema = schemaId ? `${schemaId}-` : "";
1994
+ return {
1995
+ key: `${keySchema}${ep.method}-${ep.path}`,
1996
+ endpoint: ep,
1997
+ isLoaded: isSameEndpoint(loadedEndpoint, ep),
1998
+ schemaId
1999
+ };
2000
+ }
2001
+ __name(buildEndpointRow, "buildEndpointRow");
2002
+ function buildSchemaSectionVM(entry, selectedVersion, loadedEndpoint) {
2003
+ const title = entry.info?.title ?? entry.source.name;
2004
+ const version = entry.info?.version ?? null;
2005
+ const description = entry.info?.description ?? null;
2006
+ let state;
2007
+ if (entry.loading) {
2008
+ state = { kind: "loading" };
2009
+ } else if (entry.error) {
2010
+ state = { kind: "error", message: entry.error };
2011
+ } else {
2012
+ const visible = deduplicateEndpoints(entry.endpoints, selectedVersion);
2013
+ state = visible.length === 0 ? { kind: "empty" } : {
2014
+ kind: "ready",
2015
+ rows: visible.map((ep) => buildEndpointRow(ep, loadedEndpoint, entry.source.id))
2016
+ };
2017
+ }
2018
+ return {
2019
+ schemaId: entry.source.id,
2020
+ title,
2021
+ version,
2022
+ description,
2023
+ state,
2024
+ rawSchema: entry.rawSchema,
2025
+ baseUrl: entry.resolvedBaseUrl,
2026
+ allEndpoints: entry.endpoints
2027
+ };
2028
+ }
2029
+ __name(buildSchemaSectionVM, "buildSchemaSectionVM");
2030
+ var DocsView = React12.forwardRef(/* @__PURE__ */ __name(function DocsView2(props, ref) {
2031
+ const scrollRef = useRef(null);
2032
+ const scrollTargetRef = useRef(null);
2033
+ const { onActiveChange } = props;
2034
+ useSectionHashRouter();
2035
+ const ensureScrollTarget = useCallback(() => {
2036
+ if (scrollTargetRef.current) return scrollTargetRef.current;
2037
+ if (!scrollRef.current) return null;
2038
+ scrollTargetRef.current = getScrollParent(scrollRef.current);
2039
+ return scrollTargetRef.current;
2040
+ }, []);
2041
+ const scrollToAnchor = useCallback(
2042
+ (anchor) => {
2043
+ const root = scrollRef.current;
2044
+ if (!root) return;
2045
+ const el = root.querySelector(`[data-endpoint-anchor="${anchor}"]`);
2046
+ if (!el) return;
2047
+ const target = ensureScrollTarget();
2048
+ if (!target) return;
2049
+ const navbar = readNavbarOffset();
2050
+ const top = el.getBoundingClientRect().top - getTargetTop(target) + getScrollTop(target) - navbar - 8;
2051
+ scrollTargetTo(target, top);
2052
+ },
2053
+ [ensureScrollTarget]
2054
+ );
2055
+ React12.useImperativeHandle(ref, () => ({ scrollToAnchor }), [scrollToAnchor]);
2056
+ useEffect(() => {
2057
+ const root = scrollRef.current;
2058
+ if (!root) return;
2059
+ const target = ensureScrollTarget();
2060
+ if (!target) return;
2061
+ let rafId = 0;
2062
+ let lastActive = null;
2063
+ const compute = /* @__PURE__ */ __name(() => {
2064
+ rafId = 0;
2065
+ const sections = root.querySelectorAll("[data-endpoint-anchor]");
2066
+ if (sections.length === 0) return;
2067
+ const navbar = readNavbarOffset();
2068
+ const viewportTop = getTargetTop(target);
2069
+ const threshold = viewportTop + navbar + getViewportHeight(target) * 0.25;
2070
+ let active = null;
2071
+ for (const s of Array.from(sections)) {
2072
+ const top = s.getBoundingClientRect().top;
2073
+ if (top <= threshold) {
2074
+ active = s;
2075
+ } else {
2076
+ break;
2077
+ }
2078
+ }
2079
+ const anchor = active?.dataset.endpointAnchor ?? null;
2080
+ if (anchor !== lastActive) {
2081
+ lastActive = anchor;
2082
+ onActiveChange(anchor, active?.dataset.schemaId || null);
2083
+ }
2084
+ }, "compute");
2085
+ const onScroll = /* @__PURE__ */ __name(() => {
2086
+ if (rafId) return;
2087
+ rafId = requestAnimationFrame(compute);
2088
+ }, "onScroll");
2089
+ compute();
2090
+ target.addEventListener("scroll", onScroll, { passive: true });
2091
+ window.addEventListener("resize", onScroll, { passive: true });
2092
+ return () => {
2093
+ target.removeEventListener("scroll", onScroll);
2094
+ window.removeEventListener("resize", onScroll);
2095
+ if (rafId) cancelAnimationFrame(rafId);
2096
+ };
2097
+ }, [onActiveChange, ensureScrollTarget, props]);
2098
+ if (props.grouping === "sections") {
2099
+ return /* @__PURE__ */ jsx(SectionsBody, { scrollRef, ...props });
2100
+ }
2101
+ return /* @__PURE__ */ jsx(SelectorBody, { scrollRef, ...props });
2102
+ }, "DocsView"));
2103
+ function SelectorBody({
2104
+ scrollRef,
2105
+ info,
2106
+ rawSchema,
2107
+ resolvedBaseUrl,
2108
+ endpoints,
2109
+ selectedVersion,
2110
+ loadedEndpoint,
2111
+ onTryEndpoint
2112
+ }) {
2113
+ const visibleEndpoints = useMemo(
2114
+ () => deduplicateEndpoints(endpoints, selectedVersion),
2115
+ [endpoints, selectedVersion]
2116
+ );
2117
+ const rows = useMemo(
2118
+ () => visibleEndpoints.map((ep) => buildEndpointRow(ep, loadedEndpoint, ep.schemaId ?? null)),
2119
+ [visibleEndpoints, loadedEndpoint]
2120
+ );
2121
+ const isEmpty = rows.length === 0;
2122
+ return /* @__PURE__ */ jsx("div", { ref: scrollRef, children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12", children: [
2123
+ info && /* @__PURE__ */ jsx(
2124
+ ApiIntroSection,
2125
+ {
2126
+ info,
2127
+ schema: rawSchema,
2128
+ endpoints: visibleEndpoints,
2129
+ resolvedBaseUrl
2130
+ }
2131
+ ),
2132
+ isEmpty ? /* @__PURE__ */ jsx("div", { className: "py-16 text-center text-sm text-muted-foreground", children: "No endpoints to display." }) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60", children: rows.map((row) => /* @__PURE__ */ jsx(
2133
+ EndpointDoc,
2134
+ {
2135
+ endpoint: row.endpoint,
2136
+ isLoadedInPlayground: row.isLoaded,
2137
+ onTryIt: () => onTryEndpoint(row.endpoint),
2138
+ schemaId: row.schemaId
2139
+ },
2140
+ row.key
2141
+ )) })
2142
+ ] }) });
2143
+ }
2144
+ __name(SelectorBody, "SelectorBody");
2145
+ function SectionsBody({
2146
+ scrollRef,
2147
+ schemasData,
2148
+ selectedVersion,
2149
+ loadedEndpoint,
2150
+ onTryEndpoint
2151
+ }) {
2152
+ const sections = useMemo(
2153
+ () => schemasData.map((e) => buildSchemaSectionVM(e, selectedVersion, loadedEndpoint)),
2154
+ [schemasData, selectedVersion, loadedEndpoint]
2155
+ );
2156
+ return /* @__PURE__ */ jsx("div", { ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12 space-y-16", children: sections.map((section) => /* @__PURE__ */ jsx(SchemaSectionView, { section, onTryEndpoint }, section.schemaId)) }) });
2157
+ }
2158
+ __name(SectionsBody, "SectionsBody");
2159
+ var SchemaSectionView = React12.memo(/* @__PURE__ */ __name(function SchemaSectionView2({
2160
+ section,
2161
+ onTryEndpoint
2162
+ }) {
2163
+ const canCopy = section.rawSchema !== null && section.allEndpoints.length > 0;
2164
+ return /* @__PURE__ */ jsxs("section", { "data-schema-anchor": section.schemaId, className: "scroll-mt-20", children: [
2165
+ /* @__PURE__ */ jsxs("header", { className: "mb-8 pb-4 border-b", children: [
2166
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
2167
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-3 min-w-0", children: [
2168
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight", children: section.title }),
2169
+ section.version && /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground/70", children: [
2170
+ "v",
2171
+ section.version
2172
+ ] })
2173
+ ] }),
2174
+ canCopy && /* @__PURE__ */ jsx(
2175
+ SchemaCopyMenu,
2176
+ {
2177
+ schema: section.rawSchema,
2178
+ endpoints: section.allEndpoints,
2179
+ baseUrl: section.baseUrl
2180
+ }
2181
+ )
2182
+ ] }),
2183
+ section.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground whitespace-pre-wrap", children: section.description })
2184
+ ] }),
2185
+ /* @__PURE__ */ jsx(SchemaSectionStateView, { section, onTryEndpoint })
2186
+ ] });
2187
+ }, "SchemaSectionView"));
2188
+ function SchemaSectionStateView({
2189
+ section,
2190
+ onTryEndpoint
2191
+ }) {
2192
+ switch (section.state.kind) {
2193
+ case "loading":
2194
+ return /* @__PURE__ */ jsxs("div", { className: "py-8 text-center text-sm text-muted-foreground", children: [
2195
+ "Loading ",
2196
+ section.title,
2197
+ "\u2026"
2198
+ ] });
2199
+ case "error":
2200
+ return /* @__PURE__ */ jsxs("div", { className: "py-8 text-center text-sm text-destructive", children: [
2201
+ "Failed to load ",
2202
+ section.title,
2203
+ ": ",
2204
+ section.state.message
2205
+ ] });
2206
+ case "empty":
2207
+ return /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: "No endpoints in this schema." });
2208
+ case "ready":
2209
+ return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60", children: section.state.rows.map((row) => /* @__PURE__ */ jsx(
2210
+ EndpointDoc,
2211
+ {
2212
+ endpoint: row.endpoint,
2213
+ isLoadedInPlayground: row.isLoaded,
2214
+ onTryIt: () => onTryEndpoint(row.endpoint),
2215
+ schemaId: row.schemaId
2216
+ },
2217
+ row.key
2218
+ )) });
2219
+ }
2220
+ }
2221
+ __name(SchemaSectionStateView, "SchemaSectionStateView");
2222
+ var MAX_DEPTH2 = 6;
2223
+ function defaultForSchema(schema) {
2224
+ if (!schema) return null;
2225
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
2226
+ switch (schema.type) {
2227
+ case "object": {
2228
+ const out = {};
2229
+ for (const [k, v] of Object.entries(schema.properties ?? {})) {
2230
+ out[k] = defaultForSchema(v);
2231
+ }
2232
+ return out;
2233
+ }
2234
+ case "array":
2235
+ return [];
2236
+ case "integer":
2237
+ case "number":
2238
+ return 0;
2239
+ case "boolean":
2240
+ return false;
2241
+ case "string":
2242
+ return "";
2243
+ default:
2244
+ if (schema.properties) {
2245
+ const out = {};
2246
+ for (const [k, v] of Object.entries(schema.properties)) {
2247
+ out[k] = defaultForSchema(v);
2248
+ }
2249
+ return out;
2250
+ }
2251
+ return "";
2252
+ }
2253
+ }
2254
+ __name(defaultForSchema, "defaultForSchema");
2255
+ function BodyFormEditor({ schema, value, onChange }) {
2256
+ return /* @__PURE__ */ jsx(
2257
+ SchemaField,
2258
+ {
2259
+ schema,
2260
+ value,
2261
+ onChange,
2262
+ depth: 0,
2263
+ required: false
2264
+ }
2265
+ );
2266
+ }
2267
+ __name(BodyFormEditor, "BodyFormEditor");
2268
+ function SchemaField({ schema, value, onChange, depth, required, label }) {
2269
+ if (depth > MAX_DEPTH2) {
2270
+ return /* @__PURE__ */ jsx(RawJsonField, { label, value, onChange });
2271
+ }
2272
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
2273
+ return /* @__PURE__ */ jsx(EnumField, { schema, value, onChange, label, required });
2274
+ }
2275
+ switch (schema.type) {
2276
+ case "object":
2277
+ return /* @__PURE__ */ jsx(ObjectField, { schema, value, onChange, depth, label });
2278
+ case "array":
2279
+ return /* @__PURE__ */ jsx(ArrayField, { schema, value, onChange, depth, label, required });
2280
+ case "boolean":
2281
+ return /* @__PURE__ */ jsx(BooleanField, { value, onChange, label, schema, required });
2282
+ case "integer":
2283
+ case "number":
2284
+ return /* @__PURE__ */ jsx(NumberField, { value, onChange, label, schema, required });
2285
+ case "string":
2286
+ default:
2287
+ if (!schema.type && schema.properties) {
2288
+ return /* @__PURE__ */ jsx(ObjectField, { schema, value, onChange, depth, label });
2289
+ }
2290
+ return /* @__PURE__ */ jsx(StringField, { value, onChange, label, schema, required });
2291
+ }
2292
+ }
2293
+ __name(SchemaField, "SchemaField");
2294
+ function FieldHeader({
2295
+ label,
2296
+ type,
2297
+ required,
2298
+ description
2299
+ }) {
2300
+ if (!label) return null;
2301
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
2302
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
2303
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
2304
+ required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
2305
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: type })
2306
+ ] }),
2307
+ description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/70 leading-snug", children: description })
2308
+ ] });
2309
+ }
2310
+ __name(FieldHeader, "FieldHeader");
2311
+ function StringField({
2312
+ value,
2313
+ onChange,
2314
+ label,
2315
+ schema,
2316
+ required
2317
+ }) {
2318
+ const stringValue = typeof value === "string" ? value : value == null ? "" : String(value);
2319
+ const placeholder = schema.format ? `${schema.type ?? "string"} (${schema.format})` : schema.description || schema.type || "string";
2320
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2321
+ /* @__PURE__ */ jsx(FieldHeader, { label, type: schema.format ? `string (${schema.format})` : "string", required, description: schema.description }),
2322
+ /* @__PURE__ */ jsx(
2323
+ Input,
2324
+ {
2325
+ value: stringValue,
2326
+ onChange: (e) => onChange(e.target.value),
2327
+ placeholder,
2328
+ className: "h-8 text-xs font-mono"
2329
+ }
2330
+ )
2331
+ ] });
2332
+ }
2333
+ __name(StringField, "StringField");
2334
+ function NumberField({
2335
+ value,
2336
+ onChange,
2337
+ label,
2338
+ schema,
2339
+ required
2340
+ }) {
2341
+ const raw = value == null ? "" : String(value);
2342
+ const type = schema.type === "integer" ? "integer" : "number";
2343
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2344
+ /* @__PURE__ */ jsx(FieldHeader, { label, type: schema.format ? `${type} (${schema.format})` : type, required, description: schema.description }),
2345
+ /* @__PURE__ */ jsx(
2346
+ Input,
2347
+ {
2348
+ type: "number",
2349
+ value: raw,
2350
+ onChange: (e) => {
2351
+ const v = e.target.value;
2352
+ if (v === "") return onChange(null);
2353
+ const n = schema.type === "integer" ? parseInt(v, 10) : parseFloat(v);
2354
+ onChange(Number.isNaN(n) ? null : n);
2355
+ },
2356
+ placeholder: type,
2357
+ className: "h-8 text-xs font-mono"
2358
+ }
2359
+ )
2360
+ ] });
2361
+ }
2362
+ __name(NumberField, "NumberField");
2363
+ function BooleanField({
2364
+ value,
2365
+ onChange,
2366
+ label,
2367
+ schema,
2368
+ required
2369
+ }) {
2370
+ const bool = value === true;
2371
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
2372
+ /* @__PURE__ */ jsx(FieldHeader, { label, type: "boolean", required, description: schema.description }),
2373
+ /* @__PURE__ */ jsx(Switch, { checked: bool, onCheckedChange: onChange, className: "mt-0.5 shrink-0" })
2374
+ ] });
2375
+ }
2376
+ __name(BooleanField, "BooleanField");
2377
+ function EnumField({
2378
+ schema,
2379
+ value,
2380
+ onChange,
2381
+ label,
2382
+ required
2383
+ }) {
2384
+ const options = (schema.enum ?? []).map((v) => ({
2385
+ value: String(v),
2386
+ label: String(v)
2387
+ }));
2388
+ const strValue = value == null ? "" : String(value);
2389
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2390
+ /* @__PURE__ */ jsx(FieldHeader, { label, type: `${schema.type ?? "enum"} enum`, required, description: schema.description }),
2391
+ /* @__PURE__ */ jsx(
2392
+ Combobox,
2393
+ {
2394
+ options,
2395
+ value: strValue,
2396
+ onValueChange: (v) => {
2397
+ if (schema.type === "integer") onChange(parseInt(v, 10));
2398
+ else if (schema.type === "number") onChange(parseFloat(v));
2399
+ else onChange(v);
2400
+ },
2401
+ placeholder: "Select\u2026",
2402
+ searchPlaceholder: "Search\u2026",
2403
+ className: "h-8 text-xs"
2404
+ }
2405
+ )
2406
+ ] });
2407
+ }
2408
+ __name(EnumField, "EnumField");
2409
+ function RawJsonField({
2410
+ label,
2411
+ value,
2412
+ onChange
2413
+ }) {
2414
+ const [text, setText] = React12.useState(() => JSON.stringify(value ?? null, null, 2));
2415
+ React12.useEffect(() => {
2416
+ setText(JSON.stringify(value ?? null, null, 2));
2417
+ }, [value]);
2418
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2419
+ label && /* @__PURE__ */ jsxs(SectionLabel, { children: [
2420
+ label,
2421
+ " (raw)"
2422
+ ] }),
2423
+ /* @__PURE__ */ jsx(
2424
+ Textarea,
2425
+ {
2426
+ value: text,
2427
+ onChange: (e) => {
2428
+ setText(e.target.value);
2429
+ try {
2430
+ onChange(JSON.parse(e.target.value));
2431
+ } catch {
2432
+ }
2433
+ },
2434
+ className: "font-mono text-[11px] min-h-[80px]",
2435
+ rows: 4
2436
+ }
2437
+ )
2438
+ ] });
2439
+ }
2440
+ __name(RawJsonField, "RawJsonField");
2441
+ function ObjectField({
2442
+ schema,
2443
+ value,
2444
+ onChange,
2445
+ depth,
2446
+ label
2447
+ }) {
2448
+ const obj = value && typeof value === "object" && !Array.isArray(value) ? value : {};
2449
+ const required = new Set(schema.required ?? []);
2450
+ const entries = Object.entries(schema.properties ?? {});
2451
+ const setKey = useCallback(
2452
+ (key) => (next) => {
2453
+ onChange({ ...obj, [key]: next });
2454
+ },
2455
+ [obj, onChange]
2456
+ );
2457
+ if (entries.length === 0) {
2458
+ return /* @__PURE__ */ jsx(RawJsonField, { label, value: obj, onChange });
2459
+ }
2460
+ const wrapperClass = depth === 0 ? "space-y-3" : "space-y-2 border-l-2 border-border/60 pl-3 ml-px";
2461
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2462
+ label && depth > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
2463
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
2464
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: "object" })
2465
+ ] }),
2466
+ /* @__PURE__ */ jsx("div", { className: cn(wrapperClass), children: entries.map(([key, subSchema]) => /* @__PURE__ */ jsx(
2467
+ SchemaField,
2468
+ {
2469
+ schema: subSchema,
2470
+ value: obj[key],
2471
+ onChange: setKey(key),
2472
+ depth: depth + 1,
2473
+ required: required.has(key),
2474
+ label: key
2475
+ },
2476
+ key
2477
+ )) })
2478
+ ] });
2479
+ }
2480
+ __name(ObjectField, "ObjectField");
2481
+ function ArrayField({
2482
+ schema,
2483
+ value,
2484
+ onChange,
2485
+ depth,
2486
+ label,
2487
+ required
2488
+ }) {
2489
+ const arr = Array.isArray(value) ? value : [];
2490
+ const items = schema.items ?? { type: "string" };
2491
+ const addItem = /* @__PURE__ */ __name(() => onChange([...arr, defaultForSchema(items)]), "addItem");
2492
+ const removeAt = /* @__PURE__ */ __name((i) => onChange(arr.filter((_, idx) => idx !== i)), "removeAt");
2493
+ const setAt = /* @__PURE__ */ __name((i) => (next) => onChange(arr.map((v, idx) => idx === i ? next : v)), "setAt");
2494
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2495
+ label && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
2496
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
2497
+ required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
2498
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: `array<${items.type ?? "any"}>` })
2499
+ ] }),
2500
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-l-2 border-border/60 pl-3 ml-px", children: [
2501
+ arr.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/50 italic", children: "Empty array" }),
2502
+ arr.map((v, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2503
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(
2504
+ SchemaField,
2505
+ {
2506
+ schema: items,
2507
+ value: v,
2508
+ onChange: setAt(i),
2509
+ depth: depth + 1,
2510
+ required: false,
2511
+ label: `${label ?? ""}[${i}]`
2512
+ }
2513
+ ) }),
2514
+ /* @__PURE__ */ jsx(
2515
+ "button",
2516
+ {
2517
+ type: "button",
2518
+ onClick: () => removeAt(i),
2519
+ title: "Remove item",
2520
+ className: "shrink-0 h-7 w-7 inline-flex items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors",
2521
+ children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
2522
+ }
2523
+ )
2524
+ ] }, i)),
2525
+ /* @__PURE__ */ jsxs(
2526
+ "button",
2527
+ {
2528
+ type: "button",
2529
+ onClick: addItem,
2530
+ className: "inline-flex items-center gap-1.5 text-[10px] text-muted-foreground hover:text-foreground transition-colors py-1",
2531
+ children: [
2532
+ /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
2533
+ "Add item"
2534
+ ]
2535
+ }
2536
+ )
2537
+ ] })
2538
+ ] });
2539
+ }
2540
+ __name(ArrayField, "ArrayField");
2541
+ function EndpointResetButton() {
2542
+ const { state, setParameters, setRequestBody } = usePlaygroundContext();
2543
+ const ep = state.selectedEndpoint;
2544
+ const { reset } = useEndpointDraft(state.activeSchemaId, ep);
2545
+ const hasDraft = Object.keys(state.parameters).length > 0 || state.requestBody.length > 0;
2546
+ const onClick = useCallback(() => {
2547
+ setParameters({});
2548
+ setRequestBody("");
2549
+ reset();
2550
+ }, [setParameters, setRequestBody, reset]);
2551
+ if (!ep || !hasDraft) return null;
2552
+ return /* @__PURE__ */ jsxs(
2553
+ "button",
2554
+ {
2555
+ type: "button",
2556
+ onClick,
2557
+ title: "Reset parameters & body (keeps auth)",
2558
+ className: cn(
2559
+ "inline-flex items-center gap-1 text-[10px] text-muted-foreground",
2560
+ "hover:text-foreground transition-colors"
2561
+ ),
2562
+ children: [
2563
+ /* @__PURE__ */ jsx(RotateCcw, { className: "h-2.5 w-2.5" }),
2564
+ "Reset"
2565
+ ]
2566
+ }
2567
+ );
2568
+ }
2569
+ __name(EndpointResetButton, "EndpointResetButton");
2570
+ function ParamFields({ label, params }) {
2571
+ const { state, setParameters } = usePlaygroundContext();
2572
+ function handleChange(name, value) {
2573
+ setParameters({ ...state.parameters, [name]: value });
2574
+ }
2575
+ __name(handleChange, "handleChange");
2576
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2577
+ /* @__PURE__ */ jsx(SectionLabel, { children: label }),
2578
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: params.map((p) => {
2579
+ const value = state.parameters[p.name] ?? "";
2580
+ const placeholder = p.description || p.name;
2581
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2582
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2583
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: p.name }),
2584
+ p.required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
2585
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: p.type })
2586
+ ] }),
2587
+ /* @__PURE__ */ jsx(
2588
+ Input,
2589
+ {
2590
+ value,
2591
+ onChange: (e) => handleChange(p.name, e.target.value),
2592
+ placeholder,
2593
+ className: "h-8 text-xs font-mono"
2594
+ }
2595
+ )
2596
+ ] }, p.name);
2597
+ }) })
2598
+ ] });
2599
+ }
2600
+ __name(ParamFields, "ParamFields");
2601
+ function RequestPanel() {
2602
+ const {
2603
+ state,
2604
+ apiKeys,
2605
+ apiKeysLoading,
2606
+ setRequestBody,
2607
+ setRequestHeaders,
2608
+ setSelectedApiKey,
2609
+ setManualApiToken,
2610
+ sendRequest
2611
+ } = usePlaygroundContext();
2612
+ const apiKeyOptions = useMemo(
2613
+ () => apiKeys.map((k) => ({
2614
+ value: k.id,
2615
+ label: k.name || "Unnamed key",
2616
+ // Surface the first 8 chars of the secret so the user
2617
+ // can tell two similarly-named keys apart at a glance.
2618
+ description: k.secret ? `${k.secret.slice(0, 8)}\u2026` : void 0
2619
+ })),
2620
+ [apiKeys]
2621
+ );
2622
+ const hasApiKeys = apiKeyOptions.length > 0;
2623
+ const ep = state.selectedEndpoint;
2624
+ const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
2625
+ const curlCommand = useMemo(() => {
2626
+ if (!state.requestUrl) return "";
2627
+ const absoluteUrl = resolveAbsolute(state.requestUrl);
2628
+ const apiKey = state.selectedApiKey ? findApiKeyById(apiKeys, state.selectedApiKey) : null;
2629
+ const hdrs = parseRequestHeaders(state.requestHeaders);
2630
+ if (apiKey) hdrs["X-API-Key"] = apiKey.secret || apiKey.id;
2631
+ let cmd = `curl -X ${state.requestMethod} "${absoluteUrl}"`;
2632
+ Object.entries(hdrs).forEach(([k, v]) => {
2633
+ cmd += ` \\
2634
+ -H "${k}: ${v}"`;
2635
+ });
2636
+ if (state.requestBody && state.requestMethod !== "GET" && isJsonValid) {
2637
+ cmd += ` \\
2638
+ -d '${state.requestBody}'`;
2639
+ }
2640
+ return cmd;
2641
+ }, [state, apiKeys, isJsonValid]);
2642
+ const pathParams = useMemo(
2643
+ () => ep?.parameters?.filter((p) => ep.path.includes(`{${p.name}}`)) ?? [],
2644
+ [ep]
2645
+ );
2646
+ const queryParams = useMemo(
2647
+ () => ep?.parameters?.filter((p) => !ep.path.includes(`{${p.name}}`)) ?? [],
2648
+ [ep]
2649
+ );
2650
+ state.loading || !state.requestUrl || !isJsonValid;
2651
+ const displayUrl = resolveAbsolute(state.requestUrl || ep?.path || "");
2652
+ const hasBody = ep?.method !== "GET";
2653
+ const bodyType = ep?.requestBody?.type ?? "";
2654
+ const hasPathParams = pathParams.length > 0;
2655
+ const hasQueryParams = queryParams.length > 0;
2656
+ const hasCurl = Boolean(curlCommand);
2657
+ const epPath = ep ? relativePath(ep.path) : "";
2658
+ const urlChanged = displayUrl !== "" && displayUrl !== epPath;
2659
+ if (!ep) {
2660
+ return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: "Select an endpoint to build a request" });
2661
+ }
2662
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2663
+ (urlChanged || ep) && /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 bg-muted/10 flex items-center gap-2 min-h-[28px]", children: [
2664
+ urlChanged ? /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/60 break-all leading-snug truncate min-w-0 flex-1", children: displayUrl }) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
2665
+ /* @__PURE__ */ jsx(EndpointResetButton, {})
2666
+ ] }),
2667
+ /* @__PURE__ */ jsxs(ScrollArea, { className: "px-4 py-3 space-y-3", children: [
2668
+ hasPathParams && /* @__PURE__ */ jsx(ParamFields, { label: "Path Parameters", params: pathParams }),
2669
+ hasQueryParams && /* @__PURE__ */ jsx(ParamFields, { label: "Query Parameters", params: queryParams }),
2670
+ hasBody && /* @__PURE__ */ jsx(
2671
+ BodySection,
2672
+ {
2673
+ schema: ep.requestBody?.schema,
2674
+ bodyType,
2675
+ bodyDescription: ep.requestBody?.description,
2676
+ value: state.requestBody,
2677
+ onChange: setRequestBody,
2678
+ isJsonValid
2679
+ }
2680
+ ),
2681
+ /* @__PURE__ */ jsx(
2682
+ CollapsibleSection,
2683
+ {
2684
+ label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
2685
+ /* @__PURE__ */ jsx(Key, { className: "h-2.5 w-2.5" }),
2686
+ "Auth & Headers"
2687
+ ] }),
2688
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 pt-2", children: [
2689
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2690
+ /* @__PURE__ */ jsx(SectionLabel, { children: "API Key" }),
2691
+ /* @__PURE__ */ jsx(
2692
+ Combobox,
2693
+ {
2694
+ options: apiKeyOptions,
2695
+ value: state.selectedApiKey ?? "",
2696
+ onValueChange: (v) => setSelectedApiKey(v || null),
2697
+ placeholder: apiKeysLoading ? "Loading keys\u2026" : hasApiKeys ? "Select an API key" : "No API keys yet",
2698
+ searchPlaceholder: "Search keys\u2026",
2699
+ emptyText: "No matching key",
2700
+ disabled: apiKeysLoading || !hasApiKeys,
2701
+ className: "h-8"
2702
+ }
2703
+ ),
2704
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
2705
+ "Picks are sent via the",
2706
+ " ",
2707
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: "X-API-Key" }),
2708
+ " header."
2709
+ ] })
2710
+ ] }),
2711
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2712
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Bearer Token" }),
2713
+ /* @__PURE__ */ jsx(
2714
+ Input,
2715
+ {
2716
+ type: "password",
2717
+ placeholder: "Leave empty to use JWT from localStorage",
2718
+ value: state.manualApiToken,
2719
+ onChange: (e) => setManualApiToken(e.target.value),
2720
+ className: "font-mono text-xs h-8"
2721
+ }
2722
+ )
2723
+ ] }),
2724
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2725
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Headers" }),
2726
+ /* @__PURE__ */ jsx(
2727
+ Textarea,
2728
+ {
2729
+ value: state.requestHeaders,
2730
+ onChange: (e) => setRequestHeaders(e.target.value),
2731
+ className: "font-mono text-[11px] min-h-[60px] resize-y",
2732
+ rows: 3
2733
+ }
2734
+ )
2735
+ ] })
2736
+ ] })
2737
+ }
2738
+ ),
2739
+ hasCurl && /* @__PURE__ */ jsx(
2740
+ CollapsibleSection,
2741
+ {
2742
+ label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
2743
+ /* @__PURE__ */ jsx(Terminal, { className: "h-2.5 w-2.5" }),
2744
+ "cURL"
2745
+ ] }),
2746
+ children: /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
2747
+ PrettyCode_default,
2748
+ {
2749
+ data: curlCommand,
2750
+ language: "bash",
2751
+ isCompact: true,
2752
+ maxLines: 50
2753
+ }
2754
+ ) })
2755
+ }
2756
+ ),
2757
+ /* @__PURE__ */ jsx("div", { className: "h-4" })
2758
+ ] })
2759
+ ] });
2760
+ }
2761
+ __name(RequestPanel, "RequestPanel");
2762
+ function BodySection({ schema, bodyType, bodyDescription, value, onChange, isJsonValid }) {
2763
+ const hasSchema = !!schema;
2764
+ const [mode, setMode] = React12.useState(hasSchema ? "form" : "json");
2765
+ const parsed = React12.useMemo(() => {
2766
+ if (!value) return null;
2767
+ try {
2768
+ return JSON.parse(value);
2769
+ } catch {
2770
+ return null;
2771
+ }
2772
+ }, [value]);
2773
+ const handleFormChange = useCallback(
2774
+ (next) => {
2775
+ onChange(JSON.stringify(next, null, 2));
2776
+ },
2777
+ [onChange]
2778
+ );
2779
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2780
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
2781
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 min-w-0", children: [
2782
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Body" }),
2783
+ bodyType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/40 font-mono", children: bodyType }),
2784
+ bodyDescription && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60 truncate", children: bodyDescription })
2785
+ ] }),
2786
+ hasSchema && /* @__PURE__ */ jsxs("div", { className: "inline-flex rounded-md border overflow-hidden text-[10px]", children: [
2787
+ /* @__PURE__ */ jsx(ModeButton, { active: mode === "form", onClick: () => setMode("form"), children: "Form" }),
2788
+ /* @__PURE__ */ jsx(ModeButton, { active: mode === "json", onClick: () => setMode("json"), children: "JSON" })
2789
+ ] }),
2790
+ mode === "json" && isJsonValid && value && /* @__PURE__ */ jsxs(
2791
+ "button",
2792
+ {
2793
+ type: "button",
2794
+ onClick: () => {
2795
+ try {
2796
+ onChange(JSON.stringify(JSON.parse(value), null, 2));
2797
+ } catch {
2798
+ }
2799
+ },
2800
+ className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors",
2801
+ children: [
2802
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-2.5 w-2.5" }),
2803
+ "Format"
2804
+ ]
2805
+ }
2806
+ )
2807
+ ] }),
2808
+ mode === "form" && hasSchema ? /* @__PURE__ */ jsx(
2809
+ BodyFormEditor,
2810
+ {
2811
+ schema,
2812
+ value: parsed,
2813
+ onChange: handleFormChange
2814
+ }
2815
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2816
+ /* @__PURE__ */ jsx(
2817
+ Textarea,
2818
+ {
2819
+ placeholder: '{\n "key": "value"\n}',
2820
+ value,
2821
+ onChange: (e) => onChange(e.target.value),
2822
+ className: cn(
2823
+ "font-mono text-[11px] min-h-[90px] resize-y",
2824
+ !isJsonValid && "border-destructive focus-visible:ring-destructive/30"
2825
+ ),
2826
+ rows: 4
2827
+ }
2828
+ ),
2829
+ !isJsonValid && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-destructive", children: "Invalid JSON" })
2830
+ ] })
2831
+ ] });
2832
+ }
2833
+ __name(BodySection, "BodySection");
2834
+ function ModeButton({
2835
+ active,
2836
+ onClick,
2837
+ children
2838
+ }) {
2839
+ return /* @__PURE__ */ jsx(
2840
+ "button",
2841
+ {
2842
+ type: "button",
2843
+ onClick,
2844
+ className: cn(
2845
+ "px-2 py-0.5 font-medium transition-colors",
2846
+ active ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:text-foreground"
2847
+ ),
2848
+ children
2849
+ }
2850
+ );
2851
+ }
2852
+ __name(ModeButton, "ModeButton");
2853
+ function looksLikeSpaShell(html) {
2854
+ const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
2855
+ const bodyContent = (bodyMatch?.[1] ?? html).replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<!--[\s\S]*?-->/g, "").trim();
2856
+ if (bodyContent.length === 0) return true;
2857
+ const singleEmptyContainer = /^<(div|main|section)[^>]*>\s*<\/\1>$/i;
2858
+ if (singleEmptyContainer.test(bodyContent)) return true;
2859
+ return false;
2860
+ }
2861
+ __name(looksLikeSpaShell, "looksLikeSpaShell");
2862
+ function PreviewView({ html }) {
2863
+ const isSpaShell = useMemo(() => looksLikeSpaShell(html), [html]);
2864
+ if (!html) {
2865
+ return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
2866
+ }
2867
+ if (isSpaShell) {
2868
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 px-6 text-center gap-3 min-h-[400px]", children: [
2869
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center justify-center h-10 w-10 rounded-full bg-muted", children: /* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-muted-foreground" }) }),
2870
+ /* @__PURE__ */ jsxs("div", { className: "max-w-sm space-y-1.5", children: [
2871
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Looks like a single-page app shell" }),
2872
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: [
2873
+ "This page renders its content with JavaScript at runtime. Scripts are disabled in the sandbox, so Preview would show a blank page. Switch to ",
2874
+ /* @__PURE__ */ jsx("strong", { children: "Pretty" }),
2875
+ " or",
2876
+ " ",
2877
+ /* @__PURE__ */ jsx("strong", { children: "Raw" }),
2878
+ " to inspect the HTML source."
2879
+ ] })
2880
+ ] })
2881
+ ] });
2882
+ }
2883
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-[400px]", children: [
2884
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center gap-1.5 px-3 py-1.5 bg-muted/30 border-b text-[10px] text-muted-foreground/70", children: [
2885
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "h-3 w-3" }),
2886
+ "Sandboxed preview \u2014 scripts, forms and popups are disabled"
2887
+ ] }),
2888
+ /* @__PURE__ */ jsx(
2889
+ "div",
2890
+ {
2891
+ className: "flex-1 min-h-[360px] p-2",
2892
+ style: {
2893
+ backgroundColor: "#fff",
2894
+ backgroundImage: "linear-gradient(45deg, #f3f4f6 25%, transparent 25%), linear-gradient(-45deg, #f3f4f6 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f3f4f6 75%), linear-gradient(-45deg, transparent 75%, #f3f4f6 75%)",
2895
+ backgroundSize: "16px 16px",
2896
+ backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0px"
2897
+ },
2898
+ children: /* @__PURE__ */ jsx(
2899
+ "iframe",
2900
+ {
2901
+ title: "Response preview",
2902
+ srcDoc: html,
2903
+ sandbox: "",
2904
+ className: "w-full h-full min-h-[360px] bg-white border-0 rounded shadow-sm"
2905
+ }
2906
+ )
2907
+ }
2908
+ )
2909
+ ] });
2910
+ }
2911
+ __name(PreviewView, "PreviewView");
2912
+ var JSON_TREE_CONFIG = {
2913
+ maxAutoExpandDepth: 2,
2914
+ maxAutoExpandArrayItems: 10,
2915
+ maxAutoExpandObjectKeys: 5,
2916
+ maxStringLength: 200,
2917
+ collectionLimit: 50,
2918
+ showCollectionInfo: true,
2919
+ showExpandControls: true,
2920
+ showActionButtons: false,
2921
+ preserveKeyOrder: true,
2922
+ className: "border-0 rounded-none"
2923
+ };
2924
+ function PrettyView({ treeData, rawText, detected }) {
2925
+ if (detected.kind === "json" && treeData != null) {
2926
+ return /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG });
2927
+ }
2928
+ if (!rawText) {
2929
+ return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
2930
+ }
2931
+ return /* @__PURE__ */ jsx(
2932
+ PrettyCode_default,
2933
+ {
2934
+ data: rawText,
2935
+ language: detected.prism,
2936
+ variant: "plain",
2937
+ isCompact: true
2938
+ }
2939
+ );
2940
+ }
2941
+ __name(PrettyView, "PrettyView");
2942
+ function RawView({ rawText }) {
2943
+ if (!rawText) {
2944
+ return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
2945
+ }
2946
+ return /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText });
2947
+ }
2948
+ __name(RawView, "RawView");
2949
+ function StatusBar({ response, rawText, contentType }) {
2950
+ const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
2951
+ const duration = response.duration != null ? `${response.duration}ms` : "";
2952
+ const hasStatus = response.status != null;
2953
+ const hasCopy = Boolean(rawText);
2954
+ return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
2955
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
2956
+ hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
2957
+ response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
2958
+ sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb }),
2959
+ duration && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: duration }),
2960
+ contentType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono truncate", children: contentType })
2961
+ ] }),
2962
+ hasCopy && /* @__PURE__ */ jsx(
2963
+ CopyButton,
2964
+ {
2965
+ value: rawText,
2966
+ variant: "ghost",
2967
+ size: "sm",
2968
+ className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0",
2969
+ children: "Copy"
2970
+ }
2971
+ )
2972
+ ] });
2973
+ }
2974
+ __name(StatusBar, "StatusBar");
2975
+
2976
+ // src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts
2977
+ function normaliseContentType(raw) {
2978
+ if (!raw) return null;
2979
+ const semi = raw.indexOf(";");
2980
+ return (semi === -1 ? raw : raw.slice(0, semi)).trim().toLowerCase();
2981
+ }
2982
+ __name(normaliseContentType, "normaliseContentType");
2983
+ function readContentType(headers) {
2984
+ if (!headers) return null;
2985
+ if (typeof headers.get === "function") {
2986
+ return headers.get("content-type");
2987
+ }
2988
+ if (typeof headers === "object") {
2989
+ for (const [k, v] of Object.entries(headers)) {
2990
+ if (k.toLowerCase() === "content-type") {
2991
+ return typeof v === "string" ? v : null;
2992
+ }
2993
+ }
2994
+ }
2995
+ return null;
2996
+ }
2997
+ __name(readContentType, "readContentType");
2998
+ function kindFromContentType(mime) {
2999
+ if (!mime) return "text";
3000
+ if (mime === "application/json" || mime.endsWith("+json")) return "json";
3001
+ if (mime === "text/html" || mime === "application/xhtml+xml") return "html";
3002
+ if (mime === "application/xml" || mime === "text/xml" || mime.endsWith("+xml")) return "xml";
3003
+ if (mime === "text/css") return "css";
3004
+ if (mime === "application/javascript" || mime === "text/javascript" || mime === "application/x-javascript") return "javascript";
3005
+ return "text";
3006
+ }
3007
+ __name(kindFromContentType, "kindFromContentType");
3008
+ function kindFromBody(body) {
3009
+ const trimmed = body.trimStart();
3010
+ if (!trimmed) return null;
3011
+ if (trimmed.startsWith("<!DOCTYPE") || /^<html[\s>]/i.test(trimmed)) return "html";
3012
+ if (trimmed.startsWith("<?xml")) return "xml";
3013
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
3014
+ try {
3015
+ JSON.parse(trimmed);
3016
+ return "json";
3017
+ } catch {
3018
+ }
3019
+ }
3020
+ return null;
3021
+ }
3022
+ __name(kindFromBody, "kindFromBody");
3023
+ var PRISM_BY_KIND = {
3024
+ json: "json",
3025
+ // ``markup`` is Prism's HTML/XML grammar — there isn't a separate
3026
+ // ``html`` language. XML piggy-backs on the same tokeniser.
3027
+ html: "markup",
3028
+ xml: "markup",
3029
+ css: "css",
3030
+ javascript: "javascript",
3031
+ text: "markup"
3032
+ };
3033
+ function detectContent(headers, rawBody) {
3034
+ const contentType = normaliseContentType(readContentType(headers));
3035
+ const headerKind = kindFromContentType(contentType);
3036
+ const kind = headerKind === "text" ? kindFromBody(rawBody) ?? "text" : headerKind;
3037
+ return {
3038
+ kind,
3039
+ prism: PRISM_BY_KIND[kind],
3040
+ contentType
3041
+ };
3042
+ }
3043
+ __name(detectContent, "detectContent");
3044
+
3045
+ // src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts
3046
+ function useResponseView(data, headers) {
3047
+ return useMemo(() => {
3048
+ if (data == null) {
3049
+ return {
3050
+ treeData: null,
3051
+ rawText: "",
3052
+ detected: detectContent(headers, "")
3053
+ };
3054
+ }
3055
+ if (typeof data === "string") {
3056
+ try {
3057
+ return {
3058
+ treeData: JSON.parse(data),
3059
+ rawText: data,
3060
+ detected: detectContent(headers, data)
3061
+ };
3062
+ } catch {
3063
+ return {
3064
+ treeData: null,
3065
+ rawText: data,
3066
+ detected: detectContent(headers, data)
3067
+ };
3068
+ }
3069
+ }
3070
+ const stringified = (() => {
3071
+ try {
3072
+ return JSON.stringify(data, null, 2);
3073
+ } catch {
3074
+ return String(data);
3075
+ }
3076
+ })();
3077
+ return {
3078
+ treeData: data,
3079
+ rawText: stringified,
3080
+ detected: detectContent(headers, stringified)
3081
+ };
3082
+ }, [data, headers]);
3083
+ }
3084
+ __name(useResponseView, "useResponseView");
3085
+ var LABELS = {
3086
+ pretty: "Pretty",
3087
+ raw: "Raw",
3088
+ preview: "Preview"
3089
+ };
3090
+ function ViewTabs({ active, onChange, showPreview }) {
3091
+ const tabs = showPreview ? ["pretty", "raw", "preview"] : ["pretty", "raw"];
3092
+ return /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-3 py-1.5 flex items-center gap-1", children: tabs.map((t) => /* @__PURE__ */ jsx(
3093
+ "button",
3094
+ {
3095
+ type: "button",
3096
+ onClick: () => onChange(t),
3097
+ className: cn(
3098
+ "h-6 px-2.5 rounded text-[11px] font-medium transition-colors",
3099
+ active === t ? "bg-muted text-foreground" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted/50"
3100
+ ),
3101
+ children: LABELS[t]
3102
+ },
3103
+ t
3104
+ )) });
3105
+ }
3106
+ __name(ViewTabs, "ViewTabs");
3107
+ function ResponsePanel() {
3108
+ const { state } = usePlaygroundContext();
3109
+ const { response, loading, selectedEndpoint } = state;
3110
+ const { treeData, rawText, detected } = useResponseView(response?.data, response?.headers);
3111
+ const showPreview = detected.kind === "html";
3112
+ const [mode, setMode] = useState(showPreview ? "preview" : "pretty");
3113
+ useEffect(() => {
3114
+ setMode(showPreview ? "preview" : "pretty");
3115
+ }, [selectedEndpoint, response, showPreview]);
3116
+ if (loading) {
3117
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center h-full gap-2", children: [
3118
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }),
3119
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Sending\u2026" })
3120
+ ] });
3121
+ }
3122
+ if (!selectedEndpoint) return /* @__PURE__ */ jsx(EmptyState, { icon: Terminal, text: "Response will appear here" });
3123
+ if (!response) return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: 'Press "Send Request" to see the response' });
3124
+ const hasError = Boolean(response.error);
3125
+ const hasStatus = response.status != null;
3126
+ if (hasError && !hasStatus) {
3127
+ return /* @__PURE__ */ jsx(
3128
+ EmptyState,
3129
+ {
3130
+ icon: WifiOff,
3131
+ text: response.error,
3132
+ className: "text-destructive [&_svg]:text-destructive"
3133
+ }
3134
+ );
3135
+ }
3136
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3137
+ /* @__PURE__ */ jsx(StatusBar, { response, rawText, contentType: detected.contentType }),
3138
+ hasError && /* @__PURE__ */ jsx("div", { className: "shrink-0 mx-4 mt-3 rounded border border-destructive/20 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: response.error }) }),
3139
+ /* @__PURE__ */ jsx(ViewTabs, { active: mode, onChange: setMode, showPreview }),
3140
+ /* @__PURE__ */ jsxs(ScrollArea, { children: [
3141
+ mode === "pretty" && /* @__PURE__ */ jsx(PrettyView, { treeData, rawText, detected }),
3142
+ mode === "raw" && /* @__PURE__ */ jsx(RawView, { rawText }),
3143
+ mode === "preview" && /* @__PURE__ */ jsx(PreviewView, { html: rawText })
3144
+ ] })
3145
+ ] });
3146
+ }
3147
+ __name(ResponsePanel, "ResponsePanel");
3148
+ function SendButton({ className }) {
3149
+ const { state, sendRequest } = usePlaygroundContext();
3150
+ const ep = state.selectedEndpoint;
3151
+ const builder = useMemo(
3152
+ () => ep ? new UrlBuilder(ep, state.parameters) : null,
3153
+ [ep, state.parameters]
3154
+ );
3155
+ const missingRequired = builder?.missingRequired() ?? [];
3156
+ const unsubstituted = builder?.unfilledPlaceholders() ?? [];
3157
+ const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
3158
+ const blockers = [];
3159
+ if (missingRequired.length > 0) {
3160
+ blockers.push(
3161
+ `Fill required parameter${missingRequired.length > 1 ? "s" : ""}: ${missingRequired.join(", ")}`
3162
+ );
3163
+ } else if (unsubstituted.length > 0) {
3164
+ blockers.push(`URL still has unfilled placeholder${unsubstituted.length > 1 ? "s" : ""}: ${unsubstituted.map((n) => `{${n}}`).join(", ")}`);
3165
+ }
3166
+ if (!isJsonValid) blockers.push("Request body is not valid JSON");
3167
+ const disabled = state.loading || !state.requestUrl || blockers.length > 0;
3168
+ const tooltip = blockers.length > 0 ? blockers.join("\n") : void 0;
3169
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
3170
+ blockers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/25 bg-amber-500/[0.06] px-3 py-2 text-[11px] text-amber-600 dark:text-amber-400", children: [
3171
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 shrink-0 mt-px" }),
3172
+ /* @__PURE__ */ jsx("span", { className: "leading-snug", children: blockers[0] })
3173
+ ] }),
3174
+ /* @__PURE__ */ jsx(
3175
+ Button,
3176
+ {
3177
+ onClick: sendRequest,
3178
+ disabled,
3179
+ size: "sm",
3180
+ title: tooltip,
3181
+ className: "w-full gap-2 h-9",
3182
+ children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
3183
+ /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
3184
+ "Sending\u2026"
3185
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3186
+ /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
3187
+ "Send Request"
3188
+ ] })
3189
+ }
3190
+ )
3191
+ ] });
3192
+ }
3193
+ __name(SendButton, "SendButton");
3194
+ var WIDTH_NARROW = "clamp(380px, 30vw, 480px)";
3195
+ var WIDTH_WIDE = "clamp(720px, 60vw, 1280px)";
3196
+ function SlideInPlayground({ open, onClose }) {
3197
+ const { state } = usePlaygroundContext();
3198
+ const ep = state.selectedEndpoint;
3199
+ const showResponse = state.response !== null || state.loading;
3200
+ const width = showResponse ? WIDTH_WIDE : WIDTH_NARROW;
3201
+ return /* @__PURE__ */ jsx(SidePanel, { open, onOpenChange: (v) => !v && onClose(), side: "right", children: /* @__PURE__ */ jsxs(SidePanel.Content, { width, className: "max-w-[95vw]", children: [
3202
+ /* @__PURE__ */ jsxs(SidePanel.Header, { children: [
3203
+ /* @__PURE__ */ jsx(SidePanel.Title, { children: "Playground" }),
3204
+ ep && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [
3205
+ /* @__PURE__ */ jsx(MethodBadge, { method: ep.method }),
3206
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground truncate", children: relativePath(ep.path) })
3207
+ ] }),
3208
+ /* @__PURE__ */ jsx(SidePanel.Close, { className: "ml-auto" })
3209
+ ] }),
3210
+ /* @__PURE__ */ jsxs(
3211
+ SidePanel.Body,
3212
+ {
3213
+ className: cn(
3214
+ "overflow-hidden grid divide-x transition-[grid-template-columns] duration-250",
3215
+ showResponse ? "grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" : "grid-cols-1"
3216
+ ),
3217
+ children: [
3218
+ /* @__PURE__ */ jsxs(Panel, { children: [
3219
+ /* @__PURE__ */ jsx(RequestPanel, {}),
3220
+ ep && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background", children: /* @__PURE__ */ jsx(SendButton, {}) })
3221
+ ] }),
3222
+ showResponse && /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
3223
+ ]
3224
+ }
3225
+ )
3226
+ ] }) });
3227
+ }
3228
+ __name(SlideInPlayground, "SlideInPlayground");
3229
+ function TryItSheet({ open, onOpenChange }) {
3230
+ const { state } = usePlaygroundContext();
3231
+ const showResponse = state.response !== null || state.loading;
3232
+ return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-xl flex flex-col h-full p-0", children: [
3233
+ /* @__PURE__ */ jsx(ResponsiveSheetHeader, { className: "px-4 py-3 border-b shrink-0", children: /* @__PURE__ */ jsx(ResponsiveSheetTitle, { className: "text-sm", children: "Playground" }) }),
3234
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col divide-y", children: [
3235
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col", children: [
3236
+ /* @__PURE__ */ jsx(RequestPanel, {}),
3237
+ state.selectedEndpoint && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background", children: /* @__PURE__ */ jsx(SendButton, {}) })
3238
+ ] }),
3239
+ showResponse && /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
3240
+ ] })
3241
+ ] }) });
3242
+ }
3243
+ __name(TryItSheet, "TryItSheet");
3244
+ var DocsLayout = /* @__PURE__ */ __name(() => {
3245
+ const { state, config, setSelectedEndpoint } = usePlaygroundContext();
3246
+ const isDesktop = useMediaQuery("(min-width: 1024px)");
3247
+ const isMobile = !isDesktop;
3248
+ const grouping = config.schemaGrouping ?? "selector";
3249
+ const preloadAll = grouping === "sections";
3250
+ const urlSyncEnabled = typeof config.urlSync === "boolean" ? config.urlSync : Boolean(config.urlSync?.enabled);
3251
+ const {
3252
+ endpoints,
3253
+ schemaInfo,
3254
+ rawSchema,
3255
+ resolvedBaseUrl,
3256
+ loading,
3257
+ error,
3258
+ schemas,
3259
+ currentSchema,
3260
+ setCurrentSchema,
3261
+ schemasData
3262
+ } = useOpenApiSchema({
3263
+ schemas: config.schemas,
3264
+ defaultSchemaId: config.defaultSchemaId,
3265
+ baseUrl: config.baseUrl,
3266
+ preloadAll
3267
+ });
3268
+ const [activeAnchor, setActiveAnchor] = useState(null);
3269
+ const [activeSchemaId, setActiveSchemaId] = useState(null);
3270
+ const [sheetOpen, setSheetOpen] = useState(false);
3271
+ const docsRef = useRef(null);
3272
+ const slideOpen = !isMobile && state.selectedEndpoint !== null;
3273
+ const endpointsBySchema = useMemo(() => {
3274
+ if (grouping !== "sections") return {};
3275
+ const byId = keyBy(schemasData, (e) => e.source.id);
3276
+ const out = {};
3277
+ for (const src of schemas) out[src.id] = byId[src.id]?.endpoints ?? [];
3278
+ return out;
3279
+ }, [grouping, schemasData, schemas]);
3280
+ const handleTry = useCallback(
3281
+ (ep) => {
3282
+ setSelectedEndpoint(ep);
3283
+ if (isMobile) setSheetOpen(true);
3284
+ },
3285
+ [isMobile, setSelectedEndpoint]
3286
+ );
3287
+ const handleCloseSlide = useCallback(() => {
3288
+ setSelectedEndpoint(null);
3289
+ }, [setSelectedEndpoint]);
3290
+ const handleNavigate = useCallback(
3291
+ (anchor, schemaId) => {
3292
+ if (schemaId && schemaId !== currentSchema?.id && grouping === "selector") {
3293
+ setCurrentSchema(schemaId);
3294
+ requestAnimationFrame(() => {
3295
+ docsRef.current?.scrollToAnchor(anchor);
3296
+ });
3297
+ return;
3298
+ }
3299
+ docsRef.current?.scrollToAnchor(anchor);
3300
+ },
3301
+ [currentSchema?.id, grouping, setCurrentSchema]
3302
+ );
3303
+ const handleActiveChange = useCallback((anchor, schemaId) => {
3304
+ setActiveAnchor(anchor);
3305
+ setActiveSchemaId(schemaId);
3306
+ }, []);
3307
+ const effectiveSchemaId = grouping === "sections" ? activeSchemaId : currentSchema?.id ?? null;
3308
+ const handleHashTarget = useCallback(
3309
+ (target) => {
3310
+ if (!target.schemaId && !target.anchor) return;
3311
+ const matched = target.schemaId ? schemas.find((s) => s.id === target.schemaId || slugifySchemaId(s.id) === target.schemaId) : null;
3312
+ const needsSchemaSwitch = matched && grouping === "selector" && matched.id !== currentSchema?.id;
3313
+ if (needsSchemaSwitch) {
3314
+ setCurrentSchema(matched.id);
3315
+ }
3316
+ if (target.anchor) {
3317
+ const anchor = target.anchor;
3318
+ if (needsSchemaSwitch) {
3319
+ requestAnimationFrame(() => {
3320
+ docsRef.current?.scrollToAnchor(anchor);
3321
+ });
3322
+ } else {
3323
+ docsRef.current?.scrollToAnchor(anchor);
3324
+ }
3325
+ }
3326
+ },
3327
+ [schemas, grouping, currentSchema?.id, setCurrentSchema]
3328
+ );
3329
+ useDocsUrlSync({
3330
+ enabled: urlSyncEnabled,
3331
+ currentSchemaId: effectiveSchemaId,
3332
+ activeAnchor,
3333
+ onHashTarget: handleHashTarget
3334
+ });
3335
+ if (loading) {
3336
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[260px_1fr] items-start", children: [
3337
+ /* @__PURE__ */ jsx(
3338
+ "div",
3339
+ {
3340
+ className: "sticky top-[var(--navbar-height,64px)] border-r p-3 space-y-1.5 overflow-y-auto",
3341
+ style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
3342
+ children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-full rounded" }, i))
3343
+ }
3344
+ ),
3345
+ /* @__PURE__ */ jsxs("div", { className: "p-8 space-y-4", children: [
3346
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-1/2" }),
3347
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
3348
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/4" }),
3349
+ /* @__PURE__ */ jsx("div", { className: "mt-8 space-y-6", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3350
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-1/3" }),
3351
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-20 w-full" })
3352
+ ] }, i)) })
3353
+ ] })
3354
+ ] });
3355
+ }
3356
+ if (error) {
3357
+ return /* @__PURE__ */ jsx(
3358
+ "div",
3359
+ {
3360
+ className: "flex items-center justify-center p-8",
3361
+ style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
3362
+ children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
3363
+ "Failed to load schema: ",
3364
+ error
3365
+ ] })
3366
+ }
3367
+ );
3368
+ }
3369
+ if (isMobile) {
3370
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
3371
+ /* @__PURE__ */ jsx(EndpointDraftSync, { schemaId: currentSchema?.id ?? null }),
3372
+ grouping === "sections" ? /* @__PURE__ */ jsx(
3373
+ DocsView,
3374
+ {
3375
+ ref: docsRef,
3376
+ grouping: "sections",
3377
+ schemasData,
3378
+ selectedVersion: state.selectedVersion,
3379
+ loadedEndpoint: state.selectedEndpoint,
3380
+ onTryEndpoint: handleTry,
3381
+ onActiveChange: handleActiveChange
3382
+ }
3383
+ ) : /* @__PURE__ */ jsx(
3384
+ DocsView,
3385
+ {
3386
+ ref: docsRef,
3387
+ info: schemaInfo,
3388
+ rawSchema,
3389
+ resolvedBaseUrl,
3390
+ endpoints,
3391
+ selectedVersion: state.selectedVersion,
3392
+ loadedEndpoint: state.selectedEndpoint,
3393
+ onTryEndpoint: handleTry,
3394
+ onActiveChange: handleActiveChange
3395
+ }
3396
+ ),
3397
+ /* @__PURE__ */ jsx(TryItSheet, { open: sheetOpen, onOpenChange: setSheetOpen })
3398
+ ] });
3399
+ }
3400
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 350, children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[260px_minmax(0,1fr)] items-start", children: [
3401
+ /* @__PURE__ */ jsx(EndpointDraftSync, { schemaId: currentSchema?.id ?? null }),
3402
+ /* @__PURE__ */ jsx(
3403
+ "div",
3404
+ {
3405
+ className: "sticky top-[var(--navbar-height,64px)]",
3406
+ style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
3407
+ children: /* @__PURE__ */ jsx(
3408
+ DocsSidebar,
3409
+ {
3410
+ info: schemaInfo,
3411
+ endpoints,
3412
+ schemas,
3413
+ currentSchemaId: currentSchema?.id ?? null,
3414
+ onSchemaChange: setCurrentSchema,
3415
+ activeEndpointId: activeAnchor,
3416
+ selectedVersion: state.selectedVersion,
3417
+ onNavigate: handleNavigate,
3418
+ grouping,
3419
+ endpointsBySchema,
3420
+ rawSchema,
3421
+ resolvedBaseUrl
3422
+ }
3423
+ )
3424
+ }
3425
+ ),
3426
+ grouping === "sections" ? /* @__PURE__ */ jsx(
3427
+ DocsView,
3428
+ {
3429
+ ref: docsRef,
3430
+ grouping: "sections",
3431
+ schemasData,
3432
+ selectedVersion: state.selectedVersion,
3433
+ loadedEndpoint: state.selectedEndpoint,
3434
+ onTryEndpoint: handleTry,
3435
+ onActiveChange: handleActiveChange
3436
+ }
3437
+ ) : /* @__PURE__ */ jsx(
3438
+ DocsView,
3439
+ {
3440
+ ref: docsRef,
3441
+ info: schemaInfo,
3442
+ rawSchema,
3443
+ resolvedBaseUrl,
3444
+ endpoints,
3445
+ selectedVersion: state.selectedVersion,
3446
+ loadedEndpoint: state.selectedEndpoint,
3447
+ onTryEndpoint: handleTry,
3448
+ onActiveChange: handleActiveChange
3449
+ }
3450
+ ),
3451
+ /* @__PURE__ */ jsx(SlideInPlayground, { open: slideOpen, onClose: handleCloseSlide })
3452
+ ] }) });
3453
+ }, "DocsLayout");
3454
+
3455
+ export { DocsLayout };
3456
+ //# sourceMappingURL=DocsLayout-JPXFUKAR.mjs.map
3457
+ //# sourceMappingURL=DocsLayout-JPXFUKAR.mjs.map