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

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 (124) hide show
  1. package/dist/core/initFormData.d.ts +17 -0
  2. package/dist/core/initFormData.d.ts.map +1 -0
  3. package/dist/core/initFormData.js +99 -0
  4. package/dist/core/initFormData.js.map +1 -0
  5. package/dist/core/projection.d.ts +4 -0
  6. package/dist/core/projection.d.ts.map +1 -1
  7. package/dist/core/projection.js +17 -14
  8. package/dist/core/projection.js.map +1 -1
  9. package/dist/core/refs.d.ts +58 -0
  10. package/dist/core/refs.d.ts.map +1 -0
  11. package/dist/core/refs.js +70 -0
  12. package/dist/core/refs.js.map +1 -0
  13. package/dist/core/resolveScope.d.ts +6 -0
  14. package/dist/core/resolveScope.d.ts.map +1 -1
  15. package/dist/core/resolveScope.js +14 -8
  16. package/dist/core/resolveScope.js.map +1 -1
  17. package/dist/core/transforms.d.ts.map +1 -1
  18. package/dist/core/transforms.js +3 -1
  19. package/dist/core/transforms.js.map +1 -1
  20. package/dist/core/types.d.ts +1 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/jsonforms-provider-protocols.css +2 -2
  27. package/dist/no-eval-ajv.d.ts +70 -0
  28. package/dist/no-eval-ajv.d.ts.map +1 -0
  29. package/dist/no-eval-ajv.js +247 -0
  30. package/dist/no-eval-ajv.js.map +1 -0
  31. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  32. package/dist/vue/components/ProviderAutocomplete.vue.js +10 -6
  33. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  34. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  35. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  36. package/dist/vue/components/ProviderMultiSelect.vue2.js +17 -9
  37. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  38. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  39. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  40. package/dist/vue/components/ProviderSelect.vue2.js +19 -9
  41. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  42. package/dist/vue/composables/useDataLayer.d.ts +1 -0
  43. package/dist/vue/composables/useDataLayer.d.ts.map +1 -1
  44. package/dist/vue/composables/useDataLayer.js +1 -0
  45. package/dist/vue/composables/useDataLayer.js.map +1 -1
  46. package/dist/vue/composables/useDerive.d.ts +1 -1
  47. package/dist/vue/composables/useDerive.d.ts.map +1 -1
  48. package/dist/vue/composables/useDerive.js +19 -2
  49. package/dist/vue/composables/useDerive.js.map +1 -1
  50. package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
  51. package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
  52. package/dist/vue/composables/useDeriveInitialValue.js +125 -0
  53. package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
  54. package/dist/vue/composables/useDirtyValidation.d.ts +3 -3
  55. package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -1
  56. package/dist/vue/composables/useDirtyValidation.js +2 -2
  57. package/dist/vue/composables/useDirtyValidation.js.map +1 -1
  58. package/dist/vue/composables/useProjection.d.ts +7 -0
  59. package/dist/vue/composables/useProjection.d.ts.map +1 -1
  60. package/dist/vue/composables/useProjection.js +87 -4
  61. package/dist/vue/composables/useProjection.js.map +1 -1
  62. package/dist/vue/composables/useProvider.d.ts +2 -2
  63. package/dist/vue/composables/useProvider.d.ts.map +1 -1
  64. package/dist/vue/composables/useProvider.js +14 -10
  65. package/dist/vue/composables/useProvider.js.map +1 -1
  66. package/dist/vue/index.d.ts +2 -0
  67. package/dist/vue/index.d.ts.map +1 -1
  68. package/dist/vue/index.js +30 -10
  69. package/dist/vue/index.js.map +1 -1
  70. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  71. package/dist/vue/primevue/JfBoolean.vue.js +17 -6
  72. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  73. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  74. package/dist/vue/primevue/JfEnum.vue.js +22 -10
  75. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  76. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  77. package/dist/vue/primevue/JfEnumArray.vue.js +20 -10
  78. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  79. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  80. package/dist/vue/primevue/JfNumber.vue.js +18 -10
  81. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  82. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  83. package/dist/vue/primevue/JfText.vue.js +27 -12
  84. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  85. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  86. package/dist/vue/primevue/JfTextArea.vue.js +15 -9
  87. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  88. package/dist/vue/primevue/index.d.ts.map +1 -1
  89. package/dist/vue/primevue/index.js +93 -16
  90. package/dist/vue/primevue/index.js.map +1 -1
  91. package/dist/vue/utils/autoSelect.js.map +1 -1
  92. package/dist/vue/utils/placeholder.d.ts +17 -0
  93. package/dist/vue/utils/placeholder.d.ts.map +1 -0
  94. package/dist/vue/utils/placeholder.js +17 -0
  95. package/dist/vue/utils/placeholder.js.map +1 -0
  96. package/package.json +10 -2
  97. package/src/core/initFormData.ts +208 -0
  98. package/src/core/projection.ts +33 -22
  99. package/src/core/refs.ts +166 -0
  100. package/src/core/resolveScope.ts +23 -8
  101. package/src/core/transforms.ts +33 -6
  102. package/src/core/types.ts +1 -0
  103. package/src/index.ts +14 -2
  104. package/src/no-eval-ajv.ts +381 -0
  105. package/src/vue/components/ProviderAutocomplete.vue +9 -7
  106. package/src/vue/components/ProviderMultiSelect.vue +20 -15
  107. package/src/vue/components/ProviderSelect.vue +21 -14
  108. package/src/vue/composables/useDataLayer.ts +1 -1
  109. package/src/vue/composables/useDerive.ts +46 -3
  110. package/src/vue/composables/useDeriveInitialValue.ts +195 -0
  111. package/src/vue/composables/useDirtyValidation.ts +8 -3
  112. package/src/vue/composables/useProjection.ts +172 -1
  113. package/src/vue/composables/useProvider.ts +28 -11
  114. package/src/vue/index.ts +28 -9
  115. package/src/vue/primevue/JfBoolean.vue +10 -5
  116. package/src/vue/primevue/JfEnum.vue +23 -14
  117. package/src/vue/primevue/JfEnumArray.vue +22 -17
  118. package/src/vue/primevue/JfNumber.vue +20 -12
  119. package/src/vue/primevue/JfText.vue +31 -16
  120. package/src/vue/primevue/JfTextArea.vue +15 -13
  121. package/src/vue/primevue/index.ts +104 -23
  122. package/src/vue/styles.css +26 -1
  123. package/src/vue/utils/autoSelect.ts +2 -2
  124. package/src/vue/utils/placeholder.ts +42 -0
