schema-components 1.17.0 → 1.18.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 (48) hide show
  1. package/dist/core/adapter.d.mts +1 -1
  2. package/dist/core/constraints.d.mts +1 -1
  3. package/dist/core/diagnostics.d.mts +1 -1
  4. package/dist/core/merge.d.mts +14 -8
  5. package/dist/core/merge.mjs +81 -12
  6. package/dist/core/normalise.d.mts +1 -1
  7. package/dist/core/ref.d.mts +1 -1
  8. package/dist/core/renderer.d.mts +2 -2
  9. package/dist/core/renderer.mjs +50 -1
  10. package/dist/core/swagger2.d.mts +1 -1
  11. package/dist/core/typeInference.d.mts +2 -2
  12. package/dist/core/walkBuilders.d.mts +2 -2
  13. package/dist/core/walker.mjs +2 -2
  14. package/dist/{diagnostics-DzbZmcLI.d.mts → diagnostics-BYk63jsC.d.mts} +1 -1
  15. package/dist/html/a11y.d.mts +13 -2
  16. package/dist/html/a11y.mjs +26 -2
  17. package/dist/html/renderToHtml.d.mts +1 -1
  18. package/dist/html/renderToHtml.mjs +5 -3
  19. package/dist/html/renderToHtmlStream.d.mts +1 -1
  20. package/dist/html/renderers.d.mts +4 -3
  21. package/dist/html/renderers.mjs +9 -13
  22. package/dist/html/streamRenderers.d.mts +1 -1
  23. package/dist/openapi/bundle.d.mts +9 -4
  24. package/dist/openapi/bundle.mjs +73 -15
  25. package/dist/openapi/components.d.mts +1 -1
  26. package/dist/openapi/components.mjs +22 -17
  27. package/dist/openapi/parser.mjs +8 -8
  28. package/dist/openapi/resolve.d.mts +13 -2
  29. package/dist/openapi/resolve.mjs +19 -3
  30. package/dist/react/SchemaComponent.d.mts +4 -4
  31. package/dist/react/SchemaComponent.mjs +2 -31
  32. package/dist/react/SchemaView.d.mts +4 -3
  33. package/dist/react/SchemaView.mjs +13 -44
  34. package/dist/react/headless.d.mts +1 -1
  35. package/dist/react/headlessRenderers.d.mts +1 -1
  36. package/dist/react/headlessRenderers.mjs +3 -2
  37. package/dist/{ref-DvWoULcy.d.mts → ref-Ckt5liZs.d.mts} +1 -1
  38. package/dist/{renderer-B3s8o2B8.d.mts → renderer-DXo-rXHJ.d.mts} +18 -1
  39. package/dist/themes/mantine.d.mts +1 -1
  40. package/dist/themes/mantine.mjs +4 -3
  41. package/dist/themes/mui.d.mts +1 -1
  42. package/dist/themes/mui.mjs +6 -5
  43. package/dist/themes/radix.d.mts +1 -1
  44. package/dist/themes/radix.mjs +3 -3
  45. package/dist/themes/shadcn.d.mts +1 -1
  46. package/dist/themes/shadcn.mjs +6 -5
  47. package/dist/{typeInference-k7FXfTVO.d.mts → typeInference-5JiqIZ8t.d.mts} +57 -4
  48. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { T as SchemaMeta, m as JsonObject } from "../types-D_5ST7SS.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
3
3
 
4
4
  //#region src/core/adapter.d.ts
5
5
  type SchemaInput = Record<string, unknown>;
@@ -1,5 +1,5 @@
1
1
  import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-D_5ST7SS.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
3
3
 
4
4
  //#region src/core/constraints.d.ts
5
5
  declare function extractStringConstraints(schema: Record<string, unknown>, diagnostics?: DiagnosticsOptions, pointer?: string): StringConstraints;
@@ -1,2 +1,2 @@
1
- import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-DzbZmcLI.mjs";
1
+ import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-BYk63jsC.mjs";
2
2
  export { Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticsOptions, appendPointer, emitDiagnostic };
