@gitbook/react-openapi 1.5.2 → 1.5.4

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 (39) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/InteractiveSection.js +2 -2
  3. package/dist/OpenAPICodeSample.js +8 -5
  4. package/dist/OpenAPICodeSampleInteractive.js +1 -5
  5. package/dist/OpenAPICopyButton.js +12 -7
  6. package/dist/OpenAPIDisclosure.js +4 -6
  7. package/dist/OpenAPIDisclosureGroup.js +21 -7
  8. package/dist/OpenAPIPath.js +12 -38
  9. package/dist/OpenAPIPathItem.js +22 -0
  10. package/dist/OpenAPIPathMultipleServers.js +43 -0
  11. package/dist/OpenAPIRequiredScopes.js +67 -0
  12. package/dist/OpenAPISchema.js +187 -79
  13. package/dist/OpenAPISecurities.js +17 -43
  14. package/dist/OpenAPISelect.js +6 -6
  15. package/dist/OpenAPISpec.js +1 -1
  16. package/dist/OpenAPITooltip.js +23 -0
  17. package/dist/ScalarApiButton.js +5 -2
  18. package/dist/code-samples.js +33 -3
  19. package/dist/context.d.ts +3 -0
  20. package/dist/formatPath.js +25 -0
  21. package/dist/generateSchemaExample.js +20 -3
  22. package/dist/getOrCreateDisclosureStoreByKey.js +31 -0
  23. package/dist/resolveOpenAPIOperation.js +5 -2
  24. package/dist/translate.js +2 -2
  25. package/dist/translations/de.js +2 -0
  26. package/dist/translations/en.d.ts +2 -0
  27. package/dist/translations/en.js +2 -0
  28. package/dist/translations/es.js +2 -0
  29. package/dist/translations/fr.js +2 -0
  30. package/dist/translations/index.d.ts +18 -0
  31. package/dist/translations/ja.js +2 -0
  32. package/dist/translations/nl.js +2 -0
  33. package/dist/translations/no.js +2 -0
  34. package/dist/translations/pt-br.js +2 -0
  35. package/dist/translations/zh.js +2 -0
  36. package/dist/types.d.ts +1 -0
  37. package/dist/util/tryit-prefill.js +4 -1
  38. package/dist/utils.js +8 -6
  39. package/package.json +23 -10
@@ -18,34 +18,26 @@ import { useId } from "react";
18
18
  * Render a property of an OpenAPI schema.
19
19
  */
