schema-components 1.16.2 → 1.17.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/renderer.d.mts +1 -1
- package/dist/html/a11y.d.mts +1 -1
- package/dist/html/renderToHtml.d.mts +1 -1
- package/dist/html/renderToHtmlStream.d.mts +1 -1
- package/dist/html/renderers.d.mts +1 -1
- package/dist/html/streamRenderers.d.mts +1 -1
- package/dist/openapi/components.mjs +42 -13
- package/dist/react/SchemaComponent.d.mts +32 -4
- package/dist/react/SchemaComponent.mjs +50 -15
- package/dist/react/SchemaView.d.mts +9 -2
- package/dist/react/SchemaView.mjs +17 -11
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +41 -2
- package/dist/react/headlessRenderers.mjs +171 -53
- package/dist/{renderer-BdSqllx5.d.mts → renderer-B3s8o2B8.d.mts} +10 -1
- package/dist/themes/mantine.d.mts +6 -1
- package/dist/themes/mantine.mjs +40 -8
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +17 -3
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +41 -9
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +22 -5
- package/package.json +5 -2
package/dist/themes/radix.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isObject } from "../core/guards.mjs";
|
|
2
|
-
import { toReactNode } from "../react/headlessRenderers.mjs";
|
|
2
|
+
import { inputId, toReactNode } from "../react/headlessRenderers.mjs";
|
|
3
3
|
import { headlessResolver } from "../react/headless.mjs";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/themes/radix.tsx
|
|
@@ -41,13 +41,19 @@ function registerRadixComponents(components) {
|
|
|
41
41
|
function renderStringInput(props) {
|
|
42
42
|
const strValue = typeof props.value === "string" ? props.value : "";
|
|
43
43
|
const label = getLabel(props);
|
|
44
|
-
|
|
44
|
+
const id = inputId(props.path);
|
|
45
|
+
if (props.readOnly) return /* @__PURE__ */ jsx(RadixText, {
|
|
46
|
+
id,
|
|
47
|
+
children: strValue || "—"
|
|
48
|
+
});
|
|
45
49
|
return /* @__PURE__ */ jsxs(RadixBox, { children: [label !== void 0 && /* @__PURE__ */ jsx(RadixText, {
|
|
46
50
|
as: "label",
|
|
47
51
|
size: "2",
|
|
48
52
|
weight: "medium",
|
|
53
|
+
htmlFor: id,
|
|
49
54
|
children: label
|
|
50
55
|
}), /* @__PURE__ */ jsx(RadixTextField, {
|
|
56
|
+
id,
|
|
51
57
|
type: props.constraints.format === "email" ? "email" : props.constraints.format === "uri" ? "url" : "text",
|
|
52
58
|
value: props.writeOnly ? "" : strValue,
|
|
53
59
|
onChange: (e) => {
|
|
@@ -58,16 +64,25 @@ function renderStringInput(props) {
|
|
|
58
64
|
}
|
|
59
65
|
function renderNumberInput(props) {
|
|
60
66
|
const label = getLabel(props);
|
|
67
|
+
const id = inputId(props.path);
|
|
61
68
|
if (props.readOnly) {
|
|
62
|
-
if (typeof props.value !== "number") return /* @__PURE__ */ jsx(RadixText, {
|
|
63
|
-
|
|
69
|
+
if (typeof props.value !== "number") return /* @__PURE__ */ jsx(RadixText, {
|
|
70
|
+
id,
|
|
71
|
+
children: "—"
|
|
72
|
+
});
|
|
73
|
+
return /* @__PURE__ */ jsx(RadixText, {
|
|
74
|
+
id,
|
|
75
|
+
children: props.value.toLocaleString()
|
|
76
|
+
});
|
|
64
77
|
}
|
|
65
78
|
return /* @__PURE__ */ jsxs(RadixBox, { children: [label !== void 0 && /* @__PURE__ */ jsx(RadixText, {
|
|
66
79
|
as: "label",
|
|
67
80
|
size: "2",
|
|
68
81
|
weight: "medium",
|
|
82
|
+
htmlFor: id,
|
|
69
83
|
children: label
|
|
70
84
|
}), /* @__PURE__ */ jsx(RadixTextField, {
|
|
85
|
+
id,
|
|
71
86
|
type: "number",
|
|
72
87
|
value: props.writeOnly ? "" : typeof props.value === "number" ? props.value : "",
|
|
73
88
|
onChange: (e) => {
|
|
@@ -78,20 +93,29 @@ function renderNumberInput(props) {
|
|
|
78
93
|
}
|
|
79
94
|
function renderBooleanInput(props) {
|
|
80
95
|
const label = getLabel(props);
|
|
96
|
+
const id = inputId(props.path);
|
|
81
97
|
if (props.readOnly) {
|
|
82
|
-
if (typeof props.value !== "boolean") return /* @__PURE__ */ jsx(RadixText, {
|
|
83
|
-
|
|
98
|
+
if (typeof props.value !== "boolean") return /* @__PURE__ */ jsx(RadixText, {
|
|
99
|
+
id,
|
|
100
|
+
children: "—"
|
|
101
|
+
});
|
|
102
|
+
return /* @__PURE__ */ jsx(RadixText, {
|
|
103
|
+
id,
|
|
104
|
+
children: props.value ? "Yes" : "No"
|
|
105
|
+
});
|
|
84
106
|
}
|
|
85
107
|
return /* @__PURE__ */ jsxs(RadixFlex, {
|
|
86
108
|
align: "center",
|
|
87
109
|
gap: "2",
|
|
88
110
|
children: [/* @__PURE__ */ jsx(RadixCheckbox, {
|
|
111
|
+
id,
|
|
89
112
|
checked: props.writeOnly ? false : props.value === true,
|
|
90
113
|
onCheckedChange: (checked) => {
|
|
91
114
|
if (typeof checked === "boolean") props.onChange(checked);
|
|
92
115
|
}
|
|
93
116
|
}), label !== void 0 && /* @__PURE__ */ jsx(RadixText, {
|
|
94
117
|
as: "label",
|
|
118
|
+
htmlFor: id,
|
|
95
119
|
children: label
|
|
96
120
|
})]
|
|
97
121
|
});
|
|
@@ -99,18 +123,26 @@ function renderBooleanInput(props) {
|
|
|
99
123
|
function renderEnumInput(props) {
|
|
100
124
|
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
101
125
|
const label = getLabel(props);
|
|
102
|
-
|
|
126
|
+
const id = inputId(props.path);
|
|
127
|
+
if (props.readOnly) return /* @__PURE__ */ jsx(RadixText, {
|
|
128
|
+
id,
|
|
129
|
+
children: enumValue || "—"
|
|
130
|
+
});
|
|
103
131
|
return /* @__PURE__ */ jsxs(RadixBox, { children: [label !== void 0 && /* @__PURE__ */ jsx(RadixText, {
|
|
104
132
|
as: "label",
|
|
105
133
|
size: "2",
|
|
106
134
|
weight: "medium",
|
|
135
|
+
htmlFor: id,
|
|
107
136
|
children: label
|
|
108
137
|
}), /* @__PURE__ */ jsxs(RadixSelectRoot, {
|
|
109
138
|
value: props.writeOnly ? "" : enumValue,
|
|
110
139
|
onValueChange: (value) => {
|
|
111
140
|
props.onChange(value);
|
|
112
141
|
},
|
|
113
|
-
children: [/* @__PURE__ */ jsx(RadixSelectTrigger, {
|
|
142
|
+
children: [/* @__PURE__ */ jsx(RadixSelectTrigger, {
|
|
143
|
+
id,
|
|
144
|
+
mt: "1"
|
|
145
|
+
}), /* @__PURE__ */ jsx(RadixSelectContent, { children: (props.enumValues ?? []).map((value) => /* @__PURE__ */ jsx(RadixSelectItem, {
|
|
114
146
|
value,
|
|
115
147
|
children: value
|
|
116
148
|
}, value)) })]
|
|
@@ -137,7 +169,7 @@ function renderObjectContainer(props) {
|
|
|
137
169
|
updated[key] = v;
|
|
138
170
|
props.onChange(updated);
|
|
139
171
|
};
|
|
140
|
-
return /* @__PURE__ */ jsx(RadixBox, { children: toReactNode(props.renderChild(field, childValue, childOnChange)) }, key);
|
|
172
|
+
return /* @__PURE__ */ jsx(RadixBox, { children: toReactNode(props.renderChild(field, childValue, childOnChange, key)) }, key);
|
|
141
173
|
})
|
|
142
174
|
})] });
|
|
143
175
|
}
|
package/dist/themes/shadcn.d.mts
CHANGED
package/dist/themes/shadcn.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toRecord } from "../core/guards.mjs";
|
|
2
|
-
import { toReactNode } from "../react/headlessRenderers.mjs";
|
|
2
|
+
import { inputId, toReactNode } from "../react/headlessRenderers.mjs";
|
|
3
3
|
import { headlessResolver } from "../react/headless.mjs";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/themes/shadcn.tsx
|
|
@@ -11,12 +11,15 @@ function buildClassNames(...classes) {
|
|
|
11
11
|
}
|
|
12
12
|
function renderStringInput(props) {
|
|
13
13
|
const strValue = typeof props.value === "string" ? props.value : "";
|
|
14
|
+
const id = inputId(props.path);
|
|
14
15
|
const className = buildClassNames("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground", "placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50");
|
|
15
16
|
if (props.readOnly) return /* @__PURE__ */ jsx("span", {
|
|
17
|
+
id,
|
|
16
18
|
className: "text-sm",
|
|
17
19
|
children: strValue || "—"
|
|
18
20
|
});
|
|
19
21
|
if (props.writeOnly) return /* @__PURE__ */ jsx("input", {
|
|
22
|
+
id,
|
|
20
23
|
type: props.constraints.format === "email" ? "email" : "text",
|
|
21
24
|
className,
|
|
22
25
|
placeholder: typeof props.meta.description === "string" ? props.meta.description : void 0,
|
|
@@ -26,6 +29,7 @@ function renderStringInput(props) {
|
|
|
26
29
|
}
|
|
27
30
|
});
|
|
28
31
|
return /* @__PURE__ */ jsx("input", {
|
|
32
|
+
id,
|
|
29
33
|
type: props.constraints.format === "email" ? "email" : "text",
|
|
30
34
|
className,
|
|
31
35
|
value: strValue,
|
|
@@ -38,18 +42,22 @@ function renderStringInput(props) {
|
|
|
38
42
|
});
|
|
39
43
|
}
|
|
40
44
|
function renderNumberInput(props) {
|
|
45
|
+
const id = inputId(props.path);
|
|
41
46
|
const className = buildClassNames("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50");
|
|
42
47
|
if (props.readOnly) {
|
|
43
48
|
if (typeof props.value !== "number") return /* @__PURE__ */ jsx("span", {
|
|
49
|
+
id,
|
|
44
50
|
className: "text-sm",
|
|
45
51
|
children: "—"
|
|
46
52
|
});
|
|
47
53
|
return /* @__PURE__ */ jsx("span", {
|
|
54
|
+
id,
|
|
48
55
|
className: "text-sm",
|
|
49
56
|
children: props.value.toLocaleString()
|
|
50
57
|
});
|
|
51
58
|
}
|
|
52
59
|
return /* @__PURE__ */ jsx("input", {
|
|
60
|
+
id,
|
|
53
61
|
type: "number",
|
|
54
62
|
className,
|
|
55
63
|
value: props.writeOnly ? "" : typeof props.value === "number" ? props.value : "",
|
|
@@ -61,18 +69,22 @@ function renderNumberInput(props) {
|
|
|
61
69
|
});
|
|
62
70
|
}
|
|
63
71
|
function renderBooleanInput(props) {
|
|
72
|
+
const id = inputId(props.path);
|
|
64
73
|
const className = buildClassNames("h-4 w-4 rounded border border-primary shadow", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50");
|
|
65
74
|
if (props.readOnly) {
|
|
66
75
|
if (typeof props.value !== "boolean") return /* @__PURE__ */ jsx("span", {
|
|
76
|
+
id,
|
|
67
77
|
className: "text-sm",
|
|
68
78
|
children: "—"
|
|
69
79
|
});
|
|
70
80
|
return /* @__PURE__ */ jsx("span", {
|
|
81
|
+
id,
|
|
71
82
|
className: "text-sm",
|
|
72
83
|
children: props.value ? "Yes" : "No"
|
|
73
84
|
});
|
|
74
85
|
}
|
|
75
86
|
return /* @__PURE__ */ jsx("input", {
|
|
87
|
+
id,
|
|
76
88
|
type: "checkbox",
|
|
77
89
|
className,
|
|
78
90
|
checked: props.writeOnly ? false : props.value === true,
|
|
@@ -92,6 +104,7 @@ function renderObjectContainer(props) {
|
|
|
92
104
|
children: props.meta.description
|
|
93
105
|
}), Object.entries(fields).map(([key, field]) => {
|
|
94
106
|
const childValue = toRecord(obj)[key];
|
|
107
|
+
const childId = inputId(`${props.path}.${key}`);
|
|
95
108
|
const childOnChange = (v) => {
|
|
96
109
|
const updated = {};
|
|
97
110
|
for (const [k, val] of Object.entries(obj)) updated[k] = val;
|
|
@@ -101,9 +114,10 @@ function renderObjectContainer(props) {
|
|
|
101
114
|
return /* @__PURE__ */ jsxs("div", {
|
|
102
115
|
className: "space-y-1",
|
|
103
116
|
children: [/* @__PURE__ */ jsx("label", {
|
|
117
|
+
htmlFor: childId,
|
|
104
118
|
className: "text-sm font-medium leading-none",
|
|
105
119
|
children: field.meta.description ?? key
|
|
106
|
-
}), toReactNode(props.renderChild(field, childValue, childOnChange))]
|
|
120
|
+
}), toReactNode(props.renderChild(field, childValue, childOnChange, key))]
|
|
107
121
|
}, key);
|
|
108
122
|
})]
|
|
109
123
|
});
|
|
@@ -120,26 +134,29 @@ function renderArrayContainer(props) {
|
|
|
120
134
|
next[i] = v;
|
|
121
135
|
props.onChange(next);
|
|
122
136
|
};
|
|
123
|
-
return /* @__PURE__ */ jsx("div", { children: toReactNode(props.renderChild(element, item, childOnChange)) }, String(i));
|
|
137
|
+
return /* @__PURE__ */ jsx("div", { children: toReactNode(props.renderChild(element, item, childOnChange, `[${String(i)}]`)) }, String(i));
|
|
124
138
|
})
|
|
125
139
|
});
|
|
126
140
|
}
|
|
127
141
|
function renderEnumInput(props) {
|
|
142
|
+
const id = inputId(props.path);
|
|
128
143
|
const className = buildClassNames("flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm", "focus:outline-none focus:ring-1 focus:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50");
|
|
129
144
|
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
130
145
|
if (props.readOnly) return /* @__PURE__ */ jsx("span", {
|
|
146
|
+
id,
|
|
131
147
|
className: "text-sm",
|
|
132
148
|
children: enumValue || "—"
|
|
133
149
|
});
|
|
134
150
|
return /* @__PURE__ */ jsxs("select", {
|
|
151
|
+
id,
|
|
135
152
|
className,
|
|
136
153
|
value: props.writeOnly ? "" : enumValue,
|
|
137
154
|
onChange: (e) => {
|
|
138
155
|
props.onChange(e.target.value);
|
|
139
156
|
},
|
|
140
|
-
children: [/* @__PURE__ */
|
|
157
|
+
children: [/* @__PURE__ */ jsxs("option", {
|
|
141
158
|
value: "",
|
|
142
|
-
children: "Select
|
|
159
|
+
children: ["Select", "…"]
|
|
143
160
|
}), props.enumValues?.map((v) => {
|
|
144
161
|
const display = v === null ? "null" : typeof v === "string" ? v : String(v);
|
|
145
162
|
return /* @__PURE__ */ jsx("option", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schema-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "React components that render UI from Zod schemas, JSON Schema, and OpenAPI documents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -78,13 +78,16 @@
|
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@eslint/js": "10.0.1",
|
|
81
|
-
"@
|
|
81
|
+
"@testing-library/react": "16.3.2",
|
|
82
|
+
"@testing-library/user-event": "14.6.1",
|
|
83
|
+
"@types/node": "25.6.2",
|
|
82
84
|
"@types/react": "19.2.14",
|
|
83
85
|
"@types/react-dom": "19.2.3",
|
|
84
86
|
"@vitest/coverage-v8": "4.1.5",
|
|
85
87
|
"eslint": "10.3.0",
|
|
86
88
|
"eslint-config-prettier": "10.1.8",
|
|
87
89
|
"eslint-plugin-prettier": "5.5.5",
|
|
90
|
+
"happy-dom": "20.9.0",
|
|
88
91
|
"prettier": "3.8.3",
|
|
89
92
|
"react": "19.2.6",
|
|
90
93
|
"react-dom": "19.2.6",
|