@narrative.io/jsonforms-provider-protocols 1.1.0-beta.2 → 1.1.0-beta.4
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/jsonforms-provider-protocols.css +1 -1
- package/dist/vue/composables/useDerive.d.ts +13 -0
- package/dist/vue/composables/useDerive.d.ts.map +1 -0
- package/dist/vue/composables/useDerive.js +59 -0
- package/dist/vue/composables/useDerive.js.map +1 -0
- package/dist/vue/primevue/JfBoolean.vue.d.ts +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.js +1 -1
- package/dist/vue/primevue/JfEnum.vue2.js +67 -9
- package/dist/vue/primevue/JfEnum.vue2.js.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.js +56 -8
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.js +15 -5
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfText.vue.js +94 -11
- package/dist/vue/primevue/JfText.vue.js.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.js +12 -6
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
- package/dist/vue/primevue/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/vue/composables/useDerive.ts +85 -0
- package/src/vue/primevue/JfEnum.vue +88 -9
- package/src/vue/primevue/JfEnumArray.vue +76 -9
- package/src/vue/primevue/JfNumber.vue +19 -4
- package/src/vue/primevue/JfText.vue +115 -8
- package/src/vue/primevue/JfTextArea.vue +16 -5
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { defineComponent, computed, createElementBlock, openBlock, createCommentVNode,
|
|
1
|
+
import { defineComponent, computed, inject, ref, watch, createElementBlock, openBlock, createCommentVNode, createBlock, unref, toDisplayString } from "vue";
|
|
2
2
|
import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
|
|
3
|
+
import { useProvider } from "../composables/useProvider.js";
|
|
4
|
+
import { useDerive } from "../composables/useDerive.js";
|
|
3
5
|
import InputText from "primevue/inputtext";
|
|
6
|
+
import AutoComplete from "primevue/autocomplete";
|
|
4
7
|
const _hoisted_1 = { class: "flex flex-column gap-2" };
|
|
5
8
|
const _hoisted_2 = {
|
|
6
9
|
key: 0,
|
|
@@ -11,7 +14,12 @@ const _hoisted_3 = {
|
|
|
11
14
|
class: "text-color-secondary text-left"
|
|
12
15
|
};
|
|
13
16
|
const _hoisted_4 = {
|
|
14
|
-
key:
|
|
17
|
+
key: 4,
|
|
18
|
+
class: "p-error",
|
|
19
|
+
role: "alert"
|
|
20
|
+
};
|
|
21
|
+
const _hoisted_5 = {
|
|
22
|
+
key: 5,
|
|
15
23
|
class: "p-error"
|
|
16
24
|
};
|
|
17
25
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
@@ -21,31 +29,106 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
21
29
|
setup(__props) {
|
|
22
30
|
const props = __props;
|
|
23
31
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
24
|
-
const
|
|
25
|
-
|
|
32
|
+
const binding = computed(() => {
|
|
33
|
+
const provider = control.value.uischema?.options?.provider;
|
|
34
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
35
|
+
return { ...provider, load: "query" };
|
|
36
|
+
}
|
|
37
|
+
return provider;
|
|
38
|
+
});
|
|
39
|
+
const deps = computed(
|
|
40
|
+
() => control.value.schema?.["x-provider"]?.dependsOn ?? []
|
|
26
41
|
);
|
|
42
|
+
const depValues = computed(() => {
|
|
43
|
+
return deps.value.map((dep) => {
|
|
44
|
+
const path = dep.startsWith("#/") ? dep.slice(2) : dep;
|
|
45
|
+
const keys = path.replace(/\//g, ".").split(".");
|
|
46
|
+
let value = rootData.value;
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (value && typeof value === "object" && key in value) {
|
|
49
|
+
value = value[key];
|
|
50
|
+
} else {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
const injectedFormData = inject("formData", { value: {} });
|
|
58
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
59
|
+
const query = ref("");
|
|
60
|
+
const { items, loading, error, reload } = useProvider(binding, {
|
|
61
|
+
data: rootData,
|
|
62
|
+
path: control.value.path,
|
|
63
|
+
uiQuery: query.value,
|
|
64
|
+
dependsOnValues: depValues.value
|
|
65
|
+
});
|
|
66
|
+
watch(query, () => {
|
|
67
|
+
if (binding.value?.load === "query") reload();
|
|
68
|
+
});
|
|
69
|
+
const placeholder = computed(() => {
|
|
70
|
+
if (loading.value) return "Loading…";
|
|
71
|
+
return control.value.uischema?.options?.placeholder ?? control.value.description;
|
|
72
|
+
});
|
|
73
|
+
const isAutocomplete = computed(() => !!binding.value);
|
|
74
|
+
useDerive({ control, handleChange });
|
|
75
|
+
const hasInteracted = ref(false);
|
|
76
|
+
const hasFocused = ref(false);
|
|
77
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
27
78
|
function onInput(val) {
|
|
28
|
-
const newValue = val
|
|
29
|
-
if (
|
|
79
|
+
const newValue = val && val.trim() !== "" ? val : void 0;
|
|
80
|
+
if (control.value.data !== newValue) {
|
|
30
81
|
handleChange(control.value.path, newValue);
|
|
31
82
|
}
|
|
32
83
|
}
|
|
84
|
+
function onBlur() {
|
|
85
|
+
if (hasFocused.value) {
|
|
86
|
+
hasInteracted.value = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function onFocus() {
|
|
90
|
+
hasFocused.value = true;
|
|
91
|
+
}
|
|
92
|
+
const onComplete = (event) => {
|
|
93
|
+
query.value = event.query;
|
|
94
|
+
};
|
|
95
|
+
const onSelect = (event) => {
|
|
96
|
+
const newValue = event.value?.value ?? event.value;
|
|
97
|
+
handleChange(control.value.path, newValue);
|
|
98
|
+
};
|
|
33
99
|
return (_ctx, _cache) => {
|
|
34
100
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
35
101
|
unref(control).label ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(control).label), 1)) : createCommentVNode("", true),
|
|
36
102
|
unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
|
|
37
|
-
|
|
103
|
+
isAutocomplete.value ? (openBlock(), createBlock(unref(AutoComplete), {
|
|
104
|
+
key: 2,
|
|
105
|
+
class: "w-full",
|
|
106
|
+
"model-value": unref(control).data ?? "",
|
|
107
|
+
suggestions: unref(items),
|
|
108
|
+
"option-label": "label",
|
|
109
|
+
placeholder: placeholder.value,
|
|
110
|
+
disabled: !unref(control).enabled,
|
|
111
|
+
"aria-invalid": !!showErrors.value || void 0,
|
|
112
|
+
onComplete,
|
|
113
|
+
onItemSelect: onSelect,
|
|
114
|
+
"onUpdate:modelValue": onInput,
|
|
115
|
+
onBlur,
|
|
116
|
+
onFocus
|
|
117
|
+
}, null, 8, ["model-value", "suggestions", "placeholder", "disabled", "aria-invalid"])) : (openBlock(), createBlock(unref(InputText), {
|
|
118
|
+
key: 3,
|
|
38
119
|
class: "w-full",
|
|
39
120
|
"model-value": unref(control).data ?? "",
|
|
40
121
|
disabled: !unref(control).enabled,
|
|
41
|
-
"aria-invalid": !!
|
|
122
|
+
"aria-invalid": !!showErrors.value || void 0,
|
|
42
123
|
placeholder: placeholder.value,
|
|
43
124
|
autocapitalize: "off",
|
|
44
125
|
autocomplete: "off",
|
|
45
126
|
spellcheck: "false",
|
|
46
|
-
"onUpdate:modelValue": onInput
|
|
47
|
-
|
|
48
|
-
|
|
127
|
+
"onUpdate:modelValue": onInput,
|
|
128
|
+
onBlur,
|
|
129
|
+
onFocus
|
|
130
|
+
}, null, 8, ["model-value", "disabled", "aria-invalid", "placeholder"])),
|
|
131
|
+
unref(error) ? (openBlock(), createElementBlock("small", _hoisted_4, "Failed: " + toDisplayString(unref(error)), 1)) : showErrors.value ? (openBlock(), createElementBlock("small", _hoisted_5, toDisplayString(unref(control).errors), 1)) : createCommentVNode("", true)
|
|
49
132
|
]);
|
|
50
133
|
};
|
|
51
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfText.vue.js","sources":["../../../src/vue/primevue/JfText.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ControlElement } from \"@jsonforms/core\";\nimport { rendererProps, useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed } from \"vue\";\nimport InputText from \"primevue/inputtext\";\n\ndefineOptions({ name: \"JfText\" });\n\nconst props = defineProps(rendererProps<ControlElement>());\nconst { control, handleChange } = useJsonFormsControl(props);\n\nconst placeholder = computed<string | undefined>(\n ()
|
|
1
|
+
{"version":3,"file":"JfText.vue.js","sources":["../../../src/vue/primevue/JfText.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ControlElement } from \"@jsonforms/core\";\nimport { rendererProps, useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, ref, inject, watch } from \"vue\";\nimport { useProvider } from \"../composables/useProvider\";\nimport { useDerive } from \"../composables/useDerive\";\nimport InputText from \"primevue/inputtext\";\nimport AutoComplete from \"primevue/autocomplete\";\n\ndefineOptions({ name: \"JfText\" });\n\nconst props = defineProps(rendererProps<ControlElement>());\nconst { control, handleChange } = useJsonFormsControl(props);\n\n// Provider support for autocomplete functionality\nconst binding = computed(() => {\n const provider = control.value.uischema?.options?.provider;\n // Ensure load property is set to 'query' by default for autocomplete\n if (provider && typeof provider === \"object\" && !provider.load) {\n return { ...provider, load: \"query\" };\n }\n return provider;\n});\n\nconst deps = computed(\n () =>\n ((\n (control.value.schema as Record<string, unknown>)?.[\n \"x-provider\"\n ] as Record<string, unknown>\n )?.dependsOn as string[]) ?? [],\n);\nconst depValues = computed(() => {\n return deps.value.map((dep) => {\n // Resolve dependency value from form data using JSON pointer-like path\n const path = dep.startsWith(\"#/\") ? dep.slice(2) : dep;\n const keys = path.replace(/\\//g, \".\").split(\".\");\n let value: unknown = rootData.value;\n for (const key of keys) {\n if (value && typeof value === \"object\" && key in value) {\n value = (value as Record<string, unknown>)[key];\n } else {\n return null;\n }\n }\n return value;\n });\n});\n\n// Get the root form data from JSONForms context for template URL resolution\nconst injectedFormData = inject<{ value: unknown }>(\"formData\", { value: {} });\nconst rootData = computed(() => injectedFormData.value || {});\n\nconst query = ref(\"\");\nconst { items, loading, error, reload } = useProvider(binding, {\n data: rootData,\n path: control.value.path,\n uiQuery: query.value,\n dependsOnValues: depValues.value,\n});\n\nwatch(query, () => {\n if (binding.value?.load === \"query\") reload();\n});\n\nconst placeholder = computed<string | undefined>(() => {\n if (loading.value) return \"Loading…\";\n return (\n (control.value.uischema as { options?: { placeholder?: string } })?.options\n ?.placeholder ?? control.value.description\n );\n});\n\nconst isAutocomplete = computed(() => !!binding.value);\n\n// Add derive functionality\nuseDerive({ control, handleChange });\n\n// Track user interaction\nconst hasInteracted = ref(false);\nconst hasFocused = ref(false);\n\nconst showErrors = computed(() => hasInteracted.value && control.value.errors);\n\nfunction onInput(val: string | undefined) {\n // Convert empty strings to undefined for proper required field validation\n const newValue = val && val.trim() !== \"\" ? val : undefined;\n if (control.value.data !== newValue) {\n handleChange(control.value.path, newValue);\n }\n}\n\nfunction onBlur() {\n if (hasFocused.value) {\n hasInteracted.value = true;\n }\n}\n\nfunction onFocus() {\n hasFocused.value = true;\n}\n\n// Autocomplete specific handlers\nconst onComplete = (event: { query: string }) => {\n query.value = event.query;\n};\n\nconst onSelect = (event: { value?: { value?: unknown } | unknown }) => {\n const newValue = (event.value as { value?: unknown })?.value ?? event.value;\n handleChange(control.value.path, newValue);\n};\n</script>\n\n<template>\n <div class=\"flex flex-column gap-2\">\n <label v-if=\"control.label\" class=\"text-color text-left\">{{\n control.label\n }}</label>\n <div v-if=\"control.description\" class=\"text-color-secondary text-left\">\n {{ control.description }}\n </div>\n <AutoComplete\n v-if=\"isAutocomplete\"\n class=\"w-full\"\n :model-value=\"control.data ?? ''\"\n :suggestions=\"items\"\n option-label=\"label\"\n :placeholder=\"placeholder\"\n :disabled=\"!control.enabled\"\n :aria-invalid=\"!!showErrors || undefined\"\n @complete=\"onComplete\"\n @item-select=\"onSelect\"\n @update:model-value=\"onInput\"\n @blur=\"onBlur\"\n @focus=\"onFocus\"\n />\n <InputText\n v-else\n class=\"w-full\"\n :model-value=\"control.data ?? ''\"\n :disabled=\"!control.enabled\"\n :aria-invalid=\"!!showErrors || undefined\"\n :placeholder=\"placeholder\"\n autocapitalize=\"off\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @update:model-value=\"onInput\"\n @blur=\"onBlur\"\n @focus=\"onFocus\"\n />\n <small v-if=\"error\" class=\"p-error\" role=\"alert\">Failed: {{ error }}</small>\n <small v-else-if=\"showErrors\" class=\"p-error\">{{ control.errors }}</small>\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createBlock"],"mappings":"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"}
|
|
@@ -66,10 +66,10 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
66
66
|
default: undefined;
|
|
67
67
|
};
|
|
68
68
|
}>> & Readonly<{}>, {
|
|
69
|
+
config: Record<string, any>;
|
|
69
70
|
enabled: boolean;
|
|
70
71
|
renderers: import("@jsonforms/core").JsonFormsRendererRegistryEntry[];
|
|
71
72
|
cells: import("@jsonforms/core").JsonFormsCellRendererRegistryEntry[];
|
|
72
|
-
config: Record<string, any>;
|
|
73
73
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
74
74
|
export default _default;
|
|
75
75
|
//# sourceMappingURL=JfTextArea.vue.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfTextArea.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/primevue/JfTextArea.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"JfTextArea.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/primevue/JfTextArea.vue"],"names":[],"mappings":"AA2DA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkJtD,wBAEG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, computed, createElementBlock, openBlock, createCommentVNode, createVNode, unref, toDisplayString } from "vue";
|
|
1
|
+
import { defineComponent, computed, ref, createElementBlock, openBlock, createCommentVNode, createVNode, unref, toDisplayString } from "vue";
|
|
2
2
|
import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
|
|
3
3
|
import Textarea from "primevue/textarea";
|
|
4
4
|
const _hoisted_1 = { class: "flex flex-column gap-2" };
|
|
@@ -24,12 +24,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
24
24
|
const placeholder = computed(
|
|
25
25
|
() => control.value.uischema?.options?.placeholder ?? control.value.description
|
|
26
26
|
);
|
|
27
|
+
const hasInteracted = ref(false);
|
|
28
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
27
29
|
function onInput(val) {
|
|
28
|
-
const newValue = val
|
|
29
|
-
if (
|
|
30
|
+
const newValue = val && val.trim() !== "" ? val : void 0;
|
|
31
|
+
if (control.value.data !== newValue) {
|
|
30
32
|
handleChange(control.value.path, newValue);
|
|
31
33
|
}
|
|
32
34
|
}
|
|
35
|
+
function onBlur() {
|
|
36
|
+
hasInteracted.value = true;
|
|
37
|
+
}
|
|
33
38
|
return (_ctx, _cache) => {
|
|
34
39
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
35
40
|
unref(control).label ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(control).label), 1)) : createCommentVNode("", true),
|
|
@@ -38,13 +43,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
38
43
|
class: "w-full",
|
|
39
44
|
"model-value": unref(control).data ?? "",
|
|
40
45
|
disabled: !unref(control).enabled,
|
|
41
|
-
"aria-invalid": !!
|
|
46
|
+
"aria-invalid": !!showErrors.value || void 0,
|
|
42
47
|
placeholder: placeholder.value,
|
|
43
48
|
rows: 4,
|
|
44
49
|
"auto-resize": true,
|
|
45
|
-
"onUpdate:modelValue": onInput
|
|
50
|
+
"onUpdate:modelValue": onInput,
|
|
51
|
+
onBlur
|
|
46
52
|
}, null, 8, ["model-value", "disabled", "aria-invalid", "placeholder"]),
|
|
47
|
-
|
|
53
|
+
showErrors.value ? (openBlock(), createElementBlock("small", _hoisted_4, toDisplayString(unref(control).errors), 1)) : createCommentVNode("", true)
|
|
48
54
|
]);
|
|
49
55
|
};
|
|
50
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfTextArea.vue.js","sources":["../../../src/vue/primevue/JfTextArea.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ControlElement } from \"@jsonforms/core\";\nimport { rendererProps, useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed } from \"vue\";\nimport Textarea from \"primevue/textarea\";\n\ndefineOptions({ name: \"JfTextArea\" });\n\nconst props = defineProps(rendererProps<ControlElement>());\nconst { control, handleChange } = useJsonFormsControl(props);\n\nconst placeholder = computed<string | undefined>(\n () =>\n (control.value.uischema as { options?: { placeholder?: string } })?.options\n ?.placeholder ?? control.value.description,\n);\n\nfunction onInput(val: string | undefined) {\n const newValue = val
|
|
1
|
+
{"version":3,"file":"JfTextArea.vue.js","sources":["../../../src/vue/primevue/JfTextArea.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ControlElement } from \"@jsonforms/core\";\nimport { rendererProps, useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, ref } from \"vue\";\nimport Textarea from \"primevue/textarea\";\n\ndefineOptions({ name: \"JfTextArea\" });\n\nconst props = defineProps(rendererProps<ControlElement>());\nconst { control, handleChange } = useJsonFormsControl(props);\n\nconst placeholder = computed<string | undefined>(\n () =>\n (control.value.uischema as { options?: { placeholder?: string } })?.options\n ?.placeholder ?? control.value.description,\n);\n\n// Track user interaction\nconst hasInteracted = ref(false);\n\nconst showErrors = computed(() => hasInteracted.value && control.value.errors);\n\nfunction onInput(val: string | undefined) {\n // Convert empty strings to undefined for proper required field validation\n const newValue = val && val.trim() !== \"\" ? val : undefined;\n if (control.value.data !== newValue) {\n handleChange(control.value.path, newValue);\n }\n}\n\nfunction onBlur() {\n hasInteracted.value = true;\n}\n</script>\n\n<template>\n <div class=\"flex flex-column gap-2\">\n <label v-if=\"control.label\" class=\"text-color text-left\">{{\n control.label\n }}</label>\n <div v-if=\"control.description\" class=\"text-color-secondary text-left\">\n {{ control.description }}\n </div>\n <Textarea\n class=\"w-full\"\n :model-value=\"control.data ?? ''\"\n :disabled=\"!control.enabled\"\n :aria-invalid=\"!!showErrors || undefined\"\n :placeholder=\"placeholder\"\n :rows=\"4\"\n :auto-resize=\"true\"\n @update:model-value=\"onInput\"\n @blur=\"onBlur\"\n />\n <small v-if=\"showErrors\" class=\"p-error\">{{ control.errors }}</small>\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAQA,UAAM,QAAQ;AACd,UAAM,EAAE,SAAS,iBAAiB,oBAAoB,KAAK;AAE3D,UAAM,cAAc;AAAA,MAClB,MACG,QAAQ,MAAM,UAAqD,SAChE,eAAe,QAAQ,MAAM;AAAA,IAAA;AAIrC,UAAM,gBAAgB,IAAI,KAAK;AAE/B,UAAM,aAAa,SAAS,MAAM,cAAc,SAAS,QAAQ,MAAM,MAAM;AAE7E,aAAS,QAAQ,KAAyB;AAExC,YAAM,WAAW,OAAO,IAAI,KAAA,MAAW,KAAK,MAAM;AAClD,UAAI,QAAQ,MAAM,SAAS,UAAU;AACnC,qBAAa,QAAQ,MAAM,MAAM,QAAQ;AAAA,MAC3C;AAAA,IACF;AAEA,aAAS,SAAS;AAChB,oBAAc,QAAQ;AAAA,IACxB;;AAIE,aAAAA,UAAA,GAAAC,mBAmBM,OAnBN,YAmBM;AAAA,QAlBSC,MAAA,OAAA,EAAQ,SAArBF,UAAA,GAAAC,mBAEU,SAFV,YAEUE,gBADRD,MAAA,OAAA,EAAQ,KAAK,GAAA,CAAA;QAEJA,MAAA,OAAA,EAAQ,eAAnBF,UAAA,GAAAC,mBAEM,OAFN,YAEME,gBADDD,MAAA,OAAA,EAAQ,WAAW,GAAA,CAAA;QAExBE,YAUEF,MAAA,QAAA,GAAA;AAAA,UATA,OAAM;AAAA,UACL,eAAaA,MAAA,OAAA,EAAQ,QAAI;AAAA,UACzB,UAAQ,CAAGA,MAAA,OAAA,EAAQ;AAAA,UACnB,gBAAY,CAAA,CAAI,WAAA,SAAc;AAAA,UAC9B,aAAa,YAAA;AAAA,UACb,MAAM;AAAA,UACN,eAAa;AAAA,UACb,uBAAoB;AAAA,UACpB;AAAA,QAAA;QAEU,WAAA,SAAbF,UAAA,GAAAC,mBAAqE,SAArE,YAAqEE,gBAAzBD,MAAA,OAAA,EAAQ,MAAM,GAAA,CAAA;;;;;"}
|
|
@@ -73,10 +73,10 @@ export declare const primevueRenderers: {
|
|
|
73
73
|
default: undefined;
|
|
74
74
|
};
|
|
75
75
|
}>> & Readonly<{}>, {
|
|
76
|
+
config: Record<string, any>;
|
|
76
77
|
enabled: boolean;
|
|
77
78
|
renderers: import("@jsonforms/core").JsonFormsRendererRegistryEntry[];
|
|
78
79
|
cells: import("@jsonforms/core").JsonFormsCellRendererRegistryEntry[];
|
|
79
|
-
config: Record<string, any>;
|
|
80
80
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
81
81
|
}[];
|
|
82
82
|
export { JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean };
|
package/package.json
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { computed, watch, inject, type Ref } from "vue";
|
|
2
|
+
import { type ControlElement } from "@jsonforms/core";
|
|
3
|
+
|
|
4
|
+
interface DeriveOptions {
|
|
5
|
+
control: Ref<{
|
|
6
|
+
uischema: ControlElement;
|
|
7
|
+
path: string;
|
|
8
|
+
data: unknown;
|
|
9
|
+
}>;
|
|
10
|
+
handleChange: (path: string, value: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
14
|
+
// Get the root form data from JSONForms context
|
|
15
|
+
const injectedFormData = inject<{ value: unknown }>("formData", {
|
|
16
|
+
value: {},
|
|
17
|
+
});
|
|
18
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
19
|
+
|
|
20
|
+
// Extract derive configuration from uischema options
|
|
21
|
+
const deriveConfig = computed(() => {
|
|
22
|
+
const options = control.value.uischema?.options as
|
|
23
|
+
| { derive?: string; mode?: string }
|
|
24
|
+
| undefined;
|
|
25
|
+
return {
|
|
26
|
+
expression: options?.derive,
|
|
27
|
+
mode: options?.mode || "follow", // 'follow' = auto-update, 'manual' = user controlled
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Watch for changes in form data and update derived field
|
|
32
|
+
watch(
|
|
33
|
+
[rootData, deriveConfig],
|
|
34
|
+
([data, config]) => {
|
|
35
|
+
if (!config.expression || config.mode !== "follow") {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const derivedValue = resolveDeriveExpression(config.expression, data);
|
|
41
|
+
if (derivedValue !== control.value.data) {
|
|
42
|
+
handleChange(control.value.path, derivedValue);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.warn(
|
|
46
|
+
`Failed to derive value for ${control.value.path}:`,
|
|
47
|
+
error,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{ deep: true, immediate: true },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveDeriveExpression(expression: string, data: unknown): unknown {
|
|
56
|
+
// Handle simple property paths like "country.name"
|
|
57
|
+
if (
|
|
58
|
+
!expression.includes("(") &&
|
|
59
|
+
!expression.includes("+") &&
|
|
60
|
+
!expression.includes("?")
|
|
61
|
+
) {
|
|
62
|
+
return resolvePropertyPath(expression, data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// For now, we'll only support simple property paths
|
|
66
|
+
// Complex expressions would require a safe expression evaluator
|
|
67
|
+
return resolvePropertyPath(expression, data);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolvePropertyPath(path: string, data: unknown): unknown {
|
|
71
|
+
if (!path || !data) return "";
|
|
72
|
+
|
|
73
|
+
const keys = path.split(".");
|
|
74
|
+
let value: unknown = data;
|
|
75
|
+
|
|
76
|
+
for (const key of keys) {
|
|
77
|
+
if (value && typeof value === "object" && key in value) {
|
|
78
|
+
value = (value as Record<string, unknown>)[key];
|
|
79
|
+
} else {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ControlElement, JsonSchema } from "@jsonforms/core";
|
|
3
3
|
import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
|
|
4
|
-
import { computed } from "vue";
|
|
4
|
+
import { computed, ref, inject } from "vue";
|
|
5
|
+
import { useProvider } from "../composables/useProvider";
|
|
6
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
7
|
import Dropdown from "primevue/dropdown";
|
|
6
8
|
|
|
7
9
|
defineOptions({ name: "JfEnum" });
|
|
@@ -26,14 +28,87 @@ const toOptions = (schema?: JsonSchema): Opt[] => {
|
|
|
26
28
|
return [];
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
// Provider support
|
|
32
|
+
const binding = computed(() => {
|
|
33
|
+
const provider = control.value.uischema?.options?.provider;
|
|
34
|
+
// Ensure load property is set to 'mount' by default
|
|
35
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
36
|
+
return { ...provider, load: "mount" };
|
|
37
|
+
}
|
|
38
|
+
return provider;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const deps = computed(
|
|
30
42
|
() =>
|
|
31
|
-
(
|
|
32
|
-
|
|
43
|
+
((
|
|
44
|
+
(control.value.schema as Record<string, unknown>)?.[
|
|
45
|
+
"x-provider"
|
|
46
|
+
] as Record<string, unknown>
|
|
47
|
+
)?.dependsOn as string[]) ?? [],
|
|
33
48
|
);
|
|
49
|
+
const depValues = computed(() => {
|
|
50
|
+
return deps.value.map((dep) => {
|
|
51
|
+
// Resolve dependency value from form data using JSON pointer-like path
|
|
52
|
+
const path = dep.startsWith("#/") ? dep.slice(2) : dep;
|
|
53
|
+
const keys = path.replace(/\//g, ".").split(".");
|
|
54
|
+
let value: unknown = rootData.value;
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
if (value && typeof value === "object" && key in value) {
|
|
57
|
+
value = (value as Record<string, unknown>)[key];
|
|
58
|
+
} else {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Get the root form data from JSONForms context for template URL resolution
|
|
67
|
+
const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
|
|
68
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
69
|
+
|
|
70
|
+
// Use provider if available, otherwise fall back to schema enum/oneOf
|
|
71
|
+
const {
|
|
72
|
+
items: providerItems,
|
|
73
|
+
loading,
|
|
74
|
+
error,
|
|
75
|
+
} = useProvider(binding, {
|
|
76
|
+
data: rootData,
|
|
77
|
+
path: control.value.path,
|
|
78
|
+
dependsOnValues: depValues.value,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const placeholder = computed<string | undefined>(() => {
|
|
82
|
+
if (loading.value) return "Loading…";
|
|
83
|
+
return (
|
|
84
|
+
(control.value.uischema as { options?: { placeholder?: string } })?.options
|
|
85
|
+
?.placeholder ?? control.value.description
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const options = computed(() => {
|
|
90
|
+
// Use provider items if available, otherwise fall back to schema enum/oneOf
|
|
91
|
+
if (binding.value && providerItems.value.length > 0) {
|
|
92
|
+
return providerItems.value;
|
|
93
|
+
}
|
|
94
|
+
return toOptions(control.value.schema);
|
|
95
|
+
});
|
|
34
96
|
|
|
35
|
-
|
|
36
|
-
|
|
97
|
+
// Add derive functionality
|
|
98
|
+
useDerive({ control, handleChange });
|
|
99
|
+
|
|
100
|
+
// Track user interaction
|
|
101
|
+
const hasInteracted = ref(false);
|
|
102
|
+
|
|
103
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
104
|
+
|
|
105
|
+
const onSelect = (val: unknown) => {
|
|
106
|
+
handleChange(control.value.path, val);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const onBlur = () => {
|
|
110
|
+
hasInteracted.value = true;
|
|
111
|
+
};
|
|
37
112
|
</script>
|
|
38
113
|
|
|
39
114
|
<template>
|
|
@@ -51,12 +126,16 @@ const onSelect = (val: unknown) => handleChange(control.value.path, val);
|
|
|
51
126
|
option-value="value"
|
|
52
127
|
:model-value="control.data ?? null"
|
|
53
128
|
:placeholder="placeholder"
|
|
54
|
-
:disabled="!control.enabled"
|
|
55
|
-
:aria-invalid="!!
|
|
129
|
+
:disabled="!control.enabled || loading"
|
|
130
|
+
:aria-invalid="!!showErrors || undefined"
|
|
56
131
|
:show-clear="true"
|
|
57
132
|
@update:model-value="onSelect"
|
|
133
|
+
@blur="onBlur"
|
|
58
134
|
/>
|
|
59
|
-
<small v-if="
|
|
135
|
+
<small v-if="error" class="p-error" role="alert"
|
|
136
|
+
>Failed to load: {{ error }}</small
|
|
137
|
+
>
|
|
138
|
+
<small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
|
|
60
139
|
</div>
|
|
61
140
|
</template>
|
|
62
141
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ControlElement, JsonSchema } from "@jsonforms/core";
|
|
3
3
|
import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
|
|
4
|
-
import { computed } from "vue";
|
|
4
|
+
import { computed, inject } from "vue";
|
|
5
|
+
import { useProvider } from "../composables/useProvider";
|
|
6
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
7
|
import MultiSelect from "primevue/multiselect";
|
|
6
8
|
|
|
7
9
|
defineOptions({ name: "JfEnumArray" });
|
|
@@ -26,14 +28,74 @@ const toOptions = (schema?: JsonSchema): Opt[] => {
|
|
|
26
28
|
return [];
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// Provider support
|
|
32
|
+
const binding = computed(() => {
|
|
33
|
+
const provider = control.value.uischema?.options?.provider;
|
|
34
|
+
// Ensure load property is set to 'mount' by default
|
|
35
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
36
|
+
return { ...provider, load: "mount" };
|
|
37
|
+
}
|
|
38
|
+
return provider;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const deps = computed(
|
|
33
42
|
() =>
|
|
34
|
-
(
|
|
35
|
-
|
|
43
|
+
((
|
|
44
|
+
(control.value.schema as Record<string, unknown>)?.[
|
|
45
|
+
"x-provider"
|
|
46
|
+
] as Record<string, unknown>
|
|
47
|
+
)?.dependsOn as string[]) ?? [],
|
|
36
48
|
);
|
|
49
|
+
const depValues = computed(() => {
|
|
50
|
+
return deps.value.map((dep) => {
|
|
51
|
+
// Resolve dependency value from form data using JSON pointer-like path
|
|
52
|
+
const path = dep.startsWith("#/") ? dep.slice(2) : dep;
|
|
53
|
+
const keys = path.replace(/\//g, ".").split(".");
|
|
54
|
+
let value: unknown = rootData.value;
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
if (value && typeof value === "object" && key in value) {
|
|
57
|
+
value = (value as Record<string, unknown>)[key];
|
|
58
|
+
} else {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Get the root form data from JSONForms context for template URL resolution
|
|
67
|
+
const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
|
|
68
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
69
|
+
|
|
70
|
+
// Use provider if available, otherwise fall back to schema enum/oneOf
|
|
71
|
+
const {
|
|
72
|
+
items: providerItems,
|
|
73
|
+
loading,
|
|
74
|
+
error,
|
|
75
|
+
} = useProvider(binding, {
|
|
76
|
+
data: rootData,
|
|
77
|
+
path: control.value.path,
|
|
78
|
+
dependsOnValues: depValues.value,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const options = computed(() => {
|
|
82
|
+
// Use provider items if available, otherwise fall back to schema enum/oneOf
|
|
83
|
+
if (binding.value && providerItems.value.length > 0) {
|
|
84
|
+
return providerItems.value;
|
|
85
|
+
}
|
|
86
|
+
return toOptions((control.value.schema as { items?: JsonSchema })?.items);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Add derive functionality
|
|
90
|
+
useDerive({ control, handleChange });
|
|
91
|
+
|
|
92
|
+
const placeholder = computed<string | undefined>(() => {
|
|
93
|
+
if (loading.value) return "Loading…";
|
|
94
|
+
return (
|
|
95
|
+
(control.value.uischema as { options?: { placeholder?: string } })?.options
|
|
96
|
+
?.placeholder ?? control.value.description
|
|
97
|
+
);
|
|
98
|
+
});
|
|
37
99
|
|
|
38
100
|
// order-insensitive shallow equality for primitive enums
|
|
39
101
|
const sameSet = (a: unknown[], b: unknown[]) => {
|
|
@@ -75,11 +137,16 @@ const model = computed<unknown[]>({
|
|
|
75
137
|
option-value="value"
|
|
76
138
|
data-key="value"
|
|
77
139
|
display="chip"
|
|
78
|
-
:disabled="!control.enabled"
|
|
140
|
+
:disabled="!control.enabled || loading"
|
|
79
141
|
:aria-invalid="!!control.errors || undefined"
|
|
80
142
|
:placeholder="placeholder"
|
|
81
143
|
/>
|
|
82
144
|
|
|
83
|
-
<small v-if="
|
|
145
|
+
<small v-if="error" class="p-error" role="alert"
|
|
146
|
+
>Failed to load: {{ error }}</small
|
|
147
|
+
>
|
|
148
|
+
<small v-else-if="control.errors" class="p-error">{{
|
|
149
|
+
control.errors
|
|
150
|
+
}}</small>
|
|
84
151
|
</div>
|
|
85
152
|
</template>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ControlElement } from "@jsonforms/core";
|
|
3
3
|
import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
|
|
4
|
-
import { computed } from "vue";
|
|
4
|
+
import { computed, ref } from "vue";
|
|
5
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
6
|
import InputNumber from "primevue/inputnumber";
|
|
6
7
|
|
|
7
8
|
defineOptions({ name: "JfNumber" });
|
|
@@ -19,6 +20,9 @@ const placeholder = computed<string | undefined>(
|
|
|
19
20
|
() => (options.value.placeholder as string) ?? control.value.description,
|
|
20
21
|
);
|
|
21
22
|
|
|
23
|
+
// Add derive functionality
|
|
24
|
+
useDerive({ control, handleChange });
|
|
25
|
+
|
|
22
26
|
// Currency and decimal configuration
|
|
23
27
|
const mode = computed(() => {
|
|
24
28
|
if (options.value.currency) return "currency";
|
|
@@ -51,8 +55,18 @@ const useGrouping = computed(() => {
|
|
|
51
55
|
return options.value.useGrouping === true;
|
|
52
56
|
});
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
// Track user interaction
|
|
59
|
+
const hasInteracted = ref(false);
|
|
60
|
+
|
|
61
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
62
|
+
|
|
63
|
+
const onNumber = (val: number | null) => {
|
|
55
64
|
handleChange(control.value.path, val ?? undefined);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const onBlur = () => {
|
|
68
|
+
hasInteracted.value = true;
|
|
69
|
+
};
|
|
56
70
|
</script>
|
|
57
71
|
|
|
58
72
|
<template>
|
|
@@ -74,9 +88,10 @@ const onNumber = (val: number | null) =>
|
|
|
74
88
|
:model-value="typeof control.data === 'number' ? control.data : null"
|
|
75
89
|
:placeholder="placeholder"
|
|
76
90
|
:disabled="!control.enabled"
|
|
77
|
-
:aria-invalid="!!
|
|
91
|
+
:aria-invalid="!!showErrors || undefined"
|
|
78
92
|
@update:model-value="onNumber"
|
|
93
|
+
@blur="onBlur"
|
|
79
94
|
/>
|
|
80
|
-
<small v-if="
|
|
95
|
+
<small v-if="showErrors" class="p-error">{{ control.errors }}</small>
|
|
81
96
|
</div>
|
|
82
97
|
</template>
|