fumadocs-openapi 9.1.0 → 9.1.2

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.
@@ -142,43 +142,48 @@ function SecurityTabs({ securities, setSecurityId, securityId, children, }) {
142
142
  }
143
143
  return result;
144
144
  }
145
- const paramNames = ['Headers', 'Cookies', 'Query', 'Path'];
146
- const paramTypes = ['header', 'cookie', 'query', 'path'];
145
+ const ParamTypes = ['path', 'header', 'cookie', 'query'];
147
146
  function FormBody({ parameters = [], fields = {}, body, }) {
148
- const params = useMemo(() => {
149
- return paramTypes.map((param) => parameters.filter((v) => v.in === param));
150
- }, [parameters]);
151
- return (_jsxs(_Fragment, { children: [params.map((param, i) => {
152
- if (param.length === 0)
153
- return;
154
- const name = paramNames[i];
155
- const type = paramTypes[i];
156
- return (_jsx(CollapsiblePanel, { title: name, children: param.map((field) => {
157
- const fieldName = `${type}.${field.name}`;
158
- const schema = (field.content
159
- ? field.content[Object.keys(field.content)[0]].schema
160
- : field.schema);
161
- if (fields?.parameter) {
162
- return renderCustomField(fieldName, schema, fields.parameter, field.name);
163
- }
164
- return (_jsx(FieldSet, { name: field.name, fieldName: fieldName, field: schema }, fieldName));
165
- }) }, name));
166
- }), body && (_jsx(CollapsiblePanel, { title: "Body", children: fields.body ? (renderCustomField('body', body.schema, fields.body)) : (_jsx(BodyInput, { field: body.schema })) }))] }));
147
+ const panels = useMemo(() => {
148
+ return ParamTypes.map((type) => {
149
+ const items = parameters.filter((v) => v.in === type);
150
+ if (items.length === 0)
151
+ return;
152
+ return (_jsx(CollapsiblePanel, { title: {
153
+ header: 'Header',
154
+ cookie: 'Cookies',
155
+ query: 'Query',
156
+ path: 'Path',
157
+ }[type], children: items.map((field) => {
158
+ const fieldName = `${type}.${field.name}`;
159
+ const schema = (field.content
160
+ ? field.content[Object.keys(field.content)[0]].schema
161
+ : field.schema);
162
+ if (fields?.parameter) {
163
+ return renderCustomField(fieldName, schema, fields.parameter, field.name);
164
+ }
165
+ return (_jsx(FieldSet, { name: field.name, fieldName: fieldName, field: schema }, fieldName));
166
+ }) }, type));
167
+ });
168
+ }, [fields.parameter, parameters]);
169
+ return (_jsxs(_Fragment, { children: [panels, body && (_jsx(CollapsiblePanel, { title: "Body", children: fields.body ? (renderCustomField('body', body.schema, fields.body)) : (_jsx(BodyInput, { field: body.schema })) }))] }));
167
170
  }
168
171
  function BodyInput({ field: _field }) {
169
172
  const field = useResolvedSchema(_field);
170
173
  const [isJson, setIsJson] = useState(false);
171
174
  if (field.format === 'binary')
172
175
  return _jsx(FieldSet, { field: field, fieldName: "body" });
173
- return (_jsx(_Fragment, { children: isJson ? (_jsxs(_Fragment, { children: [_jsx("button", { className: cn(buttonVariants({
176
+ if (isJson)
177
+ return (_jsxs(_Fragment, { children: [_jsx("button", { className: cn(buttonVariants({
174
178
  color: 'secondary',
175
179
  size: 'sm',
176
180
  className: 'w-fit font-mono p-2',
177
- })), onClick: () => setIsJson(false), type: "button", children: "Close JSON Editor" }), _jsx(JsonInput, { fieldName: "body" })] })) : (_jsx(FieldSet, { field: field, fieldName: "body", collapsible: false, name: _jsx("button", { className: cn(buttonVariants({
178
- color: 'secondary',
179
- size: 'sm',
180
- className: 'p-2',
181
- })), onClick: () => setIsJson(true), type: "button", children: "Open JSON Editor" }) })) }));
181
+ })), onClick: () => setIsJson(false), type: "button", children: "Close JSON Editor" }), _jsx(JsonInput, { fieldName: "body" })] }));
182
+ return (_jsx(FieldSet, { field: field, fieldName: "body", collapsible: false, name: _jsx("button", { type: "button", className: cn(buttonVariants({
183
+ color: 'secondary',
184
+ size: 'sm',
185
+ className: 'p-2',
186
+ })), onClick: () => setIsJson(true), children: "Open JSON Editor" }) }));
182
187
  }
