@djangocfg/ui-tools 2.1.287 → 2.1.290

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +14 -3
  2. package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
  3. package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
  4. package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
  5. package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
  6. package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
  7. package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
  9. package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
  10. package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
  11. package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
  12. package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
  13. package/dist/chunk-EFWOJPA6.mjs.map +1 -0
  14. package/dist/index.cjs +10 -10
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +34 -0
  17. package/dist/index.d.ts +34 -0
  18. package/dist/index.mjs +5 -5
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +21 -14
  21. package/src/components/markdown/MarkdownMessage.tsx +46 -0
  22. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
  23. package/src/tools/OpenapiViewer/README.md +114 -6
  24. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
  25. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
  26. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
  27. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
  39. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
  40. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
  41. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
  42. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
  43. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
  44. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
  45. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
  46. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
  47. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
  48. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
  49. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
  50. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
  51. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
  52. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
  53. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
  54. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
  55. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
  56. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
  57. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
  58. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
  59. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
  60. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
  61. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
  62. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
  63. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
  64. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
  65. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
  66. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
  67. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
  68. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
  69. package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
  70. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
  71. package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
  72. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
  73. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
  74. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
  75. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
  76. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
  77. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
  78. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
  79. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
  80. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
  81. package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
  82. package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
  83. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
  84. package/src/tools/OpenapiViewer/types.ts +46 -1
  85. package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
  86. package/src/tools/OpenapiViewer/utils/index.ts +3 -0
  87. package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
  88. package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
  89. package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
  90. package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
  91. package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
  92. package/src/tools/PrettyCode/index.tsx +13 -0
  93. package/src/tools/PrettyCode/lazy.tsx +5 -0
  94. package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
  95. package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
  96. package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
  97. package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
  98. package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
  99. package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
  100. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
  101. package/dist/chunk-IULI4XII.cjs.map +0 -1
  102. package/dist/chunk-VZGQC3NG.mjs.map +0 -1
  103. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
  104. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
  105. package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
@@ -3,78 +3,32 @@
3
3
  import consola from 'consola';
4
4
  import { useCallback, useEffect, useMemo, useState } from 'react';
5
5
 
6
- import { ApiEndpoint, OpenApiInfo, OpenApiSchema, SchemaSource, UseOpenApiSchemaReturn } from '../types';
6
+ import { ApiEndpoint, LoadedSchemaEntry, OpenApiInfo, OpenApiSchema, SchemaSource, UseOpenApiSchemaReturn } from '../types';
7
+ import { sampleSchemaJson } from '../utils/sampler';
7
8
  import { dereferenceSchema } from '../utils/schemaExport';
8
9
  import { joinUrl, resolveBaseUrl } from '../utils/url';
9
10
 
10
- // ─── JSON Schema → example value ──────────────────────────────────────────────
11
-
12
- type JsonSchemaNode = Record<string, unknown> & {
13
- type?: string;
14
- properties?: Record<string, JsonSchemaNode>;
15
- required?: string[];
16
- items?: JsonSchemaNode;
17
- enum?: unknown[];
18
- example?: unknown;
19
- default?: unknown;
20
- format?: string;
21
- };
22
-
23
- /** Walk a JSON Schema and build a realistic-looking example value. */
24
- function exampleFromSchema(schema: JsonSchemaNode | undefined, depth = 0): unknown {
25
- if (!schema || depth > 8) return null;
26
-
27
- // Respect schema-provided examples first — no need to invent a value
28
- // when the spec author already did the work.
29
- if (schema.example !== undefined) return schema.example;
30
- if (schema.default !== undefined) return schema.default;
31
- if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
32
-
33
- switch (schema.type) {
34
- case 'object': {
35
- const out: Record<string, unknown> = {};
36
- const props = schema.properties ?? {};
37
- for (const [k, v] of Object.entries(props)) {
38
- out[k] = exampleFromSchema(v, depth + 1);
39
- }
40
- return out;
41
- }
42
- case 'array':
43
- return [exampleFromSchema(schema.items, depth + 1)];
44
- case 'integer':
45
- case 'number':
46
- return 0;
47
- case 'boolean':
48
- return false;
49
- case 'string':
50
- if (schema.format === 'date-time') return new Date().toISOString();
51
- if (schema.format === 'date') return new Date().toISOString().slice(0, 10);
52
- if (schema.format === 'email') return 'user@example.com';
53
- if (schema.format === 'uri' || schema.format === 'url') return 'https://example.com';
54
- if (schema.format === 'uuid') return '00000000-0000-0000-0000-000000000000';
55
- return '';
56
- default:
57
- // No type (or composed schema like allOf/oneOf we don't unpack) —
58
- // fall back to an empty object rather than ``null`` so the resulting
59
- // JSON is still valid-looking.
60
- if (schema.properties) {
61
- const out: Record<string, unknown> = {};
62
- for (const [k, v] of Object.entries(schema.properties)) {
63
- out[k] = exampleFromSchema(v, depth + 1);
64
- }
65
- return out;
66
- }
67
- return null;
68
- }
69
- }
11
+ type JsonSchemaNode = Record<string, unknown>;
70
12
 