package/src/vue/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { UISchemaElement } from "@jsonforms/core";
2
2
  import {
3
3
  and,
4
+ isIntegerControl,
4
5
  isNumberControl,
5
6
  isStringControl,
6
7
  or,
@@ -22,25 +23,38 @@ const isIntegerScope = (uischema: unknown, schema: unknown) => {
22
23
  const ui = uischema as { type?: string; scope?: string };
23
24
  if (ui?.type !== "Control" || !ui?.scope) return false;
24
25
 
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- const propertySchema = resolveScopeSchema(ui.scope, schema as Record<string, any>);
26
+ const propertySchema = resolveScopeSchema(
27
+ ui.scope,
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ schema as Record<string, any>,
30
+ );
27
31
  return propertySchema?.type === "integer";
28
32
  };
29
33
 
30
34
  // Create specific testers for each component type
31
35
  const providerSelectTester = rankWith(
32
- 106, // Higher than PrimeVue base (100) to ensure providers take precedence
36
+ 107, // Higher than PrimeVue JfNumber integer renderer (106) so provider wins
33
37
  and(
34
- or(isStringControl, isNumberControl, and(isControl, isIntegerScope)),
38
+ or(
39
+ isStringControl,
40
+ isNumberControl,
41
+ isIntegerControl,
42
+ and(isControl, isIntegerScope),
43
+ ),
35
44
  hasProvider,
36
45
  (uischema) => !uischema?.options?.autocomplete,
37
46
  ),
38
47
  );
39
48
 
40
49
  const providerAutocompleteTester = rankWith(
41
- 107, // Higher than PrimeVue base (100) to ensure providers take precedence
50
+ 108, // Higher than providerSelectTester so autocomplete variant wins when flagged
42
51
  and(
43
- or(isStringControl, isNumberControl, and(isControl, isIntegerScope)),
52
+ or(
53
+ isStringControl,
54
+ isNumberControl,
55
+ isIntegerControl,
56
+ and(isControl, isIntegerScope),
57
+ ),
44
58
  hasProvider,
45
59
  (uischema) => uischema?.options?.autocomplete === true,
46
60
  ),
@@ -53,13 +67,16 @@ const isArrayControl = (uischema: UISchemaElement, schema: unknown) => {
53
67
  return false;
54
68
  }
55
69
 
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- const propertySchema = resolveScopeSchema(controlSchema.scope, schema as Record<string, any>);
70
+ const propertySchema = resolveScopeSchema(
71
+ controlSchema.scope,
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ schema as Record<string, any>,
74
+ );
58
75
  return propertySchema?.type === "array";
59
76
  };
60
77
 
61
78
  const providerMultiSelectTester = rankWith(
62
- 108, // Highest priority for array controls with providers
79
+ 109, // Highest priority for array controls with providers
63
80
  and(isArrayControl, hasProvider),
64
81
  );
65
82
 
@@ -79,6 +96,8 @@ export { useProjection } from "./composables/useProjection";
79
96
  export type { ProjectionResult } from "./composables/useProjection";
80
97
  export { createDataLayer, useDataLayer } from "./composables/useDataLayer";
81
98
  export type { DataLayer } from "./composables/useDataLayer";
99
+ export { useDeriveInitialValue } from "./composables/useDeriveInitialValue";
100
+ export type { DeriveInitialValueCfg } from "./composables/useDeriveInitialValue";
82
101
  export { useDirtyValidation } from "./composables/useDirtyValidation";
83
102
  export * from "./testers";
84
103
 
@@ -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>
@@ -45,16 +45,23 @@ import { useJsonFormsControl } from "@jsonforms/vue";
45
45
  import { computed, inject, getCurrentInstance, watch } from "vue";
46
46
  import { useProvider } from "../composables/useProvider";
47
47
  import { useDerive } from "../composables/useDerive";
48
+ import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
48
49
  import { useProjection } from "../composables/useProjection";
49
50
  import { useDirtyValidation } from "../composables/useDirtyValidation";
50
51
  import { shouldAutoSelect } from "../utils/autoSelect";
52
+ import { resolvePlaceholder } from "../utils/placeholder";
51
53
  import Dropdown from "primevue/dropdown";
52
54
 
53
55
  // Access props from the component instance
54
56
  const instance = getCurrentInstance()!;
55
57
  const props = instance.props as unknown as ControlProps;
56
58
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
57
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
59
+ const {
60
+ projectedData,
61
+ handleProjectedChange: handleChange,
62
+ projectedErrors,
63
+ projectedLabel,
64
+ } = useProjection(control, rawHandleChange);
58
65
 
59
66
  type Opt = { label: string; value: unknown };
60
67
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -120,14 +127,15 @@ const {
120
127
  } = useProvider(binding, {
121
128
  data: rootData,
122
129
  path: control.value.path,
123
- dependsOnValues: depValues.value,
130
+ dependsOnValues: depValues,
124
131
  });
125
132
 
126
133
  const placeholder = computed<string | undefined>(() => {
127
134
  if (loading.value) return "Loading…";
128
- return (
129
- (control.value.uischema as { options?: { placeholder?: string } })?.options
130
- ?.placeholder ?? control.value.description
135
+ return resolvePlaceholder(
136
+ control.value.uischema,
137
+ projectedLabel.value,
138
+ "select",
131
139
  );
132
140
  });
133
141
 
@@ -142,6 +150,9 @@ const options = computed(() => {
142
150
  // Add derive functionality
143
151
  useDerive({ control, handleChange, data: projectedData });
144
152
 
153
+ // Add deriveInitialValue — async API-based initial value seeding
154
+ useDeriveInitialValue({ control, handleChange });
155
+
145
156
  // Auto-select when provider returns only one item (enabled by default)
146
157
  watch(
147
158
  [providerItems, loading],
@@ -158,11 +169,11 @@ watch(
158
169
  handleChange(control.value.path, valueToSelect);
159
170
  }
160
171
  },
161
- { immediate: true }
172
+ { immediate: true },
162
173
  );
163
174
 
164
175
  // Track user interaction — errors only show after blur
165
- const { showErrors, markDirty } = useDirtyValidation(control);
176
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
166
177
 
167
178
  const onSelect = (val: unknown) => {
168
179
  handleChange(control.value.path, val);
@@ -170,15 +181,13 @@ const onSelect = (val: unknown) => {
170
181
  </script>
171
182
 
172
183
  <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">
184
+ <div class="jf-control">
185
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
186
+ <div v-if="control.description" class="jf-description">
178
187
  {{ control.description }}
179
188
  </div>
180
189
  <Dropdown
181
- :class="['w-full', { 'p-invalid': showErrors }]"
190
+ :class="['w-full!', { 'p-invalid': showErrors }]"
182
191
  :options="options"
183
192
  option-label="label"
184
193
  option-value="value"
@@ -193,6 +202,6 @@ const onSelect = (val: unknown) => {
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">{{ control.errors }}</small>
205
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
197
206
  </div>
198
207
  </template>
@@ -47,13 +47,19 @@ import { useDerive } from "../composables/useDerive";
47
47
  import { useProjection } from "../composables/useProjection";
48
48
  import { useDirtyValidation } from "../composables/useDirtyValidation";
49
49
  import { shouldAutoSelectMulti } from "../utils/autoSelect";
50
+ import { resolvePlaceholder } from "../utils/placeholder";
50
51
  import MultiSelect from "primevue/multiselect";
51
52
 
52
53
  // Access props from the component instance
53
54
  const instance = getCurrentInstance()!;
54
55
  const props = instance.props as unknown as ControlProps;
55
56
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
56
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
57
+ const {
58
+ projectedData,
59
+ handleProjectedChange: handleChange,
60
+ projectedErrors,
61
+ projectedLabel,
62
+ } = useProjection(control, rawHandleChange);
57
63
 
58
64
  type Opt = { label: string; value: unknown };
59
65
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -119,7 +125,7 @@ const {
119
125
  } = useProvider(binding, {
120
126
  data: rootData,
121
127
  path: control.value.path,
122
- dependsOnValues: depValues.value,
128
+ dependsOnValues: depValues,
123
129
  });
124
130
 
125
131
  const options = computed(() => {
@@ -134,7 +140,7 @@ const options = computed(() => {
134
140
  useDerive({ control, handleChange, data: projectedData });
135
141
 
136
142
  // Track user interaction — errors only show after first change
137
- const { showErrors, markDirty } = useDirtyValidation(control);
143
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
138
144
 
139
145
  // Auto-select when provider returns only one item (opt-in for multiselect)
140
146
  watch(
@@ -145,21 +151,24 @@ watch(
145
151
  control.value.uischema?.options?.autoSelectSingle === true,
146
152
  isLoading,
147
153
  items,
148
- currentValue: Array.isArray(projectedData.value) ? projectedData.value : [],
154
+ currentValue: Array.isArray(projectedData.value)
155
+ ? projectedData.value
156
+ : [],
149
157
  });
150
158
 
151
159
  if (valueToSelect !== null) {
152
160
  handleChange(control.value.path, valueToSelect);
153
161
  }
154
162
  },
155
- { immediate: true }
163
+ { immediate: true },
156
164
  );
157
165
 
158
166
  const placeholder = computed<string | undefined>(() => {
159
167
  if (loading.value) return "Loading…";
160
- return (
161
- (control.value.uischema as { options?: { placeholder?: string } })?.options
162
- ?.placeholder ?? control.value.description
168
+ return resolvePlaceholder(
169
+ control.value.uischema,
170
+ projectedLabel.value,
171
+ "select",
163
172
  );
164
173
  });
165
174
 
@@ -190,17 +199,15 @@ const model = computed<unknown[]>({
190
199
  </script>
191
200
 
192
201
  <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">
202
+ <div class="jf-control">
203
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
204
+ <div v-if="control.description" class="jf-description">
198
205
  {{ control.description }}
199
206
  </div>
200
207
 
201
208
  <MultiSelect
202
209
  v-model="model"
203
- :class="['w-full', { 'p-invalid': showErrors }]"
210
+ :class="['w-full!', { 'p-invalid': showErrors }]"
204
211
  :options="options"
205
212
  option-label="label"
206
213
  option-value="value"
@@ -214,8 +221,6 @@ const model = computed<unknown[]>({
214
221
  <small v-if="error" class="p-error" role="alert"
215
222
  >Failed to load: {{ error }}</small
216
223
  >
217
- <small v-else-if="showErrors" class="p-error">{{
218
- control.errors
219
- }}</small>
224
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
220
225
  </div>
221
226
  </template>
@@ -42,15 +42,22 @@ import type { ControlProps } from "@jsonforms/vue";
42
42
  import { useJsonFormsControl } from "@jsonforms/vue";
43
43
  import { computed, getCurrentInstance } from "vue";
44
44
  import { useDerive } from "../composables/useDerive";
45
+ import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
45
46
  import { useProjection } from "../composables/useProjection";
46
47
  import { useDirtyValidation } from "../composables/useDirtyValidation";
48
+ import { resolvePlaceholder } from "../utils/placeholder";
47
49
  import InputNumber from "primevue/inputnumber";
48
50
 
49
51
  // Access props from the component instance
50
52
  const instance = getCurrentInstance()!;
51
53
  const props = instance.props as unknown as ControlProps;
52
54
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
53
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
55
+ const {
56
+ projectedData,
57
+ handleProjectedChange: handleChange,
58
+ projectedErrors,
59
+ projectedLabel,
60
+ } = useProjection(control, rawHandleChange);
54
61
 
55
62
  const options = computed(
56
63
  () =>
@@ -58,13 +65,16 @@ const options = computed(
58
65
  ?.options ?? {},
59
66
  );
60
67
 
61
- const placeholder = computed<string | undefined>(
62
- () => (options.value.placeholder as string) ?? control.value.description,
68
+ const placeholder = computed<string | undefined>(() =>
69
+ resolvePlaceholder(control.value.uischema, projectedLabel.value, "input"),
63
70
  );
64
71
 
65
72
  // Add derive functionality
66
73
  useDerive({ control, handleChange, data: projectedData });
67
74
 
75
+ // Add deriveInitialValue — async API-based initial value seeding
76
+ useDeriveInitialValue({ control, handleChange });
77
+
68
78
  // Currency and decimal configuration
69
79
  const mode = computed(() => {
70
80
  if (options.value.currency) return "currency";
@@ -102,7 +112,7 @@ const useGrouping = computed(() => {
102
112
  });
103
113
 
104
114
  // Track user interaction — errors only show after blur
105
- const { showErrors, markDirty } = useDirtyValidation(control);
115
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
106
116
 
107
117
  const onNumber = (val: number | null) => {
108
118
  handleChange(control.value.path, val ?? undefined);
@@ -110,16 +120,14 @@ const onNumber = (val: number | null) => {
110
120
  </script>
111
121
 
112
122
  <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">
123
+ <div class="jf-control">
124
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
125
+ <div v-if="control.description" class="jf-description">
118
126
  {{ control.description }}
119
127
  </div>
120
128
  <InputNumber
121
- :class="['w-full', { 'p-invalid': showErrors }]"
122
- :input-class="['w-full', { 'p-invalid': showErrors }]"
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"
@@ -132,6 +140,6 @@ const onNumber = (val: number | null) => {
132
140
  @update:model-value="onNumber"
133
141
  @blur="markDirty"
134
142
  />
135
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
143
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
136
144
  </div>
137
145
  </template>
@@ -43,8 +43,10 @@ import { useJsonFormsControl } from "@jsonforms/vue";
43
43
  import { computed, ref, inject, watch, getCurrentInstance } from "vue";
44
44
  import { useProvider } from "../composables/useProvider";
45
45
  import { useDerive } from "../composables/useDerive";
46
+ import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
46
47
  import { useProjection } from "../composables/useProjection";
47
48
  import { useDirtyValidation } from "../composables/useDirtyValidation";
49
+ import { resolvePlaceholder } from "../utils/placeholder";
48
50
  import InputText from "primevue/inputtext";
49
51
  import AutoComplete from "primevue/autocomplete";
50
52
 
@@ -52,7 +54,12 @@ import AutoComplete from "primevue/autocomplete";
52
54
  const instance = getCurrentInstance()!;
53
55
  const props = instance.props as unknown as ControlProps;
54
56
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
55
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
57
+ const {
58
+ projectedData,
59
+ handleProjectedChange: handleChange,
60
+ projectedErrors,
61
+ projectedLabel,
62
+ } = useProjection(control, rawHandleChange);
56
63
 
57
64
  // Provider support for autocomplete functionality
58
65
  const binding = computed(() => {
@@ -98,28 +105,38 @@ const { items, loading, error, reload } = useProvider(binding, {
98
105
  data: rootData,
99
106
  path: control.value.path,
100
107
  uiQuery: query.value,
101
- dependsOnValues: depValues.value,
108
+ dependsOnValues: depValues,
102
109
  });
103
110
 
104
111
  watch(query, () => {
105
112
  if (binding.value?.load === "query") reload();
106
113
  });
107
114
 
115
+ const isAutocomplete = computed(() => !!binding.value);
116
+
108
117
  const placeholder = computed<string | undefined>(() => {
109
118
  if (loading.value) return "Loading…";
110
- return (
111
- (control.value.uischema as { options?: { placeholder?: string } })?.options
112
- ?.placeholder ?? control.value.description
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",
113
129
  );
114
130
  });
115
131
 
116
- const isAutocomplete = computed(() => !!binding.value);
117
-
118
132
  // Add derive functionality
119
133
  useDerive({ control, handleChange, data: projectedData });
120
134
 
135
+ // Add deriveInitialValue — async API-based initial value seeding
136
+ useDeriveInitialValue({ control, handleChange });
137
+
121
138
  // Track user interaction — errors only show after blur
122
- const { showErrors, markDirty } = useDirtyValidation(control);
139
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
123
140
 
124
141
  function onInput(val: string | undefined) {
125
142
  // Convert empty strings to undefined for proper required field validation
@@ -150,16 +167,14 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
150
167
  </script>
151
168
 
152
169
  <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">
170
+ <div class="jf-control">
171
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
172
+ <div v-if="control.description" class="jf-description">
158
173
  {{ control.description }}
159
174
  </div>
160
175
  <AutoComplete
161
176
  v-if="isAutocomplete"
162
- :class="['w-full', { 'p-invalid': showErrors }]"
177
+ :class="['w-full!', { 'p-invalid': showErrors }]"
163
178
  :model-value="projectedData ?? ''"
164
179
  :suggestions="items"
165
180
  option-label="label"
@@ -173,7 +188,7 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
173
188
  />
174
189
  <InputText
175
190
  v-else
176
- :class="['w-full', { 'p-invalid': showErrors }]"
191
+ :class="['w-full!', { 'p-invalid': showErrors }]"
177
192
  :model-value="(projectedData as string) ?? ''"
178
193
  :disabled="!control.enabled"
179
194
  :aria-invalid="showErrors || undefined"
@@ -185,6 +200,6 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
185
200
  @blur="onBlur"
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">{{ control.errors }}</small>
203
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
189
204
  </div>
190
205
  </template>
@@ -43,22 +43,26 @@ import { useJsonFormsControl } from "@jsonforms/vue";
43
43
  import { computed, getCurrentInstance } from "vue";
44
44
  import { useProjection } from "../composables/useProjection";
45
45
  import { useDirtyValidation } from "../composables/useDirtyValidation";
46
+ import { resolvePlaceholder } from "../utils/placeholder";
46
47
  import Textarea from "primevue/textarea";
47
48
 
48
49
  // Access props from the component instance
49
50
  const instance = getCurrentInstance()!;
50
51
  const props = instance.props as unknown as ControlProps;
51
52
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
52
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
53
+ const {
54
+ projectedData,
55
+ handleProjectedChange: handleChange,
56
+ projectedErrors,
57
+ projectedLabel,
58
+ } = useProjection(control, rawHandleChange);
53
59
 
54
- const placeholder = computed<string | undefined>(
55
- () =>
56
- (control.value.uischema as { options?: { placeholder?: string } })?.options
57
- ?.placeholder ?? control.value.description,
60
+ const placeholder = computed<string | undefined>(() =>
61
+ resolvePlaceholder(control.value.uischema, projectedLabel.value, "input"),
58
62
  );
59
63
 
60
64
  // Track user interaction — errors only show after blur
61
- const { showErrors, markDirty } = useDirtyValidation(control);
65
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
62
66
 
63
67
  function onInput(val: string | undefined) {
64
68
  // Convert empty strings to undefined for proper required field validation
@@ -79,15 +83,13 @@ function onBlur() {
79
83
  </script>
80
84
 
81
85
  <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">
86
+ <div class="jf-control">
87
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
88
+ <div v-if="control.description" class="jf-description">
87
89
  {{ control.description }}
88
90
  </div>
89
91
  <Textarea
90
- :class="['w-full', { 'p-invalid': showErrors }]"
92
+ :class="['w-full!', { 'p-invalid': showErrors }]"
91
93
  :model-value="(projectedData as string) ?? ''"
92
94
  :disabled="!control.enabled"
93
95
  :aria-invalid="showErrors || undefined"
@@ -97,6 +99,6 @@ function onBlur() {
97
99
  @update:model-value="onInput"
98
100
  @blur="onBlur"
99
101
  />
100
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
102
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
101
103
  </div>
102
104
  </template>