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

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 (99) 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/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 +35 -0
  45. package/dist/vue/composables/useProjection.d.ts.map +1 -0
  46. package/dist/vue/composables/useProjection.js +33 -0
  47. package/dist/vue/composables/useProjection.js.map +1 -0
  48. package/dist/vue/index.d.ts +5 -0
  49. package/dist/vue/index.d.ts.map +1 -1
  50. package/dist/vue/index.js +17 -29
  51. package/dist/vue/index.js.map +1 -1
  52. package/dist/vue/primevue/JfBoolean.vue.d.ts +9 -0
  53. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  54. package/dist/vue/primevue/JfBoolean.vue.js +21 -10
  55. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  56. package/dist/vue/primevue/JfEnum.vue.d.ts +9 -0
  57. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  58. package/dist/vue/primevue/JfEnum.vue.js +20 -18
  59. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  60. package/dist/vue/primevue/JfEnumArray.vue.d.ts +9 -0
  61. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  62. package/dist/vue/primevue/JfEnumArray.vue.js +24 -14
  63. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  64. package/dist/vue/primevue/JfNumber.vue.d.ts +9 -0
  65. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  66. package/dist/vue/primevue/JfNumber.vue.js +20 -18
  67. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  68. package/dist/vue/primevue/JfText.vue.d.ts +9 -0
  69. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  70. package/dist/vue/primevue/JfText.vue.js +29 -28
  71. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  72. package/dist/vue/primevue/JfTextArea.vue.d.ts +9 -0
  73. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  74. package/dist/vue/primevue/JfTextArea.vue.js +23 -14
  75. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  76. package/dist/vue/primevue/index.d.ts.map +1 -1
  77. package/dist/vue/primevue/index.js +22 -7
  78. package/dist/vue/primevue/index.js.map +1 -1
  79. package/package.json +7 -3
  80. package/src/core/projection.ts +136 -0
  81. package/src/core/resolveScope.ts +39 -0
  82. package/src/core/transforms.ts +91 -26
  83. package/src/core/types.ts +8 -0
  84. package/src/index.ts +7 -0
  85. package/src/vue/components/ProviderAutocomplete.vue +4 -2
  86. package/src/vue/components/ProviderMultiSelect.vue +6 -4
  87. package/src/vue/components/ProviderSelect.vue +5 -3
  88. package/src/vue/composables/useDataLayer.ts +43 -0
  89. package/src/vue/composables/useDerive.ts +19 -16
  90. package/src/vue/composables/useDirtyValidation.ts +15 -0
  91. package/src/vue/composables/useProjection.ts +74 -0
  92. package/src/vue/index.ts +21 -46
  93. package/src/vue/primevue/JfBoolean.vue +18 -5
  94. package/src/vue/primevue/JfEnum.vue +16 -16
  95. package/src/vue/primevue/JfEnumArray.vue +21 -9
  96. package/src/vue/primevue/JfNumber.vue +16 -16
  97. package/src/vue/primevue/JfText.vue +22 -22
  98. package/src/vue/primevue/JfTextArea.vue +20 -11
  99. package/src/vue/primevue/index.ts +32 -7
@@ -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,16 @@ 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";
47
+ import { useDirtyValidation } from "../composables/useDirtyValidation";
43
48
  import InputText from "primevue/inputtext";
44
49
  import AutoComplete from "primevue/autocomplete";
45
50
 
46
51
  // Access props from the component instance
47
52
  const instance = getCurrentInstance()!;
48
53
  const props = instance.props as unknown as ControlProps;
49
- const { control, handleChange } = useJsonFormsControl(props);
54
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
55
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
50
56
 
51
57
  // Provider support for autocomplete functionality
