@narrative.io/jsonforms-provider-protocols 3.0.0-beta.1 → 3.0.0-beta.11

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 (102) hide show
  1. package/dist/core/initFormData.d.ts +10 -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.map +1 -1
  6. package/dist/core/projection.js.map +1 -1
  7. package/dist/core/transforms.d.ts.map +1 -1
  8. package/dist/core/transforms.js +3 -1
  9. package/dist/core/transforms.js.map +1 -1
  10. package/dist/core/types.d.ts +1 -0
  11. package/dist/core/types.d.ts.map +1 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/jsonforms-provider-protocols.css +2 -2
  17. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  18. package/dist/vue/components/ProviderAutocomplete.vue.js +8 -5
  19. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  20. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  21. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  22. package/dist/vue/components/ProviderMultiSelect.vue2.js +9 -6
  23. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  24. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  25. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  26. package/dist/vue/components/ProviderSelect.vue2.js +11 -6
  27. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  28. package/dist/vue/composables/useDataLayer.d.ts +1 -0
  29. package/dist/vue/composables/useDataLayer.d.ts.map +1 -1
  30. package/dist/vue/composables/useDataLayer.js +1 -0
  31. package/dist/vue/composables/useDataLayer.js.map +1 -1
  32. package/dist/vue/composables/useDerive.d.ts +1 -1
  33. package/dist/vue/composables/useDerive.d.ts.map +1 -1
  34. package/dist/vue/composables/useDerive.js +19 -2
  35. package/dist/vue/composables/useDerive.js.map +1 -1
  36. package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
  37. package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
  38. package/dist/vue/composables/useDeriveInitialValue.js +125 -0
  39. package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
  40. package/dist/vue/composables/useDirtyValidation.d.ts +9 -0
  41. package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -0
  42. package/dist/vue/composables/useDirtyValidation.js +15 -0
  43. package/dist/vue/composables/useDirtyValidation.js.map +1 -0
  44. package/dist/vue/composables/useProjection.d.ts +6 -0
  45. package/dist/vue/composables/useProjection.d.ts.map +1 -1
  46. package/dist/vue/composables/useProjection.js +54 -3
  47. package/dist/vue/composables/useProjection.js.map +1 -1
  48. package/dist/vue/composables/useProvider.d.ts +2 -2
  49. package/dist/vue/composables/useProvider.d.ts.map +1 -1
  50. package/dist/vue/composables/useProvider.js +14 -10
  51. package/dist/vue/composables/useProvider.js.map +1 -1
  52. package/dist/vue/index.d.ts +3 -0
  53. package/dist/vue/index.d.ts.map +1 -1
  54. package/dist/vue/index.js +32 -10
  55. package/dist/vue/index.js.map +1 -1
  56. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  57. package/dist/vue/primevue/JfBoolean.vue.js +26 -9
  58. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  59. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  60. package/dist/vue/primevue/JfEnum.vue.js +21 -17
  61. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  62. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  63. package/dist/vue/primevue/JfEnumArray.vue.js +22 -12
  64. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  65. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  66. package/dist/vue/primevue/JfNumber.vue.js +21 -17
  67. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  68. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  69. package/dist/vue/primevue/JfText.vue.js +29 -26
  70. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  71. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  72. package/dist/vue/primevue/JfTextArea.vue.js +22 -13
  73. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  74. package/dist/vue/primevue/index.d.ts.map +1 -1
  75. package/dist/vue/primevue/index.js +93 -16
  76. package/dist/vue/primevue/index.js.map +1 -1
  77. package/dist/vue/utils/autoSelect.js.map +1 -1
  78. package/package.json +3 -1
  79. package/src/core/initFormData.ts +189 -0
  80. package/src/core/projection.ts +5 -5
  81. package/src/core/transforms.ts +33 -6
  82. package/src/core/types.ts +1 -0
  83. package/src/index.ts +1 -0
  84. package/src/vue/components/ProviderAutocomplete.vue +8 -5
  85. package/src/vue/components/ProviderMultiSelect.vue +13 -8
  86. package/src/vue/components/ProviderSelect.vue +14 -7
  87. package/src/vue/composables/useDataLayer.ts +1 -1
  88. package/src/vue/composables/useDerive.ts +46 -3
  89. package/src/vue/composables/useDeriveInitialValue.ts +195 -0
  90. package/src/vue/composables/useDirtyValidation.ts +20 -0
  91. package/src/vue/composables/useProjection.ts +108 -1
  92. package/src/vue/composables/useProvider.ts +28 -11
  93. package/src/vue/index.ts +29 -9
  94. package/src/vue/primevue/JfBoolean.vue +19 -6
  95. package/src/vue/primevue/JfEnum.vue +23 -21
  96. package/src/vue/primevue/JfEnumArray.vue +25 -15
  97. package/src/vue/primevue/JfNumber.vue +22 -20
  98. package/src/vue/primevue/JfText.vue +26 -24
  99. package/src/vue/primevue/JfTextArea.vue +22 -15
  100. package/src/vue/primevue/index.ts +104 -23
  101. package/src/vue/styles.css +26 -1
  102. package/src/vue/utils/autoSelect.ts +2 -2