@@ -1,10 +1,6 @@
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
2
+
1
3
  //#region src/core/merge.d.ts
2
- /**
3
- * Schema merging, nullable detection, and discriminated union detection.
4
- *
5
- * Used by the walker to handle `allOf`, `anyOf [T, null]`, and
6
- * `oneOf` with discriminator properties.
7
- */
8
4
  /**
9
5
  * Annotation keywords that can appear as siblings of `$ref` per
10
6
  * Draft 2020-12 / OpenAPI 3.1. Structural keywords (type, properties,
@@ -24,8 +20,13 @@ declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedM
24
20
  /**
25
21
  * Merge multiple JSON Schema objects from allOf into one.
26
22
  * Merges: properties, required, meta fields, and constraints.
23
+ *
24
+ * Semantics are first-write-wins for meta and constraint keywords.
25
+ * When a later branch redefines a keyword with a non-equal value the
26
+ * later value is silently dropped — an `allof-conflict` diagnostic is
27
+ * emitted so the loss is visible to consumers.
27
28
  */
28
- declare function mergeAllOf(schemas: unknown[]): Record<string, unknown>;
29
+ declare function mergeAllOf(schemas: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Record<string, unknown>;
29
30
  interface NormalisedAnyOf {
30
31
  inner: Record<string, unknown>;
31
32
  isNullable: boolean;
@@ -42,7 +43,12 @@ interface Discriminated {
42
43
  /**
43
44
  * Detect oneOf where every option is an object with a property
44
45
  * that has a `const` value → discriminated union.
46
+ *
47
+ * When options carry inconsistent discriminator candidates (e.g. one
48
+ * uses `kind` while another uses `type`) detection fails and a
49
+ * `discriminator-inconsistent` diagnostic is emitted so callers can
50
+ * see why the union falls back to a generic oneOf.
45
51
  */
46
- declare function detectDiscriminated(options: unknown[]): Discriminated | undefined;
52
+ declare function detectDiscriminated(options: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Discriminated | undefined;
47
53
  //#endregion
48
54
  export { ANNOTATION_SIBLINGS, Discriminated, NormalisedAnyOf, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf };
@@ -1,4 +1,5 @@
1
1
  import { isObject } from "./guards.mjs";
2
+ import { emitDiagnostic } from "./diagnostics.mjs";
2
3
  //#region src/core/merge.ts
3
4
  /**
4
5
  * Schema merging, nullable detection, and discriminated union detection.
@@ -19,6 +20,31 @@ function getObject(obj, key) {
19
20
  return isObject(value) ? value : void 0;
20
21
  }
21
22
  /**
23
+ * Structural equality for arbitrary JSON-like values. Used to decide
24
+ * whether a duplicated keyword across `allOf` branches genuinely
25
+ * conflicts (different values) or is benign (identical values).
26
+ */
27
+ function deepEqual(a, b) {
28
+ if (a === b) return true;
29
+ if (typeof a !== typeof b) return false;
30
+ if (Array.isArray(a)) {
31
+ if (!Array.isArray(b) || a.length !== b.length) return false;
32
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
33
+ return true;
34
+ }
35
+ if (isObject(a) && isObject(b)) {
36
+ const keysA = Object.keys(a);
37
+ const keysB = Object.keys(b);
38
+ if (keysA.length !== keysB.length) return false;
39
+ for (const key of keysA) {
40
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
41
+ if (!deepEqual(a[key], b[key])) return false;
42
+ }
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ /**
22
48
  * Annotation keywords that can appear as siblings of `$ref` per
23
49
  * Draft 2020-12 / OpenAPI 3.1. Structural keywords (type, properties,
24
50
  * etc.) are NOT annotation siblings and should not be merged.
@@ -50,8 +76,13 @@ function mergeRefSiblings(referencer, resolvedMeta) {
50
76
  /**
51
77
  * Merge multiple JSON Schema objects from allOf into one.
52
78
  * Merges: properties, required, meta fields, and constraints.
79
+ *
80
+ * Semantics are first-write-wins for meta and constraint keywords.
81
+ * When a later branch redefines a keyword with a non-equal value the
82
+ * later value is silently dropped — an `allof-conflict` diagnostic is
83
+ * emitted so the loss is visible to consumers.
53
84
  */
54
- function mergeAllOf(schemas) {
85
+ function mergeAllOf(schemas, diagnostics, pointer = "") {
55
86
  const merged = {};
56
87
  const properties = {};
57
88
  const required = [];
@@ -66,10 +97,30 @@ function mergeAllOf(schemas) {
66
97
  for (const [key, value] of Object.entries(entry)) {
67
98
  if (key === "properties" || key === "required" || key === "allOf" || key === "type") continue;
68
99
  if (!(key in merged)) merged[key] = value;
100
+ else if (!deepEqual(merged[key], value)) emitDiagnostic(diagnostics, {
101
+ code: "allof-conflict",
102
+ message: `allOf branches define conflicting values for "${key}"; keeping the first occurrence and discarding subsequent values`,
103
+ pointer,
104
+ detail: {
105
+ key,
106
+ kept: merged[key],
107
+ discarded: value
108
+ }
109
+ });
69
110
  }
70
- if (!("type" in merged)) {
71
- const type = getString(entry, "type");
72
- if (type !== void 0) merged.type = type;
111
+ const entryType = getString(entry, "type");
112
+ if (entryType !== void 0) {
113
+ if (!("type" in merged)) merged.type = entryType;
114
+ else if (!deepEqual(merged.type, entryType)) emitDiagnostic(diagnostics, {
115
+ code: "allof-conflict",
116
+ message: `allOf branches define conflicting values for "type"; keeping the first occurrence and discarding subsequent values`,
117
+ pointer,
118
+ detail: {
119
+ key: "type",
120
+ kept: merged.type,
121
+ discarded: entryType
122
+ }
123
+ });
73
124
  }
74
125
  }
75
126
  if (Object.keys(properties).length > 0) merged.properties = properties;
@@ -98,22 +149,40 @@ function normaliseAnyOf(options) {
98
149
  /**
99
150
  * Detect oneOf where every option is an object with a property
100
151
  * that has a `const` value → discriminated union.
152
+ *
153
+ * When options carry inconsistent discriminator candidates (e.g. one
154
+ * uses `kind` while another uses `type`) detection fails and a
155
+ * `discriminator-inconsistent` diagnostic is emitted so callers can
156
+ * see why the union falls back to a generic oneOf.
101
157
  */
102
- function detectDiscriminated(options) {
158
+ function detectDiscriminated(options, diagnostics, pointer = "") {
103
159
  if (options.length === 0) return void 0;
104
160
  let discriminator;
161
+ const perOptionKeys = [];
105
162
  for (const opt of options) {
106
163
  if (!isObject(opt)) return void 0;
107
164
  const props = getObject(opt, "properties");
108
165
  if (props === void 0) return void 0;
109
- let foundKey;
110
- for (const [key, value] of Object.entries(props)) if (isObject(value) && "const" in value) {
111
- foundKey = key;
112
- break;
166
+ const constKeys = [];
167
+ for (const [key, value] of Object.entries(props)) if (isObject(value) && "const" in value) constKeys.push(key);
168
+ if (constKeys.length === 0) {
169
+ perOptionKeys.push(void 0);
170
+ continue;
113
171
  }
114
- if (foundKey === void 0) return void 0;
115
- if (discriminator === void 0) discriminator = foundKey;
116
- else if (discriminator !== foundKey) return;
172
+ const foundKey = constKeys[0];
173
+ perOptionKeys.push(foundKey);
174
+ discriminator ??= foundKey;
175
+ }
176
+ if (perOptionKeys.some((k) => k === void 0)) return void 0;
177
+ const uniqueKeys = new Set(perOptionKeys);
178
+ if (uniqueKeys.size > 1) {
179
+ emitDiagnostic(diagnostics, {
180
+ code: "discriminator-inconsistent",
181
+ message: `oneOf options use inconsistent discriminator keys (${[...uniqueKeys].map((k) => `"${k ?? ""}"`).join(", ")}); rendering as a generic union`,
182
+ pointer,
183
+ detail: { candidates: perOptionKeys }
184
+ });
185
+ return;
117
186
  }
118
187
  if (discriminator === void 0) return void 0;
119
188
  return {
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
2
2
  import { n as JsonSchemaDraft, r as OpenApiVersionInfo } from "../version-B5NV-35j.mjs";
3
3
 
4
4
  //#region src/core/normalise.d.ts
@@ -1,2 +1,2 @@
1
- import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-DvWoULcy.mjs";
1
+ import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-Ckt5liZs.mjs";
2
2
  export { ExternalResolver, RefOptions, countDistinctRefs, dereference, findAnchor, resolveRef };
@@ -1,2 +1,2 @@
1
- import { a as HtmlRenderProps, c as RenderFunction, d as getRenderFunction, f as mergeHtmlResolvers, i as HtmlRenderFunction, l as RenderProps, m as typeToKey, n as BaseFieldProps, o as HtmlResolver, p as mergeResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as getHtmlRenderFn } from "../renderer-B3s8o2B8.mjs";
2
- export { AllConstraints, BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
1
+ import { a as HtmlRenderProps, c as RenderFunction, d as getHtmlRenderFn, f as getRenderFunction, h as typeToKey, i as HtmlRenderFunction, l as RenderProps, m as mergeResolvers, n as BaseFieldProps, o as HtmlResolver, p as mergeHtmlResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as buildRenderProps } from "../renderer-DXo-rXHJ.mjs";
2
+ export { AllConstraints, BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, buildRenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
@@ -1,4 +1,53 @@
1
1
  //#region src/core/renderer.ts
2
+ /** No-op onChange used when callers render in read-only mode. */
3
+ function noopOnChange() {}
4
+ /**
5
+ * Build the `RenderProps` object handed to a resolver render function or a
6
+ * widget. Used by both the server-side `<SchemaView>` (which has no
7
+ * `onChange`) and the client-side `<SchemaComponent>` (which threads an
8
+ * `onChange` callback).
9
+ *
10
+ * When `onChange` is `undefined` the caller is rendering in read-only mode:
11
+ * a noop `onChange` is wired up, `readOnly` is forced to `true`, and
12
+ * `writeOnly` is forced to `false`. Otherwise the editability is taken
13
+ * from `tree.editability`.
14
+ *
15
+ * The duplicate sibling fields (`enumValues`, `element`, `fields`, etc.)
16
+ * are populated for backwards compatibility with renderers that have not
17
+ * yet migrated to reading from `tree` directly. New renderers should
18
+ * narrow on `tree.type` and read from `tree`.
19
+ */
20
+ function buildRenderProps(tree, value, onChange, renderChild, path) {
21
+ const isReadOnly = onChange === void 0 || tree.editability === "presentation";
22
+ const isWriteOnly = onChange !== void 0 && tree.editability === "input";
23
+ const props = {
24
+ value,
25
+ onChange: onChange ?? noopOnChange,
26
+ readOnly: isReadOnly,
27
+ writeOnly: isWriteOnly,
28
+ meta: tree.meta,
29
+ constraints: tree.constraints,
30
+ path,
31
+ tree,
32
+ renderChild
33
+ };
34
+ if (tree.type === "enum") props.enumValues = tree.enumValues;
35
+ if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
36
+ if (tree.type === "object") props.fields = tree.fields;
37
+ if (tree.type === "union" || tree.type === "discriminatedUnion") props.options = tree.options;
38
+ if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
39
+ if (tree.type === "record") props.keyType = tree.keyType;
40
+ if (tree.type === "record") props.valueType = tree.valueType;
41
+ if (tree.type === "tuple") props.prefixItems = tree.prefixItems;
42
+ if (tree.type === "conditional") props.ifClause = tree.ifClause;
43
+ if (tree.type === "conditional" && tree.thenClause !== void 0) props.thenClause = tree.thenClause;
44
+ if (tree.type === "conditional" && tree.elseClause !== void 0) props.elseClause = tree.elseClause;
45
+ if (tree.type === "negation") props.negated = tree.negated;
46
+ if (tree.type === "recursive") props.refTarget = tree.refTarget;
47
+ if (tree.type === "literal") props.literalValues = tree.literalValues;
48
+ if (tree.examples !== void 0) props.examples = tree.examples;
49
+ return props;
50
+ }
2
51
  const RESOLVER_KEYS = [
3
52
  "string",
4
53
  "number",
@@ -77,4 +126,4 @@ function mergeHtmlResolvers(user, fallback) {
77
126
  return merged;
78
127
  }
79
128
  //#endregion
80
- export { RESOLVER_KEYS, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
129
+ export { RESOLVER_KEYS, buildRenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
2
2
  import { NodeTransform } from "./normalise.mjs";
3
3
 
4
4
  //#region src/core/swagger2.d.ts
@@ -1,2 +1,2 @@
1
- import { a as OpenAPIRequestBodyType, c as ResolveOpenAPIRef, d as __SchemaInferenceFellBack, i as InferResponseFields, l as TypeAtPath, n as InferParameterOverrides, o as OpenAPIResponseType, r as InferRequestBodyFields, s as PathOfType, t as FromJSONSchema, u as UnsafeFields } from "../typeInference-k7FXfTVO.mjs";
2
- export { FromJSONSchema, InferParameterOverrides, InferRequestBodyFields, InferResponseFields, OpenAPIRequestBodyType, OpenAPIResponseType, PathOfType, ResolveOpenAPIRef, TypeAtPath, UnsafeFields, __SchemaInferenceFellBack };
1
+ import { a as InferResponseFields, c as PathOfType, d as UnsafeFields, f as __SchemaInferenceFellBack, i as InferRequestBodyFields, l as ResolveOpenAPIRef, n as FromJSONSchema, o as OpenAPIRequestBodyType, r as InferParameterOverrides, s as OpenAPIResponseType, t as DEFAULT_MAX_DEPTH, u as TypeAtPath } from "../typeInference-5JiqIZ8t.mjs";
2
+ export { DEFAULT_MAX_DEPTH, FromJSONSchema, InferParameterOverrides, InferRequestBodyFields, InferResponseFields, OpenAPIRequestBodyType, OpenAPIResponseType, PathOfType, ResolveOpenAPIRef, TypeAtPath, UnsafeFields, __SchemaInferenceFellBack };
@@ -1,6 +1,6 @@
1
1
  import { M as WalkedField, O as StringField, T as SchemaMeta, b as NumberField, c as FieldBase, j as UnknownField, o as Editability, p as FileField, r as BooleanField, v as NullField } from "../types-D_5ST7SS.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
3
- import { t as ExternalResolver } from "../ref-DvWoULcy.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
3
+ import { t as ExternalResolver } from "../ref-Ckt5liZs.mjs";
4
4
 
5
5
  //#region src/core/walkBuilders.d.ts
6
6
  declare function getString(obj: Record<string, unknown>, key: string): string | undefined;
@@ -74,7 +74,7 @@ function walk(schema, options = {}) {
74
74
  }
75
75
  function walkNode(schema, ctx) {
76
76
  const allOf = getArray(schema, "allOf");
77
- if (allOf !== void 0 && allOf.length > 0) return walkNode(mergeAllOf(allOf), ctx);
77
+ if (allOf !== void 0 && allOf.length > 0) return walkNode(mergeAllOf(allOf, ctx.diagnostics, ctx.pointer), ctx);
78
78
  const anyOf = getArray(schema, "anyOf");
79
79
  if (anyOf !== void 0) {
80
80
  const nullable = normaliseAnyOf(anyOf);
@@ -86,7 +86,7 @@ function walkNode(schema, ctx) {
86
86
  }
87
87
  const oneOf = getArray(schema, "oneOf");
88
88
  if (oneOf !== void 0) {
89
- const discriminated = detectDiscriminated(oneOf);
89
+ const discriminated = detectDiscriminated(oneOf, ctx.diagnostics, ctx.pointer);
90
90
  if (discriminated !== void 0) return walkDiscriminatedUnion(discriminated, ctx);
91
91
  return walkUnion(oneOf, ctx);
92
92
  }
@@ -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";
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";
20
20
  /**
21
21
  * A single diagnostic emitted during schema processing.
22
22
  */
@@ -1,8 +1,19 @@
1
1
  import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
- import { t as AllConstraints } from "../renderer-B3s8o2B8.mjs";
2
+ import { t as AllConstraints } from "../renderer-DXo-rXHJ.mjs";
3
3
  import { HtmlAttributes, HtmlNode } from "./html.mjs";
4
4
 
5
5
  //#region src/html/a11y.d.ts
6
+ /**
7
+ * Append a structural suffix to a parent path. Mirrors the canonical
8
+ * `joinPath` used by the React renderers: when the suffix is omitted
9
+ * (e.g. transparent wrappers like union options) the parent path is
10
+ * returned unchanged so the child inherits the parent's id.
11
+ *
12
+ * Suffixes are dot-joined except for bracketed array indices like `[0]`
13
+ * which append directly so `tags` + `[0]` becomes `tags[0]` rather than
14
+ * `tags.[0]`.
15
+ */
16
+ declare function joinPath(parent: string, suffix: string | undefined): string;
6
17
  /**
7
18
  * Build the input ID for a field at a given path.
8
19
  */
@@ -45,4 +56,4 @@ declare function buildHintElement(inputId: string, constraints: AllConstraints):
45
56
  */
46
57
  declare function requiredIndicator(field: WalkedField): HtmlNode;
47
58
  //#endregion
48
- export { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildHintId, buildInputId, constraintHint, requiredIndicator };
59
+ export { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildHintId, buildInputId, constraintHint, joinPath, requiredIndicator };
@@ -1,10 +1,34 @@
1
1
  import { h } from "./html.mjs";
2
2
  //#region src/html/a11y.ts
3
3
  /**
4
+ * Append a structural suffix to a parent path. Mirrors the canonical
5
+ * `joinPath` used by the React renderers: when the suffix is omitted
6
+ * (e.g. transparent wrappers like union options) the parent path is
7
+ * returned unchanged so the child inherits the parent's id.
8
+ *
9
+ * Suffixes are dot-joined except for bracketed array indices like `[0]`
10
+ * which append directly so `tags` + `[0]` becomes `tags[0]` rather than
11
+ * `tags.[0]`.
12
+ */
13
+ function joinPath(parent, suffix) {
14
+ if (suffix === void 0 || suffix.length === 0) return parent;
15
+ if (parent.length === 0) return suffix;
16
+ if (suffix.startsWith("[")) return `${parent}${suffix}`;
17
+ return `${parent}.${suffix}`;
18
+ }
19
+ /**
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.
23
+ */
24
+ function normaliseIdSegment(value) {
25
+ return value.replace(/[.[\]]+/g, "-").replace(/-+$/g, "");
26
+ }
27
+ /**
4
28
  * Build the input ID for a field at a given path.
5
29
  */
6
30
  function buildInputId(path, key) {
7
- return `sc-${path ? `${path}-${key}` : key}`;
31
+ return `sc-${normaliseIdSegment(joinPath(path, key))}`;
8
32
  }
9
33
  /**
10
34
  * Derive the hint element ID from the input ID.
@@ -78,4 +102,4 @@ function requiredIndicator(field) {
78
102
  }, " *");
79
103
  }
80
104
  //#endregion
81
- export { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildHintId, buildInputId, constraintHint, requiredIndicator };
105
+ export { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildHintId, buildInputId, constraintHint, joinPath, requiredIndicator };
@@ -1,5 +1,5 @@
1
1
  import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-B3s8o2B8.mjs";
2
+ import { o as HtmlResolver } from "../renderer-DXo-rXHJ.mjs";
3
3
 
4
4
  //#region src/html/renderToHtml.d.ts
5
5
  interface RenderToHtmlOptions {
@@ -2,6 +2,7 @@ import { normaliseSchema } from "../core/adapter.mjs";
2
2
  import { getHtmlRenderFn, mergeHtmlResolvers } from "../core/renderer.mjs";
3
3
  import { walk } from "../core/walker.mjs";
4
4
  import { h, serialize } from "./html.mjs";
5
+ import { joinPath } from "./a11y.mjs";
5
6
  import { defaultHtmlResolver } from "./renderers.mjs";
6
7
  //#region src/html/renderToHtml.ts
7
8
  /**
@@ -45,11 +46,12 @@ function renderToHtml(schema, options = {}) {
45
46
  });
46
47
  const resolver = options.resolver ?? defaultHtmlResolver;
47
48
  const MAX_HTML_DEPTH = 10;
48
- const makeRenderChild = (currentDepth) => (childTree, childValue, pathSuffix) => {
49
+ const makeRenderChild = (currentDepth, parentPath) => (childTree, childValue, pathSuffix) => {
49
50
  if (currentDepth >= MAX_HTML_DEPTH) return `<fieldset class="sc-recursive"><em>\u21bb ${typeof childTree.meta.description === "string" ? childTree.meta.description : "schema"} (recursive)</em></fieldset>`;
50
- return renderFieldHtml(childTree, childValue, resolver, pathSuffix ?? childTree.meta.description ?? "", makeRenderChild(currentDepth + 1));
51
+ const childPath = joinPath(parentPath, pathSuffix);
52
+ return renderFieldHtml(childTree, childValue, resolver, childPath, makeRenderChild(currentDepth + 1, childPath));
51
53
  };
52
- const renderChild = makeRenderChild(0);
54
+ const renderChild = makeRenderChild(0, "");
53
55
  return renderFieldHtml(tree, options.value ?? tree.defaultValue, resolver, "", renderChild);
54
56
  }
55
57
  function renderFieldHtml(tree, value, resolver, path, renderChild) {
@@ -1,5 +1,5 @@
1
1
  import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-B3s8o2B8.mjs";
2
+ import { o as HtmlResolver } from "../renderer-DXo-rXHJ.mjs";
3
3
 
4
4
  //#region src/html/renderToHtmlStream.d.ts
5
5
  interface StreamRenderOptions {
@@ -1,11 +1,12 @@
1
1
  import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-B3s8o2B8.mjs";
2
+ import { o as HtmlResolver } from "../renderer-DXo-rXHJ.mjs";
3
3
 
4
4
  //#region src/html/renderers.d.ts
5
5
  declare function dateInputType(format: string | undefined): string | undefined;
6
6
  /**
7
- * Normalise a dot-separated path into a valid, `sc-` prefixed HTML ID.
8
- * Dots in paths (from nested objects) become hyphens.
7
+ * Normalise a structural path into a valid, `sc-` prefixed HTML ID.
8
+ * Dots (object nesting) and brackets (array indices) become hyphens so
9
+ * the id remains a valid CSS selector and predictable in test queries.
9
10
  */
10
11
  declare function fieldId(path: string): string;
11
12
  declare function matchUnionOption(options: WalkedField[], value: unknown): WalkedField | undefined;
@@ -7,11 +7,12 @@ function dateInputType(format) {
7
7
  if (format === "date-time" || format === "datetime") return "datetime-local";
8
8
  }
9
9
  /**
10
- * Normalise a dot-separated path into a valid, `sc-` prefixed HTML ID.
11
- * Dots in paths (from nested objects) become hyphens.
10
+ * Normalise a structural path into a valid, `sc-` prefixed HTML ID.
11
+ * Dots (object nesting) and brackets (array indices) become hyphens so
12
+ * the id remains a valid CSS selector and predictable in test queries.
12
13
  */
13
14
  function fieldId(path) {
14
- return `sc-${path.replace(/\./g, "-")}`;
15
+ return `sc-${path.replace(/[.[\]]+/g, "-").replace(/-+$/g, "")}`;
15
16
  }
16
17
  function renderStringHtml(props) {
17
18
  if (props.readOnly) return serialize(renderStringReadOnly(props));
@@ -167,7 +168,7 @@ function renderObjectNode(props) {
167
168
  for (const [key, field] of sortedEntries) {
168
169
  const label = typeof field.meta.description === "string" ? field.meta.description : key;
169
170
  const childValue = obj[key];
170
- const childHtml = props.renderChild(field, childValue, props.path ? `${props.path}.${key}` : key);
171
+ const childHtml = props.renderChild(field, childValue, key);
171
172
  children.push(h("dt", { class: "sc-label" }, label));
172
173
  children.push(h("dd", { class: "sc-value" }, raw(childHtml)));
173
174
  }
@@ -180,9 +181,8 @@ function renderObjectNode(props) {
180
181
  for (const [key, field] of sortedEntries) {
181
182
  const label = typeof field.meta.description === "string" ? field.meta.description : key;
182
183
  const fieldId = buildInputId(props.path, key);
183
- const childPath = props.path ? `${props.path}.${key}` : key;
184
184
  const childValue = obj[key];
185
- const childHtml = props.renderChild(field, childValue, childPath);
185
+ const childHtml = props.renderChild(field, childValue, key);
186
186
  const required = requiredIndicator(field);
187
187
  const labelContent = [label];
188
188
  if (required !== void 0) labelContent.push(required);
@@ -205,13 +205,9 @@ function renderArrayNode(props) {
205
205
  const arr = Array.isArray(props.value) ? props.value : [];
206
206
  const element = props.element;
207
207
  if (element === void 0) return "";
208
- const items = arr.map((item) => {
209
- return h("li", { class: "sc-item" }, raw(props.renderChild(element, item)));
210
- });
211
- if (props.readOnly) return h("ul", { class: "sc-array" }, ...items);
212
- return h("div", { class: "sc-array" }, ...arr.map((item) => {
213
- return h("div", {}, raw(props.renderChild(element, item)));
214
- }));
208
+ const childHtmls = arr.map((item, i) => props.renderChild(element, item, `[${String(i)}]`));
209
+ if (props.readOnly) return h("ul", { class: "sc-array" }, ...childHtmls.map((childHtml) => h("li", { class: "sc-item" }, raw(childHtml))));
210
+ return h("div", { class: "sc-array" }, ...childHtmls.map((childHtml) => h("div", {}, raw(childHtml))));
215
211
  }
216
212
  function renderRecordHtml(props) {
217
213
  return serialize(renderRecordNode(props));
@@ -1,5 +1,5 @@
1
1
  import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
- import { o as HtmlResolver } from "../renderer-B3s8o2B8.mjs";
2
+ import { o as HtmlResolver } from "../renderer-DXo-rXHJ.mjs";
3
3
  import { HtmlElement } from "./html.mjs";
4
4
 
5
5
  //#region src/html/streamRenderers.d.ts
@@ -34,13 +34,18 @@ type BundleResolver = (uri: string) => unknown;
34
34
  *
35
35
  * Walks every $ref in the document. For external refs (not starting with `#`),
36
36
  * calls the resolver to fetch the external document, extracts the referenced
37
- * schema, inlines it into `components.schemas` with a synthesised name, and
38
- * rewrites the $ref to point to the inlined copy.
37
+ * schema, inlines it into `components.schemas` under a synthesised name, and
38
+ * rewrites the original $ref to point at the new internal location
39
+ * (`#/components/schemas/<name>`).
40
+ *
41
+ * Identical external refs share a single entry — the second occurrence of
42
+ * the same `(uri, fragment)` pair reuses the name produced for the first.
43
+ * Name collisions between different refs are resolved by suffixing a counter.
39
44
  *
40
45
  * The resolver is called once per unique URI and the result is cached.
41
46
  *
42
- * Returns a deep-cloned document with all external refs resolved.
43
- * The original document is never mutated.
47
+ * Returns a deep-cloned document with all external refs replaced by internal
48
+ * refs. The original document is never mutated.
44
49
  */
45
50
  declare function bundleOpenApiDoc(doc: Record<string, unknown>, resolver: BundleResolver): Promise<Record<string, unknown>>;
46
51
  //#endregion