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

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 (94) hide show
  1. package/README.md +166 -30
  2. package/dist/core/projection.d.ts +32 -0
  3. package/dist/core/projection.d.ts.map +1 -0
  4. package/dist/core/projection.js +74 -0
  5. package/dist/core/projection.js.map +1 -0
  6. package/dist/core/resolveScope.d.ts +11 -0
  7. package/dist/core/resolveScope.d.ts.map +1 -0
  8. package/dist/core/resolveScope.js +22 -0
  9. package/dist/core/resolveScope.js.map +1 -0
  10. package/dist/core/transforms.d.ts +8 -10
  11. package/dist/core/transforms.d.ts.map +1 -1
  12. package/dist/core/transforms.js +56 -13
  13. package/dist/core/transforms.js.map +1 -1
  14. package/dist/core/types.d.ts +7 -0
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/index.d.ts +4 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +12 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/jsonforms-provider-protocols.css +2 -2
  21. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  22. package/dist/vue/components/ProviderAutocomplete.vue.js +4 -2
  23. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  24. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  25. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  26. package/dist/vue/components/ProviderMultiSelect.vue2.js +6 -4
  27. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  28. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  29. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  30. package/dist/vue/components/ProviderSelect.vue2.js +5 -3
  31. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  32. package/dist/vue/composables/useDataLayer.d.ts +9 -0
  33. package/dist/vue/composables/useDataLayer.d.ts.map +1 -0
  34. package/dist/vue/composables/useDataLayer.js +25 -0
  35. package/dist/vue/composables/useDataLayer.js.map +1 -0
  36. package/dist/vue/composables/useDerive.d.ts +5 -2
  37. package/dist/vue/composables/useDerive.d.ts.map +1 -1
  38. package/dist/vue/composables/useDerive.js +12 -12
  39. package/dist/vue/composables/useDerive.js.map +1 -1
  40. package/dist/vue/composables/useProjection.d.ts +35 -0
  41. package/dist/vue/composables/useProjection.d.ts.map +1 -0
  42. package/dist/vue/composables/useProjection.js +33 -0
  43. package/dist/vue/composables/useProjection.js.map +1 -0
  44. package/dist/vue/index.d.ts +4 -0
  45. package/dist/vue/index.d.ts.map +1 -1
  46. package/dist/vue/index.js +15 -29
  47. package/dist/vue/index.js.map +1 -1
  48. package/dist/vue/primevue/JfBoolean.vue.d.ts +9 -0
  49. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  50. package/dist/vue/primevue/JfBoolean.vue.js +10 -5
  51. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  52. package/dist/vue/primevue/JfEnum.vue.d.ts +9 -0
  53. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  54. package/dist/vue/primevue/JfEnum.vue.js +12 -7
  55. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  56. package/dist/vue/primevue/JfEnumArray.vue.d.ts +9 -0
  57. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  58. package/dist/vue/primevue/JfEnumArray.vue.js +13 -8
  59. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  60. package/dist/vue/primevue/JfNumber.vue.d.ts +9 -0
  61. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  62. package/dist/vue/primevue/JfNumber.vue.js +11 -6
  63. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  64. package/dist/vue/primevue/JfText.vue.d.ts +9 -0
  65. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  66. package/dist/vue/primevue/JfText.vue.js +13 -8
  67. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  68. package/dist/vue/primevue/JfTextArea.vue.d.ts +9 -0
  69. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  70. package/dist/vue/primevue/JfTextArea.vue.js +11 -6
  71. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  72. package/dist/vue/primevue/index.d.ts.map +1 -1
  73. package/dist/vue/primevue/index.js +22 -7
  74. package/dist/vue/primevue/index.js.map +1 -1
  75. package/package.json +7 -3
  76. package/src/core/projection.ts +136 -0
  77. package/src/core/resolveScope.ts +39 -0
  78. package/src/core/transforms.ts +91 -26
  79. package/src/core/types.ts +8 -0
  80. package/src/index.ts +7 -0
  81. package/src/vue/components/ProviderAutocomplete.vue +4 -2
  82. package/src/vue/components/ProviderMultiSelect.vue +6 -4
  83. package/src/vue/components/ProviderSelect.vue +5 -3
  84. package/src/vue/composables/useDataLayer.ts +43 -0
  85. package/src/vue/composables/useDerive.ts +19 -16
  86. package/src/vue/composables/useProjection.ts +74 -0
  87. package/src/vue/index.ts +20 -46
  88. package/src/vue/primevue/JfBoolean.vue +7 -2
  89. package/src/vue/primevue/JfEnum.vue +9 -4
  90. package/src/vue/primevue/JfEnumArray.vue +10 -5
  91. package/src/vue/primevue/JfNumber.vue +8 -3
  92. package/src/vue/primevue/JfText.vue +10 -5
  93. package/src/vue/primevue/JfTextArea.vue +8 -3
  94. package/src/vue/primevue/index.ts +32 -7