@@ -45,6 +45,7 @@ import { computed, inject, getCurrentInstance, watch } from "vue";
45
45
  import { useProvider } from "../composables/useProvider";
46
46
  import { useDerive } from "../composables/useDerive";
47
47
  import { useProjection } from "../composables/useProjection";
48
+ import { useDirtyValidation } from "../composables/useDirtyValidation";
48
49
  import { shouldAutoSelectMulti } from "../utils/autoSelect";
49
50
  import MultiSelect from "primevue/multiselect";
50
51
 
@@ -52,7 +53,12 @@ import MultiSelect from "primevue/multiselect";
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 } = useProjection(control, rawHandleChange);
56
+ const {
57
+ projectedData,
58
+ handleProjectedChange: handleChange,
59
+ projectedErrors,
60
+ projectedLabel,
61
+ } = useProjection(control, rawHandleChange);
56
62
 
57
63
  type Opt = { label: string; value: unknown };
58
64
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -118,7 +124,7 @@ const {
118
124
  } = useProvider(binding, {
119
125
  data: rootData,
120
126
  path: control.value.path,
121
- dependsOnValues: depValues.value,
127
+ dependsOnValues: depValues,
122
128
  });
123
129
 
124
130
  const options = computed(() => {
@@ -132,6 +138,9 @@ const options = computed(() => {
132
138
  // Add derive functionality
133
139
  useDerive({ control, handleChange, data: projectedData });
134
140
 
141
+ // Track user interaction — errors only show after first change
142
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
143
+
135
144
  // Auto-select when provider returns only one item (opt-in for multiselect)
136
145
  watch(
137
146
  [providerItems, loading],
@@ -141,14 +150,16 @@ watch(
141
150
  control.value.uischema?.options?.autoSelectSingle === true,
142
151
  isLoading,
143
152
  items,
144
- currentValue: Array.isArray(projectedData.value) ? projectedData.value : [],
153
+ currentValue: Array.isArray(projectedData.value)
154
+ ? projectedData.value
155
+ : [],
145
156
  });
146
157
 
147
158
  if (valueToSelect !== null) {
148
159
  handleChange(control.value.path, valueToSelect);
149
160
  }
150
161
  },
151
- { immediate: true }
162
+ { immediate: true },
152
163
  );
153
164
 
154
165
  const placeholder = computed<string | undefined>(() => {
@@ -177,38 +188,37 @@ const model = computed<unknown[]>({
177
188
  set(val) {
178
189
  const next = Array.isArray(val) ? [...val] : [];
179
190
  const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
180
- if (!sameSet(curr, next)) handleChange(control.value.path, next);
191
+ if (!sameSet(curr, next)) {
192
+ markDirty();
193
+ handleChange(control.value.path, next);
194
+ }
181
195
  },
182
196
  });
183
197
  </script>
184
198
 
185
199
  <template>
186
- <div class="flex flex-column gap-2">
187
- <label v-if="control.label" class="text-color text-left">{{
188
- control.label
189
- }}</label>
190
- <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">
191
203
  {{ control.description }}
192
204
  </div>
193
205
 
194
206
  <MultiSelect
195
207
  v-model="model"
196
- class="w-full"
208
+ :class="['w-full!', { 'p-invalid': showErrors }]"
197
209
  :options="options"
198
210
  option-label="label"
199
211
  option-value="value"
200
212
  data-key="value"
201
213
  display="chip"
202
214
  :disabled="!control.enabled || loading"
203
- :aria-invalid="!!control.errors || undefined"
215
+ :aria-invalid="showErrors || undefined"
204
216
  :placeholder="placeholder"
205
217
  />
206
218
 
207
219
  <small v-if="error" class="p-error" role="alert"
208
220
  >Failed to load: {{ error }}</small
209
221
  >
210
- <small v-else-if="control.errors" class="p-error">{{
211
- control.errors
212
- }}</small>
222
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
213
223
  </div>
214
224
  </template>
@@ -40,16 +40,23 @@ export default {
40
40
  <script setup lang="ts">
41
41
  import type { ControlProps } from "@jsonforms/vue";
42
42
  import { useJsonFormsControl } from "@jsonforms/vue";
43
- import { computed, ref, getCurrentInstance } from "vue";
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";
47
+ import { useDirtyValidation } from "../composables/useDirtyValidation";
46
48
  import InputNumber from "primevue/inputnumber";
47
49
 
48
50
  // Access props from the component instance
49
51
  const instance = getCurrentInstance()!;
50
52
  const props = instance.props as unknown as ControlProps;
51
53
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
52
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
54
+ const {
55
+ projectedData,
56
+ handleProjectedChange: handleChange,
57
+ projectedErrors,
58
+ projectedLabel,
59
+ } = useProjection(control, rawHandleChange);
53
60
 
54
61
  const options = computed(
55
62
  () =>
@@ -64,6 +71,9 @@ const placeholder = computed<string | undefined>(
64
71
  // Add derive functionality
65
72
  useDerive({ control, handleChange, data: projectedData });
66
73
 
74
+ // Add deriveInitialValue — async API-based initial value seeding
75
+ useDeriveInitialValue({ control, handleChange });
76
+
67
77
  // Currency and decimal configuration
68
78
  const mode = computed(() => {
69
79
  if (options.value.currency) return "currency";
@@ -100,31 +110,23 @@ const useGrouping = computed(() => {
100
110
  return options.value.useGrouping === true;
101
111
  });
102
112
 
103
- // Track user interaction
104
- const hasInteracted = ref(false);
105
-
106
- const showErrors = computed(() => hasInteracted.value && control.value.errors);
113
+ // Track user interaction — errors only show after blur
114
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
107
115
 
108
116
  const onNumber = (val: number | null) => {
109
117
  handleChange(control.value.path, val ?? undefined);
110
118
  };
111
-
112
- const onBlur = () => {
113
- hasInteracted.value = true;
114
- };
115
119
  </script>
116
120
 
117
121
  <template>
118
- <div class="flex flex-column gap-2">
119
- <label v-if="control.label" class="text-color text-left">{{
120
- control.label
121
- }}</label>
122
- <div v-if="control.description" class="text-color-secondary text-left">
122
+ <div class="jf-control">
123
+ <label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
124
+ <div v-if="control.description" class="jf-description">
123
125
  {{ control.description }}
124
126
  </div>
125
127
  <InputNumber
126
- class="w-full"
127
- input-class="w-full"
128
+ :class="['w-full!', { 'p-invalid': showErrors }]"
129
+ :input-class="['w-full!', { 'p-invalid': showErrors }]"
128
130
  :use-grouping="useGrouping"
129
131
  :mode="mode"
130
132
  :currency="currency"
@@ -133,10 +135,10 @@ const onBlur = () => {
133
135
  :model-value="typeof projectedData === 'number' ? projectedData : null"
134
136
  :placeholder="placeholder"
135
137
  :disabled="!control.enabled"
136
- :aria-invalid="!!showErrors || undefined"
138
+ :aria-invalid="showErrors || undefined"
137
139
  @update:model-value="onNumber"
138
- @blur="onBlur"
140
+ @blur="markDirty"
139
141
  />
140
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
142
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
141
143
  </div>
142
144
  </template>
@@ -43,7 +43,9 @@ 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";
48
+ import { useDirtyValidation } from "../composables/useDirtyValidation";
47
49
  import InputText from "primevue/inputtext";
48
50
  import AutoComplete from "primevue/autocomplete";
49
51
 
@@ -51,7 +53,12 @@ import AutoComplete from "primevue/autocomplete";
51
53
  const instance = getCurrentInstance()!;
52
54
  const props = instance.props as unknown as ControlProps;
53
55
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
54
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
56
+ const {
57
+ projectedData,
58
+ handleProjectedChange: handleChange,
59
+ projectedErrors,
60
+ projectedLabel,
61
+ } = useProjection(control, rawHandleChange);
55
62
 
56
63
  // Provider support for autocomplete functionality
57
64
  const binding = computed(() => {
@@ -97,7 +104,7 @@ const { items, loading, error, reload } = useProvider(binding, {
97
104
  data: rootData,
98
105
  path: control.value.path,
99
106
  uiQuery: query.value,
100
- dependsOnValues: depValues.value,
107
+ dependsOnValues: depValues,
101
108
  });
102
109
 
103
110
  watch(query, () => {
@@ -117,11 +124,11 @@ const isAutocomplete = computed(() => !!binding.value);
117
124
  // Add derive functionality
118
125
  useDerive({ control, handleChange, data: projectedData });
119
126
 
120
- // Track user interaction
121
- const hasInteracted = ref(false);
122
- const hasFocused = ref(false);
127
+ // Add deriveInitialValue — async API-based initial value seeding
128
+ useDeriveInitialValue({ control, handleChange });
123
129
 
124
- const showErrors = computed(() => hasInteracted.value && control.value.errors);
130
+ // Track user interaction errors only show after blur
131
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
125
132
 
126
133
  function onInput(val: string | undefined) {
127
134
  // Convert empty strings to undefined for proper required field validation
@@ -132,15 +139,14 @@ function onInput(val: string | undefined) {
132
139
  }
133
140
 
134
141
  function onBlur() {
135
- if (hasFocused.value) {
136
- hasInteracted.value = true;
142
+ markDirty();
143
+ // Normalize empty strings to undefined so required validation fires
144
+ const val = projectedData.value;
145
+ if (typeof val === "string" && val.trim() === "") {
146
+ handleChange(control.value.path, undefined);
137
147
  }
138
148
  }
139
149
 
140
- function onFocus() {
141
- hasFocused.value = true;
142
- }
143
-
144
150
  // Autocomplete specific handlers
145
151
  const onComplete = (event: { query: string }) => {
146
152
  query.value = event.query;
@@ -153,43 +159,39 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
153
159
  </script>
154
160
 
155
161
  <template>
156
- <div class="flex flex-column gap-2">
157
- <label v-if="control.label" class="text-color text-left">{{
158
- control.label
159
- }}</label>
160
- <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">
161
165
  {{ control.description }}
162
166
  </div>
163
167
  <AutoComplete
164
168
  v-if="isAutocomplete"
165
- class="w-full"
169
+ :class="['w-full!', { 'p-invalid': showErrors }]"
166
170
  :model-value="projectedData ?? ''"
167
171
  :suggestions="items"
168
172
  option-label="label"
169
173
  :placeholder="placeholder"
170
174
  :disabled="!control.enabled"
171
- :aria-invalid="!!showErrors || undefined"
175
+ :aria-invalid="showErrors || undefined"
172
176
  @complete="onComplete"
173
177
  @item-select="onSelect"
174
178
  @update:model-value="onInput"
175
179
  @blur="onBlur"
176
- @focus="onFocus"
177
180
  />
178
181
  <InputText
179
182
  v-else
180
- class="w-full"
183
+ :class="['w-full!', { 'p-invalid': showErrors }]"
181
184
  :model-value="(projectedData as string) ?? ''"
182
185
  :disabled="!control.enabled"
183
- :aria-invalid="!!showErrors || undefined"
186
+ :aria-invalid="showErrors || undefined"
184
187
  :placeholder="placeholder"
185
188
  autocapitalize="off"
186
189
  autocomplete="off"
187
190
  spellcheck="false"
188
191
  @update:model-value="onInput"
189
192
  @blur="onBlur"
190
- @focus="onFocus"
191
193
  />
192
194
  <small v-if="error" class="p-error" role="alert">Failed: {{ error }}</small>
193
- <small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
195
+ <small v-else-if="showErrors" class="p-error">{{ projectedErrors }}</small>
194
196
  </div>
195
197
  </template>
@@ -40,15 +40,21 @@ export default {
40
40
  <script setup lang="ts">
41
41
  import type { ControlProps } from "@jsonforms/vue";
42
42
  import { useJsonFormsControl } from "@jsonforms/vue";
43
- import { computed, ref, getCurrentInstance } from "vue";
43
+ import { computed, getCurrentInstance } from "vue";
44
44
  import { useProjection } from "../composables/useProjection";
45
+ import { useDirtyValidation } from "../composables/useDirtyValidation";
45
46
  import Textarea from "primevue/textarea";
46
47
 
47
48
  // Access props from the component instance
48
49
  const instance = getCurrentInstance()!;
49
50
  const props = instance.props as unknown as ControlProps;
50
51
  const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
51
- const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
52
+ const {
53
+ projectedData,
54
+ handleProjectedChange: handleChange,
55
+ projectedErrors,
56
+ projectedLabel,
57
+ } = useProjection(control, rawHandleChange);
52
58
 
53
59
  const placeholder = computed<string | undefined>(
54
60
  () =>
@@ -56,10 +62,8 @@ const placeholder = computed<string | undefined>(
56
62
  ?.placeholder ?? control.value.description,
57
63
  );
58
64
 
59
- // Track user interaction
60
- const hasInteracted = ref(false);
61
-
62
- const showErrors = computed(() => hasInteracted.value && control.value.errors);
65
+ // Track user interaction — errors only show after blur
66
+ const { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);
63
67
 
64
68
  function onInput(val: string | undefined) {
65
69
  // Convert empty strings to undefined for proper required field validation
@@ -70,29 +74,32 @@ function onInput(val: string | undefined) {
70
74
  }
71
75
 
72
76
  function onBlur() {
73
- hasInteracted.value = true;
77
+ markDirty();
78
+ // Normalize empty strings to undefined so required validation fires
79
+ const val = projectedData.value;
80
+ if (typeof val === "string" && val.trim() === "") {
81
+ handleChange(control.value.path, undefined);
82
+ }
74
83
  }
75
84
  </script>
76
85
 
77
86
  <template>
78
- <div class="flex flex-column gap-2">
79
- <label v-if="control.label" class="text-color text-left">{{
80
- control.label
81
- }}</label>
82
- <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">
83
90
  {{ control.description }}
84
91
  </div>
85
92
  <Textarea
86
- class="w-full"
93
+ :class="['w-full!', { 'p-invalid': showErrors }]"
87
94
  :model-value="(projectedData as string) ?? ''"
88
95
  :disabled="!control.enabled"
89
- :aria-invalid="!!showErrors || undefined"
96
+ :aria-invalid="showErrors || undefined"
90
97
  :placeholder="placeholder"
91
98
  :rows="4"
92
99
  :auto-resize="true"
93
100
  @update:model-value="onInput"
94
101
  @blur="onBlur"
95
102
  />
96
- <small v-if="showErrors" class="p-error">{{ control.errors }}</small>
103
+ <small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
97
104
  </div>
98
105
  </template>
@@ -19,13 +19,38 @@ const injectLayoutStyles = () => {
19
19
  display: flex;
20
20
  flex-direction: column;
21
21
  align-items: flex-start;
22
- gap: 1rem;
22
+ gap: 24px;
23
23
  width: 100%;
24
24
  }
25
25
 
26
26
  .vertical-layout-item {
27
27
  width: 100%;
28
28
  }
29
+
30
+ /* Form control wrapper */
31
+ .jf-control {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: 12px;
35
+ }
36
+
37
+ /* Form control label typography */
38
+ .jf-label {
39
+ font-weight: 600;
40
+ font-size: 14px;
41
+ line-height: 14px;
42
+ color: #031553;
43
+ text-align: left;
44
+ }
45
+
46
+ /* Form control description typography */
47
+ .jf-description {
48
+ font-weight: 400;
49
+ font-size: 14px;
50
+ line-height: 14px;
51
+ color: #415290;
52
+ text-align: left;
53
+ }
29
54
  `;
30
55
  document.head.appendChild(style);
31
56
  }
@@ -102,47 +127,103 @@ export function registerPrimevueRenderers(jsonformsCore: any): unknown[] {
102
127
 
103
128
  // Projection-aware schema check: when options.projection is set,
104
129
  // resolve the projected schema and test against it instead of the original
105
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
- const projectedSchemaMatches = (check: (schema: any) => boolean) =>
107
- (uischema: unknown, schema: unknown): boolean => {
108
- const ui = uischema as { type?: string; scope?: string; options?: { projection?: string } };
109
- const projection = ui?.options?.projection;
110
- if (!projection || ui?.type !== "Control" || !ui?.scope) return false;
111
-
112
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
- const propertySchema = resolveScopeSchema(ui.scope, schema as Record<string, any>);
114
- if (!propertySchema) return false;
115
-
116
- return check(getProjectedSchema(propertySchema, projection));
117
- };
130
+
131
+ const projectedSchemaMatches =
132
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
+ (check: (schema: any) => boolean) =>
134
+ (uischema: unknown, schema: unknown): boolean => {
135
+ const ui = uischema as {
136
+ type?: string;
137
+ scope?: string;
138
+ options?: { projection?: string };
139
+ };
140
+ const projection = ui?.options?.projection;
141
+ if (!projection || ui?.type !== "Control" || !ui?.scope) return false;
142
+
143
+ const propertySchema = resolveScopeSchema(
144
+ ui.scope,
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ schema as Record<string, any>,
147
+ );
148
+ if (!propertySchema) return false;
149
+
150
+ return check(getProjectedSchema(propertySchema, projection));
151
+ };
118
152
 
119
153
  const isMultilineProjection = (uischema: unknown, schema: unknown) => {
120
154
  const ui = uischema as { options?: { multi?: boolean } };
121
- return ui?.options?.multi === true &&
122
- projectedSchemaMatches((s) => s?.type === "string")(uischema, schema);
155
+ return (
156
+ ui?.options?.multi === true &&
157
+ projectedSchemaMatches((s) => s?.type === "string")(uischema, schema)
158
+ );
123
159
  };
124
160
 
125
161
  const renderers = [
126
162
  // Multiline text has higher priority than regular text
127
- { tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)), renderer: JfTextArea },
128
- { tester: rankWith(PRIME + 3, or(isStringControl, projectedSchemaMatches((s) => s?.type === "string"))), renderer: JfText },
129
163
  {
130
- tester: rankWith(PRIME + 6, or(isIntegerControl, projectedSchemaMatches((s) => s?.type === "integer"))),
164
+ tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)),
165
+ renderer: JfTextArea,
166
+ },
167
+ {
168
+ tester: rankWith(
169
+ PRIME + 3,
170
+ or(
171
+ isStringControl,
172
+ projectedSchemaMatches((s) => s?.type === "string"),
173
+ ),
174
+ ),
175
+ renderer: JfText,
176
+ },
177
+ {
178
+ tester: rankWith(
179
+ PRIME + 6,
180
+ or(
181
+ isIntegerControl,
182
+ projectedSchemaMatches((s) => s?.type === "integer"),
183
+ ),
184
+ ),
131
185
  renderer: JfNumber,
132
186
  },
133
187
  {
134
- tester: rankWith(PRIME + 4, or(isNumberControl, projectedSchemaMatches((s) => s?.type === "number"))),
188
+ tester: rankWith(
189
+ PRIME + 4,
190
+ or(
191
+ isNumberControl,
192
+ projectedSchemaMatches((s) => s?.type === "number"),
193
+ ),
194
+ ),
135
195
  renderer: JfNumber,
136
196
  },
137
197
  {
138
- tester: rankWith(PRIME + 7, or(and(isControl, schemaMatches(isScalarEnum)), and(isControl, projectedSchemaMatches(isScalarEnum)))),
198
+ tester: rankWith(
199
+ PRIME + 7,
200
+ or(
201
+ and(isControl, schemaMatches(isScalarEnum)),
202
+ and(isControl, projectedSchemaMatches(isScalarEnum)),
203
+ ),
204
+ ),
139
205
  renderer: JfEnum,
140
206
  },
141
207
  {
142
- tester: rankWith(PRIME + 8, or(and(isControl, schemaMatches(isEnumArray)), and(isControl, projectedSchemaMatches(isEnumArray)))),
208
+ tester: rankWith(
209
+ PRIME + 8,
210
+ or(
211
+ and(isControl, schemaMatches(isEnumArray)),
212
+ and(isControl, projectedSchemaMatches(isEnumArray)),
213
+ ),
214
+ ),
143
215
  renderer: JfEnumArray,
144
216
  },
145
- { tester: rankWith(PRIME + 3, or(isBooleanControl, projectedSchemaMatches((s) => s?.type === "boolean"))), renderer: JfBoolean },
217
+ {
218
+ tester: rankWith(
219
+ PRIME + 3,
220
+ or(
221
+ isBooleanControl,
222
+ projectedSchemaMatches((s) => s?.type === "boolean"),
223
+ ),
224
+ ),
225
+ renderer: JfBoolean,
226
+ },
146
227
  ];
147
228
 
148
229
  // Update the exported array
@@ -5,7 +5,7 @@
5
5
  display: flex;
6
6
  flex-direction: column;
7
7
  align-items: flex-start;
8
- gap: 1rem;
8
+ gap: 24px;
9
9
  width: 100%;
10
10
  }
11
11
 
@@ -14,6 +14,31 @@
14
14
  width: 100%;
15
15
  }
16
16
 
17
+ /* Form control wrapper */
18
+ .jf-control {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 12px;
22
+ }
23
+
24
+ /* Form control label typography */
25
+ .jf-label {
26
+ font-weight: 600;
27
+ font-size: 14px;
28
+ line-height: 14px;
29
+ color: #031553;
30
+ text-align: left;
31
+ }
32
+
33
+ /* Form control description typography */
34
+ .jf-description {
35
+ font-weight: 400;
36
+ font-size: 14px;
37
+ line-height: 14px;
38
+ color: #415290;
39
+ text-align: left;
40
+ }
41
+
17
42
  /* PrimeVue dropdown text alignment fix */
18
43
  .p-dropdown-label {
19
44
  text-align: left !important;
@@ -57,7 +57,7 @@ export interface AutoSelectMultiParams {
57
57
  * - Current value is empty array OR current selection is not in the current options
58
58
  */
59
59
  export function shouldAutoSelectMulti(
60
- params: AutoSelectMultiParams
60
+ params: AutoSelectMultiParams,
61
61
  ): unknown[] | null {
62
62
  const { autoSelectSingle, isLoading, items, currentValue } = params;
63
63
 
@@ -73,7 +73,7 @@ export function shouldAutoSelectMulti(
73
73
  const currentArray = Array.isArray(currentValue) ? currentValue : [];
74
74
  const isValueEmpty = currentArray.length === 0;
75
75
  const hasValidSelection = currentArray.some((val) =>
76
- items.some((item) => item.value === val)
76
+ items.some((item) => item.value === val),
77
77
  );
78
78
 
79
79
  if (isValueEmpty || !hasValidSelection) {