schema-components 0.0.0 → 1.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.
- package/CHANGELOG.md +59 -0
- package/LICENSE +21 -0
- package/README.md +526 -0
- package/dist/core/adapter.d.mts +19 -0
- package/dist/core/adapter.mjs +140 -0
- package/dist/core/errors.d.mts +2 -0
- package/dist/core/errors.mjs +74 -0
- package/dist/core/guards.d.mts +44 -0
- package/dist/core/guards.mjs +58 -0
- package/dist/core/renderer.d.mts +2 -0
- package/dist/core/renderer.mjs +71 -0
- package/dist/core/types.d.mts +3 -0
- package/dist/core/types.mjs +40 -0
- package/dist/core/walker.d.mts +14 -0
- package/dist/core/walker.mjs +366 -0
- package/dist/errors-DIKI2C78.d.mts +57 -0
- package/dist/html/a11y.d.mts +47 -0
- package/dist/html/a11y.mjs +81 -0
- package/dist/html/html.d.mts +135 -0
- package/dist/html/html.mjs +168 -0
- package/dist/html/renderToHtml.d.mts +32 -0
- package/dist/html/renderToHtml.mjs +352 -0
- package/dist/html/renderToHtmlStream.d.mts +58 -0
- package/dist/html/renderToHtmlStream.mjs +285 -0
- package/dist/html/styles.css +151 -0
- package/dist/openapi/components.d.mts +76 -0
- package/dist/openapi/components.mjs +223 -0
- package/dist/openapi/parser.d.mts +45 -0
- package/dist/openapi/parser.mjs +159 -0
- package/dist/react/SchemaComponent.d.mts +96 -0
- package/dist/react/SchemaComponent.mjs +283 -0
- package/dist/react/SchemaErrorBoundary.d.mts +26 -0
- package/dist/react/SchemaErrorBoundary.mjs +47 -0
- package/dist/react/headless.d.mts +13 -0
- package/dist/react/headless.mjs +163 -0
- package/dist/themes/shadcn.d.mts +6 -0
- package/dist/themes/shadcn.mjs +166 -0
- package/dist/types-BU0ETFHk.d.mts +326 -0
- package/package.json +113 -3
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { isObject } from "./guards.mjs";
|
|
2
|
+
import { resolveEditability } from "./types.mjs";
|
|
3
|
+
//#region src/core/walker.ts
|
|
4
|
+
function getString(obj, key) {
|
|
5
|
+
const value = obj[key];
|
|
6
|
+
return typeof value === "string" ? value : void 0;
|
|
7
|
+
}
|
|
8
|
+
function getNumber(obj, key) {
|
|
9
|
+
const value = obj[key];
|
|
10
|
+
return typeof value === "number" ? value : void 0;
|
|
11
|
+
}
|
|
12
|
+
function getArray(obj, key) {
|
|
13
|
+
const value = obj[key];
|
|
14
|
+
return Array.isArray(value) ? value : void 0;
|
|
15
|
+
}
|
|
16
|
+
function getObject(obj, key) {
|
|
17
|
+
const value = obj[key];
|
|
18
|
+
return isObject(value) ? value : void 0;
|
|
19
|
+
}
|
|
20
|
+
const MAX_REF_DEPTH = 10;
|
|
21
|
+
function resolveRef(schema, rootDocument, visited) {
|
|
22
|
+
const ref = getString(schema, "$ref");
|
|
23
|
+
if (ref === void 0) return schema;
|
|
24
|
+
if (visited.has(ref)) return { type: "unknown" };
|
|
25
|
+
if (visited.size >= MAX_REF_DEPTH) return { type: "unknown" };
|
|
26
|
+
const resolved = dereference(ref, rootDocument);
|
|
27
|
+
if (resolved === void 0) return { type: "unknown" };
|
|
28
|
+
const nextVisited = new Set(visited);
|
|
29
|
+
nextVisited.add(ref);
|
|
30
|
+
return resolveRef(resolved, rootDocument, nextVisited);
|
|
31
|
+
}
|
|
32
|
+
function dereference(ref, root) {
|
|
33
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
34
|
+
const parts = ref.slice(2).split("/");
|
|
35
|
+
let current = root;
|
|
36
|
+
for (const part of parts) {
|
|
37
|
+
if (!isObject(current)) return void 0;
|
|
38
|
+
const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
39
|
+
current = current[decoded];
|
|
40
|
+
}
|
|
41
|
+
return isObject(current) ? current : void 0;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Merge multiple JSON Schema objects from allOf into one.
|
|
45
|
+
* Merges: properties, required, meta fields, and constraints.
|
|
46
|
+
*/
|
|
47
|
+
function mergeAllOf(schemas) {
|
|
48
|
+
const merged = {};
|
|
49
|
+
const properties = {};
|
|
50
|
+
const required = [];
|
|
51
|
+
for (const entry of schemas) {
|
|
52
|
+
if (!isObject(entry)) continue;
|
|
53
|
+
const props = getObject(entry, "properties");
|
|
54
|
+
if (props !== void 0) for (const [key, value] of Object.entries(props)) properties[key] = value;
|
|
55
|
+
const req = getArray(entry, "required");
|
|
56
|
+
if (req !== void 0) {
|
|
57
|
+
for (const r of req) if (typeof r === "string" && !required.includes(r)) required.push(r);
|
|
58
|
+
}
|
|
59
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
60
|
+
if (key === "properties" || key === "required" || key === "allOf" || key === "type") continue;
|
|
61
|
+
if (!(key in merged)) merged[key] = value;
|
|
62
|
+
}
|
|
63
|
+
if (!("type" in merged)) {
|
|
64
|
+
const type = getString(entry, "type");
|
|
65
|
+
if (type !== void 0) merged.type = type;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(properties).length > 0) merged.properties = properties;
|
|
69
|
+
if (required.length > 0) merged.required = required;
|
|
70
|
+
return merged;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Detect `anyOf: [T, { type: "null" }]` → nullable T.
|
|
74
|
+
* Returns the non-null schema and a nullable flag.
|
|
75
|
+
*/
|
|
76
|
+
function normaliseAnyOf(options) {
|
|
77
|
+
if (options.length !== 2) return void 0;
|
|
78
|
+
let inner;
|
|
79
|
+
let hasNull = false;
|
|
80
|
+
for (const opt of options) {
|
|
81
|
+
if (!isObject(opt)) return void 0;
|
|
82
|
+
if (opt.type === "null") hasNull = true;
|
|
83
|
+
else inner = opt;
|
|
84
|
+
}
|
|
85
|
+
if (!hasNull || inner === void 0) return void 0;
|
|
86
|
+
return {
|
|
87
|
+
inner,
|
|
88
|
+
isNullable: true
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect oneOf where every option is an object with a property
|
|
93
|
+
* that has a `const` value → discriminated union.
|
|
94
|
+
*/
|
|
95
|
+
function detectDiscriminated(options) {
|
|
96
|
+
if (options.length === 0) return void 0;
|
|
97
|
+
let discriminator;
|
|
98
|
+
for (const opt of options) {
|
|
99
|
+
if (!isObject(opt)) return void 0;
|
|
100
|
+
const props = getObject(opt, "properties");
|
|
101
|
+
if (props === void 0) return void 0;
|
|
102
|
+
let foundKey;
|
|
103
|
+
for (const [key, value] of Object.entries(props)) if (isObject(value) && "const" in value) {
|
|
104
|
+
foundKey = key;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (foundKey === void 0) return void 0;
|
|
108
|
+
if (discriminator === void 0) discriminator = foundKey;
|
|
109
|
+
else if (discriminator !== foundKey) return;
|
|
110
|
+
}
|
|
111
|
+
if (discriminator === void 0) return void 0;
|
|
112
|
+
return {
|
|
113
|
+
options: options.filter(isObject),
|
|
114
|
+
discriminator
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const META_KEYWORDS = new Set([
|
|
118
|
+
"readOnly",
|
|
119
|
+
"writeOnly",
|
|
120
|
+
"description",
|
|
121
|
+
"title",
|
|
122
|
+
"deprecated",
|
|
123
|
+
"default",
|
|
124
|
+
"component",
|
|
125
|
+
"example",
|
|
126
|
+
"examples"
|
|
127
|
+
]);
|
|
128
|
+
function extractMetaFromJson(schema) {
|
|
129
|
+
const meta = {};
|
|
130
|
+
for (const [key, value] of Object.entries(schema)) if (META_KEYWORDS.has(key)) meta[key] = value;
|
|
131
|
+
return meta;
|
|
132
|
+
}
|
|
133
|
+
function extractConstraintsFromJson(schema) {
|
|
134
|
+
const constraints = {};
|
|
135
|
+
const minLength = getNumber(schema, "minLength");
|
|
136
|
+
if (minLength !== void 0) constraints.minLength = minLength;
|
|
137
|
+
const maxLength = getNumber(schema, "maxLength");
|
|
138
|
+
if (maxLength !== void 0) constraints.maxLength = maxLength;
|
|
139
|
+
const minimum = getNumber(schema, "minimum");
|
|
140
|
+
if (minimum !== void 0) constraints.minimum = minimum;
|
|
141
|
+
const maximum = getNumber(schema, "maximum");
|
|
142
|
+
if (maximum !== void 0) constraints.maximum = maximum;
|
|
143
|
+
const pattern = getString(schema, "pattern");
|
|
144
|
+
if (pattern !== void 0) constraints.pattern = pattern;
|
|
145
|
+
const format = getString(schema, "format");
|
|
146
|
+
if (format !== void 0) constraints.format = format;
|
|
147
|
+
const minItems = getNumber(schema, "minItems");
|
|
148
|
+
if (minItems !== void 0) constraints.minItems = minItems;
|
|
149
|
+
const maxItems = getNumber(schema, "maxItems");
|
|
150
|
+
if (maxItems !== void 0) constraints.maxItems = maxItems;
|
|
151
|
+
if (format === "binary") {
|
|
152
|
+
const contentMediaType = getString(schema, "contentMediaType");
|
|
153
|
+
if (contentMediaType !== void 0) constraints.mimeTypes = [contentMediaType];
|
|
154
|
+
}
|
|
155
|
+
return constraints;
|
|
156
|
+
}
|
|
157
|
+
const OVERRIDE_META_KEYS = new Set([
|
|
158
|
+
"readOnly",
|
|
159
|
+
"writeOnly",
|
|
160
|
+
"description",
|
|
161
|
+
"title",
|
|
162
|
+
"deprecated",
|
|
163
|
+
"component"
|
|
164
|
+
]);
|
|
165
|
+
function extractSchemaMetaFields(overrides) {
|
|
166
|
+
if (overrides === void 0) return void 0;
|
|
167
|
+
const meta = {};
|
|
168
|
+
for (const key of Object.keys(overrides)) if (OVERRIDE_META_KEYS.has(key)) meta[key] = overrides[key];
|
|
169
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
170
|
+
}
|
|
171
|
+
function extractChildOverride(overrides, key) {
|
|
172
|
+
if (overrides === void 0) return void 0;
|
|
173
|
+
const child = overrides[key];
|
|
174
|
+
if (child === void 0 || child === null) return void 0;
|
|
175
|
+
if (typeof child !== "object" || Array.isArray(child)) return void 0;
|
|
176
|
+
const result = {};
|
|
177
|
+
for (const [k, v] of Object.entries(child)) result[k] = v;
|
|
178
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
179
|
+
}
|
|
180
|
+
function walk(schema, options = {}) {
|
|
181
|
+
const { componentMeta, rootMeta, fieldOverrides, rootDocument } = options;
|
|
182
|
+
if (!isObject(schema)) return {
|
|
183
|
+
type: "unknown",
|
|
184
|
+
editability: "editable",
|
|
185
|
+
meta: {},
|
|
186
|
+
constraints: {}
|
|
187
|
+
};
|
|
188
|
+
const doc = rootDocument ?? schema;
|
|
189
|
+
return walkNode(resolveRef(schema, doc, /* @__PURE__ */ new Set()), {
|
|
190
|
+
componentMeta,
|
|
191
|
+
rootMeta,
|
|
192
|
+
fieldOverrides,
|
|
193
|
+
rootDocument: doc,
|
|
194
|
+
isNullable: false,
|
|
195
|
+
isOptional: false,
|
|
196
|
+
defaultValue: void 0
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function walkNode(schema, ctx) {
|
|
200
|
+
const allOf = getArray(schema, "allOf");
|
|
201
|
+
if (allOf !== void 0 && allOf.length > 0) return walkNode(mergeAllOf(allOf), ctx);
|
|
202
|
+
const anyOf = getArray(schema, "anyOf");
|
|
203
|
+
if (anyOf !== void 0) {
|
|
204
|
+
const nullable = normaliseAnyOf(anyOf);
|
|
205
|
+
if (nullable !== void 0) return walkNode(nullable.inner, {
|
|
206
|
+
...ctx,
|
|
207
|
+
isNullable: true
|
|
208
|
+
});
|
|
209
|
+
return walkUnion(anyOf, ctx);
|
|
210
|
+
}
|
|
211
|
+
const oneOf = getArray(schema, "oneOf");
|
|
212
|
+
if (oneOf !== void 0) {
|
|
213
|
+
const discriminated = detectDiscriminated(oneOf);
|
|
214
|
+
if (discriminated !== void 0) return walkDiscriminatedUnion(discriminated, ctx);
|
|
215
|
+
return walkUnion(oneOf, ctx);
|
|
216
|
+
}
|
|
217
|
+
const resolved = resolveRef(schema, ctx.rootDocument, /* @__PURE__ */ new Set());
|
|
218
|
+
const enumValues = getArray(resolved, "enum");
|
|
219
|
+
if (enumValues !== void 0) return walkEnum(resolved, enumValues, ctx);
|
|
220
|
+
if ("const" in resolved) return walkLiteral(resolved, ctx);
|
|
221
|
+
const type = getString(resolved, "type");
|
|
222
|
+
if (type === void 0) return buildField(resolved, "unknown", ctx);
|
|
223
|
+
if (type === "string") return walkString(resolved, ctx);
|
|
224
|
+
if (type === "number" || type === "integer") return walkNumber(resolved, ctx);
|
|
225
|
+
if (type === "boolean") return walkBoolean(resolved, ctx);
|
|
226
|
+
if (type === "null") return buildField(resolved, "null", ctx);
|
|
227
|
+
if (type === "object") {
|
|
228
|
+
const properties = getObject(resolved, "properties");
|
|
229
|
+
if (properties !== void 0) return walkObject(resolved, properties, ctx);
|
|
230
|
+
const additionalProps = getObject(resolved, "additionalProperties");
|
|
231
|
+
if (additionalProps !== void 0) return walkRecord(resolved, additionalProps, ctx);
|
|
232
|
+
return buildField(resolved, "object", ctx);
|
|
233
|
+
}
|
|
234
|
+
if (type === "array") return walkArray(resolved, ctx);
|
|
235
|
+
return buildField(resolved, "unknown", ctx);
|
|
236
|
+
}
|
|
237
|
+
function walkString(schema, ctx) {
|
|
238
|
+
if (getString(schema, "format") === "binary") return buildField(schema, "file", ctx);
|
|
239
|
+
return buildField(schema, "string", ctx);
|
|
240
|
+
}
|
|
241
|
+
function walkNumber(schema, ctx) {
|
|
242
|
+
return buildField(schema, "number", ctx);
|
|
243
|
+
}
|
|
244
|
+
function walkBoolean(schema, ctx) {
|
|
245
|
+
return buildField(schema, "boolean", ctx);
|
|
246
|
+
}
|
|
247
|
+
function walkEnum(schema, enumValues, ctx) {
|
|
248
|
+
return {
|
|
249
|
+
...buildField(schema, "enum", ctx),
|
|
250
|
+
enumValues: enumValues.filter((v) => typeof v === "string")
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function walkLiteral(schema, ctx) {
|
|
254
|
+
const constValue = schema.const;
|
|
255
|
+
const values = isPrimitive(constValue) ? [constValue] : [];
|
|
256
|
+
return {
|
|
257
|
+
...buildField(schema, "literal", ctx),
|
|
258
|
+
literalValues: values
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function walkObject(schema, properties, ctx) {
|
|
262
|
+
const base = buildField(schema, "object", ctx);
|
|
263
|
+
const required = getArray(schema, "required");
|
|
264
|
+
const fields = {};
|
|
265
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
266
|
+
const childOverride = extractChildOverride(ctx.fieldOverrides, key);
|
|
267
|
+
const isRequired = required?.includes(key) === true;
|
|
268
|
+
const childCtx = {
|
|
269
|
+
...ctx,
|
|
270
|
+
fieldOverrides: childOverride,
|
|
271
|
+
isOptional: !isRequired
|
|
272
|
+
};
|
|
273
|
+
const overrideMeta = extractSchemaMetaFields(childOverride);
|
|
274
|
+
if (overrideMeta !== void 0 && ("readOnly" in overrideMeta || "writeOnly" in overrideMeta)) childCtx.componentMeta = void 0;
|
|
275
|
+
if (isObject(propSchema)) fields[key] = walkNode(propSchema, childCtx);
|
|
276
|
+
else fields[key] = {
|
|
277
|
+
type: "unknown",
|
|
278
|
+
editability: "editable",
|
|
279
|
+
meta: {},
|
|
280
|
+
constraints: {}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
...base,
|
|
285
|
+
fields
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function walkRecord(schema, valueSchema, ctx) {
|
|
289
|
+
const base = buildField(schema, "record", ctx);
|
|
290
|
+
const propertyNames = getObject(schema, "propertyNames");
|
|
291
|
+
const keyType = propertyNames !== void 0 ? walkNode(propertyNames, ctx) : {
|
|
292
|
+
type: "string",
|
|
293
|
+
editability: "editable",
|
|
294
|
+
meta: {},
|
|
295
|
+
constraints: {}
|
|
296
|
+
};
|
|
297
|
+
const valueType = walkNode(valueSchema, ctx);
|
|
298
|
+
return {
|
|
299
|
+
...base,
|
|
300
|
+
keyType,
|
|
301
|
+
valueType
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function walkArray(schema, ctx) {
|
|
305
|
+
const base = buildField(schema, "array", ctx);
|
|
306
|
+
const items = getObject(schema, "items");
|
|
307
|
+
if (items !== void 0) {
|
|
308
|
+
const elementOverride = extractChildOverride(ctx.fieldOverrides, "[]");
|
|
309
|
+
return {
|
|
310
|
+
...base,
|
|
311
|
+
element: walkNode(items, {
|
|
312
|
+
...ctx,
|
|
313
|
+
fieldOverrides: elementOverride
|
|
314
|
+
})
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return base;
|
|
318
|
+
}
|
|
319
|
+
function walkUnion(options, ctx) {
|
|
320
|
+
const optionsArray = options.filter(isObject);
|
|
321
|
+
return {
|
|
322
|
+
...buildField({}, "union", ctx),
|
|
323
|
+
options: optionsArray.map((opt) => walkNode(opt, {
|
|
324
|
+
...ctx,
|
|
325
|
+
fieldOverrides: void 0
|
|
326
|
+
}))
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function walkDiscriminatedUnion(discriminated, ctx) {
|
|
330
|
+
return {
|
|
331
|
+
...buildField({}, "discriminatedUnion", ctx),
|
|
332
|
+
options: discriminated.options.map((opt) => walkNode(opt, {
|
|
333
|
+
...ctx,
|
|
334
|
+
fieldOverrides: void 0
|
|
335
|
+
})),
|
|
336
|
+
discriminator: discriminated.discriminator
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function buildField(schema, type, ctx) {
|
|
340
|
+
const propertyMeta = extractMetaFromJson(schema);
|
|
341
|
+
const overrideMeta = extractSchemaMetaFields(ctx.fieldOverrides);
|
|
342
|
+
const mergedMeta = {
|
|
343
|
+
...propertyMeta,
|
|
344
|
+
...overrideMeta
|
|
345
|
+
};
|
|
346
|
+
const defaultValue = "default" in schema ? schema.default : void 0;
|
|
347
|
+
const editability = resolveEditability(mergedMeta, ctx.componentMeta, ctx.rootMeta);
|
|
348
|
+
if ((overrideMeta !== void 0 && ("readOnly" in overrideMeta || "writeOnly" in overrideMeta) || Boolean(propertyMeta.readOnly) || Boolean(propertyMeta.writeOnly)) && ctx.componentMeta !== void 0) ctx = {
|
|
349
|
+
...ctx,
|
|
350
|
+
componentMeta: void 0
|
|
351
|
+
};
|
|
352
|
+
return {
|
|
353
|
+
type,
|
|
354
|
+
editability,
|
|
355
|
+
meta: mergedMeta,
|
|
356
|
+
isOptional: ctx.isOptional,
|
|
357
|
+
isNullable: ctx.isNullable,
|
|
358
|
+
defaultValue: defaultValue ?? ctx.defaultValue,
|
|
359
|
+
constraints: extractConstraintsFromJson(schema)
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function isPrimitive(value) {
|
|
363
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
|
|
364
|
+
}
|
|
365
|
+
//#endregion
|
|
366
|
+
export { walk };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/core/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Structured error types for schema-components.
|
|
4
|
+
*
|
|
5
|
+
* Every error produced by the library is one of these three types:
|
|
6
|
+
*
|
|
7
|
+
* - SchemaNormalisationError — the adapter failed to convert the input
|
|
8
|
+
* to JSON Schema (invalid Zod, bad OpenAPI ref, malformed schema)
|
|
9
|
+
* - SchemaRenderError — a theme adapter's render function threw
|
|
10
|
+
* - SchemaFieldError — a field path couldn't be resolved
|
|
11
|
+
*
|
|
12
|
+
* All extend `SchemaError` so consumers can catch the base class.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Base class for all schema-components errors.
|
|
16
|
+
* Catch this to handle any library error uniformly.
|
|
17
|
+
*/
|
|
18
|
+
declare class SchemaError extends Error {
|
|
19
|
+
/** The schema input that caused the error. */
|
|
20
|
+
readonly schema: unknown;
|
|
21
|
+
constructor(message: string, schema: unknown);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The adapter failed to convert the input schema to JSON Schema.
|
|
25
|
+
*
|
|
26
|
+
* Causes: invalid Zod schema, Zod 3 schema (unsupported), malformed
|
|
27
|
+
* JSON Schema, missing OpenAPI ref, unsupported ref format.
|
|
28
|
+
*/
|
|
29
|
+
declare class SchemaNormalisationError extends SchemaError {
|
|
30
|
+
readonly kind: "invalid-zod" | "zod3-unsupported" | "invalid-json-schema" | "openapi-missing-ref" | "openapi-invalid" | "unknown";
|
|
31
|
+
constructor(message: string, schema: unknown, kind: SchemaNormalisationError["kind"]);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A theme adapter's render function threw during rendering.
|
|
35
|
+
*
|
|
36
|
+
* The `cause` is the original error from the render function.
|
|
37
|
+
*/
|
|
38
|
+
declare class SchemaRenderError extends SchemaError {
|
|
39
|
+
/** The schema type being rendered when the error occurred. */
|
|
40
|
+
readonly schemaType: string;
|
|
41
|
+
/** The original error from the render function. */
|
|
42
|
+
readonly cause: unknown;
|
|
43
|
+
constructor(message: string, schema: unknown, schemaType: string, cause: unknown);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A field path couldn't be resolved against the walked schema tree.
|
|
47
|
+
*
|
|
48
|
+
* This is produced by `<SchemaField>` when the `path` prop doesn't
|
|
49
|
+
* match any field in the schema.
|
|
50
|
+
*/
|
|
51
|
+
declare class SchemaFieldError extends SchemaError {
|
|
52
|
+
/** The unresolvable dot-separated path. */
|
|
53
|
+
readonly path: string;
|
|
54
|
+
constructor(message: string, schema: unknown, path: string);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { SchemaRenderError as i, SchemaFieldError as n, SchemaNormalisationError as r, SchemaError as t };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { _ as WalkedField, n as FieldConstraints } from "../types-BU0ETFHk.mjs";
|
|
2
|
+
import { HtmlAttributes, HtmlNode } from "./html.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/html/a11y.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Build the input ID for a field at a given path.
|
|
7
|
+
*/
|
|
8
|
+
declare function buildInputId(path: string, key: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Derive the hint element ID from the input ID.
|
|
11
|
+
*/
|
|
12
|
+
declare function buildHintId(inputId: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Build a human-readable constraint description string.
|
|
15
|
+
* Returns undefined if no constraints are present.
|
|
16
|
+
*/
|
|
17
|
+
declare function constraintHint(c: FieldConstraints): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Build `aria-required` attribute for required fields.
|
|
20
|
+
* Returns an object to spread into `h()` attributes, or empty object.
|
|
21
|
+
*/
|
|
22
|
+
declare function ariaRequiredAttrs(tree: WalkedField): Pick<HtmlAttributes, "aria-required"> | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Build `aria-describedby` attribute pointing to the constraint hint element.
|
|
25
|
+
* Only present when constraints exist.
|
|
26
|
+
*/
|
|
27
|
+
declare function ariaDescribedByAttrs(inputId: string, constraints: FieldConstraints): Pick<HtmlAttributes, "aria-describedby"> | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Build `aria-readonly` attribute for read-only presentation.
|
|
30
|
+
*/
|
|
31
|
+
declare function ariaReadonlyAttrs(): Pick<HtmlAttributes, "aria-readonly">;
|
|
32
|
+
/**
|
|
33
|
+
* Build `aria-label` attribute from description, if present.
|
|
34
|
+
*/
|
|
35
|
+
declare function ariaLabelAttrs(description: unknown): Pick<HtmlAttributes, "aria-label"> | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Build a `<small class="sc-hint">` element for constraint hints.
|
|
38
|
+
* Returns undefined if no constraints are present.
|
|
39
|
+
*/
|
|
40
|
+
declare function buildHintElement(inputId: string, constraints: FieldConstraints): HtmlNode;
|
|
41
|
+
/**
|
|
42
|
+
* Build the required-field asterisk indicator for labels.
|
|
43
|
+
* Returns undefined if the field is optional.
|
|
44
|
+
*/
|
|
45
|
+
declare function requiredIndicator(field: WalkedField): HtmlNode;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildHintId, buildInputId, constraintHint, requiredIndicator };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { h } from "./html.mjs";
|
|
2
|
+
//#region src/html/a11y.ts
|
|
3
|
+
/**
|
|
4
|
+
* Build the input ID for a field at a given path.
|
|
5
|
+
*/
|
|
6
|
+
function buildInputId(path, key) {
|
|
7
|
+
return `sc-${path ? `${path}-${key}` : key}`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Derive the hint element ID from the input ID.
|
|
11
|
+
*/
|
|
12
|
+
function buildHintId(inputId) {
|
|
13
|
+
return `${inputId}-hint`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a human-readable constraint description string.
|
|
17
|
+
* Returns undefined if no constraints are present.
|
|
18
|
+
*/
|
|
19
|
+
function constraintHint(c) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
if (c.minLength !== void 0) parts.push(`Minimum ${String(c.minLength)} characters`);
|
|
22
|
+
if (c.maxLength !== void 0) parts.push(`Maximum ${String(c.maxLength)} characters`);
|
|
23
|
+
if (c.minimum !== void 0) parts.push(`Minimum ${String(c.minimum)}`);
|
|
24
|
+
if (c.maximum !== void 0) parts.push(`Maximum ${String(c.maximum)}`);
|
|
25
|
+
if (c.pattern !== void 0 && c.format === void 0) parts.push("Must match pattern");
|
|
26
|
+
if (c.minItems !== void 0) parts.push(`Minimum ${String(c.minItems)} items`);
|
|
27
|
+
if (c.maxItems !== void 0) parts.push(`Maximum ${String(c.maxItems)} items`);
|
|
28
|
+
if (parts.length === 0) return void 0;
|
|
29
|
+
return parts.join(". ");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build `aria-required` attribute for required fields.
|
|
33
|
+
* Returns an object to spread into `h()` attributes, or empty object.
|
|
34
|
+
*/
|
|
35
|
+
function ariaRequiredAttrs(tree) {
|
|
36
|
+
if (tree.isOptional === false) return { "aria-required": "true" };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build `aria-describedby` attribute pointing to the constraint hint element.
|
|
40
|
+
* Only present when constraints exist.
|
|
41
|
+
*/
|
|
42
|
+
function ariaDescribedByAttrs(inputId, constraints) {
|
|
43
|
+
if (constraintHint(constraints) === void 0) return void 0;
|
|
44
|
+
return { "aria-describedby": buildHintId(inputId) };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build `aria-readonly` attribute for read-only presentation.
|
|
48
|
+
*/
|
|
49
|
+
function ariaReadonlyAttrs() {
|
|
50
|
+
return { "aria-readonly": "true" };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build `aria-label` attribute from description, if present.
|
|
54
|
+
*/
|
|
55
|
+
function ariaLabelAttrs(description) {
|
|
56
|
+
if (typeof description === "string" && description.length > 0) return { "aria-label": description };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a `<small class="sc-hint">` element for constraint hints.
|
|
60
|
+
* Returns undefined if no constraints are present.
|
|
61
|
+
*/
|
|
62
|
+
function buildHintElement(inputId, constraints) {
|
|
63
|
+
const hint = constraintHint(constraints);
|
|
64
|
+
if (hint === void 0) return void 0;
|
|
65
|
+
return h("small", {
|
|
66
|
+
class: "sc-hint",
|
|
67
|
+
id: buildHintId(inputId)
|
|
68
|
+
}, hint);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build the required-field asterisk indicator for labels.
|
|
72
|
+
* Returns undefined if the field is optional.
|
|
73
|
+
*/
|
|
74
|
+
function requiredIndicator(field) {
|
|
75
|
+
if (field.isOptional === false) return h("span", {
|
|
76
|
+
class: "sc-required",
|
|
77
|
+
"aria-hidden": "true"
|
|
78
|
+
}, " *");
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildHintId, buildInputId, constraintHint, requiredIndicator };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//#region src/html/html.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed HTML builder — structured HTML construction with compile-time safety.
|
|
4
|
+
*
|
|
5
|
+
* Instead of string templates, renderers call `h(tag, attrs, ...children)` to
|
|
6
|
+
* build an AST, then `serialize()` converts it to an HTML string. This gives:
|
|
7
|
+
*
|
|
8
|
+
* - Compile-time checking of tag names and attribute keys
|
|
9
|
+
* - Automatic HTML escaping (serialiser handles it — callers never escape manually)
|
|
10
|
+
* - Streaming via `serializeChunks()` which yields at element boundaries
|
|
11
|
+
* - Zero dependencies
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
*
|
|
15
|
+
* import { h, serialize } from "./html.ts";
|
|
16
|
+
*
|
|
17
|
+
* const el = h("input", { type: "text", id: "name", "aria-required": true });
|
|
18
|
+
* serialize(el); // → '<input type="text" id="name" aria-required>'
|
|
19
|
+
*
|
|
20
|
+
* const form = h("form", {},
|
|
21
|
+
* h("label", { for: "name" }, "Name"),
|
|
22
|
+
* h("input", { type: "text", id: "name" }),
|
|
23
|
+
* );
|
|
24
|
+
* serialize(form); // → '<form><label for="name">Name</label><input type="text" id="name"></form>'
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* An HTML element node. Void elements (input, br, etc.) have no children
|
|
28
|
+
* in the serialiser regardless of what's passed.
|
|
29
|
+
*/
|
|
30
|
+
interface HtmlElement {
|
|
31
|
+
readonly tag: string;
|
|
32
|
+
readonly attributes: Readonly<HtmlAttributes>;
|
|
33
|
+
readonly children: readonly (HtmlElement | HtmlText | HtmlRaw | string)[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A text node. The `text` value is stored raw (unescaped) — the serialiser
|
|
37
|
+
* escapes it during output. Callers should NOT pre-escape.
|
|
38
|
+
*/
|
|
39
|
+
interface HtmlText {
|
|
40
|
+
readonly text: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A raw HTML node. The `html` value is emitted verbatim — NOT escaped.
|
|
44
|
+
* Use for embedding already-serialised HTML from resolvers or external sources.
|
|
45
|
+
* Never use for user-supplied data.
|
|
46
|
+
*/
|
|
47
|
+
interface HtmlRaw {
|
|
48
|
+
readonly html: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Any node that can appear in the HTML tree.
|
|
52
|
+
* - `string` is treated as a text node (will be escaped by the serialiser)
|
|
53
|
+
* - `HtmlElement` and `HtmlText` are structured nodes
|
|
54
|
+
* - `undefined` and `null` are silently dropped (useful for conditional children)
|
|
55
|
+
* - `false` is silently dropped (useful for `{condition && h(...)}`)
|
|
56
|
+
*/
|
|
57
|
+
type HtmlNode = HtmlElement | HtmlText | HtmlRaw | string | undefined | null | false;
|
|
58
|
+
/**
|
|
59
|
+
* Attribute value types. `true` renders as a boolean attribute (`disabled`),
|
|
60
|
+
* `false` and `undefined` are omitted. Numbers are converted to strings.
|
|
61
|
+
*/
|
|
62
|
+
type AttrValue = string | number | boolean | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* HTML attributes. Standard attributes are typed per-element via overloads;
|
|
65
|
+
* arbitrary `data-*` and `aria-*` keys are allowed via index signature.
|
|
66
|
+
*/
|
|
67
|
+
type HtmlAttributes = Record<string, AttrValue>;
|
|
68
|
+
declare const VOID_ELEMENTS: Set<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Build an HTML element node.
|
|
71
|
+
*
|
|
72
|
+
* - Tag name is type-checked (must be a known HTML tag)
|
|
73
|
+
* - Attributes are collected as a record — callers get IntelliSense for
|
|
74
|
+
* common attributes but can also pass `aria-*`, `data-*` etc.
|
|
75
|
+
* - Children are flattened; `undefined`, `null`, and `false` are dropped.
|
|
76
|
+
* - For void elements (input, img, etc.), children are ignored.
|
|
77
|
+
*
|
|
78
|
+
* @param tag - HTML element tag name
|
|
79
|
+
* @param attrs - Optional attributes (class, id, aria-*, etc.)
|
|
80
|
+
* @param children - Child nodes (strings are escaped by the serialiser)
|
|
81
|
+
*/
|
|
82
|
+
declare function h(tag: string, attrs?: HtmlAttributes, ...children: HtmlNode[]): HtmlElement;
|
|
83
|
+
/**
|
|
84
|
+
* Create a text node. The value is NOT escaped — the serialiser handles it.
|
|
85
|
+
* Use this for dynamic text that must appear in the output.
|
|
86
|
+
*/
|
|
87
|
+
declare function text(value: string): HtmlText;
|
|
88
|
+
/**
|
|
89
|
+
* Create a raw HTML node. The value is emitted verbatim — NOT escaped.
|
|
90
|
+
* Use for embedding already-serialised HTML (e.g. from child renderers).
|
|
91
|
+
* Never use for user-supplied data.
|
|
92
|
+
*/
|
|
93
|
+
declare function raw(html: string): HtmlRaw;
|
|
94
|
+
/**
|
|
95
|
+
* Serialise an HTML node to a string.
|
|
96
|
+
*
|
|
97
|
+
* - Text content is automatically escaped
|
|
98
|
+
* - Void elements are self-closing
|
|
99
|
+
* - Boolean attributes render as just the name (`disabled`, `checked`)
|
|
100
|
+
* - `false`/`undefined` attribute values are omitted
|
|
101
|
+
*
|
|
102
|
+
* @param node - An HtmlElement, HtmlText, or string to serialise
|
|
103
|
+
* @returns HTML string
|
|
104
|
+
*/
|
|
105
|
+
declare function serialize(node: HtmlNode): string;
|
|
106
|
+
declare function serializeElement(el: HtmlElement): string;
|
|
107
|
+
declare function serializeAttributes(attrs: HtmlAttributes): string;
|
|
108
|
+
/**
|
|
109
|
+
* Serialise an HTML node to chunks, yielded at natural element boundaries.
|
|
110
|
+
*
|
|
111
|
+
* - Each top-level child element becomes its own chunk
|
|
112
|
+
* - Leaf text within an element stays with its parent
|
|
113
|
+
* - Void elements are single chunks
|
|
114
|
+
*
|
|
115
|
+
* This is used by the streaming renderer to produce incremental output.
|
|
116
|
+
*
|
|
117
|
+
* @param node - An HTML node to serialise
|
|
118
|
+
* @returns Iterable of HTML string chunks
|
|
119
|
+
*/
|
|
120
|
+
declare function serializeChunks(node: HtmlNode): Iterable<string, void, undefined>;
|
|
121
|
+
/**
|
|
122
|
+
* Escape a string for safe inclusion in HTML text content or attribute values.
|
|
123
|
+
*/
|
|
124
|
+
declare function escapeHtml(str: string): string;
|
|
125
|
+
/**
|
|
126
|
+
* Create a fragment: children rendered sequentially with no wrapping element.
|
|
127
|
+
* Useful when a renderer needs to return multiple top-level nodes.
|
|
128
|
+
*/
|
|
129
|
+
declare function fragment(...children: HtmlNode[]): HtmlElement;
|
|
130
|
+
/**
|
|
131
|
+
* Serialise a node, treating fragments (empty tag) as just their children.
|
|
132
|
+
*/
|
|
133
|
+
declare function serializeFragment(node: HtmlNode): string;
|
|
134
|
+
//#endregion
|
|
135
|
+
export { AttrValue, HtmlAttributes, HtmlElement, HtmlNode, HtmlRaw, HtmlText, VOID_ELEMENTS, escapeHtml, fragment, h, raw, serialize, serializeAttributes, serializeChunks, serializeElement, serializeFragment, text };
|