package/src/vue/index.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  rankWith,
8
8
  isControl,
9
9
  } from "@jsonforms/core";
10
+ import { resolveScopeSchema } from "../core/resolveScope";
10
11
  import ProviderAutocomplete from "./components/ProviderAutocomplete.vue";
11
12
  import ProviderSelect from "./components/ProviderSelect.vue";
12
13
  import ProviderMultiSelect from "./components/ProviderMultiSelect.vue";
@@ -16,29 +17,21 @@ const hasProvider = (uischema: UISchemaElement) => {
16
17
  return uischema?.options?.provider !== undefined;
17
18
  };
18
19
 
20
+ // Integer fallback tester — handles nested scopes like #/properties/parent/properties/child
21
+ const isIntegerScope = (uischema: unknown, schema: unknown) => {
22
+ const ui = uischema as { type?: string; scope?: string };
23
+ if (ui?.type !== "Control" || !ui?.scope) return false;
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ const propertySchema = resolveScopeSchema(ui.scope, schema as Record<string, any>);
27
+ return propertySchema?.type === "integer";
28
+ };
29
+
19
30
  // Create specific testers for each component type
20
31
  const providerSelectTester = rankWith(
21
32
  106, // Higher than PrimeVue base (100) to ensure providers take precedence
22
33
  and(
23
- or(
24
- isStringControl,
25
- isNumberControl,
26
- and(isControl, (uischema: unknown, schema: unknown) => {
27
- const ui = uischema as { type?: string; scope?: string };
28
- const rootSchema = schema as {
29
- properties?: Record<string, { type?: string }>;
30
- };
31
-
32
- if (ui?.type !== "Control" || !ui?.scope || !rootSchema?.properties)
33
- return false;
34
-
35
- // Extract property name from scope (e.g., "#/properties/age" -> "age")
36
- const propertyName = ui.scope.replace("#/properties/", "");
37
- const propertySchema = rootSchema.properties[propertyName];
38
-
39
- return propertySchema?.type === "integer";
40
- }),
41
- ),
34
+ or(isStringControl, isNumberControl, and(isControl, isIntegerScope)),
42
35
  hasProvider,
43
36
  (uischema) => !uischema?.options?.autocomplete,
44
37
  ),
@@ -47,44 +40,21 @@ const providerSelectTester = rankWith(
47
40
  const providerAutocompleteTester = rankWith(
48
41
  107, // Higher than PrimeVue base (100) to ensure providers take precedence
49
42
  and(
50
- or(
51
- isStringControl,
52
- isNumberControl,
53
- and(isControl, (uischema: unknown, schema: unknown) => {
54
- const ui = uischema as { type?: string; scope?: string };
55
- const rootSchema = schema as {
56
- properties?: Record<string, { type?: string }>;
57
- };
58
-
59
- if (ui?.type !== "Control" || !ui?.scope || !rootSchema?.properties)
60
- return false;
61
-
62
- // Extract property name from scope (e.g., "#/properties/age" -> "age")
63
- const propertyName = ui.scope.replace("#/properties/", "");
64
- const propertySchema = rootSchema.properties[propertyName];
65
-
66
- return propertySchema?.type === "integer";
67
- }),
68
- ),
43
+ or(isStringControl, isNumberControl, and(isControl, isIntegerScope)),
69
44
  hasProvider,
70
45
  (uischema) => uischema?.options?.autocomplete === true,
71
46
  ),
72
47
  );
73
48
 
74
- // Custom array tester - check both uischema control type and schema type
49
+ // Custom array tester - supports nested scope paths
75
50
  const isArrayControl = (uischema: UISchemaElement, schema: unknown) => {
76
51
  const controlSchema = uischema as { type: string; scope?: string };
77
52
  if (controlSchema.type !== "Control" || !controlSchema.scope) {
78
53
  return false;
79
54
  }
80
55
 
81
- // Extract the property schema from the root schema
82
- const rootSchema = schema as { properties?: Record<string, unknown> };
83
- const propertyPath = controlSchema.scope.replace("#/properties/", "");
84
- const propertySchema = rootSchema?.properties?.[propertyPath] as {
85
- type?: string;
86
- };
87
-
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const propertySchema = resolveScopeSchema(controlSchema.scope, schema as Record<string, any>);
88
58
  return propertySchema?.type === "array";
89
59
  };
90
60
 
@@ -105,6 +75,10 @@ export { primevueRenderers, registerPrimevueRenderers } from "./primevue";
105
75
  // Export individual components
106
76
  export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };
107
77
  export { useProvider } from "./composables/useProvider";
78
+ export { useProjection } from "./composables/useProjection";
79
+ export type { ProjectionResult } from "./composables/useProjection";
80
+ export { createDataLayer, useDataLayer } from "./composables/useDataLayer";
81
+ export type { DataLayer } from "./composables/useDataLayer";
108
82
  export * from "./testers";
109
83
 
110
84
  // Export individual PrimeVue components using lazy evaluation to avoid circular deps
@@ -21,14 +21,17 @@ export default {
21
21
  renderers: {
22
22
  type: Array,
23
23
  required: false,
24
+ default: undefined,
24
25
  },
25
26
  cells: {
26
27
  type: Array,
27
28
  required: false,
29
+ default: undefined,
28
30
  },
29
31
  config: {
30
32
  type: Object,
31
33
  required: false,
34
+ default: undefined,
32
35
  },
33
36
  },
34
37
  };
