schema-components 2.0.1 → 2.1.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.
Files changed (196) hide show
  1. package/README.md +98 -1
  2. package/dist/SchemaComponent-B__6-5-E.d.mts +277 -0
  3. package/dist/SchemaComponent-BxzzsHsK.mjs +668 -0
  4. package/dist/adapter-ktQaheWB.d.mts +213 -0
  5. package/dist/constructorTypes-BdCiMS6e.d.mts +30 -0
  6. package/dist/core/adapter.d.mts +3 -213
  7. package/dist/core/adapter.mjs +33 -25
  8. package/dist/core/constraintHint.d.mts +1 -1
  9. package/dist/core/constraints.d.mts +2 -2
  10. package/dist/core/contexts.d.mts +71 -0
  11. package/dist/core/contexts.mjs +1 -0
  12. package/dist/core/diagnostics.d.mts +1 -1
  13. package/dist/core/errors.d.mts +1 -1
  14. package/dist/core/fieldOrder.d.mts +1 -1
  15. package/dist/{react → core}/fieldPath.d.mts +2 -2
  16. package/dist/{react → core}/fieldPath.mjs +3 -3
  17. package/dist/core/formats.d.mts +1 -1
  18. package/dist/core/guards.d.mts +2 -2
  19. package/dist/core/guards.mjs +2 -2
  20. package/dist/core/inferValue.d.mts +1 -1
  21. package/dist/core/limits.d.mts +1 -1
  22. package/dist/core/merge.d.mts +1 -1
  23. package/dist/core/normalise.d.mts +6 -6
  24. package/dist/core/normalise.mjs +1 -1
  25. package/dist/core/openapi30.d.mts +1 -1
  26. package/dist/core/openapi30.mjs +1 -1
  27. package/dist/core/ref.d.mts +1 -1
  28. package/dist/core/renderField.d.mts +147 -0
  29. package/dist/core/renderField.mjs +81 -0
  30. package/dist/core/renderer.d.mts +2 -199
  31. package/dist/core/swagger2.d.mts +1 -1
  32. package/dist/core/swagger2.mjs +1 -1
  33. package/dist/core/typeInference.d.mts +1 -981
  34. package/dist/core/types.d.mts +1 -1
  35. package/dist/core/unionMatch.d.mts +1 -1
  36. package/dist/core/uri.d.mts +2 -2
  37. package/dist/core/uri.mjs +2 -2
  38. package/dist/core/version.d.mts +1 -1
  39. package/dist/core/walkBuilders.d.mts +4 -4
  40. package/dist/core/walkBuilders.mjs +1 -1
  41. package/dist/core/walker.d.mts +1 -1
  42. package/dist/core/walker.mjs +3 -3
  43. package/dist/{errors-Dki7tji4.d.mts → errors-DbaI04x2.d.mts} +1 -1
  44. package/dist/html/a11y.d.mts +2 -2
  45. package/dist/html/html.d.mts +10 -8
  46. package/dist/html/renderToHtml.d.mts +5 -5
  47. package/dist/html/renderToHtml.mjs +45 -24
  48. package/dist/html/renderToHtmlStream.d.mts +5 -5
  49. package/dist/html/renderers.d.mts +1 -1
  50. package/dist/html/streamRenderers.d.mts +3 -3
  51. package/dist/{inferValue-PPXWJpbN.d.mts → inferValue-eAnh50EM.d.mts} +6 -6
  52. package/dist/lit/SchemaComponent.d.mts +125 -0
  53. package/dist/lit/SchemaComponent.mjs +2 -0
  54. package/dist/lit/SchemaField.d.mts +65 -0
  55. package/dist/lit/SchemaField.mjs +2 -0
  56. package/dist/lit/SchemaView.d.mts +14 -0
  57. package/dist/lit/SchemaView.mjs +2 -0
  58. package/dist/lit/constructorTypes.d.mts +2 -0
  59. package/dist/lit/constructorTypes.mjs +1 -0
  60. package/dist/lit/contexts.d.mts +78 -0
  61. package/dist/lit/contexts.mjs +238 -0
  62. package/dist/lit/defaultResolver.d.mts +33 -0
  63. package/dist/lit/defaultResolver.mjs +2 -0
  64. package/dist/lit/registry.d.mts +66 -0
  65. package/dist/lit/registry.mjs +2 -0
  66. package/dist/lit/renderers/baseElement.d.mts +131 -0
  67. package/dist/lit/renderers/baseElement.mjs +109 -0
  68. package/dist/lit/renderers/recordHelpers.d.mts +25 -0
  69. package/dist/lit/renderers/recordHelpers.mjs +55 -0
  70. package/dist/lit/renderers/scArray.d.mts +14 -0
  71. package/dist/lit/renderers/scArray.mjs +86 -0
  72. package/dist/lit/renderers/scBoolean.d.mts +15 -0
  73. package/dist/lit/renderers/scBoolean.mjs +47 -0
  74. package/dist/lit/renderers/scConditional.d.mts +23 -0
  75. package/dist/lit/renderers/scConditional.mjs +65 -0
  76. package/dist/lit/renderers/scDiscriminated.d.mts +23 -0
  77. package/dist/lit/renderers/scDiscriminated.mjs +138 -0
  78. package/dist/lit/renderers/scEnum.d.mts +16 -0
  79. package/dist/lit/renderers/scEnum.mjs +66 -0
  80. package/dist/lit/renderers/scFile.d.mts +15 -0
  81. package/dist/lit/renderers/scFile.mjs +53 -0
  82. package/dist/lit/renderers/scLiteralNullNever.d.mts +30 -0
  83. package/dist/lit/renderers/scLiteralNullNever.mjs +57 -0
  84. package/dist/lit/renderers/scNumber.d.mts +15 -0
  85. package/dist/lit/renderers/scNumber.mjs +64 -0
  86. package/dist/lit/renderers/scObject.d.mts +14 -0
  87. package/dist/lit/renderers/scObject.mjs +57 -0
  88. package/dist/lit/renderers/scRecord.d.mts +14 -0
  89. package/dist/lit/renderers/scRecord.mjs +112 -0
  90. package/dist/lit/renderers/scString.d.mts +19 -0
  91. package/dist/lit/renderers/scString.mjs +165 -0
  92. package/dist/lit/renderers/scTuple.d.mts +14 -0
  93. package/dist/lit/renderers/scTuple.mjs +58 -0
  94. package/dist/lit/renderers/scUnion.d.mts +14 -0
  95. package/dist/lit/renderers/scUnion.mjs +44 -0
  96. package/dist/lit/renderers/scUnknown.d.mts +15 -0
  97. package/dist/lit/renderers/scUnknown.mjs +45 -0
  98. package/dist/lit/ssr.d.mts +37 -0
  99. package/dist/lit/ssr.mjs +9565 -0
  100. package/dist/lit/types.d.mts +2 -0
  101. package/dist/lit/types.mjs +1 -0
  102. package/dist/lit/widget.d.mts +71 -0
  103. package/dist/lit/widget.mjs +87 -0
  104. package/dist/{normalise-DB-Xtjmn.mjs → normalise-BkePrJ4v.mjs} +6 -6
  105. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  106. package/dist/openapi/ApiLinks.d.mts +1 -1
  107. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  108. package/dist/openapi/ApiSecurity.d.mts +1 -1
  109. package/dist/openapi/components.d.mts +5 -5
  110. package/dist/openapi/components.mjs +1 -1
  111. package/dist/openapi/parser.d.mts +2 -2
  112. package/dist/openapi/resolve.d.mts +1 -1
  113. package/dist/openapi/resolve.mjs +1 -1
  114. package/dist/preact/SchemaComponent.d.mts +3 -0
  115. package/dist/preact/SchemaComponent.mjs +26 -0
  116. package/dist/preact/SchemaErrorBoundary.d.mts +2 -0
  117. package/dist/preact/SchemaErrorBoundary.mjs +20 -0
  118. package/dist/preact/SchemaView.d.mts +2 -0
  119. package/dist/preact/SchemaView.mjs +22 -0
  120. package/dist/preact/headless.d.mts +2 -0
  121. package/dist/preact/headless.mjs +18 -0
  122. package/dist/react/SchemaComponent.d.mts +3 -270
  123. package/dist/react/SchemaComponent.mjs +48 -39
  124. package/dist/react/SchemaErrorBoundary.mjs +7 -4
  125. package/dist/react/SchemaView.d.mts +11 -10
  126. package/dist/react/SchemaView.mjs +32 -29
  127. package/dist/react/a11y.d.mts +2 -2
  128. package/dist/react/fieldShell.d.mts +1 -1
  129. package/dist/react/headless.d.mts +1 -1
  130. package/dist/react/headlessRenderers.d.mts +2 -2
  131. package/dist/{ref-DdsbekXX.d.mts → ref-DWrQG1Er.d.mts} +1 -1
  132. package/dist/renderer-ab9E52Bp.d.mts +245 -0
  133. package/dist/solid/SchemaComponent.d.mts +136 -0
  134. package/dist/solid/SchemaComponent.mjs +391 -0
  135. package/dist/solid/SchemaErrorBoundary.d.mts +38 -0
  136. package/dist/solid/SchemaErrorBoundary.mjs +57 -0
  137. package/dist/solid/SchemaField.d.mts +40 -0
  138. package/dist/solid/SchemaField.mjs +113 -0
  139. package/dist/solid/SchemaView.d.mts +54 -0
  140. package/dist/solid/SchemaView.mjs +168 -0
  141. package/dist/solid/a11y.d.mts +70 -0
  142. package/dist/solid/a11y.mjs +71 -0
  143. package/dist/solid/contexts.d.mts +37 -0
  144. package/dist/solid/contexts.mjs +66 -0
  145. package/dist/solid/headless.d.mts +10 -0
  146. package/dist/solid/headless.mjs +27 -0
  147. package/dist/solid/renderers.d.mts +79 -0
  148. package/dist/solid/renderers.mjs +840 -0
  149. package/dist/solid/types.d.mts +90 -0
  150. package/dist/solid/types.mjs +1 -0
  151. package/dist/solid/widget.d.mts +29 -0
  152. package/dist/solid/widget.mjs +35 -0
  153. package/dist/themes/mantine.d.mts +1 -1
  154. package/dist/themes/mui.d.mts +1 -1
  155. package/dist/themes/radix.d.mts +1 -1
  156. package/dist/themes/shadcn.d.mts +1 -1
  157. package/dist/typeInference-Y8tNEQJk.d.mts +983 -0
  158. package/dist/types-BCy7K3nk.d.mts +125 -0
  159. package/package.json +71 -1
  160. package/src/svelte/SchemaComponent.svelte +427 -0
  161. package/src/svelte/SchemaErrorBoundary.svelte +66 -0
  162. package/src/svelte/SchemaField.svelte +216 -0
  163. package/src/svelte/SchemaProvider.svelte +46 -0
  164. package/src/svelte/SchemaView.svelte +244 -0
  165. package/src/svelte/a11y.ts +112 -0
  166. package/src/svelte/contexts.ts +79 -0
  167. package/src/svelte/dispatch.ts +267 -0
  168. package/src/svelte/headless.ts +73 -0
  169. package/src/svelte/headlessFns.ts +124 -0
  170. package/src/svelte/renderers/Array.svelte +98 -0
  171. package/src/svelte/renderers/Boolean.svelte +43 -0
  172. package/src/svelte/renderers/Conditional.svelte +67 -0
  173. package/src/svelte/renderers/DiscriminatedUnion.svelte +197 -0
  174. package/src/svelte/renderers/Enum.svelte +53 -0
  175. package/src/svelte/renderers/Fallback.svelte +24 -0
  176. package/src/svelte/renderers/File.svelte +46 -0
  177. package/src/svelte/renderers/Literal.svelte +29 -0
  178. package/src/svelte/renderers/Mount.svelte +24 -0
  179. package/src/svelte/renderers/Negation.svelte +35 -0
  180. package/src/svelte/renderers/Never.svelte +24 -0
  181. package/src/svelte/renderers/Null.svelte +19 -0
  182. package/src/svelte/renderers/Number.svelte +68 -0
  183. package/src/svelte/renderers/Object.svelte +74 -0
  184. package/src/svelte/renderers/Record.svelte +134 -0
  185. package/src/svelte/renderers/RecursionSentinel.svelte +27 -0
  186. package/src/svelte/renderers/String.svelte +152 -0
  187. package/src/svelte/renderers/Tuple.svelte +84 -0
  188. package/src/svelte/renderers/Union.svelte +49 -0
  189. package/src/svelte/renderers/Unknown.svelte +42 -0
  190. package/src/svelte/svelte-modules.d.ts +25 -0
  191. package/src/svelte/types.ts +238 -0
  192. package/src/svelte/widget.ts +62 -0
  193. /package/dist/{diagnostics-BTrm3O6J.d.mts → diagnostics-mftUZI7c.d.mts} +0 -0
  194. /package/dist/{limits-x4OiyJxh.d.mts → limits-Vv9hUbI_.d.mts} +0 -0
  195. /package/dist/{types-BrYbjC7_.d.mts → types-BBQaEPfE.d.mts} +0 -0
  196. /package/dist/{version-DL8U5RuA.d.mts → version-BEBx10ND.d.mts} +0 -0
