@narrative.io/jsonforms-provider-protocols 3.0.0-beta.2 → 3.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/core/projection.d.ts.map +1 -1
  2. package/dist/core/projection.js.map +1 -1
  3. package/dist/core/transforms.d.ts.map +1 -1
  4. package/dist/core/transforms.js +3 -1
  5. package/dist/core/transforms.js.map +1 -1
  6. package/dist/jsonforms-provider-protocols.css +2 -2
  7. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  8. package/dist/vue/components/ProviderAutocomplete.vue.js +8 -5
  9. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  10. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  11. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  12. package/dist/vue/components/ProviderMultiSelect.vue2.js +8 -5
  13. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  14. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  15. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  16. package/dist/vue/components/ProviderSelect.vue2.js +8 -5
  17. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  18. package/dist/vue/composables/useDerive.d.ts +1 -1
  19. package/dist/vue/composables/useDerive.d.ts.map +1 -1
  20. package/dist/vue/composables/useDerive.js +5 -1
  21. package/dist/vue/composables/useDerive.js.map +1 -1
  22. package/dist/vue/composables/useDirtyValidation.d.ts +3 -3
  23. package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -1
  24. package/dist/vue/composables/useDirtyValidation.js +2 -2
  25. package/dist/vue/composables/useDirtyValidation.js.map +1 -1
  26. package/dist/vue/composables/useProjection.d.ts +6 -0
  27. package/dist/vue/composables/useProjection.d.ts.map +1 -1
  28. package/dist/vue/composables/useProjection.js +54 -3
  29. package/dist/vue/composables/useProjection.js.map +1 -1
  30. package/dist/vue/index.d.ts.map +1 -1
  31. package/dist/vue/index.js +10 -2
  32. package/dist/vue/index.js.map +1 -1
  33. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  34. package/dist/vue/primevue/JfBoolean.vue.js +17 -6
  35. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  36. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  37. package/dist/vue/primevue/JfEnum.vue.js +13 -8
  38. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  39. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  40. package/dist/vue/primevue/JfEnumArray.vue.js +13 -8
  41. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  42. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  43. package/dist/vue/primevue/JfNumber.vue.js +14 -9
  44. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  45. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  46. package/dist/vue/primevue/JfText.vue.js +14 -9
  47. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  48. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  49. package/dist/vue/primevue/JfTextArea.vue.js +13 -8
  50. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  51. package/dist/vue/primevue/index.d.ts.map +1 -1
  52. package/dist/vue/primevue/index.js +93 -16
  53. package/dist/vue/primevue/index.js.map +1 -1
  54. package/dist/vue/utils/autoSelect.js.map +1 -1
  55. package/package.json +3 -1
  56. package/src/core/projection.ts +5 -5
  57. package/src/core/transforms.ts +33 -6
  58. package/src/vue/components/ProviderAutocomplete.vue +8 -5
  59. package/src/vue/components/ProviderMultiSelect.vue +12 -7
  60. package/src/vue/components/ProviderSelect.vue +9 -6
  61. package/src/vue/composables/useDerive.ts +16 -3
  62. package/src/vue/composables/useDirtyValidation.ts +8 -3
  63. package/src/vue/composables/useProjection.ts +108 -1
  64. package/src/vue/index.ts +10 -4
  65. package/src/vue/primevue/JfBoolean.vue +10 -5
  66. package/src/vue/primevue/JfEnum.vue +13 -10
  67. package/src/vue/primevue/JfEnumArray.vue +16 -13
  68. package/src/vue/primevue/JfNumber.vue +13 -10
  69. package/src/vue/primevue/JfText.vue +13 -10
  70. package/src/vue/primevue/JfTextArea.vue +12 -9
  71. package/src/vue/primevue/index.ts +104 -23
  72. package/src/vue/styles.css +26 -1
  73. package/src/vue/utils/autoSelect.ts +2 -2
@@ -1,4 +1,11 @@
1
- import { computed, watch, unref, inject, type Ref, type ComputedRef } from "vue";
1
+ import {
2
+ computed,
3
+ watch,
4
+ unref,
5
+ inject,
6
+ type Ref,
7
+ type ComputedRef,
8
+ } from "vue";
2
9
  import { type ControlElement } from "@jsonforms/core";
