@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
|
@@ -22,14 +22,17 @@ export default {
|
|
|
22
22
|
renderers: {
|
|
23
23
|
type: Array,
|
|
24
24
|
required: false,
|
|
25
|
+
default: undefined,
|
|
25
26
|
},
|
|
26
27
|
cells: {
|
|
27
28
|
type: Array,
|
|
28
29
|
required: false,
|
|
30
|
+
default: undefined,
|
|
29
31
|
},
|
|
30
32
|
config: {
|
|
31
33
|
type: Object,
|
|
32
34
|
required: false,
|
|
35
|
+
default: undefined,
|
|
33
36
|
},
|
|
34
37
|
},
|
|
35
38
|
};
|
|
@@ -39,16 +42,25 @@ export default {
|
|
|
39
42
|
import type { JsonSchema } from "@jsonforms/core";
|
|
40
43
|
import type { ControlProps } from "@jsonforms/vue";
|
|
41
44
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
42
|
-
import { computed,
|
|
45
|
+
import { computed, inject, getCurrentInstance, watch } from "vue";
|
|
43
46
|
import { useProvider } from "../composables/useProvider";
|
|
44
47
|
import { useDerive } from "../composables/useDerive";
|
|
48
|
+
import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
|
|
49
|
+
import { useProjection } from "../composables/useProjection";
|
|
50
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation";
|
|
45
51
|
import { shouldAutoSelect } from "../utils/autoSelect";
|
|
46
52
|
import Dropdown from "primevue/dropdown";
|
|
47
53
|
|
|
48
54
|
// Access props from the component instance
|
|
49
55
|
const instance = getCurrentInstance()!;
|
|
50
56
|
const props = instance.props as unknown as ControlProps;
|
|
51
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
57
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
58
|
+
const {
|
|
59
|
+
projectedData,
|
|
60
|
+
handleProjectedChange: handleChange,
|
|
61
|
+
projectedErrors,
|
|
62
|
+
projectedLabel,
|
|
63
|
+
} = useProjection(control, rawHandleChange);
|
|
52
64
|
|
|
53
65
|
type Opt = { label: string; value: unknown };
|
|
54
66
|
const toOptions = (schema?: JsonSchema): Opt[] => {
|
|
@@ -134,7 +146,10 @@ const options = computed(() => {
|
|
|
134
146
|
});
|
|
135
147
|
|
|
136
148
|
// Add derive functionality
|
|
137
|
-
useDerive({ control, handleChange });
|
|
149
|
+
useDerive({ control, handleChange, data: projectedData });
|
|
150
|
+
|
|
151
|
+
// Add deriveInitialValue — async API-based initial value seeding
|
|
152
|
+
useDeriveInitialValue({ control, handleChange });
|
|
138
153
|
|
|
139
154
|
// Auto-select when provider returns only one item (enabled by default)
|
|
140
155
|
watch(
|
|
@@ -145,7 +160,7 @@ watch(
|
|
|
145
160
|
control.value.uischema?.options?.autoSelectSingle !== false,
|
|
146
161
|
isLoading,
|
|
147
162
|
items,
|
|
148
|
-
currentValue:
|
|
163
|
+
currentValue: projectedData.value,
|
|
149
164
|
});
|
|
150
165
|
|
|
151
166
|
if (valueToSelect !== null) {
|
|
@@ -155,44 +170,36 @@ watch(
|
|
|
155
170
|
{ immediate: true },
|
|
156
171
|
);
|
|
157
172
|
|
|
158
|
-
// Track user interaction
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
173
|
+
// Track user interaction — errors only show after blur
|
|
174
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
162
175
|
|
|
163
176
|
const onSelect = (val: unknown) => {
|
|
164
177
|
handleChange(control.value.path, val);
|
|
165
178
|
};
|
|
166
|
-
|
|
167
|
-
const onBlur = () => {
|
|
168
|
-
hasInteracted.value = true;
|
|
169
|
-
};
|
|
170
179
|
</script>
|
|
171
180
|
|
|
172
181
|
<template>
|
|
173
|
-
<div class="
|
|
174
|
-
<label v-if="
|
|
175
|
-
|
|
176
|
-
}}</label>
|
|
177
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
182
|
+
<div class="jf-control">
|
|
183
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
184
|
+
<div v-if="control.description" class="jf-description">
|
|
178
185
|
{{ control.description }}
|
|
179
186
|
</div>
|
|
180
187
|
<Dropdown
|
|
181
|
-
class="w-full!"
|
|
188
|
+
:class="['w-full!', { 'p-invalid': showErrors }]"
|
|
182
189
|
:options="options"
|
|
183
190
|
option-label="label"
|
|
184
191
|
option-value="value"
|
|
185
|
-
:model-value="
|
|
192
|
+
:model-value="projectedData ?? null"
|
|
186
193
|
:placeholder="placeholder"
|
|
187
194
|
:disabled="!control.enabled || loading"
|
|
188
|
-
:aria-invalid="
|
|
195
|
+
:aria-invalid="showErrors || undefined"
|
|
189
196
|
:show-clear="true"
|
|
190
197
|
@update:model-value="onSelect"
|
|
191
|
-
@blur="
|
|
198
|
+
@blur="markDirty"
|
|
192
199
|
/>
|
|
193
200
|
<small v-if="error" class="p-error" role="alert"
|
|
194
201
|
>Failed to load: {{ error }}</small
|
|
195
202
|
>
|
|
196
|
-
<small v-else-if="showErrors" class="p-error">{{
|
|
203
|
+
<small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
197
204
|
</div>
|
|
198
205
|
</template>
|
|
@@ -21,14 +21,17 @@ export default {
|
|
|
21
21
|
renderers: {
|
|
22
22
|
type: Array,
|
|
23
23
|
required: false,
|
|
24
|
+
default: undefined,
|
|
24
25
|
},
|
|
25
26
|
cells: {
|
|
26
27
|
type: Array,
|
|
27
28
|
required: false,
|
|
29
|
+
default: undefined,
|
|
28
30
|
},
|
|
29
31
|
config: {
|
|
30
32
|
type: Object,
|
|
31
33
|
required: false,
|
|
34
|
+
default: undefined,
|
|
32
35
|
},
|
|
33
36
|
},
|
|
34
37
|
};
|
|
@@ -41,13 +44,21 @@ import { useJsonFormsControl } from "@jsonforms/vue";
|
|
|
41
44
|
import { computed, inject, getCurrentInstance, watch } from "vue";
|
|
42
45
|
import { useProvider } from "../composables/useProvider";
|
|
43
46
|
import { useDerive } from "../composables/useDerive";
|
|
47
|
+
import { useProjection } from "../composables/useProjection";
|
|
48
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation";
|
|
44
49
|
import { shouldAutoSelectMulti } from "../utils/autoSelect";
|
|
45
50
|
import MultiSelect from "primevue/multiselect";
|
|
46
51
|
|
|
47
52
|
// Access props from the component instance
|
|
48
53
|
const instance = getCurrentInstance()!;
|
|
49
54
|
const props = instance.props as unknown as ControlProps;
|
|
50
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
55
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
56
|
+
const {
|
|
57
|
+
projectedData,
|
|
58
|
+
handleProjectedChange: handleChange,
|
|
59
|
+
projectedErrors,
|
|
60
|
+
projectedLabel,
|
|
61
|
+
} = useProjection(control, rawHandleChange);
|
|
51
62
|
|
|
52
63
|
type Opt = { label: string; value: unknown };
|
|
53
64
|
const toOptions = (schema?: JsonSchema): Opt[] => {
|
|
@@ -125,7 +136,10 @@ const options = computed(() => {
|
|
|
125
136
|
});
|
|
126
137
|
|
|
127
138
|
// Add derive functionality
|
|
128
|
-
useDerive({ control, handleChange });
|
|
139
|
+
useDerive({ control, handleChange, data: projectedData });
|
|
140
|
+
|
|
141
|
+
// Track user interaction — errors only show after first change
|
|
142
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
129
143
|
|
|
130
144
|
// Auto-select when provider returns only one item (opt-in for multiselect)
|
|
131
145
|
watch(
|
|
@@ -136,7 +150,9 @@ watch(
|
|
|
136
150
|
control.value.uischema?.options?.autoSelectSingle === true,
|
|
137
151
|
isLoading,
|
|
138
152
|
items,
|
|
139
|
-
currentValue: Array.isArray(
|
|
153
|
+
currentValue: Array.isArray(projectedData.value)
|
|
154
|
+
? projectedData.value
|
|
155
|
+
: [],
|
|
140
156
|
});
|
|
141
157
|
|
|
142
158
|
if (valueToSelect !== null) {
|
|
@@ -165,45 +181,44 @@ const sameSet = (a: unknown[], b: unknown[]) => {
|
|
|
165
181
|
// v-model with guard to avoid recursive updates
|
|
166
182
|
const model = computed<unknown[]>({
|
|
167
183
|
get() {
|
|
168
|
-
const curr = Array.isArray(
|
|
184
|
+
const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
169
185
|
// return a fresh copy so PrimeMultiSelect can't mutate JSONForms' array in place
|
|
170
186
|
return [...curr];
|
|
171
187
|
},
|
|
172
188
|
set(val) {
|
|
173
189
|
const next = Array.isArray(val) ? [...val] : [];
|
|
174
|
-
const curr = Array.isArray(
|
|
175
|
-
if (!sameSet(curr, next))
|
|
190
|
+
const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
191
|
+
if (!sameSet(curr, next)) {
|
|
192
|
+
markDirty();
|
|
193
|
+
handleChange(control.value.path, next);
|
|
194
|
+
}
|
|
176
195
|
},
|
|
177
196
|
});
|
|
178
197
|
</script>
|
|
179
198
|
|
|
180
199
|
<template>
|
|
181
|
-
<div class="
|
|
182
|
-
<label v-if="
|
|
183
|
-
|
|
184
|
-
}}</label>
|
|
185
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
200
|
+
<div class="jf-control">
|
|
201
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
202
|
+
<div v-if="control.description" class="jf-description">
|
|
186
203
|
{{ control.description }}
|
|
187
204
|
</div>
|
|
188
205
|
|
|
189
206
|
<MultiSelect
|
|
190
207
|
v-model="model"
|
|
191
|
-
class="w-full!"
|
|
208
|
+
:class="['w-full!', { 'p-invalid': showErrors }]"
|
|
192
209
|
:options="options"
|
|
193
210
|
option-label="label"
|
|
194
211
|
option-value="value"
|
|
195
212
|
data-key="value"
|
|
196
213
|
display="chip"
|
|
197
214
|
:disabled="!control.enabled || loading"
|
|
198
|
-
:aria-invalid="
|
|
215
|
+
:aria-invalid="showErrors || undefined"
|
|
199
216
|
:placeholder="placeholder"
|
|
200
217
|
/>
|
|
201
218
|
|
|
202
219
|
<small v-if="error" class="p-error" role="alert"
|
|
203
220
|
>Failed to load: {{ error }}</small
|
|
204
221
|
>
|
|
205
|
-
<small v-else-if="
|
|
206
|
-
control.errors
|
|
207
|
-
}}</small>
|
|
222
|
+
<small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
208
223
|
</div>
|
|
209
224
|
</template>
|
|
@@ -21,14 +21,17 @@ export default {
|
|
|
21
21
|
renderers: {
|
|
22
22
|
type: Array,
|
|
23
23
|
required: false,
|
|
24
|
+
default: undefined,
|
|
24
25
|
},
|
|
25
26
|
cells: {
|
|
26
27
|
type: Array,
|
|
27
28
|
required: false,
|
|
29
|
+
default: undefined,
|
|
28
30
|
},
|
|
29
31
|
config: {
|
|
30
32
|
type: Object,
|
|
31
33
|
required: false,
|
|
34
|
+
default: undefined,
|
|
32
35
|
},
|
|
33
36
|
},
|
|
34
37
|
};
|
|
@@ -37,14 +40,23 @@ export default {
|
|
|
37
40
|
<script setup lang="ts">
|
|
38
41
|
import type { ControlProps } from "@jsonforms/vue";
|
|
39
42
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
40
|
-
import { computed,
|
|
43
|
+
import { computed, getCurrentInstance } from "vue";
|
|
41
44
|
import { useDerive } from "../composables/useDerive";
|
|
45
|
+
import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
|
|
46
|
+
import { useProjection } from "../composables/useProjection";
|
|
47
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation";
|
|
42
48
|
import InputNumber from "primevue/inputnumber";
|
|
43
49
|
|
|
44
50
|
// Access props from the component instance
|
|
45
51
|
const instance = getCurrentInstance()!;
|
|
46
52
|
const props = instance.props as unknown as ControlProps;
|
|
47
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
53
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
54
|
+
const {
|
|
55
|
+
projectedData,
|
|
56
|
+
handleProjectedChange: handleChange,
|
|
57
|
+
projectedErrors,
|
|
58
|
+
projectedLabel,
|
|
59
|
+
} = useProjection(control, rawHandleChange);
|
|
48
60
|
|
|
49
61
|
const options = computed(
|
|
50
62
|
() =>
|
|
@@ -57,7 +69,10 @@ const placeholder = computed<string | undefined>(
|
|
|
57
69
|
);
|
|
58
70
|
|
|
59
71
|
// Add derive functionality
|
|
60
|
-
useDerive({ control, handleChange });
|
|
72
|
+
useDerive({ control, handleChange, data: projectedData });
|
|
73
|
+
|
|
74
|
+
// Add deriveInitialValue — async API-based initial value seeding
|
|
75
|
+
useDeriveInitialValue({ control, handleChange });
|
|
61
76
|
|
|
62
77
|
// Currency and decimal configuration
|
|
63
78
|
const mode = computed(() => {
|
|
@@ -95,43 +110,35 @@ const useGrouping = computed(() => {
|
|
|
95
110
|
return options.value.useGrouping === true;
|
|
96
111
|
});
|
|
97
112
|
|
|
98
|
-
// Track user interaction
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
113
|
+
// Track user interaction — errors only show after blur
|
|
114
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
102
115
|
|
|
103
116
|
const onNumber = (val: number | null) => {
|
|
104
117
|
handleChange(control.value.path, val ?? undefined);
|
|
105
118
|
};
|
|
106
|
-
|
|
107
|
-
const onBlur = () => {
|
|
108
|
-
hasInteracted.value = true;
|
|
109
|
-
};
|
|
110
119
|
</script>
|
|
111
120
|
|
|
112
121
|
<template>
|
|
113
|
-
<div class="
|
|
114
|
-
<label v-if="
|
|
115
|
-
|
|
116
|
-
}}</label>
|
|
117
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
122
|
+
<div class="jf-control">
|
|
123
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
124
|
+
<div v-if="control.description" class="jf-description">
|
|
118
125
|
{{ control.description }}
|
|
119
126
|
</div>
|
|
120
127
|
<InputNumber
|
|
121
|
-
class="w-full!"
|
|
122
|
-
input-class="w-full!"
|
|
128
|
+
:class="['w-full!', { 'p-invalid': showErrors }]"
|
|
129
|
+
:input-class="['w-full!', { 'p-invalid': showErrors }]"
|
|
123
130
|
:use-grouping="useGrouping"
|
|
124
131
|
:mode="mode"
|
|
125
132
|
:currency="currency"
|
|
126
133
|
:min-fraction-digits="minFractionDigits"
|
|
127
134
|
:max-fraction-digits="maxFractionDigits"
|
|
128
|
-
:model-value="typeof
|
|
135
|
+
:model-value="typeof projectedData === 'number' ? projectedData : null"
|
|
129
136
|
:placeholder="placeholder"
|
|
130
137
|
:disabled="!control.enabled"
|
|
131
|
-
:aria-invalid="
|
|
138
|
+
:aria-invalid="showErrors || undefined"
|
|
132
139
|
@update:model-value="onNumber"
|
|
133
|
-
@blur="
|
|
140
|
+
@blur="markDirty"
|
|
134
141
|
/>
|
|
135
|
-
<small v-if="showErrors" class="p-error">{{
|
|
142
|
+
<small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
136
143
|
</div>
|
|
137
144
|
</template>
|
|
@@ -21,14 +21,17 @@ export default {
|
|
|
21
21
|
renderers: {
|
|
22
22
|
type: Array,
|
|
23
23
|
required: false,
|
|
24
|
+
default: undefined,
|
|
24
25
|
},
|
|
25
26
|
cells: {
|
|
26
27
|
type: Array,
|
|
27
28
|
required: false,
|
|
29
|
+
default: undefined,
|
|
28
30
|
},
|
|
29
31
|
config: {
|
|
30
32
|
type: Object,
|
|
31
33
|
required: false,
|
|
34
|
+
default: undefined,
|
|
32
35
|
},
|
|
33
36
|
},
|
|
34
37
|
};
|
|
@@ -40,13 +43,22 @@ import { useJsonFormsControl } from "@jsonforms/vue";
|
|
|
40
43
|
import { computed, ref, inject, watch, getCurrentInstance } from "vue";
|
|
41
44
|
import { useProvider } from "../composables/useProvider";
|
|
42
45
|
import { useDerive } from "../composables/useDerive";
|
|
46
|
+
import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
|
|
47
|
+
import { useProjection } from "../composables/useProjection";
|
|
48
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation";
|
|
43
49
|
import InputText from "primevue/inputtext";
|
|
44
50
|
import AutoComplete from "primevue/autocomplete";
|
|
45
51
|
|
|
46
52
|
// Access props from the component instance
|
|
47
53
|
const instance = getCurrentInstance()!;
|
|
48
54
|
const props = instance.props as unknown as ControlProps;
|
|
49
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
55
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
56
|
+
const {
|
|
57
|
+
projectedData,
|
|
58
|
+
handleProjectedChange: handleChange,
|
|
59
|
+
projectedErrors,
|
|
60
|
+
projectedLabel,
|
|
61
|
+
} = useProjection(control, rawHandleChange);
|
|
50
62
|
|
|
51
63
|
// Provider support for autocomplete functionality
|
|
52
64
|
const binding = computed(() => {
|
|
@@ -110,32 +122,31 @@ const placeholder = computed<string | undefined>(() => {
|
|
|
110
122
|
const isAutocomplete = computed(() => !!binding.value);
|
|
111
123
|
|
|
112
124
|
// Add derive functionality
|
|
113
|
-
useDerive({ control, handleChange });
|
|
125
|
+
useDerive({ control, handleChange, data: projectedData });
|
|
114
126
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
const hasFocused = ref(false);
|
|
127
|
+
// Add deriveInitialValue — async API-based initial value seeding
|
|
128
|
+
useDeriveInitialValue({ control, handleChange });
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
// Track user interaction — errors only show after blur
|
|
131
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
120
132
|
|
|
121
133
|
function onInput(val: string | undefined) {
|
|
122
134
|
// Convert empty strings to undefined for proper required field validation
|
|
123
135
|
const newValue = val && val.trim() !== "" ? val : undefined;
|
|
124
|
-
if (
|
|
136
|
+
if (projectedData.value !== newValue) {
|
|
125
137
|
handleChange(control.value.path, newValue);
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
function onBlur() {
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
markDirty();
|
|
143
|
+
// Normalize empty strings to undefined so required validation fires
|
|
144
|
+
const val = projectedData.value;
|
|
145
|
+
if (typeof val === "string" && val.trim() === "") {
|
|
146
|
+
handleChange(control.value.path, undefined);
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
|
|
135
|
-
function onFocus() {
|
|
136
|
-
hasFocused.value = true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
150
|
// Autocomplete specific handlers
|
|
140
151
|
const onComplete = (event: { query: string }) => {
|
|
141
152
|
query.value = event.query;
|
|
@@ -148,43 +159,39 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
|
|
|
148
159
|
</script>
|
|
149
160
|
|
|
150
161
|
<template>
|
|
151
|
-
<div class="
|
|
152
|
-
<label v-if="
|
|
153
|
-
|
|
154
|
-
}}</label>
|
|
155
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
162
|
+
<div class="jf-control">
|
|
163
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
164
|
+
<div v-if="control.description" class="jf-description">
|
|
156
165
|
{{ control.description }}
|
|
157
166
|
</div>
|
|
158
167
|
<AutoComplete
|
|
159
168
|
v-if="isAutocomplete"
|
|
160
|
-
class="w-full!"
|
|
161
|
-
:model-value="
|
|
169
|
+
:class="['w-full!', { 'p-invalid': showErrors }]"
|
|
170
|
+
:model-value="projectedData ?? ''"
|
|
162
171
|
:suggestions="items"
|
|
163
172
|
option-label="label"
|
|
164
173
|
:placeholder="placeholder"
|
|
165
174
|
:disabled="!control.enabled"
|
|
166
|
-
:aria-invalid="
|
|
175
|
+
:aria-invalid="showErrors || undefined"
|
|
167
176
|
@complete="onComplete"
|
|
168
177
|
@item-select="onSelect"
|
|
169
178
|
@update:model-value="onInput"
|
|
170
179
|
@blur="onBlur"
|
|
171
|
-
@focus="onFocus"
|
|
172
180
|
/>
|
|
173
181
|
<InputText
|
|
174
182
|
v-else
|
|
175
|
-
class="w-full!"
|
|
176
|
-
:model-value="
|
|
183
|
+
:class="['w-full!', { 'p-invalid': showErrors }]"
|
|
184
|
+
:model-value="(projectedData as string) ?? ''"
|
|
177
185
|
:disabled="!control.enabled"
|
|
178
|
-
:aria-invalid="
|
|
186
|
+
:aria-invalid="showErrors || undefined"
|
|
179
187
|
:placeholder="placeholder"
|
|
180
188
|
autocapitalize="off"
|
|
181
189
|
autocomplete="off"
|
|
182
190
|
spellcheck="false"
|
|
183
191
|
@update:model-value="onInput"
|
|
184
192
|
@blur="onBlur"
|
|
185
|
-
@focus="onFocus"
|
|
186
193
|
/>
|
|
187
194
|
<small v-if="error" class="p-error" role="alert">Failed: {{ error }}</small>
|
|
188
|
-
<small v-else-if="showErrors" class="p-error">{{
|
|
195
|
+
<small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
189
196
|
</div>
|
|
190
197
|
</template>
|
|
@@ -21,14 +21,17 @@ export default {
|
|
|
21
21
|
renderers: {
|
|
22
22
|
type: Array,
|
|
23
23
|
required: false,
|
|
24
|
+
default: undefined,
|
|
24
25
|
},
|
|
25
26
|
cells: {
|
|
26
27
|
type: Array,
|
|
27
28
|
required: false,
|
|
29
|
+
default: undefined,
|
|
28
30
|
},
|
|
29
31
|
config: {
|
|
30
32
|
type: Object,
|
|
31
33
|
required: false,
|
|
34
|
+
default: undefined,
|
|
32
35
|
},
|
|
33
36
|
},
|
|
34
37
|
};
|
|
@@ -37,13 +40,21 @@ export default {
|
|
|
37
40
|
<script setup lang="ts">
|
|
38
41
|
import type { ControlProps } from "@jsonforms/vue";
|
|
39
42
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
40
|
-
import { computed,
|
|
43
|
+
import { computed, getCurrentInstance } from "vue";
|
|
44
|
+
import { useProjection } from "../composables/useProjection";
|
|
45
|
+
import { useDirtyValidation } from "../composables/useDirtyValidation";
|
|
41
46
|
import Textarea from "primevue/textarea";
|
|
42
47
|
|
|
43
48
|
// Access props from the component instance
|
|
44
49
|
const instance = getCurrentInstance()!;
|
|
45
50
|
const props = instance.props as unknown as ControlProps;
|
|
46
|
-
const { control, handleChange } = useJsonFormsControl(props);
|
|
51
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
52
|
+
const {
|
|
53
|
+
projectedData,
|
|
54
|
+
handleProjectedChange: handleChange,
|
|
55
|
+
projectedErrors,
|
|
56
|
+
projectedLabel,
|
|
57
|
+
} = useProjection(control, rawHandleChange);
|
|
47
58
|
|
|
48
59
|
const placeholder = computed<string | undefined>(
|
|
49
60
|
() =>
|
|
@@ -51,43 +62,44 @@ const placeholder = computed<string | undefined>(
|
|
|
51
62
|
?.placeholder ?? control.value.description,
|
|
52
63
|
);
|
|
53
64
|
|
|
54
|
-
// Track user interaction
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
65
|
+
// Track user interaction — errors only show after blur
|
|
66
|
+
const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
|
|
58
67
|
|
|
59
68
|
function onInput(val: string | undefined) {
|
|
60
69
|
// Convert empty strings to undefined for proper required field validation
|
|
61
70
|
const newValue = val && val.trim() !== "" ? val : undefined;
|
|
62
|
-
if (
|
|
71
|
+
if (projectedData.value !== newValue) {
|
|
63
72
|
handleChange(control.value.path, newValue);
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
function onBlur() {
|
|
68
|
-
|
|
77
|
+
markDirty();
|
|
78
|
+
// Normalize empty strings to undefined so required validation fires
|
|
79
|
+
const val = projectedData.value;
|
|
80
|
+
if (typeof val === "string" && val.trim() === "") {
|
|
81
|
+
handleChange(control.value.path, undefined);
|
|
82
|
+
}
|
|
69
83
|
}
|
|
70
84
|
</script>
|
|
71
85
|
|
|
72
86
|
<template>
|
|
73
|
-
<div class="
|
|
74
|
-
<label v-if="
|
|
75
|
-
|
|
76
|
-
}}</label>
|
|
77
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
87
|
+
<div class="jf-control">
|
|
88
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
89
|
+
<div v-if="control.description" class="jf-description">
|
|
78
90
|
{{ control.description }}
|
|
79
91
|
</div>
|
|
80
92
|
<Textarea
|
|
81
|
-
class="w-full!"
|
|
82
|
-
:model-value="
|
|
93
|
+
:class="['w-full!', { 'p-invalid': showErrors }]"
|
|
94
|
+
:model-value="(projectedData as string) ?? ''"
|
|
83
95
|
:disabled="!control.enabled"
|
|
84
|
-
:aria-invalid="
|
|
96
|
+
:aria-invalid="showErrors || undefined"
|
|
85
97
|
:placeholder="placeholder"
|
|
86
98
|
:rows="4"
|
|
87
99
|
:auto-resize="true"
|
|
88
100
|
@update:model-value="onInput"
|
|
89
101
|
@blur="onBlur"
|
|
90
102
|
/>
|
|
91
|
-
<small v-if="showErrors" class="p-error">{{
|
|
103
|
+
<small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
92
104
|
</div>
|
|
93
105
|
</template>
|