183
188
  /**
184
189
  * manipulate values without mutating the original object
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/playground/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,oBAAoB,EACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,eAAe,CAAC,GAAG;IAC1D,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC;AAQzC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,GAAG,EAAE,aAAa,CAAC;IAEnB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC/B;AAED,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG;IACjD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,MAAM,EACN,GAAG,EACH,MAAM,GACP,EAAE,kBAAkB,oDAwCpB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/playground/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,oBAAoB,EACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,eAAe,CAAC,GAAG;IAC1D,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC;AAQzC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,GAAG,EAAE,aAAa,CAAC;IAEnB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC/B;AAED,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG;IACjD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,MAAM,EACN,GAAG,EACH,MAAM,GACP,EAAE,kBAAkB,oDAkCpB"}
@@ -16,10 +16,7 @@ export async function APIPlayground({ path, method, ctx, client, }) {
16
16
  securities: parseSecurities(method, ctx),
17
17
  method: method.method,
18
18
  route: path,
19
- parameters: method.parameters?.map((v) => ({
20
- ...v,
21
- schema: writeReferences((v.schema ?? true), context),
22
- })),
19
+ parameters: method.parameters,
23
20
  body: bodyContent && mediaType
24
21
  ? {
25
22
  schema: writeReferences(bodyContent[mediaType].schema, context),
@@ -1 +1 @@
1
- {"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../src/playground/inputs.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAkDxD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EAAE,MAAM,EACb,SAAS,EACT,GAAG,KAAK,EACT,EAAE;IACD,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,cAAc,CAAC,KAAK,CAAC,uDAwCxB;AAED,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,2CA8B7D;AA6FD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,SAAS,EACT,UAAU,EACV,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;IAC/B,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,uDA6FA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EAAE,MAAM,EACb,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,KAAS,EACT,QAAQ,EACR,WAAkB,EAClB,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,kDA2KA"}
1
+ {"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../src/playground/inputs.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAkDxD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EAAE,MAAM,EACb,SAAS,EACT,GAAG,KAAK,EACT,EAAE;IACD,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,cAAc,CAAC,KAAK,CAAC,uDAwCxB;AAED,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,2CA8B7D;AA6FD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,SAAS,EACT,UAAU,EACV,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;IAC/B,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,uDA6FA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EAAE,MAAM,EACb,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,KAAS,EACT,QAAQ,EACR,WAAkB,EAClB,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,kDA4KA"}
@@ -85,7 +85,7 @@ function DynamicProperties({ fieldName, filterKey = () => true, getType = () =>
85
85
  setProperties((p) => p.filter((prop) => prop !== item));
86
86
  control.unregister(`${fieldName}.${item}`);
87
87
  }, children: _jsx(Trash2, {}) }) }, item));
88
- }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: nextName, placeholder: "Enter Property Name", onChange: (e) => setNextName(e.target.value), onKeyDown: (e) => {
88
+ }), _jsxs("div", { className: "flex gap-2 col-span-full", children: [_jsx(Input, { value: nextName, placeholder: "Enter Property Name", onChange: (e) => setNextName(e.target.value), onKeyDown: (e) => {
89
89
  if (e.key === 'Enter') {
90
90
  onAppend();
91
91
  e.preventDefault();
@@ -119,13 +119,14 @@ export function FieldSet({ field: _field, fieldName, toolbar, name, isRequired,
119
119
  const { info, updateInfo } = useFieldInfo(fieldName, field, depth);
120
120
  if (_field === false)
121
121
  return null;
122
- if (field.oneOf) {
123
- const showSelect = field.oneOf.length > 1;
124
- return (_jsx(FieldSet, { ...props, name: name, fieldName: fieldName, isRequired: isRequired, field: field.oneOf[info.oneOf], depth: depth + 1, slotType: showSelect ? false : slotType, toolbar: _jsxs(_Fragment, { children: [showSelect && (_jsx("select", { className: "text-xs font-mono", value: info.oneOf, onChange: (e) => {
122
+ if (info.unionField) {
123
+ const union = field[info.unionField];
124
+ const showSelect = union.length > 1;
125
+ return (_jsx(FieldSet, { ...props, name: name, fieldName: fieldName, isRequired: isRequired, field: union[info.oneOf], depth: depth + 1, slotType: showSelect ? false : slotType, toolbar: _jsxs(_Fragment, { children: [showSelect && (_jsx("select", { className: "text-xs font-mono", value: info.oneOf, onChange: (e) => {
125
126
  updateInfo({
126
127
  oneOf: Number(e.target.value),
127
128
  });
128
- }, children: field.oneOf.map((item, i) => (_jsx("option", { value: i, children: schemaToString(item) }, i))) })), toolbar] }) }));
129
+ }, children: union.map((item, i) => (_jsx("option", { value: i, children: schemaToString(item) }, i))) })), toolbar] }) }));
129
130
  }
130
131
  if (Array.isArray(field.type)) {
131
132
  const showSelect = field.type.length > 1;
@@ -6,9 +6,14 @@ interface SchemaContextType {
6
6
  fieldInfoMap: Map<string, FieldInfo>;
7
7
  ajv: Ajv2020;
8
8
  }
9
+ type UnionField = 'anyOf' | 'allOf' | 'oneOf';
9
10
  export interface FieldInfo {
10
11
  selectedType?: string;
11
12
  oneOf: number;
13
+ /**
14
+ * The actual field that represents union members.
15
+ */
16
+ unionField?: UnionField;
12
17
  }
