docusaurus-theme-openapi-docs 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/lib/markdown/schema.js +38 -15
  2. package/lib/markdown/schema.test.d.ts +1 -0
  3. package/lib/markdown/schema.test.js +86 -0
  4. package/lib/theme/ApiExplorer/ApiCodeBlock/Container/index.js +4 -2
  5. package/lib/theme/ApiExplorer/ApiCodeBlock/Content/String.js +9 -6
  6. package/lib/theme/ApiExplorer/ApiCodeBlock/Line/index.d.ts +1 -1
  7. package/lib/theme/ApiExplorer/ApiCodeBlock/index.d.ts +1 -1
  8. package/lib/theme/ApiExplorer/Authorization/index.js +9 -10
  9. package/lib/theme/ApiExplorer/Body/index.js +4 -5
  10. package/lib/theme/ApiExplorer/CodeSnippets/index.js +96 -61
  11. package/lib/theme/ApiExplorer/CodeSnippets/languages.js +12 -1
  12. package/lib/theme/ApiExplorer/CodeSnippets/languages.test.d.ts +1 -0
  13. package/lib/theme/ApiExplorer/CodeSnippets/languages.test.js +102 -0
  14. package/lib/theme/ApiExplorer/CodeTabs/index.d.ts +1 -1
  15. package/lib/theme/ApiExplorer/CodeTabs/index.js +6 -5
  16. package/lib/theme/ApiExplorer/Export/index.js +9 -2
  17. package/lib/theme/ApiExplorer/FormFileUpload/index.js +1 -2
  18. package/lib/theme/ApiExplorer/FormLabel/index.js +1 -2
  19. package/lib/theme/ApiExplorer/FormTextInput/index.js +1 -2
  20. package/lib/theme/ApiExplorer/LiveEditor/index.js +1 -2
  21. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.js +5 -3
  22. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +75 -4
  23. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.js +1 -2
  24. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.js +67 -4
  25. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem.js +65 -1
  26. package/lib/theme/ApiExplorer/ParamOptions/index.js +2 -3
  27. package/lib/theme/ApiExplorer/Request/index.js +17 -18
  28. package/lib/theme/ApiExplorer/Response/index.js +54 -12
  29. package/lib/theme/ApiExplorer/SecuritySchemes/index.js +57 -50
  30. package/lib/theme/ApiExplorer/Server/index.js +2 -3
  31. package/lib/theme/ApiItem/index.js +59 -33
  32. package/lib/theme/ApiTabs/index.d.ts +1 -1
  33. package/lib/theme/ApiTabs/index.js +7 -7
  34. package/lib/theme/DiscriminatorTabs/index.d.ts +1 -1
  35. package/lib/theme/DiscriminatorTabs/index.js +6 -5
  36. package/lib/theme/Example/index.js +3 -4
  37. package/lib/theme/MimeTabs/index.d.ts +1 -1
  38. package/lib/theme/MimeTabs/index.js +6 -5
  39. package/lib/theme/OperationTabs/index.d.ts +1 -1
  40. package/lib/theme/OperationTabs/index.js +6 -5
  41. package/lib/theme/ParamsDetails/index.js +1 -2
  42. package/lib/theme/ParamsItem/index.js +7 -8
  43. package/lib/theme/RequestSchema/index.js +57 -57
  44. package/lib/theme/ResponseExamples/index.js +3 -4
  45. package/lib/theme/ResponseSchema/index.js +26 -24
  46. package/lib/theme/Schema/index.js +148 -27
  47. package/lib/theme/SchemaExpansion/_SchemaExpansion.scss +113 -0
  48. package/lib/theme/SchemaExpansion/context.d.ts +24 -0
  49. package/lib/theme/SchemaExpansion/context.js +187 -0
  50. package/lib/theme/SchemaExpansion/index.d.ts +4 -0
  51. package/lib/theme/SchemaExpansion/index.js +314 -0
  52. package/lib/theme/SchemaItem/index.js +9 -10
  53. package/lib/theme/SchemaTabs/index.d.ts +1 -1
  54. package/lib/theme/SchemaTabs/index.js +6 -5
  55. package/lib/theme/StatusCodes/index.js +2 -4
  56. package/lib/theme/TabItem/index.d.ts +5 -0
  57. package/lib/theme/TabItem/index.js +51 -0
  58. package/lib/theme/TabItem/styles.module.css +3 -0
  59. package/lib/theme/Tabs/index.d.ts +5 -0
  60. package/lib/theme/Tabs/index.js +148 -0
  61. package/lib/theme/Tabs/styles.module.css +7 -0
  62. package/lib/theme/styles.scss +1 -0
  63. package/lib/theme/translationIds.d.ts +1 -93
  64. package/lib/theme/translationIds.js +0 -109
  65. package/lib/theme/utils/codeBlockUtils.d.ts +28 -0
  66. package/lib/theme/utils/codeBlockUtils.js +223 -0
  67. package/lib/theme/utils/reactUtils.d.ts +1 -0
  68. package/lib/theme/utils/reactUtils.js +23 -0
  69. package/lib/theme/utils/scrollUtils.d.ts +7 -0
  70. package/lib/theme/utils/scrollUtils.js +175 -0
  71. package/lib/theme/utils/tabsUtils.d.ts +47 -0
  72. package/lib/theme/utils/tabsUtils.js +299 -0
  73. package/lib/theme/utils/useCodeWordWrap.d.ts +8 -0
  74. package/lib/theme/utils/useCodeWordWrap.js +84 -0
  75. package/lib/theme/utils/useMutationObserver.d.ts +3 -0
  76. package/lib/theme/utils/useMutationObserver.js +34 -0
  77. package/package.json +4 -4
  78. package/src/markdown/schema.test.ts +102 -0
  79. package/src/markdown/schema.ts +42 -15
  80. package/src/theme/ApiExplorer/ApiCodeBlock/Container/index.tsx +2 -1
  81. package/src/theme/ApiExplorer/ApiCodeBlock/Content/String.tsx +8 -7
  82. package/src/theme/ApiExplorer/ApiCodeBlock/Line/index.tsx +1 -1
  83. package/src/theme/ApiExplorer/ApiCodeBlock/index.tsx +1 -1
  84. package/src/theme/ApiExplorer/Authorization/index.tsx +9 -10
  85. package/src/theme/ApiExplorer/Body/index.tsx +7 -5
  86. package/src/theme/ApiExplorer/CodeSnippets/index.tsx +103 -59
  87. package/src/theme/ApiExplorer/CodeSnippets/languages.test.ts +109 -0
  88. package/src/theme/ApiExplorer/CodeSnippets/languages.ts +13 -1
  89. package/src/theme/ApiExplorer/CodeTabs/index.tsx +5 -5
  90. package/src/theme/ApiExplorer/Export/index.tsx +6 -2
  91. package/src/theme/ApiExplorer/FormFileUpload/index.tsx +1 -2
  92. package/src/theme/ApiExplorer/FormLabel/index.tsx +1 -2
  93. package/src/theme/ApiExplorer/FormTextInput/index.tsx +1 -2
  94. package/src/theme/ApiExplorer/LiveEditor/index.tsx +1 -2
  95. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx +5 -3
  96. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx +20 -4
  97. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx +1 -2
  98. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx +15 -4
  99. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem.tsx +11 -1
  100. package/src/theme/ApiExplorer/ParamOptions/index.tsx +2 -3
  101. package/src/theme/ApiExplorer/Request/index.tsx +23 -18
  102. package/src/theme/ApiExplorer/Response/index.tsx +63 -9
  103. package/src/theme/ApiExplorer/SecuritySchemes/index.tsx +60 -52
  104. package/src/theme/ApiExplorer/Server/index.tsx +8 -3
  105. package/src/theme/ApiItem/index.tsx +43 -21
  106. package/src/theme/ApiTabs/index.tsx +8 -8
  107. package/src/theme/DiscriminatorTabs/index.tsx +6 -5
  108. package/src/theme/Example/index.tsx +3 -4
  109. package/src/theme/MimeTabs/index.tsx +9 -8
  110. package/src/theme/OperationTabs/index.tsx +5 -4
  111. package/src/theme/ParamsDetails/index.tsx +1 -2
  112. package/src/theme/ParamsItem/index.tsx +13 -8
  113. package/src/theme/RequestSchema/index.tsx +38 -40
  114. package/src/theme/ResponseExamples/index.tsx +3 -4
  115. package/src/theme/ResponseSchema/index.tsx +16 -17
  116. package/src/theme/Schema/index.tsx +156 -27
  117. package/src/theme/SchemaExpansion/_SchemaExpansion.scss +113 -0
  118. package/src/theme/SchemaExpansion/context.tsx +154 -0
  119. package/src/theme/SchemaExpansion/index.tsx +236 -0
  120. package/src/theme/SchemaItem/index.tsx +18 -10
  121. package/src/theme/SchemaTabs/index.tsx +6 -5
  122. package/src/theme/StatusCodes/index.tsx +2 -3
  123. package/src/theme/TabItem/index.tsx +61 -0
  124. package/src/theme/TabItem/styles.module.css +3 -0
  125. package/src/theme/Tabs/index.tsx +164 -0
  126. package/src/theme/Tabs/styles.module.css +7 -0
  127. package/src/theme/styles.scss +1 -0
  128. package/src/theme/translationIds.ts +37 -106
  129. package/src/theme/utils/codeBlockUtils.ts +296 -0
  130. package/src/theme/utils/reactUtils.ts +22 -0
  131. package/src/theme/utils/scrollUtils.tsx +153 -0
  132. package/src/theme/utils/tabsUtils.tsx +329 -0
  133. package/src/theme/utils/useCodeWordWrap.ts +110 -0
  134. package/src/theme/utils/useMutationObserver.ts +43 -0
  135. package/src/theme-classic.d.ts +0 -96
  136. package/src/types.d.ts +27 -0
  137. package/tsconfig.tsbuildinfo +1 -1