71
13
  // HTTP methods to extract from OpenAPI schema
72
14
  const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;
73
15
 
74
16
  // Extract endpoints from OpenAPI schema (all methods). ``baseUrl`` is
75
17
  // resolved by the caller via ``resolveBaseUrl`` — we just paste it onto
76
- // the front of each path here.
77
- const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[] => {
18
+ // the front of each path here. ``schemaId`` is tagged onto every
19
+ // endpoint so downstream consumers (sections-mode sidebar, anchors, URL
20
+ // sync) can correlate endpoints back to their source schema.
21
+ //
22
+ // ``specRoot`` is the raw, un-dereferenced schema — passed to
23
+ // ``openapi-sampler`` so it can resolve any ``$ref`` nodes our own
24
+ // ``dereferenceSchema`` left behind (depth-limited, external, or
25
+ // circular). Without it the sampler throws on deep ref chains.
26
+ const extractEndpoints = (
27
+ schema: OpenApiSchema,
28
+ baseUrl: string,
29
+ schemaId?: string,
30
+ specRoot?: OpenApiSchema,
31
+ ): ApiEndpoint[] => {
78
32
  const endpoints: ApiEndpoint[] = [];
79
33
 
80
34
  if (!schema.paths) return [];
@@ -107,17 +61,32 @@ const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[]
107
61
  });
108
62
  }
109
63
 
110
- // Collect responses
111
- const responses: Array<{
112
- code: string;
113
- description: string;
114
- }> = [];
64
+ // Collect responses. We also extract the ``application/json``
65
+ // schema (preferring it, falling back to whatever media type is
66
+ // present) and generate a sampled example — the docs layout
67
+ // renders it as a collapsible "Example response" block under the
68
+ // status-code table. ``writeOnly`` fields are skipped so secrets
69
+ // declared ``writeOnly: true`` don't leak into response samples.
70
+ const responses: NonNullable<ApiEndpoint['responses']> = [];
115
71
 
116
72
  if (op.responses) {
117
73
  for (const [code, response] of Object.entries(op.responses)) {
74
+ const respContent = (response as any).content as Record<string, any> | undefined;
75
+ const contentKeys = respContent ? Object.keys(respContent) : [];
76
+ const chosenContentType = respContent?.['application/json']
77
+ ? 'application/json'
78
+ : contentKeys[0];
79
+ const chosen = chosenContentType ? respContent?.[chosenContentType] : undefined;
80
+ const respSchema = chosen?.schema as JsonSchemaNode | undefined;
81
+
118
82
  responses.push({
119
83
  code,
120
84
  description: (response as any).description || `Response ${code}`,
85
+ contentType: chosenContentType,
86
+ schema: respSchema,
87
+ example: respSchema
88
+ ? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot)
89
+ : undefined,
121
90
  });
122
91
  }
123
92
  }
@@ -125,17 +94,20 @@ const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[]
125
94
  // Extract request body info — keep the dereferenced schema so
126
95
  // downstream UI can render a fields table and generate a starter
127
96
  // example instead of showing an opaque ``object`` / ``array`` tag.
97
+ // ``readOnly`` fields are skipped: the body is what the client
98
+ // *sends*, so server-owned fields (id, created_at, …) must not
99
+ // pre-fill the editor.
128
100
  let requestBody: ApiEndpoint['requestBody'];