@@ -38,12 +41,14 @@ export default {
38
41
  import type { ControlProps } from "@jsonforms/vue";
39
42
  import { useJsonFormsControl } from "@jsonforms/vue";
40
43
  import { getCurrentInstance } from "vue";
44
+ import { useProjection } from "../composables/useProjection";
41
45
  import Checkbox from "primevue/checkbox";
42
46
 
43
47
  // Access props from the component instance
44
48
  const instance = getCurrentInstance()!;
45
49
  const props = instance.props as unknown as ControlProps;
46
- const { control, handleChange } = useJsonFormsControl(props);
50
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
51
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
47
52
 
48
53
  const onToggle = (val: boolean) => handleChange(control.value.path, val);
49
54
  </script>
@@ -52,7 +57,7 @@ const onToggle = (val: boolean) => handleChange(control.value.path, val);
52
57
  <div class="flex items-center gap-2">
53
58
  <Checkbox
54
59
  :binary="true"
55
- :model-value="!!control.data"
60
+ :model-value="!!projectedData"
56
61
  :disabled="!control.enabled"
57
62
  :aria-invalid="!!control.errors || undefined"
58
63
  @update:model-value="onToggle"
@@ -22,14 +22,17 @@ export default {
22
22
  renderers: {
23
23
  type: Array,
24
24
  required: false,
25
+ default: undefined,
25
26
  },
26
27
  cells: {
27
28
  type: Array,
28
29
  required: false,
30
+ default: undefined,
29
31
  },
30
32
  config: {
31
33
  type: Object,
32
34
  required: false,
35
+ default: undefined,
33
36
  },
34
37
  },
35
38
  };
