@narrative.io/jsonforms-provider-protocols 3.0.0-beta.3 → 3.0.0-beta.5

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 +10 -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/useDeriveInitialValue.d.ts +36 -0
  23. package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
  24. package/dist/vue/composables/useDeriveInitialValue.js +125 -0
  25. package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
  26. package/dist/vue/composables/useProjection.d.ts.map +1 -1
  27. package/dist/vue/composables/useProjection.js +1 -3
  28. package/dist/vue/composables/useProjection.js.map +1 -1
  29. package/dist/vue/index.d.ts +2 -0
  30. package/dist/vue/index.d.ts.map +1 -1
  31. package/dist/vue/index.js +12 -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 +14 -3
  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 +12 -5
  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 +10 -5
  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 +11 -6
  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 +13 -6
  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 +10 -5
  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 +1 -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 +13 -6
  61. package/src/vue/composables/useDerive.ts +16 -3
  62. package/src/vue/composables/useDeriveInitialValue.ts +195 -0
  63. package/src/vue/composables/useProjection.ts +6 -12
  64. package/src/vue/index.ts +12 -4
  65. package/src/vue/primevue/JfBoolean.vue +8 -3
  66. package/src/vue/primevue/JfEnum.vue +15 -8
  67. package/src/vue/primevue/JfEnumArray.vue +15 -12
  68. package/src/vue/primevue/JfNumber.vue +11 -8
  69. package/src/vue/primevue/JfText.vue +15 -8
  70. package/src/vue/primevue/JfTextArea.vue +10 -7
  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
@@ -3,6 +3,7 @@ import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
3
  import { useJsonFormsControl } from "@jsonforms/vue";
4
4
  import { computed, inject, watch } from "vue";
5
5
  import { useProvider } from "../composables/useProvider";
6
+ import { useDeriveInitialValue } from "../composables/useDeriveInitialValue";
6
7
  import { useProjection } from "../composables/useProjection";
7
8
  import { shouldAutoSelect } from "../utils/autoSelect";
8
9
  import Dropdown from "primevue/dropdown";
@@ -13,7 +14,10 @@ const props = defineProps<{
13
14
  path: string;
14
15
  }>();
15
16
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
16
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
17
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(
18
+ control,
19
+ rawHandleChange,
20
+ );
17
21
 