129
101
  if (op.requestBody) {
130
102
  const content = op.requestBody.content;
131
103
  const mediaType = content?.['application/json'] || content?.[Object.keys(content || {})[0]];
132
104
  const rawSchema = mediaType?.schema as JsonSchemaNode | undefined;
133
105
  requestBody = {
134
- type: rawSchema?.type || 'object',
106
+ type: (rawSchema?.type as string | undefined) || 'object',
135
107
  description: op.requestBody.description,
136
108
  schema: rawSchema,
137
109
  example: rawSchema
138
- ? JSON.stringify(exampleFromSchema(rawSchema), null, 2)
110
+ ? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot)
139
111
  : undefined,
140
112
  };
141
113
  }
@@ -150,6 +122,7 @@ const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[]
150
122
  parameters: parameters.length > 0 ? parameters : undefined,
151
123
  requestBody,
152
124
  responses: responses.length > 0 ? responses : undefined,
125
+ schemaId,
153
126
  };
154
127
 
155
128
  endpoints.push(endpoint);
@@ -185,12 +158,24 @@ interface UseOpenApiSchemaProps {
185
158
  /** Global base URL override from ``PlaygroundConfig.baseUrl``.
186
159
  * Per-schema ``SchemaSource.baseUrl`` takes precedence over this. */
187
160
  baseUrl?: string;
161
+ /** When ``true`` the hook fetches every schema in ``schemas`` (not just
162
+ * the active one) and exposes them via ``schemasData``. Used by the
163
+ * ``sections`` grouping mode — the docs column concatenates endpoints
164
+ * from every schema, so they all need to be on the client. Default
165
+ * is ``false`` to preserve the original lazy behaviour. */
166
+ preloadAll?: boolean;
167
+ }
168
+
169
+ interface SchemaLoadState {
170
+ loading: boolean;
171
+ error: string | null;
188
172
  }
189
173
 
