fumadocs-openapi 8.1.2 → 8.1.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 (48) hide show
  1. package/css/preset.css +1 -0
  2. package/dist/generate-file.d.ts.map +1 -1
  3. package/dist/generate-file.js +40 -23
  4. package/dist/playground/client.d.ts +5 -16
  5. package/dist/playground/client.d.ts.map +1 -1
  6. package/dist/playground/client.js +39 -44
  7. package/dist/playground/get-default-values.d.ts +2 -2
  8. package/dist/playground/get-default-values.d.ts.map +1 -1
  9. package/dist/playground/get-default-values.js +21 -22
  10. package/dist/playground/index.d.ts +4 -38
  11. package/dist/playground/index.d.ts.map +1 -1
  12. package/dist/playground/index.js +43 -139
  13. package/dist/playground/inputs.d.ts +11 -13
  14. package/dist/playground/inputs.d.ts.map +1 -1
  15. package/dist/playground/inputs.js +72 -129
  16. package/dist/playground/schema.d.ts +38 -0
  17. package/dist/playground/schema.d.ts.map +1 -0
  18. package/dist/playground/schema.js +93 -0
  19. package/dist/render/operation/get-request-data.d.ts +1 -2
  20. package/dist/render/operation/get-request-data.d.ts.map +1 -1
  21. package/dist/render/operation/get-request-data.js +2 -2
  22. package/dist/render/operation/index.d.ts.map +1 -1
  23. package/dist/render/operation/index.js +5 -5
  24. package/dist/render/schema.d.ts +3 -3
  25. package/dist/render/schema.d.ts.map +1 -1
  26. package/dist/render/schema.js +70 -100
  27. package/dist/ui/client.d.ts +3 -0
  28. package/dist/ui/client.d.ts.map +1 -1
  29. package/dist/ui/client.js +15 -0
  30. package/dist/ui/components/dialog.d.ts.map +1 -1
  31. package/dist/ui/components/dialog.js +4 -3
  32. package/dist/ui/components/select.js +1 -1
  33. package/dist/ui/index.js +1 -1
  34. package/dist/ui/server-select.d.ts +2 -7
  35. package/dist/ui/server-select.d.ts.map +1 -1
  36. package/dist/ui/server-select.js +52 -19
  37. package/dist/utils/combine-schema.d.ts.map +1 -1
  38. package/dist/utils/combine-schema.js +12 -2
  39. package/dist/utils/schema-to-string.d.ts +3 -0
  40. package/dist/utils/schema-to-string.d.ts.map +1 -0
  41. package/dist/utils/schema-to-string.js +62 -0
  42. package/dist/utils/schema.d.ts +9 -4
  43. package/dist/utils/schema.d.ts.map +1 -1
  44. package/dist/utils/schema.js +2 -0
  45. package/package.json +10 -8
  46. package/dist/playground/resolve.d.ts +0 -6
  47. package/dist/playground/resolve.d.ts.map +0 -1
  48. package/dist/playground/resolve.js +0 -14
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { getPreferredType, } from '../utils/schema.js';
2
+ import { getPreferredType } from '../utils/schema.js';
3
3
  import { getSecurities } from '../utils/get-security.js';
4
4
  import { ClientLazy } from '../playground/client.lazy.js';
