@narrative.io/jsonforms-provider-protocols 2.11.0 → 3.0.0-beta.10
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 +101 -29
- 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 +32 -0
- package/dist/core/projection.d.ts.map +1 -0
- package/dist/core/projection.js +74 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/resolveScope.d.ts +11 -0
- package/dist/core/resolveScope.d.ts.map +1 -0
- package/dist/core/resolveScope.js +22 -0
- package/dist/core/resolveScope.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 +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- 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 +10 -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 +12 -7
- 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 +13 -6
- 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 +41 -0
- package/dist/vue/composables/useProjection.d.ts.map +1 -0
- package/dist/vue/composables/useProjection.js +84 -0
- package/dist/vue/composables/useProjection.js.map +1 -0
- package/dist/vue/index.d.ts +7 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +35 -27
- 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 +35 -13
- 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 +31 -22
- 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 +33 -18
- 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 +31 -22
- 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 +40 -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 +32 -18
- 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 +100 -8
- package/dist/vue/primevue/index.js.map +1 -1
- package/package.json +3 -1
- package/src/core/initFormData.ts +189 -0
- package/src/core/projection.ts +136 -0
- package/src/core/resolveScope.ts +39 -0
- package/src/core/transforms.ts +118 -26
- package/src/core/types.ts +9 -0
- package/src/index.ts +7 -1
- package/src/vue/components/ProviderAutocomplete.vue +10 -5
- package/src/vue/components/ProviderMultiSelect.vue +14 -7
- package/src/vue/components/ProviderSelect.vue +15 -6
- 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 +181 -0
- package/src/vue/index.ts +35 -41
- package/src/vue/primevue/JfBoolean.vue +25 -7
- package/src/vue/primevue/JfEnum.vue +29 -22
- package/src/vue/primevue/JfEnumArray.vue +31 -16
- package/src/vue/primevue/JfNumber.vue +29 -22
- package/src/vue/primevue/JfText.vue +34 -27
- package/src/vue/primevue/JfTextArea.vue +29 -17
- package/src/vue/primevue/index.ts +114 -8
- package/src/vue/styles.css +26 -1
package/src/core/transforms.ts
CHANGED
|
@@ -14,30 +14,38 @@ export interface FlattenTransform extends Transform {
|
|
|
14
14
|
labelFormat?: string; // Optional format string like "{parent.name} → {name}"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export type FilterOperator =
|
|
18
|
+
| "eq"
|
|
19
|
+
| "neq"
|
|
20
|
+
| "empty"
|
|
21
|
+
| "notEmpty"
|
|
22
|
+
| "gt"
|
|
23
|
+
| "gte"
|
|
24
|
+
| "lt"
|
|
25
|
+
| "lte"
|
|
26
|
+
| "contains";
|
|
27
|
+
|
|
28
|
+
export interface FilterCondition {
|
|
29
|
+
key: string;
|
|
30
|
+
operator?: FilterOperator; // Defaults to "eq" when values is provided, "exists" behavior when neither
|
|
31
|
+
values?: unknown[];
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
export interface FilterTransform extends Transform {
|
|
18
35
|
name: "filter";
|
|
19
|
-
key
|
|
20
|
-
values?: unknown[]; //
|
|
36
|
+
key?: string; // Legacy: single key to check
|
|
37
|
+
values?: unknown[]; // Legacy: single values array
|
|
38
|
+
conditions?: FilterCondition[]; // Multi-condition filter (AND logic)
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
export type TransformStep = FlattenTransform | FilterTransform;
|
|
24
42
|
|
|
25
43
|
export type TransformPipeline = TransformStep[];
|
|
26
44
|
|
|
27
|
-
/**
|
|
28
|
-
* Registry of transform functions
|
|
29
|
-
*/
|
|
30
45
|
type TransformFunction = (items: unknown[], config: Transform) => unknown[];
|
|
31
46
|
|
|
32
47
|
const transformRegistry: Record<string, TransformFunction> = {};
|
|
33
48
|
|
|
34
|
-
/**
|
|
35
|
-
* Register a transform function
|
|
36
|
-
*/
|
|
37
|
-
export function registerTransform(name: string, fn: TransformFunction): void {
|
|
38
|
-
transformRegistry[name] = fn;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
49
|
/**
|
|
42
50
|
* Apply a pipeline of transforms to data
|
|
43
51
|
*/
|
|
@@ -124,29 +132,113 @@ function flattenTransform(items: unknown[], config: Transform): unknown[] {
|
|
|
124
132
|
return flattened;
|
|
125
133
|
}
|
|
126
134
|
|
|
135
|
+
function isEmpty(value: unknown): boolean {
|
|
136
|
+
if (value === null || value === undefined) return true;
|
|
137
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
138
|
+
if (typeof value === "string") return value.length === 0;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function evaluateCondition(
|
|
143
|
+
itemObj: Record<string, unknown>,
|
|
144
|
+
condition: FilterCondition,
|
|
145
|
+
): boolean {
|
|
146
|
+
const value = itemObj[condition.key];
|
|
147
|
+
const operator = condition.operator ?? (condition.values ? "eq" : "eq");
|
|
148
|
+
|
|
149
|
+
switch (operator) {
|
|
150
|
+
case "eq":
|
|
151
|
+
if (!condition.values || condition.values.length === 0) {
|
|
152
|
+
return condition.key in itemObj;
|
|
153
|
+
}
|
|
154
|
+
return condition.values.includes(value);
|
|
155
|
+
case "neq":
|
|
156
|
+
if (!condition.values || condition.values.length === 0) {
|
|
157
|
+
return !(condition.key in itemObj);
|
|
158
|
+
}
|
|
159
|
+
return !condition.values.includes(value);
|
|
160
|
+
case "empty":
|
|
161
|
+
return isEmpty(value);
|
|
162
|
+
case "notEmpty":
|
|
163
|
+
return !isEmpty(value);
|
|
164
|
+
case "gt":
|
|
165
|
+
return (
|
|
166
|
+
typeof value === "number" &&
|
|
167
|
+
condition.values !== undefined &&
|
|
168
|
+
value > (condition.values[0] as number)
|
|
169
|
+
);
|
|
170
|
+
case "gte":
|
|
171
|
+
return (
|
|
172
|
+
typeof value === "number" &&
|
|
173
|
+
condition.values !== undefined &&
|
|
174
|
+
value >= (condition.values[0] as number)
|
|
175
|
+
);
|
|
176
|
+
case "lt":
|
|
177
|
+
return (
|
|
178
|
+
typeof value === "number" &&
|
|
179
|
+
condition.values !== undefined &&
|
|
180
|
+
value < (condition.values[0] as number)
|
|
181
|
+
);
|
|
182
|
+
case "lte":
|
|
183
|
+
return (
|
|
184
|
+
typeof value === "number" &&
|
|
185
|
+
condition.values !== undefined &&
|
|
186
|
+
value <= (condition.values[0] as number)
|
|
187
|
+
);
|
|
188
|
+
case "contains":
|
|
189
|
+
if (typeof value === "string" && condition.values) {
|
|
190
|
+
return condition.values.some((v) => value.includes(String(v)));
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(value) && condition.values) {
|
|
193
|
+
return condition.values.some((v) => value.includes(v));
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
default:
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
127
201
|
/**
|
|
128
|
-
* Filter transform - filters items based on
|
|
202
|
+
* Filter transform - filters items based on conditions
|
|
203
|
+
*
|
|
204
|
+
* Supports legacy single key/values syntax and new multi-condition syntax.
|
|
205
|
+
* When using conditions, all conditions must match (AND logic).
|
|
206
|
+
*
|
|
207
|
+
* Operators:
|
|
208
|
+
* eq - item[key] matches one of values (default)
|
|
209
|
+
* neq - item[key] does NOT match any of values
|
|
210
|
+
* empty - item[key] is null, undefined, empty array, or empty string
|
|
211
|
+
* notEmpty - inverse of empty
|
|
212
|
+
* gt - item[key] > values[0]
|
|
213
|
+
* gte - item[key] >= values[0]
|
|
214
|
+
* lt - item[key] < values[0]
|
|
215
|
+
* lte - item[key] <= values[0]
|
|
216
|
+
* contains - string includes substring, or array includes value
|
|
129
217
|
*/
|
|
130
218
|
function filterTransform(items: unknown[], config: Transform): unknown[] {
|
|
131
219
|
const filterConfig = config as FilterTransform;
|
|
132
|
-
|
|
220
|
+
|
|
221
|
+
// Build conditions array from either new or legacy syntax
|
|
222
|
+
let conditions: FilterCondition[];
|
|
223
|
+
|
|
224
|
+
if (filterConfig.conditions) {
|
|
225
|
+
conditions = filterConfig.conditions;
|
|
226
|
+
} else if (filterConfig.key) {
|
|
227
|
+
// Legacy single key/values syntax
|
|
228
|
+
conditions = [{ key: filterConfig.key, values: filterConfig.values }];
|
|
229
|
+
} else {
|
|
230
|
+
return items;
|
|
231
|
+
}
|
|
133
232
|
|
|
134
233
|
return items.filter((item) => {
|
|
135
234
|
if (typeof item !== "object" || item === null) return false;
|
|
136
|
-
|
|
137
235
|
const itemObj = item as Record<string, unknown>;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return key in itemObj;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// If values array provided, check if item[key] matches any of the values
|
|
145
|
-
const itemValue = itemObj[key];
|
|
146
|
-
return values.includes(itemValue);
|
|
236
|
+
return conditions.every((condition) =>
|
|
237
|
+
evaluateCondition(itemObj, condition),
|
|
238
|
+
);
|
|
147
239
|
});
|
|
148
240
|
}
|
|
149
241
|
|
|
150
242
|
// Register built-in transforms
|
|
151
|
-
|
|
152
|
-
|
|
243
|
+
transformRegistry["flatten"] = flattenTransform;
|
|
244
|
+
transformRegistry["filter"] = filterTransform;
|
package/src/core/types.ts
CHANGED
|
@@ -44,3 +44,12 @@ export interface AuthConfig {
|
|
|
44
44
|
token?: string | (() => string); // Generic token auth
|
|
45
45
|
[key: string]: unknown; // Custom auth fields
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
export interface ConnectorDataLayer {
|
|
49
|
+
dataset_name?: string;
|
|
50
|
+
dataset_description?: string;
|
|
51
|
+
dataset_id?: number;
|
|
52
|
+
profile_id?: string;
|
|
53
|
+
profile_name?: string;
|
|
54
|
+
datasetWriteMode?: "append" | "overwrite";
|
|
55
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,15 +3,18 @@ import { cache as globalCache } from "./core/cache";
|
|
|
3
3
|
import { registry as globalRegistry } from "./core/registry";
|
|
4
4
|
import type { Protocol, AuthConfig } from "./core/types";
|
|
5
5
|
|
|
6
|
-
console.log("[jsonforms-provider-protocols] loaded v2.10.0");
|
|
6
|
+
console.log("[jsonforms-provider-protocols] loaded v2.10.0 (symlink)");
|
|
7
7
|
|
|
8
8
|
export { cache } from "./core/cache";
|
|
9
9
|
export * from "./core/jsonpath";
|
|
10
10
|
export { registry } from "./core/registry";
|
|
11
11
|
export * from "./core/templating";
|
|
12
12
|
export * from "./core/transforms";
|
|
13
|
+
export * from "./core/projection";
|
|
14
|
+
export * from "./core/resolveScope";
|
|
13
15
|
// Core exports
|
|
14
16
|
export * from "./core/types";
|
|
17
|
+
export { initFormDataFromSchema } from "./core/initFormData";
|
|
15
18
|
|
|
16
19
|
// Protocol exports
|
|
17
20
|
export { RestApiProtocol } from "./protocols/rest_api";
|
|
@@ -24,6 +27,8 @@ export {
|
|
|
24
27
|
ProviderSelect,
|
|
25
28
|
ProviderMultiSelect,
|
|
26
29
|
useProvider,
|
|
30
|
+
createDataLayer,
|
|
31
|
+
useDataLayer,
|
|
27
32
|
JfText,
|
|
28
33
|
JfTextArea,
|
|
29
34
|
JfNumber,
|
|
@@ -31,6 +36,7 @@ export {
|
|
|
31
36
|
JfEnumArray,
|
|
32
37
|
JfBoolean,
|
|
33
38
|
} from "./vue";
|
|
39
|
+
export type { DataLayer } from "./vue";
|
|
34
40
|
|
|
35
41
|
export interface ProviderConfig {
|
|
36
42
|
protocols?: Protocol[];
|
|
@@ -3,6 +3,7 @@ import type { ControlElement, JsonSchema } from "@jsonforms/core";
|
|
|
3
3
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
4
4
|
import { computed, ref, watch } from "vue";
|
|
5
5
|
import { useProvider } from "../composables/useProvider";
|
|
6
|
+
import { useProjection } from "../composables/useProjection";
|
|
6
7
|
import AutoComplete from "primevue/autocomplete";
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{
|
|
@@ -10,7 +11,11 @@ const props = defineProps<{
|
|
|
10
11
|
schema: JsonSchema;
|
|
11
12
|
path: string;
|
|
12
13
|
}>();
|
|
13
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
14
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
15
|
+
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
16
|
+
control,
|
|
17
|
+
rawHandleChange,
|
|
18
|
+
);
|
|
14
19
|
|
|
15
20
|
const binding = computed(() => {
|
|
16
21
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -33,7 +38,7 @@ watch(query, () => {
|
|
|
33
38
|
});
|
|
34
39
|
|
|
35
40
|
const value = computed({
|
|
36
|
-
get: () =>
|
|
41
|
+
get: () => projectedData.value,
|
|
37
42
|
set: (v) => handleChange(control.value.path, v),
|
|
38
43
|
});
|
|
39
44
|
|
|
@@ -51,11 +56,11 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
|
|
|
51
56
|
</script>
|
|
52
57
|
|
|
53
58
|
<template>
|
|
54
|
-
<div class="
|
|
55
|
-
<label v-if="control.schema.title" class="
|
|
59
|
+
<div class="jf-control">
|
|
60
|
+
<label v-if="control.schema.title" class="jf-label">{{
|
|
56
61
|
control.schema.title
|
|
57
62
|
}}</label>
|
|
58
|
-
<div v-if="control.description" class="
|
|
63
|
+
<div v-if="control.description" class="jf-description">
|
|
59
64
|
{{ control.description }}
|
|
60
65
|
</div>
|
|
61
66
|
<AutoComplete
|
|
@@ -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 { useProjection } from "../composables/useProjection";
|
|
6
7
|
import { shouldAutoSelectMulti } from "../utils/autoSelect";
|
|
7
8
|
import MultiSelect from "primevue/multiselect";
|
|
8
9
|
|
|
@@ -11,7 +12,11 @@ const props = defineProps<{
|
|
|
11
12
|
schema: JsonSchema;
|
|
12
13
|
path: string;
|
|
13
14
|
}>();
|
|
14
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
15
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
16
|
+
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
17
|
+
control,
|
|
18
|
+
rawHandleChange,
|
|
19
|
+
);
|
|
15
20
|
|
|
16
21
|
const binding = computed(() => {
|
|
17
22
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -53,7 +58,9 @@ watch(
|
|
|
53
58
|
control.value.uischema?.options?.autoSelectSingle === true,
|
|
54
59
|
isLoading,
|
|
55
60
|
items: newItems,
|
|
56
|
-
currentValue: Array.isArray(
|
|
61
|
+
currentValue: Array.isArray(projectedData.value)
|
|
62
|
+
? projectedData.value
|
|
63
|
+
: [],
|
|
57
64
|
});
|
|
58
65
|
|
|
59
66
|
if (valueToSelect !== null) {
|
|
@@ -74,13 +81,13 @@ const sameSet = (a: unknown[], b: unknown[]) => {
|
|
|
74
81
|
// v-model with guard to avoid recursive updates
|
|
75
82
|
const value = computed({
|
|
76
83
|
get() {
|
|
77
|
-
const curr = Array.isArray(
|
|
84
|
+
const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
78
85
|
// return a fresh copy so MultiSelect can't mutate JSONForms' array in place
|
|
79
86
|
return [...curr];
|
|
80
87
|
},
|
|
81
88
|
set(val) {
|
|
82
89
|
const next = Array.isArray(val) ? [...val] : [];
|
|
83
|
-
const curr = Array.isArray(
|
|
90
|
+
const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
84
91
|
if (!sameSet(curr, next)) handleChange(control.value.path, next);
|
|
85
92
|
},
|
|
86
93
|
});
|
|
@@ -96,11 +103,11 @@ const placeholder = computed(() => {
|
|
|
96
103
|
</script>
|
|
97
104
|
|
|
98
105
|
<template>
|
|
99
|
-
<div class="
|
|
100
|
-
<label v-if="control.schema.title" class="
|
|
106
|
+
<div class="jf-control">
|
|
107
|
+
<label v-if="control.schema.title" class="jf-label">{{
|
|
101
108
|
control.schema.title
|
|
102
109
|
}}</label>
|
|
103
|
-
<div v-if="control.description" class="
|
|
110
|
+
<div v-if="control.description" class="jf-description">
|
|
104
111
|
{{ control.description }}
|
|
105
112
|
</div>
|
|
106
113
|
<MultiSelect
|
|
@@ -3,6 +3,8 @@ 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";
|
|
7
|
+
import { useProjection } from "../composables/useProjection";
|
|
6
8
|
import { shouldAutoSelect } from "../utils/autoSelect";
|
|
7
9
|
import Dropdown from "primevue/dropdown";
|
|
8
10
|
|
|
@@ -11,7 +13,11 @@ const props = defineProps<{
|
|
|
11
13
|
schema: JsonSchema;
|
|
12
14
|
path: string;
|
|
13
15
|
}>();
|
|
14
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
16
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
17
|
+
const { projectedData, handleProjectedChange: handleChange } = useProjection(
|
|
18
|
+
control,
|
|
19
|
+
rawHandleChange,
|
|
20
|
+
);
|
|
15
21
|
|
|
16
22
|
const binding = computed(() => {
|
|
17
23
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -44,6 +50,9 @@ const { items, loading, error } = useProvider(binding, {
|
|
|
44
50
|
|
|
45
51
|
// Provider will automatically reload when rootData changes due to reactive cache key
|
|
46
52
|
|
|
53
|
+
// deriveInitialValue — async API-based initial value seeding
|
|
54
|
+
useDeriveInitialValue({ control, handleChange });
|
|
55
|
+
|
|
47
56
|
// Auto-select when provider returns only one item (enabled by default)
|
|
48
57
|
watch(
|
|
49
58
|
[items, loading],
|
|
@@ -53,7 +62,7 @@ watch(
|
|
|
53
62
|
control.value.uischema?.options?.autoSelectSingle !== false,
|
|
54
63
|
isLoading,
|
|
55
64
|
items: newItems,
|
|
56
|
-
currentValue:
|
|
65
|
+
currentValue: projectedData.value,
|
|
57
66
|
});
|
|
58
67
|
|
|
59
68
|
if (valueToSelect !== null) {
|
|
@@ -64,7 +73,7 @@ watch(
|
|
|
64
73
|
);
|
|
65
74
|
|
|
66
75
|
const value = computed({
|
|
67
|
-
get: () =>
|
|
76
|
+
get: () => projectedData.value,
|
|
68
77
|
set: (v) => handleChange(control.value.path, v),
|
|
69
78
|
});
|
|
70
79
|
|
|
@@ -79,11 +88,11 @@ const placeholder = computed(() => {
|
|
|
79
88
|
</script>
|
|
80
89
|
|
|
81
90
|
<template>
|
|
82
|
-
<div class="
|
|
83
|
-
<label v-if="control.schema.title" class="
|
|
91
|
+
<div class="jf-control">
|
|
92
|
+
<label v-if="control.schema.title" class="jf-label">{{
|
|
84
93
|
control.schema.title
|
|
85
94
|
}}</label>
|
|
86
|
-
<div v-if="control.description" class="
|
|
95
|
+
<div v-if="control.description" class="jf-description">
|
|
87
96
|
{{ control.description }}
|
|
88
97
|
</div>
|
|
89
98
|
<Dropdown
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ref,
|
|
3
|
+
provide,
|
|
4
|
+
inject,
|
|
5
|
+
readonly,
|
|
6
|
+
type DeepReadonly,
|
|
7
|
+
type Ref,
|
|
8
|
+
} from "vue";
|
|
9
|
+
import type { ConnectorDataLayer } from "../../core/types";
|
|
10
|
+
|
|
11
|
+
export const DATA_LAYER_KEY = Symbol("dataLayer");
|
|
12
|
+
|
|
13
|
+
export interface DataLayer {
|
|
14
|
+
push(data: Partial<ConnectorDataLayer>): void;
|
|
15
|
+
state: DeepReadonly<Ref<ConnectorDataLayer>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createDataLayer(): DataLayer {
|
|
19
|
+
const state = ref<ConnectorDataLayer>({});
|
|
20
|
+
|
|
21
|
+
const dataLayer: DataLayer = {
|
|
22
|
+
push(data: Partial<ConnectorDataLayer>) {
|
|
23
|
+
state.value = { ...state.value, ...data };
|
|
24
|
+
},
|
|
25
|
+
state: readonly(state) as DeepReadonly<Ref<ConnectorDataLayer>>,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
provide(DATA_LAYER_KEY, dataLayer);
|
|
29
|
+
|
|
30
|
+
return dataLayer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useDataLayer(): DeepReadonly<Ref<ConnectorDataLayer>> {
|
|
34
|
+
const dataLayer = inject<DataLayer | null>(DATA_LAYER_KEY, null);
|
|
35
|
+
if (dataLayer) {
|
|
36
|
+
return dataLayer.state;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// No dataLayer provided — return empty reactive ref
|
|
40
|
+
return readonly(ref<ConnectorDataLayer>({})) as DeepReadonly<
|
|
41
|
+
Ref<ConnectorDataLayer>
|
|
42
|
+
>;
|
|
43
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
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";
|
|
11
|
+
import { useDataLayer } from "./useDataLayer";
|
|
3
12
|
|
|
4
13
|
interface DeriveOptions {
|
|
5
14
|
control: Ref<{
|
|
@@ -8,20 +17,25 @@ interface DeriveOptions {
|
|
|
8
17
|
data: unknown;
|
|
9
18
|
}>;
|
|
10
19
|
handleChange: (path: string, value: unknown) => void;
|
|
20
|
+
/** When projection is active, pass projectedData so the comparison
|
|
21
|
+
* matches the projected (unwrapped) value rather than raw scope data. */
|
|
22
|
+
data?: Ref<unknown> | ComputedRef<unknown>;
|
|
11
23
|
}
|
|
12
24
|
|
|
13
|
-
export function useDerive({
|
|
25
|
+
export function useDerive({
|
|
26
|
+
control,
|
|
27
|
+
handleChange,
|
|
28
|
+
data: dataOverride,
|
|
29
|
+
}: DeriveOptions) {
|
|
14
30
|
// Get the root form data from JSONForms context
|
|
15
31
|
const injectedFormData = inject<{ value: unknown }>("formData", {
|
|
16
32
|
value: {},
|
|
17
33
|
});
|
|
18
34
|
const rootData = computed(() => injectedFormData.value || {});
|
|
19
35
|
|
|
20
|
-
// Get
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
const externalData = computed(() => injectedExternalData.value || {});
|
|
36
|
+
// Get data from the dataLayer
|
|
37
|
+
const dataLayerState = useDataLayer();
|
|
38
|
+
const dataLayerData = computed(() => dataLayerState.value || {});
|
|
25
39
|
|
|
26
40
|
// Extract derive configuration from uischema options
|
|
27
41
|
const deriveConfig = computed(() => {
|
|
@@ -34,9 +48,17 @@ export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
|
34
48
|
};
|
|
35
49
|
});
|
|
36
50
|
|
|
37
|
-
//
|
|
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
|
+
|
|
59
|
+
// Watch for changes in form data and dataLayer and update derived field
|
|
38
60
|
watch(
|
|
39
|
-
[rootData,
|
|
61
|
+
[rootData, dataLayerData, deriveConfig],
|
|
40
62
|
([data, extData, config]) => {
|
|
41
63
|
if (!config.expression || config.mode !== "follow") {
|
|
42
64
|
return;
|
|
@@ -48,7 +70,31 @@ export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
|
48
70
|
data,
|
|
49
71
|
extData,
|
|
50
72
|
);
|
|
51
|
-
|
|
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
|
+
|
|
97
|
+
if (derivedValue !== compareData) {
|
|
52
98
|
handleChange(control.value.path, derivedValue);
|
|
53
99
|
}
|
|
54
100
|
} catch (error) {
|
|
@@ -65,12 +111,12 @@ export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
|
65
111
|
function resolveDeriveExpression(
|
|
66
112
|
expression: string,
|
|
67
113
|
data: unknown,
|
|
68
|
-
|
|
114
|
+
dataLayerData?: unknown,
|
|
69
115
|
): unknown {
|
|
70
|
-
// Handle
|
|
71
|
-
if (expression.startsWith("
|
|
72
|
-
const propertyPath = expression.slice(
|
|
73
|
-
return resolvePropertyPath(propertyPath,
|
|
116
|
+
// Handle dataLayer() syntax
|
|
117
|
+
if (expression.startsWith("dataLayer(") && expression.endsWith(")")) {
|
|
118
|
+
const propertyPath = expression.slice(10, -1); // Remove "dataLayer(" and ")"
|
|
119
|
+
return resolvePropertyPath(propertyPath, dataLayerData);
|
|
74
120
|
}
|
|
75
121
|
|
|
76
122
|
// Handle simple property paths like "country.name"
|
|
@@ -82,7 +128,7 @@ function resolveDeriveExpression(
|
|
|
82
128
|
return resolvePropertyPath(expression, data);
|
|
83
129
|
}
|
|
84
130
|
|
|
85
|
-
// For now, we'll only support simple property paths and
|
|
131
|
+
// For now, we'll only support simple property paths and dataLayer() calls
|
|
86
132
|
// Complex expressions would require a safe expression evaluator
|
|
87
133
|
return resolvePropertyPath(expression, data);
|
|
88
134
|
}
|