@@ -0,0 +1,125 @@
1
+ import { j as WalkedField, w as SchemaMeta } from "./types-BBQaEPfE.mjs";
2
+ import { t as AllConstraints } from "./renderer-ab9E52Bp.mjs";
3
+ import { TemplateResult } from "lit";
4
+
5
+ //#region src/lit/types.d.ts
6
+ /**
7
+ * Props handed to a Lit render function.
8
+ *
9
+ * Shares the same per-field data as `RenderProps` (React) and
10
+ * `HtmlRenderProps` (HTML-string) but with a `renderChild` typed
11
+ * over Lit's {@link TemplateResult}. Editable fields receive a
12
+ * `change` callback that emits a Custom Event up the DOM tree —
13
+ * unlike React's prop-callback model, Custom Elements communicate via
14
+ * DOM events, so the renderer wires the input event to a
15
+ * `dispatchEvent(new CustomEvent("sc-change", { detail: { value } }))`
16
+ * on the host element.
17
+ *
18
+ * Renderers narrow on `tree.type` for per-variant data
19
+ * (object fields, array element schema, union options, etc.) — the
20
+ * walker's discriminated union enforces type-correct access.
21
+ */
22
+ interface LitRenderProps {
23
+ /** Current field value. */
24
+ value: unknown;
25
+ /** Whether to render as read-only display. */
26
+ readOnly: boolean;
27
+ /** Whether to render as an empty input. */
28
+ writeOnly: boolean;
29
+ /** Schema metadata for this field. */
30
+ meta: SchemaMeta;
31
+ /** Constraints from schema checks. */
32
+ constraints: AllConstraints;
33
+ /** Dot-separated path from root (e.g. "address.city"). */
34
+ path: string;
35
+ /** Example values from the schema's `examples` keyword. */
36
+ examples?: unknown[];
37
+ /** Walked field tree for recursive rendering. */
38
+ tree: WalkedField;
39
+ /**
40
+ * Callback to propagate the next value back to the host element.
41
+ *
42
+ * Renderers wire DOM events (`@input`, `@change`, `@click` on add /
43
+ * remove controls) to this callback. The host element catches the
44
+ * resulting change and emits a `change` Custom Event on itself so
45
+ * framework consumers can observe via standard `addEventListener`.
46
+ */
47
+ change: (value: unknown) => void;
48
+ /**
49
+ * Render a child field. Resolver overrides call this to recursively
50
+ * render nested structures (object fields, array elements, union
51
+ * options) without re-running the resolver dispatch loop.
52
+ *
53
+ * @param tree - The walked field tree for the child
54
+ * @param value - The child's current value
55
+ * @param change - Callback receiving the child's next value
56
+ * @param pathSuffix - Path segment from the parent (e.g. `city` for
57
+ * an object key, `[0]` for an array index). Required for every
58
+ * container — without it children inherit no path and DOM id
59
+ * derivation throws.
60
+ */
61
+ renderChild: (tree: WalkedField, value: unknown, change: (next: unknown) => void, pathSuffix?: string) => TemplateResult;
62
+ }
63
+ /**
64
+ * Signature for a Lit render function attached to a
65
+ * {@link LitComponentResolver}. Receives the per-field
66
+ * {@link LitRenderProps} built by the walker and returns a Lit
67
+ * {@link TemplateResult} for direct interpolation into the host
68
+ * element's `render()` method.
69
+ *
70
+ * The return type is `TemplateResult`, the type produced by
71
+ * the `html`-tagged template literal. Unlike `RenderFunction` (React)
72
+ * which returns `unknown` so React elements, primitive children, and
73
+ * arbitrary value types can flow through, Lit templates have a single
74
+ * canonical type — narrowing makes the resolver dispatch loop simpler
75
+ * and forces every renderer to return a structurally compatible value.
76
+ */
77
+ type LitRenderFunction = (props: LitRenderProps) => TemplateResult;
78
+ /**
79
+ * Theme adapter — maps every schema field type to its Lit renderer.
80
+ *
81
+ * Structurally mirrors `ComponentResolver` (React) and `HtmlResolver`
82
+ * (HTML-string) so the resolver-key tuple in `core/renderer.ts`
83
+ * (`RESOLVER_KEYS`) drives every dispatch surface from a single
84
+ * source-of-truth list.
85
+ *
86
+ * Unset keys fall through to the default Lit resolver
87
+ * (`defaultLitResolver` in `lit/renderers/registry.ts`). The default
88
+ * resolver is composed from the built-in `<sc-*>` Custom Elements
89
+ * registered by {@link registerSchemaComponents} — overriding a key
90
+ * here lets a consumer bypass the Custom Element registry for a
91
+ * specific schema type without unregistering the element.
92
+ */
93
+ interface LitComponentResolver {
94
+ string?: LitRenderFunction;
95
+ number?: LitRenderFunction;
96
+ boolean?: LitRenderFunction;
97
+ null?: LitRenderFunction;
98
+ enum?: LitRenderFunction;
99
+ object?: LitRenderFunction;
100
+ array?: LitRenderFunction;
101
+ tuple?: LitRenderFunction;
102
+ record?: LitRenderFunction;
103
+ union?: LitRenderFunction;
104
+ discriminatedUnion?: LitRenderFunction;
105
+ conditional?: LitRenderFunction;
106
+ negation?: LitRenderFunction;
107
+ literal?: LitRenderFunction;
108
+ file?: LitRenderFunction;
109
+ never?: LitRenderFunction;
110
+ unknown?: LitRenderFunction;
111
+ }
112
+ /**
113
+ * Compile-time witness that {@link LitRenderFunction} is a
114
+ * `(props: P) => O` shape exactly matching the parallel signatures
115
+ * carried by `core/renderer.ts`. The walker's dispatch loop is
116
+ * parameterised over `O` (React's `unknown`, HTML's `string`, Lit's
117
+ * `TemplateResult`) and `P` (the matching per-renderer props), so this
118
+ * alias documents the contract without forcing `core/renderer.ts` to
119
+ * import Lit.
120
+ *
121
+ * @internal
122
+ */
123
+ type _LitFunctionShapeAssertion = LitRenderFunction extends ((props: LitRenderProps) => TemplateResult) ? true : false;
124
+ //#endregion
125
+ export { _LitFunctionShapeAssertion as i, LitRenderFunction as n, LitRenderProps as r, LitComponentResolver as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schema-components",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "React components that render UI from Zod schemas, JSON Schema, and OpenAPI documents",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,6 +13,10 @@
13
13
  "types": "./dist/react/*.d.mts",
14
14
  "import": "./dist/react/*.mjs"
15
15
  },