20
20
  function OpenAPISchemaProperty(props) {
21
- const { circularRefs: parentCircularRefs, context, className, property,...rest } = props;
21
+ const { circularRefs: parentCircularRefs, context, className, property, discriminator, discriminatorValue,...rest } = props;
22
22
  const { schema } = property;
23
23
  const id = useId();
24
24
  const circularRefId = parentCircularRefs.get(schema);
25
- if (circularRefId) return <OpenAPISchemaCircularRef id={circularRefId} schema={schema} />;
25
+ if (circularRefId) return <OpenAPISchemaPresentation context={context} property={property} circularRefId={circularRefId} />;
26
26
  const circularRefs = new Map(parentCircularRefs);
27
27
  circularRefs.set(schema, id);
28
- const properties = getSchemaProperties(schema);
28
+ const properties = getSchemaProperties(schema, discriminator, discriminatorValue);
29
29
  const alternatives = getSchemaAlternatives(schema, new Set(circularRefs.keys()));
30
- const header = <OpenAPISchemaPresentation context={context} property={property} />;
30
+ const header = <OpenAPISchemaPresentation id={id} context={context} property={property} />;
31
31
  const content = (() => {
32
- if (alternatives?.schemas) {
33
- const { schemas, discriminator } = alternatives;
34
- return <div className="openapi-schema-alternatives">
35
- {schemas.map((alternativeSchema, index) => <div key={index} className="openapi-schema-alternative">
36
- <OpenAPISchemaAlternative schema={alternativeSchema} discriminator={discriminator} circularRefs={circularRefs} context={context} />
37
- {index < schemas.length - 1 ? <OpenAPISchemaAlternativeSeparator schema={schema} context={context} /> : null}
38
- </div>)}
39
- </div>;
40
- }
32
+ if (alternatives?.schemas) return <OpenAPISchemaAlternatives alternatives={alternatives} schema={schema} circularRefs={circularRefs} context={context} parentDiscriminator={discriminator} parentDiscriminatorValue={discriminatorValue} />;
41
33
  if (properties?.length) return <OpenAPISchemaProperties properties={properties} circularRefs={circularRefs} context={context} />;
42
34
  return null;
43
35
  })();
44
- if (properties?.length) return <OpenAPIDisclosure icon={context.icons.plus} className={clsx("openapi-schema", className)} header={header} label={(isExpanded) => getDisclosureLabel({
36
+ if (properties?.length) return <OpenAPIDisclosure icon={context.icons.plus} header={header} label={(isExpanded) => getDisclosureLabel({
45
37
  schema,
46
38
  isExpanded,
47
39
  context
48
- })} {...rest}>
40
+ })}>
49
41
  {content}
50
42
  </OpenAPIDisclosure>;
51
43
  return <div id={id} {...rest} className={clsx("openapi-schema", className)}>
@@ -75,34 +67,75 @@ function OpenAPIRootSchema(props) {
75
67
  const id = useId();
76
68
  const properties = getSchemaProperties(schema);
77
69
  const description = resolveDescription(schema);
78
- if (properties?.length) {
79
- const circularRefs = new Map(parentCircularRefs);
80
- circularRefs.set(schema, id);
81
- return <>
70
+ const alternatives = getSchemaAlternatives(schema, new Set(parentCircularRefs.keys()));
71
+ const circularRefs = new Map(parentCircularRefs);
72
+ circularRefs.set(schema, id);
73
+ if (alternatives?.schemas) return <>
74
+ {description ? <Markdown source={description} className="openapi-schema-root-description" /> : null}
75
+ <OpenAPISchemaAlternatives alternatives={alternatives} schema={schema} circularRefs={circularRefs} context={context} />
76
+ </>;
77
+ if (properties?.length) return <>
82
78
  {description ? <Markdown source={description} className="openapi-schema-root-description" /> : null}
83
79
  <OpenAPISchemaProperties properties={properties} circularRefs={circularRefs} context={context} />
84
80
  </>;
85
- }
86
81
  return <OpenAPISchemaProperty className="openapi-schema-root" property={{ schema }} context={context} circularRefs={parentCircularRefs} />;
87
82
  }
88
83
  function OpenAPIRootSchemaFromServer(props) {
89
84
  return <OpenAPIRootSchema schema={JSON.parse(props.schema, retrocycle())} context={props.context} />;
90
85
  }
91
86
  /**
87
+ * Get the discriminator value for a schema.
88
+ */
89
+ function getDiscriminatorValue(schema, discriminator) {
90
+ if (!discriminator) return;
91
+ if (discriminator.mapping) {
92
+ const mappingEntry = Object.entries(discriminator.mapping).find(([key, ref]) => {
93
+ if (schema.title === ref || !!schema.title && ref.endsWith(`/${schema.title}`)) return true;
94
+ if (schema.title?.toLowerCase().replace(/\s/g, "").includes(key.toLowerCase())) return true;
95
+ return false;
96
+ });
97
+ if (mappingEntry) return mappingEntry[0];
98
+ }
99
+ if (!discriminator.propertyName || !schema.properties) return;
100
+ const property = schema.properties[discriminator.propertyName];
101
+ if (!property || checkIsReference(property)) return;
102
+ if (property.const) return String(property.const);
103
+ if (property.enum?.length === 1) return String(property.enum[0]);
104
+ }
105
+ /**
106
+ * Render alternatives (oneOf/allOf/anyOf) for a schema.
107
+ */
108
+ function OpenAPISchemaAlternatives(props) {
109
+ const { alternatives, schema, circularRefs, context, parentDiscriminator, parentDiscriminatorValue } = props;
110
+ if (!alternatives?.schemas) return null;
111
+ const { schemas, discriminator: alternativeDiscriminator, type } = alternatives;
112
+ return <div className="openapi-schema-alternatives">
113
+ {schemas.map((alternativeSchema, index) => {
114
+ const effectiveDiscriminator = alternativeDiscriminator || (type === "allOf" ? parentDiscriminator : void 0);
115
+ const effectiveDiscriminatorValue = !alternativeDiscriminator && type === "allOf" ? parentDiscriminatorValue : void 0;
116
+ return <div key={index} className="openapi-schema-alternative">
117
+ <OpenAPISchemaAlternative schema={alternativeSchema} discriminator={effectiveDiscriminator} discriminatorValue={effectiveDiscriminatorValue} circularRefs={circularRefs} context={context} />
118
+ {index < schemas.length - 1 ? <OpenAPISchemaAlternativeSeparator schema={schema} context={context} /> : null}
119
+ </div>;
120
+ })}
121
+ </div>;
122
+ }
123
+ /**
92
124
  * Render a tab for an alternative schema.
93
125
  * It renders directly the properties if relevant;
94
126
  * for primitives, it renders the schema itself.
95
127
  */
96
128
  function OpenAPISchemaAlternative(props) {
97
129
  const { schema, discriminator, circularRefs, context } = props;
98
- const properties = getSchemaProperties(schema, discriminator);
130
+ const discriminatorValue = props.discriminatorValue || getDiscriminatorValue(schema, discriminator);
131
+ const properties = getSchemaProperties(schema, discriminator, discriminatorValue);
99
132
  return properties?.length ? <OpenAPIDisclosure icon={context.icons.plus} header={<OpenAPISchemaPresentation property={{ schema }} context={context} />} label={(isExpanded) => getDisclosureLabel({
100
133
  schema,
101
134
  isExpanded,
102
135
  context
103
136
  })}>
104
137
  <OpenAPISchemaProperties properties={properties} circularRefs={circularRefs} context={context} />
105
- </OpenAPIDisclosure> : <OpenAPISchemaProperty property={{ schema }} circularRefs={circularRefs} context={context} />;
138
+ </OpenAPIDisclosure> : <OpenAPISchemaProperty property={{ schema }} discriminator={discriminator} discriminatorValue={discriminatorValue} circularRefs={circularRefs} context={context} />;
106
139
  }
107
140
  function OpenAPISchemaAlternativeSeparator(props) {
108
141
  const { schema, context } = props;
@@ -121,8 +154,8 @@ function OpenAPISchemaAlternativeSeparator(props) {
121
154
  function OpenAPISchemaCircularRef(props) {
122
155
  const { id, schema } = props;
123
156
  return <div className="openapi-schema-circular">
157
+ <span className="openapi-schema-circular-glyph">⤷</span>
124
158
  Circular reference to <a href={`#${id}`}>{getSchemaTitle(schema)}</a>{" "}
125
- <span className="openapi-schema-circular-glyph">↩</span>
126
159
  </div>;
127
160
  }
128
161
  /**
@@ -164,40 +197,81 @@ function OpenAPISchemaEnum(props) {
164
197
  * Render the top row of a schema. e.g: name, type, and required status.
165
198
  */
166
199
  function OpenAPISchemaPresentation(props) {
167
- const { property: { schema, propertyName, required, isDiscriminatorProperty }, context } = props;
200
+ const { id, property: { schema, propertyName, required, isDiscriminatorProperty }, circularRefId, context } = props;
168
201
  const description = resolveDescription(schema);
169
202
  const example = resolveFirstExample(schema);
170
- return <div className="openapi-schema-presentation">
171
- <OpenAPISchemaName schema={schema} type={getSchemaTitle(schema)} propertyName={propertyName} isDiscriminatorProperty={isDiscriminatorProperty} required={required} context={context} />
172
- {typeof schema["x-deprecated-sunset"] === "string" ? <div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
173
- Sunset date:{" "}
174
- <span className="openapi-deprecated-sunset-date">
175
- {schema["x-deprecated-sunset"]}
176
- </span>
177
- </div> : null}
178
- {description ? <Markdown source={description} className="openapi-schema-description" /> : null}
179
- {schema.default !== void 0 ? <span className="openapi-schema-default">
180
- Default:{" "}
181
- <code>
182
- {typeof schema.default === "string" && schema.default ? schema.default : stringifyOpenAPI(schema.default)}
183
- </code>
184
- </span> : null}
185
- {typeof example === "string" ? <span className="openapi-schema-example">
186
- Example: <code>{example}</code>
187
- </span> : null}
188
- {schema.pattern ? <span className="openapi-schema-pattern">
189
- Pattern: <code>{schema.pattern}</code>
190
- </span> : null}
191
- <OpenAPISchemaEnum schema={schema} context={context} />
203
+ return <div id={id} className="openapi-schema-presentation">
204
+ <OpenAPISchemaName schema={schema} type={getSchemaTitle(schema, { ignoreAlternatives: !propertyName })} propertyName={propertyName} isDiscriminatorProperty={isDiscriminatorProperty} required={required} context={context} />
205
+ {circularRefId ? <OpenAPISchemaCircularRef id={circularRefId} schema={schema} /> : <>
206
+ {typeof schema["x-deprecated-sunset"] === "string" ? <div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
207
+ Sunset date:{" "}
208
+ <span className="openapi-deprecated-sunset-date">
209
+ {schema["x-deprecated-sunset"]}
210
+ </span>
211
+ </div> : null}
212
+ {description ? <Markdown source={description} className="openapi-schema-description" /> : null}
213
+ {schema.default !== void 0 ? <span className="openapi-schema-default">
214
+ Default:{" "}
215
+ <code>
216
+ {typeof schema.default === "string" && schema.default ? schema.default : stringifyOpenAPI(schema.default)}
217
+ </code>
218
+ </span> : null}
219
+ {typeof example === "string" ? <span className="openapi-schema-example">
220
+ Example: <code>{example}</code>
221
+ </span> : null}
222
+ {schema.pattern ? <span className="openapi-schema-pattern">
223
+ Pattern: <code>{schema.pattern}</code>
224
+ </span> : null}
225
+ <OpenAPISchemaEnum schema={schema} context={context} />
226
+ </>}
192
227
  </div>;
193
228
  }
194
229
  /**
230
+ * Process properties from a schema object into property entries.
231
+ */
232
+ function processSchemaProperties(schema, discriminator, discriminatorValue, allRequired) {
233
+ const result = [];
234
+ if (schema.properties) Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
235
+ const isDiscriminator = discriminator?.propertyName === propertyName;
236
+ if (checkIsReference(propertySchema)) {
237
+ if (!isDiscriminator || !discriminatorValue) return;
238
+ }
239
+ let finalSchema = propertySchema;
240
+ if (isDiscriminator && discriminatorValue) finalSchema = {
241
+ ...propertySchema,
242
+ const: discriminatorValue,
243
+ enum: [discriminatorValue]
244
+ };
245
+ result.push({
246
+ propertyName,
247
+ required: Array.isArray(schema.required) ? schema.required.includes(propertyName) : allRequired?.has(propertyName) ? true : void 0,
248
+ isDiscriminatorProperty: isDiscriminator,
249
+ schema: finalSchema
250
+ });
251
+ });
252
+ if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) result.push({
253
+ propertyName: "Other properties",
254
+ schema: schema.additionalProperties === true ? {} : schema.additionalProperties
255
+ });
256
+ return result;
257
+ }
258
+ /**
259
+ * Merge properties into a result array, with later properties overriding earlier ones.
260
+ */
261
+ function mergeProperties(result, newProperties) {
262
+ for (const prop of newProperties) {
263
+ const existingIndex = result.findIndex((p) => p.propertyName === prop.propertyName);
264
+ if (existingIndex >= 0) result[existingIndex] = prop;
265
+ else result.push(prop);
266
+ }
267
+ }
268
+ /**
195
269
  * Get the sub-properties of a schema.
196
270
  */
