@narrative.io/jsonforms-provider-protocols 3.0.0-beta.1 → 3.0.0-beta.11
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/dist/core/initFormData.d.ts +10 -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.map +1 -1
- package/dist/core/projection.js.map +1 -1
- package/dist/core/transforms.d.ts.map +1 -1
- package/dist/core/transforms.js +3 -1
- package/dist/core/transforms.js.map +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jsonforms-provider-protocols.css +2 -2
- package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderAutocomplete.vue.js +8 -5
- 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 +9 -6
- package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
- 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 +11 -6
- package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
- package/dist/vue/composables/useDataLayer.d.ts +1 -0
- package/dist/vue/composables/useDataLayer.d.ts.map +1 -1
- package/dist/vue/composables/useDataLayer.js +1 -0
- package/dist/vue/composables/useDataLayer.js.map +1 -1
- package/dist/vue/composables/useDerive.d.ts +1 -1
- package/dist/vue/composables/useDerive.d.ts.map +1 -1
- package/dist/vue/composables/useDerive.js +19 -2
- 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 +6 -0
- package/dist/vue/composables/useProjection.d.ts.map +1 -1
- package/dist/vue/composables/useProjection.js +54 -3
- package/dist/vue/composables/useProjection.js.map +1 -1
- 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 +3 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +32 -10
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.js +26 -9
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.js +21 -17
- package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.js +22 -12
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.js +21 -17
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfText.vue.js +29 -26
- package/dist/vue/primevue/JfText.vue.js.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.js +22 -13
- 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 +93 -16
- package/dist/vue/primevue/index.js.map +1 -1
- package/dist/vue/utils/autoSelect.js.map +1 -1
- package/package.json +3 -1
- package/src/core/initFormData.ts +189 -0
- package/src/core/projection.ts +5 -5
- package/src/core/transforms.ts +33 -6
- package/src/core/types.ts +1 -0
- package/src/index.ts +1 -0
- package/src/vue/components/ProviderAutocomplete.vue +8 -5
- package/src/vue/components/ProviderMultiSelect.vue +13 -8
- package/src/vue/components/ProviderSelect.vue +14 -7
- package/src/vue/composables/useDataLayer.ts +1 -1
- package/src/vue/composables/useDerive.ts +46 -3
- package/src/vue/composables/useDeriveInitialValue.ts +195 -0
- package/src/vue/composables/useDirtyValidation.ts +20 -0
- package/src/vue/composables/useProjection.ts +108 -1
- package/src/vue/composables/useProvider.ts +28 -11
- package/src/vue/index.ts +29 -9
- package/src/vue/primevue/JfBoolean.vue +19 -6
- package/src/vue/primevue/JfEnum.vue +23 -21
- package/src/vue/primevue/JfEnumArray.vue +25 -15
- package/src/vue/primevue/JfNumber.vue +22 -20
- package/src/vue/primevue/JfText.vue +26 -24
- package/src/vue/primevue/JfTextArea.vue +22 -15
- package/src/vue/primevue/index.ts +104 -23
- package/src/vue/styles.css +26 -1
- package/src/vue/utils/autoSelect.ts +2 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialize a form data object from a JSON Schema.
|
|
3
|
+
* Resolves $ref, const, default, oneOf/discriminator, and typed empty values.
|
|
4
|
+
*
|
|
5
|
+
* @param schema - The full JSON Schema (must include $defs if $refs are used)
|
|
6
|
+
* @param seed - Optional existing data to merge (seed values take priority)
|
|
7
|
+
* @returns A data object with all schema-defined fields initialized
|
|
8
|
+
*/
|
|
9
|
+
export function initFormDataFromSchema(
|
|
10
|
+
schema: Record<string, unknown>,
|
|
11
|
+
seed?: Record<string, unknown>,
|
|
12
|
+
): Record<string, unknown> {
|
|
13
|
+
const result = initProperty(schema, schema, seed) as Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
// If result is an object and seed has extra keys not in schema, preserve them
|
|
16
|
+
if (
|
|
17
|
+
result &&
|
|
18
|
+
typeof result === "object" &&
|
|
19
|
+
!Array.isArray(result) &&
|
|
20
|
+
seed &&
|
|
21
|
+
typeof seed === "object"
|
|
22
|
+
) {
|
|
23
|
+
const schemaKeys = new Set(
|
|
24
|
+
Object.keys(
|
|
25
|
+
(resolveRef(schema, schema) as Record<string, unknown>)?.properties ??
|
|
26
|
+
{},
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
for (const key of Object.keys(seed)) {
|
|
30
|
+
if (!schemaKeys.has(key) && !(key in result)) {
|
|
31
|
+
result[key] = seed[key];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result ?? {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a $ref pointer against the root schema's $defs.
|
|
41
|
+
* Supports nested $ref chains.
|
|
42
|
+
*/
|
|
43
|
+
function resolveRef(
|
|
44
|
+
property: Record<string, unknown>,
|
|
45
|
+
root: Record<string, unknown>,
|
|
46
|
+
seen?: Set<string>,
|
|
47
|
+
): Record<string, unknown> {
|
|
48
|
+
if (!property || typeof property !== "object") return property;
|
|
49
|
+
|
|
50
|
+
const ref = property.$ref as string | undefined;
|
|
51
|
+
if (!ref) return property;
|
|
52
|
+
|
|
53
|
+
// Guard against circular refs
|
|
54
|
+
const visited = seen ?? new Set<string>();
|
|
55
|
+
if (visited.has(ref)) return property;
|
|
56
|
+
visited.add(ref);
|
|
57
|
+
|
|
58
|
+
const resolved = resolvePointer(root, ref);
|
|
59
|
+
if (!resolved) return property;
|
|
60
|
+
|
|
61
|
+
// Continue resolving if the result itself has a $ref
|
|
62
|
+
return resolveRef(resolved as Record<string, unknown>, root, visited);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a JSON pointer like "#/$defs/Price" against an object.
|
|
67
|
+
*/
|
|
68
|
+
function resolvePointer(
|
|
69
|
+
obj: Record<string, unknown>,
|
|
70
|
+
pointer: string,
|
|
71
|
+
): unknown {
|
|
72
|
+
if (!pointer.startsWith("#/")) return undefined;
|
|
73
|
+
const parts = pointer.slice(2).split("/");
|
|
74
|
+
let current: unknown = obj;
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (current && typeof current === "object" && part in current) {
|
|
77
|
+
current = (current as Record<string, unknown>)[part];
|
|
78
|
+
} else {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return current;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Initialize a single property value based on its schema definition.
|
|
87
|
+
*/
|
|
88
|
+
function initProperty(
|
|
89
|
+
property: Record<string, unknown>,
|
|
90
|
+
root: Record<string, unknown>,
|
|
91
|
+
seed?: unknown,
|
|
92
|
+
): unknown {
|
|
93
|
+
if (!property || typeof property !== "object") return null;
|
|
94
|
+
|
|
95
|
+
// Resolve $ref first
|
|
96
|
+
const resolved = resolveRef(property, root);
|
|
97
|
+
|
|
98
|
+
// Handle oneOf with discriminator — pick first variant
|
|
99
|
+
if (Array.isArray(resolved.oneOf)) {
|
|
100
|
+
return initOneOf(resolved, root, seed);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Priority 1: seed wins (for object types, we merge recursively below)
|
|
104
|
+
// For non-object types, return seed directly if present
|
|
105
|
+
const type = resolved.type as string | undefined;
|
|
106
|
+
if (seed !== undefined && seed !== null && type !== "object") {
|
|
107
|
+
return seed;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Priority 2: const
|
|
111
|
+
if ("const" in resolved) {
|
|
112
|
+
return resolved.const;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Priority 3: single-value enum
|
|
116
|
+
if (
|
|
117
|
+
Array.isArray(resolved.enum) &&
|
|
118
|
+
(resolved.enum as unknown[]).length === 1
|
|
119
|
+
) {
|
|
120
|
+
return (resolved.enum as unknown[])[0];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Priority 4: default
|
|
124
|
+
if ("default" in resolved) {
|
|
125
|
+
return resolved.default;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Priority 5: typed empty values
|
|
129
|
+
switch (type) {
|
|
130
|
+
case "object":
|
|
131
|
+
return initObject(resolved, root, seed as Record<string, unknown>);
|
|
132
|
+
case "array":
|
|
133
|
+
return seed !== undefined && seed !== null ? seed : [];
|
|
134
|
+
case "string":
|
|
135
|
+
return "";
|
|
136
|
+
case "boolean":
|
|
137
|
+
return false;
|
|
138
|
+
case "number":
|
|
139
|
+
case "integer":
|
|
140
|
+
return null;
|
|
141
|
+
default:
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Initialize an object type by recursing into its properties.
|
|
148
|
+
*/
|
|
149
|
+
function initObject(
|
|
150
|
+
schema: Record<string, unknown>,
|
|
151
|
+
root: Record<string, unknown>,
|
|
152
|
+
seed?: Record<string, unknown>,
|
|
153
|
+
): Record<string, unknown> {
|
|
154
|
+
const properties = schema.properties as
|
|
155
|
+
| Record<string, Record<string, unknown>>
|
|
156
|
+
| undefined;
|
|
157
|
+
if (!properties) {
|
|
158
|
+
// Object with no defined properties — return seed or empty object
|
|
159
|
+
return seed && typeof seed === "object" ? { ...seed } : {};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result: Record<string, unknown> = {};
|
|
163
|
+
|
|
164
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
165
|
+
const seedValue = seed && typeof seed === "object" ? seed[key] : undefined;
|
|
166
|
+
result[key] = initProperty(propSchema, root, seedValue);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Handle oneOf schemas — pick the first variant and initialize it.
|
|
174
|
+
*/
|
|
175
|
+
function initOneOf(
|
|
176
|
+
schema: Record<string, unknown>,
|
|
177
|
+
root: Record<string, unknown>,
|
|
178
|
+
seed?: unknown,
|
|
179
|
+
): unknown {
|
|
180
|
+
const variants = schema.oneOf as Record<string, unknown>[];
|
|
181
|
+
if (!variants || variants.length === 0) return null;
|
|
182
|
+
|
|
183
|
+
const first = variants[0];
|
|
184
|
+
if (!first) return null;
|
|
185
|
+
|
|
186
|
+
// Pick first variant, resolve its $ref if needed
|
|
187
|
+
const firstVariant = resolveRef(first, root);
|
|
188
|
+
return initProperty(firstVariant, root, seed);
|
|
189
|
+
}
|
package/src/core/projection.ts
CHANGED
|
@@ -26,10 +26,7 @@ export function parseProjectionPath(path: string): ProjectionSegment[] {
|
|
|
26
26
|
* Read a value from `data` by following the projection path.
|
|
27
27
|
* Returns `undefined` if any segment along the path is missing.
|
|
28
28
|
*/
|
|
29
|
-
export function getProjectedValue(
|
|
30
|
-
data: unknown,
|
|
31
|
-
path: string,
|
|
32
|
-
): unknown {
|
|
29
|
+
export function getProjectedValue(data: unknown, path: string): unknown {
|
|
33
30
|
const segments = parseProjectionPath(path);
|
|
34
31
|
let current: unknown = data;
|
|
35
32
|
|
|
@@ -85,7 +82,10 @@ function setAtPath(
|
|
|
85
82
|
} else {
|
|
86
83
|
// Object key — ensure we have an object
|
|
87
84
|
const obj: Record<string, unknown> =
|
|
88
|
-
current !== null &&
|
|
85
|
+
current !== null &&
|
|
86
|
+
current !== undefined &&
|
|
87
|
+
typeof current === "object" &&
|
|
88
|
+
!Array.isArray(current)
|
|
89
89
|
? { ...(current as Record<string, unknown>) }
|
|
90
90
|
: {};
|
|
91
91
|
obj[seg] = setAtPath(obj[seg], segments, index + 1, value);
|
package/src/core/transforms.ts
CHANGED
|
@@ -14,7 +14,16 @@ export interface FlattenTransform extends Transform {
|
|
|
14
14
|
labelFormat?: string; // Optional format string like "{parent.name} → {name}"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export type FilterOperator =
|
|
17
|
+
export type FilterOperator =
|
|
18
|
+
| "eq"
|
|
19
|
+
| "neq"
|
|
20
|
+
| "empty"
|
|
21
|
+
| "notEmpty"
|
|
22
|
+
| "gt"
|
|
23
|
+
| "gte"
|
|
24
|
+
| "lt"
|
|
25
|
+
| "lte"
|
|
26
|
+
| "contains";
|
|
18
27
|
|
|
19
28
|
export interface FilterCondition {
|
|
20
29
|
key: string;
|
|
@@ -153,13 +162,29 @@ function evaluateCondition(
|
|
|
153
162
|
case "notEmpty":
|
|
154
163
|
return !isEmpty(value);
|
|
155
164
|
case "gt":
|
|
156
|
-
return
|
|
165
|
+
return (
|
|
166
|
+
typeof value === "number" &&
|
|
167
|
+
condition.values !== undefined &&
|
|
168
|
+
value > (condition.values[0] as number)
|
|
169
|
+
);
|
|
157
170
|
case "gte":
|
|
158
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
typeof value === "number" &&
|
|
173
|
+
condition.values !== undefined &&
|
|
174
|
+
value >= (condition.values[0] as number)
|
|
175
|
+
);
|
|
159
176
|
case "lt":
|
|
160
|
-
return
|
|
177
|
+
return (
|
|
178
|
+
typeof value === "number" &&
|
|
179
|
+
condition.values !== undefined &&
|
|
180
|
+
value < (condition.values[0] as number)
|
|
181
|
+
);
|
|
161
182
|
case "lte":
|
|
162
|
-
return
|
|
183
|
+
return (
|
|
184
|
+
typeof value === "number" &&
|
|
185
|
+
condition.values !== undefined &&
|
|
186
|
+
value <= (condition.values[0] as number)
|
|
187
|
+
);
|
|
163
188
|
case "contains":
|
|
164
189
|
if (typeof value === "string" && condition.values) {
|
|
165
190
|
return condition.values.some((v) => value.includes(String(v)));
|
|
@@ -208,7 +233,9 @@ function filterTransform(items: unknown[], config: Transform): unknown[] {
|
|
|
208
233
|
return items.filter((item) => {
|
|
209
234
|
if (typeof item !== "object" || item === null) return false;
|
|
210
235
|
const itemObj = item as Record<string, unknown>;
|
|
211
|
-
return conditions.every((condition) =>
|
|
236
|
+
return conditions.every((condition) =>
|
|
237
|
+
evaluateCondition(itemObj, condition),
|
|
238
|
+
);
|
|
212
239
|
});
|
|
213
240
|
}
|
|
214
241
|
|
package/src/core/types.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./core/projection";
|
|
|
14
14
|
export * from "./core/resolveScope";
|
|
15
15
|
// Core exports
|
|
16
16
|
export * from "./core/types";
|
|
17
|
+
export { initFormDataFromSchema } from "./core/initFormData";
|
|
17
18
|
|
|
18
19
|
// Protocol exports
|
|
19
20
|
export { RestApiProtocol } from "./protocols/rest_api";
|
|
@@ -12,7 +12,10 @@ const props = defineProps<{
|
|
|
12
12
|
path: string;
|
|
13
13
|
}>();
|
|
14
14
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
15
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
15
|
+
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
16
|
+
control,
|
|
17
|
+
rawHandleChange,
|
|
18
|
+
);
|
|
16
19
|
|
|
17
20
|
const binding = computed(() => {
|
|
18
21
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -53,16 +56,16 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
|
|
|
53
56
|
</script>
|
|
54
57
|
|
|
55
58
|
<template>
|
|
56
|
-
<div class="
|
|
57
|
-
<label v-if="control.schema.title" class="
|
|
59
|
+
<div class="jf-control">
|
|
60
|
+
<label v-if="control.schema.title" class="jf-label">{{
|
|
58
61
|
control.schema.title
|
|
59
62
|
}}</label>
|
|
60
|
-
<div v-if="control.description" class="
|
|
63
|
+
<div v-if="control.description" class="jf-description">
|
|
61
64
|
{{ control.description }}
|
|
62
65
|
</div>
|
|
63
66
|
<AutoComplete
|
|
64
67
|
v-model="value"
|
|
65
|
-
class="w-full"
|
|
68
|
+
class="w-full!"
|
|
66
69
|
:suggestions="items"
|
|
67
70
|
option-label="label"
|
|
68
71
|
:placeholder="placeholder"
|
|
@@ -13,7 +13,10 @@ const props = defineProps<{
|
|
|
13
13
|
path: string;
|
|
14
14
|
}>();
|
|
15
15
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
16
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
16
|
+
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
17
|
+
control,
|
|
18
|
+
rawHandleChange,
|
|
19
|
+
);
|
|
17
20
|
|
|
18
21
|
const binding = computed(() => {
|
|
19
22
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -41,7 +44,7 @@ const rootData = computed(() => injectedFormData.value || {});
|
|
|
41
44
|
const { items, loading, error } = useProvider(binding, {
|
|
42
45
|
data: rootData, // Pass the reactive reference
|
|
43
46
|
path: control.value.path,
|
|
44
|
-
dependsOnValues: depValues
|
|
47
|
+
dependsOnValues: depValues,
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
// Provider will automatically reload when rootData changes due to reactive cache key
|
|
@@ -55,14 +58,16 @@ watch(
|
|
|
55
58
|
control.value.uischema?.options?.autoSelectSingle === true,
|
|
56
59
|
isLoading,
|
|
57
60
|
items: newItems,
|
|
58
|
-
currentValue: Array.isArray(projectedData.value)
|
|
61
|
+
currentValue: Array.isArray(projectedData.value)
|
|
62
|
+
? projectedData.value
|
|
63
|
+
: [],
|
|
59
64
|
});
|
|
60
65
|
|
|
61
66
|
if (valueToSelect !== null) {
|
|
62
67
|
handleChange(control.value.path, valueToSelect);
|
|
63
68
|
}
|
|
64
69
|
},
|
|
65
|
-
{ immediate: true }
|
|
70
|
+
{ immediate: true },
|
|
66
71
|
);
|
|
67
72
|
|
|
68
73
|
// order-insensitive shallow equality for primitive arrays
|
|
@@ -98,16 +103,16 @@ const placeholder = computed(() => {
|
|
|
98
103
|
</script>
|
|
99
104
|
|
|
100
105
|
<template>
|
|
101
|
-
<div class="
|
|
102
|
-
<label v-if="control.schema.title" class="
|
|
106
|
+
<div class="jf-control">
|
|
107
|
+
<label v-if="control.schema.title" class="jf-label">{{
|
|
103
108
|
control.schema.title
|
|
104
109
|
}}</label>
|
|
105
|
-
<div v-if="control.description" class="
|
|
110
|
+
<div v-if="control.description" class="jf-description">
|
|
106
111
|
{{ control.description }}
|
|
107
112
|
</div>
|
|
108
113
|
<MultiSelect
|
|
109
114
|
v-model="value"
|
|
110
|
-
class="w-full"
|
|
115
|
+
class="w-full!"
|
|
111
116
|
:options="items"
|
|
112
117
|
option-label="label"
|
|
113
118
|
option-value="value"
|
|
@@ -3,6 +3,7 @@ import type { ControlElement, JsonSchema } from "@jsonforms/core";
|
|
|
3
3
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
4
4
|
import { computed, inject, watch } from "vue";
|
|
5
5
|
import { useProvider } from "../composables/useProvider";
|
|
6
|
+
import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
|
|
6
7
|
import { useProjection } from "../composables/useProjection";
|
|
7
8
|
import { shouldAutoSelect } from "../utils/autoSelect";
|
|
8
9
|
import Dropdown from "primevue/dropdown";
|
|
@@ -13,7 +14,10 @@ const props = defineProps<{
|
|
|
13
14
|
path: string;
|
|
14
15
|
}>();
|
|
15
16
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
16
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
17
|
+
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
18
|
+
control,
|
|
19
|
+
rawHandleChange,
|
|
20
|
+
);
|
|
17
21
|
|
|
18
22
|
const binding = computed(() => {
|
|
19
23
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -41,11 +45,14 @@ const rootData = computed(() => injectedFormData.value || {});
|
|
|
41
45
|
const { items, loading, error } = useProvider(binding, {
|
|
42
46
|
data: rootData, // Pass the reactive reference
|
|
43
47
|
path: control.value.path,
|
|
44
|
-
dependsOnValues: depValues
|
|
48
|
+
dependsOnValues: depValues,
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
// Provider will automatically reload when rootData changes due to reactive cache key
|
|
48
52
|
|
|
53
|
+
// deriveInitialValue — async API-based initial value seeding
|
|
54
|
+
useDeriveInitialValue({ control, handleChange });
|
|
55
|
+
|
|
49
56
|
// Auto-select when provider returns only one item (enabled by default)
|
|
50
57
|
watch(
|
|
51
58
|
[items, loading],
|
|
@@ -62,7 +69,7 @@ watch(
|
|
|
62
69
|
handleChange(control.value.path, valueToSelect);
|
|
63
70
|
}
|
|
64
71
|
},
|
|
65
|
-
{ immediate: true }
|
|
72
|
+
{ immediate: true },
|
|
66
73
|
);
|
|
67
74
|
|
|
68
75
|
const value = computed({
|
|
@@ -81,16 +88,16 @@ const placeholder = computed(() => {
|
|
|
81
88
|
</script>
|
|
82
89
|
|
|
83
90
|
<template>
|
|
84
|
-
<div class="
|
|
85
|
-
<label v-if="control.schema.title" class="
|
|
91
|
+
<div class="jf-control">
|
|
92
|
+
<label v-if="control.schema.title" class="jf-label">{{
|
|
86
93
|
control.schema.title
|
|
87
94
|
}}</label>
|
|
88
|
-
<div v-if="control.description" class="
|
|
95
|
+
<div v-if="control.description" class="jf-description">
|
|
89
96
|
{{ control.description }}
|
|
90
97
|
</div>
|
|
91
98
|
<Dropdown
|
|
92
99
|
v-model="value"
|
|
93
|
-
class="w-full"
|
|
100
|
+
class="w-full!"
|
|
94
101
|
:options="items"
|
|
95
102
|
option-label="label"
|
|
96
103
|
option-value="value"
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "vue";
|
|
9
9
|
import type { ConnectorDataLayer } from "../../core/types";
|
|
10
10
|
|
|
11
|
-
const DATA_LAYER_KEY = Symbol("dataLayer");
|
|
11
|
+
export const DATA_LAYER_KEY = Symbol("dataLayer");
|
|
12
12
|
|
|
13
13
|
export interface DataLayer {
|
|
14
14
|
push(data: Partial<ConnectorDataLayer>): void;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
ref,
|
|
4
|
+
watch,
|
|
5
|
+
unref,
|
|
6
|
+
inject,
|
|
7
|
+
type Ref,
|
|
8
|
+
type ComputedRef,
|
|
9
|
+
} from "vue";
|
|
2
10
|
import { type ControlElement } from "@jsonforms/core";
|
|
3
11
|
import { useDataLayer } from "./useDataLayer";
|
|
4
12
|
|
|
@@ -14,7 +22,11 @@ interface DeriveOptions {
|
|
|
14
22
|
data?: Ref<unknown> | ComputedRef<unknown>;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
export function useDerive({
|
|
25
|
+
export function useDerive({
|
|
26
|
+
control,
|
|
27
|
+
handleChange,
|
|
28
|
+
data: dataOverride,
|
|
29
|
+
}: DeriveOptions) {
|
|
18
30
|
// Get the root form data from JSONForms context
|
|
19
31
|
const injectedFormData = inject<{ value: unknown }>("formData", {
|
|
20
32
|
value: {},
|
|
@@ -36,6 +48,14 @@ export function useDerive({ control, handleChange, data: dataOverride }: DeriveO
|
|
|
36
48
|
};
|
|
37
49
|
});
|
|
38
50
|
|
|
51
|
+
// Track the last resolved source value so we only update the field
|
|
52
|
+
// when the source itself changes, not on every form data mutation.
|
|
53
|
+
const lastDerivedValue = ref<unknown>(undefined);
|
|
54
|
+
// First watch fire is special: a pre-populated field (edit flow seeded
|
|
55
|
+
// from a saved connection) must not be clobbered by whatever the source
|
|
56
|
+
// resolves to on mount. We record the source value but skip handleChange.
|
|
57
|
+
let isFirstRun = true;
|
|
58
|
+
|
|
39
59
|
// Watch for changes in form data and dataLayer and update derived field
|
|
40
60
|
watch(
|
|
41
61
|
[rootData, dataLayerData, deriveConfig],
|
|
@@ -50,7 +70,30 @@ export function useDerive({ control, handleChange, data: dataOverride }: DeriveO
|
|
|
50
70
|
data,
|
|
51
71
|
extData,
|
|
52
72
|
);
|
|
53
|
-
const compareData = dataOverride
|
|
73
|
+
const compareData = dataOverride
|
|
74
|
+
? unref(dataOverride)
|
|
75
|
+
: control.value.data;
|
|
76
|
+
|
|
77
|
+
if (isFirstRun) {
|
|
78
|
+
isFirstRun = false;
|
|
79
|
+
lastDerivedValue.value = derivedValue;
|
|
80
|
+
// On mount, only populate the field if it's empty.
|
|
81
|
+
// A non-empty seed represents user intent (edit flow) and must
|
|
82
|
+
// be preserved. Subsequent source changes will still propagate.
|
|
83
|
+
const isFieldEmpty =
|
|
84
|
+
compareData === undefined ||
|
|
85
|
+
compareData === null ||
|
|
86
|
+
compareData === "";
|
|
87
|
+
if (isFieldEmpty && derivedValue !== compareData) {
|
|
88
|
+
handleChange(control.value.path, derivedValue);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Only update if the SOURCE value changed, not just the field value
|
|
94
|
+
if (derivedValue === lastDerivedValue.value) return;
|
|
95
|
+
lastDerivedValue.value = derivedValue;
|
|
96
|
+
|
|
54
97
|
if (derivedValue !== compareData) {
|
|
55
98
|
handleChange(control.value.path, derivedValue);
|
|
56
99
|
}
|