@djangocfg/ui-tools 2.1.390 → 2.1.393

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