@narrative.io/jsonforms-provider-protocols 2.11.0-beta.0 → 2.12.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/README.md +193 -33
- package/dist/core/initFormData.d.ts +17 -0
- package/dist/core/initFormData.d.ts.map +1 -0
- package/dist/core/initFormData.js +99 -0
- package/dist/core/initFormData.js.map +1 -0
- package/dist/core/projection.d.ts +36 -0
- package/dist/core/projection.d.ts.map +1 -0
- package/dist/core/projection.js +77 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/refs.d.ts +58 -0
- package/dist/core/refs.d.ts.map +1 -0
- package/dist/core/refs.js +70 -0
- package/dist/core/refs.js.map +1 -0
- package/dist/core/resolveScope.d.ts +17 -0
- package/dist/core/resolveScope.d.ts.map +1 -0
- package/dist/core/resolveScope.js +28 -0
- package/dist/core/resolveScope.js.map +1 -0
- package/dist/core/seedProjectionTargets.d.ts +60 -0
- package/dist/core/seedProjectionTargets.d.ts.map +1 -0
- package/dist/core/seedProjectionTargets.js +52 -0
- package/dist/core/seedProjectionTargets.js.map +1 -0
- package/dist/core/transforms.d.ts +8 -10
- package/dist/core/transforms.d.ts.map +1 -1
- package/dist/core/transforms.js +58 -13
- package/dist/core/transforms.js.map +1 -1
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/jsonforms-provider-protocols.css +6 -2
- package/dist/no-eval-ajv.d.ts +70 -0
- package/dist/no-eval-ajv.d.ts.map +1 -0
- package/dist/no-eval-ajv.js +247 -0
- package/dist/no-eval-ajv.js.map +1 -0
- package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderAutocomplete.vue.js +10 -4
- package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue2.js +19 -9
- package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
- package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts +9 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts.map +1 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.js +8 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.js.map +1 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue2.js +142 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue2.js.map +1 -0
- package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderSelect.vue.js +1 -1
- package/dist/vue/components/ProviderSelect.vue2.js +20 -8
- package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
- package/dist/vue/composables/useDataLayer.d.ts +10 -0
- package/dist/vue/composables/useDataLayer.d.ts.map +1 -0
- package/dist/vue/composables/useDataLayer.js +26 -0
- package/dist/vue/composables/useDataLayer.js.map +1 -0
- package/dist/vue/composables/useDerive.d.ts +5 -2
- package/dist/vue/composables/useDerive.d.ts.map +1 -1
- package/dist/vue/composables/useDerive.js +29 -12
- package/dist/vue/composables/useDerive.js.map +1 -1
- package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
- package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
- package/dist/vue/composables/useDeriveInitialValue.js +125 -0
- package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
- package/dist/vue/composables/useDirtyValidation.d.ts +9 -0
- package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -0
- package/dist/vue/composables/useDirtyValidation.js +15 -0
- package/dist/vue/composables/useDirtyValidation.js.map +1 -0
- package/dist/vue/composables/useProjection.d.ts +42 -0
- package/dist/vue/composables/useProjection.d.ts.map +1 -0
- package/dist/vue/composables/useProjection.js +116 -0
- package/dist/vue/composables/useProjection.js.map +1 -0
- package/dist/vue/composables/useProvider.d.ts +2 -2
- package/dist/vue/composables/useProvider.d.ts.map +1 -1
- package/dist/vue/composables/useProvider.js +14 -10
- package/dist/vue/composables/useProvider.js.map +1 -1
- package/dist/vue/index.d.ts +9 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +72 -34
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.d.ts +9 -0
- package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.js +42 -21
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts +9 -0
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.js +35 -21
- package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts +9 -0
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.js +37 -17
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts +9 -0
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.js +30 -20
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts +9 -0
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfText.vue.js +48 -32
- package/dist/vue/primevue/JfText.vue.js.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts +9 -0
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.js +31 -16
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
- package/dist/vue/primevue/index.d.ts.map +1 -1
- package/dist/vue/primevue/index.js +74 -7
- package/dist/vue/primevue/index.js.map +1 -1
- package/dist/vue/utils/autoSelect.js.map +1 -1
- package/dist/vue/utils/objectMultiSelect.d.ts +68 -0
- package/dist/vue/utils/objectMultiSelect.d.ts.map +1 -0
- package/dist/vue/utils/objectMultiSelect.js +72 -0
- package/dist/vue/utils/objectMultiSelect.js.map +1 -0
- package/dist/vue/utils/placeholder.d.ts +17 -0
- package/dist/vue/utils/placeholder.d.ts.map +1 -0
- package/dist/vue/utils/placeholder.js +17 -0
- package/dist/vue/utils/placeholder.js.map +1 -0
- package/package.json +10 -2
- package/src/core/initFormData.ts +208 -0
- package/src/core/projection.ts +147 -0
- package/src/core/refs.ts +166 -0
- package/src/core/resolveScope.ts +54 -0
- package/src/core/seedProjectionTargets.ts +144 -0
- package/src/core/transforms.ts +118 -26
- package/src/core/types.ts +9 -0
- package/src/index.ts +22 -2
- package/src/no-eval-ajv.ts +381 -0
- package/src/vue/components/ProviderAutocomplete.vue +10 -6
- package/src/vue/components/ProviderMultiSelect.vue +22 -15
- package/src/vue/components/ProviderObjectMultiSelect.vue +169 -0
- package/src/vue/components/ProviderSelect.vue +23 -14
- package/src/vue/composables/useDataLayer.ts +43 -0
- package/src/vue/composables/useDerive.ts +62 -16
- package/src/vue/composables/useDeriveInitialValue.ts +195 -0
- package/src/vue/composables/useDirtyValidation.ts +20 -0
- package/src/vue/composables/useProjection.ts +245 -0
- package/src/vue/composables/useProvider.ts +28 -11
- package/src/vue/index.ts +83 -47
- package/src/vue/primevue/JfBoolean.vue +35 -12
- package/src/vue/primevue/JfEnum.vue +34 -25
- package/src/vue/primevue/JfEnumArray.vue +36 -19
- package/src/vue/primevue/JfNumber.vue +30 -22
- package/src/vue/primevue/JfText.vue +46 -31
- package/src/vue/primevue/JfTextArea.vue +30 -19
- package/src/vue/primevue/index.ts +88 -7
- package/src/vue/utils/autoSelect.ts +2 -2
- package/src/vue/utils/objectMultiSelect.ts +171 -0
- package/src/vue/utils/placeholder.ts +42 -0
|
@@ -4,6 +4,8 @@ import _sfc_main$2 from "./JfNumber.vue.js";
|
|
|
4
4
|
import _sfc_main$3 from "./JfEnum.vue.js";
|
|
5
5
|
import _sfc_main$4 from "./JfEnumArray.vue.js";
|
|
6
6
|
import _sfc_main$5 from "./JfBoolean.vue.js";
|
|
7
|
+
import { getProjectedSchema } from "../../core/projection.js";
|
|
8
|
+
import { resolveScopeSchema } from "../../core/resolveScope.js";
|
|
7
9
|
const injectLayoutStyles = () => {
|
|
8
10
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
9
11
|
if (!document.getElementById("jsonforms-primevue-styles")) {
|
|
@@ -65,6 +67,7 @@ function registerPrimevueRenderers(jsonformsCore) {
|
|
|
65
67
|
isNumberControl,
|
|
66
68
|
isIntegerControl,
|
|
67
69
|
and,
|
|
70
|
+
or,
|
|
68
71
|
isControl,
|
|
69
72
|
schemaMatches,
|
|
70
73
|
isBooleanControl
|
|
@@ -86,27 +89,91 @@ function registerPrimevueRenderers(jsonformsCore) {
|
|
|
86
89
|
{}
|
|
87
90
|
);
|
|
88
91
|
};
|
|
92
|
+
const projectedSchemaMatches = (
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
|
+
((check) => (uischema, schema) => {
|
|
95
|
+
const ui = uischema;
|
|
96
|
+
const projection = ui?.options?.projection;
|
|
97
|
+
if (!projection || ui?.type !== "Control" || !ui?.scope) return false;
|
|
98
|
+
const propertySchema = resolveScopeSchema(
|
|
99
|
+
ui.scope,
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
+
schema
|
|
102
|
+
);
|
|
103
|
+
if (!propertySchema) return false;
|
|
104
|
+
return check(getProjectedSchema(propertySchema, projection));
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
const isMultilineProjection = (uischema, schema) => {
|
|
108
|
+
const ui = uischema;
|
|
109
|
+
return ui?.options?.multi === true && projectedSchemaMatches((s) => s?.type === "string")(uischema, schema);
|
|
110
|
+
};
|
|
89
111
|
const renderers = [
|
|
90
112
|
// Multiline text has higher priority than regular text
|
|
91
|
-
{ tester: rankWith(PRIME + 4, isMultilineString), renderer: _sfc_main },
|
|
92
|
-
{ tester: rankWith(PRIME + 3, isStringControl), renderer: _sfc_main$1 },
|
|
93
113
|
{
|
|
94
|
-
tester: rankWith(PRIME +
|
|
114
|
+
tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)),
|
|
115
|
+
renderer: _sfc_main
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
tester: rankWith(
|
|
119
|
+
PRIME + 3,
|
|
120
|
+
or(
|
|
121
|
+
isStringControl,
|
|
122
|
+
projectedSchemaMatches((s) => s?.type === "string")
|
|
123
|
+
)
|
|
124
|
+
),
|
|
125
|
+
renderer: _sfc_main$1
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
tester: rankWith(
|
|
129
|
+
PRIME + 6,
|
|
130
|
+
or(
|
|
131
|
+
isIntegerControl,
|
|
132
|
+
projectedSchemaMatches((s) => s?.type === "integer")
|
|
133
|
+
)
|
|
134
|
+
),
|
|
95
135
|
renderer: _sfc_main$2
|
|
96
136
|
},
|
|
97
137
|
{
|
|
98
|
-
tester: rankWith(
|
|
138
|
+
tester: rankWith(
|
|
139
|
+
PRIME + 4,
|
|
140
|
+
or(
|
|
141
|
+
isNumberControl,
|
|
142
|
+
projectedSchemaMatches((s) => s?.type === "number")
|
|
143
|
+
)
|
|
144
|
+
),
|
|
99
145
|
renderer: _sfc_main$2
|
|
100
146
|
},
|
|
101
147
|
{
|
|
102
|
-
tester: rankWith(
|
|
148
|
+
tester: rankWith(
|
|
149
|
+
PRIME + 7,
|
|
150
|
+
or(
|
|
151
|
+
and(isControl, schemaMatches(isScalarEnum)),
|
|
152
|
+
and(isControl, projectedSchemaMatches(isScalarEnum))
|
|
153
|
+
)
|
|
154
|
+
),
|
|
103
155
|
renderer: _sfc_main$3
|
|
104
156
|
},
|
|
105
157
|
{
|
|
106
|
-
tester: rankWith(
|
|
158
|
+
tester: rankWith(
|
|
159
|
+
PRIME + 8,
|
|
160
|
+
or(
|
|
161
|
+
and(isControl, schemaMatches(isEnumArray)),
|
|
162
|
+
and(isControl, projectedSchemaMatches(isEnumArray))
|
|
163
|
+
)
|
|
164
|
+
),
|
|
107
165
|
renderer: _sfc_main$4
|
|
108
166
|
},
|
|
109
|
-
{
|
|
167
|
+
{
|
|
168
|
+
tester: rankWith(
|
|
169
|
+
PRIME + 3,
|
|
170
|
+
or(
|
|
171
|
+
isBooleanControl,
|
|
172
|
+
projectedSchemaMatches((s) => s?.type === "boolean")
|
|
173
|
+
)
|
|
174
|
+
),
|
|
175
|
+
renderer: _sfc_main$5
|
|
176
|
+
}
|
|
110
177
|
];
|
|
111
178
|
primevueRenderers.splice(0, primevueRenderers.length, ...renderers);
|
|
112
179
|
renderersInitialized = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/vue/primevue/index.ts"],"sourcesContent":["import JfText from \"./JfText.vue\";\nimport JfTextArea from \"./JfTextArea.vue\";\nimport JfNumber from \"./JfNumber.vue\";\nimport JfEnum from \"./JfEnum.vue\";\nimport JfEnumArray from \"./JfEnumArray.vue\";\nimport JfBoolean from \"./JfBoolean.vue\";\n\n// Auto-inject layout styles\nconst injectLayoutStyles = () => {\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n if (!document.getElementById(\"jsonforms-primevue-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"jsonforms-primevue-styles\";\n style.textContent = `\n/* JSONForms PrimeVue Provider Protocols Layout Styles */\n.vertical-layout {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 24px;\n width: 100%;\n}\n\n.vertical-layout-item {\n width: 100%;\n}\n\n/* Form control wrapper */\n.jf-control {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n/* Form control label typography */\n.jf-label {\n font-weight: 600;\n font-size: 14px;\n line-height: 14px;\n color: #031553;\n text-align: left;\n}\n\n/* Form control description typography */\n.jf-description {\n font-weight: 400;\n font-size: 14px;\n line-height: 14px;\n color: #415290;\n text-align: left;\n}\n `;\n document.head.appendChild(style);\n }\n }\n};\n\n// Inject styles when this module is imported\ninjectLayoutStyles();\n\n// Track whether renderers have been registered to avoid duplicate registrations\nlet renderersInitialized = false;\n\n// Return empty renderers array initially\nexport const primevueRenderers: unknown[] = [];\n\n// Export a function that can be called by the consumer to register renderers\n// This should only be called once during application initialization\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerPrimevueRenderers(jsonformsCore: any): unknown[] {\n // Prevent duplicate registration which causes AJV schema recompilation\n if (renderersInitialized) {\n return primevueRenderers as unknown[];\n }\n\n const {\n rankWith,\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and,\n isControl,\n schemaMatches,\n isBooleanControl,\n } = jsonformsCore;\n\n // Give PrimeVue renderers a high base rank; vanilla commonly uses small ranks (2–5)\n const PRIME = 100;\n\n // helpers for enum detection - using simple schema checks instead of JSONForms internal functions\n const isScalarEnum = (s?: object) => {\n const schema = s as {\n type?: string;\n enum?: unknown[];\n oneOf?: unknown[];\n };\n return (\n schema &&\n schema.type !== \"array\" &&\n (Array.isArray(schema.enum) || Array.isArray(schema.oneOf))\n );\n };\n\n const isEnumArray = (s?: object) => {\n const schema = s as {\n type?: string;\n items?: { enum?: unknown[]; oneOf?: unknown[] };\n };\n return (\n schema?.type === \"array\" &&\n (Array.isArray(schema.items?.enum) || Array.isArray(schema.items?.oneOf))\n );\n };\n\n // helper for multiline detection\n const isMultilineString = (uischema: unknown, schema: unknown) => {\n const controlUischema = uischema as { options?: { multi?: boolean } };\n return and(isStringControl, () => controlUischema?.options?.multi === true)(\n uischema as Parameters<typeof isStringControl>[0],\n schema as Parameters<typeof isStringControl>[1],\n {} as Parameters<typeof isStringControl>[2],\n );\n };\n\n const renderers = [\n // Multiline text has higher priority than regular text\n {
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/vue/primevue/index.ts"],"sourcesContent":["import JfText from \"./JfText.vue\";\nimport JfTextArea from \"./JfTextArea.vue\";\nimport JfNumber from \"./JfNumber.vue\";\nimport JfEnum from \"./JfEnum.vue\";\nimport JfEnumArray from \"./JfEnumArray.vue\";\nimport JfBoolean from \"./JfBoolean.vue\";\nimport { getProjectedSchema } from \"../../core/projection\";\nimport { resolveScopeSchema } from \"../../core/resolveScope\";\n\n// Auto-inject layout styles\nconst injectLayoutStyles = () => {\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n if (!document.getElementById(\"jsonforms-primevue-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"jsonforms-primevue-styles\";\n style.textContent = `\n/* JSONForms PrimeVue Provider Protocols Layout Styles */\n.vertical-layout {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 24px;\n width: 100%;\n}\n\n.vertical-layout-item {\n width: 100%;\n}\n\n/* Form control wrapper */\n.jf-control {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n/* Form control label typography */\n.jf-label {\n font-weight: 600;\n font-size: 14px;\n line-height: 14px;\n color: #031553;\n text-align: left;\n}\n\n/* Form control description typography */\n.jf-description {\n font-weight: 400;\n font-size: 14px;\n line-height: 14px;\n color: #415290;\n text-align: left;\n}\n `;\n document.head.appendChild(style);\n }\n }\n};\n\n// Inject styles when this module is imported\ninjectLayoutStyles();\n\n// Track whether renderers have been registered to avoid duplicate registrations\nlet renderersInitialized = false;\n\n// Return empty renderers array initially\nexport const primevueRenderers: unknown[] = [];\n\n// Export a function that can be called by the consumer to register renderers\n// This should only be called once during application initialization\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerPrimevueRenderers(jsonformsCore: any): unknown[] {\n // Prevent duplicate registration which causes AJV schema recompilation\n if (renderersInitialized) {\n return primevueRenderers as unknown[];\n }\n\n const {\n rankWith,\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and,\n or,\n isControl,\n schemaMatches,\n isBooleanControl,\n } = jsonformsCore;\n\n // Give PrimeVue renderers a high base rank; vanilla commonly uses small ranks (2–5)\n const PRIME = 100;\n\n // helpers for enum detection - using simple schema checks instead of JSONForms internal functions\n const isScalarEnum = (s?: object) => {\n const schema = s as {\n type?: string;\n enum?: unknown[];\n oneOf?: unknown[];\n };\n return (\n schema &&\n schema.type !== \"array\" &&\n (Array.isArray(schema.enum) || Array.isArray(schema.oneOf))\n );\n };\n\n const isEnumArray = (s?: object) => {\n const schema = s as {\n type?: string;\n items?: { enum?: unknown[]; oneOf?: unknown[] };\n };\n return (\n schema?.type === \"array\" &&\n (Array.isArray(schema.items?.enum) || Array.isArray(schema.items?.oneOf))\n );\n };\n\n // helper for multiline detection\n const isMultilineString = (uischema: unknown, schema: unknown) => {\n const controlUischema = uischema as { options?: { multi?: boolean } };\n return and(isStringControl, () => controlUischema?.options?.multi === true)(\n uischema as Parameters<typeof isStringControl>[0],\n schema as Parameters<typeof isStringControl>[1],\n {} as Parameters<typeof isStringControl>[2],\n );\n };\n\n // Projection-aware schema check: when options.projection is set,\n // resolve the projected schema and test against it instead of the original\n\n const projectedSchemaMatches =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (check: (schema: any) => boolean) =>\n (uischema: unknown, schema: unknown): boolean => {\n const ui = uischema as {\n type?: string;\n scope?: string;\n options?: { projection?: string };\n };\n const projection = ui?.options?.projection;\n if (!projection || ui?.type !== \"Control\" || !ui?.scope) return false;\n\n const propertySchema = resolveScopeSchema(\n ui.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n if (!propertySchema) return false;\n\n return check(getProjectedSchema(propertySchema, projection));\n };\n\n const isMultilineProjection = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { options?: { multi?: boolean } };\n return (\n ui?.options?.multi === true &&\n projectedSchemaMatches((s) => s?.type === \"string\")(uischema, schema)\n );\n };\n\n const renderers = [\n // Multiline text has higher priority than regular text\n {\n tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)),\n renderer: JfTextArea,\n },\n {\n tester: rankWith(\n PRIME + 3,\n or(\n isStringControl,\n projectedSchemaMatches((s) => s?.type === \"string\"),\n ),\n ),\n renderer: JfText,\n },\n {\n tester: rankWith(\n PRIME + 6,\n or(\n isIntegerControl,\n projectedSchemaMatches((s) => s?.type === \"integer\"),\n ),\n ),\n renderer: JfNumber,\n },\n {\n tester: rankWith(\n PRIME + 4,\n or(\n isNumberControl,\n projectedSchemaMatches((s) => s?.type === \"number\"),\n ),\n ),\n renderer: JfNumber,\n },\n {\n tester: rankWith(\n PRIME + 7,\n or(\n and(isControl, schemaMatches(isScalarEnum)),\n and(isControl, projectedSchemaMatches(isScalarEnum)),\n ),\n ),\n renderer: JfEnum,\n },\n {\n tester: rankWith(\n PRIME + 8,\n or(\n and(isControl, schemaMatches(isEnumArray)),\n and(isControl, projectedSchemaMatches(isEnumArray)),\n ),\n ),\n renderer: JfEnumArray,\n },\n {\n tester: rankWith(\n PRIME + 3,\n or(\n isBooleanControl,\n projectedSchemaMatches((s) => s?.type === \"boolean\"),\n ),\n ),\n renderer: JfBoolean,\n },\n ];\n\n // Update the exported array\n primevueRenderers.splice(0, primevueRenderers.length, ...renderers);\n\n // Mark as initialized to prevent duplicate registrations\n renderersInitialized = true;\n\n return renderers;\n}\n\nexport { JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean };\n"],"names":["JfTextArea","JfText","JfNumber","JfEnum","JfEnumArray","JfBoolean"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoSelect.js","sources":["../../../src/vue/utils/autoSelect.ts"],"sourcesContent":["import type { ProviderItem } from \"../../core/types\";\n\nexport interface AutoSelectParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown;\n}\n\n/**\n * Determines if auto-select should trigger for single-select dropdowns.\n * Returns null if auto-select should not trigger.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is enabled (default: true for single-select)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty (undefined/null) OR not in the current options\n */\nexport function shouldAutoSelect(params: AutoSelectParams): unknown | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const isValueEmpty = currentValue === undefined || currentValue === null;\n const isValueInOptions = items.some((item) => item.value === currentValue);\n\n if (isValueEmpty || !isValueInOptions) {\n return singleItem.value;\n }\n\n return null;\n}\n\nexport interface AutoSelectMultiParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown[];\n}\n\n/**\n * Determines if auto-select should trigger for multiselect dropdowns.\n * Returns null if auto-select should not trigger, otherwise returns an array with the single value.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is explicitly enabled (default: false for multiselect)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty array OR current selection is not in the current options\n */\nexport function shouldAutoSelectMulti(\n params: AutoSelectMultiParams
|
|
1
|
+
{"version":3,"file":"autoSelect.js","sources":["../../../src/vue/utils/autoSelect.ts"],"sourcesContent":["import type { ProviderItem } from \"../../core/types\";\n\nexport interface AutoSelectParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown;\n}\n\n/**\n * Determines if auto-select should trigger for single-select dropdowns.\n * Returns null if auto-select should not trigger.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is enabled (default: true for single-select)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty (undefined/null) OR not in the current options\n */\nexport function shouldAutoSelect(params: AutoSelectParams): unknown | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const isValueEmpty = currentValue === undefined || currentValue === null;\n const isValueInOptions = items.some((item) => item.value === currentValue);\n\n if (isValueEmpty || !isValueInOptions) {\n return singleItem.value;\n }\n\n return null;\n}\n\nexport interface AutoSelectMultiParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown[];\n}\n\n/**\n * Determines if auto-select should trigger for multiselect dropdowns.\n * Returns null if auto-select should not trigger, otherwise returns an array with the single value.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is explicitly enabled (default: false for multiselect)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty array OR current selection is not in the current options\n */\nexport function shouldAutoSelectMulti(\n params: AutoSelectMultiParams,\n): unknown[] | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const currentArray = Array.isArray(currentValue) ? currentValue : [];\n const isValueEmpty = currentArray.length === 0;\n const hasValidSelection = currentArray.some((val) =>\n items.some((item) => item.value === val),\n );\n\n if (isValueEmpty || !hasValidSelection) {\n return [singleItem.value];\n }\n\n return null;\n}\n"],"names":[],"mappings":"AAmBO,SAAS,iBAAiB,QAA0C;AACzE,QAAM,EAAE,kBAAkB,WAAW,OAAO,iBAAiB;AAE7D,MAAI,CAAC,oBAAoB,aAAa,MAAM,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,CAAC;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,iBAAiB,UAAa,iBAAiB;AACpE,QAAM,mBAAmB,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,YAAY;AAEzE,MAAI,gBAAgB,CAAC,kBAAkB;AACrC,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO;AACT;AAmBO,SAAS,sBACd,QACkB;AAClB,QAAM,EAAE,kBAAkB,WAAW,OAAO,iBAAiB;AAE7D,MAAI,CAAC,oBAAoB,aAAa,MAAM,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,CAAC;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAA;AAClE,QAAM,eAAe,aAAa,WAAW;AAC7C,QAAM,oBAAoB,aAAa;AAAA,IAAK,CAAC,QAC3C,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,GAAG;AAAA,EAAA;AAGzC,MAAI,gBAAgB,CAAC,mBAAmB;AACtC,WAAO,CAAC,WAAW,KAAK;AAAA,EAC1B;AAEA,SAAO;AACT;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for `ProviderObjectMultiSelect`: bidirectional translation between
|
|
3
|
+
* the form-data shape (paired objects with consumer-named keys) and the
|
|
4
|
+
* MultiSelect model shape (`{ value, label }` matching the provider's `map`
|
|
5
|
+
* config), plus `objectKeys` inference when the consumer doesn't specify
|
|
6
|
+
* them on the uischema.
|
|
7
|
+
*
|
|
8
|
+
* Pure functions — no Vue, no rendering — so the renderer can compose them
|
|
9
|
+
* and tests can exercise the logic in isolation.
|
|
10
|
+
*/
|
|
11
|
+
export interface ObjectKeys {
|
|
12
|
+
/** Property name on the form-data object that holds the identifier. */
|
|
13
|
+
value: string;
|
|
14
|
+
/** Property name on the form-data object that holds the display string. */
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MultiSelectOption {
|
|
18
|
+
value: unknown;
|
|
19
|
+
label: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the
|
|
24
|
+
* shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items
|
|
25
|
+
* already in `{ value, label }` shape pass through; missing keys yield
|
|
26
|
+
* `undefined` / `""` so the renderer doesn't blow up on partial data.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toMultiSelectShape(formData: unknown, keys: ObjectKeys): MultiSelectOption[];
|
|
29
|
+
/**
|
|
30
|
+
* Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items
|
|
31
|
+
* back into form-data shape using the consumer-specified property names.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fromMultiSelectShape(modelData: unknown, keys: ObjectKeys): Record<string, unknown>[];
|
|
34
|
+
/**
|
|
35
|
+
* Order-insensitive equality of two object-shape selections by the
|
|
36
|
+
* identifier property. Used to short-circuit redundant `handleChange`
|
|
37
|
+
* calls when the model emits the same selection under reference inequality.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sameObjectSet(a: unknown, b: unknown, identifierKey: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the items schema of an array control, dereferencing `items.$ref`
|
|
42
|
+
* against the root if present. Returns `undefined` if no items schema exists.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveItemsSchema(arraySchema: Record<string, any> | undefined, rootSchema: Record<string, any>): Record<string, any> | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Infer `objectKeys` from a resolved items schema when the consumer hasn't
|
|
47
|
+
* specified them on the uischema.
|
|
48
|
+
*
|
|
49
|
+
* Strategy: look at `items.required`. If it has exactly two entries, the
|
|
50
|
+
* one whose property has `format: 'uuid'` becomes `value`; the other
|
|
51
|
+
* becomes `label`. If neither has a uuid format, the first entry is
|
|
52
|
+
* `value`, the second is `label`. Returns `undefined` (and the renderer
|
|
53
|
+
* throws at mount) for any other shape — explicit `objectKeys` is required.
|
|
54
|
+
*/
|
|
55
|
+
export declare function inferObjectKeys(itemsSchema: Record<string, any> | undefined): ObjectKeys | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the active `objectKeys` for a control: prefer the explicit
|
|
58
|
+
* `uischema.options.objectKeys`, fall back to schema-driven inference.
|
|
59
|
+
* Returns `undefined` when neither is available; the renderer surfaces a
|
|
60
|
+
* runtime error in that case so the consumer knows to be explicit.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveObjectKeys(uischemaOptions: {
|
|
63
|
+
objectKeys?: {
|
|
64
|
+
value?: unknown;
|
|
65
|
+
label?: unknown;
|
|
66
|
+
};
|
|
67
|
+
} | undefined, itemsSchema: Record<string, any> | undefined): ObjectKeys | undefined;
|
|
68
|
+
//# sourceMappingURL=objectMultiSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectMultiSelect.d.ts","sourceRoot":"","sources":["../../../src/vue/utils/objectMultiSelect.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GACf,iBAAiB,EAAE,CAqBrB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,OAAO,EAClB,IAAI,EAAE,UAAU,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAW3B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,OAAO,EACV,CAAC,EAAE,OAAO,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAaT;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAEhC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAE5C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAOjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAE7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC3C,UAAU,GAAG,SAAS,CAcxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,eAAe,EAAE;IAAE,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,EAElF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC3C,UAAU,GAAG,SAAS,CAUxB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { deref } from "../../core/refs.js";
|
|
2
|
+
function toMultiSelectShape(formData, keys) {
|
|
3
|
+
if (!Array.isArray(formData)) return [];
|
|
4
|
+
return formData.filter((item) => item !== null && typeof item === "object").map((item) => {
|
|
5
|
+
const obj = item;
|
|
6
|
+
if (!(keys.value in obj) && !(keys.label in obj) && "value" in obj && "label" in obj) {
|
|
7
|
+
return obj;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
value: obj[keys.value],
|
|
11
|
+
label: typeof obj[keys.label] === "string" ? obj[keys.label] : ""
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function fromMultiSelectShape(modelData, keys) {
|
|
16
|
+
if (!Array.isArray(modelData)) return [];
|
|
17
|
+
return modelData.filter((item) => item !== null && typeof item === "object").map((item) => {
|
|
18
|
+
const obj = item;
|
|
19
|
+
return {
|
|
20
|
+
[keys.value]: obj.value,
|
|
21
|
+
[keys.label]: obj.label
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function sameObjectSet(a, b, identifierKey) {
|
|
26
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const ids = new Set(
|
|
30
|
+
b.filter((x) => x !== null && typeof x === "object").map((x) => x[identifierKey])
|
|
31
|
+
);
|
|
32
|
+
return a.every((x) => {
|
|
33
|
+
if (x === null || typeof x !== "object") return false;
|
|
34
|
+
return ids.has(x[identifierKey]);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function resolveItemsSchema(arraySchema, rootSchema) {
|
|
38
|
+
if (!arraySchema || typeof arraySchema !== "object") return void 0;
|
|
39
|
+
const items = arraySchema.items;
|
|
40
|
+
if (!items || typeof items !== "object" || Array.isArray(items)) {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
return deref(items, rootSchema);
|
|
44
|
+
}
|
|
45
|
+
function inferObjectKeys(itemsSchema) {
|
|
46
|
+
if (!itemsSchema || itemsSchema.type !== "object") return void 0;
|
|
47
|
+
const required = itemsSchema.required;
|
|
48
|
+
if (!Array.isArray(required) || required.length !== 2) return void 0;
|
|
49
|
+
const [a, b] = required;
|
|
50
|
+
const props = itemsSchema.properties ?? {};
|
|
51
|
+
const aIsUuid = props[a]?.format === "uuid";
|
|
52
|
+
const bIsUuid = props[b]?.format === "uuid";
|
|
53
|
+
if (aIsUuid && !bIsUuid) return { value: a, label: b };
|
|
54
|
+
if (bIsUuid && !aIsUuid) return { value: b, label: a };
|
|
55
|
+
return { value: a, label: b };
|
|
56
|
+
}
|
|
57
|
+
function resolveObjectKeys(uischemaOptions, itemsSchema) {
|
|
58
|
+
const explicit = uischemaOptions?.objectKeys;
|
|
59
|
+
if (explicit && typeof explicit.value === "string" && typeof explicit.label === "string") {
|
|
60
|
+
return { value: explicit.value, label: explicit.label };
|
|
61
|
+
}
|
|
62
|
+
return inferObjectKeys(itemsSchema);
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
fromMultiSelectShape,
|
|
66
|
+
inferObjectKeys,
|
|
67
|
+
resolveItemsSchema,
|
|
68
|
+
resolveObjectKeys,
|
|
69
|
+
sameObjectSet,
|
|
70
|
+
toMultiSelectShape
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=objectMultiSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectMultiSelect.js","sources":["../../../src/vue/utils/objectMultiSelect.ts"],"sourcesContent":["import { deref } from \"../../core/refs\";\n\n/**\n * Helpers for `ProviderObjectMultiSelect`: bidirectional translation between\n * the form-data shape (paired objects with consumer-named keys) and the\n * MultiSelect model shape (`{ value, label }` matching the provider's `map`\n * config), plus `objectKeys` inference when the consumer doesn't specify\n * them on the uischema.\n *\n * Pure functions — no Vue, no rendering — so the renderer can compose them\n * and tests can exercise the logic in isolation.\n */\n\nexport interface ObjectKeys {\n /** Property name on the form-data object that holds the identifier. */\n value: string;\n /** Property name on the form-data object that holds the display string. */\n label: string;\n}\n\nexport interface MultiSelectOption {\n value: unknown;\n label: string;\n [key: string]: unknown;\n}\n\n/**\n * Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the\n * shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items\n * already in `{ value, label }` shape pass through; missing keys yield\n * `undefined` / `\"\"` so the renderer doesn't blow up on partial data.\n */\nexport function toMultiSelectShape(\n formData: unknown,\n keys: ObjectKeys,\n): MultiSelectOption[] {\n if (!Array.isArray(formData)) return [];\n return formData\n .filter((item) => item !== null && typeof item === \"object\")\n .map((item) => {\n const obj = item as Record<string, unknown>;\n // Tolerate already-translated items: if both `value` and `label` are\n // present and the form-data keys aren't, assume MultiSelect shape.\n if (\n !(keys.value in obj) &&\n !(keys.label in obj) &&\n \"value\" in obj &&\n \"label\" in obj\n ) {\n return obj as MultiSelectOption;\n }\n return {\n value: obj[keys.value],\n label: typeof obj[keys.label] === \"string\" ? (obj[keys.label] as string) : \"\",\n };\n });\n}\n\n/**\n * Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items\n * back into form-data shape using the consumer-specified property names.\n */\nexport function fromMultiSelectShape(\n modelData: unknown,\n keys: ObjectKeys,\n): Record<string, unknown>[] {\n if (!Array.isArray(modelData)) return [];\n return modelData\n .filter((item) => item !== null && typeof item === \"object\")\n .map((item) => {\n const obj = item as Record<string, unknown>;\n return {\n [keys.value]: obj.value,\n [keys.label]: obj.label,\n };\n });\n}\n\n/**\n * Order-insensitive equality of two object-shape selections by the\n * identifier property. Used to short-circuit redundant `handleChange`\n * calls when the model emits the same selection under reference inequality.\n */\nexport function sameObjectSet(\n a: unknown,\n b: unknown,\n identifierKey: string,\n): boolean {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {\n return false;\n }\n const ids = new Set(\n b\n .filter((x) => x !== null && typeof x === \"object\")\n .map((x) => (x as Record<string, unknown>)[identifierKey]),\n );\n return a.every((x) => {\n if (x === null || typeof x !== \"object\") return false;\n return ids.has((x as Record<string, unknown>)[identifierKey]);\n });\n}\n\n/**\n * Resolve the items schema of an array control, dereferencing `items.$ref`\n * against the root if present. Returns `undefined` if no items schema exists.\n */\nexport function resolveItemsSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n arraySchema: Record<string, any> | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!arraySchema || typeof arraySchema !== \"object\") return undefined;\n const items = arraySchema.items;\n if (!items || typeof items !== \"object\" || Array.isArray(items)) {\n return undefined;\n }\n return deref(items, rootSchema);\n}\n\n/**\n * Infer `objectKeys` from a resolved items schema when the consumer hasn't\n * specified them on the uischema.\n *\n * Strategy: look at `items.required`. If it has exactly two entries, the\n * one whose property has `format: 'uuid'` becomes `value`; the other\n * becomes `label`. If neither has a uuid format, the first entry is\n * `value`, the second is `label`. Returns `undefined` (and the renderer\n * throws at mount) for any other shape — explicit `objectKeys` is required.\n */\nexport function inferObjectKeys(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n itemsSchema: Record<string, any> | undefined,\n): ObjectKeys | undefined {\n if (!itemsSchema || itemsSchema.type !== \"object\") return undefined;\n const required = itemsSchema.required;\n if (!Array.isArray(required) || required.length !== 2) return undefined;\n const [a, b] = required as [string, string];\n const props = (itemsSchema.properties ?? {}) as Record<\n string,\n { format?: string }\n >;\n const aIsUuid = props[a]?.format === \"uuid\";\n const bIsUuid = props[b]?.format === \"uuid\";\n if (aIsUuid && !bIsUuid) return { value: a, label: b };\n if (bIsUuid && !aIsUuid) return { value: b, label: a };\n return { value: a, label: b };\n}\n\n/**\n * Resolve the active `objectKeys` for a control: prefer the explicit\n * `uischema.options.objectKeys`, fall back to schema-driven inference.\n * Returns `undefined` when neither is available; the renderer surfaces a\n * runtime error in that case so the consumer knows to be explicit.\n */\nexport function resolveObjectKeys(\n uischemaOptions: { objectKeys?: { value?: unknown; label?: unknown } } | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n itemsSchema: Record<string, any> | undefined,\n): ObjectKeys | undefined {\n const explicit = uischemaOptions?.objectKeys;\n if (\n explicit &&\n typeof explicit.value === \"string\" &&\n typeof explicit.label === \"string\"\n ) {\n return { value: explicit.value, label: explicit.label };\n }\n return inferObjectKeys(itemsSchema);\n}\n"],"names":[],"mappings":";AAgCO,SAAS,mBACd,UACA,MACqB;AACrB,MAAI,CAAC,MAAM,QAAQ,QAAQ,UAAU,CAAA;AACrC,SAAO,SACJ,OAAO,CAAC,SAAS,SAAS,QAAQ,OAAO,SAAS,QAAQ,EAC1D,IAAI,CAAC,SAAS;AACb,UAAM,MAAM;AAGZ,QACE,EAAE,KAAK,SAAS,QAChB,EAAE,KAAK,SAAS,QAChB,WAAW,OACX,WAAW,KACX;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,OAAO,IAAI,KAAK,KAAK;AAAA,MACrB,OAAO,OAAO,IAAI,KAAK,KAAK,MAAM,WAAY,IAAI,KAAK,KAAK,IAAe;AAAA,IAAA;AAAA,EAE/E,CAAC;AACL;AAMO,SAAS,qBACd,WACA,MAC2B;AAC3B,MAAI,CAAC,MAAM,QAAQ,SAAS,UAAU,CAAA;AACtC,SAAO,UACJ,OAAO,CAAC,SAAS,SAAS,QAAQ,OAAO,SAAS,QAAQ,EAC1D,IAAI,CAAC,SAAS;AACb,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,CAAC,KAAK,KAAK,GAAG,IAAI;AAAA,MAClB,CAAC,KAAK,KAAK,GAAG,IAAI;AAAA,IAAA;AAAA,EAEtB,CAAC;AACL;AAOO,SAAS,cACd,GACA,GACA,eACS;AACT,MAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ;AACnE,WAAO;AAAA,EACT;AACA,QAAM,MAAM,IAAI;AAAA,IACd,EACG,OAAO,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ,EACjD,IAAI,CAAC,MAAO,EAA8B,aAAa,CAAC;AAAA,EAAA;AAE7D,SAAO,EAAE,MAAM,CAAC,MAAM;AACpB,QAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,WAAO,IAAI,IAAK,EAA8B,aAAa,CAAC;AAAA,EAC9D,CAAC;AACH;AAMO,SAAS,mBAEd,aAEA,YAEiC;AACjC,MAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO;AAC5D,QAAM,QAAQ,YAAY;AAC1B,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO;AAAA,EACT;AACA,SAAO,MAAM,OAAO,UAAU;AAChC;AAYO,SAAS,gBAEd,aACwB;AACxB,MAAI,CAAC,eAAe,YAAY,SAAS,SAAU,QAAO;AAC1D,QAAM,WAAW,YAAY;AAC7B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO;AAC9D,QAAM,CAAC,GAAG,CAAC,IAAI;AACf,QAAM,QAAS,YAAY,cAAc,CAAA;AAIzC,QAAM,UAAU,MAAM,CAAC,GAAG,WAAW;AACrC,QAAM,UAAU,MAAM,CAAC,GAAG,WAAW;AACrC,MAAI,WAAW,CAAC,QAAS,QAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AACnD,MAAI,WAAW,CAAC,QAAS,QAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AACnD,SAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AAC5B;AAQO,SAAS,kBACd,iBAEA,aACwB;AACxB,QAAM,WAAW,iBAAiB;AAClC,MACE,YACA,OAAO,SAAS,UAAU,YAC1B,OAAO,SAAS,UAAU,UAC1B;AACA,WAAO,EAAE,OAAO,SAAS,OAAO,OAAO,SAAS,MAAA;AAAA,EAClD;AACA,SAAO,gBAAgB,WAAW;AACpC;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder resolution for form controls.
|
|
3
|
+
*
|
|
4
|
+
* Precedence:
|
|
5
|
+
* 1. `uischema.options.placeholder` (explicit author intent — always wins)
|
|
6
|
+
* 2. `Select ${label}` or `Enter ${label}` when a label is resolvable
|
|
7
|
+
* 3. Kind-appropriate bare fallback
|
|
8
|
+
*
|
|
9
|
+
* Never falls back to `schema.description` — descriptions are rendered as
|
|
10
|
+
* prose above the field by our renderers, so re-using them as placeholder
|
|
11
|
+
* produces a duplicated, truncated string inside the input.
|
|
12
|
+
*/
|
|
13
|
+
export type PlaceholderKind = "select" | "input";
|
|
14
|
+
export declare function resolvePlaceholder(uischema: {
|
|
15
|
+
options?: unknown;
|
|
16
|
+
} | undefined, resolvedLabel: string | undefined, kind: PlaceholderKind): string | undefined;
|
|
17
|
+
//# sourceMappingURL=placeholder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholder.d.ts","sourceRoot":"","sources":["../../../src/vue/utils/placeholder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC;AAUjD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC3C,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,IAAI,EAAE,eAAe,GACpB,MAAM,GAAG,SAAS,CAcpB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function stripRequiredMarker(label) {
|
|
2
|
+
return label.replace(/\s*\*\s*$/, "").trim();
|
|
3
|
+
}
|
|
4
|
+
function resolvePlaceholder(uischema, resolvedLabel, kind) {
|
|
5
|
+
const options = uischema?.options;
|
|
6
|
+
const explicit = options && typeof options === "object" ? options.placeholder : void 0;
|
|
7
|
+
if (typeof explicit === "string" && explicit.length > 0) return explicit;
|
|
8
|
+
const label = resolvedLabel ? stripRequiredMarker(resolvedLabel) : "";
|
|
9
|
+
if (label) {
|
|
10
|
+
return kind === "select" ? `Select ${label}` : `Enter ${label}`;
|
|
11
|
+
}
|
|
12
|
+
return kind === "select" ? "Select…" : void 0;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
resolvePlaceholder
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=placeholder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholder.js","sources":["../../../src/vue/utils/placeholder.ts"],"sourcesContent":["/**\n * Placeholder resolution for form controls.\n *\n * Precedence:\n * 1. `uischema.options.placeholder` (explicit author intent — always wins)\n * 2. `Select ${label}` or `Enter ${label}` when a label is resolvable\n * 3. Kind-appropriate bare fallback\n *\n * Never falls back to `schema.description` — descriptions are rendered as\n * prose above the field by our renderers, so re-using them as placeholder\n * produces a duplicated, truncated string inside the input.\n */\n\nexport type PlaceholderKind = \"select\" | \"input\";\n\n/**\n * Strip a trailing required-indicator asterisk (\" *\" or \"*\") that `resolveLabel`\n * appends for required fields, so composed placeholders read naturally.\n */\nfunction stripRequiredMarker(label: string): string {\n return label.replace(/\\s*\\*\\s*$/, \"\").trim();\n}\n\nexport function resolvePlaceholder(\n uischema: { options?: unknown } | undefined,\n resolvedLabel: string | undefined,\n kind: PlaceholderKind,\n): string | undefined {\n const options = uischema?.options;\n const explicit =\n options && typeof options === \"object\"\n ? (options as Record<string, unknown>).placeholder\n : undefined;\n if (typeof explicit === \"string\" && explicit.length > 0) return explicit;\n\n const label = resolvedLabel ? stripRequiredMarker(resolvedLabel) : \"\";\n if (label) {\n return kind === \"select\" ? `Select ${label}` : `Enter ${label}`;\n }\n\n return kind === \"select\" ? \"Select…\" : undefined;\n}\n"],"names":[],"mappings":"AAmBA,SAAS,oBAAoB,OAAuB;AAClD,SAAO,MAAM,QAAQ,aAAa,EAAE,EAAE,KAAA;AACxC;AAEO,SAAS,mBACd,UACA,eACA,MACoB;AACpB,QAAM,UAAU,UAAU;AAC1B,QAAM,WACJ,WAAW,OAAO,YAAY,WACzB,QAAoC,cACrC;AACN,MAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EAAG,QAAO;AAEhE,QAAM,QAAQ,gBAAgB,oBAAoB,aAAa,IAAI;AACnE,MAAI,OAAO;AACT,WAAO,SAAS,WAAW,UAAU,KAAK,KAAK,SAAS,KAAK;AAAA,EAC/D;AAEA,SAAO,SAAS,WAAW,YAAY;AACzC;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narrative.io/jsonforms-provider-protocols",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Narrative I/O",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"import": "./dist/protocols/rest_api.js",
|
|
48
48
|
"types": "./dist/protocols/rest_api.d.ts"
|
|
49
49
|
},
|
|
50
|
+
"./no-eval-ajv": {
|
|
51
|
+
"import": "./dist/no-eval-ajv.js",
|
|
52
|
+
"types": "./dist/no-eval-ajv.d.ts"
|
|
53
|
+
},
|
|
50
54
|
"./style.css": "./dist/jsonforms-provider-protocols.css"
|
|
51
55
|
},
|
|
52
56
|
"files": [
|
|
@@ -84,12 +88,14 @@
|
|
|
84
88
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
85
89
|
"@vue/eslint-config-typescript": "^14.6.0",
|
|
86
90
|
"@vue/runtime-core": "^3.5.20",
|
|
91
|
+
"@vue/test-utils": "^2.4.6",
|
|
87
92
|
"bun-git-hooks": "^0.2.19",
|
|
88
93
|
"commitlint": "^20.0.0",
|
|
89
94
|
"eslint": "^9.34.0",
|
|
90
95
|
"eslint-config-prettier": "^10.1.8",
|
|
91
96
|
"eslint-plugin-vue": "^10.4.0",
|
|
92
97
|
"globals": "^16.3.0",
|
|
98
|
+
"happy-dom": "^20.6.3",
|
|
93
99
|
"prettier": "^3.6.2",
|
|
94
100
|
"primevue": "^4.3.8",
|
|
95
101
|
"typescript": "^5.9.2",
|
|
@@ -105,5 +111,7 @@
|
|
|
105
111
|
"@jsonforms/vue": "^3.6.0",
|
|
106
112
|
"@jsonforms/vue-vanilla": "^3.6.0"
|
|
107
113
|
},
|
|
108
|
-
"dependencies": {
|
|
114
|
+
"dependencies": {
|
|
115
|
+
"@cfworker/json-schema": "4.1.1"
|
|
116
|
+
}
|
|
109
117
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialize a form data object from a JSON Schema.
|
|
3
|
+
* Resolves $ref, const, default, oneOf/discriminator, and typed empty values.
|
|
4
|
+
*
|
|
5
|
+
* Optional fields (not listed in the parent schema's `required` array) that
|
|
6
|
+
* have no `const`, no single-value `enum`, and no `default` are omitted
|
|
7
|
+
* entirely from the result. This avoids seeding values (e.g. `null` for
|
|
8
|
+
* `type: "integer"`) that fail AJV's type check and surface spurious errors
|
|
9
|
+
* on untouched fields. Required fields retain legacy typed-empty seeding
|
|
10
|
+
* (`""`, `null`, `false`, `[]`) so that "is required" surfaces cleanly.
|
|
11
|
+
*
|
|
12
|
+
* @param schema - The full JSON Schema (must include $defs if $refs are used)
|
|
13
|
+
* @param seed - Optional existing data to merge (seed values take priority)
|
|
14
|
+
* @returns A data object with schema-defined fields initialized
|
|
15
|
+
*/
|
|
16
|
+
export function initFormDataFromSchema(
|
|
17
|
+
schema: Record<string, unknown>,
|
|
18
|
+
seed?: Record<string, unknown>,
|
|
19
|
+
): Record<string, unknown> {
|
|
20
|
+
const result = initProperty(schema, schema, seed, true) as
|
|
21
|
+
| Record<string, unknown>
|
|
22
|
+
| undefined;
|
|
23
|
+
|
|
24
|
+
const base =
|
|
25
|
+
result && typeof result === "object" && !Array.isArray(result)
|
|
26
|
+
? result
|
|
27
|
+
: {};
|
|
28
|
+
|
|
29
|
+
// Preserve seed keys not described by the schema's properties.
|
|
30
|
+
if (seed && typeof seed === "object") {
|
|
31
|
+
const schemaKeys = new Set(
|
|
32
|
+
Object.keys(
|
|
33
|
+
(resolveRef(schema, schema) as Record<string, unknown>)?.properties ??
|
|
34
|
+
{},
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
for (const key of Object.keys(seed)) {
|
|
38
|
+
if (!schemaKeys.has(key) && !(key in base)) {
|
|
39
|
+
base[key] = seed[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
import { resolveRef } from "./refs";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize a single property value based on its schema definition.
|
|
51
|
+
* Returns `undefined` when the property is optional and has nothing
|
|
52
|
+
* concrete to seed — the caller then omits the key from its result.
|
|
53
|
+
*/
|
|
54
|
+
function initProperty(
|
|
55
|
+
property: Record<string, unknown>,
|
|
56
|
+
root: Record<string, unknown>,
|
|
57
|
+
seed: unknown,
|
|
58
|
+
required: boolean,
|
|
59
|
+
): unknown {
|
|
60
|
+
if (!property || typeof property !== "object") {
|
|
61
|
+
return required ? null : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const resolved = resolveRef(property, root);
|
|
65
|
+
const type = resolved.type as string | undefined;
|
|
66
|
+
const isOneOf = Array.isArray(resolved.oneOf);
|
|
67
|
+
|
|
68
|
+
// Priority 1: seed wins for non-object, non-oneOf types. For oneOf we still
|
|
69
|
+
// descend so that a partial seed (e.g. `{value: 5}` missing the
|
|
70
|
+
// discriminator) picks up the variant's const/default for untouched fields.
|
|
71
|
+
if (seed !== undefined && seed !== null && type !== "object" && !isOneOf) {
|
|
72
|
+
return seed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Priority 2: const (schema-invariant — always set).
|
|
76
|
+
if ("const" in resolved) {
|
|
77
|
+
return resolved.const;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Priority 3: single-value enum (same reasoning — only one valid value).
|
|
81
|
+
if (
|
|
82
|
+
Array.isArray(resolved.enum) &&
|
|
83
|
+
(resolved.enum as unknown[]).length === 1
|
|
84
|
+
) {
|
|
85
|
+
return (resolved.enum as unknown[])[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Priority 4: default (explicit author intent — always honored).
|
|
89
|
+
if ("default" in resolved) {
|
|
90
|
+
return resolved.default;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Priority 5: oneOf. Recurse into the first variant, propagating the
|
|
94
|
+
// `required` flag so that the variant's own schema-declared required fields
|
|
95
|
+
// only get typed-empty seeding when the container is required. For optional
|
|
96
|
+
// containers, the variant's const / default / single-value enum still seed
|
|
97
|
+
// (author intent is unconditional) but typed-empty placeholders do not.
|
|
98
|
+
// If recursion yields nothing forced, we collapse back to undefined.
|
|
99
|
+
if (isOneOf) {
|
|
100
|
+
const value = initOneOf(resolved, root, seed, required);
|
|
101
|
+
if (
|
|
102
|
+
!required &&
|
|
103
|
+
(value === undefined ||
|
|
104
|
+
(value !== null &&
|
|
105
|
+
typeof value === "object" &&
|
|
106
|
+
!Array.isArray(value) &&
|
|
107
|
+
Object.keys(value as Record<string, unknown>).length === 0))
|
|
108
|
+
) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Priority 6: objects recurse. The `required` flag propagates downward:
|
|
115
|
+
// inside an optional ancestor, nested required primitives also collapse,
|
|
116
|
+
// so an optional object with nested required fields stays absent instead
|
|
117
|
+
// of materializing a shell that AJV would flag.
|
|
118
|
+
if (type === "object") {
|
|
119
|
+
const obj = initObject(
|
|
120
|
+
resolved,
|
|
121
|
+
root,
|
|
122
|
+
seed as Record<string, unknown>,
|
|
123
|
+
required,
|
|
124
|
+
);
|
|
125
|
+
if (!required && Object.keys(obj).length === 0) return undefined;
|
|
126
|
+
return obj;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Priority 7: optional primitives — omit.
|
|
130
|
+
if (!required) return undefined;
|
|
131
|
+
|
|
132
|
+
// Priority 8: required primitives — legacy typed empty.
|
|
133
|
+
switch (type) {
|
|
134
|
+
case "array":
|
|
135
|
+
return seed !== undefined && seed !== null ? seed : [];
|
|
136
|
+
case "string":
|
|
137
|
+
return "";
|
|
138
|
+
case "boolean":
|
|
139
|
+
return false;
|
|
140
|
+
case "number":
|
|
141
|
+
case "integer":
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Initialize an object type by recursing into its properties.
|
|
149
|
+
* Keys whose initialization returns `undefined` are omitted.
|
|
150
|
+
*
|
|
151
|
+
* `parentRequired` controls how schema.required propagates: a child is
|
|
152
|
+
* treated as required only if both its parent is required AND it appears in
|
|
153
|
+
* the parent's required array. Inside an optional ancestor the whole
|
|
154
|
+
* subtree collapses (except for author-forced values: const, default,
|
|
155
|
+
* single-value enum, or seeded values).
|
|
156
|
+
*/
|
|
157
|
+
function initObject(
|
|
158
|
+
schema: Record<string, unknown>,
|
|
159
|
+
root: Record<string, unknown>,
|
|
160
|
+
seed: Record<string, unknown> | undefined,
|
|
161
|
+
parentRequired: boolean,
|
|
162
|
+
): Record<string, unknown> {
|
|
163
|
+
const properties = schema.properties as
|
|
164
|
+
| Record<string, Record<string, unknown>>
|
|
165
|
+
| undefined;
|
|
166
|
+
if (!properties) {
|
|
167
|
+
return seed && typeof seed === "object" ? { ...seed } : {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const requiredSet = new Set<string>(
|
|
171
|
+
Array.isArray(schema.required) ? (schema.required as string[]) : [],
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const result: Record<string, unknown> = {};
|
|
175
|
+
|
|
176
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
177
|
+
const seedValue = seed && typeof seed === "object" ? seed[key] : undefined;
|
|
178
|
+
const effectiveRequired = parentRequired && requiredSet.has(key);
|
|
179
|
+
const value = initProperty(propSchema, root, seedValue, effectiveRequired);
|
|
180
|
+
if (value !== undefined) {
|
|
181
|
+
result[key] = value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Handle oneOf schemas — pick the first variant and initialize it.
|
|
190
|
+
* `required` propagates: if the outer oneOf is required, the variant is
|
|
191
|
+
* materialized with typed-empty seeding for its required fields; if optional,
|
|
192
|
+
* only author-forced values (const / default / single-enum / seed) survive.
|
|
193
|
+
*/
|
|
194
|
+
function initOneOf(
|
|
195
|
+
schema: Record<string, unknown>,
|
|
196
|
+
root: Record<string, unknown>,
|
|
197
|
+
seed: unknown,
|
|
198
|
+
required: boolean,
|
|
199
|
+
): unknown {
|
|
200
|
+
const variants = schema.oneOf as Record<string, unknown>[];
|
|
201
|
+
if (!variants || variants.length === 0) return required ? null : undefined;
|
|
202
|
+
|
|
203
|
+
const first = variants[0];
|
|
204
|
+
if (!first) return required ? null : undefined;
|
|
205
|
+
|
|
206
|
+
const firstVariant = resolveRef(first, root);
|
|
207
|
+
return initProperty(firstVariant, root, seed, required);
|
|
208
|
+
}
|