52
58
  const binding = computed(() => {
@@ -110,32 +116,28 @@ const placeholder = computed<string | undefined>(() => {
110
116
  const isAutocomplete = computed(() => !!binding.value);
111
117
 
112
118
  // Add derive functionality
113
- useDerive({ control, handleChange });
119
+ useDerive({ control, handleChange, data: projectedData });
114
120
 
115
- // Track user interaction
116
- const hasInteracted = ref(false);
117
- const hasFocused = ref(false);
118
-
119
- const showErrors = computed(() => hasInteracted.value && control.value.errors);
121
+ // Track user interaction — errors only show after blur
122
+ const { showErrors, markDirty } = useDirtyValidation(control);
120
123
 
121
124
  function onInput(val: string | undefined) {
122
125
  // Convert empty strings to undefined for proper required field validation
123
126
  const newValue = val && val.trim() !== "" ? val : undefined;
124
- if (control.value.data !== newValue) {
127
+ if (projectedData.value !== newValue) {
125
128
  handleChange(control.value.path, newValue);
126
129
  }
127
130
  }
128
131
 
129
132
  function onBlur() {
130
- if (hasFocused.value) {
131
- hasInteracted.value = true;
133
+ markDirty();
134
+ // Normalize empty strings to undefined so required validation fires
135
+ const val = projectedData.value;
136
+ if (typeof val === "string" && val.trim() === "") {
137
+ handleChange(control.value.path, undefined);
132
138
  }
133
139
  }
134
140
 
135
- function onFocus() {
136
- hasFocused.value = true;
137
- }
138
-
139
141
  // Autocomplete specific handlers
140
142
  const onComplete = (event: { query: string }) => {
141
143
  query.value = event.query;
@@ -157,32 +159,30 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
157
159
  </div>
158
160
  <AutoComplete
159
161
  v-if="isAutocomplete"
160
- class="w-full"
161
- :model-value="control.data ?? ''"
162
+ :class="['w-full', { 'p-invalid': showErrors }]"
163
+ :model-value="projectedData ?? ''"
162
164
  :suggestions="items"
163
165
  option-label="label"
164
166
  :placeholder="placeholder"
165
167
  :disabled="!control.enabled"
166
- :aria-invalid="!!showErrors || undefined"
168
+ :aria-invalid="showErrors || undefined"
167
169
  @complete="onComplete"
168
170
  @item-select="onSelect"
169
171
  @update:model-value="onInput"
170
172
  @blur="onBlur"
171
- @focus="onFocus"
172
173
  />
173
174
  <InputText
174
175
  v-else
175
- class="w-full"
176
- :model-value="control.data ?? ''"
176
+ :class="['w-full', { 'p-invalid': showErrors }]"
177
+ :model-value="(projectedData as string) ?? ''"
177
178
  :disabled="!control.enabled"
178
- :aria-invalid="!!showErrors || undefined"
179
+ :aria-invalid="showErrors || undefined"
179
180
  :placeholder="placeholder"
180
181
  autocapitalize="off"
181
182
  autocomplete="off"
182
183
  spellcheck="false"
183
184
  @update:model-value="onInput"
184
185
  @blur="onBlur"
185
- @focus="onFocus"
186
186
  />
187
187
  <small v-if="error" class="p-error" role="alert">Failed: {{ error }}</small>
188
188
  <small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
@@ -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
  };
@@ -37,13 +40,16 @@ export default {
37
40
  <script setup lang="ts">
38
41
  import type { ControlProps } from "@jsonforms/vue";
39
42
  import { useJsonFormsControl } from "@jsonforms/vue";
40
- import { computed, ref, getCurrentInstance } from "vue";
43
+ import { computed, getCurrentInstance } from "vue";
44
+ import { useProjection } from "../composables/useProjection";
45
+ import { useDirtyValidation } from "../composables/useDirtyValidation";
41
46
  import Textarea from "primevue/textarea";
42
47
 
43
48
  // Access props from the component instance
44
49
  const instance = getCurrentInstance()!;
45
50
  const props = instance.props as unknown as ControlProps;
46
- const { control, handleChange } = useJsonFormsControl(props);
51
+ const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
52
+ const { projectedData, handleProjectedChange: handleChange } = useProjection(control, rawHandleChange);
47
53
 
48
54
  const placeholder = computed<string | undefined>(
49
55
  () =>
@@ -51,21 +57,24 @@ const placeholder = computed<string | undefined>(
51
57
  ?.placeholder ?? control.value.description,
52
58
  );
53
59
 
54
- // Track user interaction
55
- const hasInteracted = ref(false);
56
-
57
- const showErrors = computed(() => hasInteracted.value && control.value.errors);
60
+ // Track user interaction — errors only show after blur
61
+ const { showErrors, markDirty } = useDirtyValidation(control);
58
62
 
59
63
  function onInput(val: string | undefined) {
60
64
  // Convert empty strings to undefined for proper required field validation
61
65
  const newValue = val && val.trim() !== "" ? val : undefined;
62
- if (control.value.data !== newValue) {
66
+ if (projectedData.value !== newValue) {
63
67
  handleChange(control.value.path, newValue);
64
68
  }
65
69
  }
66
70
 
67
71
  function onBlur() {
68
- hasInteracted.value = true;
72
+ markDirty();
73
+ // Normalize empty strings to undefined so required validation fires
74
+ const val = projectedData.value;
75
+ if (typeof val === "string" && val.trim() === "") {
76
+ handleChange(control.value.path, undefined);
77
+ }
69
78
  }
70
79
  </script>
71
80
 
@@ -78,10 +87,10 @@ function onBlur() {
78
87
  {{ control.description }}
79
88
  </div>
80
89
  <Textarea
81
- class="w-full"
82
- :model-value="control.data ?? ''"
90
+ :class="['w-full', { 'p-invalid': showErrors }]"
91
+ :model-value="(projectedData as string) ?? ''"
83
92
  :disabled="!control.enabled"
84
- :aria-invalid="!!showErrors || undefined"
93
+ :aria-invalid="showErrors || undefined"
85
94
  :placeholder="placeholder"
86
95
  :rows="4"
87
96
  :auto-resize="true"
@@ -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