16
+ "./preact/*": {
17
+ "types": "./dist/preact/*.d.mts",
18
+ "import": "./dist/preact/*.mjs"
19
+ },
16
20
  "./openapi/*": {
17
21
  "types": "./dist/openapi/*.d.mts",
18
22
  "import": "./dist/openapi/*.mjs"
@@ -24,10 +28,30 @@
24
28
  "./themes/*": {
25
29
  "types": "./dist/themes/*.d.mts",
26
30
  "import": "./dist/themes/*.mjs"
31
+ },
32
+ "./vue/*": {
33
+ "types": "./dist/vue/*.d.mts",
34
+ "import": "./dist/vue/*.mjs"
35
+ },
36
+ "./svelte/*.svelte": "./src/svelte/*.svelte",
37
+ "./svelte/renderers/*.svelte": "./src/svelte/renderers/*.svelte",
38
+ "./svelte/*": {
39
+ "types": "./src/svelte/*.ts",
40
+ "svelte": "./src/svelte/*.ts",
41
+ "import": "./src/svelte/*.ts"
42
+ },
43
+ "./solid/*": {
44
+ "types": "./dist/solid/*.d.mts",
45
+ "import": "./dist/solid/*.mjs"
46
+ },
47
+ "./lit/*": {
48
+ "types": "./dist/lit/*.d.mts",
49
+ "import": "./dist/lit/*.mjs"
27
50
  }
28
51
  },