13
18
  export declare const anyFields: {
14
19
  type: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/playground/schema.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAiB,SAAS,EAAiC,MAAM,OAAO,CAAC;AAKhF,UAAU,iBAAiB;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACrC,GAAG,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,eAAO,MAAM,SAAS;;;;CAIG,CAAC;AAE1B,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAsB1D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,EACvC,KAAK,EAAE,MAAM,GACZ;IACD,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;CACjD,CAiEA;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAQjC;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAEjC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/playground/schema.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAiB,SAAS,EAAiC,MAAM,OAAO,CAAC;AAMhF,UAAU,iBAAiB;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACrC,GAAG,EAAE,OAAO,CAAC;CACd;AAED,KAAK,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAGD,eAAO,MAAM,SAAS;;;;CAIG,CAAC;AAE1B,wBAAgB,cAAc,CAAC,EAC7B,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAsB1D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,EACvC,KAAK,EAAE,MAAM,GACZ;IACD,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;CACjD,CA8FA;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAQjC;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAEjC"}
@@ -28,50 +28,79 @@ export function SchemaProvider({ references, fieldInfoMap, children, }) {
28
28
  */
29
29
  export function useFieldInfo(fieldName, schema, depth) {
30
30
  const { fieldInfoMap, ajv } = useContext(SchemaContext);
31
- const [_, trigger] = useState(0);
32
31
  const form = useFormContext();
33
32
  const keyName = `${fieldName}:${depth}`;
34
33
  const value = form.getValues(fieldName);
35
- const info = fieldInfoMap.get(keyName) ?? {
36
- oneOf: -1,
37
- };
38
- // update info if needed
39
- if (!info.selectedType && Array.isArray(schema.type)) {
40
- info.selectedType =
41
- schema.type.find((type) => ajv.validate({
42
- ...schema,
43
- type,
44
- }, value)) ?? schema.type[0];
45
- }
46
- if (info.oneOf === -1 && schema.oneOf) {
47
- info.oneOf = schema.oneOf.findIndex((item) => {
48
- return ajv.validate(item, value);
34
+ const [info, setInfo] = useState(() => {
35
+ return fieldInfoMap.get(keyName) ?? init();
36
+ });
37
+ fieldInfoMap.set(keyName, info);
38
+ /**
39
+ * We automatically merge `allOf` | `anyOf` if all members are objects, but it's also possible for them to behave same as a union (`oneOf`).
40
+ */
41
+ function isUnion(anyOrAllOf) {
42
+ return anyOrAllOf.every((item) => {
43
+ if (typeof item === 'boolean')
44
+ return true;
45
+ const u = item.anyOf || item.allOf;
46
+ return item.type !== 'object' && (!u || isUnion(u));
49
47
  });
50
- if (info.oneOf === -1)
51
- info.oneOf = 0;
52
48
  }
53
- fieldInfoMap.set(keyName, info);
49
+ function getUnion() {
50
+ if (schema.anyOf && isUnion(schema.anyOf)) {
51
+ return [schema.anyOf, 'anyOf'];
52
+ }
53
+ if (schema.allOf && isUnion(schema.allOf)) {
54
+ return [schema.allOf, 'allOf'];
55
+ }
56
+ if (schema.oneOf)
57
+ return [schema.oneOf, 'oneOf'];
58
+ }
59
+ function init() {
60
+ const union = getUnion();
61
+ if (union) {
62
+ const [members, field] = union;
63
+ let oneOf = members.findIndex((item) => ajv.validate(item, value));
64
+ if (oneOf === -1)
65
+ oneOf = 0;
66
+ return {
67
+ oneOf,
68
+ unionField: field,
69
+ };
70
+ }
71
+ if (Array.isArray(schema.type)) {
72
+ const types = schema.type;
73
+ return {
74
+ selectedType: types.find((type) => {
75
+ schema.type = type;
76
+ const match = ajv.validate(schema, value);
77
+ schema.type = types;
78
+ return match;
79
+ }) ?? types[0],
80
+ oneOf: -1,
81
+ };
82
+ }
83
+ return { oneOf: -1 };
84
+ }
54
85
  return {
55
86
  info,
56
87
  updateInfo: useEffectEvent((value) => {
57
- const prev = fieldInfoMap.get(keyName);
58
- if (!prev)
59
- return;
60
88
  const updated = {
61
- ...prev,
89
+ ...info,
62
90
  ...value,
63
91
  };
64
- if (updated.oneOf === prev.oneOf &&
65
- updated.selectedType === prev.selectedType)
92
+ if (updated.oneOf === info.oneOf &&
93
+ updated.selectedType === info.selectedType)
66
94
  return;
67
- fieldInfoMap.set(keyName, updated);
68
- form.setValue(fieldName, getDefaultValue(schema.oneOf && updated.oneOf !== -1
69
- ? schema.oneOf[updated.oneOf]
70
- : {
71
- ...schema,
72
- type: value.selectedType ?? schema.type,
73
- }));
74
- trigger((prev) => prev + 1);
95
+ setInfo(updated);
96
+ let valueSchema = schema;
97
+ if (updated.unionField) {
98
+ valueSchema = schema[updated.unionField][updated.oneOf];
99
+ }
100
+ else if (updated.selectedType) {
101
+ valueSchema = { ...schema, type: updated.selectedType };
102
+ }
103
+ form.setValue(fieldName, getDefaultValue(valueSchema));
75
104
  }),
76
105
  };
77
106
  }
@@ -87,7 +87,7 @@ async function ResponseAccordion({ status, operation, ctx, }) {
87
87
  const contentTypes = response.content
88
88
  ? Object.entries(response.content)
89
89
  : null;
90
- return (_jsxs(SelectTabs, { defaultValue: contentTypes?.[0][0], children: [_jsxs(AccordionHeader, { children: [_jsx(AccordionTrigger, { className: "font-mono", children: status }), contentTypes && (_jsx(SelectTabTrigger, { items: contentTypes.map((v) => v[0]) }))] }), _jsxs(AccordionContent, { children: [response.description && (_jsx("div", { className: "prose-no-margin", children: _jsx(Markdown, { text: response.description }) })), contentTypes?.map(async ([type, resType]) => {
90
+ return (_jsxs(SelectTabs, { defaultValue: contentTypes?.[0][0], children: [_jsxs(AccordionHeader, { children: [_jsx(AccordionTrigger, { className: "font-mono", children: status }), contentTypes && (_jsx(SelectTabTrigger, { items: contentTypes.map((v) => v[0]) }))] }), _jsxs(AccordionContent, { className: "ps-4.5", children: [response.description && (_jsx("div", { className: "prose-no-margin", children: _jsx(Markdown, { text: response.description }) })), contentTypes?.map(async ([type, resType]) => {
91
91
  const schema = resType.schema;
92
92
  let ts;
93
93
  if (generateTypeScriptSchema) {
@@ -96,7 +96,7 @@ async function ResponseAccordion({ status, operation, ctx, }) {
96
96
  else if (generateTypeScriptSchema === undefined && schema) {
97
97
  ts = await getTypescriptSchema(schema, dereferenceMap);
98
98
  }
99
- return (_jsxs(SelectTab, { value: type, className: "mt-2", children: [ts && _jsx(CopyResponseTypeScript, { code: ts }), schema && (_jsx("div", { className: "border px-3 rounded-lg my-2 overflow-auto max-h-[400px]", children: _jsx(Schema, { name: "response", schema: schema, as: "body", readOnly: true, ctx: ctx }) }))] }, type));
99
+ return (_jsxs(SelectTab, { value: type, className: "my-2", children: [ts && _jsx(CopyResponseTypeScript, { code: ts }), schema && (_jsx("div", { className: "border px-3 py-2 rounded-lg overflow-auto max-h-[400px]", children: _jsx(Schema, { name: "response", schema: schema, as: "body", readOnly: true, ctx: ctx }) }))] }, type));
100
100
  })] })] }));
101
101
  }
102
102
  function WebhookCallback({ callback, ctx, headingLevel, }) {
@@ -10,7 +10,7 @@ export function AccordionItem(props) {
10
10
  return (_jsx(Primitive.Item, { ...props, className: cn('scroll-m-20', props.className), children: props.children }));
11
11
  }
12
12
  export function AccordionContent(props) {
13
- return (_jsx(Primitive.Content, { ...props, className: cn('overflow-hidden px-1 data-[state=closed]:animate-fd-accordion-up data-[state=open]:animate-fd-accordion-down', props.className), children: props.children }));
13
+ return (_jsx(Primitive.Content, { ...props, className: cn('overflow-hidden data-[state=closed]:animate-fd-accordion-up data-[state=open]:animate-fd-accordion-down', props.className), children: props.children }));
14
14
  }
15
15
  export function AccordionHeader(props) {
16
16
  return (_jsx(Primitive.Header, { ...props, className: cn('not-prose flex py-2 text-fd-foreground font-medium', props.className), children: props.children }));
@@ -1 +1 @@
1
- {"version":3,"file":"schema-to-string.d.ts","sourceRoot":"","sources":["../../src/utils/schema-to-string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,EACrB,GAAG,CAAC,EAAE,iBAAiB,GACtB,MAAM,CA8DR"}
1
+ {"version":3,"file":"schema-to-string.d.ts","sourceRoot":"","sources":["../../src/utils/schema-to-string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,EACrB,GAAG,CAAC,EAAE,iBAAiB,GACtB,MAAM,CAmER"}
@@ -1,47 +1,52 @@
1
1
  export function schemaToString(value, ctx) {
2
- function run(schema, isRoot) {
2
+ function union(union, sep) {
3
+ const members = new Set();
4
+ let nullable = false;
5
+ for (const item of union) {
6
+ const result = run(item);
7
+ if (result === 'null') {
8
+ nullable = true;
9
+ }
10
+ else if (result !== 'unknown') {
11
+ members.add(result);
12
+ }
13
+ }
14
+ const result = Array.from(members).join(sep);
15
+ return nullable ? `${result} | null` : result;
16
+ }
17
+ function run(schema) {
3
18
  if (schema === true)
4
19
  return 'any';
5
20
  else if (schema === false)
6
21
  return 'never';
7
- if (isNullable(schema) && isRoot) {
8
- const type = run(schema, false);
9
- // null if schema only contains `nullable`
10
- return type === 'unknown' ? 'null' : `${type} | null`;
11
- }
12
22
  if (schema.title)
13
23
  return schema.title;
14
24
  const referenceName = ctx?.dereferenceMap.get(schema);
15
25
  if (referenceName)
16
26
  return referenceName.split('/').at(-1);
17
27
  if (Array.isArray(schema.type)) {
18
- return schema.type
19
- .map((type, _, originalType) => {
28
+ const members = new Set();
29
+ const types = schema.type;
30
+ for (const type of types) {
20
31
  schema.type = type;
21
- const str = run(schema, false);
22
- schema.type = originalType;
23
- return str;
24
- })
25
- .filter((v) => v !== 'unknown' && v !== 'null')
26
- .join(' | ');
32
+ const str = run(schema);
33
+ schema.type = types;
34
+ if (str !== 'unknown')
35
+ members.add(str);
36
+ }
37
+ return Array.from(members).join(' | ');
27
38
  }
28
39
  if (schema.type === 'array')
29
- return `array<${schema.items ? run(schema.items, true) : 'unknown'}>`;
40
+ return `array<${schema.items ? run(schema.items) : 'unknown'}>`;
30
41
  if (schema.oneOf) {
31
- return schema.oneOf
32
- .map((one) => run(one, false))
33
- .filter((v) => v !== 'unknown' && v !== 'null')
34
- .join(' | ');
42
+ return union(schema.oneOf, ' | ');
35
43
  }
36
44
  const combinedOf = schema.anyOf ?? schema.allOf;
37
45
  if (combinedOf) {
38
- return combinedOf
39
- .map((one) => run(one, false))
40
- .filter((v) => v !== 'unknown' && v !== 'null')
41
- .join(' & ');
46
+ return union(combinedOf, ' & ');
42
47
  }
43
48
  if (schema.not)
44
- return `not ${run(schema.not, false)}`;
49
+ return `not ${run(schema.not)}`;
45
50
  if (schema.type === 'string' && schema.format === 'binary')
46
51
  return 'file';
47
52
  if (schema.type && Array.isArray(schema.type)) {
@@ -52,15 +57,5 @@ export function schemaToString(value, ctx) {
52
57
  }
53
58
  return 'unknown';
54
59
  }
55
- return run(value, true);
56
- }
57
- function isNullable(schema) {
58
- if (typeof schema === 'boolean')
59
- return false;
60
- if (Array.isArray(schema.type) && schema.type.includes('null'))
61
- return true;
62
- const combined = schema.anyOf ?? schema.oneOf ?? schema.allOf;
63
- if (combined && combined.some(isNullable))
64
- return true;
65
- return schema.type === 'null';
60
+ return run(value);
66
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "9.1.0",
3
+ "version": "9.1.2",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -50,7 +50,7 @@
50
50
  "@radix-ui/react-dialog": "^1.1.14",
51
51
  "@radix-ui/react-select": "^2.2.5",
52
52
  "@radix-ui/react-slot": "^1.2.3",
53
- "@scalar/openapi-parser": "0.18.0",
53
+ "@scalar/openapi-parser": "0.18.1",
54
54
  "ajv": "^8.17.1",
55
55
  "class-variance-authority": "^0.7.1",
56
56
  "github-slugger": "^2.0.0",
@@ -58,23 +58,23 @@
58
58
  "js-yaml": "^4.1.0",
59
59
  "next-themes": "^0.4.6",
60
60
  "openapi-sampler": "^1.6.1",
61
- "react-hook-form": "^7.59.0",
61
+ "react-hook-form": "^7.60.0",
62
62
  "remark": "^15.0.1",
63
63
  "remark-rehype": "^11.1.2",
64
64
  "shiki": "^3.7.0",
65
65
  "tinyglobby": "^0.2.14",
66
66
  "xml-js": "^1.6.11",
67
- "fumadocs-core": "15.6.2",
68
- "fumadocs-ui": "15.6.2"
67
+ "fumadocs-core": "15.6.3",
68
+ "fumadocs-ui": "15.6.3"
69
69
  },
70
70
  "devDependencies": {
71
- "@scalar/api-client-react": "^1.3.18",
71
+ "@scalar/api-client-react": "^1.3.21",
72
72
  "@types/js-yaml": "^4.0.9",
73
- "@types/node": "24.0.7",
73
+ "@types/node": "24.0.13",
74
74
  "@types/openapi-sampler": "^1.0.3",
75
75
  "@types/react": "^19.1.8",
76
76
  "json-schema-typed": "^8.0.1",
77
- "next": "15.3.4",
77
+ "next": "15.3.5",
78
78
  "openapi-types": "^12.1.3",
79
79
  "tailwindcss": "^4.1.11",
80
80
  "tsc-alias": "^1.8.16",