5
5
  export async function APIPlayground({ path, method, ctx, client, }) {
@@ -15,18 +15,20 @@ export async function APIPlayground({ path, method, ctx, client, }) {
15
15
  registered: new WeakMap(),
16
16
  render: ctx,
17
17
  };
18
- const bodySchema = bodyContent && mediaType && bodyContent[mediaType].schema
19
- ? toSchema(bodyContent[mediaType].schema, true, context)
20
- : undefined;
21
18
  const props = {
22
19
  authorization: getAuthorizationField(method, ctx),
23
20
  method: method.method,
24
21
  route: path,
25
- parameters: method.parameters?.map((v) => parameterToField(v, context)),
26
- body: bodySchema && mediaType
22
+ parameters: method.parameters?.map((v) => ({
23
+ name: v.name,
24
+ in: v.in,
25
+ schema: writeReferences((v.schema ?? true), context),
26
+ description: v.description,
27
+ })),
28
+ body: bodyContent && mediaType
27
29
  ? {
28
- ...bodySchema,
29
- mediaType: mediaType,
30
+ schema: writeReferences(bodyContent[mediaType].schema, context),
31
+ mediaType,
30
32
  }
31
33
  : undefined,
32
34
  references: context.references,
@@ -35,6 +37,39 @@ export async function APIPlayground({ path, method, ctx, client, }) {
35
37
  };
36
38
  return _jsx(ClientLazy, { ...props });
37
39
  }
40
+ function writeReferences(schema, ctx, stack = new WeakMap()) {
41
+ if (typeof schema !== 'object' || !schema)
42
+ return schema;
43
+ if (stack.has(schema)) {
44
+ const out = stack.get(schema);
45
+ const id = ctx.nextId();
46
+ ctx.references[id] = out;
47
+ return {
48
+ $ref: id,
49
+ };
50
+ }
51
+ const output = { ...schema };
52
+ stack.set(schema, output);
53
+ for (const name of ['items', 'additionalProperties']) {
54
+ if (!output[name])
55
+ continue;
56
+ output[name] = writeReferences(output[name], ctx, stack);
57
+ }
58
+ for (const name of ['oneOf', 'allOf', 'anyOf']) {
59
+ if (!output[name])
60
+ continue;
61
+ output[name] = output[name].map((item) => writeReferences(item, ctx, stack));
62
+ }
63
+ for (const name of ['properties', 'patternProperties']) {
64
+ if (!output[name])
65
+ continue;
66
+ output[name] = { ...output[name] };
67
+ for (const key in output[name]) {
68
+ output[name][key] = writeReferences(output[name][key], ctx, stack);
69
+ }
70
+ }
71
+ return output;
72
+ }
38
73
  function getAuthorizationField(method, { schema: { document } }) {
39
74
  const security = method.security ?? document.security ?? [];
40
75
  if (security.length === 0)
@@ -64,134 +99,3 @@ function getAuthorizationField(method, { schema: { document } }) {
64
99
  ...scheme,
65
100
  };
66
101
  }
67
- function getIdFromSchema(schema, required, ctx) {
68
- const registered = ctx.registered.get(schema);
69
- if (registered === undefined) {
70
- const id = ctx.nextId();
71
- ctx.registered.set(schema, id);
72
- ctx.references[id] = toSchema(schema, required, ctx);
73
- return id;
74
- }
75
- return registered;
76
- }
77
- function parameterToField(v, ctx) {
78
- const allowed = ['header', 'cookie', 'query', 'path'];
79
- if (!allowed.includes(v.in))
80
- throw new Error(`Unsupported parameter in: "${v.in}"`);
81
- return {
82
- name: v.name,
83
- in: v.in,
84
- ...toSchema(v.schema ?? { type: 'string' }, v.required ?? false, ctx),
85
- };
86
- }
87
- function toReference(schema, required, ctx) {
88
- return {
89
- type: 'ref',
90
- isRequired: required,
91
- schema: getIdFromSchema(schema, false, ctx),
92
- };
93
- }
94
- function toSchema(schema, required, ctx) {
95
- if (schema.type === 'array') {
96
- return {
97
- type: 'array',
98
- description: schema.description ?? schema.title,
99
- isRequired: required,
100
- items: getIdFromSchema(schema.items, false, ctx),
101
- };
102
- }
103
- if (schema.type === 'object' ||
104
- schema.properties !== undefined ||
105
- schema.allOf !== undefined) {
106
- const properties = {};
107
- Object.entries(schema.properties ?? {}).forEach(([key, prop]) => {
108
- properties[key] = toReference(prop, schema.required?.includes(key) ?? false, ctx);
109
- });
110
- schema.allOf?.forEach((c) => {
111
- const field = toSchema(c, true, ctx);
112
- if (field.type === 'object')
113
- Object.assign(properties, field.properties);
114
- });
115
- const additional = schema.additionalProperties;
116
- let additionalProperties;
117
- if (additional && typeof additional === 'object') {
118
- if ((!additional.type || additional.type.length === 0) &&
119
- !additional.anyOf &&
120
- !additional.allOf &&
121
- !additional.oneOf) {
122
- additionalProperties = true;
123
- }
124
- else {
125
- additionalProperties = getIdFromSchema(additional, false, ctx);
126
- }
127
- }
128
- else {
129
- additionalProperties = additional;
130
- }
131
- return {
132
- type: 'object',
133
- isRequired: required,
134
- description: schema.description ?? schema.title,
135
- properties,
136
- additionalProperties,
137
- };
138
- }
139
- if (schema.type === undefined) {
140
- const combine = schema.anyOf ?? schema.oneOf;
141
- if (combine) {
142
- return {
143
- type: 'switcher',
144
- description: schema.description ?? schema.title,
145
- items: Object.fromEntries(combine.map((item, idx) => {
146
- return [
147
- item.title ?? item.type ?? `Item ${idx.toString()}`,
148
- toReference(item, true, ctx),
149
- ];
150
- })),
151
- isRequired: required,
152
- };
153
- }
154
- return {
155
- type: 'null',
156
- isRequired: false,
157
- };
158
- }
159
- if (ctx.allowFile && schema.type === 'string' && schema.format === 'binary') {
160
- return {
161
- type: 'file',
162
- isRequired: required,
163
- description: schema.description ?? schema.title,
164
- };
165
- }
166
- if (Array.isArray(schema.type)) {
167
- const items = {};
168
- for (const type of schema.type) {
169
- if (type === 'array') {
170
- items[type] = {
171
- type,
172
- items: 'items' in schema && schema.items
173
- ? toSchema(schema.items, false, ctx)
174
- : toSchema({}, required, ctx),
175
- isRequired: required,
176
- };
177
- }
178
- else {
179
- items[type] = toSchema({
180
- ...schema,
181
- type,
182
- }, required, ctx);
183
- }
184
- }
185
- return {
186
- type: 'switcher',
187
- description: schema.description ?? schema.title,
188
- items,
189
- isRequired: required,
190
- };
191
- }
192
- return {
193
- type: schema.type === 'integer' ? 'number' : schema.type,
194
- isRequired: required,
195
- description: schema.description ?? schema.title,
196
- };
197
- }
@@ -1,26 +1,24 @@
1
1
  import { type HTMLAttributes, type ReactNode } from 'react';
2
2
  import type { RequestSchema } from '../playground/index.js';
3
- type FieldOfType<Type> = Extract<RequestSchema, {
4
- type: Type;
5
- }>;
6
- export declare function ObjectInput({ field, fieldName, ...props }: {
7
- field: FieldOfType<'object'>;
3
+ export declare function ObjectInput({ field: _field, fieldName, ...props }: {
4
+ field: Exclude<RequestSchema, boolean>;
8
5
  fieldName: string;
9
6
  } & HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
10
- export declare function JsonInput({ fieldName }: {
7
+ export declare function JsonInput({ fieldName, children, }: {
11
8
  fieldName: string;
9
+ children: ReactNode;
12
10
  }): import("react/jsx-runtime").JSX.Element;
13
- export declare function FieldInput({ field, fieldName, ...props }: HTMLAttributes<HTMLElement> & {
14
- field: Exclude<RequestSchema, {
15
- type: 'switcher';
16
- }>;
11
+ export declare function FieldInput({ field, fieldName, isRequired, ...props }: HTMLAttributes<HTMLElement> & {
12
+ field: Exclude<RequestSchema, boolean>;
13
+ isRequired?: boolean;
17
14
  fieldName: string;
18
- }): import("react/jsx-runtime").JSX.Element | null;
19
- export declare function FieldSet({ field, fieldName, toolbar, name, ...props }: HTMLAttributes<HTMLElement> & {
15
+ }): import("react/jsx-runtime").JSX.Element;
16
+ export declare function FieldSet({ field: _field, fieldName, toolbar, name, isRequired, depth, ...props }: HTMLAttributes<HTMLElement> & {
17
+ isRequired?: boolean;
20
18
  name?: ReactNode;
21
19
  field: RequestSchema;
22
20
  fieldName: string;
21
+ depth?: number;
23
22
  toolbar?: ReactNode;
24
23
  }): import("react/jsx-runtime").JSX.Element | null;
25
- export {};
26
24
  //# sourceMappingURL=inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../src/playground/inputs.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQxD,KAAK,WAAW,CAAC,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AA6BhE,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE;IACD,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,cAAc,CAAC,cAAc,CAAC,2CAqBjC;AAED,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,2CAuB7D;AA+ID,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;IAC/B,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IACpD,SAAS,EAAE,MAAM,CAAC;CACnB,kDAmGA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,SAAS,EACT,OAAO,EACP,IAAI,EACJ,GAAG,KAAK,EACT,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;IAC/B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,kDAgGA"}
1
+ {"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../src/playground/inputs.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA8CxD,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,cAAc,CAAC,2CAqCjC;AAED,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,QAAQ,GACT,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CA0BA;AAyFD,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,2CA+FA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EAAE,MAAM,EACb,SAAS,EACT,OAAO,EACP,IAAI,EACJ,UAAU,EACV,KAAS,EACT,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;IACf,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,kDA2HA"}
@@ -4,126 +4,88 @@ import { useState, } from 'react';
4
4
  import { Plus, Trash2 } from '../icons.js';
5
5
  import { Controller, useController, useFieldArray, useFormContext, } from 'react-hook-form';
6
6
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/components/select.js';
7
- import { resolve } from '../playground/resolve.js';
8
7
  import { Input, labelVariants } from '../ui/components/input.js';
9
8
  import { getDefaultValue } from './get-default-values.js';
10
- import { useSchemaContext } from './client.js';
11
9
  import { cn } from 'fumadocs-ui/utils/cn';
12
10
  import { buttonVariants } from 'fumadocs-ui/components/ui/button';
11
+ import { combineSchema } from '../utils/combine-schema.js';
12
+ import { schemaToString } from '../utils/schema-to-string.js';
13
+ import { anyFields, useFieldInfo, useResolvedSchema, } from '../playground/schema.js';
13
14
  function FieldHeader({ name, required = false, type, ...props }) {
14
- return (_jsxs("label", { ...props, className: cn('w-full inline-flex items-center gap-1', props.className), children: [_jsx("span", { className: cn(labelVariants()), children: name }), required ? _jsx("span", { className: "text-red-500", children: "*" }) : null, _jsx("div", { className: "flex-1" }), type ? (_jsx("code", { className: "text-xs text-fd-muted-foreground", children: type })) : null, props.children] }));
15
+ return (_jsxs("label", { ...props, className: cn('w-full inline-flex items-center gap-1', props.className), children: [_jsxs("span", { className: cn(labelVariants()), children: [name, required ? _jsx("span", { className: "text-red-400/80 mx-1", children: "*" }) : null] }), _jsx("div", { className: "flex-1" }), type ? (_jsx("code", { "data-type": true, className: "text-xs text-fd-muted-foreground", children: type })) : null, props.children] }));
15
16
  }
16
- export function ObjectInput({ field, fieldName, ...props }) {
17
- const { references } = useSchemaContext();
18
- return (_jsxs("div", { ...props, className: cn('flex flex-col gap-6', props.className), children: [Object.entries(field.properties).map(([key, child]) => (_jsx(FieldSet, { name: key, field: resolve(child, references), fieldName: `${fieldName}.${key}` }, key))), field.additionalProperties ? (_jsx(AdditionalProperties, { fieldName: fieldName, type: field.additionalProperties })) : null] }));
17
+ export function ObjectInput({ field: _field, fieldName, ...props }) {
18
+ const field = useResolvedSchema(combineSchema([_field, ...(_field.allOf ?? []), ...(_field.anyOf ?? [])]));
19
+ return (_jsxs("div", { ...props, className: cn('flex flex-col gap-6', props.className), children: [Object.entries(field.properties ?? {}).map(([key, child]) => (_jsx(FieldSet, { name: key, field: child, fieldName: `${fieldName}.${key}`, isRequired: field.required?.includes(key) }, key))), (field.additionalProperties || field.patternProperties) && (_jsx(DynamicProperties, { fieldName: fieldName, filterKey: (v) => !field.properties || !Object.keys(field.properties).includes(v), getType: (key) => {
20
+ for (const pattern in field.patternProperties) {
21
+ if (key.match(RegExp(pattern))) {
22
+ return field.patternProperties[pattern];
23
+ }
24
+ }
25
+ if (field.additionalProperties)
26
+ return field.additionalProperties;
27
+ return anyFields;
28
+ } }))] }));
19
29
  }
20
- export function JsonInput({ fieldName }) {
30
+ export function JsonInput({ fieldName, children, }) {
21
31
  const controller = useController({
22
32
  name: fieldName,
23
33
  });
24
34
  const [value, setValue] = useState(() => JSON.stringify(controller.field.value, null, 2));
25
- return (_jsx("textarea", { ...controller.field, value: value, className: "w-full h-[300px] text-[13px] font-mono resize-none rounded-lg border p-2 bg-fd-secondary text-fd-secondary-foreground focus-visible:outline-none", onChange: (v) => {
26
- setValue(v.target.value);
27
- try {
28
- controller.field.onChange(JSON.parse(v.target.value));
29
- }
30
- catch {
31
- // ignore
32
- }
33
- } }));
35
+ return (_jsxs("div", { className: "rounded-lg border bg-fd-secondary text-fd-secondary-foreground", children: [children, _jsx("textarea", { ...controller.field, value: value, className: "p-2 w-full h-[240px] text-[13px] font-mono resize-none focus-visible:outline-none", onChange: (v) => {
36
+ setValue(v.target.value);
37
+ try {
38
+ controller.field.onChange(JSON.parse(v.target.value));
39
+ }
40
+ catch {
41
+ // ignore
42
+ }
43
+ } })] }));
34
44
  }
35
- function AdditionalProperties({ fieldName, type, }) {
45
+ function DynamicProperties({ fieldName, filterKey = () => true, getType = () => anyFields, }) {
36
46
  const { control, setValue, getValues } = useFormContext();
37
- const { references } = useSchemaContext();
38
47
  const [nextName, setNextName] = useState('');
39
48
  const [properties, setProperties] = useState(() => {
40
49
  const value = getValues(fieldName);
41
50
  if (value)
42
- return Object.keys(value);
51
+ return Object.keys(value).filter(filterKey);
43
52
  return [];
44
53
  });
45
- const types = typeof type === 'string'
46
- ? resolveDynamicTypes(references[type], references)
47
- : anyFields;
48
54
  const onAppend = () => {
49
55
  const name = nextName.trim();
50
56
  if (name.length === 0)
51
57
  return;
52
58
  setProperties((p) => {
53
- if (p.includes(name))
59
+ if (p.includes(name) || !filterKey(name))
54
60
  return p;
55
- setValue(`${fieldName}.${name}`, getDefaultValue(Object.values(types)[0], references));
61
+ const type = getType(name);
62
+ setValue(`${fieldName}.${name}`, getDefaultValue(type));
56
63
  setNextName('');
57
64
  return [...p, name];
58
65
  });
59
66
  };
60
- return (_jsxs(_Fragment, { children: [properties.map((item) => (_jsx(FieldSet, { name: item, field: {
61
- type: 'switcher',
62
- items: types,
63
- isRequired: false,
64
- }, fieldName: `${fieldName}.${item}`, toolbar: _jsx("button", { type: "button", "aria-label": "Remove Item", className: cn(buttonVariants({
65
- color: 'secondary',
66
- size: 'sm',
67
+ return (_jsxs(_Fragment, { children: [properties.map((item) => (_jsx(FieldSet, { name: item, field: getType(item), fieldName: `${fieldName}.${item}`, toolbar: _jsx("button", { type: "button", "aria-label": "Remove Item", className: cn(buttonVariants({
68
+ color: 'ghost',
69
+ className: 'p-1',
67
70
  })), onClick: () => {
68
71
  setProperties((p) => p.filter((prop) => prop !== item));
69
72
  control.unregister(`${fieldName}.${item}`);
70
- }, children: _jsx(Trash2, { className: "size-4" }) }) }, item))), _jsxs("div", { className: "flex flex-row gap-1", children: [_jsx(Input, { value: nextName, placeholder: "Enter Property Name", onChange: (e) => {
71
- setNextName(e.target.value);
72
- }, onKeyDown: (e) => {
73
+ }, children: _jsx(Trash2, { className: "size-4" }) }) }, item))), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: nextName, placeholder: "Enter Property Name", onChange: (e) => setNextName(e.target.value), onKeyDown: (e) => {
73
74
  if (e.key === 'Enter') {
74
75
  onAppend();
75
76
  e.preventDefault();
76
77
  }
77
- } }), _jsx("button", { type: "button", className: cn(buttonVariants({ color: 'secondary' })), onClick: onAppend, children: "New" })] })] }));
78
+ } }), _jsx("button", { type: "button", className: cn(buttonVariants({ color: 'secondary', size: 'sm' }), 'px-4'), onClick: onAppend, children: "New" })] })] }));
78
79
  }
79
- function resolveDynamicTypes(schema, references) {
80
- if (schema.type !== 'switcher')
81
- return { [schema.type]: schema };
82
- return Object.fromEntries(Object.entries(schema.items).map(([key, value]) => [
83
- key,
84
- resolve(value, references),
85
- ]));
86
- }
87
- const anyFields = {
88
- string: {
89
- type: 'string',
90
- isRequired: false,
91
- },
92
- boolean: {
93
- type: 'boolean',
94
- isRequired: false,
95
- },
96
- number: {
97
- type: 'number',
98
- isRequired: false,
99
- },
100
- object: {
101
- type: 'object',
102
- properties: {},
103
- additionalProperties: true,
104
- isRequired: false,
105
- },
106
- };
107
- anyFields.array = {
108
- type: 'array',
109
- isRequired: false,
110
- items: {
111
- type: 'switcher',
112
- isRequired: false,
113
- items: anyFields,
114
- },
115
- };
116
- export function FieldInput({ field, fieldName, ...props }) {
80
+ export function FieldInput({ field, fieldName, isRequired, ...props }) {
117
81
  const { control, register } = useFormContext();
118
- if (field.type === 'null')
119
- return null;
120
82
  if (field.type === 'object') {
121
83
  return (_jsx(ObjectInput, { field: field, fieldName: fieldName, ...props, className: cn('rounded-lg border border-fd-primary/20 bg-fd-background/50 p-3 shadow-sm', props.className) }));
122
84
  }
123
85
  if (field.type === 'array') {
124
- return (_jsx(ArrayInput, { fieldName: fieldName, field: field, ...props, className: cn('rounded-lg border border-fd-primary/20 bg-fd-background/50 p-3 shadow-sm', props.className) }));
86
+ return (_jsx(ArrayInput, { fieldName: fieldName, items: field.items ?? anyFields, ...props, className: cn('rounded-lg border border-fd-primary/20 bg-fd-background/50 p-3 shadow-sm', props.className) }));
125
87
  }
126
- if (field.type === 'file') {
88
+ if (field.type === 'string' && field.format === 'binary') {
127
89
  return (_jsx(Controller, { control: control, name: fieldName, render: ({ field: { value: _, onChange, ...restField } }) => (_jsx("input", { id: fieldName, type: "file", multiple: false, onChange: (e) => {
128
90
  if (!e.target.files)
129
91
  return;
@@ -131,74 +93,55 @@ export function FieldInput({ field, fieldName, ...props }) {
131
93
  }, ...props, ...restField })) }));
132
94
  }
133
95
  if (field.type === 'boolean') {
134
- return (_jsx(Controller, { control: control, name: fieldName, render: ({ field: { value, onChange, ...restField } }) => (_jsxs(Select, { value: String(value), onValueChange: (value) => onChange(value === 'null' ? null : value === 'true'), disabled: restField.disabled, children: [_jsx(SelectTrigger, { id: fieldName, className: props.className, ...restField, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "true", children: "True" }), _jsx(SelectItem, { value: "false", children: "False" }), field.isRequired ? null : (_jsx(SelectItem, { value: "null", children: "Null" }))] })] })) }));
96
+ return (_jsx(Controller, { control: control, name: fieldName, render: ({ field: { value, onChange, ...restField } }) => (_jsxs(Select, { value: String(value), onValueChange: (value) => onChange(value === 'null' ? null : value === 'true'), disabled: restField.disabled, children: [_jsx(SelectTrigger, { id: fieldName, className: props.className, ...restField, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "true", children: "True" }), _jsx(SelectItem, { value: "false", children: "False" }), !isRequired && _jsx(SelectItem, { value: "null", children: "Null" })] })] })) }));
135
97
  }
136
98
  return (_jsx(Input, { id: fieldName, placeholder: "Enter value", type: field.type === 'string' ? 'text' : 'number', ...register(fieldName, {
137
- valueAsNumber: field.type === 'number',
99
+ valueAsNumber: field.type === 'number' || field.type === 'integer',
138
100
  }), ...props }));
139
101
  }
140
- export function FieldSet({ field, fieldName, toolbar, name, ...props }) {
141
- const form = useFormContext();
142
- const { references, dynamic } = useSchemaContext();
143
- const [type, setType] = useState(() => {
144
- if (field.type !== 'switcher')
145
- return '';
146
- const d = dynamic.current.get(fieldName);
147
- const items = Object.keys(field.items);
148
- if (d?.type === 'field') {
149
- // schemas are passed from server components, object references are maintained
150
- const cached = items.find((item) => d.schema === field.items[item]);
151
- if (cached)
152
- return cached;
153
- }
154
- const value = form.getValues(fieldName);
155
- let type = typeof value;
156
- if (Array.isArray(value)) {
157
- type = 'array';
158
- }
159
- else if (value instanceof File) {
160
- type = 'file';
161
- }
162
- else if (value === null) {
163
- type = 'null';
164
- }
165
- return items.find((item) => field.items[item].type === type) ?? items[0];
166
- });
167
- if (field.type === 'null')
102
+ export function FieldSet({ field: _field, fieldName, toolbar, name, isRequired, depth = 0, ...props }) {
103
+ const field = useResolvedSchema(_field);
104
+ const [show, setShow] = useState(() => typeof _field !== 'object' ||
105
+ !_field.$ref ||
106
+ (field.type !== 'object' && !field.allOf && !field.oneOf && !field.anyOf));
107
+ const { info, updateInfo } = useFieldInfo(fieldName, field, depth);
108
+ if (_field === false)
168
109
  return null;
169
- if (field.type === 'switcher') {
170
- const child = resolve(field.items[type], references);
171
- return (_jsxs("fieldset", { ...props, className: cn('flex flex-col gap-1.5', props.className), children: [_jsxs(FieldHeader, { name: name, htmlFor: fieldName, required: field.isRequired, children: [_jsx("select", { value: type, onChange: (e) => {
172
- const value = e.target.value;
173
- if (value === type)
174
- return;
175
- setType(value);
176
- dynamic.current.set(fieldName, {
177
- type: 'field',
178
- schema: field.items[value],
110
+ if (!show) {
111
+ return (_jsxs(FieldHeader, { ...props, name: name, required: isRequired, children: [toolbar, _jsx("button", { type: "button", className: cn(buttonVariants({ size: 'sm' })), onClick: () => setShow(true), children: "Show" })] }));
112
+ }
113
+ if (field.oneOf) {
114
+ return (_jsx(FieldSet, { ...props, className: cn(props.className, '[&>label>[data-type]]:hidden'), name: name, fieldName: fieldName, isRequired: isRequired, field: field.oneOf[info.oneOf], depth: depth + 1, toolbar: _jsxs(_Fragment, { children: [_jsx("select", { className: "text-xs font-mono", value: info.oneOf, onChange: (e) => {
115
+ updateInfo({
116
+ oneOf: Number(e.target.value),
117
+ });
118
+ }, children: field.oneOf.map((item, i) => (_jsx("option", { value: i, children: schemaToString(item) }, i))) }), toolbar] }) }));
119
+ }
120
+ if (Array.isArray(field.type)) {
121
+ return (_jsxs("fieldset", { ...props, className: cn('flex flex-col gap-1.5', props.className), children: [_jsxs(FieldHeader, { name: name, htmlFor: fieldName, required: isRequired, children: [_jsx("select", { className: "text-xs font-mono", value: info.selectedType, onChange: (e) => {
122
+ updateInfo({
123
+ selectedType: e.target.value,
179
124
  });
180
- form.setValue(fieldName, getDefaultValue(field.items[value], references));
181
- }, className: "text-xs", children: Object.keys(field.items).map((item) => (_jsx("option", { value: item, children: item }, item))) }), toolbar] }), _jsx("p", { className: "text-xs text-fd-muted-foreground", children: field.description }), child.type === 'switcher' ? (_jsx(FieldSet, { field: child, fieldName: fieldName })) : (_jsx(FieldInput, { field: child, fieldName: fieldName }))] }));
125
+ }, children: field.type.map((item) => (_jsx("option", { value: item, children: item }, item))) }), toolbar] }), _jsx("p", { className: "text-xs text-fd-muted-foreground", children: field.description }), _jsx(FieldInput, { isRequired: isRequired, field: {
126
+ ...field,
127
+ type: info.selectedType,
128
+ }, fieldName: fieldName })] }));
182
129
  }
183
- return (_jsxs("fieldset", { ...props, className: cn('flex flex-col gap-1.5', props.className), children: [_jsx(FieldHeader, { htmlFor: fieldName, name: name, required: field.isRequired, type: field.type, children: toolbar }), _jsx(FieldInput, { field: field, fieldName: fieldName })] }));
130
+ return (_jsxs("fieldset", { ...props, className: cn('flex flex-col gap-1.5', props.className), children: [_jsx(FieldHeader, { htmlFor: fieldName, name: name, required: isRequired, type: field.oneOf ? undefined : schemaToString(field), children: toolbar }), show ? (_jsx(FieldInput, { field: field, fieldName: fieldName, isRequired: isRequired })) : (_jsx("div", { children: _jsx("button", { onClick: () => setShow((prev) => !prev), children: "Show" }) }))] }));
184
131
  }
185
- function ArrayInput({ fieldName, field, ...props }) {
186
- const { references } = useSchemaContext();
187
- const items = resolve(field.items, references);
132
+ function ArrayInput({ fieldName, items, ...props }) {
188
133
  const name = fieldName.split('.').at(-1) ?? '';
189
134
  const { fields, append, remove } = useFieldArray({
190
135
  name: fieldName,
191
136
  });
192
137
  return (_jsxs("div", { ...props, className: cn('flex flex-col gap-2', props.className), children: [fields.map((item, index) => (_jsx(FieldSet, { name: _jsxs("span", { className: "text-fd-muted-foreground", children: [name, "[", index, "]"] }), field: items, fieldName: `${fieldName}.${index}`, toolbar: _jsx("button", { type: "button", "aria-label": "Remove Item", className: cn(buttonVariants({
193
- color: 'secondary',
194
- size: 'sm',
195
- })), onClick: () => {
196
- remove(index);
197
- }, children: _jsx(Trash2, { className: "size-4" }) }) }, item.id))), _jsxs("button", { type: "button", className: cn(buttonVariants({
138
+ color: 'ghost',
139
+ className: 'p-1',
140
+ })), onClick: () => remove(index), children: _jsx(Trash2, { className: "size-4" }) }) }, item.id))), _jsxs("button", { type: "button", className: cn(buttonVariants({
198
141
  color: 'outline',
199
142
  className: 'gap-1.5 py-2',
200
143
  size: 'sm',
201
144
  })), onClick: () => {
202
- append(getDefaultValue(items, references));
145
+ append(getDefaultValue(items));
203
146
  }, children: [_jsx(Plus, { className: "size-4" }), "New Item"] })] }));
204
147
  }
@@ -0,0 +1,38 @@
1
+ import { Ajv2020 } from 'ajv/dist/2020';
2
+ import type { RequestSchema } from '../playground/index.js';
3
+ import { ReactNode } from 'react';
4
+ interface SchemaContextType {
5
+ references: Record<string, RequestSchema>;
6
+ fieldInfoMap: Map<string, FieldInfo>;
7
+ ajv: Ajv2020;
8
+ }
9
+ export interface FieldInfo {
10
+ selectedType?: string;
11
+ oneOf: number;
12
+ }
13
+ export declare const anyFields: {
14
+ type: string[];
15
+ items: true;
16
+ additionalProperties: true;
17
+ };
18
+ export declare function SchemaProvider({ references, fieldInfoMap, children, }: Omit<SchemaContextType, 'ajv'> & {
19
+ children: ReactNode;
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ /**
22
+ * A hook to store dynamic info of a field, such as selected schema of `oneOf`.
23
+ *
24
+ * @param fieldName - field name of form.
25
+ * @param schema - The JSON Schema to generate initial values.
26
+ * @param depth - The depth to avoid duplicated field name with same schema (e.g. nested `oneOf`).
27
+ */
28
+ export declare function useFieldInfo(fieldName: string, schema: Exclude<RequestSchema, boolean>, depth: number): {
29
+ info: FieldInfo;
30
+ updateInfo: (value: Partial<FieldInfo>) => void;
31
+ };
32
+ /**
33
+ * Resolve `$ref` in the schema, **not recursive**.
34
+ */
35
+ export declare function useResolvedSchema(schema: RequestSchema): Exclude<RequestSchema, boolean>;
36
+ export declare function fallbackAny(schema: RequestSchema): Exclude<RequestSchema, boolean>;
37
+ export {};
38
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +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"}