29
52
  "files": [
30
53
  "dist",
54
+ "src/svelte",
31
55
  "LICENSE",
32
56
  "README.md",
33
57
  "CHANGELOG.md"
@@ -38,6 +62,11 @@
38
62
  "_lint:fix": "eslint --cache --fix 'src/**/*.{ts,tsx}'",
39
63
  "_test": "vitest run --project=unit",
40
64
  "_test:e2e": "vitest run --project=e2e",
65
+ "_test:preact": "vitest run --project=unit-preact",
66
+ "_test:vue": "vitest run --project=unit-vue",
67
+ "_test:svelte": "vitest run --project=unit-svelte",
68
+ "_test:solid": "vitest run --project=unit-solid",
69
+ "_test:lit": "vitest run --project=unit-lit",
41
70
  "_test:coverage": "vitest run --project=unit --coverage",
42
71
  "_build": "tsdown && cp src/html/styles.css dist/html/styles.css",
43
72
  "_typedoc": "typedoc",
@@ -47,6 +76,10 @@
47
76
  "lint": "turbo run _lint",
48
77
  "lint:fix": "turbo run _lint:fix",
49
78
  "test": "turbo run _test",
79
+ "test:preact": "turbo run _test:preact",
80
+ "test:svelte": "turbo run _test:svelte",
81
+ "test:solid": "turbo run _test:solid",
82
+ "test:lit": "turbo run _test:lit",
50
83
  "test:coverage": "turbo run _test:coverage",
51
84
  "check": "turbo run _check",
52
85
  "validate": "turbo run _validate",
@@ -77,17 +110,47 @@
77
110
  "provenance": true
78
111
  },
