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