@@ -42,13 +45,15 @@ import { useJsonFormsControl } from "@jsonforms/vue";
42
45
  import { computed, ref, inject, getCurrentInstance, watch } from "vue";
43
46
  import { useProvider } from "../composables/useProvider";
44
47
  import { useDerive } from "../composables/useDerive";
48
+ import { useProjection } from "../composables/useProjection";
45
49
  import { shouldAutoSelect } from "../utils/autoSelect";
46
50
  import Dropdown from "primevue/dropdown";
47
51
 
48
52
  // Access props from the component instance
49
53
  const instance = getCurrentInstance()!;
50
54
  const props = instance.props as unknown as ControlProps;
51
- const { control, handleChange } = useJsonFormsControl(props);
55
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
56
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
52
57
 
53
58
  type Opt = { label: string; value: unknown };
54
59
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -134,7 +139,7 @@ const options = computed(() => {
134
139
  });
135
140
 
136
141
  // Add derive functionality
137
- useDerive({ control, handleChange });
142
+ useDerive({ control, handleChange, data: projectedData });
138
143
 
139
144
  // Auto-select when provider returns only one item (enabled by default)
140
145
  watch(
@@ -145,7 +150,7 @@ watch(
145
150
  control.value.uischema?.options?.autoSelectSingle !== false,
146
151
  isLoading,
147
152
  items,
148
- currentValue: control.value.data,
153
+ currentValue: projectedData.value,
149
154
  });
150
155
 