3
10
  import { useDataLayer } from "./useDataLayer";
4
11
 
@@ -14,7 +21,11 @@ interface DeriveOptions {
14
21
  data?: Ref<unknown> | ComputedRef<unknown>;
15
22
  }
16
23
 
17
- export function useDerive({ control, handleChange, data: dataOverride }: DeriveOptions) {
24
+ export function useDerive({
25
+ control,
26
+ handleChange,
27
+ data: dataOverride,
28
+ }: DeriveOptions) {
18
29
  // Get the root form data from JSONForms context
19
30
  const injectedFormData = inject<{ value: unknown }>("formData", {
20
31
  value: {},
@@ -50,7 +61,9 @@ export function useDerive({ control, handleChange, data: dataOverride }: DeriveO
50
61
  data,
51
62
  extData,
52
63
  );
53
- const compareData = dataOverride ? unref(dataOverride) : control.value.data;
64
+ const compareData = dataOverride
65
+ ? unref(dataOverride)
66
+ : control.value.data;
54
67
  if (derivedValue !== compareData) {
55
68
  handleChange(control.value.path, derivedValue);
56
69
  }
@@ -1,10 +1,15 @@
1
- import { ref, computed, type Ref } from "vue";
1
+ import { ref, computed, type Ref, type ComputedRef } from "vue";
2
2
 
3
- export function useDirtyValidation(control: Ref<{ errors: string }>) {
3
+ export function useDirtyValidation(
4
+ control: Ref<{ errors: string }>,
5
+ errorsOverride?: Ref<string> | ComputedRef<string>,
6
+ ) {
4
7
  const hasInteracted = ref(false);
5
8
 
6
9
  const showErrors = computed(
7
- () => hasInteracted.value && !!control.value.errors,
10
+ () =>
11
+ hasInteracted.value &&
12
+ !!(errorsOverride ? errorsOverride.value : control.value.errors),
8
13
  );
9
14
 
10
15
  const markDirty = () => {
@@ -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,68 @@ 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(ctrl: ProjectionControl, schemaTitle?: string): string {
31
+ return (
32
+ (ctrl.uischema?.options?.label as string) ?? schemaTitle ?? ctrl.label ?? ""
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Normalize AJV error message fragments into user-friendly text.
38
+ * e.g. "is a required property" → "is required"
39
+ */
40
+ function normalizeErrors(errors: string): string {
41
+ if (!errors) return errors;
42
+ return errors
43
+ .replace(/is a required property/g, "is required")
44
+ .replace(/must have required property '[^']*'/g, "is required");
45
+ }
46
+
47
+ /**
48
+ * Prefix each error message line with the field label so that AJV fragments
49
+ * like "is required" become "Name is required".
50
+ */
51
+ function prefixErrors(label: string, errors: string): string {
52
+ if (!label || !errors) return errors;
53
+ return errors
54
+ .split("\n")
55
+ .map((line) => `${label} ${line}`)
56
+ .join("\n");
57
+ }
58
+
59
+ /**
60
+ * Convert an AJV ErrorObject's instancePath to a dot-separated control path.
61
+ * Replicates the logic from @jsonforms/core getControlPath.
62
+ */
63
+ function getErrorPath(error: ErrorLike): string {
64
+ let p = (error.instancePath || "").replace(/\//g, ".").replace(/^\./, "");
65
+ if (error.keyword === "required" && error.params?.missingProperty) {
66
+ p = p
67
+ ? p + "." + error.params.missingProperty
68
+ : error.params.missingProperty;
69
+ }
70
+ return p;
71
+ }
72
+
16
73
  export interface ProjectionResult {
17
74
  /** The value at the projected path (for rendering) */
18
75
  projectedData: ComputedRef<unknown>;
@@ -23,6 +80,10 @@ export interface ProjectionResult {
23
80
  handleProjectedChange: (path: string, value: unknown) => void;
24
81
  /** Whether projection is active */
25
82
  hasProjection: boolean;
83
+ /** Resolved display label (options.label → projected schema title → control.label) */
84
+ projectedLabel: ComputedRef<string>;
85
+ /** Error string combining base-path and projected sub-path errors */
86
+ projectedErrors: ComputedRef<string>;
26
87
  }
27
88
 
28
89
  /**
@@ -44,14 +105,32 @@ export function useProjection(
44
105
  | undefined;
45
106
 
46
107
  if (!projection) {
108
+ const label = computed(() => resolveLabel(control.value));
47
109
  return {
48
110
  projectedData: computed(() => control.value.data),
49
111
  projectedSchema: computed(() => control.value.schema),
50
112
  handleProjectedChange: handleChange,
51
113
  hasProjection: false,
114
+ projectedLabel: label,
115
+ projectedErrors: computed(() =>
116
+ prefixErrors(
117
+ label.value.replace(/\*$/, "").trim(),
118
+ normalizeErrors(control.value.errors),
119
+ ),
120
+ ),
52
121
  };
53
122
  }
54
123
 
124
+ // Inject JSONForms state to access raw AJV errors for projected sub-paths.
125
+ // control.errors only contains errors at the exact control path (e.g. "data_rates"),
126
+ // but projected fields need errors at the full path (e.g. "data_rates.0.video_rate_usd").
127
+ const jsonforms = inject<{ core?: { errors?: ErrorLike[] } } | null>(
128
+ "jsonforms",
129
+ null,
130
+ );
131
+
132
+ const fullProjectedPath = control.value.path + "." + projection;
133
+
55
134
  const projectedData = computed(() =>
56
135
  getProjectedValue(control.value.data, projection),
57
136
  );
@@ -60,6 +139,32 @@ export function useProjection(
60
139
  getProjectedSchema(control.value.schema, projection),
61
140
  );
62
141
 
142
+ const label = computed(() =>
143
+ resolveLabel(control.value, projectedSchema.value?.title),
144
+ );
145
+
146
+ const projectedErrors = computed(() => {
147
+ const baseErrors = normalizeErrors(control.value.errors || "");
148
+
149
+ const rawErrors = jsonforms?.core?.errors ?? [];
150
+ const matching = rawErrors.filter(
151
+ (err) => getErrorPath(err) === fullProjectedPath,
152
+ );
153
+
154
+ let errStr: string;
155
+ if (matching.length === 0) {
156
+ errStr = baseErrors;
157
+ } else {
158
+ const projMsg = matching
159
+ .map((e) => (e.keyword === "required" ? "is required" : e.message))
160
+ .filter(Boolean)
161
+ .join("\n");
162
+ errStr = [baseErrors, projMsg].filter(Boolean).join("\n");
163
+ }
164
+
165
+ return prefixErrors(label.value.replace(/\*$/, "").trim(), errStr);
166
+ });
167
+
63
168
  const handleProjectedChange = (path: string, value: unknown) => {
64
169
  const fullValue = setProjectedValue(control.value.data, projection, value);
65
170
  handleChange(path, fullValue);
@@ -70,5 +175,7 @@ export function useProjection(
70
175
  projectedSchema,
71
176
  handleProjectedChange,
72
177
  hasProjection: true,
178
+ projectedLabel: label,
179
+ projectedErrors,
73
180
  };
74
181
  }
package/src/vue/index.ts CHANGED
@@ -22,8 +22,11 @@ const isIntegerScope = (uischema: unknown, schema: unknown) => {
22
22
  const ui = uischema as { type?: string; scope?: string };
23
23
  if (ui?.type !== "Control" || !ui?.scope) return false;
24
24
 
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- const propertySchema = resolveScopeSchema(ui.scope, schema as Record<string, any>);
25
+ const propertySchema = resolveScopeSchema(
26
+ ui.scope,
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ schema as Record<string, any>,
29
+ );
27
30
  return propertySchema?.type === "integer";
28
31
  };
29
32
 
@@ -53,8 +56,11 @@ const isArrayControl = (uischema: UISchemaElement, schema: unknown) => {
53
56
  return false;
54
57
  }
55
58
 
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- const propertySchema = resolveScopeSchema(controlSchema.scope, schema as Record<string, any>);
59
+ const propertySchema = resolveScopeSchema(
60
+ controlSchema.scope,
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ schema as Record<string, any>,
63
+ );
58
64
  return propertySchema?.type === "array";
59
65
  };
60
66
 
@@ -49,10 +49,15 @@ import Checkbox from "primevue/checkbox";
49
49
  const instance = getCurrentInstance()!;
50
50
  const props = instance.props as unknown as ControlProps;
51
51
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
52
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
52
+ const {
53
+ projectedData,
54
+ handleProjectedChange: handleChange,
55
+ projectedErrors,
56
+ projectedLabel,
57
+ } = useProjection(control, rawHandleChange);
53
58
 
54
59
  // Track user interaction — errors only show after first toggle
55
- const { showErrors, markDirty } = useDirtyValidation(control);
60
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
56
61
 
57
62
  const onToggle = (val: boolean) => {
58
63
  markDirty();
@@ -61,7 +66,7 @@ const onToggle = (val: boolean) => {
61
66
  </script>
62
67
 
63
68
  <template>
64
- <div class="flex items-center gap-2">
69
+ <div class="jf-control" style="flex-direction: row; align-items: center">
65
70
  <Checkbox
66
71
  :binary="true"
67
72
  :model-value="!!projectedData"
@@ -70,7 +75,7 @@ const onToggle = (val: boolean) => {
70
75
  :aria-invalid="showErrors || undefined"
71
76
  @update:model-value="onToggle"
72
77
  />
73
- <label v-if="control.label">{{ control.label }}</label>
74
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
78
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
79
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
75
80
  </div>
76
81
  </template>
@@ -54,7 +54,12 @@ import Dropdown from "primevue/dropdown";
54
54
  const instance = getCurrentInstance()!;
55
55
  const props = instance.props as unknown as ControlProps;
56
56
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
57
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
57
+ const {
58
+ projectedData,
59
+ handleProjectedChange: handleChange,
60
+ projectedErrors,
61
+ projectedLabel,
62
+ } = useProjection(control, rawHandleChange);
58
63
 
59
64
  type Opt = { label: string; value: unknown };
60
65
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -158,11 +163,11 @@ watch(
158
163
  handleChange(control.value.path, valueToSelect);
159
164
  }
160
165
  },
161
- { immediate: true }
166
+ { immediate: true },
162
167
  );
163
168
 
164
169
  // Track user interaction — errors only show after blur
165
- const { showErrors, markDirty } = useDirtyValidation(control);
170
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
166
171
 
167
172
  const onSelect = (val: unknown) => {
168
173
  handleChange(control.value.path, val);
@@ -170,15 +175,13 @@ const onSelect = (val: unknown) => {
170
175
  </script>
171
176
 
172
177
  <template>
173
- <div class="flex flex-column gap-2">
174
- <label v-if="control.label" class="text-color text-left">{{
175
- control.label
176
- }}</label>
177
- <div v-if="control.description" class="text-color-secondary text-left">
178
+ <div class="jf-control">
179
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
180
+ <div v-if="control.description" class="jf-description">
178
181
  {{ control.description }}
179
182
  </div>
180
183
  <Dropdown
181
- :class="['w-full', { 'p-invalid': showErrors }]"
184
+ :class="['w-full!', { 'p-invalid': showErrors }]"
182
185
  :options="options"
183
186
  option-label="label"
184
187
  option-value="value"
@@ -193,6 +196,6 @@ const onSelect = (val: unknown) => {
193
196
  <small v-if="error" class="p-error" role="alert"
194
197
  >Failed to load: {{ error }}</small
195
198
  >
196
- <small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
199
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
197
200
  </div>
198
201
  </template>
@@ -53,7 +53,12 @@ import MultiSelect from "primevue/multiselect";
53
53
  const instance = getCurrentInstance()!;
54
54
  const props = instance.props as unknown as ControlProps;
55
55
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
56
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
56
+ const {
57
+ projectedData,
58
+ handleProjectedChange: handleChange,
59
+ projectedErrors,
60
+ projectedLabel,
61
+ } = useProjection(control, rawHandleChange);
57
62
 
58
63
  type Opt = { label: string; value: unknown };
59
64
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -134,7 +139,7 @@ const options = computed(() => {
134
139
  useDerive({ control, handleChange, data: projectedData });
135
140
 
136
141
  // Track user interaction — errors only show after first change
137
- const { showErrors, markDirty } = useDirtyValidation(control);
142
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
138
143
 
139
144
  // Auto-select when provider returns only one item (opt-in for multiselect)
140
145
  watch(
@@ -145,14 +150,16 @@ watch(
145
150
  control.value.uischema?.options?.autoSelectSingle === true,
146
151
  isLoading,
147
152
  items,
148
- currentValue: Array.isArray(projectedData.value) ? projectedData.value : [],
153
+ currentValue: Array.isArray(projectedData.value)
154
+ ? projectedData.value
155
+ : [],
149
156
  });
150
157
 
151
158
  if (valueToSelect !== null) {
152
159
  handleChange(control.value.path, valueToSelect);
153
160
  }
154
161
  },
155
- { immediate: true }
162
+ { immediate: true },
156
163
  );
157
164
 
158
165
  const placeholder = computed<string | undefined>(() => {
@@ -190,17 +197,15 @@ const model = computed<unknown[]>({
190
197
  </script>
191
198
 
192
199
  <template>
193
- <div class="flex flex-column gap-2">
194
- <label v-if="control.label" class="text-color text-left">{{
195
- control.label
196
- }}</label>
197
- <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">
198
203
  {{ control.description }}
199
204
  </div>
200
205
 
201
206
  <MultiSelect
202
207
  v-model="model"
203
- :class="['w-full', { 'p-invalid': showErrors }]"
208
+ :class="['w-full!', { 'p-invalid': showErrors }]"
204
209
  :options="options"
205
210
  option-label="label"
206
211
  option-value="value"
@@ -214,8 +219,6 @@ const model = computed<unknown[]>({
214
219
  <small v-if="error" class="p-error" role="alert"
215
220
  >Failed to load: {{ error }}</small
216
221
  >
217
- <small v-else-if="showErrors" class="p-error">{{
218
- control.errors
219
- }}</small>
222
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
220
223
  </div>
221
224
  </template>
@@ -50,7 +50,12 @@ import InputNumber from "primevue/inputnumber";
50
50
  const instance = getCurrentInstance()!;
51
51
  const props = instance.props as unknown as ControlProps;
52
52
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
53
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
53
+ const {
54
+ projectedData,
55
+ handleProjectedChange: handleChange,
56
+ projectedErrors,
57
+ projectedLabel,
58
+ } = useProjection(control, rawHandleChange);
54
59
 
55
60
  const options = computed(
56
61
  () =>
@@ -102,7 +107,7 @@ const useGrouping = computed(() => {
102
107
  });
103
108
 
104
109
  // Track user interaction — errors only show after blur
105
- const { showErrors, markDirty } = useDirtyValidation(control);
110
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
106
111
 
107
112
  const onNumber = (val: number | null) => {
108
113
  handleChange(control.value.path, val ?? undefined);
@@ -110,16 +115,14 @@ const onNumber = (val: number | null) => {
110
115
  </script>
111
116
 
112
117
  <template>
113
- <div class="flex flex-column gap-2">
114
- <label v-if="control.label" class="text-color text-left">{{
115
- control.label
116
- }}</label>
117
- <div v-if="control.description" class="text-color-secondary text-left">
118
+ <div class="jf-control">
119
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
120
+ <div v-if="control.description" class="jf-description">
118
121
  {{ control.description }}
119
122
  </div>
120
123
  <InputNumber
121
- :class="['w-full', { 'p-invalid': showErrors }]"
122
- :input-class="['w-full', { 'p-invalid': showErrors }]"
124
+ :class="['w-full!', { 'p-invalid': showErrors }]"
125
+ :input-class="['w-full!', { 'p-invalid': showErrors }]"
123
126
  :use-grouping="useGrouping"
124
127
  :mode="mode"
125
128
  :currency="currency"
@@ -132,6 +135,6 @@ const onNumber = (val: number | null) => {
132
135
  @update:model-value="onNumber"
133
136
  @blur="markDirty"
134
137
  />
135
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
138
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
136
139
  </div>
137
140
  </template>
@@ -52,7 +52,12 @@ import AutoComplete from "primevue/autocomplete";
52
52
  const instance = getCurrentInstance()!;
53
53
  const props = instance.props as unknown as ControlProps;
54
54
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
55
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
55
+ const {
56
+ projectedData,
57
+ handleProjectedChange: handleChange,
58
+ projectedErrors,
59
+ projectedLabel,
60
+ } = useProjection(control, rawHandleChange);
56
61
 
57
62
  // Provider support for autocomplete functionality
58
63
  const binding = computed(() => {
@@ -119,7 +124,7 @@ const isAutocomplete = computed(() => !!binding.value);
119
124
  useDerive({ control, handleChange, data: projectedData });
120
125
 
121
126
  // Track user interaction — errors only show after blur
122
- const { showErrors, markDirty } = useDirtyValidation(control);
127
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
123
128
 
124
129
  function onInput(val: string | undefined) {
125
130
  // Convert empty strings to undefined for proper required field validation
@@ -150,16 +155,14 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
150
155
  </script>
151
156
 
152
157
  <template>
153
- <div class="flex flex-column gap-2">
154
- <label v-if="control.label" class="text-color text-left">{{
155
- control.label
156
- }}</label>
157
- <div v-if="control.description" class="text-color-secondary text-left">
158
+ <div class="jf-control">
159
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
160
+ <div v-if="control.description" class="jf-description">
158
161
  {{ control.description }}
159
162
  </div>
160
163
  <AutoComplete
161
164
  v-if="isAutocomplete"
162
- :class="['w-full', { 'p-invalid': showErrors }]"
165
+ :class="['w-full!', { 'p-invalid': showErrors }]"
163
166
  :model-value="projectedData ?? ''"
164
167
  :suggestions="items"
165
168
  option-label="label"
@@ -173,7 +176,7 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
173
176
  />
174
177
  <InputText
175
178
  v-else
176
- :class="['w-full', { 'p-invalid': showErrors }]"
179
+ :class="['w-full!', { 'p-invalid': showErrors }]"
177
180
  :model-value="(projectedData as string) ?? ''"
178
181
  :disabled="!control.enabled"
179
182
  :aria-invalid="showErrors || undefined"
@@ -185,6 +188,6 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
185
188
  @blur="onBlur"
186
189
  />
187
190
  <small v-if="error" class="p-error" role="alert">Failed: {{ error }}</small>
188
- <small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
191
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
189
192
  </div>
190
193
  </template>
@@ -49,7 +49,12 @@ import Textarea from "primevue/textarea";
49
49
  const instance = getCurrentInstance()!;
50
50
  const props = instance.props as unknown as ControlProps;
51
51
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
52
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
52
+ const {
53
+ projectedData,
54
+ handleProjectedChange: handleChange,
55
+ projectedErrors,
56
+ projectedLabel,
57
+ } = useProjection(control, rawHandleChange);
53
58
 
54
59
  const placeholder = computed<string | undefined>(
55
60
  () =>
@@ -58,7 +63,7 @@ const placeholder = computed<string | undefined>(
58
63
  );
59
64
 
60
65
  // Track user interaction — errors only show after blur
61
- const { showErrors, markDirty } = useDirtyValidation(control);
66
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
62
67
 
63
68
  function onInput(val: string | undefined) {
64
69
  // Convert empty strings to undefined for proper required field validation
@@ -79,15 +84,13 @@ function onBlur() {
79
84
  </script>
80
85
 
81
86
  <template>
82
- <div class="flex flex-column gap-2">
83
- <label v-if="control.label" class="text-color text-left">{{
84
- control.label
85
- }}</label>
86
- <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">
87
90
  {{ control.description }}
88
91
  </div>
89
92
  <Textarea
90
- :class="['w-full', { 'p-invalid': showErrors }]"
93
+ :class="['w-full!', { 'p-invalid': showErrors }]"
91
94
  :model-value="(projectedData as string) ?? ''"
92
95
  :disabled="!control.enabled"
93
96
  :aria-invalid="showErrors || undefined"
@@ -97,6 +100,6 @@ function onBlur() {
97
100
  @update:model-value="onInput"
98
101
  @blur="onBlur"
99
102
  />
100
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
103
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
101
104
  </div>
102
105
  </template>