@narrative.io/jsonforms-provider-protocols 3.0.0-beta.1 → 3.0.0-beta.3
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/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 +56 -3
- package/dist/vue/composables/useProjection.js.map +1 -1
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +2 -0
- 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 +13 -7
- 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 +10 -13
- 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 +13 -8
- 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 +11 -14
- 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 +18 -22
- 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 +14 -10
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
- package/package.json +3 -1
- package/src/vue/composables/useDirtyValidation.ts +20 -0
- package/src/vue/composables/useProjection.ts +114 -1
- package/src/vue/index.ts +1 -0
- package/src/vue/primevue/JfBoolean.vue +13 -5
- package/src/vue/primevue/JfEnum.vue +11 -16
- package/src/vue/primevue/JfEnumArray.vue +15 -8
- package/src/vue/primevue/JfNumber.vue +12 -17
- package/src/vue/primevue/JfText.vue +16 -21
- package/src/vue/primevue/JfTextArea.vue +16 -12
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { defineComponent, getCurrentInstance, computed,
|
|
1
|
+
import { defineComponent, getCurrentInstance, computed, createElementBlock, openBlock, createCommentVNode, createVNode, unref, toDisplayString, normalizeClass } from "vue";
|
|
2
2
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
3
3
|
import { useDerive } from "../composables/useDerive.js";
|
|
4
4
|
import { useProjection } from "../composables/useProjection.js";
|
|
5
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation.js";
|
|
5
6
|
import InputNumber from "primevue/inputnumber";
|
|
6
7
|
const _hoisted_1 = { class: "flex flex-column gap-2" };
|
|
7
8
|
const _hoisted_2 = {
|
|
@@ -58,7 +59,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
58
59
|
const instance = getCurrentInstance();
|
|
59
60
|
const props = instance.props;
|
|
60
61
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
61
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
|
|
62
|
+
const { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);
|
|
62
63
|
const options = computed(
|
|
63
64
|
() => control.value.uischema?.options ?? {}
|
|
64
65
|
);
|
|
@@ -93,21 +94,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
93
94
|
if (mode.value === "currency") return true;
|
|
94
95
|
return options.value.useGrouping === true;
|
|
95
96
|
});
|
|
96
|
-
const
|
|
97
|
-
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
97
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
98
98
|
const onNumber = (val) => {
|
|
99
99
|
handleChange(control.value.path, val ?? void 0);
|
|
100
100
|
};
|
|
101
|
-
const onBlur = () => {
|
|
102
|
-
hasInteracted.value = true;
|
|
103
|
-
};
|
|
104
101
|
return (_ctx, _cache) => {
|
|
105
102
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
106
|
-
unref(
|
|
103
|
+
unref(projectedLabel) ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(projectedLabel)), 1)) : createCommentVNode("", true),
|
|
107
104
|
unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
|
|
108
105
|
createVNode(unref(InputNumber), {
|
|
109
|
-
class: "w-full",
|
|
110
|
-
"input-class": "w-full",
|
|
106
|
+
class: normalizeClass(["w-full", { "p-invalid": unref(showErrors) }]),
|
|
107
|
+
"input-class": ["w-full", { "p-invalid": unref(showErrors) }],
|
|
111
108
|
"use-grouping": useGrouping.value,
|
|
112
109
|
mode: mode.value,
|
|
113
110
|
currency: currency.value,
|
|
@@ -116,11 +113,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
116
113
|
"model-value": typeof unref(projectedData) === "number" ? unref(projectedData) : null,
|
|
117
114
|
placeholder: placeholder.value,
|
|
118
115
|
disabled: !unref(control).enabled,
|
|
119
|
-
"aria-invalid":
|
|
116
|
+
"aria-invalid": unref(showErrors) || void 0,
|
|
120
117
|
"onUpdate:modelValue": onNumber,
|
|
121
|
-
onBlur
|
|
122
|
-
}, null, 8, ["use-grouping", "mode", "currency", "min-fraction-digits", "max-fraction-digits", "model-value", "placeholder", "disabled", "aria-invalid"]),
|
|
123
|
-
showErrors
|
|
118
|
+
onBlur: unref(markDirty)
|
|
119
|
+
}, null, 8, ["class", "input-class", "use-grouping", "mode", "currency", "min-fraction-digits", "max-fraction-digits", "model-value", "placeholder", "disabled", "aria-invalid", "onBlur"]),
|
|
120
|
+
unref(showErrors) ? (openBlock(), createElementBlock("small", _hoisted_4, toDisplayString(unref(projectedErrors)), 1)) : createCommentVNode("", true)
|
|
124
121
|
]);
|
|
125
122
|
};
|
|
126
123
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfNumber.vue.js","sources":["../../../src/vue/primevue/JfNumber.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfNumber\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed,
|
|
1
|
+
{"version":3,"file":"JfNumber.vue.js","sources":["../../../src/vue/primevue/JfNumber.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfNumber\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, getCurrentInstance } from \"vue\";\nimport { useDerive } from \"../composables/useDerive\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { useDirtyValidation } from \"../composables/useDirtyValidation\";\nimport InputNumber from \"primevue/inputnumber\";\n\n// Access props from the component instance\nconst instance = getCurrentInstance()!;\nconst props = instance.props as unknown as ControlProps;\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);\n\nconst options = computed(\n () =>\n (control.value.uischema as { options?: Record<string, unknown> })\n ?.options ?? {},\n);\n\nconst placeholder = computed<string | undefined>(\n () => (options.value.placeholder as string) ?? control.value.description,\n);\n\n// Add derive functionality\nuseDerive({ control, handleChange, data: projectedData });\n\n// Currency and decimal configuration\nconst mode = computed(() => {\n if (options.value.currency) return \"currency\";\n if (options.value.decimal || typeof options.value.precision === \"number\")\n return \"decimal\";\n return undefined;\n});\n\nconst currency = computed(() =>\n typeof options.value.currency === \"string\" ? options.value.currency : \"USD\",\n);\n\nconst minFractionDigits = computed(() => {\n // For integer types, no fractional digits\n if (control.value.schema?.type === \"integer\") return 0;\n if (mode.value === \"currency\") return 2;\n if (typeof options.value.precision === \"number\")\n return options.value.precision;\n return undefined;\n});\n\nconst maxFractionDigits = computed(() => {\n // For integer types, no fractional digits\n if (control.value.schema?.type === \"integer\") return 0;\n if (mode.value === \"currency\") return 2;\n if (typeof options.value.precision === \"number\")\n return options.value.precision;\n return undefined;\n});\n\nconst useGrouping = computed(() => {\n // Enable grouping for currency by default, or if explicitly set\n if (mode.value === \"currency\") return true;\n return options.value.useGrouping === true;\n});\n\n// Track user interaction — errors only show after blur\nconst { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);\n\nconst onNumber = (val: number | null) => {\n handleChange(control.value.path, val ?? undefined);\n};\n</script>\n\n<template>\n <div class=\"flex flex-column gap-2\">\n <label v-if=\"projectedLabel\" class=\"text-color text-left\">{{\n projectedLabel\n }}</label>\n <div v-if=\"control.description\" class=\"text-color-secondary text-left\">\n {{ control.description }}\n </div>\n <InputNumber\n :class=\"['w-full', { 'p-invalid': showErrors }]\"\n :input-class=\"['w-full', { 'p-invalid': showErrors }]\"\n :use-grouping=\"useGrouping\"\n :mode=\"mode\"\n :currency=\"currency\"\n :min-fraction-digits=\"minFractionDigits\"\n :max-fraction-digits=\"maxFractionDigits\"\n :model-value=\"typeof projectedData === 'number' ? projectedData : null\"\n :placeholder=\"placeholder\"\n :disabled=\"!control.enabled\"\n :aria-invalid=\"showErrors || undefined\"\n @update:model-value=\"onNumber\"\n @blur=\"markDirty\"\n />\n <small v-if=\"showErrors\" class=\"p-error\">{{ projectedErrors }}</small>\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createVNode"],"mappings":";;;;;;;;;;;;;;;;;;;AACA,MAAA,cAAe;AAAA,EACb,MAAM;AAAA,EACN,OAAO;AAAA,IACL,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,IAAA;AAAA,IAEX,WAAW;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,IAEX,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,IAEX,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;;;;AAaA,UAAM,WAAW,mBAAA;AACjB,UAAM,QAAQ,SAAS;AACvB,UAAM,EAAE,SAAS,cAAc,gBAAA,IAAoB,oBAAoB,KAAK;AAC5E,UAAM,EAAE,eAAe,uBAAuB,cAAc,iBAAiB,mBAAmB,cAAc,SAAS,eAAe;AAEtI,UAAM,UAAU;AAAA,MACd,MACG,QAAQ,MAAM,UACX,WAAW,CAAA;AAAA,IAAC;AAGpB,UAAM,cAAc;AAAA,MAClB,MAAO,QAAQ,MAAM,eAA0B,QAAQ,MAAM;AAAA,IAAA;AAI/D,cAAU,EAAE,SAAS,cAAc,MAAM,eAAe;AAGxD,UAAM,OAAO,SAAS,MAAM;AAC1B,UAAI,QAAQ,MAAM,SAAU,QAAO;AACnC,UAAI,QAAQ,MAAM,WAAW,OAAO,QAAQ,MAAM,cAAc;AAC9D,eAAO;AACT,aAAO;AAAA,IACT,CAAC;AAED,UAAM,WAAW;AAAA,MAAS,MACxB,OAAO,QAAQ,MAAM,aAAa,WAAW,QAAQ,MAAM,WAAW;AAAA,IAAA;AAGxE,UAAM,oBAAoB,SAAS,MAAM;AAEvC,UAAI,QAAQ,MAAM,QAAQ,SAAS,UAAW,QAAO;AACrD,UAAI,KAAK,UAAU,WAAY,QAAO;AACtC,UAAI,OAAO,QAAQ,MAAM,cAAc;AACrC,eAAO,QAAQ,MAAM;AACvB,aAAO;AAAA,IACT,CAAC;AAED,UAAM,oBAAoB,SAAS,MAAM;AAEvC,UAAI,QAAQ,MAAM,QAAQ,SAAS,UAAW,QAAO;AACrD,UAAI,KAAK,UAAU,WAAY,QAAO;AACtC,UAAI,OAAO,QAAQ,MAAM,cAAc;AACrC,eAAO,QAAQ,MAAM;AACvB,aAAO;AAAA,IACT,CAAC;AAED,UAAM,cAAc,SAAS,MAAM;AAEjC,UAAI,KAAK,UAAU,WAAY,QAAO;AACtC,aAAO,QAAQ,MAAM,gBAAgB;AAAA,IACvC,CAAC;AAGD,UAAM,EAAE,YAAY,UAAA,IAAc,mBAAmB,SAAS,eAAe;AAE7E,UAAM,WAAW,CAAC,QAAuB;AACvC,mBAAa,QAAQ,MAAM,MAAM,OAAO,MAAS;AAAA,IACnD;;AAIE,aAAAA,UAAA,GAAAC,mBAuBM,OAvBN,YAuBM;AAAA,QAtBSC,MAAA,cAAA,kBAAbD,mBAEU,SAFV,YAEUE,gBADRD,MAAA,cAAA,CAAc,GAAA,CAAA;QAELA,MAAA,OAAA,EAAQ,eAAnBF,UAAA,GAAAC,mBAEM,OAFN,YAEME,gBADDD,MAAA,OAAA,EAAQ,WAAW,GAAA,CAAA;QAExBE,YAcEF,MAAA,WAAA,GAAA;AAAA,UAbC,gDAAiCA,MAAA,UAAA,EAAA,CAAU,CAAA;AAAA,UAC3C,yCAAuCA,MAAA,UAAA,GAAU;AAAA,UACjD,gBAAc,YAAA;AAAA,UACd,MAAM,KAAA;AAAA,UACN,UAAU,SAAA;AAAA,UACV,uBAAqB,kBAAA;AAAA,UACrB,uBAAqB,kBAAA;AAAA,UACrB,eAAW,OAASA,MAAA,aAAA,MAAa,WAAgBA,MAAA,aAAA,IAAa;AAAA,UAC9D,aAAa,YAAA;AAAA,UACb,UAAQ,CAAGA,MAAA,OAAA,EAAQ;AAAA,UACnB,gBAAcA,MAAA,UAAA,KAAc;AAAA,UAC5B,uBAAoB;AAAA,UACpB,QAAMA,MAAA,SAAA;AAAA,QAAA;QAEIA,MAAA,UAAA,kBAAbD,mBAAsE,SAAtE,YAAsEE,gBAA1BD,MAAA,eAAA,CAAe,GAAA,CAAA;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfText.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/primevue/JfText.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"JfText.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/primevue/JfText.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2MA,wBA6WK"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { defineComponent, getCurrentInstance, computed, inject, ref, watch, createElementBlock, openBlock, createCommentVNode, createBlock, unref, toDisplayString } from "vue";
|
|
1
|
+
import { defineComponent, getCurrentInstance, computed, inject, ref, watch, createElementBlock, openBlock, createCommentVNode, createBlock, unref, toDisplayString, normalizeClass } from "vue";
|
|
2
2
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
3
3
|
import { useProvider } from "../composables/useProvider.js";
|
|
4
4
|
import { useDerive } from "../composables/useDerive.js";
|
|
5
5
|
import { useProjection } from "../composables/useProjection.js";
|
|
6
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation.js";
|
|
6
7
|
import InputText from "primevue/inputtext";
|
|
7
8
|
import AutoComplete from "primevue/autocomplete";
|
|
8
9
|
const _hoisted_1 = { class: "flex flex-column gap-2" };
|
|
@@ -65,7 +66,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
65
66
|
const instance = getCurrentInstance();
|
|
66
67
|
const props = instance.props;
|
|
67
68
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
68
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
|
|
69
|
+
const { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);
|
|
69
70
|
const binding = computed(() => {
|
|
70
71
|
const provider = control.value.uischema?.options?.provider;
|
|
71
72
|
if (provider && typeof provider === "object" && !provider.load) {
|
|
@@ -109,9 +110,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
109
110
|
});
|
|
110
111
|
const isAutocomplete = computed(() => !!binding.value);
|
|
111
112
|
useDerive({ control, handleChange, data: projectedData });
|
|
112
|
-
const
|
|
113
|
-
const hasFocused = ref(false);
|
|
114
|
-
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
113
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
115
114
|
function onInput(val) {
|
|
116
115
|
const newValue = val && val.trim() !== "" ? val : void 0;
|
|
117
116
|
if (projectedData.value !== newValue) {
|
|
@@ -119,13 +118,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
120
|
function onBlur() {
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
markDirty();
|
|
122
|
+
const val = projectedData.value;
|
|
123
|
+
if (typeof val === "string" && val.trim() === "") {
|
|
124
|
+
handleChange(control.value.path, void 0);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
|
-
function onFocus() {
|
|
127
|
-
hasFocused.value = true;
|
|
128
|
-
}
|
|
129
127
|
const onComplete = (event) => {
|
|
130
128
|
query.value = event.query;
|
|
131
129
|
};
|
|
@@ -135,37 +133,35 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
135
133
|
};
|
|
136
134
|
return (_ctx, _cache) => {
|
|
137
135
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
138
|
-
unref(
|
|
136
|
+
unref(projectedLabel) ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(projectedLabel)), 1)) : createCommentVNode("", true),
|
|
139
137
|
unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
|
|
140
138
|
isAutocomplete.value ? (openBlock(), createBlock(unref(AutoComplete), {
|
|
141
139
|
key: 2,
|
|
142
|
-
class: "w-full",
|
|
140
|
+
class: normalizeClass(["w-full", { "p-invalid": unref(showErrors) }]),
|
|
143
141
|
"model-value": unref(projectedData) ?? "",
|
|
144
142
|
suggestions: unref(items),
|
|
145
143
|
"option-label": "label",
|
|
146
144
|
placeholder: placeholder.value,
|
|
147
145
|
disabled: !unref(control).enabled,
|
|
148
|
-
"aria-invalid":
|
|
146
|
+
"aria-invalid": unref(showErrors) || void 0,
|
|
149
147
|
onComplete,
|
|
150
148
|
onItemSelect: onSelect,
|
|
151
149
|
"onUpdate:modelValue": onInput,
|
|
152
|
-
onBlur
|
|
153
|
-
|
|
154
|
-
}, null, 8, ["model-value", "suggestions", "placeholder", "disabled", "aria-invalid"])) : (openBlock(), createBlock(unref(InputText), {
|
|
150
|
+
onBlur
|
|
151
|
+
}, null, 8, ["class", "model-value", "suggestions", "placeholder", "disabled", "aria-invalid"])) : (openBlock(), createBlock(unref(InputText), {
|
|
155
152
|
key: 3,
|
|
156
|
-
class: "w-full",
|
|
153
|
+
class: normalizeClass(["w-full", { "p-invalid": unref(showErrors) }]),
|
|
157
154
|
"model-value": unref(projectedData) ?? "",
|
|
158
155
|
disabled: !unref(control).enabled,
|
|
159
|
-
"aria-invalid":
|
|
156
|
+
"aria-invalid": unref(showErrors) || void 0,
|
|
160
157
|
placeholder: placeholder.value,
|
|
161
158
|
autocapitalize: "off",
|
|
162
159
|
autocomplete: "off",
|
|
163
160
|
spellcheck: "false",
|
|
164
161
|
"onUpdate:modelValue": onInput,
|
|
165
|
-
onBlur
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
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)
|
|
162
|
+
onBlur
|
|
163
|
+
}, null, 8, ["class", "model-value", "disabled", "aria-invalid", "placeholder"])),
|
|
164
|
+
unref(error) ? (openBlock(), createElementBlock("small", _hoisted_4, "Failed: " + toDisplayString(unref(error)), 1)) : unref(showErrors) ? (openBlock(), createElementBlock("small", _hoisted_5, toDisplayString(unref(projectedErrors)), 1)) : createCommentVNode("", true)
|
|
169
165
|
]);
|
|
170
166
|
};
|
|
171
167
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfText.vue.js","sources":["../../../src/vue/primevue/JfText.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfText\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, ref, inject, watch, getCurrentInstance } from \"vue\";\nimport { useProvider } from \"../composables/useProvider\";\nimport { useDerive } from \"../composables/useDerive\";\nimport { useProjection } from \"../composables/useProjection\";\nimport InputText from \"primevue/inputtext\";\nimport AutoComplete from \"primevue/autocomplete\";\n\n// Access props from the component instance\nconst instance = getCurrentInstance()!;\nconst props = instance.props as unknown as ControlProps;\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);\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, data: projectedData });\n\n// Track user interaction
|
|
1
|
+
{"version":3,"file":"JfText.vue.js","sources":["../../../src/vue/primevue/JfText.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfText\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, ref, inject, watch, getCurrentInstance } from \"vue\";\nimport { useProvider } from \"../composables/useProvider\";\nimport { useDerive } from \"../composables/useDerive\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { useDirtyValidation } from \"../composables/useDirtyValidation\";\nimport InputText from \"primevue/inputtext\";\nimport AutoComplete from \"primevue/autocomplete\";\n\n// Access props from the component instance\nconst instance = getCurrentInstance()!;\nconst props = instance.props as unknown as ControlProps;\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);\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, data: projectedData });\n\n// Track user interaction — errors only show after blur\nconst { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);\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 (projectedData.value !== newValue) {\n handleChange(control.value.path, newValue);\n }\n}\n\nfunction onBlur() {\n markDirty();\n // Normalize empty strings to undefined so required validation fires\n const val = projectedData.value;\n if (typeof val === \"string\" && val.trim() === \"\") {\n handleChange(control.value.path, undefined);\n }\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=\"projectedLabel\" class=\"text-color text-left\">{{\n projectedLabel\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', { 'p-invalid': showErrors }]\"\n :model-value=\"projectedData ?? ''\"\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 />\n <InputText\n v-else\n :class=\"['w-full', { 'p-invalid': showErrors }]\"\n :model-value=\"(projectedData as string) ?? ''\"\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 />\n <small v-if=\"error\" class=\"p-error\" role=\"alert\">Failed: {{ error }}</small>\n <small v-else-if=\"showErrors\" class=\"p-error\">{{ projectedErrors }}</small>\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createBlock"],"mappings":"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"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgHA,wBA6NK"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { defineComponent, getCurrentInstance, computed,
|
|
1
|
+
import { defineComponent, getCurrentInstance, computed, createElementBlock, openBlock, createCommentVNode, createVNode, unref, toDisplayString, normalizeClass } from "vue";
|
|
2
2
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
3
3
|
import { useProjection } from "../composables/useProjection.js";
|
|
4
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation.js";
|
|
4
5
|
import Textarea from "primevue/textarea";
|
|
5
6
|
const _hoisted_1 = { class: "flex flex-column gap-2" };
|
|
6
7
|
const _hoisted_2 = {
|
|
@@ -57,12 +58,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
57
58
|
const instance = getCurrentInstance();
|
|
58
59
|
const props = instance.props;
|
|
59
60
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
60
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
|
|
61
|
+
const { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);
|
|
61
62
|
const placeholder = computed(
|
|
62
63
|
() => control.value.uischema?.options?.placeholder ?? control.value.description
|
|
63
64
|
);
|
|
64
|
-
const
|
|
65
|
-
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
65
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
66
66
|
function onInput(val) {
|
|
67
67
|
const newValue = val && val.trim() !== "" ? val : void 0;
|
|
68
68
|
if (projectedData.value !== newValue) {
|
|
@@ -70,24 +70,28 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
function onBlur() {
|
|
73
|
-
|
|
73
|
+
markDirty();
|
|
74
|
+
const val = projectedData.value;
|
|
75
|
+
if (typeof val === "string" && val.trim() === "") {
|
|
76
|
+
handleChange(control.value.path, void 0);
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
return (_ctx, _cache) => {
|
|
76
80
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
77
|
-
unref(
|
|
81
|
+
unref(projectedLabel) ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(projectedLabel)), 1)) : createCommentVNode("", true),
|
|
78
82
|
unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
|
|
79
83
|
createVNode(unref(Textarea), {
|
|
80
|
-
class: "w-full",
|
|
84
|
+
class: normalizeClass(["w-full", { "p-invalid": unref(showErrors) }]),
|
|
81
85
|
"model-value": unref(projectedData) ?? "",
|
|
82
86
|
disabled: !unref(control).enabled,
|
|
83
|
-
"aria-invalid":
|
|
87
|
+
"aria-invalid": unref(showErrors) || void 0,
|
|
84
88
|
placeholder: placeholder.value,
|
|
85
89
|
rows: 4,
|
|
86
90
|
"auto-resize": true,
|
|
87
91
|
"onUpdate:modelValue": onInput,
|
|
88
92
|
onBlur
|
|
89
|
-
}, null, 8, ["model-value", "disabled", "aria-invalid", "placeholder"]),
|
|
90
|
-
showErrors
|
|
93
|
+
}, null, 8, ["class", "model-value", "disabled", "aria-invalid", "placeholder"]),
|
|
94
|
+
unref(showErrors) ? (openBlock(), createElementBlock("small", _hoisted_4, toDisplayString(unref(projectedErrors)), 1)) : createCommentVNode("", true)
|
|
91
95
|
]);
|
|
92
96
|
};
|
|
93
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfTextArea.vue.js","sources":["../../../src/vue/primevue/JfTextArea.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfTextArea\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed,
|
|
1
|
+
{"version":3,"file":"JfTextArea.vue.js","sources":["../../../src/vue/primevue/JfTextArea.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfTextArea\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, getCurrentInstance } from \"vue\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { useDirtyValidation } from \"../composables/useDirtyValidation\";\nimport Textarea from \"primevue/textarea\";\n\n// Access props from the component instance\nconst instance = getCurrentInstance()!;\nconst props = instance.props as unknown as ControlProps;\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);\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 — errors only show after blur\nconst { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);\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 (projectedData.value !== newValue) {\n handleChange(control.value.path, newValue);\n }\n}\n\nfunction onBlur() {\n markDirty();\n // Normalize empty strings to undefined so required validation fires\n const val = projectedData.value;\n if (typeof val === \"string\" && val.trim() === \"\") {\n handleChange(control.value.path, undefined);\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-column gap-2\">\n <label v-if=\"projectedLabel\" class=\"text-color text-left\">{{\n projectedLabel\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', { 'p-invalid': showErrors }]\"\n :model-value=\"(projectedData as string) ?? ''\"\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\">{{ projectedErrors }}</small>\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createVNode"],"mappings":";;;;;;;;;;;;;;;;;;AACA,MAAA,cAAe;AAAA,EACb,MAAM;AAAA,EACN,OAAO;AAAA,IACL,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,IAAA;AAAA,IAEX,WAAW;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,IAEX,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,IAEX,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;;;;AAYA,UAAM,WAAW,mBAAA;AACjB,UAAM,QAAQ,SAAS;AACvB,UAAM,EAAE,SAAS,cAAc,gBAAA,IAAoB,oBAAoB,KAAK;AAC5E,UAAM,EAAE,eAAe,uBAAuB,cAAc,iBAAiB,mBAAmB,cAAc,SAAS,eAAe;AAEtI,UAAM,cAAc;AAAA,MAClB,MACG,QAAQ,MAAM,UAAqD,SAChE,eAAe,QAAQ,MAAM;AAAA,IAAA;AAIrC,UAAM,EAAE,YAAY,UAAA,IAAc,mBAAmB,SAAS,eAAe;AAE7E,aAAS,QAAQ,KAAyB;AAExC,YAAM,WAAW,OAAO,IAAI,KAAA,MAAW,KAAK,MAAM;AAClD,UAAI,cAAc,UAAU,UAAU;AACpC,qBAAa,QAAQ,MAAM,MAAM,QAAQ;AAAA,MAC3C;AAAA,IACF;AAEA,aAAS,SAAS;AAChB,gBAAA;AAEA,YAAM,MAAM,cAAc;AAC1B,UAAI,OAAO,QAAQ,YAAY,IAAI,KAAA,MAAW,IAAI;AAChD,qBAAa,QAAQ,MAAM,MAAM,MAAS;AAAA,MAC5C;AAAA,IACF;;AAIE,aAAAA,UAAA,GAAAC,mBAmBM,OAnBN,YAmBM;AAAA,QAlBSC,MAAA,cAAA,kBAAbD,mBAEU,SAFV,YAEUE,gBADRD,MAAA,cAAA,CAAc,GAAA,CAAA;QAELA,MAAA,OAAA,EAAQ,eAAnBF,UAAA,GAAAC,mBAEM,OAFN,YAEME,gBADDD,MAAA,OAAA,EAAQ,WAAW,GAAA,CAAA;QAExBE,YAUEF,MAAA,QAAA,GAAA;AAAA,UATC,gDAAiCA,MAAA,UAAA,EAAA,CAAU,CAAA;AAAA,UAC3C,eAAcA,MAAA,aAAA,KAAa;AAAA,UAC3B,UAAQ,CAAGA,MAAA,OAAA,EAAQ;AAAA,UACnB,gBAAcA,MAAA,UAAA,KAAc;AAAA,UAC5B,aAAa,YAAA;AAAA,UACb,MAAM;AAAA,UACN,eAAa;AAAA,UACb,uBAAoB;AAAA,UACpB;AAAA,QAAA;QAEUA,MAAA,UAAA,kBAAbD,mBAAsE,SAAtE,YAAsEE,gBAA1BD,MAAA,eAAA,CAAe,GAAA,CAAA;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narrative.io/jsonforms-provider-protocols",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.3",
|
|
4
4
|
"description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Narrative I/O",
|
|
@@ -84,12 +84,14 @@
|
|
|
84
84
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
85
85
|
"@vue/eslint-config-typescript": "^14.6.0",
|
|
86
86
|
"@vue/runtime-core": "^3.5.20",
|
|
87
|
+
"@vue/test-utils": "^2.4.6",
|
|
87
88
|
"bun-git-hooks": "^0.2.19",
|
|
88
89
|
"commitlint": "^20.0.0",
|
|
89
90
|
"eslint": "^9.34.0",
|
|
90
91
|
"eslint-config-prettier": "^10.1.8",
|
|
91
92
|
"eslint-plugin-vue": "^10.4.0",
|
|
92
93
|
"globals": "^16.3.0",
|
|
94
|
+
"happy-dom": "^20.6.3",
|
|
93
95
|
"prettier": "^3.6.2",
|
|
94
96
|
"primevue": "^4.3.8",
|
|
95
97
|
"typescript": "^5.9.2",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ref, computed, type Ref, type ComputedRef } from "vue";
|
|
2
|
+
|
|
3
|
+
export function useDirtyValidation(
|
|
4
|
+
control: Ref<{ errors: string }>,
|
|
5
|
+
errorsOverride?: Ref<string> | ComputedRef<string>,
|
|
6
|
+
) {
|
|
7
|
+
const hasInteracted = ref(false);
|
|
8
|
+
|
|
9
|
+
const showErrors = computed(
|
|
10
|
+
() =>
|
|
11
|
+
hasInteracted.value &&
|
|
12
|
+
!!(errorsOverride ? errorsOverride.value : control.value.errors),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const markDirty = () => {
|
|
16
|
+
hasInteracted.value = true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return { hasInteracted, showErrors, markDirty };
|
|
20
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, type ComputedRef, type Ref } from "vue";
|
|
1
|
+
import { computed, inject, type ComputedRef, type Ref } from "vue";
|
|
2
2
|
import {
|
|
3
3
|
getProjectedValue,
|
|
4
4
|
setProjectedValue,
|
|
@@ -8,11 +8,72 @@ import {
|
|
|
8
8
|
interface ProjectionControl {
|
|
9
9
|
data: unknown;
|
|
10
10
|
path: string;
|
|
11
|
+
errors: string;
|
|
12
|
+
label?: string;
|
|
11
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
14
|
schema: Record<string, any>;
|
|
13
15
|
uischema: { options?: { projection?: string; [key: string]: unknown } };
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
// Minimal AJV ErrorObject shape for filtering
|
|
19
|
+
interface ErrorLike {
|
|
20
|
+
instancePath?: string;
|
|
21
|
+
keyword?: string;
|
|
22
|
+
message?: string;
|
|
23
|
+
params?: { missingProperty?: string };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the display label for a control.
|
|
28
|
+
* Priority: uischema options.label → schemaTitle (projected sub-schema) → control.label.
|
|
29
|
+
*/
|
|
30
|
+
function resolveLabel(
|
|
31
|
+
ctrl: ProjectionControl,
|
|
32
|
+
schemaTitle?: string,
|
|
33
|
+
): string {
|
|
34
|
+
return (
|
|
35
|
+
(ctrl.uischema?.options?.label as string) ??
|
|
36
|
+
schemaTitle ??
|
|
37
|
+
ctrl.label ??
|
|
38
|
+
""
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalize AJV error message fragments into user-friendly text.
|
|
44
|
+
* e.g. "is a required property" → "is required"
|
|
45
|
+
*/
|
|
46
|
+
function normalizeErrors(errors: string): string {
|
|
47
|
+
if (!errors) return errors;
|
|
48
|
+
return errors
|
|
49
|
+
.replace(/is a required property/g, "is required")
|
|
50
|
+
.replace(/must have required property '[^']*'/g, "is required");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Prefix each error message line with the field label so that AJV fragments
|
|
55
|
+
* like "is required" become "Name is required".
|
|
56
|
+
*/
|
|
57
|
+
function prefixErrors(label: string, errors: string): string {
|
|
58
|
+
if (!label || !errors) return errors;
|
|
59
|
+
return errors
|
|
60
|
+
.split("\n")
|
|
61
|
+
.map((line) => `${label} ${line}`)
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert an AJV ErrorObject's instancePath to a dot-separated control path.
|
|
67
|
+
* Replicates the logic from @jsonforms/core getControlPath.
|
|
68
|
+
*/
|
|
69
|
+
function getErrorPath(error: ErrorLike): string {
|
|
70
|
+
let p = (error.instancePath || "").replace(/\//g, ".").replace(/^\./, "");
|
|
71
|
+
if (error.keyword === "required" && error.params?.missingProperty) {
|
|
72
|
+
p = p ? p + "." + error.params.missingProperty : error.params.missingProperty;
|
|
73
|
+
}
|
|
74
|
+
return p;
|
|
75
|
+
}
|
|
76
|
+
|
|
16
77
|
export interface ProjectionResult {
|
|
17
78
|
/** The value at the projected path (for rendering) */
|
|
18
79
|
projectedData: ComputedRef<unknown>;
|
|
@@ -23,6 +84,10 @@ export interface ProjectionResult {
|
|
|
23
84
|
handleProjectedChange: (path: string, value: unknown) => void;
|
|
24
85
|
/** Whether projection is active */
|
|
25
86
|
hasProjection: boolean;
|
|
87
|
+
/** Resolved display label (options.label → projected schema title → control.label) */
|
|
88
|
+
projectedLabel: ComputedRef<string>;
|
|
89
|
+
/** Error string combining base-path and projected sub-path errors */
|
|
90
|
+
projectedErrors: ComputedRef<string>;
|
|
26
91
|
}
|
|
27
92
|
|
|
28
93
|
/**
|
|
@@ -44,14 +109,32 @@ export function useProjection(
|
|
|
44
109
|
| undefined;
|
|
45
110
|
|
|
46
111
|
if (!projection) {
|
|
112
|
+
const label = computed(() => resolveLabel(control.value));
|
|
47
113
|
return {
|
|
48
114
|
projectedData: computed(() => control.value.data),
|
|
49
115
|
projectedSchema: computed(() => control.value.schema),
|
|
50
116
|
handleProjectedChange: handleChange,
|
|
51
117
|
hasProjection: false,
|
|
118
|
+
projectedLabel: label,
|
|
119
|
+
projectedErrors: computed(() =>
|
|
120
|
+
prefixErrors(
|
|
121
|
+
label.value.replace(/\*$/, "").trim(),
|
|
122
|
+
normalizeErrors(control.value.errors),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
52
125
|
};
|
|
53
126
|
}
|
|
54
127
|
|
|
128
|
+
// Inject JSONForms state to access raw AJV errors for projected sub-paths.
|
|
129
|
+
// control.errors only contains errors at the exact control path (e.g. "data_rates"),
|
|
130
|
+
// but projected fields need errors at the full path (e.g. "data_rates.0.video_rate_usd").
|
|
131
|
+
const jsonforms = inject<{ core?: { errors?: ErrorLike[] } } | null>(
|
|
132
|
+
"jsonforms",
|
|
133
|
+
null,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const fullProjectedPath = control.value.path + "." + projection;
|
|
137
|
+
|
|
55
138
|
const projectedData = computed(() =>
|
|
56
139
|
getProjectedValue(control.value.data, projection),
|
|
57
140
|
);
|
|
@@ -60,6 +143,34 @@ export function useProjection(
|
|
|
60
143
|
getProjectedSchema(control.value.schema, projection),
|
|
61
144
|
);
|
|
62
145
|
|
|
146
|
+
const label = computed(() =>
|
|
147
|
+
resolveLabel(control.value, projectedSchema.value?.title),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const projectedErrors = computed(() => {
|
|
151
|
+
const baseErrors = normalizeErrors(control.value.errors || "");
|
|
152
|
+
|
|
153
|
+
const rawErrors = jsonforms?.core?.errors ?? [];
|
|
154
|
+
const matching = rawErrors.filter(
|
|
155
|
+
(err) => getErrorPath(err) === fullProjectedPath,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
let errStr: string;
|
|
159
|
+
if (matching.length === 0) {
|
|
160
|
+
errStr = baseErrors;
|
|
161
|
+
} else {
|
|
162
|
+
const projMsg = matching
|
|
163
|
+
.map((e) =>
|
|
164
|
+
e.keyword === "required" ? "is required" : e.message,
|
|
165
|
+
)
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.join("\n");
|
|
168
|
+
errStr = [baseErrors, projMsg].filter(Boolean).join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return prefixErrors(label.value.replace(/\*$/, "").trim(), errStr);
|
|
172
|
+
});
|
|
173
|
+
|
|
63
174
|
const handleProjectedChange = (path: string, value: unknown) => {
|
|
64
175
|
const fullValue = setProjectedValue(control.value.data, projection, value);
|
|
65
176
|
handleChange(path, fullValue);
|
|
@@ -70,5 +181,7 @@ export function useProjection(
|
|
|
70
181
|
projectedSchema,
|
|
71
182
|
handleProjectedChange,
|
|
72
183
|
hasProjection: true,
|
|
184
|
+
projectedLabel: label,
|
|
185
|
+
projectedErrors,
|
|
73
186
|
};
|
|
74
187
|
}
|
package/src/vue/index.ts
CHANGED
|
@@ -79,6 +79,7 @@ export { useProjection } from "./composables/useProjection";
|
|
|
79
79
|
export type { ProjectionResult } from "./composables/useProjection";
|
|
80
80
|
export { createDataLayer, useDataLayer } from "./composables/useDataLayer";
|
|
81
81
|
export type { DataLayer } from "./composables/useDataLayer";
|
|
82
|
+
export { useDirtyValidation } from "./composables/useDirtyValidation";
|
|
82
83
|
export * from "./testers";
|
|
83
84
|
|
|
84
85
|
// Export individual PrimeVue components using lazy evaluation to avoid circular deps
|
|
@@ -42,15 +42,22 @@ import type { ControlProps } from "@jsonforms/vue";
|
|
|
42
42
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
43
43
|
import { getCurrentInstance } from "vue";
|
|
44
44
|
import { useProjection } from "../composables/useProjection";
|
|
45
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation";
|
|
45
46
|
import Checkbox from "primevue/checkbox";
|
|
46
47
|
|
|
47
48
|
// Access props from the component instance
|
|
48
49
|
const instance = getCurrentInstance()!;
|
|
49
50
|
const props = instance.props as unknown as ControlProps;
|
|
50
51
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
51
|
-
const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
|
|
52
|
+
const { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
// Track user interaction — errors only show after first toggle
|
|
55
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
56
|
+
|
|
57
|
+
const onToggle = (val: boolean) => {
|
|
58
|
+
markDirty();
|
|
59
|
+
handleChange(control.value.path, val);
|
|
60
|
+
};
|
|
54
61
|
</script>
|
|
55
62
|
|
|
56
63
|
<template>
|
|
@@ -59,10 +66,11 @@ const onToggle = (val: boolean) => handleChange(control.value.path, val);
|
|
|
59
66
|
:binary="true"
|
|
60
67
|
:model-value="!!projectedData"
|
|
61
68
|
:disabled="!control.enabled"
|
|
62
|
-
:
|
|
69
|
+
:class="{ 'p-invalid': showErrors }"
|
|
70
|
+
:aria-invalid="showErrors || undefined"
|
|
63
71
|
@update:model-value="onToggle"
|
|
64
72
|
/>
|
|
65
|
-
<label v-if="
|
|
66
|
-
<small v-if="
|
|
73
|
+
<label v-if="projectedLabel">{{ projectedLabel }}</label>
|
|
74
|
+
<small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
67
75
|
</div>
|
|
68
76
|
</template>
|