schema-components 1.20.0 → 1.22.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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/core/adapter.d.mts +28 -4
  3. package/dist/core/adapter.mjs +408 -71
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -2
  6. package/dist/core/diagnostics.d.mts +1 -1
  7. package/dist/core/errors.d.mts +1 -1
  8. package/dist/core/errors.mjs +9 -15
  9. package/dist/core/fieldOrder.d.mts +1 -1
  10. package/dist/core/formats.d.mts +22 -1
  11. package/dist/core/formats.mjs +21 -0
  12. package/dist/core/limits.d.mts +2 -0
  13. package/dist/core/limits.mjs +23 -0
  14. package/dist/core/merge.d.mts +11 -2
  15. package/dist/core/merge.mjs +11 -0
  16. package/dist/core/normalise.d.mts +36 -4
  17. package/dist/core/normalise.mjs +2 -2
  18. package/dist/core/openapi30.d.mts +24 -1
  19. package/dist/core/openapi30.mjs +2 -2
  20. package/dist/core/ref.d.mts +1 -1
  21. package/dist/core/ref.mjs +35 -9
  22. package/dist/core/renderer.d.mts +1 -1
  23. package/dist/core/renderer.mjs +0 -2
  24. package/dist/core/swagger2.d.mts +1 -1
  25. package/dist/core/swagger2.mjs +1 -1
  26. package/dist/core/typeInference.d.mts +2 -2
  27. package/dist/core/types.d.mts +2 -2
  28. package/dist/core/types.mjs +1 -4
  29. package/dist/core/version.d.mts +1 -1
  30. package/dist/core/walkBuilders.d.mts +13 -5
  31. package/dist/core/walkBuilders.mjs +11 -3
  32. package/dist/core/walker.d.mts +1 -1
  33. package/dist/core/walker.mjs +110 -26
  34. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-D0QCYGv0.d.mts} +1 -1
  35. package/dist/{errors-C2iABcn9.d.mts → errors-DpFwqs5C.d.mts} +7 -11
  36. package/dist/html/a11y.d.mts +2 -2
  37. package/dist/html/a11y.mjs +10 -3
  38. package/dist/html/renderToHtml.d.mts +10 -3
  39. package/dist/html/renderToHtml.mjs +13 -3
  40. package/dist/html/renderToHtmlStream.d.mts +2 -2
  41. package/dist/html/renderers.d.mts +2 -2
  42. package/dist/html/renderers.mjs +1 -6
  43. package/dist/html/streamRenderers.d.mts +5 -4
  44. package/dist/html/streamRenderers.mjs +91 -30
  45. package/dist/limits-Cw5QZND8.d.mts +29 -0
  46. package/dist/{normalise-CMMEl4cd.mjs → normalise-DVEJQmF7.mjs} +791 -141
  47. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  48. package/dist/openapi/ApiLinks.d.mts +1 -1
  49. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  50. package/dist/openapi/ApiSecurity.d.mts +1 -1
  51. package/dist/openapi/ApiSecurity.mjs +127 -7
  52. package/dist/openapi/components.d.mts +175 -21
  53. package/dist/openapi/components.mjs +145 -21
  54. package/dist/openapi/parser.d.mts +1 -1
  55. package/dist/openapi/parser.mjs +74 -7
  56. package/dist/openapi/resolve.d.mts +70 -12
  57. package/dist/openapi/resolve.mjs +265 -42
  58. package/dist/react/SchemaComponent.d.mts +100 -35
  59. package/dist/react/SchemaComponent.mjs +66 -24
  60. package/dist/react/SchemaView.d.mts +3 -3
  61. package/dist/react/SchemaView.mjs +2 -2
  62. package/dist/react/fieldPath.d.mts +1 -1
  63. package/dist/react/headless.d.mts +1 -1
  64. package/dist/react/headless.mjs +1 -2
  65. package/dist/react/headlessRenderers.d.mts +3 -4
  66. package/dist/react/headlessRenderers.mjs +11 -31
  67. package/dist/{ref-C8JbwfiS.d.mts → ref-D-_JBZkF.d.mts} +7 -2
  68. package/dist/{renderer-SOIbJBtk.d.mts → renderer-BaRlQIuN.d.mts} +2 -2
  69. package/dist/themes/mantine.d.mts +1 -1
  70. package/dist/themes/mui.d.mts +1 -1
  71. package/dist/themes/radix.d.mts +1 -1
  72. package/dist/themes/shadcn.d.mts +1 -1
  73. package/dist/typeInference-DkcUHfaM.d.mts +982 -0
  74. package/dist/{types-C9zw9wbX.d.mts → types-BrRMV0en.d.mts} +15 -12
  75. package/package.json +1 -3
  76. package/dist/typeInference-CDoD_LZ_.d.mts +0 -533
  77. /package/dist/{version-D-u7aMfy.d.mts → version-D2jfdX6E.d.mts} +0 -0