79
112
  "peerDependencies": {
113
+ "lit": ">=3.0.0",
114
+ "preact": ">=10.0.0",
80
115
  "react": "^18.0.0 || ^19.0.0",
116
+ "solid-js": ">=1.8.0",
117
+ "svelte": ">=5.0.0",
118
+ "vue": ">=3.5.0",
81
119
  "zod": "^4.0.0"
82
120
  },
121
+ "peerDependenciesMeta": {
122
+ "lit": {
123
+ "optional": true
124
+ },
125
+ "preact": {
126
+ "optional": true
127
+ },
128
+ "solid-js": {
129
+ "optional": true
130
+ },
131
+ "svelte": {
132
+ "optional": true
133
+ },
134
+ "vue": {
135
+ "optional": true
136
+ }
137
+ },
83
138
  "devDependencies": {
84
139
  "@eslint/js": "10.0.1",
140
+ "@lit-labs/ssr": "4.0.0",
141
+ "@lit-labs/ssr-client": "1.1.8",
142
+ "@lit/context": "1.1.6",
143
+ "@solidjs/testing-library": "0.8.10",
144
+ "@sveltejs/vite-plugin-svelte": "7.1.2",
85
145
  "@testing-library/react": "16.3.2",
146
+ "@testing-library/svelte": "5.3.1",
86
147
  "@testing-library/user-event": "14.6.1",
87
148
  "@types/node": "25.6.2",
88
149
  "@types/react": "19.2.14",
89
150
  "@types/react-dom": "19.2.3",
151
+ "@vitejs/plugin-vue": "6.0.6",
90
152
  "@vitest/coverage-v8": "4.1.5",
153
+ "@vue/test-utils": "2.4.10",
91
154
  "eslint": "10.3.0",
92
155
  "eslint-config-prettier": "10.1.8",
93
156
  "eslint-plugin-import": "2.32.0",
@@ -96,9 +159,14 @@
96
159
  "eslint-plugin-prettier": "5.5.5",
97
160
  "eslint-plugin-tsdoc": "0.5.2",
98
161
  "happy-dom": "20.9.0",
162
+ "lit": "3.3.2",
163
+ "preact": "10.29.1",
164
+ "preact-render-to-string": "6.6.7",
99
165
  "prettier": "3.8.3",
100
166
  "react": "19.2.6",
101
167
  "react-dom": "19.2.6",
168
+ "solid-js": "1.9.12",
169
+ "svelte": "5.55.5",
102
170
  "tsdown": "0.22.0",
103
171
  "tslib": "2.8.1",
104
172
  "typedoc": "0.28.19",
@@ -107,7 +175,9 @@
107
175
  "typedoc-plugin-missing-exports": "4.1.3",
108
176
  "typescript": "6.0.3",
109
177
  "typescript-eslint": "8.59.2",
178
+ "vite-plugin-solid": "2.11.12",
110
179
  "vitest": "4.1.5",
180
+ "vue": "3.5.34",
111
181
  "zod": "4.4.3"
112
182
  }
113
183
  }
