schema-components 1.6.0 → 1.7.1
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/CHANGELOG.md +12 -0
- package/README.md +61 -0
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/types.d.mts +1 -1
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +3 -1
- package/dist/html/a11y.d.mts +1 -1
- package/dist/html/renderToHtml.d.mts +1 -1
- package/dist/html/renderToHtml.mjs +6 -2
- package/dist/openapi/components.d.mts +1 -1
- package/dist/openapi/parser.d.mts +1 -1
- package/dist/react/SchemaComponent.d.mts +1 -1
- package/dist/react/SchemaComponent.mjs +1 -0
- package/dist/react/SchemaView.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headless.mjs +4 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/{types-CnlV7bBK.d.mts → types-BJzEgJdX.d.mts} +6 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.7.1](https://github.com/Mearman/schema-components/compare/v1.7.0...v1.7.1) (2026-05-14)
|
|
2
|
+
|
|
3
|
+
### Documentation
|
|
4
|
+
|
|
5
|
+
* update README and add Storybook stories for visibility, ordering, and per-field validation ([4be4b6f](https://github.com/Mearman/schema-components/commit/4be4b6feaaa4819937280c010eacb3f36a521a54))
|
|
6
|
+
|
|
7
|
+
## [1.7.0](https://github.com/Mearman/schema-components/compare/v1.6.0...v1.7.0) (2026-05-14)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add field visibility and ordering controls ([27cb3e1](https://github.com/Mearman/schema-components/commit/27cb3e19ba930200116c79293078fa6fa2743723))
|
|
12
|
+
|
|
1
13
|
## [1.6.0](https://github.com/Mearman/schema-components/compare/v1.5.1...v1.6.0) (2026-05-14)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/README.md
CHANGED
|
@@ -525,6 +525,25 @@ Server Components: `<SchemaView>` accepts a `widgets` prop directly (no React co
|
|
|
525
525
|
|
|
526
526
|
Validation uses the original Zod schema (if input was Zod) or `z.fromJSONSchema()` (if input was JSON Schema / OpenAPI).
|
|
527
527
|
|
|
528
|
+
### Per-field validation errors
|
|
529
|
+
|
|
530
|
+
Add `onValidationError` to individual field overrides to receive errors for specific fields:
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
<SchemaComponent
|
|
534
|
+
schema={userSchema}
|
|
535
|
+
value={user}
|
|
536
|
+
onChange={setUser}
|
|
537
|
+
validate
|
|
538
|
+
fields={{
|
|
539
|
+
email: { onValidationError: (err) => setEmailError(err) },
|
|
540
|
+
name: { onValidationError: (err) => setNameError(err) },
|
|
541
|
+
}}
|
|
542
|
+
/>
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Errors are dispatched based on Zod error paths. The root-level `onValidationError` still receives all errors.
|
|
546
|
+
|
|
528
547
|
## Discriminated unions
|
|
529
548
|
|
|
530
549
|
Discriminated unions (`z.discriminatedUnion` or JSON Schema `oneOf` with `const` properties) render as tabbed panels. Each tab is labelled by the discriminator's `const` value. Clicking a tab resets the value with the new discriminator.
|
|
@@ -578,6 +597,48 @@ const schema = z.object({
|
|
|
578
597
|
|
|
579
598
|
Defaults propagate through nested objects — each field uses its own default independently.
|
|
580
599
|
|
|
600
|
+
## Field visibility
|
|
601
|
+
|
|
602
|
+
Hide fields conditionally using the `visible` override:
|
|
603
|
+
|
|
604
|
+
```tsx
|
|
605
|
+
<SchemaComponent
|
|
606
|
+
schema={paymentSchema}
|
|
607
|
+
value={payment}
|
|
608
|
+
fields={{
|
|
609
|
+
cardNumber: { visible: payment.method === "card" },
|
|
610
|
+
sortCode: { visible: payment.method === "bank" },
|
|
611
|
+
}}
|
|
612
|
+
/>
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
When `visible: false`, the field is completely removed — no label, no empty placeholder, no hidden input. The parent component controls visibility based on the current value.
|
|
616
|
+
|
|
617
|
+
## Field ordering
|
|
618
|
+
|
|
619
|
+
Control the order fields appear in rendered objects using `order`:
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
<SchemaComponent
|
|
623
|
+
schema={userSchema}
|
|
624
|
+
value={user}
|
|
625
|
+
fields={{
|
|
626
|
+
email: { order: 1 },
|
|
627
|
+
name: { order: 2 },
|
|
628
|
+
role: { order: 3 },
|
|
629
|
+
}}
|
|
630
|
+
/>
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Lower `order` values render first. Fields without `order` keep their insertion order and appear after ordered fields. Can also be set in schema metadata:
|
|
634
|
+
|
|
635
|
+
```tsx
|
|
636
|
+
const schema = z.object({
|
|
637
|
+
summary: z.string().meta({ order: 1 }),
|
|
638
|
+
title: z.string().meta({ order: 2 }),
|
|
639
|
+
});
|
|
640
|
+
```
|
|
641
|
+
|
|
581
642
|
## Server Components
|
|
582
643
|
|
|
583
644
|
For read-only rendering in a React Server Component, use `<SchemaView>`. It has zero hooks — no `useContext`, no `useMemo`, no `useCallback` — so it works without the `"use client"` directive.
|
package/dist/core/adapter.d.mts
CHANGED
package/dist/core/renderer.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as mergeResolvers, C as HtmlResolver, D as getHtmlRenderFn, E as RenderProps, O as getRenderFunction, S as HtmlRenderProps, T as RenderFunction, b as ComponentResolver, j as typeToKey, k as mergeHtmlResolvers, w as RESOLVER_KEYS, x as HtmlRenderFunction, y as BaseFieldProps } from "../types-
|
|
1
|
+
import { A as mergeResolvers, C as HtmlResolver, D as getHtmlRenderFn, E as RenderProps, O as getRenderFunction, S as HtmlRenderProps, T as RenderFunction, b as ComponentResolver, j as typeToKey, k as mergeHtmlResolvers, w as RESOLVER_KEYS, x as HtmlRenderFunction, y as BaseFieldProps } from "../types-BJzEgJdX.mjs";
|
|
2
2
|
export { BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
|
package/dist/core/types.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as HtmlResolver, E as RenderProps, S as HtmlRenderProps, T as RenderFunction, _ as WalkedField, a as FromJSONSchema, b as ComponentResolver, c as InferResponseFields, d as OpenAPIResponseType, f as PathOfType, g as TypeAtPath, h as SchemaType, i as FieldOverrides, l as JsonObject, m as SchemaMeta, n as FieldConstraints, o as InferParameterOverrides, p as ResolveOpenAPIRef, r as FieldOverride, s as InferRequestBodyFields, t as Editability, u as OpenAPIRequestBodyType, v as resolveEditability, x as HtmlRenderFunction, y as BaseFieldProps } from "../types-
|
|
1
|
+
import { C as HtmlResolver, E as RenderProps, S as HtmlRenderProps, T as RenderFunction, _ as WalkedField, a as FromJSONSchema, b as ComponentResolver, c as InferResponseFields, d as OpenAPIResponseType, f as PathOfType, g as TypeAtPath, h as SchemaType, i as FieldOverrides, l as JsonObject, m as SchemaMeta, n as FieldConstraints, o as InferParameterOverrides, p as ResolveOpenAPIRef, r as FieldOverride, s as InferRequestBodyFields, t as Editability, u as OpenAPIRequestBodyType, v as resolveEditability, x as HtmlRenderFunction, y as BaseFieldProps } from "../types-BJzEgJdX.mjs";
|
|
2
2
|
import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-DIKI2C78.mjs";
|
|
3
3
|
export { BaseFieldProps, ComponentResolver, Editability, FieldConstraints, FieldOverride, FieldOverrides, FromJSONSchema, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, InferParameterOverrides, InferRequestBodyFields, InferResponseFields, JsonObject, OpenAPIRequestBodyType, OpenAPIResponseType, PathOfType, RenderFunction, RenderProps, ResolveOpenAPIRef, SchemaError, SchemaFieldError, SchemaMeta, SchemaNormalisationError, SchemaRenderError, SchemaType, TypeAtPath, WalkedField, resolveEditability };
|
package/dist/core/walker.d.mts
CHANGED
package/dist/core/walker.mjs
CHANGED
|
@@ -160,7 +160,9 @@ const OVERRIDE_META_KEYS = new Set([
|
|
|
160
160
|
"description",
|
|
161
161
|
"title",
|
|
162
162
|
"deprecated",
|
|
163
|
-
"component"
|
|
163
|
+
"component",
|
|
164
|
+
"visible",
|
|
165
|
+
"order"
|
|
164
166
|
]);
|
|
165
167
|
function extractSchemaMetaFields(overrides) {
|
|
166
168
|
if (overrides === void 0) return void 0;
|
package/dist/html/a11y.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as HtmlResolver, S as HtmlRenderProps, m as SchemaMeta, x as HtmlRenderFunction } from "../types-
|
|
1
|
+
import { C as HtmlResolver, S as HtmlRenderProps, m as SchemaMeta, x as HtmlRenderFunction } from "../types-BJzEgJdX.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/html/renderToHtml.d.ts
|
|
4
4
|
interface RenderToHtmlOptions {
|
|
@@ -180,10 +180,13 @@ function renderObjectNode(props) {
|
|
|
180
180
|
const obj = isRecord(props.value) ? props.value : {};
|
|
181
181
|
const descriptionText = typeof props.meta.description === "string" ? props.meta.description : void 0;
|
|
182
182
|
const legend = descriptionText !== void 0 ? h("legend", {}, descriptionText) : void 0;
|
|
183
|
+
const sortedEntries = Object.entries(fields).sort((a, b) => {
|
|
184
|
+
return (typeof a[1].meta.order === "number" ? a[1].meta.order : Infinity) - (typeof b[1].meta.order === "number" ? b[1].meta.order : Infinity);
|
|
185
|
+
}).filter(([, field]) => field.meta.visible !== false);
|
|
183
186
|
if (props.readOnly) {
|
|
184
187
|
const children = [];
|
|
185
188
|
if (legend !== void 0) children.push(legend);
|
|
186
|
-
for (const [key, field] of
|
|
189
|
+
for (const [key, field] of sortedEntries) {
|
|
187
190
|
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
188
191
|
const childValue = obj[key];
|
|
189
192
|
const childHtml = props.renderChild(field, childValue, props.path ? `${props.path}.${key}` : key);
|
|
@@ -196,7 +199,7 @@ function renderObjectNode(props) {
|
|
|
196
199
|
}
|
|
197
200
|
const children = [];
|
|
198
201
|
if (legend !== void 0) children.push(legend);
|
|
199
|
-
for (const [key, field] of
|
|
202
|
+
for (const [key, field] of sortedEntries) {
|
|
200
203
|
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
201
204
|
const fieldId = buildInputId(props.path, key);
|
|
202
205
|
const childPath = props.path ? `${props.path}.${key}` : key;
|
|
@@ -413,6 +416,7 @@ function renderToHtml(schema, options = {}) {
|
|
|
413
416
|
return renderFieldHtml(tree, options.value ?? tree.defaultValue, resolver, "", renderChild);
|
|
414
417
|
}
|
|
415
418
|
function renderFieldHtml(tree, value, resolver, path, renderChild) {
|
|
419
|
+
if (tree.meta.visible === false) return "";
|
|
416
420
|
const effectiveValue = value ?? tree.defaultValue;
|
|
417
421
|
const mergedResolver = mergeHtmlResolvers(resolver, defaultHtmlResolver);
|
|
418
422
|
const renderFn = getHtmlRenderFn(tree.type, mergedResolver);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as InferResponseFields, m as SchemaMeta, o as InferParameterOverrides, r as FieldOverride, s as InferRequestBodyFields } from "../types-
|
|
1
|
+
import { c as InferResponseFields, m as SchemaMeta, o as InferParameterOverrides, r as FieldOverride, s as InferRequestBodyFields } from "../types-BJzEgJdX.mjs";
|
|
2
2
|
import { WidgetMap } from "../react/SchemaComponent.mjs";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as RenderProps, _ as WalkedField, a as FromJSONSchema, b as ComponentResolver, f as PathOfType, i as FieldOverrides, m as SchemaMeta, p as ResolveOpenAPIRef, r as FieldOverride } from "../types-
|
|
1
|
+
import { E as RenderProps, _ as WalkedField, a as FromJSONSchema, b as ComponentResolver, f as PathOfType, i as FieldOverrides, m as SchemaMeta, p as ResolveOpenAPIRef, r as FieldOverride } from "../types-BJzEgJdX.mjs";
|
|
2
2
|
import { t as SchemaError } from "../errors-DIKI2C78.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { ReactNode } from "react";
|
|
@@ -125,6 +125,7 @@ function runValidation(zodSchema, jsonSchema, value) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
function renderField(tree, value, onChange, userResolver, renderChild, instanceWidgets, contextWidgets) {
|
|
128
|
+
if (tree.meta.visible === false) return null;
|
|
128
129
|
const componentHint = tree.meta.component;
|
|
129
130
|
if (typeof componentHint === "string") {
|
|
130
131
|
const widget = instanceWidgets?.get(componentHint) ?? contextWidgets?.get(componentHint) ?? globalWidgets.get(componentHint);
|
package/dist/react/headless.mjs
CHANGED
|
@@ -237,7 +237,10 @@ function renderObject(props) {
|
|
|
237
237
|
const obj = isObject(props.value) ? props.value : {};
|
|
238
238
|
const fields = props.fields;
|
|
239
239
|
if (fields === void 0) return null;
|
|
240
|
-
|
|
240
|
+
const sortedEntries = Object.entries(fields).sort((a, b) => {
|
|
241
|
+
return (typeof a[1].meta.order === "number" ? a[1].meta.order : Infinity) - (typeof b[1].meta.order === "number" ? b[1].meta.order : Infinity);
|
|
242
|
+
});
|
|
243
|
+
return /* @__PURE__ */ jsxs("fieldset", { children: [typeof props.meta.description === "string" && /* @__PURE__ */ jsx("legend", { children: props.meta.description }), sortedEntries.filter(([, field]) => field.meta.visible !== false).map(([key, field]) => {
|
|
241
244
|
const childValue = obj[key];
|
|
242
245
|
const childId = inputId(props.path ? `${props.path}.${key}` : key);
|
|
243
246
|
const childOnChange = (v) => {
|
package/dist/themes/mui.d.mts
CHANGED
package/dist/themes/shadcn.d.mts
CHANGED
|
@@ -147,6 +147,8 @@ interface SchemaMeta {
|
|
|
147
147
|
deprecated?: boolean;
|
|
148
148
|
/** Component hint — resolved before theme adapter. */
|
|
149
149
|
component?: string;
|
|
150
|
+
/** Sort order for object fields. Lower values render first. */
|
|
151
|
+
order?: number;
|
|
150
152
|
/** Arbitrary UI hints passed through to theme adapters. */
|
|
151
153
|
[key: string]: unknown;
|
|
152
154
|
}
|
|
@@ -172,11 +174,12 @@ declare function resolveEditability(propertyMeta: SchemaMeta | undefined, compon
|
|
|
172
174
|
*/
|
|
173
175
|
type FieldOverrides<T> = { [K in keyof T]?: T[K] extends object ? FieldOverrides<T[K]> & FieldOverride : FieldOverride };
|
|
174
176
|
/**
|
|
175
|
-
* Per-field override. Extends SchemaMeta with
|
|
176
|
-
*
|
|
177
|
+
* Per-field override. Extends SchemaMeta with rendering controls
|
|
178
|
+
* and a per-field validation error callback.
|
|
177
179
|
*/
|
|
178
180
|
type FieldOverride = Partial<SchemaMeta> & {
|
|
179
|
-
/** Called with the ZodError when this field fails validation. */onValidationError?: (error: unknown) => void;
|
|
181
|
+
/** Called with the ZodError when this field fails validation. */onValidationError?: (error: unknown) => void; /** Hide this field when false. Defaults to true (visible). */
|
|
182
|
+
visible?: boolean;
|
|
180
183
|
};
|
|
181
184
|
type SchemaType = "string" | "number" | "boolean" | "null" | "enum" | "literal" | "object" | "array" | "record" | "union" | "discriminatedUnion" | "optional" | "nullable" | "default" | "readonly" | "pipe" | "lazy" | "file" | "unknown";
|
|
182
185
|
interface WalkedField {
|