190
174
  export default function useOpenApiSchema({
191
175
  schemas,
192
176
  defaultSchemaId,
193
177
  baseUrl: configBaseUrl,
178
+ preloadAll = false,
194
179
  }: UseOpenApiSchemaProps): UseOpenApiSchemaReturn {
195
180
  const [loading, setLoading] = useState(true);
196
181
  const [error, setError] = useState<string | null>(null);
@@ -200,6 +185,10 @@ export default function useOpenApiSchema({
200
185
  const [loadedSchemas, setLoadedSchemas] = useState<Map<string, OpenApiSchema>>(
201
186
  new Map()
202
187
  );
188
+ // Per-schema loading/error state for ``preloadAll`` — each schema may
189
+ // succeed or fail independently, and the UI wants to render partial
190
+ // results while slow/broken ones are still resolving.
191
+ const [loadStates, setLoadStates] = useState<Map<string, SchemaLoadState>>(new Map());
203
192
 
204
193
  const currentSchema = useMemo(
205
194
  () => schemas.find((s) => s.id === currentSchemaId) || null,
@@ -233,8 +222,11 @@ export default function useOpenApiSchema({
233
222
  );
234
223
 
235
224
  const endpoints = useMemo(
236
- () => (dereferencedSchema ? extractEndpoints(dereferencedSchema, resolvedBaseUrl) : []),
237
- [dereferencedSchema, resolvedBaseUrl]
225
+ () =>
226
+ dereferencedSchema
227
+ ? extractEndpoints(dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema ?? undefined)
228
+ : [],
229
+ [dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema]
238
230
  );
239
231
 
240
232
  const categories = useMemo(() => getCategories(endpoints), [endpoints]);
@@ -250,8 +242,9 @@ export default function useOpenApiSchema({
250
242
  };
251
243
  }, [currentOpenApiSchema]);
252
244
 
253
- // Load schema when current schema changes
245
+ // Load schema when current schema changes (single-schema mode)
254
246
  useEffect(() => {
247
+ if (preloadAll) return;
255
248
  if (!currentSchema) return;
256
249
 
257
250
  // Skip if already loaded
@@ -274,7 +267,103 @@ export default function useOpenApiSchema({
274
267
  setError(err instanceof Error ? err.message : 'Failed to load schema');
275
268
  setLoading(false);
276
269
  });
277
- }, [currentSchema, loadedSchemas]);
270
+ }, [currentSchema, loadedSchemas, preloadAll]);
271
+
272
+ // Preload every schema (sections-grouping mode). Each schema is fetched
273
+ // independently — a slow or broken source doesn't block the rest.
274
+ useEffect(() => {
275
+ if (!preloadAll) return;
276
+ if (schemas.length === 0) {
277
+ setLoading(false);
278
+ return;
279
+ }
280
+
281
+ let cancelled = false;
282
+ const pending = schemas.filter((s) => !loadedSchemas.has(s.id));
283
+ if (pending.length === 0) {
284
+ setLoading(false);
285
+ return;
286
+ }
287
+
288
+ setLoading(true);
289
+ setLoadStates((prev) => {
290
+ const next = new Map(prev);
291
+ for (const s of pending) next.set(s.id, { loading: true, error: null });
292
+ return next;
293
+ });
294
+
295
+ Promise.allSettled(
296
+ pending.map((s) =>
297
+ fetchSchema(s.url).then((schema) => ({ id: s.id, name: s.name, schema })),
298
+ ),
299
+ ).then((results) => {
300
+ if (cancelled) return;
301
+
302
+ setLoadedSchemas((prev) => {
303
+ const next = new Map(prev);
304
+ for (const r of results) {
305
+ if (r.status === 'fulfilled') {
306
+ next.set(r.value.id, r.value.schema);
307
+ consola.success(`Schema loaded: ${r.value.name}`);
308
+ }
309
+ }
310
+ return next;
311
+ });
312
+
313
+ setLoadStates((prev) => {
314
+ const next = new Map(prev);
315
+ results.forEach((r, i) => {
316
+ const src = pending[i]!;
317
+ if (r.status === 'fulfilled') {
318
+ next.set(src.id, { loading: false, error: null });
319
+ } else {
320
+ const msg = r.reason instanceof Error ? r.reason.message : 'Failed to load schema';
321
+ consola.error(`Error loading schema from ${src.url}:`, r.reason);
322
+ next.set(src.id, { loading: false, error: msg });
323
+ }
324
+ });
325
+ return next;
326
+ });
327
+
328
+ setLoading(false);
329
+ });
330
+
331
+ return () => {
332
+ cancelled = true;
333
+ };
334
+ }, [preloadAll, schemas, loadedSchemas]);
335
+
336
+ const schemasData = useMemo<LoadedSchemaEntry[]>(() => {
337
+ if (!preloadAll) return [];
338
+ return schemas.map((src) => {
339
+ const raw = loadedSchemas.get(src.id) ?? null;
340
+ const deref = raw ? dereferenceSchema(raw) : null;
341
+ const resolved = resolveBaseUrl({
342
+ schemaSource: src.baseUrl,
343
+ config: configBaseUrl,
344
+ fromServers: raw?.servers?.[0]?.url,
345
+ });
346
+ const info: OpenApiInfo | null = raw?.info
347
+ ? {
348
+ title: raw.info.title,
349
+ version: raw.info.version,
350
+ description: raw.info.description,
351
+ servers: raw.servers,
352
+ }
353
+ : null;
354
+ const eps = deref ? extractEndpoints(deref, resolved, src.id, raw ?? undefined) : [];
355
+ const state = loadStates.get(src.id) ?? { loading: !raw, error: null };
356
+ return {
357
+ source: src,
358
+ info,
359
+ rawSchema: raw,
360
+ endpoints: eps,
361
+ resolvedBaseUrl: resolved || undefined,
362
+ loading: state.loading,
363
+ error: state.error,
364
+ };
365
+ });
366
+ }, [preloadAll, schemas, loadedSchemas, loadStates, configBaseUrl]);
278
367
 
279
368
  const setCurrentSchema = useCallback((schemaId: string) => {
280
369
  setCurrentSchemaId(schemaId);
@@ -321,5 +410,6 @@ export default function useOpenApiSchema({
321
410
  resolvedBaseUrl: resolvedBaseUrl || undefined,
322
411
  setCurrentSchema,
323
412
  refresh,
413
+ schemasData,
324
414
  };
325
415
  }
@@ -19,6 +19,10 @@ export interface ApiEndpoint {
19
19
  name: string;
20
20
  method: string;
21
21
  path: string;
22
+ /** ID of the schema this endpoint was extracted from. Populated whenever
23
+ * endpoints from multiple schemas coexist (``sections`` grouping), so
24
+ * sidebar grouping, anchors and URL sync can all tell them apart. */
25
+ schemaId?: string;
22
26
  /** Short human label from OpenAPI ``operation.summary``. Empty when
23
27
  * the spec provides none. Prefer this for sidebar rows and breadcrumbs. */
24
28
  summary: string;
@@ -43,6 +47,16 @@ export interface ApiEndpoint {
43
47
  responses?: Array<{
44
48
  code: string;
45
49
  description: string;
50
+ /** Media type the schema is sourced from (e.g. ``application/json``).
51
+ * ``undefined`` when the response advertises no body. */
52
+ contentType?: string;
53
+ /** Dereferenced JSON Schema for the response body. Used by the docs
54
+ * layout to render a sampled example under the status-code table. */
55
+ schema?: Record<string, unknown>;
56
+ /** Pre-generated example JSON (sampled via ``openapi-sampler``).
57
+ * ``undefined`` when the response has no schema or sampling failed
58
+ * — the UI conditionally hides the "Example" block in that case. */
59
+ example?: string;
46
60
  }>;
47
61
  }
48
62
 
@@ -92,6 +106,20 @@ export interface PlaygroundConfig {
92
106
  * a spinner instead of "no keys yet". */
93
107
  apiKeys?: ApiKey[];
94
108
  apiKeysLoading?: boolean;
109
+ /** How multiple schemas are presented in the sidebar.
110
+ * - ``'selector'`` (default): a Combobox switches between schemas, the
111
+ * docs column shows endpoints of the active schema only.
112
+ * - ``'sections'``: the Combobox is hidden and every schema becomes a
113
+ * top-level heading in the sidebar, with endpoints of all schemas
114
+ * rendered back-to-back in the docs column. Scrollspy picks the
115
+ * active schema based on what's visible. */
116
+ schemaGrouping?: 'selector' | 'sections';
117
+ /** Optional URL-hash sync. When enabled, the viewer reads/writes
118
+ * ``#<schemaId>/<anchor>`` on the browser location. Falsy value (the
119
+ * default) keeps the viewer hash-free. Set to an object with
120
+ * ``{ enabled: true }`` to opt in; future fields (e.g. a custom
121
+ * adapter) stay backwards compatible. */
122
+ urlSync?: boolean | { enabled: boolean };
95
123
  }
96
124
 
97
125
  // Playground state types
@@ -190,6 +218,19 @@ export interface OpenApiInfo {
190
218
  servers?: Array<{ url: string; description?: string }>;
191
219
  }
192
220
 
221
+ /** Per-schema snapshot used by the ``sections`` grouping mode. Mirrors the
222
+ * fields that ``UseOpenApiSchemaReturn`` exposes for the active schema,
223
+ * but repeated for every loaded schema. */
224
+ export interface LoadedSchemaEntry {
225
+ source: SchemaSource;
226
+ info: OpenApiInfo | null;
227
+ rawSchema: OpenApiSchema | null;
228
+ endpoints: ApiEndpoint[];
229
+ resolvedBaseUrl: string | undefined;
230
+ loading: boolean;
231
+ error: string | null;
232
+ }
233
+
193
234
  // Hook return types
194
235
  export interface UseOpenApiSchemaReturn {
195
236
  loading: boolean;
@@ -208,4 +249,8 @@ export interface UseOpenApiSchemaReturn {
208
249
  resolvedBaseUrl: string | undefined;
209
250
  setCurrentSchema: (schemaId: string) => void;
210
251
  refresh: () => void;
211
- }
252
+ /** Populated only when the hook was called with ``preloadAll: true``
253
+ * (``sections`` grouping mode). One entry per schema source, in the
254
+ * same order as ``schemas``. */
255
+ schemasData: LoadedSchemaEntry[];
256
+ }