18
22
  const binding = computed(() => {
19
23
  const provider = control.value.uischema?.options?.provider;
@@ -46,6 +50,9 @@ const { items, loading, error } = useProvider(binding, {
46
50
 
47
51
  // Provider will automatically reload when rootData changes due to reactive cache key
48
52
 
53
+ // deriveInitialValue — async API-based initial value seeding
54
+ useDeriveInitialValue({ control, handleChange });
55
+
49
56
  // Auto-select when provider returns only one item (enabled by default)
50
57
  watch(
51
58
  [items, loading],
@@ -62,7 +69,7 @@ watch(
62
69
  handleChange(control.value.path, valueToSelect);
63
70
  }
64
71
  },
65
- { immediate: true }
72
+ { immediate: true },
66
73
  );
67
74
 
68
75
  const value = computed({
@@ -81,16 +88,16 @@ const placeholder = computed(() => {
81
88
  </script>
82
89
 
83
90
  <template>
84
- <div class="flex flex-column gap-2">
85
- <label v-if="control.schema.title" class="text-color text-left">{{
91
+ <div class="jf-control">
92
+ <label v-if="control.schema.title" class="jf-label">{{
86
93
  control.schema.title
87
94
  }}</label>
88
- <div v-if="control.description" class="text-color-secondary text-left">
95
+ <div v-if="control.description" class="jf-description">
89
96
  {{ control.description }}
90
97
  </div>
91
98
  <Dropdown
92
99
  v-model="value"
93
- class="w-full"
100
+ class="w-full!"
94
101
  :options="items"
95
102
  option-label="label"
96
103
  option-value="value"
@@ -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
  }
@@ -0,0 +1,195 @@
1
+ import { computed, inject, ref, watch, type Ref, type ComputedRef } from "vue";
2
+ import { type ControlElement } from "@jsonforms/core";
3
+ import { renderTpl, renderObj } from "../../core/templating";
4
+ import { jp } from "../../core/jsonpath";
5
+ import {
6
+ applyTransformPipeline,
7
+ type TransformPipeline,
8
+ } from "../../core/transforms";
9
+ import type { AuthConfig } from "../../core/types";
10
+
11
+ export interface DeriveInitialValueCfg {
12
+ protocol: string;
13
+ config: {
14
+ url: string;
15
+ method?: "GET" | "POST";
16
+ headers?: Record<string, string>;
17
+ query?: Record<string, unknown>;
18
+ body?: unknown;
19
+ auth?: AuthConfig;
20
+ items: string;
21
+ map: { value: string };
22
+ transforms?: TransformPipeline;
23
+ showError?: boolean;
24
+ };
25
+ }
26
+
27
+ interface DeriveInitialValueOptions {
28
+ control: Ref<{
29
+ uischema: ControlElement;
30
+ path: string;
31
+ data: unknown;
32
+ }>;
33
+ handleChange: (path: string, value: unknown) => void;
34
+ data?: Ref<unknown> | ComputedRef<unknown>;
35
+ }
36
+
37
+ function buildAuthHeaders(
38
+ auth?: AuthConfig,
39
+ globalAuth?: Record<string, unknown>,
40
+ ): Record<string, string> {
41
+ const headers: Record<string, string> = {};
42
+ if (!auth) return headers;
43
+
44
+ if (auth.use && globalAuth?.[auth.use]) {
45
+ const globalValue = globalAuth[auth.use];
46
+ const value =
47
+ typeof globalValue === "function" ? globalValue() : globalValue;
48
+ if (auth.use === "apiKey") headers["X-API-Key"] = String(value);
49
+ else if (auth.use === "bearer")
50
+ headers["Authorization"] = `Bearer ${value}`;
51
+ else if (auth.use === "token") headers["Authorization"] = `Token ${value}`;
52
+ return headers;
53
+ }
54
+
55
+ if (auth.bearer) {
56
+ const v = typeof auth.bearer === "function" ? auth.bearer() : auth.bearer;
57
+ headers["Authorization"] = `Bearer ${v}`;
58
+ }
59
+ if (auth.apiKey) {
60
+ const v = typeof auth.apiKey === "function" ? auth.apiKey() : auth.apiKey;
61
+ headers["X-API-Key"] = String(v);
62
+ }
63
+ if (auth.token) {
64
+ const v = typeof auth.token === "function" ? auth.token() : auth.token;
65
+ headers["Authorization"] = `Token ${v}`;
66
+ }
67
+
68
+ return headers;
69
+ }
70
+
71
+ /**
72
+ * Returns true when the template URL contains `{{…}}` placeholders but
73
+ * one or more of those placeholders resolved to an empty string, which
74
+ * means a required data dependency hasn't been set yet.
75
+ */
76
+ function hasUnresolvedTemplates(
77
+ templateUrl: string,
78
+ renderedUrl: string,
79
+ ): boolean {
80
+ if (!templateUrl.includes("{{")) return false;
81
+ // After protocol, empty segments indicate unresolved vars
82
+ const pathPart = renderedUrl.replace(/^https?:\/\/[^/]+/, "");
83
+ if (pathPart.includes("//")) return true;
84
+ // Trailing slash when the template didn't have one
85
+ if (renderedUrl.endsWith("/") && !templateUrl.endsWith("/")) return true;
86
+ return false;
87
+ }
88
+
89
+ export function useDeriveInitialValue({
90
+ control,
91
+ handleChange,
92
+ }: DeriveInitialValueOptions) {
93
+ const injectedFormData = inject<{ value: unknown }>("formData", {
94
+ value: {},
95
+ });
96
+ const rootData = computed(() => injectedFormData.value || {});
97
+ const auth = inject("providerAuth", {}) as Record<string, unknown>;
98
+
99
+ const cfg = computed<DeriveInitialValueCfg | undefined>(() => {
100
+ return control.value.uischema?.options?.deriveInitialValue as
101
+ | DeriveInitialValueCfg
102
+ | undefined;
103
+ });
104
+
105
+ // Compute the resolved URL reactively
106
+ const resolvedUrl = computed<string | null>(() => {
107
+ const c = cfg.value;
108
+ if (!c?.config?.url) return null;
109
+ const rendered = renderTpl(c.config.url, { data: rootData.value });
110
+ if (hasUnresolvedTemplates(c.config.url, rendered)) return null;
111
+ return rendered;
112
+ });
113
+
114
+ const lastFetchedUrl = ref<string | null>(null);
115
+ const loading = ref(false);
116
+ const error = ref<string | undefined>(undefined);
117
+
118
+ watch(
119
+ resolvedUrl,
120
+ async (url) => {
121
+ if (!url || !cfg.value) return;
122
+ // Only fetch when the URL changes (new context).
123
+ // Same URL = same context; don't override user selection.
124
+ if (url === lastFetchedUrl.value) return;
125
+ lastFetchedUrl.value = url;
126
+
127
+ loading.value = true;
128
+ error.value = undefined;
129
+
130
+ try {
131
+ const c = cfg.value.config;
132
+ const fullUrl = new URL(url);
133
+
134
+ // Query params
135
+ const q = renderObj(c.query ?? {}, {
136
+ data: rootData.value,
137
+ }) as Record<string, unknown>;
138
+ for (const [k, v] of Object.entries(q)) {
139
+ if (v !== undefined && v !== "")
140
+ fullUrl.searchParams.set(k, String(v));
141
+ }
142
+
143
+ // Headers
144
+ const baseHeaders = renderObj(c.headers ?? {}, {
145
+ data: rootData.value,
146
+ }) as Record<string, string>;
147
+ const authHeaders = buildAuthHeaders(c.auth, auth);
148
+ const headers = { ...baseHeaders, ...authHeaders };
149
+
150
+ const method = c.method ?? "GET";
151
+ const requestInit: RequestInit = { method, headers };
152
+ if (method !== "GET" && c.body) {
153
+ requestInit.body = JSON.stringify(
154
+ renderObj(c.body, { data: rootData.value }),
155
+ );
156
+ }
157
+
158
+ const res = await fetch(fullUrl.toString(), requestInit);
159
+ if (!res.ok) {
160
+ if (c.showError !== false) {
161
+ throw new Error(`REST ${res.status}`);
162
+ }
163
+ return;
164
+ }
165
+
166
+ const json = await res.json();
167
+ let items = jp(json, c.items);
168
+
169
+ // Apply transforms if provided
170
+ if (c.transforms && c.transforms.length > 0) {
171
+ items = applyTransformPipeline(items, c.transforms);
172
+ }
173
+
174
+ if (items.length === 0) return; // No items → leave field empty
175
+
176
+ // Extract value from first item
177
+ const derivedValue = jp(items[0], c.map.value)[0];
178
+ if (derivedValue !== undefined) {
179
+ handleChange(control.value.path, derivedValue);
180
+ }
181
+ } catch (e) {
182
+ error.value = (e as Error)?.message ?? String(e);
183
+ console.warn(
184
+ `deriveInitialValue fetch failed for ${control.value.path}:`,
185
+ e,
186
+ );
187
+ } finally {
188
+ loading.value = false;
189
+ }
190
+ },
191
+ { immediate: true },
192
+ );
193
+
194
+ return { loading, error };
195
+ }
@@ -27,15 +27,9 @@ interface ErrorLike {
27
27
  * Resolve the display label for a control.
28
28
  * Priority: uischema options.label → schemaTitle (projected sub-schema) → control.label.
29
29
  */
30
- function resolveLabel(
31
- ctrl: ProjectionControl,
32
- schemaTitle?: string,
33
- ): string {
30
+ function resolveLabel(ctrl: ProjectionControl, schemaTitle?: string): string {
34
31
  return (
35
- (ctrl.uischema?.options?.label as string) ??
36
- schemaTitle ??
37
- ctrl.label ??
38
- ""
32
+ (ctrl.uischema?.options?.label as string) ?? schemaTitle ?? ctrl.label ?? ""
39
33
  );
40
34
  }
41
35
 
@@ -69,7 +63,9 @@ function prefixErrors(label: string, errors: string): string {
69
63
  function getErrorPath(error: ErrorLike): string {
70
64
  let p = (error.instancePath || "").replace(/\//g, ".").replace(/^\./, "");
71
65
  if (error.keyword === "required" && error.params?.missingProperty) {
72
- p = p ? p + "." + error.params.missingProperty : error.params.missingProperty;
66
+ p = p
67
+ ? p + "." + error.params.missingProperty
68
+ : error.params.missingProperty;
73
69
  }
74
70
  return p;
75
71
  }
@@ -160,9 +156,7 @@ export function useProjection(
160
156
  errStr = baseErrors;
161
157
  } else {
162
158
  const projMsg = matching
163
- .map((e) =>
164
- e.keyword === "required" ? "is required" : e.message,
165
- )
159
+ .map((e) => (e.keyword === "required" ? "is required" : e.message))
166
160
  .filter(Boolean)
167
161
  .join("\n");
168
162
  errStr = [baseErrors, projMsg].filter(Boolean).join("\n");
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
 
@@ -79,6 +85,8 @@ export { useProjection } from "./composables/useProjection";
79
85
  export type { ProjectionResult } from "./composables/useProjection";
80
86
  export { createDataLayer, useDataLayer } from "./composables/useDataLayer";
81
87
  export type { DataLayer } from "./composables/useDataLayer";
88
+ export { useDeriveInitialValue } from "./composables/useDeriveInitialValue";
89
+ export type { DeriveInitialValueCfg } from "./composables/useDeriveInitialValue";
82
90
  export { useDirtyValidation } from "./composables/useDirtyValidation";
83
91
  export * from "./testers";
84
92
 
@@ -49,7 +49,12 @@ 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, projectedErrors, projectedLabel } = 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
60
  const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
@@ -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="projectedLabel">{{ projectedLabel }}</label>
78
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
74
79
  <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
75
80
  </div>
76
81
  </template>
@@ -45,6 +45,7 @@ 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";
@@ -54,7 +55,12 @@ import Dropdown from "primevue/dropdown";
54
55
  const instance = getCurrentInstance()!;
55
56
  const props = instance.props as unknown as ControlProps;
56
57
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
57
- const { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);
58
+ const {
59
+ projectedData,
60
+ handleProjectedChange: handleChange,
61
+ projectedErrors,
62
+ projectedLabel,
63
+ } = useProjection(control, rawHandleChange);
58
64
 
59
65
  type Opt = { label: string; value: unknown };
60
66
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -142,6 +148,9 @@ const options = computed(() => {
142
148
  // Add derive functionality
143
149
  useDerive({ control, handleChange, data: projectedData });
144
150
 
151
+ // Add deriveInitialValue — async API-based initial value seeding
152
+ useDeriveInitialValue({ control, handleChange });
153
+
145
154
  // Auto-select when provider returns only one item (enabled by default)
146
155
  watch(
147
156
  [providerItems, loading],
@@ -158,7 +167,7 @@ watch(
158
167
  handleChange(control.value.path, valueToSelect);
159
168
  }
160
169
  },
161
- { immediate: true }
170
+ { immediate: true },
162
171
  );
163
172
 
164
173
  // Track user interaction — errors only show after blur
@@ -170,15 +179,13 @@ const onSelect = (val: unknown) => {
170
179
  </script>
171
180
 
172
181
  <template>
173
- <div class="flex flex-column gap-2">
174
- <label v-if="projectedLabel" class="text-color text-left">{{
175
- projectedLabel
176
- }}</label>
177
- <div v-if="control.description" class="text-color-secondary text-left">
182
+ <div class="jf-control">
183
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
184
+ <div v-if="control.description" class="jf-description">
178
185
  {{ control.description }}
179
186
  </div>
180
187
  <Dropdown
181
- :class="['w-full', { 'p-invalid': showErrors }]"
188
+ :class="['w-full!', { 'p-invalid': showErrors }]"
182
189
  :options="options"
183
190
  option-label="label"
184
191
  option-value="value"
@@ -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, projectedErrors, projectedLabel } = 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[] => {
@@ -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="projectedLabel" class="text-color text-left">{{
195
- projectedLabel
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
- projectedErrors
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, projectedErrors, projectedLabel } = 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
  () =>
@@ -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="projectedLabel" class="text-color text-left">{{
115
- projectedLabel
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"
@@ -43,6 +43,7 @@ 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";
48
49
  import InputText from "primevue/inputtext";
@@ -52,7 +53,12 @@ import AutoComplete from "primevue/autocomplete";
52
53
  const instance = getCurrentInstance()!;
53
54
  const props = instance.props as unknown as ControlProps;
54
55
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
55
- const { projectedData, handleProjectedChange: handleChange, projectedErrors, projectedLabel } = useProjection(control, rawHandleChange);
56
+ const {
57
+ projectedData,
58
+ handleProjectedChange: handleChange,
59
+ projectedErrors,
60
+ projectedLabel,
61
+ } = useProjection(control, rawHandleChange);
56
62
 
57
63
  // Provider support for autocomplete functionality
58
64
  const binding = computed(() => {
@@ -118,6 +124,9 @@ const isAutocomplete = computed(() => !!binding.value);
118
124
  // Add derive functionality
119
125
  useDerive({ control, handleChange, data: projectedData });
120
126
 
127
+ // Add deriveInitialValue — async API-based initial value seeding
128
+ useDeriveInitialValue({ control, handleChange });
129
+
121
130
  // Track user interaction — errors only show after blur
122
131
  const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
123
132
 
@@ -150,16 +159,14 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
150
159
  </script>
151
160
 
152
161
  <template>
153
- <div class="flex flex-column gap-2">
154
- <label v-if="projectedLabel" class="text-color text-left">{{
155
- projectedLabel
156
- }}</label>
157
- <div v-if="control.description" class="text-color-secondary text-left">
162
+ <div class="jf-control">
163
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
164
+ <div v-if="control.description" class="jf-description">
158
165
  {{ control.description }}
159
166
  </div>
160
167
  <AutoComplete
161
168
  v-if="isAutocomplete"
162
- :class="['w-full', { 'p-invalid': showErrors }]"
169
+ :class="['w-full!', { 'p-invalid': showErrors }]"
163
170
  :model-value="projectedData ?? ''"
164
171
  :suggestions="items"
165
172
  option-label="label"
@@ -173,7 +180,7 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
173
180
  />
174
181
  <InputText
175
182
  v-else
176
- :class="['w-full', { 'p-invalid': showErrors }]"
183
+ :class="['w-full!', { 'p-invalid': showErrors }]"
177
184
  :model-value="(projectedData as string) ?? ''"
178
185
  :disabled="!control.enabled"
179
186
  :aria-invalid="showErrors || undefined"
@@ -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, projectedErrors, projectedLabel } = 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
  () =>
@@ -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="projectedLabel" class="text-color text-left">{{
84
- projectedLabel
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"