@@ -0,0 +1,427 @@
1
+ <!--
2
+ Editable Svelte 5 entry point that renders UI from a Zod schema,
3
+ JSON Schema, or OpenAPI document.
4
+
5
+ Mirror of `react/SchemaComponent.tsx :: SchemaComponent` adapted
6
+ to Svelte 5 runes:
7
+
8
+ - `$props<…>()` destructures inputs (no React `useContext` /
9
+ `useId` / `useMemo` / `useCallback`).
10
+ - `$derived(...)` for memoised values (the merged meta, the
11
+ normalised JSON schema, the walked tree, the dispatcher's
12
+ rootPath).
13
+ - `$state` for the per-mount default `idPrefix` (computed once
14
+ and held; `useId()` equivalent provided by Svelte 5's
15
+ `$props.id()` rune is intentionally not used here — every
16
+ path generated downstream is structurally suffixed and
17
+ passing a deterministic `idPrefix` is the contracted
18
+ override for snapshot tests).
19
+ - The dispatcher is the same `dispatchRenderField` shared with
20
+ the React and HTML adapters, wired through `renderFieldSvelte`
21
+ in `./dispatch.ts`.
22
+
23
+ External-value reactivity contract: `value` is consumed as an
24
+ immutable prop and any internal edit fires `onChange(next)` with
25
+ a freshly cloned object. Consumers can either:
26
+
27
+ - Pass a plain object and a function `onChange` that mutates
28
+ local state — typical controlled-component pattern; works
29
+ identically across React / Solid / Svelte.
30
+ - Use Svelte's `bind:value` ergonomics — Svelte translates
31
+ `bind:value` into an `onChange` that mutates the bound
32
+ reference, so this component does not need to know about
33
+ the binding.
34
+
35
+ Svelte's reactivity tracks reference identity on the `value`
36
+ prop. Internal edits emit fresh objects (`{ ...obj, key: v }`)
37
+ rather than mutating the prop, so consumers using
38
+ `$state(value)` see new identities propagated correctly. Deep
39
+ mutations of the prop object from outside the component — e.g.
40
+ `value.foo = "bar"` — would not be observed; that is the same
41
+ constraint Vue, Solid, and React impose on shared mutable
42
+ state and is documented in the package README.
43
+ -->
44
+ <script lang="ts" generics="T = unknown, Ref extends string | undefined = undefined">
45
+ import { z } from "zod";
46
+ import { walk } from "../core/walker.ts";
47
+ import type { WalkOptions } from "../core/walkBuilders.ts";
48
+ import {
49
+ isCodecSchema,
50
+ normaliseSchema,
51
+ type SchemaIoSide,
52
+ } from "../core/adapter.ts";
53
+ import type {
54
+ DiagnosticsOptions,
55
+ Diagnostic,
56
+ } from "../core/diagnostics.ts";
57
+ import { SchemaNormalisationError } from "../core/errors.ts";
58
+ import type { SchemaMeta, WalkedField } from "../core/types.ts";
59
+ import type { SchemaError } from "../core/errors.ts";
60
+ import { isObject, toRecordOrUndefined } from "../core/guards.ts";
61
+ import type {
62
+ InferFields,
63
+ InferSchemaValue,
64
+ } from "../core/inferValue.ts";
65
+ import { resolverContext, widgetsContext } from "./contexts.ts";
66
+ import { renderFieldSvelte } from "./dispatch.ts";
67
+ import RecursionSentinel from "./renderers/RecursionSentinel.svelte";
68
+ import Fallback from "./renderers/Fallback.svelte";
69
+ import Mount from "./renderers/Mount.svelte";
70
+ import type {
71
+ SvelteComponentResolver,
72
+ SvelteRenderDescriptor,
73
+ SvelteRenderProps,
74
+ SvelteWidgetMap,
75
+ } from "./types.ts";
76
+
77
+ interface Props {
78
+ /** Zod 4, JSON Schema, or OpenAPI document. */
79
+ schema: T;
80
+ /** OpenAPI ref string, e.g. "#/components/schemas/User". */
81
+ ref?: Ref;
82
+ /** Direction (`"output"` / `"input"`) for codec / transform schemas. */
83
+ io?: SchemaIoSide;
84
+ /** Current value to render. */
85
+ value?: InferSchemaValue<T, Ref, "output">;
86
+ /** Called when the value changes. */
87
+ onChange?: (value: InferSchemaValue<T, Ref, "output">) => void;
88
+ /** Run `safeParse` / `safeEncode` on change. */
89
+ validate?: boolean;
90
+ /** Called with the ZodError on validation failure. */
91
+ onValidationError?: (error: unknown) => void;
92
+ /** Called when schema normalisation or rendering fails. */
93
+ onError?: (error: SchemaError) => void;
94
+ /** Called with each diagnostic emitted during processing. */
95
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
96
+ /** When true, any diagnostic becomes a thrown error. */
97
+ strict?: boolean;
98
+ /** Per-field meta overrides. */
99
+ fields?: InferFields<T, Ref>;
100
+ /** Meta overrides applied to the root schema. */
101
+ meta?: SchemaMeta;
102
+ /** Convenience: sets readOnly on all fields. */
103
+ readOnly?: boolean;
104
+ /** Convenience: sets writeOnly on all fields. */
105
+ writeOnly?: boolean;
106
+ /** Convenience: sets description on the root. */
107
+ description?: string;
108
+ /** Instance-scoped widgets. */
109
+ widgets?: SvelteWidgetMap;
110
+ /**
111
+ * Prefix used for every input `id` / label `for` in this
112
+ * component subtree. Defaults to a sanitised, mount-stable
113
+ * value derived from `crypto.randomUUID()` when available
114
+ * (falling back to a counter). Override for deterministic
115
+ * ids in snapshot tests.
116
+ */
117
+ idPrefix?: string;
118
+ }
119
+
120
+ const {
121
+ schema,
122
+ ref,
123
+ io,
124
+ value,
125
+ onChange,
126
+ validate,
127
+ onValidationError,
128
+ onError,
129
+ onDiagnostic,
130
+ strict,
131
+ fields,
132
+ meta: componentMeta,
133
+ readOnly,
134
+ writeOnly,
135
+ description,
136
+ widgets: instanceWidgets,
137
+ idPrefix,
138
+ }: Props = $props();
139
+
140
+ const userResolver = resolverContext.consume();
141
+ const contextWidgets = widgetsContext.consume();
142
+
143
+ /**
144
+ * Per-mount fallback prefix used when the consumer doesn't pass
145
+ * `idPrefix`. Module-level counter incremented once per
146
+ * component instance — deterministic enough for typical
147
+ * applications without requiring `crypto.randomUUID()` in
148
+ * non-browser environments.
149
+ */
150
+ const fallbackPrefix = `sc-svelte-${String(nextInstanceId())}`;
151
+ const rootPath = $derived(idPrefix ?? fallbackPrefix);
152
+
153
+ const mergedMeta = $derived<SchemaMeta>({
154
+ ...componentMeta,
155
+ ...(readOnly === true ? { readOnly: true } : {}),
156
+ ...(writeOnly === true ? { writeOnly: true } : {}),
157
+ ...(description !== undefined ? { description } : {}),
158
+ });
159
+
160
+ const diagnostics: DiagnosticsOptions | undefined = $derived(
161
+ onDiagnostic !== undefined || strict === true
162
+ ? {
163
+ ...(onDiagnostic !== undefined
164
+ ? { diagnostics: onDiagnostic }
165
+ : {}),
166
+ ...(strict !== undefined ? { strict } : {}),
167
+ }
168
+ : undefined
169
+ );
170
+
171
+ interface NormalisedShape {
172
+ jsonSchema: Record<string, unknown>;
173
+ zodSchema: unknown;
174
+ rootMeta: SchemaMeta | undefined;
175
+ rootDocument: Record<string, unknown>;
176
+ }
177
+
178
+ const normalisedResult = $derived<NormalisedShape | SchemaError>(
179
+ normaliseSafely(schema, ref, io, diagnostics)
180
+ );
181
+
182
+ /**
183
+ * If normalisation failed and the consumer wired up `onError`,
184
+ * surface the structured error through the callback once per
185
+ * change. Mirrors the React adapter's behaviour where
186
+ * `SchemaNormalisationError` is routed through `onError` rather
187
+ * than thrown at render time.
188
+ */
189
+ $effect(() => {
190
+ if (normalisedResult instanceof Error && onError !== undefined) {
191
+ onError(normalisedResult);
192
+ }
193
+ });
194
+
195
+ const fieldsRecord = $derived(toRecordOrUndefined(fields));
196
+
197
+ const walkOptions = $derived<WalkOptions | undefined>(
198
+ normalisedResult instanceof Error
199
+ ? undefined
200
+ : {
201
+ componentMeta: mergedMeta,
202
+ ...(normalisedResult.rootMeta !== undefined
203
+ ? { rootMeta: normalisedResult.rootMeta }
204
+ : {}),
205
+ ...(fieldsRecord !== undefined
206
+ ? { fieldOverrides: fieldsRecord }
207
+ : {}),
208
+ rootDocument: normalisedResult.rootDocument,
209
+ ...(diagnostics !== undefined ? { diagnostics } : {}),
210
+ }
211
+ );
212
+
213
+ const tree = $derived<WalkedField | undefined>(
214
+ normalisedResult instanceof Error || walkOptions === undefined
215
+ ? undefined
216
+ : walk(normalisedResult.jsonSchema, walkOptions)
217
+ );
218
+
219
+ function handleChange(nextValue: unknown): void {
220
+ if (validate === true && !(normalisedResult instanceof Error)) {
221
+ const error = runValidation(
222
+ normalisedResult.zodSchema,
223
+ normalisedResult.jsonSchema,
224
+ nextValue,
225
+ io,
226
+ onDiagnostic
227
+ );
228
+ if (error !== undefined) onValidationError?.(error);
229
+ }
230
+ if (onChange !== undefined) {
231
+ // Library boundary identical to the React adapter — the
232
+ // walker produces `unknown` typed values that downstream
233
+ // call sites receive as the inferred schema shape. The
234
+ // contravariant assignment cannot be proven by
235
+ // TypeScript and is the same pattern used in
236
+ // `react/SchemaComponent.tsx`.
237
+ onChange(nextValue as InferSchemaValue<T, Ref, "output">);
238
+ }
239
+ }
240
+
241
+ function makeRenderChild(
242
+ currentDepth: number,
243
+ parentPath: string,
244
+ currentValue: unknown,
245
+ currentOnChange: (v: unknown) => void
246
+ ): SvelteRenderProps["renderChild"] {
247
+ return (
248
+ childTree: WalkedField,
249
+ childValue: unknown,
250
+ childOnChange: (v: unknown) => void,
251
+ pathSuffix?: string
252
+ ): SvelteRenderDescriptor | null => {
253
+ const childPath = joinPath(parentPath, pathSuffix);
254
+ return renderFieldSvelte(
255
+ childTree,
256
+ childValue,
257
+ childOnChange,
258
+ userResolver,
259
+ makeRenderChild(
260
+ currentDepth + 1,
261
+ childPath,
262
+ childValue,
263
+ childOnChange
264
+ ),
265
+ childPath,
266
+ instanceWidgets,
267
+ contextWidgets,
268
+ currentDepth + 1,
269
+ Fallback,
270
+ RecursionSentinel
271
+ );
272
+ };
273
+ }
274
+
275
+ const renderChild = $derived(
276
+ tree === undefined
277
+ ? undefined
278
+ : makeRenderChild(0, rootPath, value ?? tree.defaultValue, handleChange)
279
+ );
280
+
281
+ const effectiveValue = $derived(
282
+ tree === undefined ? value : (value ?? tree.defaultValue)
283
+ );
284
+
285
+ const rootDescriptor = $derived<SvelteRenderDescriptor | null>(
286
+ tree === undefined || renderChild === undefined
287
+ ? null
288
+ : renderFieldSvelte(
289
+ tree,
290
+ effectiveValue,
291
+ handleChange,
292
+ userResolver,
293
+ renderChild,
294
+ rootPath,
295
+ instanceWidgets,
296
+ contextWidgets,
297
+ 0,
298
+ Fallback,
299
+ RecursionSentinel
300
+ )
301
+ );
302
+
303
+ /**
304
+ * Append a child path suffix to a parent path. When the suffix
305
+ * is omitted (e.g. transparent wrappers like union options), the
306
+ * parent path is returned unchanged so the child inherits the
307
+ * parent's id. Matches `react/SchemaComponent.tsx :: joinPath`.
308
+ */
309
+ function joinPath(parent: string, suffix: string | undefined): string {
310
+ if (suffix === undefined || suffix.length === 0) return parent;
311
+ if (parent.length === 0) return suffix;
312
+ if (suffix.startsWith("[")) return `${parent}${suffix}`;
313
+ return `${parent}.${suffix}`;
314
+ }
315
+
316
+ function normaliseSafely(
317
+ schemaInput: unknown,
318
+ refInput: string | undefined,
319
+ ioSide: SchemaIoSide | undefined,
320
+ diags: DiagnosticsOptions | undefined
321
+ ): NormalisedShape | SchemaError {
322
+ try {
323
+ const opts =
324
+ diags !== undefined || ioSide !== undefined
325
+ ? {
326
+ ...(diags !== undefined ? { diagnostics: diags } : {}),
327
+ ...(ioSide !== undefined ? { io: ioSide } : {}),
328
+ }
329
+ : undefined;
330
+ const out = normaliseSchema(schemaInput, refInput, opts);
331
+ return out;
332
+ } catch (err: unknown) {
333
+ if (err instanceof SchemaNormalisationError) return err;
334
+ return new SchemaNormalisationError(
335
+ err instanceof Error
336
+ ? err.message
337
+ : "Failed to normalise schema",
338
+ schemaInput,
339
+ "unknown"
340
+ );
341
+ }
342
+ }
343
+
344
+ function runValidation(
345
+ zodSchema: unknown,
346
+ jsonSchema: Record<string, unknown>,
347
+ nextValue: unknown,
348
+ ioSide: SchemaIoSide | undefined,
349
+ diag: ((diagnostic: Diagnostic) => void) | undefined
350
+ ): unknown {
351
+ if (zodSchema !== undefined && isObject(zodSchema)) {
352
+ const resolvedIo: SchemaIoSide = ioSide ?? "output";
353
+ const useSafeEncode =
354
+ isCodecSchema(zodSchema) && resolvedIo === "output";
355
+ const validateFn = useSafeEncode
356
+ ? zodSchema.safeEncode
357
+ : zodSchema.safeParse;
358
+ if (typeof validateFn === "function") {
359
+ const result: unknown = validateFn(nextValue);
360
+ if (
361
+ isObject(result) &&
362
+ "success" in result &&
363
+ result.success !== true
364
+ ) {
365
+ return result.error;
366
+ }
367
+ return undefined;
368
+ }
369
+ }
370
+ let parsed: unknown;
371
+ try {
372
+ parsed = z.fromJSONSchema(jsonSchema);
373
+ } catch (err: unknown) {
374
+ if (diag !== undefined) {
375
+ const message =
376
+ err instanceof Error
377
+ ? err.message
378
+ : "z.fromJSONSchema threw a non-Error value";
379
+ diag({
380
+ code: "unsupported-type",
381
+ message:
382
+ "Skipping fallback validation: z.fromJSONSchema could not " +
383
+ `round-trip the normalised JSON Schema. Original message: ${message}`,
384
+ pointer: "",
385
+ detail: { source: "z.fromJSONSchema" },
386
+ });
387
+ return undefined;
388
+ }
389
+ return undefined;
390
+ }
391
+ if (isObject(parsed) && typeof parsed.safeParse === "function") {
392
+ const result: unknown = parsed.safeParse(nextValue);
393
+ if (
394
+ isObject(result) &&
395
+ "success" in result &&
396
+ result.success !== true
397
+ ) {
398
+ return result.error;
399
+ }
400
+ }
401
+ return undefined;
402
+ }
403
+ </script>
404
+
405
+ <script lang="ts" module>
406
+ let instanceCounter = 0;
407
+ /**
408
+ * Module-scoped counter feeding the default `idPrefix` for every
409
+ * `<SchemaComponent>` instance. Bumped once per mount so two
410
+ * components on the same page never share generated ids.
411
+ *
412
+ * Counter rather than `crypto.randomUUID()` because the renderer
413
+ * also runs in SSR / Node environments where the Web Crypto API
414
+ * is conditional. A monotonically increasing integer per process
415
+ * is sufficient for the per-page-uniqueness contract.
416
+ *
417
+ * @returns The next per-instance integer (1, 2, …).
418
+ */
419
+ export function nextInstanceId(): number {
420
+ instanceCounter += 1;
421
+ return instanceCounter;
422
+ }
423
+ </script>
424
+
425
+ {#if rootDescriptor !== null}
426
+ <Mount descriptor={rootDescriptor} />
427
+ {/if}