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.
@@ -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
- if (props.readOnly) return /* @__PURE__ */ jsx(RadixText, { children: strValue || "—" });
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, { children: "—" });
63
- return /* @__PURE__ */ jsx(RadixText, { children: props.value.toLocaleString() });
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, { children: "—" });
83
- return /* @__PURE__ */ jsx(RadixText, { children: props.value ? "Yes" : "No" });
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
- if (props.readOnly) return /* @__PURE__ */ jsx(RadixText, { children: enumValue || "—" });
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, { mt: "1" }), /* @__PURE__ */ jsx(RadixSelectContent, { children: (props.enumValues ?? []).map((value) => /* @__PURE__ */ jsx(RadixSelectItem, {
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
  }
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-BdSqllx5.mjs";
1
+ import { r as ComponentResolver } from "../renderer-B3s8o2B8.mjs";
2
2
 
3
3
  //#region src/themes/shadcn.d.ts
4
4
  declare const shadcnResolver: ComponentResolver;
@@ -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__ */ jsx("option", {
157
+ children: [/* @__PURE__ */ jsxs("option", {
141
158
  value: "",
142
- children: "Select\\u2026"
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.16.2",
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
- "@types/node": "25.6.0",
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",