@@ -12,7 +12,6 @@ import { translate } from "@docusaurus/Translate";
12
12
  import Details from "@theme/Details";
13
13
  import ParamsItem from "@theme/ParamsItem";
14
14
  import SkeletonLoader from "@theme/SkeletonLoader";
15
- import { OPENAPI_PARAMS_DETAILS } from "@theme/translationIds";
16
15
 
17
16
  interface Props {
18
17
  parameters: any[];
@@ -35,7 +34,7 @@ const ParamsDetailsComponent: React.FC<Props> = ({ parameters }) => {
35
34
  <h3 className="openapi-markdown__details-summary-header-params">
36
35
  {translate(
37
36
  {
38
- id: OPENAPI_PARAMS_DETAILS.PARAMETERS_TITLE,
37
+ id: "theme.openapi.paramsDetails.parametersTitle",
39
38
  message: "{type} Parameters",
40
39
  },
41
40
  { type: type.charAt(0).toUpperCase() + type.slice(1) }
@@ -11,7 +11,6 @@ import { translate } from "@docusaurus/Translate";
11
11
  import { Example } from "@theme/Example";
12
12
  import Markdown from "@theme/Markdown";
13
13
  /* eslint-disable import/no-extraneous-dependencies*/
14
- import { OPENAPI_SCHEMA_ITEM } from "@theme/translationIds";
15
14
  import clsx from "clsx";
16
15
 
17
16
  import { getQualifierMessage, getSchemaName } from "../../markdown/schema";
@@ -41,11 +40,11 @@ export interface Props {
41
40
  const getEnumDescriptionMarkdown = (enumDescriptions?: [string, string][]) => {
42
41
  if (enumDescriptions?.length) {
43
42
  const enumValue = translate({
44
- id: OPENAPI_SCHEMA_ITEM.ENUM_VALUE,
43
+ id: "theme.openapi.schemaItem.enumValue",
45
44
  message: "Enum Value",
46
45
  });
47
46
  const description = translate({
48
- id: OPENAPI_SCHEMA_ITEM.ENUM_DESCRIPTION,
47
+ id: "theme.openapi.schemaItem.enumDescription",
49
48
  message: "Description",
50
49
  });
51
50
  return `| ${enumValue} | ${description} |
@@ -92,13 +91,19 @@ function ParamsItem({ param, ...rest }: Props) {
92
91
 
93
92
  const renderSchemaRequired = guard(required, () => (
94
93
  <span className="openapi-schema__required">
95
- {translate({ id: OPENAPI_SCHEMA_ITEM.REQUIRED, message: "required" })}
94
+ {translate({
95
+ id: "theme.openapi.schemaItem.required",
96
+ message: "required",
97
+ })}
96
98
  </span>
97
99
  ));
98
100
 
99
101
  const renderDeprecated = guard(deprecated, () => (
100
102
  <span className="openapi-schema__deprecated">
101
- {translate({ id: OPENAPI_SCHEMA_ITEM.DEPRECATED, message: "deprecated" })}
103
+ {translate({
104
+ id: "theme.openapi.schemaItem.deprecated",
105
+ message: "deprecated",
106
+ })}
102
107
  </span>
103
108
  ));
104
109
 
@@ -113,7 +118,7 @@ function ParamsItem({ param, ...rest }: Props) {
113
118
  return undefined;
114
119
  }
115
120
  const label = translate({
116
- id: OPENAPI_SCHEMA_ITEM.CONSTANT_VALUE,
121
+ id: "theme.openapi.schemaItem.constantValue",
117
122
  message: "Constant value:",
118
123
  });
119
124
  return (
@@ -152,7 +157,7 @@ function ParamsItem({ param, ...rest }: Props) {
152
157
  <div>
153
158
  <strong>
154
159
  {translate({
155
- id: OPENAPI_SCHEMA_ITEM.DEFAULT_VALUE,
160
+ id: "theme.openapi.schemaItem.defaultValue",
156
161
  message: "Default value:",
157
162
  })}{" "}
158
163
  </strong>
@@ -166,7 +171,7 @@ function ParamsItem({ param, ...rest }: Props) {
166
171
  <div>
167
172
  <strong>
168
173
  {translate({
169
- id: OPENAPI_SCHEMA_ITEM.DEFAULT_VALUE,
174
+ id: "theme.openapi.schemaItem.defaultValue",
170
175
  message: "Default value:",
171
176
  })}{" "}
172
177
  </strong>
@@ -18,10 +18,10 @@ import {
18
18
  ResponseExamples,
19
19
  } from "@theme/ResponseExamples";
20
20
  import SchemaNode from "@theme/Schema";
21
+ import SchemaExpansionControl from "@theme/SchemaExpansion";
21
22
  import SchemaTabs from "@theme/SchemaTabs";
22
23
  import SkeletonLoader from "@theme/SkeletonLoader";
23
24
  import TabItem from "@theme/TabItem";
24
- import { OPENAPI_REQUEST, OPENAPI_SCHEMA_ITEM } from "@theme/translationIds";
25
25
  import type { MediaTypeObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
26
26
 
27
27
  interface Props {
@@ -77,24 +77,23 @@ const RequestSchemaComponent: React.FC<Props> = ({ title, body, style }) => {
77
77
  open={true}
78
78
  style={style}
79
79
  summary={
80
- <>
81
- <summary>
82
- <h3 className="openapi-markdown__details-summary-header-body">
83
- {translate({
84
- id: OPENAPI_REQUEST.BODY_TITLE,
85
- message: title,
86
- })}
87
- {body.required === true && (
88
- <span className="openapi-schema__required">
89
- {translate({
90
- id: OPENAPI_SCHEMA_ITEM.REQUIRED,
91
- message: "required",
92
- })}
93
- </span>
94
- )}
95
- </h3>
96
- </summary>
97
- </>
80
+ <summary className="openapi-markdown__details-summary--with-control">
81
+ <h3 className="openapi-markdown__details-summary-header-body">
82
+ {translate({
83
+ id: "theme.openapi.request.body.title",
84
+ message: title,
85
+ })}
86
+ {body.required === true && (
87
+ <span className="openapi-schema__required">
88
+ {translate({
89
+ id: "theme.openapi.schemaItem.required",
90
+ message: "required",
91
+ })}
92
+ </span>
93
+ )}
94
+ </h3>
95
+ <SchemaExpansionControl />
96
+ </summary>
98
97
  }
99
98
  >
100
99
  <div style={{ textAlign: "left", marginLeft: "1rem" }}>
@@ -164,27 +163,26 @@ const RequestSchemaComponent: React.FC<Props> = ({ title, body, style }) => {
164
163
  open={true}
165
164
  style={style}
166
165
  summary={
167
- <>
168
- <summary>
169
- <h3 className="openapi-markdown__details-summary-header-body">
170
- {translate({
171
- id: OPENAPI_REQUEST.BODY_TITLE,
172
- message: title,
173
- })}
174
- {firstBody.type === "array" && (
175
- <span style={{ opacity: "0.6" }}> array</span>
176
- )}
177
- {body.required && (
178
- <strong className="openapi-schema__required">
179
- {translate({
180
- id: OPENAPI_SCHEMA_ITEM.REQUIRED,
181
- message: "required",
182
- })}
183
- </strong>
184
- )}
185
- </h3>
186
- </summary>
187
- </>
166
+ <summary className="openapi-markdown__details-summary--with-control">
167
+ <h3 className="openapi-markdown__details-summary-header-body">
168
+ {translate({
169
+ id: "theme.openapi.request.body.title",
170
+ message: title,
171
+ })}
172
+ {firstBody.type === "array" && (
173
+ <span style={{ opacity: "0.6" }}> array</span>
174
+ )}
175
+ {body.required && (
176
+ <strong className="openapi-schema__required">
177
+ {translate({
178
+ id: "theme.openapi.schemaItem.required",
179
+ message: "required",
180
+ })}
181
+ </strong>
182
+ )}
183
+ </h3>
184
+ <SchemaExpansionControl />
185
+ </summary>
188
186
  }
189
187
  >
190
188
  <div style={{ textAlign: "left", marginLeft: "1rem" }}>
@@ -11,7 +11,6 @@ import { translate } from "@docusaurus/Translate";
11
11
  import CodeSamples from "@theme/CodeSamples";
12
12
  import Markdown from "@theme/Markdown";
13
13
  import TabItem from "@theme/TabItem";
14
- import { OPENAPI_RESPONSE_EXAMPLES } from "@theme/translationIds";
15
14
  import { sampleResponseFromSchema } from "docusaurus-plugin-openapi-docs/lib/openapi/createResponseExample";
16
15
  import format from "xml-formatter";
17
16
 
@@ -115,7 +114,7 @@ export const ResponseExample: React.FC<ResponseExampleProps> = ({
115
114
  // @ts-ignore
116
115
  <TabItem
117
116
  label={translate({
118
- id: OPENAPI_RESPONSE_EXAMPLES.EXAMPLE,
117
+ id: "theme.openapi.responseExamples.example",
119
118
  message: "Example",
120
119
  })}
121
120
  value="Example"
@@ -173,7 +172,7 @@ export const ExampleFromSchema: React.FC<ExampleFromSchemaProps> = ({
173
172
  // @ts-ignore
174
173
  <TabItem
175
174
  label={translate({
176
- id: OPENAPI_RESPONSE_EXAMPLES.AUTO_EXAMPLE,
175
+ id: "theme.openapi.responseExamples.autoExample",
177
176
  message: "Example (auto)",
178
177
  })}
179
178
  value="Example (auto)"
@@ -192,7 +191,7 @@ export const ExampleFromSchema: React.FC<ExampleFromSchemaProps> = ({
192
191
  // @ts-ignore
193
192
  <TabItem
194
193
  label={translate({
195
- id: OPENAPI_RESPONSE_EXAMPLES.AUTO_EXAMPLE,
194
+ id: "theme.openapi.responseExamples.autoExample",
196
195
  message: "Example (auto)",
197
196
  })}
198
197
  value="Example (auto)"
@@ -18,10 +18,10 @@ import {
18
18
  ResponseExamples,
19
19
  } from "@theme/ResponseExamples";
20
20
  import SchemaNode from "@theme/Schema";
21
+ import SchemaExpansionControl from "@theme/SchemaExpansion";
21
22
  import SchemaTabs from "@theme/SchemaTabs";
22
23
  import SkeletonLoader from "@theme/SkeletonLoader";
23
24
  import TabItem from "@theme/TabItem";
24
- import { OPENAPI_SCHEMA, OPENAPI_SCHEMA_ITEM } from "@theme/translationIds";
25
25
  import type { MediaTypeObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
26
26
 
27
27
  interface Props {
@@ -71,7 +71,7 @@ const ResponseSchemaComponent: React.FC<Props> = ({
71
71
  <TabItem key={mimeType} label={mimeType} value={mimeType}>
72
72
  <div>
73
73
  {translate({
74
- id: OPENAPI_SCHEMA.NO_SCHEMA,
74
+ id: "theme.openapi.schema.noSchema",
75
75
  message: "No schema",
76
76
  })}
77
77
  </div>
@@ -91,21 +91,20 @@ const ResponseSchemaComponent: React.FC<Props> = ({
91
91
  open={true}
92
92
  style={style}
93
93
  summary={
94
- <>
95
- <summary>
96
- <strong className="openapi-markdown__details-summary-response">
97
- {title}
98
- {body.required === true && (
99
- <span className="openapi-schema__required">
100
- {translate({
101
- id: OPENAPI_SCHEMA_ITEM.REQUIRED,
102
- message: "required",
103
- })}
104
- </span>
105
- )}
106
- </strong>
107
- </summary>
108
- </>
94
+ <summary className="openapi-markdown__details-summary--with-control">
95
+ <strong className="openapi-markdown__details-summary-response">
96
+ {title}
97
+ {body.required === true && (
98
+ <span className="openapi-schema__required">
99
+ {translate({
100
+ id: "theme.openapi.schemaItem.required",
101
+ message: "required",
102
+ })}
103
+ </span>
104
+ )}
105
+ </strong>
106
+ <SchemaExpansionControl />
107
+ </summary>
109
108
  }
110
109
  >
111
110
  <div style={{ textAlign: "left", marginLeft: "1rem" }}>
@@ -14,10 +14,14 @@ import { ClosingArrayBracket, OpeningArrayBracket } from "@theme/ArrayBrackets";
14
14
  import Details from "@theme/Details";
15
15
  import DiscriminatorTabs from "@theme/DiscriminatorTabs";
16
16
  import Markdown from "@theme/Markdown";
17
+ import {
18
+ SchemaDepthProvider,
19
+ useSchemaDepth,
20
+ useSchemaExpansion,
21
+ } from "@theme/SchemaExpansion";
17
22
  import SchemaItem from "@theme/SchemaItem";
18
23
  import SchemaTabs from "@theme/SchemaTabs";
19
24
  import TabItem from "@theme/TabItem";
20
- import { OPENAPI_SCHEMA_ITEM } from "@theme/translationIds";
21
25
  // eslint-disable-next-line import/no-extraneous-dependencies
22
26
  import { merge } from "allof-merge";
23
27
  import clsx from "clsx";
@@ -29,16 +33,107 @@ import type { SchemaObject } from "../../types.d";
29
33
  // eslint-disable-next-line import/no-extraneous-dependencies
30
34
  // const jsonSchemaMergeAllOf = require("json-schema-merge-allof");
31
35
 
36
+ /**
37
+ * Strip `additionalProperties: false` from sibling allOf members so the
38
+ * strict-AND semantics of `allof-merge` don't collapse the result to an
39
+ * unsatisfiable empty schema.
40
+ *
41
+ * Per JSON Schema, two allOf members that each set `additionalProperties: false`
42
+ * with disjoint `properties` sets define an unsatisfiable schema (no value can
43
+ * satisfy both — each member rejects the other's properties). `allof-merge` is
44
+ * technically correct to drop all properties in that case, but it leaves the
45
+ * rendered schema blank.
46
+ *
47
+ * NSwag and Swashbuckle emit this pattern by default whenever a model uses
48
+ * inheritance/composition, so it's the dominant style for .NET-generated specs.
49
+ * Redoc, Swagger UI, and Stoplight all union the properties and ignore the
50
+ * conflicting flag — the approach this helper emulates by stripping the flag
51
+ * before delegating to `allof-merge`. The flag is render-irrelevant anyway:
52
+ * `additionalProperties: false` is treated identically to `undefined` by the
53
+ * AdditionalProperties component below (line ~641).
54
+ *
55
+ * Strips from every allOf member whenever the parent has ≥2 members. The
56
+ * collapse triggers symmetrically (both siblings strict) or asymmetrically
57
+ * (one strict member rejects another's properties as "additional"), so the
58
+ * presence of multiple members is the right gate. Single-member allOf is left
59
+ * alone — it can't conflict with anything.
60
+ *
61
+ * See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1119
62
+ * Mirrored in plugin: docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
63
+ */
64
+ const stripConflictingAdditionalProps = (node: any): any => {
65
+ if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
66
+ if (!node || typeof node !== "object") return node;
67
+
68
+ let working: any = node;
69
+ if (Array.isArray(node.allOf) && node.allOf.length > 1) {
70
+ const hasStrictMember = node.allOf.some(
71
+ (m: any) => m && m.additionalProperties === false
72
+ );
73
+ if (hasStrictMember) {
74
+ working = {
75
+ ...node,
76
+ allOf: node.allOf.map((m: any) => {
77
+ if (m && m.additionalProperties === false) {
78
+ const { additionalProperties: _drop, ...rest } = m;
79
+ return rest;
80
+ }
81
+ return m;
82
+ }),
83
+ };
84
+ }
85
+ }
86
+
87
+ const result: any = {};
88
+ for (const [k, v] of Object.entries(working)) {
89
+ result[k] = stripConflictingAdditionalProps(v);
90
+ }
91
+ return result;
92
+ };
93
+
32
94
  const mergeAllOf = (allOf: any) => {
33
95
  const onMergeError = (msg: string) => {
34
96
  console.warn(msg);
35
97
  };
36
98
 
37
- const mergedSchemas = merge(allOf, { onMergeError });
99
+ const mergedSchemas = merge(stripConflictingAdditionalProps(allOf), {
100
+ onMergeError,
101
+ });
38
102
 
39
103
  return mergedSchemas ?? {};
40
104
  };
41
105
 
106
+ /**
107
+ * Fold sibling fields into each `oneOf`/`anyOf` branch via allOf-merge, so each
108
+ * branch is self-contained. Mirrors Redoc's `SchemaModel.initOneOf` behavior.
109
+ * Without this, when an `allOf` override redefines a nested property with
110
+ * `oneOf`, the merged schema ends up with both `properties` and `oneOf` as
111
+ * siblings — and the renderer prints the shared properties twice.
112
+ *
113
+ * See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1218
114
+ */
115
+ const foldSiblingsIntoBranches = (schema: any): any => {
116
+ const branchKey = schema?.oneOf
117
+ ? "oneOf"
118
+ : schema?.anyOf
119
+ ? "anyOf"
120
+ : undefined;
121
+ if (!branchKey) return schema;
122
+
123
+ const branches = schema[branchKey];
124
+ if (!Array.isArray(branches) || branches.length === 0) return schema;
125
+
126
+ const siblings = { ...schema };
127
+ delete siblings[branchKey];
128
+ if (Object.keys(siblings).length === 0) return schema;
129
+
130
+ const folded = branches.map((branch: any) =>
131
+ mergeAllOf({ allOf: [siblings, branch] })
132
+ );
133
+
134
+ return { [branchKey]: folded };
135
+ };
136
+
42
137
  /**
43
138
  * Recursively searches for a property in a schema, including nested
44
139
  * oneOf, anyOf, and allOf structures. This is needed for discriminators
@@ -165,7 +260,7 @@ const Summary: React.FC<SummaryProps> = ({
165
260
  {nullable && (
166
261
  <span className="openapi-schema__nullable">
167
262
  {translate({
168
- id: OPENAPI_SCHEMA_ITEM.NULLABLE,
263
+ id: "theme.openapi.schemaItem.nullable",
169
264
  message: "nullable",
170
265
  })}
171
266
  </span>
@@ -173,7 +268,7 @@ const Summary: React.FC<SummaryProps> = ({
173
268
  {isRequired && (
174
269
  <span className="openapi-schema__required">
175
270
  {translate({
176
- id: OPENAPI_SCHEMA_ITEM.REQUIRED,
271
+ id: "theme.openapi.schemaItem.required",
177
272
  message: "required",
178
273
  })}
179
274
  </span>
@@ -181,7 +276,7 @@ const Summary: React.FC<SummaryProps> = ({
181
276
  {deprecated && (
182
277
  <span className="openapi-schema__deprecated">
183
278
  {translate({
184
- id: OPENAPI_SCHEMA_ITEM.DEPRECATED,
279
+ id: "theme.openapi.schemaItem.deprecated",
185
280
  message: "deprecated",
186
281
  })}
187
282
  </span>
@@ -218,8 +313,8 @@ const AnyOneOf: React.FC<SchemaProps> = ({
218
313
  }
219
314
 
220
315
  const type = schema.oneOf
221
- ? translate({ id: OPENAPI_SCHEMA_ITEM.ONE_OF, message: "oneOf" })
222
- : translate({ id: OPENAPI_SCHEMA_ITEM.ANY_OF, message: "anyOf" });
316
+ ? translate({ id: "theme.openapi.schemaItem.oneOf", message: "oneOf" })
317
+ : translate({ id: "theme.openapi.schemaItem.anyOf", message: "anyOf" });
223
318
 
224
319
  // Generate a unique ID for this anyOf/oneOf to prevent tab value collisions
225
320
  const uniqueId = React.useMemo(
@@ -268,12 +363,12 @@ const AnyOneOf: React.FC<SchemaProps> = ({
268
363
  if (!label) {
269
364
  if (anyOneSchema.oneOf) {
270
365
  label = translate({
271
- id: OPENAPI_SCHEMA_ITEM.ONE_OF,
366
+ id: "theme.openapi.schemaItem.oneOf",
272
367
  message: "oneOf",
273
368
  });
274
369
  } else if (anyOneSchema.anyOf) {
275
370
  label = translate({
276
- id: OPENAPI_SCHEMA_ITEM.ANY_OF,
371
+ id: "theme.openapi.schemaItem.anyOf",
277
372
  message: "anyOf",
278
373
  });
279
374
  } else {
@@ -454,7 +549,7 @@ const PropertyDiscriminator: React.FC<SchemaEdgeProps> = ({
454
549
  {required && (
455
550
  <span className="openapi-schema__required">
456
551
  {translate({
457
- id: OPENAPI_SCHEMA_ITEM.REQUIRED,
552
+ id: "theme.openapi.schemaItem.required",
458
553
  message: "required",
459
554
  })}
460
555
  </span>
@@ -676,10 +771,15 @@ const SchemaNodeDetails: React.FC<SchemaEdgeProps> = ({
676
771
  schemaType,
677
772
  schemaPath,
678
773
  }) => {
774
+ const depth = useSchemaDepth();
775
+ const { level } = useSchemaExpansion();
776
+ const defaultOpen = depth < level;
679
777
  return (
680
778
  <SchemaItem collapsible={true}>
681
779
  <Details
780
+ key={`level-${level}`}
682
781
  className="openapi-markdown__details"
782
+ open={defaultOpen}
683
783
  summary={
684
784
  <Summary
685
785
  name={name}
@@ -694,11 +794,13 @@ const SchemaNodeDetails: React.FC<SchemaEdgeProps> = ({
694
794
  {getQualifierMessage(schema) && (
695
795
  <MarkdownWrapper text={getQualifierMessage(schema)} />
696
796
  )}
697
- <SchemaNode
698
- schema={schema}
699
- schemaType={schemaType}
700
- schemaPath={schemaPath}
701
- />
797
+ <SchemaDepthProvider depth={depth + 1}>
798
+ <SchemaNode
799
+ schema={schema}
800
+ schemaType={schemaType}
801
+ schemaPath={schemaPath}
802
+ />
803
+ </SchemaDepthProvider>
702
804
  </div>
703
805
  </Details>
704
806
  </SchemaItem>
@@ -725,17 +827,25 @@ const Items: React.FC<{
725
827
  const itemsSchemaPath = schemaPath ? `${schemaPath}.items` : undefined;
726
828
 
727
829
  if (hasOneOfAnyOf || hasProperties || hasAdditionalProperties) {
830
+ // Fold sibling properties into each oneOf/anyOf branch to avoid duplicate
831
+ // property rendering. See issue #1218.
832
+ const renderSchema =
833
+ hasOneOfAnyOf && hasProperties
834
+ ? foldSiblingsIntoBranches(itemsSchema)
835
+ : itemsSchema;
836
+ const renderHasProperties =
837
+ hasOneOfAnyOf && hasProperties ? false : hasProperties;
728
838
  return (
729
839
  <>
730
840
  <OpeningArrayBracket />
731
841
  {hasOneOfAnyOf && (
732
842
  <AnyOneOf
733
- schema={itemsSchema}
843
+ schema={renderSchema}
734
844
  schemaType={schemaType}
735
845
  schemaPath={itemsSchemaPath}
736
846
  />
737
847
  )}
738
- {hasProperties && (
848
+ {renderHasProperties && (
739
849
  <Properties
740
850
  schema={itemsSchema}
741
851
  schemaType={schemaType}
@@ -1020,23 +1130,31 @@ function renderChildren(
1020
1130
  schemaType: "request" | "response",
1021
1131
  schemaPath?: string
1022
1132
  ) {
1133
+ // Fold sibling properties into each oneOf/anyOf branch to avoid duplicate
1134
+ // property rendering. See issue #1218.
1135
+ const hasOneOfAnyOf = !!(schema.oneOf || schema.anyOf);
1136
+ const hasProperties = !!schema.properties;
1137
+ const folded =
1138
+ hasOneOfAnyOf && hasProperties ? foldSiblingsIntoBranches(schema) : schema;
1139
+ const renderProperties =
1140
+ hasOneOfAnyOf && hasProperties ? false : hasProperties;
1023
1141
  return (
1024
1142
  <>
1025
- {schema.oneOf && (
1143
+ {folded.oneOf && (
1026
1144
  <AnyOneOf
1027
- schema={schema}
1145
+ schema={folded}
1028
1146
  schemaType={schemaType}
1029
1147
  schemaPath={schemaPath}
1030
1148
  />
1031
1149
  )}
1032
- {schema.anyOf && (
1150
+ {folded.anyOf && (
1033
1151
  <AnyOneOf
1034
- schema={schema}
1152
+ schema={folded}
1035
1153
  schemaType={schemaType}
1036
1154
  schemaPath={schemaPath}
1037
1155
  />
1038
1156
  )}
1039
- {schema.properties && (
1157
+ {renderProperties && (
1040
1158
  <Properties
1041
1159
  schema={schema}
1042
1160
  schemaType={schemaType}
@@ -1160,23 +1278,34 @@ const SchemaNode: React.FC<SchemaProps> = ({
1160
1278
  return null;
1161
1279
  }
1162
1280
 
1281
+ // Fold sibling properties into each oneOf/anyOf branch to avoid duplicate
1282
+ // property rendering. See issue #1218.
1283
+ const hasOneOfAnyOf = !!(mergedSchemas.oneOf || mergedSchemas.anyOf);
1284
+ const hasProperties = !!mergedSchemas.properties;
1285
+ const folded =
1286
+ hasOneOfAnyOf && hasProperties
1287
+ ? foldSiblingsIntoBranches(mergedSchemas)
1288
+ : mergedSchemas;
1289
+ const renderProperties =
1290
+ hasOneOfAnyOf && hasProperties ? false : hasProperties;
1291
+
1163
1292
  return (
1164
1293
  <div>
1165
- {mergedSchemas.oneOf && (
1294
+ {folded.oneOf && (
1166
1295
  <AnyOneOf
1167
- schema={mergedSchemas}
1296
+ schema={folded}
1168
1297
  schemaType={schemaType}
1169
1298
  schemaPath={schemaPath}
1170
1299
  />
1171
1300
  )}
1172
- {mergedSchemas.anyOf && (
1301
+ {folded.anyOf && (
1173
1302
  <AnyOneOf
1174
- schema={mergedSchemas}
1303
+ schema={folded}
1175
1304
  schemaType={schemaType}
1176
1305
  schemaPath={schemaPath}
1177
1306
  />
1178
1307
  )}
1179
- {mergedSchemas.properties && (
1308
+ {renderProperties && (
1180
1309
  <Properties
1181
1310
  schema={mergedSchemas}
1182
1311
  schemaType={schemaType}