151
156
  if (valueToSelect !== null) {
@@ -182,7 +187,7 @@ const onBlur = () => {
182
187
  :options="options"
183
188
  option-label="label"
184
189
  option-value="value"
185
- :model-value="control.data ?? null"
190
+ :model-value="projectedData ?? null"
186
191
  :placeholder="placeholder"
187
192
  :disabled="!control.enabled || loading"
188
193
  :aria-invalid="!!showErrors || undefined"
@@ -21,14 +21,17 @@ export default {
21
21
  renderers: {
22
22
  type: Array,
23
23
  required: false,
24
+ default: undefined,
24
25
  },
25
26
  cells: {
26
27
  type: Array,
27
28
  required: false,
29
+ default: undefined,
28
30
  },
29
31
  config: {
30
32
  type: Object,
31
33
  required: false,
34
+ default: undefined,
32
35
  },
33
36
  },
34
37
  };
@@ -41,13 +44,15 @@ import { useJsonFormsControl } from "@jsonforms/vue";
41
44
  import { computed, inject, getCurrentInstance, watch } from "vue";
42
45
  import { useProvider } from "../composables/useProvider";
43
46
  import { useDerive } from "../composables/useDerive";
47
+ import { useProjection } from "../composables/useProjection";
44
48
  import { shouldAutoSelectMulti } from "../utils/autoSelect";
45
49
  import MultiSelect from "primevue/multiselect";
46
50
 
47
51
  // Access props from the component instance
48
52
  const instance = getCurrentInstance()!;
49
53
  const props = instance.props as unknown as ControlProps;
50
- const { control, handleChange } = useJsonFormsControl(props);
54
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
55
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
51
56
 
52
57
  type Opt = { label: string; value: unknown };
53
58
  const toOptions = (schema?: JsonSchema): Opt[] => {
@@ -125,7 +130,7 @@ const options = computed(() => {
125
130
  });
126
131
 
127
132
  // Add derive functionality
128
- useDerive({ control, handleChange });
133
+ useDerive({ control, handleChange, data: projectedData });
129
134
 
130
135
  // Auto-select when provider returns only one item (opt-in for multiselect)
131
136
  watch(
@@ -136,7 +141,7 @@ watch(
136
141
  control.value.uischema?.options?.autoSelectSingle === true,
137
142
  isLoading,
138
143
  items,
139
- currentValue: Array.isArray(control.value.data) ? control.value.data : [],
144
+ currentValue: Array.isArray(projectedData.value) ? projectedData.value : [],
140
145
  });
141
146
 
142
147
  if (valueToSelect !== null) {
@@ -165,13 +170,13 @@ const sameSet = (a: unknown[], b: unknown[]) => {
165
170
  // v-model with guard to avoid recursive updates
166
171
  const model = computed<unknown[]>({
167
172
  get() {
168
- const curr = Array.isArray(control.value.data) ? control.value.data : [];
173
+ const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
169
174
  // return a fresh copy so PrimeMultiSelect can't mutate JSONForms' array in place
170
175
  return [...curr];
171
176
  },
172
177
  set(val) {
173
178
  const next = Array.isArray(val) ? [...val] : [];
174
- const curr = Array.isArray(control.value.data) ? control.value.data : [];
179
+ const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
175
180
  if (!sameSet(curr, next)) handleChange(control.value.path, next);
176
181
  },
177
182
  });
@@ -21,14 +21,17 @@ export default {
21
21
  renderers: {
22
22
  type: Array,
23
23
  required: false,
24
+ default: undefined,
24
25
  },
25
26
  cells: {
26
27
  type: Array,
27
28
  required: false,
29
+ default: undefined,
28
30
  },
29
31
  config: {
30
32
  type: Object,
31
33
  required: false,
34
+ default: undefined,
32
35
  },
33
36
  },
34
37
  };
@@ -39,12 +42,14 @@ import type { ControlProps } from "@jsonforms/vue";
39
42
  import { useJsonFormsControl } from "@jsonforms/vue";
40
43
  import { computed, ref, getCurrentInstance } from "vue";
41
44
  import { useDerive } from "../composables/useDerive";
45
+ import { useProjection } from "../composables/useProjection";
42
46
  import InputNumber from "primevue/inputnumber";
43
47
 
44
48
  // Access props from the component instance
45
49
  const instance = getCurrentInstance()!;
46
50
  const props = instance.props as unknown as ControlProps;
47
- const { control, handleChange } = useJsonFormsControl(props);
51
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
52
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
48
53
 
49
54
  const options = computed(
50
55
  () =>
@@ -57,7 +62,7 @@ const placeholder = computed<string | undefined>(
57
62
  );
58
63
 
59
64
  // Add derive functionality
60
- useDerive({ control, handleChange });
65
+ useDerive({ control, handleChange, data: projectedData });
61
66
 
62
67
  // Currency and decimal configuration
63
68
  const mode = computed(() => {
@@ -125,7 +130,7 @@ const onBlur = () => {
125
130
  :currency="currency"
126
131
  :min-fraction-digits="minFractionDigits"
127
132
  :max-fraction-digits="maxFractionDigits"
128
- :model-value="typeof control.data === 'number' ? control.data : null"
133
+ :model-value="typeof projectedData === 'number' ? projectedData : null"
129
134
  :placeholder="placeholder"
130
135
  :disabled="!control.enabled"
131
136
  :aria-invalid="!!showErrors || undefined"
@@ -21,14 +21,17 @@ export default {
21
21
  renderers: {
22
22
  type: Array,
23
23
  required: false,
24
+ default: undefined,
24
25
  },
25
26
  cells: {
26
27
  type: Array,
27
28
  required: false,
29
+ default: undefined,
28
30
  },
29
31
  config: {
30
32
  type: Object,
31
33
  required: false,
34
+ default: undefined,
32
35
  },
33
36
  },
34
37
  };
@@ -40,13 +43,15 @@ import { useJsonFormsControl } from "@jsonforms/vue";
40
43
  import { computed, ref, inject, watch, getCurrentInstance } from "vue";
41
44
  import { useProvider } from "../composables/useProvider";
42
45
  import { useDerive } from "../composables/useDerive";
46
+ import { useProjection } from "../composables/useProjection";
43
47
  import InputText from "primevue/inputtext";
44
48
  import AutoComplete from "primevue/autocomplete";
45
49
 
46
50
  // Access props from the component instance
47
51
  const instance = getCurrentInstance()!;
48
52
  const props = instance.props as unknown as ControlProps;
49
- const { control, handleChange } = useJsonFormsControl(props);
53
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
54
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
50
55
 
51
56
  // Provider support for autocomplete functionality
52
57
  const binding = computed(() => {
@@ -110,7 +115,7 @@ const placeholder = computed<string | undefined>(() => {
110
115
  const isAutocomplete = computed(() => !!binding.value);
111
116
 
112
117
  // Add derive functionality
113
- useDerive({ control, handleChange });
118
+ useDerive({ control, handleChange, data: projectedData });
114
119
 
115
120
  // Track user interaction
116
121
  const hasInteracted = ref(false);
@@ -121,7 +126,7 @@ const showErrors = computed(() => hasInteracted.value && control.value.errors);
121
126
  function onInput(val: string | undefined) {
122
127
  // Convert empty strings to undefined for proper required field validation
123
128
  const newValue = val && val.trim() !== "" ? val : undefined;
124
- if (control.value.data !== newValue) {
129
+ if (projectedData.value !== newValue) {
125
130
  handleChange(control.value.path, newValue);
126
131
  }
127
132
  }
@@ -158,7 +163,7 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
158
163
  <AutoComplete
159
164
  v-if="isAutocomplete"
160
165
  class="w-full"
161
- :model-value="control.data ?? ''"
166
+ :model-value="projectedData ?? ''"
162
167
  :suggestions="items"
163
168
  option-label="label"
164
169
  :placeholder="placeholder"
@@ -173,7 +178,7 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
173
178
  <InputText
174
179
  v-else
175
180
  class="w-full"
176
- :model-value="control.data ?? ''"
181
+ :model-value="(projectedData as string) ?? ''"
177
182
  :disabled="!control.enabled"
178
183
  :aria-invalid="!!showErrors || undefined"
179
184
  :placeholder="placeholder"
@@ -21,14 +21,17 @@ export default {
21
21
  renderers: {
22
22
  type: Array,
23
23
  required: false,
24
+ default: undefined,
24
25
  },
25
26
  cells: {
26
27
  type: Array,
27
28
  required: false,
29
+ default: undefined,
28
30
  },
29
31
  config: {
30
32
  type: Object,
31
33
  required: false,
34
+ default: undefined,
32
35
  },
33
36
  },
34
37
  };
@@ -38,12 +41,14 @@ export default {
38
41
  import type { ControlProps } from "@jsonforms/vue";
39
42
  import { useJsonFormsControl } from "@jsonforms/vue";
40
43
  import { computed, ref, getCurrentInstance } from "vue";
44
+ import { useProjection } from "../composables/useProjection";
41
45
  import Textarea from "primevue/textarea";
42
46
 
43
47
  // Access props from the component instance
44
48
  const instance = getCurrentInstance()!;
45
49
  const props = instance.props as unknown as ControlProps;
46
- const { control, handleChange } = useJsonFormsControl(props);
50
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
51
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
47
52
 
48
53
  const placeholder = computed<string | undefined>(
49
54
  () =>
@@ -59,7 +64,7 @@ const showErrors = computed(() => hasInteracted.value && control.value.errors);
59
64
  function onInput(val: string | undefined) {
60
65
  // Convert empty strings to undefined for proper required field validation
61
66
  const newValue = val && val.trim() !== "" ? val : undefined;
62
- if (control.value.data !== newValue) {
67
+ if (projectedData.value !== newValue) {
63
68
  handleChange(control.value.path, newValue);
64
69
  }
65
70
  }
@@ -79,7 +84,7 @@ function onBlur() {
79
84
  </div>
80
85
  <Textarea
81
86
  class="w-full"
82
- :model-value="control.data ?? ''"
87
+ :model-value="(projectedData as string) ?? ''"
83
88
  :disabled="!control.enabled"
84
89
  :aria-invalid="!!showErrors || undefined"
85
90
  :placeholder="placeholder"
@@ -4,6 +4,8 @@ import JfNumber from "./JfNumber.vue";
4
4
  import JfEnum from "./JfEnum.vue";
5
5
  import JfEnumArray from "./JfEnumArray.vue";
6
6
  import JfBoolean from "./JfBoolean.vue";
7
+ import { getProjectedSchema } from "../../core/projection";
8
+ import { resolveScopeSchema } from "../../core/resolveScope";
7
9
 
8
10
  // Auto-inject layout styles
9
11
  const injectLayoutStyles = () => {
@@ -54,6 +56,7 @@ export function registerPrimevueRenderers(jsonformsCore: any): unknown[] {
54
56
  isNumberControl,
55
57
  isIntegerControl,
56
58
  and,
59
+ or,
57
60
  isControl,
58
61
  schemaMatches,
59
62
  isBooleanControl,
@@ -97,27 +100,49 @@ export function registerPrimevueRenderers(jsonformsCore: any): unknown[] {
97
100
  );
98
101
  };
99
102
 
103
+ // Projection-aware schema check: when options.projection is set,
104
+ // 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
+ };
118
+
119
+ const isMultilineProjection = (uischema: unknown, schema: unknown) => {
120
+ const ui = uischema as { options?: { multi?: boolean } };
121
+ return ui?.options?.multi === true &&
122
+ projectedSchemaMatches((s) => s?.type === "string")(uischema, schema);
123
+ };
124
+
100
125
  const renderers = [
101
126
  // Multiline text has higher priority than regular text
102
- { tester: rankWith(PRIME + 4, isMultilineString), renderer: JfTextArea },
103
- { tester: rankWith(PRIME + 3, isStringControl), renderer: JfText },
127
+ { tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)), renderer: JfTextArea },
128
+ { tester: rankWith(PRIME + 3, or(isStringControl, projectedSchemaMatches((s) => s?.type === "string"))), renderer: JfText },
104
129
  {
105
- tester: rankWith(PRIME + 6, isIntegerControl),
130
+ tester: rankWith(PRIME + 6, or(isIntegerControl, projectedSchemaMatches((s) => s?.type === "integer"))),
106
131
  renderer: JfNumber,
107
132
  },
108
133
  {
109
- tester: rankWith(PRIME + 4, isNumberControl),
134
+ tester: rankWith(PRIME + 4, or(isNumberControl, projectedSchemaMatches((s) => s?.type === "number"))),
110
135
  renderer: JfNumber,
111
136
  },
112
137
  {
113
- tester: rankWith(PRIME + 7, and(isControl, schemaMatches(isScalarEnum))),
138
+ tester: rankWith(PRIME + 7, or(and(isControl, schemaMatches(isScalarEnum)), and(isControl, projectedSchemaMatches(isScalarEnum)))),
114
139
  renderer: JfEnum,
115
140
  },
116
141
  {
117
- tester: rankWith(PRIME + 8, and(isControl, schemaMatches(isEnumArray))),
142
+ tester: rankWith(PRIME + 8, or(and(isControl, schemaMatches(isEnumArray)), and(isControl, projectedSchemaMatches(isEnumArray)))),
118
143
  renderer: JfEnumArray,
119
144
  },
120
- { tester: rankWith(PRIME + 3, isBooleanControl), renderer: JfBoolean },
145
+ { tester: rankWith(PRIME + 3, or(isBooleanControl, projectedSchemaMatches((s) => s?.type === "boolean"))), renderer: JfBoolean },
121
146
  ];
122
147
 
123
148
  // Update the exported array