197
- function getSchemaProperties(schema, discriminator) {
271
+ function getSchemaProperties(schema, discriminator, discriminatorValue) {
198
272
  if (schema.type === "array" && schema.items && !checkIsReference(schema.items)) {
199
273
  const items = schema.items;
200
- const itemProperties = getSchemaProperties(items);
274
+ const itemProperties = getSchemaProperties(items, discriminator, discriminatorValue);
201
275
  if (itemProperties) return itemProperties.map((prop) => ({
202
276
  ...prop,
203
277
  isDiscriminatorProperty: discriminator?.propertyName === prop.propertyName
@@ -208,23 +282,25 @@ function getSchemaProperties(schema, discriminator) {
208
282
  schema: items
209
283
  }];
210
284
  }
211
- if (schema.type === "object" || schema.properties) {
212
- const result = [];
213
- if (schema.properties) Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
214
- if (checkIsReference(propertySchema)) return;
215
- result.push({
216
- propertyName,
217
- required: Array.isArray(schema.required) ? schema.required.includes(propertyName) : void 0,
218
- isDiscriminatorProperty: discriminator?.propertyName === propertyName,
219
- schema: propertySchema
285
+ if (schema.allOf && Array.isArray(schema.allOf)) {
286
+ const allOfSchemas = schema.allOf.filter((s) => !checkIsReference(s));
287
+ if (allOfSchemas.length > 0) {
288
+ const result = [];
289
+ const allRequired = /* @__PURE__ */ new Set();
290
+ for (const allOfSchema of allOfSchemas) {
291
+ if (Array.isArray(allOfSchema.required)) allOfSchema.required.forEach((req) => allRequired.add(req));
292
+ const allOfProperties = getSchemaProperties(allOfSchema, discriminator, discriminatorValue);
293
+ if (allOfProperties) mergeProperties(result, allOfProperties);
294
+ }
295
+ if (Array.isArray(schema.required)) schema.required.forEach((req) => allRequired.add(req));
296
+ mergeProperties(result, processSchemaProperties(schema, discriminator, discriminatorValue, allRequired));
297
+ result.forEach((prop) => {
298
+ if (prop.propertyName && allRequired.has(prop.propertyName)) prop.required = true;
220
299
  });
221
- });
222
- if (schema.additionalProperties && !checkIsReference(schema.additionalProperties)) result.push({
223
- propertyName: "Other properties",
224
- schema: schema.additionalProperties === true ? {} : schema.additionalProperties
225
- });
226
- return result;
300
+ return result.length > 0 ? result : null;
301
+ }
227
302
  }
303
+ if (schema.type === "object" || schema.properties) return processSchemaProperties(schema, discriminator, discriminatorValue);
228
304
  return null;
229
305
  }
230
306
  /**
@@ -342,23 +418,55 @@ function mergeAlternatives(alternativeType, schemasOrRefs) {
342
418
  }
343
419
  function flattenAlternatives(alternativeType, schemasOrRefs, ancestors) {
344
420
  const latestAncestor = Array.from(ancestors).pop();
345
- return schemasOrRefs.reduce((acc, schemaOrRef) => {
346
- if (checkIsReference(schemaOrRef)) return acc;
347
- if (schemaOrRef[alternativeType] && !ancestors.has(schemaOrRef)) {
348
- const alternatives = getSchemaAlternatives(schemaOrRef, ancestors);
349
- if (alternatives?.schemas) acc.push(...alternatives.schemas.map((schema$1) => ({
350
- ...schema$1,
351
- required: mergeRequiredFields(schema$1, latestAncestor)
352
- })));
353
- return acc;
421
+ const result = [];
422
+ for (const schemaOrRef of schemasOrRefs) {
423
+ if (checkIsReference(schemaOrRef)) continue;
424
+ const flattened = flattenSchema(schemaOrRef, alternativeType, ancestors, latestAncestor);
425
+ if (flattened) result.push(...flattened);
426
+ }
427
+ return result;
428
+ }
429
+ /**
430
+ * Flatten a schema that is an alternative of another schema.
431
+ */
432
+ function flattenSchema(schema, alternativeType, ancestors, latestAncestor) {
433
+ if (schema[alternativeType] && !ancestors.has(schema)) {
434
+ const alternatives = getSchemaAlternatives(schema, ancestors);
435
+ if (alternatives?.schemas) return alternatives.schemas.map((s) => {
436
+ const required$2 = mergeRequiredFields(s, latestAncestor);
437
+ return {
438
+ ...s,
439
+ ...required$2 ? { required: required$2 } : {}
440
+ };
441
+ });
442
+ const required$1 = mergeRequiredFields(schema, latestAncestor);
443
+ return [{
444
+ ...schema,
445
+ ...required$1 ? { required: required$1 } : {}
446
+ }];
447
+ }
448
+ if ((alternativeType === "oneOf" || alternativeType === "anyOf") && schema.allOf && Array.isArray(schema.allOf) && !ancestors.has(schema)) {
449
+ const allOfSchemas = schema.allOf.filter((s) => !checkIsReference(s));
450
+ if (allOfSchemas.length > 0) {
451
+ const merged = mergeAlternatives("allOf", allOfSchemas);
452
+ if (merged && merged.length > 0) {
453
+ if (merged.length === 1) return merged.map((s) => {
454
+ const required$1 = mergeRequiredFields(s, latestAncestor);
455
+ const result = {
456
+ ...s,
457
+ ...required$1 ? { required: required$1 } : {}
458
+ };
459
+ if (schema.title && !s.title) result.title = schema.title;
460
+ return result;
461
+ });
462
+ }
354
463
  }
355
- const schema = {
356
- ...schemaOrRef,
357
- required: mergeRequiredFields(schemaOrRef, latestAncestor)
358
- };
359
- acc.push(schema);
360
- return acc;
361
- }, []);
464
+ }
465
+ const required = mergeRequiredFields(schema, latestAncestor);
466
+ return [{
467
+ ...schema,
468
+ ...required ? { required } : {}
469
+ }];
362
470
  }
363
471
  /**
364
472
  * Merge the required fields of a schema with the required fields of its latest ancestor.
@@ -4,6 +4,7 @@ import { OpenAPICopyButton } from "./OpenAPICopyButton.js";
4
4
  import { OpenAPISchemaName } from "./OpenAPISchemaName.js";
5
5
  import { createStateKey, extractOperationSecurityInfo, resolveDescription } from "./utils.js";
6
6
  import { InteractiveSection } from "./InteractiveSection.js";
7
+ import { OpenAPIRequiredScopes, OpenAPISchemaScopes } from "./OpenAPIRequiredScopes.js";
7
8
  import { Fragment } from "react";
8
9
 
9
10
  //#region src/OpenAPISecurities.tsx
@@ -17,20 +18,23 @@ function OpenAPISecurities(props) {
17
18
  securityRequirement,
18
19
  securities
19
20
  });
20
- return <InteractiveSection header={t(context.translation, "authorizations")} stateKey={createStateKey("securities", context.blockKey)} toggeable defaultOpened={false} toggleIcon={context.icons.chevronRight} selectIcon={context.icons.chevronDown} className="openapi-securities" tabs={tabsData.map(({ key, label, schemes }) => ({
21
+ const stateKey = createStateKey("securities", context.blockKey);
22
+ return <>
23
+ <OpenAPIRequiredScopes context={context} stateKey={stateKey} securities={tabsData} />
24
+ <InteractiveSection header={t(context.translation, "authorizations")} stateKey={stateKey} toggleIcon={context.icons.chevronRight} selectIcon={context.icons.chevronDown} className="openapi-securities" tabs={tabsData.map(({ key, label, schemes }) => ({
21
25
  key,
22
26
  label,
23
27
  body: <div className="openapi-schema">
24
- {schemes.map((security, index) => {
25
- const description = resolveDescription(security);
28
+ {schemes.map((security, index) => {
29
+ const description = security.type !== "oauth2" ? resolveDescription(security) : void 0;
26
30
  return <div key={`${key}-${index}`} className="openapi-schema-presentation">
27
- {getLabelForType(security, context)}
28
- {description ? <Markdown source={description} className="openapi-securities-description" /> : null}
29
- {security.scopes?.length ? <OpenAPISchemaScopes scopes={security.scopes} context={context} /> : null}
30
- </div>;
31
+ {getLabelForType(security, context)}
32
+ {description ? <Markdown source={description} className="openapi-securities-description" /> : null}
33
+ </div>;
31
34
  })}
32
- </div>
33
- }))} />;
35
+ </div>
36
+ }))} />
37
+ </>;
34
38
  }
35
39
  function getLabelForType(security, context) {
36
40
  switch (security.type) {
@@ -61,11 +65,12 @@ function OpenAPISchemaOAuth2Flows(props) {
61
65
  function OpenAPISchemaOAuth2Item(props) {
62
66
  const { flow, context, security, name } = props;
63
67
  if (!flow) return null;
64
- const scopes = flow.scopes ? Object.entries(flow.scopes) : [];
68
+ const scopes = !security.scopes?.length && flow.scopes ? Object.entries(flow.scopes) : [];
69
+ const description = resolveDescription(security);
65
70
  return <div>
66
71
  <OpenAPISchemaName context={context} propertyName="OAuth2" type={name} required={security.required} />
67
72
  <div className="openapi-securities-oauth-content openapi-markdown">
68
- {security.description ? <Markdown source={security.description} /> : null}
73
+ {description ? <Markdown source={description} className="openapi-securities-description" /> : null}
69
74
  {"authorizationUrl" in flow && flow.authorizationUrl ? <span>
70
75
  Authorization URL:{" "}
71
76
  <OpenAPICopyButton value={flow.authorizationUrl} context={context} className="openapi-securities-url" withTooltip>
@@ -84,41 +89,10 @@ function OpenAPISchemaOAuth2Item(props) {
84
89
  {flow.refreshUrl}
85
90
  </OpenAPICopyButton>
86
91
  </span> : null}
87
- {scopes.length ? <OpenAPISchemaScopes scopes={scopes} context={context} /> : null}
92
+ {scopes.length ? <OpenAPISchemaScopes scopes={scopes} context={context} isOAuth2 /> : null}
88
93
  </div>
89
94
  </div>;
90
95
  }
91
- /**
92
- * Render a list of available scopes.
93
- */
94
- function OpenAPISchemaScopes(props) {
95
- const { scopes, context } = props;
96
- return <div className="openapi-securities-scopes openapi-markdown">
97
- <span>{t(context.translation, "required_scopes")}: </span>
98
- <ul>
99
- {scopes.map((scope) => <OpenAPIScopeItem key={scope[0]} scope={scope} context={context} />)}
100
- </ul>
101
- </div>;
102
- }
103
- /**
104
- * Display a scope item. Either a key-value pair or a single string.
105
- */
106
- function OpenAPIScopeItem(props) {
107
- const { scope, context } = props;
108
- return <li>
109
- <OpenAPIScopeItemKey name={scope[0]} context={context} />
110
- {scope[1] ? `: ${scope[1]}` : null}
111
- </li>;
112
- }
113
- /**
114
- * Displays the scope name within a copyable button.
115
- */
116
- function OpenAPIScopeItemKey(props) {
117
- const { name, context } = props;
118
- return <OpenAPICopyButton value={name} context={context} withTooltip>
119
- <code>{name}</code>
120
- </OpenAPICopyButton>;
121
- }
122
96
 
123
97
  //#endregion
124
98
  export { OpenAPISecurities };
@@ -16,16 +16,16 @@ function useSelectState(stateKey = "select-state", initialKey = "default") {
16
16
  };
17
17
  }
18
18
  function OpenAPISelect(props) {
19
- const { icon = "▼", items, children, className, placement, stateKey, selectedKey, onSelectionChange } = props;
20
- const state = useSelectState(stateKey, items[0]?.key);
19
+ const { icon, items, children, className, placement, stateKey, value, onChange, defaultValue } = props;
20
+ const state = useSelectState(stateKey, defaultValue ?? items[0]?.key);
21
21
  const selected = items.find((item) => item.key === state.key) || items[0];
22
- return <Select aria-label="OpenAPI Select" {...props} value={selectedKey || selected?.key} onChange={(key) => {
23
- onSelectionChange?.(key);
22
+ return <Select aria-label="OpenAPI Select" {...props} value={value ?? selected?.key} onChange={(key) => {
23
+ onChange?.(key);
24
24
  state.setKey(key);
25
25
  }} className={clsx("openapi-select", className)}>
26
26
  <Button>
27
27
  <SelectValue />
28
- {icon}
28
+ {icon !== null ? icon || "▼" : null}
29
29
  </Button>
30
30
  <Popover placement={placement} className="openapi-select-popover">
31
31
  <ListBox className="openapi-select-listbox" items={items}>
@@ -38,7 +38,7 @@ function OpenAPISelectItem(props) {
38
38
  return <ListBoxItem {...props} className={({ isFocused, isSelected }) => clsx("openapi-select-item", {
39
39
  "openapi-select-item-focused": isFocused,
40
40
  "openapi-select-item-selected": isSelected
41
- })} />;
41
+ }, props.className)} />;
42
42
  }
43
43
 
44
44
  //#endregion
@@ -36,7 +36,7 @@ function groupParameters(parameters, context) {
36
36
  const key = parameter.in;
37
37
  const label = getParameterGroupName(parameter.in, context);
38
38
  const group = groups.find((group$1) => group$1.key === key);
39
- if (group) group.parameters.push(parameter);
39
+ if (group) group.parameters = [...group.parameters, parameter];
40
40
  else groups.push({
41
41
  key,
42
42
  label,
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+
4
+ import clsx from "classnames";
5
+ import { Tooltip, TooltipTrigger } from "react-aria-components";
6
+
7
+ //#region src/OpenAPITooltip.tsx
8
+ function OpenAPITooltip(props) {
9
+ const { children,...rest } = props;
10
+ return <TooltipTrigger {...rest} closeDelay={200} delay={200}>
11
+ {children}
12
+ </TooltipTrigger>;
13
+ }
14
+ function OpenAPITooltipContent(props) {
15
+ const { children, placement = "top", offset = 4, className,...rest } = props;
16
+ return <Tooltip {...rest} placement={placement} offset={offset} className={clsx("openapi-tooltip", className)}>
17
+ {children}
18
+ </Tooltip>;
19
+ }
20
+ OpenAPITooltip.Content = OpenAPITooltipContent;
21
+
22
+ //#endregion
23
+ export { OpenAPITooltip };
@@ -47,12 +47,15 @@ function ScalarModal(props) {
47
47
  url: specUrl,
48
48
  ...prefillConfig
49
49
  }} initialRequest={{
50
- method,
50
+ method: toScalarHttpMethod(method),
51
51
  path
52
52
  }}>
53
53
  <ScalarModalController method={method} path={path} controllerRef={controllerRef} />
54
54
  </ApiClientModalProvider>;
55
55
  }
56
+ function toScalarHttpMethod(method) {
57
+ return method.toUpperCase();
58
+ }
56
59
  function ScalarModalController(props) {
57
60
  const { method, path, controllerRef } = props;
58
61
  const openScalarClient = useApiClientModal()?.open;
@@ -60,7 +63,7 @@ function ScalarModalController(props) {
60
63
  const openClient = useMemo(() => {
61
64
  if (openScalarClient) return () => {
62
65
  openScalarClient({
63
- method,
66
+ method: toScalarHttpMethod(method),
64
67
  path,
65
68
  _source: "gitbook"
66
69
  });
@@ -33,7 +33,6 @@ ${headerString}${bodyString}`;
33
33
  label: "cURL",
34
34
  syntax: "bash",
35
35
  generate: ({ method, url: { origin, path }, headers, body }) => {
36
- const separator = " \\\n";
37
36
  const lines = ["curl -L"];
38
37
  if (method.toUpperCase() !== "GET") lines.push(`--request ${method.toUpperCase()}`);
39
38
  lines.push(`--url '${origin}${path}'`);
@@ -49,7 +48,7 @@ ${headerString}${bodyString}`;
49
48
  });
50
49
  if (body) if (Array.isArray(body)) lines.push(...body);
51
50
  else lines.push(body);
52
- return lines.map((line, index) => index > 0 ? indent(line, 2) : line).join(separator);
51
+ return buildHeredoc(lines);
53
52
  }
54
53
  },
55
54
  {
@@ -138,7 +137,15 @@ const BodyGenerators = {
138
137
  headersCopy["Content-Type"] = "application/json";
139
138
  } else if (isPDF(contentType)) body = `--data-binary '@${String(body)}'`;
140
139
  else if (isYAML(contentType)) body = `--data-binary $'${yaml.dump(body).replace(/'/g, "").replace(/\\n/g, "\n")}'`;
141
- else body = `--data '${stringifyOpenAPI(body, null, 2).replace(/\\n/g, "\n")}'`;
140
+ else {
141
+ const jsonString = stringifyOpenAPI(body, null, 2).replace(/\\n/g, "\n");
142
+ if (jsonString.includes("'")) body = [
143
+ "--data @- <<'EOF'",
144
+ ...jsonString.split("\n"),
145
+ "EOF"
146
+ ];
147
+ else body = `--data '${jsonString}'`;
148
+ }
142
149
  return {
143
150
  body,
144
151
  headers: headersCopy
@@ -270,6 +277,29 @@ function convertBodyToXML(body) {
270
277
  }
271
278
  return json2xml(body).replace(/"/g, "").replace(/\\n/g, "\n").replace(/\\t/g, " ");
272
279
  }
280
+ /**
281
+ * Builds a heredoc string from an array of lines
282
+ */
283
+ function buildHeredoc(lines) {
284
+ const separator = " \\\n";
285
+ let result = "";
286
+ let inHeredoc = false;
287
+ for (let i = 0; i < lines.length; i++) {
288
+ const line = lines[i];
289
+ if (!line) continue;
290
+ const isHeredocStart = line.includes("<<'EOF'");
291
+ const isHeredocEnd = inHeredoc && line === "EOF";
292
+ if (isHeredocStart) {
293
+ inHeredoc = true;
294
+ result += `${i > 0 ? indent(line, 2) : line}\n`;
295
+ } else if (isHeredocEnd) {
296
+ inHeredoc = false;
297
+ result += line;
298
+ } else if (inHeredoc) result += `${indent(line, 2)}\n`;
299
+ else result += `${i > 0 ? indent(line, 2) : line}${i < lines.length - 1 ? separator : ""}`;
300
+ }
301
+ return result;
302
+ }
273
303
 
274
304
  //#endregion
275
305
  export { codeSampleGenerators, parseHostAndPath };
package/dist/context.d.ts CHANGED
@@ -14,6 +14,9 @@ interface OpenAPIClientContext {
14
14
  chevronDown: React.ReactNode;
15
15
  chevronRight: React.ReactNode;
16
16
  plus: React.ReactNode;
17
+ copy: React.ReactNode;
18
+ check: React.ReactNode;
19
+ lock: React.ReactNode;
17
20
  };
18
21
  /**
19
22
  * Force all sections to be opened by default.
@@ -0,0 +1,25 @@
1
+ //#region src/formatPath.tsx
2
+ /**
3
+ * Format the path by wrapping placeholders in <span> tags.
4
+ */
5
+ function formatPath(path) {
6
+ const regex = /\{\s*(\w+)\s*\}|:\w+/g;
7
+ const parts = [];
8
+ let lastIndex = 0;
9
+ path.replace(regex, (match, _, offset) => {
10
+ if (offset > lastIndex) parts.push(path.slice(lastIndex, offset));
11
+ parts.push(<span key={`offset-${offset}`} className="openapi-path-variable">
12
+ {match}
13
+ </span>);
14
+ lastIndex = offset + match.length;
15
+ return match;
16
+ });
17
+ if (lastIndex < path.length) parts.push(path.slice(lastIndex));
18
+ return parts.map((part, index) => {
19
+ if (typeof part === "string") return <span key={`part-${index}`}>{part}</span>;
20
+ return part;
21
+ });
22
+ }
23
+
24
+ //#endregion
25
+ export { formatPath };