schema-components 1.17.0 → 1.18.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.
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/constraints.d.mts +1 -1
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/merge.d.mts +14 -8
- package/dist/core/merge.mjs +81 -12
- package/dist/core/normalise.d.mts +1 -1
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/renderer.d.mts +2 -2
- package/dist/core/renderer.mjs +50 -1
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/typeInference.d.mts +2 -2
- package/dist/core/walkBuilders.d.mts +2 -2
- package/dist/core/walker.mjs +2 -2
- package/dist/{diagnostics-DzbZmcLI.d.mts → diagnostics-BYk63jsC.d.mts} +1 -1
- package/dist/html/a11y.d.mts +13 -2
- package/dist/html/a11y.mjs +26 -2
- package/dist/html/renderToHtml.d.mts +1 -1
- package/dist/html/renderToHtml.mjs +5 -3
- package/dist/html/renderToHtmlStream.d.mts +1 -1
- package/dist/html/renderers.d.mts +4 -3
- package/dist/html/renderers.mjs +9 -13
- package/dist/html/streamRenderers.d.mts +1 -1
- package/dist/openapi/bundle.d.mts +9 -4
- package/dist/openapi/bundle.mjs +73 -15
- package/dist/openapi/components.d.mts +1 -1
- package/dist/openapi/components.mjs +22 -17
- package/dist/openapi/parser.mjs +8 -8
- package/dist/openapi/resolve.d.mts +13 -2
- package/dist/openapi/resolve.mjs +19 -3
- package/dist/react/SchemaComponent.d.mts +4 -4
- package/dist/react/SchemaComponent.mjs +2 -31
- package/dist/react/SchemaView.d.mts +4 -3
- package/dist/react/SchemaView.mjs +13 -44
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +1 -1
- package/dist/react/headlessRenderers.mjs +3 -2
- package/dist/{ref-DvWoULcy.d.mts → ref-Ckt5liZs.d.mts} +1 -1
- package/dist/{renderer-B3s8o2B8.d.mts → renderer-DXo-rXHJ.d.mts} +18 -1
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mantine.mjs +4 -3
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +6 -5
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +3 -3
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +6 -5
- package/dist/{typeInference-k7FXfTVO.d.mts → typeInference-5JiqIZ8t.d.mts} +57 -4
- package/package.json +1 -1
package/dist/openapi/bundle.mjs
CHANGED
|
@@ -29,28 +29,38 @@ import { isObject } from "../core/guards.mjs";
|
|
|
29
29
|
*
|
|
30
30
|
* Walks every $ref in the document. For external refs (not starting with `#`),
|
|
31
31
|
* calls the resolver to fetch the external document, extracts the referenced
|
|
32
|
-
* schema, inlines it into `components.schemas`
|
|
33
|
-
* rewrites the $ref to point
|
|
32
|
+
* schema, inlines it into `components.schemas` under a synthesised name, and
|
|
33
|
+
* rewrites the original $ref to point at the new internal location
|
|
34
|
+
* (`#/components/schemas/<name>`).
|
|
35
|
+
*
|
|
36
|
+
* Identical external refs share a single entry — the second occurrence of
|
|
37
|
+
* the same `(uri, fragment)` pair reuses the name produced for the first.
|
|
38
|
+
* Name collisions between different refs are resolved by suffixing a counter.
|
|
34
39
|
*
|
|
35
40
|
* The resolver is called once per unique URI and the result is cached.
|
|
36
41
|
*
|
|
37
|
-
* Returns a deep-cloned document with all external refs
|
|
38
|
-
* The original document is never mutated.
|
|
42
|
+
* Returns a deep-cloned document with all external refs replaced by internal
|
|
43
|
+
* refs. The original document is never mutated.
|
|
39
44
|
*/
|
|
40
45
|
async function bundleOpenApiDoc(doc, resolver) {
|
|
41
46
|
const result = structuredClone(doc);
|
|
42
47
|
const uriCache = /* @__PURE__ */ new Map();
|
|
48
|
+
const inlineCache = /* @__PURE__ */ new Map();
|
|
43
49
|
if (!isObject(result.components)) result.components = {};
|
|
44
|
-
|
|
45
|
-
if (isObject(
|
|
46
|
-
|
|
50
|
+
const components = result.components;
|
|
51
|
+
if (!isObject(components)) throw new Error("bundleOpenApiDoc: components is not an object");
|
|
52
|
+
if (!isObject(components.schemas)) components.schemas = {};
|
|
53
|
+
const schemasNode = components.schemas;
|
|
54
|
+
if (!isObject(schemasNode)) throw new Error("bundleOpenApiDoc: components.schemas is not an object");
|
|
55
|
+
await walkAndInline(result, schemasNode, uriCache, inlineCache, resolver);
|
|
47
56
|
return result;
|
|
48
57
|
}
|
|
49
58
|
/**
|
|
50
59
|
* Walk a document tree, find external $ref strings, resolve them,
|
|
51
|
-
* inline the targets
|
|
60
|
+
* inline the targets into `components.schemas`, and rewrite each $ref
|
|
61
|
+
* to point at the new internal location.
|
|
52
62
|
*/
|
|
53
|
-
async function walkAndInline(node, uriCache, resolver) {
|
|
63
|
+
async function walkAndInline(node, schemasNode, uriCache, inlineCache, resolver) {
|
|
54
64
|
if (!isObject(node)) return;
|
|
55
65
|
if (typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
|
|
56
66
|
const ref = node.$ref;
|
|
@@ -66,15 +76,21 @@ async function walkAndInline(node, uriCache, resolver) {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
if (externalDoc !== void 0) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
const cacheKey = `${uri}${fragment}`;
|
|
80
|
+
let inlinedName = inlineCache.get(cacheKey);
|
|
81
|
+
if (inlinedName === void 0) {
|
|
82
|
+
const target = resolveFragment(externalDoc, fragment);
|
|
83
|
+
if (isObject(target)) {
|
|
84
|
+
inlinedName = registerInline(schemasNode, uri, fragment, target);
|
|
85
|
+
inlineCache.set(cacheKey, inlinedName);
|
|
86
|
+
await walkAndInline(schemasNode[inlinedName], schemasNode, uriCache, inlineCache, resolver);
|
|
87
|
+
}
|
|
73
88
|
}
|
|
89
|
+
if (inlinedName !== void 0) node.$ref = `#/components/schemas/${inlinedName}`;
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
|
-
for (const value of Object.values(node)) if (isObject(value)) await walkAndInline(value, uriCache, resolver);
|
|
77
|
-
else if (Array.isArray(value)) for (const item of value) await walkAndInline(item, uriCache, resolver);
|
|
92
|
+
for (const value of Object.values(node)) if (isObject(value)) await walkAndInline(value, schemasNode, uriCache, inlineCache, resolver);
|
|
93
|
+
else if (Array.isArray(value)) for (const item of value) await walkAndInline(item, schemasNode, uriCache, inlineCache, resolver);
|
|
78
94
|
}
|
|
79
95
|
/**
|
|
80
96
|
* Resolve a JSON Pointer fragment within a document.
|
|
@@ -91,5 +107,47 @@ function resolveFragment(doc, fragment) {
|
|
|
91
107
|
}
|
|
92
108
|
return isObject(current) ? current : void 0;
|
|
93
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Derive a candidate identifier for an inlined external schema. Prefers
|
|
112
|
+
* the last meaningful segment of the JSON Pointer fragment; falls back
|
|
113
|
+
* to the URI's filename (sans extension), then to a generic prefix.
|
|
114
|
+
*/
|
|
115
|
+
function deriveCandidateName(uri, fragment) {
|
|
116
|
+
if (fragment.startsWith("#/")) {
|
|
117
|
+
const last = fragment.slice(2).split("/").at(-1);
|
|
118
|
+
if (last !== void 0 && last.length > 0) return sanitiseName(last);
|
|
119
|
+
}
|
|
120
|
+
const pathOnly = uri.split(/[?#]/)[0] ?? uri;
|
|
121
|
+
const lastSlash = pathOnly.lastIndexOf("/");
|
|
122
|
+
const filename = lastSlash >= 0 ? pathOnly.slice(lastSlash + 1) : pathOnly;
|
|
123
|
+
const dot = filename.lastIndexOf(".");
|
|
124
|
+
const stem = dot > 0 ? filename.slice(0, dot) : filename;
|
|
125
|
+
if (stem.length > 0) return sanitiseName(stem);
|
|
126
|
+
return "ExternalSchema";
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Sanitise a string into a JSON Pointer-safe identifier: alphanumerics
|
|
130
|
+
* and underscores only. An empty result falls back to "Schema".
|
|
131
|
+
*/
|
|
132
|
+
function sanitiseName(raw) {
|
|
133
|
+
const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
|
|
134
|
+
return cleaned.length > 0 ? cleaned : "Schema";
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Place the resolved target into `components.schemas` under a unique
|
|
138
|
+
* name derived from the ref, and return the chosen name. Collisions
|
|
139
|
+
* with existing entries are resolved by suffixing a counter.
|
|
140
|
+
*/
|
|
141
|
+
function registerInline(schemasNode, uri, fragment, target) {
|
|
142
|
+
const base = deriveCandidateName(uri, fragment);
|
|
143
|
+
let name = base;
|
|
144
|
+
let counter = 2;
|
|
145
|
+
while (name in schemasNode) {
|
|
146
|
+
name = `${base}_${String(counter)}`;
|
|
147
|
+
counter++;
|
|
148
|
+
}
|
|
149
|
+
schemasNode[name] = structuredClone(target);
|
|
150
|
+
return name;
|
|
151
|
+
}
|
|
94
152
|
//#endregion
|
|
95
153
|
export { bundleOpenApiDoc };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { T as SchemaMeta, u as FieldOverride } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as InferResponseFields, d as UnsafeFields, i as InferRequestBodyFields, r as InferParameterOverrides } from "../typeInference-5JiqIZ8t.mjs";
|
|
3
3
|
import { WidgetMap } from "../react/SchemaComponent.mjs";
|
|
4
4
|
import { ReactNode } from "react";
|
|
5
5
|
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { toRecordOrUndefined } from "../core/guards.mjs";
|
|
2
|
-
import { SchemaNormalisationError } from "../core/errors.mjs";
|
|
3
|
-
import { normaliseSchema } from "../core/adapter.mjs";
|
|
1
|
+
import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
4
2
|
import { walk } from "../core/walker.mjs";
|
|
3
|
+
import { joinPath, renderField, sanitisePrefix } from "../react/SchemaComponent.mjs";
|
|
5
4
|
import { ApiCallbacks } from "./ApiCallbacks.mjs";
|
|
6
5
|
import { ApiLinks } from "./ApiLinks.mjs";
|
|
7
6
|
import { ApiResponseHeaders } from "./ApiResponseHeaders.mjs";
|
|
8
7
|
import { ApiSecurity } from "./ApiSecurity.mjs";
|
|
9
8
|
import { getLinks, getSecurityRequirements, getSecuritySchemes, listCallbacks } from "./parser.mjs";
|
|
10
|
-
import { joinPath, renderField, sanitisePrefix } from "../react/SchemaComponent.mjs";
|
|
11
9
|
import { getParsed, resolveOperation, resolveParameters, resolveRequestBody, resolveResponse, toDoc } from "./resolve.mjs";
|
|
12
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
10
|
import { useId } from "react";
|
|
11
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
14
12
|
//#region src/openapi/components.tsx
|
|
15
13
|
/**
|
|
16
14
|
* OpenAPI React components with type-safe generics.
|
|
@@ -27,25 +25,17 @@ import { useId } from "react";
|
|
|
27
25
|
*/
|
|
28
26
|
function noop() {}
|
|
29
27
|
function renderSchema(schema, rootDocument, options) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const normalised = normaliseSchema(schema);
|
|
34
|
-
jsonSchema = normalised.jsonSchema;
|
|
35
|
-
rootMeta = normalised.rootMeta;
|
|
36
|
-
} catch (err) {
|
|
37
|
-
throw new SchemaNormalisationError(err instanceof Error ? err.message : "Failed to normalise schema", schema, "unknown");
|
|
38
|
-
}
|
|
28
|
+
if (!isObject(schema)) throw new Error("renderSchema received a non-object schema from the resolver.");
|
|
29
|
+
const rootMeta = extractRootMetaFromSchema(schema);
|
|
39
30
|
const componentMeta = {};
|
|
40
31
|
if (options.readOnly === true) componentMeta.readOnly = true;
|
|
41
32
|
if (options.meta !== void 0) for (const [k, v] of Object.entries(options.meta)) componentMeta[k] = v;
|
|
42
|
-
const
|
|
33
|
+
const tree = walk(schema, {
|
|
43
34
|
componentMeta,
|
|
44
35
|
rootMeta,
|
|
45
36
|
fieldOverrides: toRecordOrUndefined(options.fields),
|
|
46
37
|
rootDocument
|
|
47
|
-
};
|
|
48
|
-
const tree = walk(jsonSchema, walkOpts);
|
|
38
|
+
});
|
|
49
39
|
const makeRenderChild = (parentPath) => (childTree, childValue, childOnChange, pathSuffix) => {
|
|
50
40
|
const childPath = joinPath(parentPath, pathSuffix);
|
|
51
41
|
return renderField(childTree, childValue, childOnChange, void 0, makeRenderChild(childPath), childPath, options.widgets);
|
|
@@ -262,5 +252,20 @@ function buildParamMeta(param, overrides, meta) {
|
|
|
262
252
|
if (meta !== void 0) for (const [k, v] of Object.entries(meta)) result[k] = v;
|
|
263
253
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
264
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Extract root-level meta (title, description, readOnly, etc.) from a
|
|
257
|
+
* JSON Schema node. Mirrors `extractRootMetaFromJson` in the adapter so
|
|
258
|
+
* pre-normalised schemas (extracted from `getParsed`) still surface root
|
|
259
|
+
* meta to the walker without an extra adapter round-trip.
|
|
260
|
+
*/
|
|
261
|
+
function extractRootMetaFromSchema(jsonSchema) {
|
|
262
|
+
const meta = {};
|
|
263
|
+
if (jsonSchema.readOnly === true) meta.readOnly = true;
|
|
264
|
+
if (jsonSchema.writeOnly === true) meta.writeOnly = true;
|
|
265
|
+
if (typeof jsonSchema.description === "string") meta.description = jsonSchema.description;
|
|
266
|
+
if (typeof jsonSchema.title === "string") meta.title = jsonSchema.title;
|
|
267
|
+
if (typeof jsonSchema.deprecated === "boolean") meta.deprecated = jsonSchema.deprecated;
|
|
268
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
269
|
+
}
|
|
265
270
|
//#endregion
|
|
266
271
|
export { ApiOperation, ApiParameters, ApiRequestBody, ApiResponse };
|
package/dist/openapi/parser.mjs
CHANGED
|
@@ -79,22 +79,22 @@ function getParameters(parsed, path, method) {
|
|
|
79
79
|
if (pathItem === void 0) return [];
|
|
80
80
|
const operation = getProperty(pathItem, method);
|
|
81
81
|
if (!isObject(operation)) return [];
|
|
82
|
-
const pathParams = extractParameterList(getProperty(pathItem, "parameters"));
|
|
83
|
-
const opParams = extractParameterList(getProperty(operation, "parameters"));
|
|
82
|
+
const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"));
|
|
83
|
+
const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"));
|
|
84
84
|
const map = /* @__PURE__ */ new Map();
|
|
85
85
|
for (const param of pathParams) map.set(`${param.name}:${param.location}`, param);
|
|
86
86
|
for (const param of opParams) map.set(`${param.name}:${param.location}`, param);
|
|
87
87
|
return [...map.values()];
|
|
88
88
|
}
|
|
89
|
-
function extractParameterList(parameters) {
|
|
89
|
+
function extractParameterList(doc, parameters) {
|
|
90
90
|
if (!Array.isArray(parameters)) return [];
|
|
91
91
|
const result = [];
|
|
92
92
|
for (const param of parameters) {
|
|
93
93
|
if (!isObject(param)) continue;
|
|
94
|
-
const
|
|
95
|
-
const
|
|
94
|
+
const resolved = resolveParam(doc, param);
|
|
95
|
+
const name = getProperty(resolved, "name");
|
|
96
|
+
const location = getProperty(resolved, "in");
|
|
96
97
|
if (typeof name !== "string" || typeof location !== "string") continue;
|
|
97
|
-
const resolved = resolveParam(param);
|
|
98
98
|
const schema = getProperty(resolved, "schema");
|
|
99
99
|
result.push({
|
|
100
100
|
name,
|
|
@@ -107,10 +107,10 @@ function extractParameterList(parameters) {
|
|
|
107
107
|
}
|
|
108
108
|
return result;
|
|
109
109
|
}
|
|
110
|
-
function resolveParam(param) {
|
|
110
|
+
function resolveParam(doc, param) {
|
|
111
111
|
const ref = getProperty(param, "$ref");
|
|
112
112
|
if (typeof ref === "string" && ref.startsWith("#/")) {
|
|
113
|
-
const resolved = resolveRefInDoc(
|
|
113
|
+
const resolved = resolveRefInDoc(doc, ref);
|
|
114
114
|
if (resolved !== void 0) return resolved;
|
|
115
115
|
}
|
|
116
116
|
return param;
|
|
@@ -2,8 +2,19 @@ import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequest
|
|
|
2
2
|
|
|
3
3
|
//#region src/openapi/resolve.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Parse and cache an OpenAPI document. Returns cached
|
|
6
|
-
*
|
|
5
|
+
* Parse and cache an OpenAPI document. Returns the cached parse for the
|
|
6
|
+
* same object identity.
|
|
7
|
+
*
|
|
8
|
+
* Before parsing, the document is run through the version-aware
|
|
9
|
+
* normalisation pipeline (`normaliseOpenApiSchemas`) so OpenAPI 3.0.x
|
|
10
|
+
* keywords (`nullable`, `discriminator`, `example`) and Swagger 2.0
|
|
11
|
+
* documents are converted to canonical Draft 2020-12 form. The parser
|
|
12
|
+
* and downstream extractors (`getRequestBody`, `getResponses`, etc.) then
|
|
13
|
+
* observe schemas in the same form `<SchemaComponent>` does, keeping the
|
|
14
|
+
* OpenAPI components on the same pipeline as the top-level adapter.
|
|
15
|
+
*
|
|
16
|
+
* The cache is keyed by the caller-supplied document so subsequent calls
|
|
17
|
+
* with the same input bypass both normalisation and parsing.
|
|
7
18
|
*/
|
|
8
19
|
declare function getParsed(doc: Record<string, unknown>): OpenApiDocument;
|
|
9
20
|
/**
|
package/dist/openapi/resolve.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isObject } from "../core/guards.mjs";
|
|
2
|
+
import { detectOpenApiVersion } from "../core/version.mjs";
|
|
3
|
+
import { i as normaliseOpenApiSchemas } from "../normalise-tL9FckAk.mjs";
|
|
2
4
|
import { getParameters, getRequestBody, getResponses, listOperations, parseOpenApiDocument } from "./parser.mjs";
|
|
3
5
|
//#region src/openapi/resolve.ts
|
|
4
6
|
/**
|
|
@@ -10,14 +12,28 @@ import { getParameters, getRequestBody, getResponses, listOperations, parseOpenA
|
|
|
10
12
|
*/
|
|
11
13
|
const docCache = /* @__PURE__ */ new WeakMap();
|
|
12
14
|
/**
|
|
13
|
-
* Parse and cache an OpenAPI document. Returns cached
|
|
14
|
-
*
|
|
15
|
+
* Parse and cache an OpenAPI document. Returns the cached parse for the
|
|
16
|
+
* same object identity.
|
|
17
|
+
*
|
|
18
|
+
* Before parsing, the document is run through the version-aware
|
|
19
|
+
* normalisation pipeline (`normaliseOpenApiSchemas`) so OpenAPI 3.0.x
|
|
20
|
+
* keywords (`nullable`, `discriminator`, `example`) and Swagger 2.0
|
|
21
|
+
* documents are converted to canonical Draft 2020-12 form. The parser
|
|
22
|
+
* and downstream extractors (`getRequestBody`, `getResponses`, etc.) then
|
|
23
|
+
* observe schemas in the same form `<SchemaComponent>` does, keeping the
|
|
24
|
+
* OpenAPI components on the same pipeline as the top-level adapter.
|
|
25
|
+
*
|
|
26
|
+
* The cache is keyed by the caller-supplied document so subsequent calls
|
|
27
|
+
* with the same input bypass both normalisation and parsing.
|
|
15
28
|
*/
|
|
16
29
|
function getParsed(doc) {
|
|
17
30
|
const cached = docCache.get(doc);
|
|
18
31
|
if (cached !== void 0) return cached;
|
|
19
|
-
const
|
|
32
|
+
const version = detectOpenApiVersion(doc);
|
|
33
|
+
const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version) : doc;
|
|
34
|
+
const parsed = parseOpenApiDocument(normalisedDoc);
|
|
20
35
|
docCache.set(doc, parsed);
|
|
36
|
+
if (normalisedDoc !== doc) docCache.set(normalisedDoc, parsed);
|
|
21
37
|
return parsed;
|
|
22
38
|
}
|
|
23
39
|
/**
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { M as WalkedField, T as SchemaMeta, d as FieldOverrides, u as FieldOverride } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { t as Diagnostic } from "../diagnostics-
|
|
2
|
+
import { t as Diagnostic } from "../diagnostics-BYk63jsC.mjs";
|
|
3
3
|
import { t as SchemaError } from "../errors-C5zRC2PU.mjs";
|
|
4
|
-
import { l as RenderProps, r as ComponentResolver } from "../renderer-
|
|
5
|
-
import { c as
|
|
4
|
+
import { l as RenderProps, r as ComponentResolver } from "../renderer-DXo-rXHJ.mjs";
|
|
5
|
+
import { c as PathOfType, l as ResolveOpenAPIRef, n as FromJSONSchema } from "../typeInference-5JiqIZ8t.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
8
7
|
import { ReactNode } from "react";
|
|
8
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
9
9
|
|
|
10
10
|
//#region src/react/SchemaComponent.d.ts
|
|
11
11
|
/**
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
3
3
|
import { SchemaFieldError, SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
|
|
4
4
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
5
|
-
import { getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
5
|
+
import { buildRenderProps, getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
6
6
|
import { walk } from "../core/walker.mjs";
|
|
7
7
|
import { headlessResolver } from "./headless.mjs";
|
|
8
8
|
import { resolvePath, resolveValue, setNestedValue } from "./fieldPath.mjs";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
10
|
import { createContext, isValidElement, useCallback, useContext, useId, useMemo } from "react";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
//#region src/react/SchemaComponent.tsx
|
|
13
13
|
/**
|
|
14
14
|
* <SchemaComponent> — renders UI from Zod, JSON Schema, or OpenAPI schemas.
|
|
@@ -203,35 +203,6 @@ function renderField(tree, value, onChange, userResolver, renderChild, path, ins
|
|
|
203
203
|
if (value === void 0 || value === null) return /* @__PURE__ */ jsx("span", { children: "—" });
|
|
204
204
|
return /* @__PURE__ */ jsx("span", { children: typeof value === "string" ? value : JSON.stringify(value) });
|
|
205
205
|
}
|
|
206
|
-
function buildRenderProps(tree, value, onChange, renderChild, path) {
|
|
207
|
-
const props = {
|
|
208
|
-
value,
|
|
209
|
-
onChange,
|
|
210
|
-
readOnly: tree.editability === "presentation",
|
|
211
|
-
writeOnly: tree.editability === "input",
|
|
212
|
-
meta: tree.meta,
|
|
213
|
-
constraints: tree.constraints,
|
|
214
|
-
path,
|
|
215
|
-
tree,
|
|
216
|
-
renderChild
|
|
217
|
-
};
|
|
218
|
-
if (tree.type === "enum") props.enumValues = tree.enumValues;
|
|
219
|
-
if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
|
|
220
|
-
if (tree.type === "object") props.fields = tree.fields;
|
|
221
|
-
if (tree.type === "union" || tree.type === "discriminatedUnion") props.options = tree.options;
|
|
222
|
-
if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
|
|
223
|
-
if (tree.type === "record") props.keyType = tree.keyType;
|
|
224
|
-
if (tree.type === "record") props.valueType = tree.valueType;
|
|
225
|
-
if (tree.type === "tuple") props.prefixItems = tree.prefixItems;
|
|
226
|
-
if (tree.type === "conditional") props.ifClause = tree.ifClause;
|
|
227
|
-
if (tree.type === "conditional" && tree.thenClause !== void 0) props.thenClause = tree.thenClause;
|
|
228
|
-
if (tree.type === "conditional" && tree.elseClause !== void 0) props.elseClause = tree.elseClause;
|
|
229
|
-
if (tree.type === "negation") props.negated = tree.negated;
|
|
230
|
-
if (tree.type === "recursive") props.refTarget = tree.refTarget;
|
|
231
|
-
if (tree.type === "literal") props.literalValues = tree.literalValues;
|
|
232
|
-
if (tree.examples !== void 0) props.examples = tree.examples;
|
|
233
|
-
return props;
|
|
234
|
-
}
|
|
235
206
|
function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange, meta: fieldMeta, validate, onValidationError }) {
|
|
236
207
|
const userResolver = useContext(UserResolverContext);
|
|
237
208
|
const contextWidgets = useContext(WidgetsContext);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { t as Diagnostic } from "../diagnostics-
|
|
3
|
-
import { r as ComponentResolver } from "../renderer-
|
|
2
|
+
import { t as Diagnostic } from "../diagnostics-BYk63jsC.mjs";
|
|
3
|
+
import { r as ComponentResolver } from "../renderer-DXo-rXHJ.mjs";
|
|
4
4
|
import { WidgetMap } from "./SchemaComponent.mjs";
|
|
5
5
|
import { ReactNode } from "react";
|
|
6
6
|
|
|
@@ -38,7 +38,8 @@ interface SchemaViewProps {
|
|
|
38
38
|
idPrefix?: string;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* Server-safe schema renderer — no
|
|
41
|
+
* Server-safe schema renderer — no context and no state. The only hook
|
|
42
|
+
* called is `useId()`, which is RSC-safe.
|
|
42
43
|
*
|
|
43
44
|
* Always renders in read-only mode. For editable forms, use
|
|
44
45
|
* `<SchemaComponent>` with `"use client"`.
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
|
|
2
2
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
3
|
-
import { getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
3
|
+
import { buildRenderProps, getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
4
4
|
import { walk } from "../core/walker.mjs";
|
|
5
5
|
import { headlessResolver } from "./headless.mjs";
|
|
6
6
|
import { joinPath, sanitisePrefix } from "./SchemaComponent.mjs";
|
|
7
|
-
import { jsx } from "react/jsx-runtime";
|
|
8
7
|
import { createElement, isValidElement, useId } from "react";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
9
|
//#region src/react/SchemaView.tsx
|
|
10
10
|
/**
|
|
11
11
|
* React Server Component for read-only schema rendering.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Uses no React state, context, or effects — no `useContext`, `useMemo`,
|
|
14
|
+
* `useCallback`, `useState`, or `useEffect`. The single hook called is
|
|
15
|
+
* `useId()`, which is one of the few hooks permitted inside a React
|
|
16
|
+
* Server Component (it is RSC-safe by design) and is used solely to
|
|
17
|
+
* derive a stable per-instance `idPrefix`. The component therefore runs
|
|
18
|
+
* in an RSC environment without the `"use client"` directive.
|
|
16
19
|
*
|
|
17
20
|
* **Read-only only.** For interactive forms with `onChange`, use
|
|
18
21
|
* `<SchemaComponent>` (which requires `"use client"`).
|
|
@@ -31,9 +34,9 @@ import { createElement, isValidElement, useId } from "react";
|
|
|
31
34
|
* Server Components cannot use React context, so the resolver
|
|
32
35
|
* is passed explicitly.
|
|
33
36
|
*/
|
|
34
|
-
function noop() {}
|
|
35
37
|
/**
|
|
36
|
-
* Server-safe schema renderer — no
|
|
38
|
+
* Server-safe schema renderer — no context and no state. The only hook
|
|
39
|
+
* called is `useId()`, which is RSC-safe.
|
|
37
40
|
*
|
|
38
41
|
* Always renders in read-only mode. For editable forms, use
|
|
39
42
|
* `<SchemaComponent>` with `"use client"`.
|
|
@@ -81,22 +84,12 @@ function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: c
|
|
|
81
84
|
}
|
|
82
85
|
function renderFieldServer(tree, value, resolver, renderChild, path, widgets) {
|
|
83
86
|
if (path.length === 0) throw new Error("renderFieldServer requires a non-empty path. Pass ROOT_PATH at the root and join children via joinPath().");
|
|
87
|
+
const adaptedRenderChild = (childTree, childValue, _childOnChange, pathSuffix) => renderChild(childTree, childValue, pathSuffix);
|
|
84
88
|
const componentHint = tree.meta.component;
|
|
85
89
|
if (typeof componentHint === "string") {
|
|
86
90
|
const widget = widgets?.get(componentHint);
|
|
87
91
|
if (widget !== void 0) {
|
|
88
|
-
const
|
|
89
|
-
const result = widget({
|
|
90
|
-
value,
|
|
91
|
-
onChange: noop,
|
|
92
|
-
readOnly: true,
|
|
93
|
-
writeOnly: false,
|
|
94
|
-
meta: tree.meta,
|
|
95
|
-
constraints: tree.constraints,
|
|
96
|
-
path,
|
|
97
|
-
tree,
|
|
98
|
-
renderChild: wrapRenderChild
|
|
99
|
-
});
|
|
92
|
+
const result = widget(buildRenderProps(tree, value, void 0, adaptedRenderChild, path));
|
|
100
93
|
if (result !== void 0 && result !== null) {
|
|
101
94
|
if (isValidElement(result)) return result;
|
|
102
95
|
if (typeof result === "string" || typeof result === "number") return result;
|
|
@@ -105,31 +98,7 @@ function renderFieldServer(tree, value, resolver, renderChild, path, widgets) {
|
|
|
105
98
|
}
|
|
106
99
|
const renderFn = getRenderFunction(tree.type, resolver);
|
|
107
100
|
if (renderFn !== void 0) {
|
|
108
|
-
const props =
|
|
109
|
-
value,
|
|
110
|
-
onChange: noop,
|
|
111
|
-
readOnly: true,
|
|
112
|
-
writeOnly: false,
|
|
113
|
-
meta: tree.meta,
|
|
114
|
-
constraints: tree.constraints,
|
|
115
|
-
path,
|
|
116
|
-
tree,
|
|
117
|
-
renderChild: (childTree, childValue, _childOnChange, pathSuffix) => renderChild(childTree, childValue, pathSuffix)
|
|
118
|
-
};
|
|
119
|
-
if (tree.type === "enum") props.enumValues = tree.enumValues;
|
|
120
|
-
if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
|
|
121
|
-
if (tree.type === "object") props.fields = tree.fields;
|
|
122
|
-
if (tree.type === "union" || tree.type === "discriminatedUnion") props.options = tree.options;
|
|
123
|
-
if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
|
|
124
|
-
if (tree.type === "record") props.keyType = tree.keyType;
|
|
125
|
-
if (tree.type === "record") props.valueType = tree.valueType;
|
|
126
|
-
if (tree.type === "tuple") props.prefixItems = tree.prefixItems;
|
|
127
|
-
if (tree.type === "conditional") props.ifClause = tree.ifClause;
|
|
128
|
-
if (tree.type === "conditional" && tree.thenClause !== void 0) props.thenClause = tree.thenClause;
|
|
129
|
-
if (tree.type === "conditional" && tree.elseClause !== void 0) props.elseClause = tree.elseClause;
|
|
130
|
-
if (tree.type === "negation") props.negated = tree.negated;
|
|
131
|
-
if (tree.type === "recursive") props.refTarget = tree.refTarget;
|
|
132
|
-
if (tree.type === "literal") props.literalValues = tree.literalValues;
|
|
101
|
+
const props = buildRenderProps(tree, value, void 0, adaptedRenderChild, path);
|
|
133
102
|
try {
|
|
134
103
|
const result = renderFn(props);
|
|
135
104
|
if (result !== void 0 && result !== null) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isObject } from "../core/guards.mjs";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
2
|
import { isValidElement, useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/react/headlessRenderers.tsx
|
|
5
5
|
/**
|
|
6
6
|
* Headless renderer functions — one per schema type.
|
|
@@ -496,7 +496,7 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, d
|
|
|
496
496
|
}, [
|
|
497
497
|
optionLabels,
|
|
498
498
|
discKey,
|
|
499
|
-
props
|
|
499
|
+
props.onChange
|
|
500
500
|
]);
|
|
501
501
|
const wrapIndex = useCallback((index) => (index % options.length + options.length) % options.length, [options.length]);
|
|
502
502
|
const handleKeyDown = useCallback((e) => {
|
|
@@ -537,6 +537,7 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, d
|
|
|
537
537
|
},
|
|
538
538
|
type: "button",
|
|
539
539
|
role: "tab",
|
|
540
|
+
id: `${panelId}-tab-${String(i)}`,
|
|
540
541
|
"aria-selected": i === activeIndex ? "true" : void 0,
|
|
541
542
|
"aria-controls": `${panelId}-panel`,
|
|
542
543
|
tabIndex: i === activeIndex ? 0 : -1,
|
|
@@ -99,6 +99,23 @@ interface HtmlRenderProps extends BaseFieldProps {
|
|
|
99
99
|
*/
|
|
100
100
|
renderChild: (tree: WalkedField, value: unknown, pathSuffix?: string) => string;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Build the `RenderProps` object handed to a resolver render function or a
|
|
104
|
+
* widget. Used by both the server-side `<SchemaView>` (which has no
|
|
105
|
+
* `onChange`) and the client-side `<SchemaComponent>` (which threads an
|
|
106
|
+
* `onChange` callback).
|
|
107
|
+
*
|
|
108
|
+
* When `onChange` is `undefined` the caller is rendering in read-only mode:
|
|
109
|
+
* a noop `onChange` is wired up, `readOnly` is forced to `true`, and
|
|
110
|
+
* `writeOnly` is forced to `false`. Otherwise the editability is taken
|
|
111
|
+
* from `tree.editability`.
|
|
112
|
+
*
|
|
113
|
+
* The duplicate sibling fields (`enumValues`, `element`, `fields`, etc.)
|
|
114
|
+
* are populated for backwards compatibility with renderers that have not
|
|
115
|
+
* yet migrated to reading from `tree` directly. New renderers should
|
|
116
|
+
* narrow on `tree.type` and read from `tree`.
|
|
117
|
+
*/
|
|
118
|
+
declare function buildRenderProps(tree: WalkedField, value: unknown, onChange: ((next: unknown) => void) | undefined, renderChild: RenderProps["renderChild"], path: string): RenderProps;
|
|
102
119
|
type RenderFunction = (props: RenderProps) => unknown;
|
|
103
120
|
interface ComponentResolver {
|
|
104
121
|
string?: RenderFunction;
|
|
@@ -166,4 +183,4 @@ declare function mergeResolvers(user: ComponentResolver, fallback: ComponentReso
|
|
|
166
183
|
*/
|
|
167
184
|
declare function mergeHtmlResolvers(user: HtmlResolver, fallback: HtmlResolver): HtmlResolver;
|
|
168
185
|
//#endregion
|
|
169
|
-
export { HtmlRenderProps as a, RenderFunction as c,
|
|
186
|
+
export { HtmlRenderProps as a, RenderFunction as c, getHtmlRenderFn as d, getRenderFunction as f, typeToKey as h, HtmlRenderFunction as i, RenderProps as l, mergeResolvers as m, BaseFieldProps as n, HtmlResolver as o, mergeHtmlResolvers as p, ComponentResolver as r, RESOLVER_KEYS as s, AllConstraints as t, buildRenderProps as u };
|
package/dist/themes/mantine.mjs
CHANGED
|
@@ -103,6 +103,7 @@ function renderEnumInput(props) {
|
|
|
103
103
|
id,
|
|
104
104
|
children: enumValue || "—"
|
|
105
105
|
});
|
|
106
|
+
const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
|
|
106
107
|
return /* @__PURE__ */ jsx(MantineSelect, {
|
|
107
108
|
id,
|
|
108
109
|
label,
|
|
@@ -110,15 +111,15 @@ function renderEnumInput(props) {
|
|
|
110
111
|
onChange: (v) => {
|
|
111
112
|
if (typeof v === "string") props.onChange(v);
|
|
112
113
|
},
|
|
113
|
-
data:
|
|
114
|
+
data: enumValues.map((v) => ({
|
|
114
115
|
value: v,
|
|
115
116
|
label: v
|
|
116
117
|
}))
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
120
|
function renderObjectContainer(props) {
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
if (props.tree.type !== "object") return null;
|
|
122
|
+
const fields = props.tree.fields;
|
|
122
123
|
const obj = isObject(props.value) ? props.value : {};
|
|
123
124
|
return /* @__PURE__ */ jsx(MantineFieldset, {
|
|
124
125
|
legend: getLabel(props),
|
package/dist/themes/mui.d.mts
CHANGED