@@ -1,9 +1,10 @@
1
1
  import { isObject } from "./guards.mjs";
2
2
  import { appendPointer, emitDiagnostic } from "./diagnostics.mjs";
3
+ import { isPrototypePollutingKey } from "./uri.mjs";
3
4
  import { countDistinctRefs, resolveRef } from "./ref.mjs";
5
+ import { matchJsonSchemaDraftUri } from "./version.mjs";
4
6
  import { extractArrayConstraints, extractObjectConstraints, stripInapplicableConstraints } from "./constraints.mjs";
5
7
  import { ANNOTATION_SIBLINGS, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf } from "./merge.mjs";
6
- import { isPrototypePollutingKey } from "./uri.mjs";
7
8
  import { buildBase, buildBooleanField, buildFileField, buildNullField, buildNumberField, buildStringField, buildUnknownField, extractChildOverride, extractSchemaMetaFields, getArray, getObject, getString, isPrimitive, walkDependentRequiredMap, walkSubSchemaMap, withoutKeys } from "./walkBuilders.mjs";
8
9
  //#region src/core/walker.ts
9
10
  /**
@@ -40,6 +41,55 @@ function walkSubSchema(value, ctx) {
40
41
  constraints: {}
41
42
  };
42
43
  }
44
+ /**
45
+ * Rank an `unevaluatedProperties`/`unevaluatedItems` value by how
46
+ * restrictive it is. Higher is stricter. The ordering reflects
47
+ * JSON Schema 2020-12 §11.2/§11.3 semantics:
48
+ *
49
+ * false (forbid all extras)
50
+ * > schema-object (extras must match the schema)
51
+ * > true (extras explicitly permitted)
52
+ * > absent (extras implicitly permitted)
53
+ *
54
+ * Unknown shapes (numbers, arrays, strings) sort below absent — we
55
+ * cannot reason about them, so do not let them override anything.
56
+ */
57
+ function unevaluatedRank(value) {
58
+ if (value === false) return 3;
59
+ if (isObject(value)) return 2;
60
+ if (value === true) return 1;
61
+ if (value === void 0) return 0;
62
+ return -1;
63
+ }
64
+ /**
65
+ * Pick the strictest `unevaluatedProperties` / `unevaluatedItems` across
66
+ * a set of `allOf` branches (including the parent prepended as a
67
+ * branch) and apply it to the merged node. `mergeAllOf` already collects
68
+ * properties from every branch into the merged result; surfacing the
69
+ * strictest unevaluated keyword closes the loop so the walker's object
70
+ * builder sees the spec-correct value.
71
+ *
72
+ * The merged node is mutated in place — that is consistent with the
73
+ * surrounding walker code, which treats the merge output as a fresh
74
+ * working node.
75
+ */
76
+ function applyStrictestUnevaluated(merged, branches) {
77
+ let strictestProps = merged.unevaluatedProperties;
78
+ let strictestItems = merged.unevaluatedItems;
79
+ for (const branch of branches) {
80
+ if (!isObject(branch)) continue;
81
+ if ("unevaluatedProperties" in branch) {
82
+ const candidate = branch.unevaluatedProperties;
83
+ if (unevaluatedRank(candidate) > unevaluatedRank(strictestProps)) strictestProps = candidate;
84
+ }
85
+ if ("unevaluatedItems" in branch) {
86
+ const candidate = branch.unevaluatedItems;
87
+ if (unevaluatedRank(candidate) > unevaluatedRank(strictestItems)) strictestItems = candidate;
88
+ }
89
+ }
90
+ if (strictestProps !== void 0) merged.unevaluatedProperties = strictestProps;
91
+ if (strictestItems !== void 0) merged.unevaluatedItems = strictestItems;
92
+ }
43
93
  function walk(schema, options = {}) {
44
94
  const { componentMeta, rootMeta, fieldOverrides, rootDocument, diagnostics, externalResolver } = options;
45
95
  if (typeof schema === "boolean") return walkBooleanSchema(schema);
@@ -75,7 +125,13 @@ function walk(schema, options = {}) {
75
125
  }
76
126
  function walkNode(schema, ctx) {
77
127
  const allOf = getArray(schema, "allOf");
78
- if (allOf !== void 0 && allOf.length > 0) return walkNode(mergeAllOf(allOf, ctx.diagnostics, ctx.pointer), ctx);
128
+ if (allOf !== void 0 && allOf.length > 0) {
129
+ const branches = [withoutKeys(schema, ["allOf"]), ...allOf];
130
+ const merged = mergeAllOf(branches, ctx.diagnostics, ctx.pointer);
131
+ if (merged === false) return walkBooleanSchema(false);
132
+ applyStrictestUnevaluated(merged, branches);
133
+ return walkNode(merged, ctx);
134
+ }
79
135
  const anyOf = getArray(schema, "anyOf");
80
136
  if (anyOf !== void 0) {
81
137
  const nullable = normaliseAnyOf(anyOf);
@@ -87,6 +143,11 @@ function walkNode(schema, ctx) {
87
143
  }
88
144
  const oneOf = getArray(schema, "oneOf");
89
145
  if (oneOf !== void 0) {
146
+ const nullable = normaliseAnyOf(oneOf);
147
+ if (nullable !== void 0) return walkNode(nullable.inner, {
148
+ ...ctx,
149
+ isNullable: true
150
+ });
90
151
  const discriminated = detectDiscriminated(oneOf, ctx.diagnostics, ctx.pointer);
91
152
  if (discriminated !== void 0) return walkDiscriminatedUnion(discriminated, ctx);
92
153
  return walkUnion(oneOf, ctx);
@@ -112,32 +173,27 @@ function walkNode(schema, ctx) {
112
173
  Object.assign(placeholder, result);
113
174
  return placeholder;
114
175
  }
115
- const ifSchema = getObject(schema, "if");
116
- if (ifSchema !== void 0) {
176
+ if ("if" in schema) {
117
177
  emitDiagnostic(ctx.diagnostics, {
118
178
  code: "conditional-fallback",
119
179
  message: "if/then/else rendered as base schema; conditionals require runtime evaluation",
120
180
  pointer: ctx.pointer
121
181
  });
122
- const base = buildBase(withoutKeys(schema, [
123
- "if",
124
- "then",
125
- "else"
126
- ]), ctx);
127
- const thenSchema = getObject(schema, "then");
128
- const elseSchema = getObject(schema, "else");
129
182
  const conditional = {
130
- ...base,
183
+ ...buildBase(withoutKeys(schema, [
184
+ "if",
185
+ "then",
186
+ "else"
187
+ ]), ctx),
131
188
  type: "conditional",
132
189
  constraints: {},
133
- ifClause: walkNode(ifSchema, ctx)
190
+ ifClause: walkSubSchema(schema.if, ctx)
134
191
  };
135
- if (thenSchema !== void 0) conditional.thenClause = walkNode(thenSchema, ctx);
136
- if (elseSchema !== void 0) conditional.elseClause = walkNode(elseSchema, ctx);
192
+ if ("then" in schema) conditional.thenClause = walkSubSchema(schema.then, ctx);
193
+ if ("else" in schema) conditional.elseClause = walkSubSchema(schema.else, ctx);
137
194
  return conditional;
138
195
  }
139
- const notSchema = getObject(schema, "not");
140
- if (notSchema !== void 0) {
196
+ if ("not" in schema) {
141
197
  emitDiagnostic(ctx.diagnostics, {
142
198
  code: "type-negation-fallback",
143
199
  message: "not schema rendered as negation; TypeScript cannot negate types",
@@ -147,7 +203,7 @@ function walkNode(schema, ctx) {
147
203
  ...buildBase(withoutKeys(schema, ["not"]), ctx),
148
204
  type: "negation",
149
205
  constraints: {},
150
- negated: walkNode(notSchema, ctx)
206
+ negated: walkSubSchema(schema.not, ctx)
151
207
  };
152
208
  }
153
209
  const enumValues = getArray(schema, "enum");
@@ -223,11 +279,36 @@ function walkNode(schema, ctx) {
223
279
  });
224
280
  return buildUnknownField(schema, ctx);
225
281
  }
282
+ /**
283
+ * Drafts that pre-date the `contentSchema` keyword (added in Draft
284
+ * 2019-09). Used to emit `keyword-out-of-draft` when a document
285
+ * declaring one of these drafts uses `contentSchema` anyway.
286
+ */
287
+ const PRE_CONTENT_SCHEMA_DRAFTS = new Set([
288
+ "draft-04",
289
+ "draft-06",
290
+ "draft-07"
291
+ ]);
226
292
  function walkString(schema, ctx) {
227
293
  if (getString(schema, "format") === "binary") return buildFileField(schema, ctx);
228
294
  const field = buildStringField(schema, ctx);
229
295
  const contentSchema = getObject(schema, "contentSchema");
230
- if (contentSchema !== void 0) field.meta.decodedSchema = walkNode(contentSchema, ctx);
296
+ if (contentSchema !== void 0) {
297
+ const rootSchema = getString(ctx.rootDocument, "$schema");
298
+ if (rootSchema !== void 0) {
299
+ const draft = matchJsonSchemaDraftUri(rootSchema);
300
+ if (draft !== void 0 && PRE_CONTENT_SCHEMA_DRAFTS.has(draft)) emitDiagnostic(ctx.diagnostics, {
301
+ code: "keyword-out-of-draft",
302
+ message: `\`contentSchema\` is a Draft 2019-09+ keyword; the document declares ${draft}`,
303
+ pointer: appendPointer(ctx.pointer, "contentSchema"),
304
+ detail: {
305
+ keyword: "contentSchema",
306
+ declaredDraft: draft
307
+ }
308
+ });
309
+ }
310
+ field.meta.decodedSchema = walkNode(contentSchema, ctx);
311
+ }
231
312
  return field;
232
313
  }
233
314
  function walkNumber(schema, ctx) {
@@ -321,7 +402,7 @@ function walkObject(schema, properties, ctx) {
321
402
  };
322
403
  }
323
404
  const patternProps = getObject(schema, "patternProperties");
324
- const walkedPatternProps = patternProps !== void 0 ? walkSubSchemaMap(patternProps, walkNode, ctx) : void 0;
405
+ const walkedPatternProps = patternProps !== void 0 ? walkSubSchemaMap(patternProps, walkSubSchema, ctx) : void 0;
325
406
  let additionalPropertiesClosed;
326
407
  let additionalPropertiesSchema;
327
408
  const additionalProps = schema.additionalProperties;
@@ -334,7 +415,7 @@ function walkObject(schema, properties, ctx) {
334
415
  };
335
416
  else if (isObject(additionalProps)) additionalPropertiesSchema = walkNode(additionalProps, ctx);
336
417
  const depSchemas = getObject(schema, "dependentSchemas");
337
- const walkedDepSchemas = depSchemas !== void 0 ? walkSubSchemaMap(depSchemas, walkNode, ctx) : void 0;
418
+ const walkedDepSchemas = depSchemas !== void 0 ? walkSubSchemaMap(depSchemas, walkSubSchema, ctx) : void 0;
338
419
  const depReq = getObject(schema, "dependentRequired");
339
420
  const walkedDepReq = depReq !== void 0 ? walkDependentRequiredMap(depReq) : void 0;
340
421
  let unevaluatedProperties;
@@ -348,8 +429,7 @@ function walkObject(schema, properties, ctx) {
348
429
  constraints: {}
349
430
  };
350
431
  else if (isObject(unevalProps)) unevaluatedProperties = walkNode(unevalProps, ctx);
351
- const propertyNamesSchema = getObject(schema, "propertyNames");
352
- const walkedPropertyNames = propertyNamesSchema !== void 0 ? walkNode(propertyNamesSchema, ctx) : void 0;
432
+ const walkedPropertyNames = "propertyNames" in schema ? walkSubSchema(schema.propertyNames, ctx) : void 0;
353
433
  return {
354
434
  ...buildBase(schema, ctx),
355
435
  type: "object",
@@ -384,6 +464,7 @@ function walkRecord(schema, valueSchema, ctx) {
384
464
  };
385
465
  }
386
466
  function walkArray(schema, ctx) {
467
+ const walkedContains = "contains" in schema ? walkSubSchema(schema.contains, ctx) : void 0;
387
468
  const prefixItems = getArray(schema, "prefixItems");
388
469
  if (prefixItems !== void 0) {
389
470
  const walkedItems = prefixItems.filter(isObject).map((item) => walkNode(item, ctx));
@@ -394,7 +475,8 @@ function walkArray(schema, ctx) {
394
475
  type: "tuple",
395
476
  constraints: extractArrayConstraints(schema),
396
477
  prefixItems: walkedItems,
397
- ...restItems !== void 0 ? { restItems } : {}
478
+ ...restItems !== void 0 ? { restItems } : {},
479
+ ...walkedContains !== void 0 ? { contains: walkedContains } : {}
398
480
  };
399
481
  }
400
482
  const unevaluatedItemsSchema = getObject(schema, "unevaluatedItems");
@@ -410,14 +492,16 @@ function walkArray(schema, ctx) {
410
492
  ...ctx,
411
493
  fieldOverrides: elementOverride
412
494
  }),
413
- ...walkedUnevaluatedItems !== void 0 ? { unevaluatedItems: walkedUnevaluatedItems } : {}
495
+ ...walkedUnevaluatedItems !== void 0 ? { unevaluatedItems: walkedUnevaluatedItems } : {},
496
+ ...walkedContains !== void 0 ? { contains: walkedContains } : {}
414
497
  };
415
498
  }
416
499
  return {
417
500
  ...buildBase(schema, ctx),
418
501
  type: "array",
419
502
  constraints: extractArrayConstraints(schema),
420
- ...walkedUnevaluatedItems !== void 0 ? { unevaluatedItems: walkedUnevaluatedItems } : {}
503
+ ...walkedUnevaluatedItems !== void 0 ? { unevaluatedItems: walkedUnevaluatedItems } : {},
504
+ ...walkedContains !== void 0 ? { contains: walkedContains } : {}
421
505
  };
422
506
  }
423
507
  function walkUnion(options, ctx) {
@@ -16,7 +16,7 @@
16
16
  * Machine-readable codes identifying each class of diagnostic.
17
17
  * Stable across releases — consumers can pattern-match on these.
18
18
  */
19
- type DiagnosticCode = "unresolved-ref" | "unknown-keyword" | "unknown-format" | "invalid-const" | "unsupported-type" | "dropped-swagger-feature" | "external-ref" | "type-negation-fallback" | "conditional-fallback" | "assumed-draft" | "depth-exceeded" | "allof-conflict" | "discriminator-inconsistent" | "divisible-by-conflict" | "legacy-dependencies-split" | "dependent-required-invalid" | "unknown-json-schema-dialect" | "relative-ref-resolved" | "bare-exclusive-bound" | "enum-value-filtered" | "required-non-string" | "pattern-invalid" | "duplicate-body-parameter" | "prototype-polluting-property";
19
+ type DiagnosticCode = "unresolved-ref" | "unknown-keyword" | "unknown-format" | "invalid-const" | "invalid-id-fragment" | "unsupported-type" | "dropped-swagger-feature" | "dynamic-ref-degraded" | "external-ref" | "type-negation-fallback" | "conditional-fallback" | "assumed-draft" | "depth-exceeded" | "allof-conflict" | "discriminator-inconsistent" | "divisible-by-conflict" | "keyword-out-of-draft" | "legacy-dependencies-split" | "dependent-required-invalid" | "unknown-json-schema-dialect" | "unknown-openapi-version" | "relative-ref-resolved" | "bare-exclusive-bound" | "enum-value-filtered" | "required-non-string" | "pattern-invalid" | "duplicate-body-parameter" | "prototype-polluting-property" | "cross-schema-relative-ref-unsupported" | "cyclic-path-item-ref" | "doc-not-object" | "path-item-ref-too-deep" | "swagger-cyclic-parameter-ref" | "swagger-invalid-file-parameter" | "swagger-missing-consumes" | "swagger-missing-host" | "type-mismatch" | "unknown-security-scheme-type" | "zod-codec-output-only";
20
20
  /**
21
21
  * A single diagnostic emitted during schema processing.
22
22
  */
@@ -14,11 +14,16 @@
14
14
  /**
15
15
  * Base class for all schema-components errors.
16
16
  * Catch this to handle any library error uniformly.
17
+ *
18
+ * Forwards the optional `cause` to the native ES2022 `Error` constructor so
19
+ * `error.cause` is wired up by the runtime and rendered correctly by
20
+ * `util.inspect` ("Caused by: ..."). Subclasses that need a typed `cause`
21
+ * field still get it via the platform's own `Error.cause` getter.
17
22
  */
18
23
  declare class SchemaError extends Error {
19
24
  /** The schema input that caused the error. */
20
25
  readonly schema: unknown;
21
- constructor(message: string, schema: unknown);
26
+ constructor(message: string, schema: unknown, cause?: unknown);
22
27
  }
23
28
  /**
24
29
  * The adapter failed to convert the input schema to JSON Schema.
@@ -27,19 +32,12 @@ declare class SchemaError extends Error {
27
32
  * JSON Schema, missing OpenAPI ref, unsupported ref format.
28
33
  */
29
34
  declare class SchemaNormalisationError extends SchemaError {
30
- readonly kind: "invalid-zod" | "zod3-unsupported" | "zod-transform-unsupported" | "zod-type-unrepresentable" | "zod-conversion-failed" | "invalid-json-schema" | "openapi-missing-ref" | "openapi-invalid" | "unknown";
35
+ readonly kind: "invalid-zod" | "unsupported-schema" | "zod3-unsupported" | "zod-transform-unsupported" | "zod-type-unrepresentable" | "zod-conversion-failed" | "zod-conversion-bug" | "zod-cycle-detected" | "zod-duplicate-id" | "invalid-json-schema" | "openapi-missing-ref" | "openapi-invalid" | "unknown";
31
36
  /**
32
37
  * For `zod-type-unrepresentable`, the offending Zod type name
33
38
  * (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
34
39
  */
35
40
  readonly zodType: string | undefined;
36
- /**
37
- * The original underlying error, when this normalisation error wraps
38
- * another exception (typically the error thrown by `z.toJSONSchema()`).
39
- * Preserves the source stack trace so the root cause is not lost when
40
- * the classifier translates the message.
41
- */
42
- readonly cause: unknown;
43
41
  constructor(message: string, schema: unknown, kind: SchemaNormalisationError["kind"], zodType?: string, cause?: unknown);
44
42
  }
45
43
  /**
@@ -50,8 +48,6 @@ declare class SchemaNormalisationError extends SchemaError {
50
48
  declare class SchemaRenderError extends SchemaError {
51
49
  /** The schema type being rendered when the error occurred. */
52
50
  readonly schemaType: string;
53
- /** The original error from the render function. */
54
- readonly cause: unknown;
55
51
  constructor(message: string, schema: unknown, schemaType: string, cause: unknown);
56
52
  }
57
53
  /**
@@ -1,5 +1,5 @@
1
- import { M as WalkedField } from "../types-C9zw9wbX.mjs";
2
- import { t as AllConstraints } from "../renderer-SOIbJBtk.mjs";
1
+ import { j as WalkedField } from "../types-BrRMV0en.mjs";
2
+ import { t as AllConstraints } from "../renderer-BaRlQIuN.mjs";
3
3
  import { HtmlAttributes, HtmlNode } from "./html.mjs";
4
4
 
5
5
  //#region src/html/a11y.d.ts
@@ -18,11 +18,18 @@ function joinPath(parent, suffix) {
18
18
  }
19
19
  /**
20
20
  * Normalise a path into the id segment used after the `sc-` prefix.
21
- * Dots (object nesting) and brackets (array indices) become hyphens so
22
- * the id remains a valid CSS selector and matches test query semantics.
21
+ *
22
+ * Whitelist-based: every character outside `[A-Za-z0-9_-]` collapses to
23
+ * a single hyphen. A blacklist that only stripped `.[]` would still leak
24
+ * spaces, slashes, and other punctuation through into ids when a path
25
+ * is sourced from a free-text field like `meta.description` — producing
26
+ * structurally invalid CSS selectors and `aria-labelledby` references.
27
+ *
28
+ * Trailing hyphens are trimmed so e.g. `tags[0]` → `tags-0` (not
29
+ * `tags-0-`).
23
30
  */
24
31
  function normaliseIdSegment(value) {
25
- return value.replace(/[.[\]]+/g, "-").replace(/-+$/g, "");
32
+ return value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-+$/g, "");
26
33
  }
27
34
  /**
28
35
  * Build the input ID for a field at a given path.
@@ -1,7 +1,14 @@
1
- import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
- import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
1
+ import { w as SchemaMeta } from "../types-BrRMV0en.mjs";
2
+ import { o as HtmlResolver } from "../renderer-BaRlQIuN.mjs";
3
3
 
4
4
  //#region src/html/renderToHtml.d.ts
5
+ /**
6
+ * Build the recursion-cap sentinel element. The label is interpolated
7
+ * via `h()` + `serialize` so any HTML in `meta.description` (which is
8
+ * schema-author content but can equally be sourced from user-supplied
9
+ * JSON Schema input) is escaped — never interpolated into raw markup.
10
+ */
11
+ declare function recursionSentinelHtml(label: string): string;
5
12
  interface RenderToHtmlOptions {
6
13
  /** The data value to render. */
7
14
  value?: unknown;
@@ -29,4 +36,4 @@ interface RenderToHtmlOptions {
29
36
  */
30
37
  declare function renderToHtml(schema: unknown, options?: RenderToHtmlOptions): string;
31
38
  //#endregion
32
- export { RenderToHtmlOptions, renderToHtml };
39
+ export { RenderToHtmlOptions, recursionSentinelHtml, renderToHtml };
@@ -1,6 +1,8 @@
1
+ import "../core/limits.mjs";
1
2
  import { normaliseSchema } from "../core/adapter.mjs";
2
3
  import { getHtmlRenderFn, mergeHtmlResolvers } from "../core/renderer.mjs";
3
4
  import { walk } from "../core/walker.mjs";
5
+ import { h, serialize } from "./html.mjs";
4
6
  import { joinPath } from "./a11y.mjs";
5
7
  import { defaultHtmlResolver } from "./renderers.mjs";
6
8
  //#region src/html/renderToHtml.ts
@@ -25,6 +27,15 @@ import { defaultHtmlResolver } from "./renderers.mjs";
25
27
  * });
26
28
  */
27
29
  /**
30
+ * Build the recursion-cap sentinel element. The label is interpolated
31
+ * via `h()` + `serialize` so any HTML in `meta.description` (which is
32
+ * schema-author content but can equally be sourced from user-supplied
33
+ * JSON Schema input) is escaped — never interpolated into raw markup.
34
+ */
35
+ function recursionSentinelHtml(label) {
36
+ return serialize(h("fieldset", { class: "sc-recursive" }, h("em", {}, `↻ ${label} (recursive)`)));
37
+ }
38
+ /**
28
39
  * Render a schema to an HTML string.
29
40
  *
30
41
  * @param schema - Zod schema, JSON Schema, or OpenAPI document
@@ -44,9 +55,8 @@ function renderToHtml(schema, options = {}) {
44
55
  rootDocument
45
56
  });
46
57
  const resolver = options.resolver ?? defaultHtmlResolver;
47
- const MAX_HTML_DEPTH = 10;
48
58
  const makeRenderChild = (currentDepth, parentPath) => (childTree, childValue, pathSuffix) => {
49
- if (currentDepth >= MAX_HTML_DEPTH) return `<fieldset class="sc-recursive"><em>\u21bb ${typeof childTree.meta.description === "string" ? childTree.meta.description : "schema"} (recursive)</em></fieldset>`;
59
+ if (currentDepth >= 10) return recursionSentinelHtml(typeof childTree.meta.description === "string" ? childTree.meta.description : "schema");
50
60
  const childPath = joinPath(parentPath, pathSuffix);
51
61
  return renderFieldHtml(childTree, childValue, resolver, childPath, makeRenderChild(currentDepth + 1, childPath));
52
62
  };
@@ -73,4 +83,4 @@ function renderFieldHtml(tree, value, resolver, path, renderChild) {
73
83
  return renderFn(props);
74
84
  }
75
85
  //#endregion
76
- export { renderToHtml };
86
+ export { recursionSentinelHtml, renderToHtml };
@@ -1,5 +1,5 @@
1
- import { T as SchemaMeta } from "../types-C9zw9wbX.mjs";
2
- import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
1
+ import { w as SchemaMeta } from "../types-BrRMV0en.mjs";
2
+ import { o as HtmlResolver } from "../renderer-BaRlQIuN.mjs";
3
3
 
4
4
  //#region src/html/renderToHtmlStream.d.ts
5
5
  interface StreamRenderOptions {
@@ -1,5 +1,5 @@
1
- import { M as WalkedField } from "../types-C9zw9wbX.mjs";
2
- import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
1
+ import { j as WalkedField } from "../types-BrRMV0en.mjs";
2
+ import { o as HtmlResolver } from "../renderer-BaRlQIuN.mjs";
3
3
 
4
4
  //#region src/html/renderers.d.ts
5
5
  declare function dateInputType(format: string | undefined): string | undefined;
@@ -1,5 +1,5 @@
1
- import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
2
1
  import { isSafeHyperlink, isSafeMailtoAddress } from "../core/uri.mjs";
2
+ import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
3
3
  import { h, raw, serialize } from "./html.mjs";
4
4
  import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
5
5
  //#region src/html/renderers.ts
@@ -334,10 +334,6 @@ function renderFileHtml(props) {
334
334
  if (typeof props.meta.description === "string") Object.assign(attrs, ariaLabelAttrs(props.meta.description));
335
335
  return serialize(h("input", attrs));
336
336
  }
337
- function renderRecursiveHtml(props) {
338
- const refTarget = props.tree.type === "recursive" ? props.tree.refTarget : "";
339
- return serialize(h("fieldset", { class: "sc-recursive" }, `↻ ${typeof props.meta.description === "string" ? props.meta.description : refTarget} (recursive)`));
340
- }
341
337
  function renderUnknownHtml(props) {
342
338
  if (props.readOnly) {
343
339
  if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
@@ -433,7 +429,6 @@ const defaultHtmlResolver = {
433
429
  discriminatedUnion: renderDiscriminatedUnionHtml,
434
430
  conditional: renderConditionalHtml,
435
431
  negation: renderNegationHtml,
436
- recursive: renderRecursiveHtml,
437
432
  file: renderFileHtml,
438
433
  never: renderNeverHtml,
439
434
  unknown: renderUnknownHtml
@@ -1,13 +1,14 @@
1
- import { M as WalkedField } from "../types-C9zw9wbX.mjs";
2
- import { o as HtmlResolver } from "../renderer-SOIbJBtk.mjs";
1
+ import { j as WalkedField } from "../types-BrRMV0en.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
3
+ import { o as HtmlResolver } from "../renderer-BaRlQIuN.mjs";
3
4
  import { HtmlElement } from "./html.mjs";
4
5
 
5
6
  //#region src/html/streamRenderers.d.ts
6
7
  declare function yieldOpen(el: HtmlElement): string;
7
8
  declare function yieldClose(el: HtmlElement): string;
8
9
  declare function renderLeaf(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string): string;
9
- declare function renderFieldSync(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver): string;
10
+ declare function renderFieldSync(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver, currentDepth: number, diagnostics: DiagnosticsOptions | undefined): string;
10
11
  declare function matchUnionOption(options: WalkedField[], value: unknown): WalkedField | undefined;
11
- declare function streamField(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver): Iterable<string, void, undefined>;
12
+ declare function streamField(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver, currentDepth?: number, diagnostics?: DiagnosticsOptions): Iterable<string, void, undefined>;
12
13
  //#endregion
13
14
  export { matchUnionOption, renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };