@narrative.io/jsonforms-provider-protocols 1.0.3 → 1.1.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 (80) hide show
  1. package/dist/jsonforms-provider-protocols.css +10 -2
  2. package/dist/protocols/rest_api.d.ts.map +1 -1
  3. package/dist/protocols/rest_api.js +5 -1
  4. package/dist/protocols/rest_api.js.map +1 -1
  5. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  6. package/dist/vue/components/ProviderAutocomplete.vue.js +36 -23
  7. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  8. package/dist/vue/components/ProviderMultiSelect.vue.d.ts +9 -0
  9. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -0
  10. package/dist/vue/components/ProviderMultiSelect.vue.js +8 -0
  11. package/dist/vue/components/ProviderMultiSelect.vue.js.map +1 -0
  12. package/dist/vue/components/ProviderMultiSelect.vue2.js +95 -0
  13. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -0
  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 +34 -28
  17. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  18. package/dist/vue/composables/useProvider.d.ts +10 -2
  19. package/dist/vue/composables/useProvider.d.ts.map +1 -1
  20. package/dist/vue/composables/useProvider.js +4 -2
  21. package/dist/vue/composables/useProvider.js.map +1 -1
  22. package/dist/vue/index.d.ts +4 -1
  23. package/dist/vue/index.d.ts.map +1 -1
  24. package/dist/vue/index.js +38 -4
  25. package/dist/vue/index.js.map +1 -1
  26. package/dist/vue/primevue/JfBoolean.vue.d.ts +75 -0
  27. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -0
  28. package/dist/vue/primevue/JfBoolean.vue.js +36 -0
  29. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -0
  30. package/dist/vue/primevue/JfBoolean.vue2.js +5 -0
  31. package/dist/vue/primevue/JfBoolean.vue2.js.map +1 -0
  32. package/dist/vue/primevue/JfEnum.vue.d.ts +75 -0
  33. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -0
  34. package/dist/vue/primevue/JfEnum.vue.js +8 -0
  35. package/dist/vue/primevue/JfEnum.vue.js.map +1 -0
  36. package/dist/vue/primevue/JfEnum.vue2.js +65 -0
  37. package/dist/vue/primevue/JfEnum.vue2.js.map +1 -0
  38. package/dist/vue/primevue/JfEnumArray.vue.d.ts +75 -0
  39. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -0
  40. package/dist/vue/primevue/JfEnumArray.vue.js +84 -0
  41. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -0
  42. package/dist/vue/primevue/JfEnumArray.vue2.js +5 -0
  43. package/dist/vue/primevue/JfEnumArray.vue2.js.map +1 -0
  44. package/dist/vue/primevue/JfNumber.vue.d.ts +75 -0
  45. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -0
  46. package/dist/vue/primevue/JfNumber.vue.js +50 -0
  47. package/dist/vue/primevue/JfNumber.vue.js.map +1 -0
  48. package/dist/vue/primevue/JfNumber.vue2.js +5 -0
  49. package/dist/vue/primevue/JfNumber.vue2.js.map +1 -0
  50. package/dist/vue/primevue/JfText.vue.d.ts +75 -0
  51. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -0
  52. package/dist/vue/primevue/JfText.vue.js +56 -0
  53. package/dist/vue/primevue/JfText.vue.js.map +1 -0
  54. package/dist/vue/primevue/JfText.vue2.js +5 -0
  55. package/dist/vue/primevue/JfText.vue2.js.map +1 -0
  56. package/dist/vue/primevue/JfTextArea.vue.d.ts +75 -0
  57. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -0
  58. package/dist/vue/primevue/JfTextArea.vue.js +55 -0
  59. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -0
  60. package/dist/vue/primevue/JfTextArea.vue2.js +5 -0
  61. package/dist/vue/primevue/JfTextArea.vue2.js.map +1 -0
  62. package/dist/vue/primevue/index.d.ts +83 -0
  63. package/dist/vue/primevue/index.d.ts.map +1 -0
  64. package/dist/vue/primevue/index.js +71 -0
  65. package/dist/vue/primevue/index.js.map +1 -0
  66. package/package.json +7 -2
  67. package/src/protocols/rest_api.ts +8 -1
  68. package/src/vue/components/ProviderAutocomplete.vue +31 -12
  69. package/src/vue/components/ProviderMultiSelect.vue +108 -0
  70. package/src/vue/components/ProviderSelect.vue +40 -21
  71. package/src/vue/composables/useProvider.ts +3 -2
  72. package/src/vue/index.ts +33 -4
  73. package/src/vue/primevue/JfBoolean.vue +26 -0
  74. package/src/vue/primevue/JfEnum.vue +67 -0
  75. package/src/vue/primevue/JfEnumArray.vue +85 -0
  76. package/src/vue/primevue/JfNumber.vue +42 -0
  77. package/src/vue/primevue/JfText.vue +47 -0
  78. package/src/vue/primevue/JfTextArea.vue +46 -0
  79. package/src/vue/primevue/index.ts +99 -0
  80. package/src/vue/styles.css +15 -0
@@ -3,6 +3,7 @@ import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
3
  import { useJsonFormsControl } from "@jsonforms/vue";
4
4
  import { computed, ref, watch } from "vue";
5
5
  import { useProvider } from "../composables/useProvider";
6
+ import AutoComplete from "primevue/autocomplete";
6
7
 
7
8
  const props = defineProps<{
8
9
  uischema: ControlElement;
@@ -19,6 +20,7 @@ const binding = computed(() => {
19
20
  }
20
21
  return provider;
21
22
  });
23
+
22
24
  const query = ref("");
23
25
  const { items, loading, error, reload } = useProvider(binding, {
24
26
  data: control.value.data,
@@ -34,21 +36,38 @@ const value = computed({
34
36
  get: () => control.value.data,
35
37
  set: (v) => handleChange(control.value.path, v),
36
38
  });
39
+
40
+ const placeholder = computed(() =>
41
+ loading.value ? "Loading…" : "Type to search…",
42
+ );
43
+
44
+ const onComplete = (event: { query: string }) => {
45
+ query.value = event.query;
46
+ };
47
+
48
+ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
49
+ value.value = (event.value as { value?: unknown })?.value ?? event.value;
50
+ };
37
51
  </script>
38
52
 
39
53
  <template>
40
- <div class="provider-autocomplete">
41
- <label>{{ control.schema.title }}</label>
42
- <input
43
- v-model="query"
44
- :placeholder="loading ? 'Loading…' : 'Type to search…'"
45
- type="text"
54
+ <div class="flex flex-column gap-1">
55
+ <label v-if="control.schema.title" class="text-color text-left">{{
56
+ control.schema.title
57
+ }}</label>
58
+ <div v-if="control.description" class="text-color-secondary text-left">
59
+ {{ control.description }}
60
+ </div>
61
+ <AutoComplete
62
+ v-model="value"
63
+ class="w-full"
64
+ :suggestions="items"
65
+ option-label="label"
66
+ :placeholder="placeholder"
67
+ :disabled="!control.enabled"
68
+ @complete="onComplete"
69
+ @item-select="onSelect"
46
70
  />
47
- <ul v-if="items.length">
48
- <li v-for="it in items" :key="String(it.value)" @click="value = it.value">
49
- {{ it.label }}
50
- </li>
51
- </ul>
52
- <small v-if="error" role="alert">Failed: {{ error }}</small>
71
+ <small v-if="error" class="p-error" role="alert">Failed: {{ error }}</small>
53
72
  </div>
54
73
  </template>
@@ -0,0 +1,108 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
+ import { useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed, inject } from "vue";
5
+ import { useProvider } from "../composables/useProvider";
6
+ import MultiSelect from "primevue/multiselect";
7
+
8
+ const props = defineProps<{
9
+ uischema: ControlElement;
10
+ schema: JsonSchema;
11
+ path: string;
12
+ }>();
13
+ const { control, handleChange } = useJsonFormsControl(props);
14
+
15
+ const binding = computed(() => {
16
+ const provider = control.value.uischema?.options?.provider;
17
+ // Ensure load property is set to 'mount' by default
18
+ if (provider && typeof provider === "object" && !provider.load) {
19
+ return { ...provider, load: "mount" };
20
+ }
21
+ return provider;
22
+ });
23
+
24
+ const deps = computed(
25
+ () =>
26
+ ((
27
+ (control.value.schema as Record<string, unknown>)?.[
28
+ "x-provider"
29
+ ] as Record<string, unknown>
30
+ )?.dependsOn as string[]) ?? [],
31
+ );
32
+ const depValues = computed(() => deps.value.map(() => null)); // you can resolve actual values via control.value.data & pointers
33
+
34
+ // Get the root form data from JSONForms context for template URL resolution
35
+ const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
36
+ const rootData = computed(() => injectedFormData.value || {});
37
+
38
+ const { items, loading, error } = useProvider(binding, {
39
+ data: rootData, // Pass the reactive reference
40
+ path: control.value.path,
41
+ dependsOnValues: depValues.value,
42
+ });
43
+
44
+ // Provider will automatically reload when rootData changes due to reactive cache key
45
+
46
+ // order-insensitive shallow equality for primitive arrays
47
+ const sameSet = (a: unknown[], b: unknown[]) => {
48
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
49
+ return false;
50
+ const s = new Set(b);
51
+ return a.every((v) => s.has(v));
52
+ };
53
+
54
+ // v-model with guard to avoid recursive updates
55
+ const value = computed({
56
+ get() {
57
+ const curr = Array.isArray(control.value.data) ? control.value.data : [];
58
+ // return a fresh copy so MultiSelect can't mutate JSONForms' array in place
59
+ return [...curr];
60
+ },
61
+ set(val) {
62
+ const next = Array.isArray(val) ? [...val] : [];
63
+ const curr = Array.isArray(control.value.data) ? control.value.data : [];
64
+ if (!sameSet(curr, next)) handleChange(control.value.path, next);
65
+ },
66
+ });
67
+
68
+ const placeholder = computed(() => {
69
+ if (loading.value) return "Loading…";
70
+ // Check for placeholder in uischema options
71
+ const uischemaPlaceholder = (
72
+ control.value.uischema as { options?: { placeholder?: string } }
73
+ )?.options?.placeholder;
74
+ return uischemaPlaceholder || "Select…";
75
+ });
76
+ </script>
77
+
78
+ <template>
79
+ <div class="flex flex-column gap-2">
80
+ <label v-if="control.schema.title" class="text-color text-left">{{
81
+ control.schema.title
82
+ }}</label>
83
+ <div v-if="control.description" class="text-color-secondary text-left">
84
+ {{ control.description }}
85
+ </div>
86
+ <MultiSelect
87
+ v-model="value"
88
+ class="w-full"
89
+ :options="items"
90
+ option-label="label"
91
+ option-value="value"
92
+ data-key="value"
93
+ display="chip"
94
+ :placeholder="placeholder"
95
+ :disabled="!control.enabled || loading"
96
+ :show-clear="true"
97
+ />
98
+ <small v-if="error" class="p-error" role="alert"
99
+ >Failed to load: {{ error }}</small
100
+ >
101
+ </div>
102
+ </template>
103
+
104
+ <style scoped>
105
+ :deep(.p-multiselect-label) {
106
+ text-align: left;
107
+ }
108
+ </style>
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
3
  import { useJsonFormsControl } from "@jsonforms/vue";
4
- import { computed, watch } from "vue";
4
+ import { computed, inject } from "vue";
5
5
  import { useProvider } from "../composables/useProvider";
6
+ import Dropdown from "primevue/dropdown";
6
7
 
7
8
  const props = defineProps<{
8
9
  uischema: ControlElement;
@@ -30,41 +31,59 @@ const deps = computed(
30
31
  );
31
32
  const depValues = computed(() => deps.value.map(() => null)); // you can resolve actual values via control.value.data & pointers
32
33
 
33
- const { items, loading, error, reload } = useProvider(binding, {
34
- data: control.value.data,
34
+ // Get the root form data from JSONForms context for template URL resolution
35
+ const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
36
+ const rootData = computed(() => injectedFormData.value || {});
37
+
38
+ const { items, loading, error } = useProvider(binding, {
39
+ data: rootData, // Pass the reactive reference
35
40
  path: control.value.path,
36
41
  dependsOnValues: depValues.value,
37
42
  });
38
43
 
39
- watch(
40
- () => control.value.data,
41
- () => {
42
- // if dependsOn changed → reload
43
- reload();
44
- },
45
- );
44
+ // Provider will automatically reload when rootData changes due to reactive cache key
46
45
 
47
46
  const value = computed({
48
47
  get: () => control.value.data,
49
48
  set: (v) => handleChange(control.value.path, v),
50
49
  });
50
+
51
+ const placeholder = computed(() => {
52
+ if (loading.value) return "Loading…";
53
+ // Check for placeholder in uischema options
54
+ const uischemaPlaceholder = (
55
+ control.value.uischema as { options?: { placeholder?: string } }
56
+ )?.options?.placeholder;
57
+ return uischemaPlaceholder || "Select…";
58
+ });
51
59
  </script>
52
60
 
53
61
  <template>
54
- <div class="provider-select">
55
- <label>{{ control.schema.title }}</label>
56
- <select v-model="value">
57
- <option value="" disabled>{{ loading ? "Loading…" : "Select…" }}</option>
58
- <option v-for="it in items" :key="String(it.value)" :value="it.value">
59
- {{ it.label }}
60
- </option>
61
- </select>
62
- <small v-if="error" role="alert">Failed to load: {{ error }}</small>
62
+ <div class="flex flex-column gap-2">
63
+ <label v-if="control.schema.title" class="text-color text-left">{{
64
+ control.schema.title
65
+ }}</label>
66
+ <div v-if="control.description" class="text-color-secondary text-left">
67
+ {{ control.description }}
68
+ </div>
69
+ <Dropdown
70
+ v-model="value"
71
+ class="w-full"
72
+ :options="items"
73
+ option-label="label"
74
+ option-value="value"
75
+ :placeholder="placeholder"
76
+ :disabled="!control.enabled || loading"
77
+ :show-clear="true"
78
+ />
79
+ <small v-if="error" class="p-error" role="alert"
80
+ >Failed to load: {{ error }}</small
81
+ >
63
82
  </div>
64
83
  </template>
65
84
 
66
85
  <style scoped>
67
- .provider-select select {
68
- min-width: 16rem;
86
+ :deep(.p-dropdown-label) {
87
+ text-align: left;
69
88
  }
70
89
  </style>
@@ -23,7 +23,7 @@ export function useProvider(
23
23
  | Ref<ProviderBinding>
24
24
  | ComputedRef<ProviderBinding>,
25
25
  ctxBits: {
26
- data: unknown;
26
+ data: unknown | Ref<unknown> | ComputedRef<unknown>;
27
27
  path: string;
28
28
  dependsOnValues?: unknown[];
29
29
  uiQuery?: string;
@@ -46,6 +46,7 @@ export function useProvider(
46
46
  b: unref(binding),
47
47
  d: ctxBits.dependsOnValues ?? [],
48
48
  q: ctxBits.uiQuery ?? "",
49
+ data: unref(ctxBits.data), // Include data in cache key for reactivity
49
50
  }),
50
51
  );
51
52
 
@@ -68,7 +69,7 @@ export function useProvider(
68
69
  );
69
70
  }
70
71
  const out = await driver.resolve(bindingValue.config ?? {}, {
71
- data: ctxBits.data,
72
+ data: unref(ctxBits.data),
72
73
  path: ctxBits.path,
73
74
  ui: { query: ctxBits.uiQuery },
74
75
  signal: ac.signal,
package/src/vue/index.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  } from "@jsonforms/core";
10
10
  import ProviderAutocomplete from "./components/ProviderAutocomplete.vue";
11
11
  import ProviderSelect from "./components/ProviderSelect.vue";
12
+ import ProviderMultiSelect from "./components/ProviderMultiSelect.vue";
12
13
 
13
14
  // Custom tester that checks if provider option exists (as object or boolean)
14
15
  const hasProvider = (uischema: UISchemaElement) => {
@@ -17,7 +18,7 @@ const hasProvider = (uischema: UISchemaElement) => {
17
18
 
18
19
  // Create specific testers for each component type
19
20
  const providerSelectTester = rankWith(
20
- 6,
21
+ 106, // Higher than PrimeVue base (100) to ensure providers take precedence
21
22
  and(
22
23
  or(isStringControl, isNumberControl, isIntegerControl),
23
24
  hasProvider,
@@ -26,7 +27,7 @@ const providerSelectTester = rankWith(
26
27
  );
27
28
 
28
29
  const providerAutocompleteTester = rankWith(
29
- 7,
30
+ 107, // Higher than PrimeVue base (100) to ensure providers take precedence
30
31
  and(
31
32
  or(isStringControl, isNumberControl, isIntegerControl),
32
33
  hasProvider,
@@ -34,11 +35,39 @@ const providerAutocompleteTester = rankWith(
34
35
  ),
35
36
  );
36
37
 
38
+ // Custom array tester - check both uischema control type and schema type
39
+ const isArrayControl = (uischema: UISchemaElement, schema: unknown) => {
40
+ const controlSchema = uischema as { type: string; scope?: string };
41
+ if (controlSchema.type !== "Control" || !controlSchema.scope) {
42
+ return false;
43
+ }
44
+
45
+ // Extract the property schema from the root schema
46
+ const rootSchema = schema as { properties?: Record<string, unknown> };
47
+ const propertyPath = controlSchema.scope.replace("#/properties/", "");
48
+ const propertySchema = rootSchema?.properties?.[propertyPath] as {
49
+ type?: string;
50
+ };
51
+
52
+ return propertySchema?.type === "array";
53
+ };
54
+
55
+ const providerMultiSelectTester = rankWith(
56
+ 108, // Highest priority for array controls with providers
57
+ and(isArrayControl, hasProvider),
58
+ );
59
+
37
60
  export const providerRenderers = [
38
- { tester: providerSelectTester, renderer: ProviderSelect },
61
+ { tester: providerMultiSelectTester, renderer: ProviderMultiSelect },
39
62
  { tester: providerAutocompleteTester, renderer: ProviderAutocomplete },
63
+ { tester: providerSelectTester, renderer: ProviderSelect },
40
64
  ];
41
65
 
42
- export { ProviderAutocomplete, ProviderSelect };
66
+ // Export PrimeVue renderers (styles are auto-injected)
67
+ export { primevueRenderers } from "./primevue";
68
+
69
+ // Export individual components
70
+ export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };
43
71
  export { useProvider } from "./composables/useProvider";
44
72
  export * from "./testers";
73
+ export * from "./primevue";
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement } from "@jsonforms/core";
3
+ import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
4
+ import Checkbox from "primevue/checkbox";
5
+
6
+ defineOptions({ name: "JfBoolean" });
7
+
8
+ const props = defineProps(rendererProps<ControlElement>());
9
+ const { control, handleChange } = useJsonFormsControl(props);
10
+
11
+ const onToggle = (val: boolean) => handleChange(control.value.path, val);
12
+ </script>
13
+
14
+ <template>
15
+ <div class="flex items-center gap-2">
16
+ <Checkbox
17
+ :binary="true"
18
+ :model-value="!!control.data"
19
+ :disabled="!control.enabled"
20
+ :aria-invalid="!!control.errors || undefined"
21
+ @update:model-value="onToggle"
22
+ />
23
+ <label v-if="control.label">{{ control.label }}</label>
24
+ <small v-if="control.errors" class="p-error">{{ control.errors }}</small>
25
+ </div>
26
+ </template>
@@ -0,0 +1,67 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
+ import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed } from "vue";
5
+ import Dropdown from "primevue/dropdown";
6
+
7
+ defineOptions({ name: "JfEnum" });
8
+
9
+ const props = defineProps(rendererProps<ControlElement>());
10
+ const { control, handleChange } = useJsonFormsControl(props);
11
+
12
+ type Opt = { label: string; value: unknown };
13
+ const toOptions = (schema?: JsonSchema): Opt[] => {
14
+ if (!schema) return [];
15
+ const s = schema as {
16
+ enum?: unknown[];
17
+ oneOf?: Array<{ title?: string; const: unknown }>;
18
+ };
19
+ if (Array.isArray(s.enum))
20
+ return s.enum.map((v: unknown) => ({ label: String(v), value: v }));
21
+ if (Array.isArray(s.oneOf))
22
+ return s.oneOf.map((o) => ({
23
+ label: o.title ?? String(o.const),
24
+ value: o.const,
25
+ }));
26
+ return [];
27
+ };
28
+
29
+ const placeholder = computed<string | undefined>(
30
+ () =>
31
+ (control.value.uischema as { options?: { placeholder?: string } })?.options
32
+ ?.placeholder ?? control.value.description,
33
+ );
34
+
35
+ const options = computed(() => toOptions(control.value.schema));
36
+ const onSelect = (val: unknown) => handleChange(control.value.path, val);
37
+ </script>
38
+
39
+ <template>
40
+ <div class="flex flex-column gap-2">
41
+ <label v-if="control.label" class="text-color text-left">{{
42
+ control.label
43
+ }}</label>
44
+ <div v-if="control.description" class="text-color-secondary text-left">
45
+ {{ control.description }}
46
+ </div>
47
+ <Dropdown
48
+ class="w-full"
49
+ :options="options"
50
+ option-label="label"
51
+ option-value="value"
52
+ :model-value="control.data ?? null"
53
+ :placeholder="placeholder"
54
+ :disabled="!control.enabled"
55
+ :aria-invalid="!!control.errors || undefined"
56
+ :show-clear="true"
57
+ @update:model-value="onSelect"
58
+ />
59
+ <small v-if="control.errors" class="p-error">{{ control.errors }}</small>
60
+ </div>
61
+ </template>
62
+
63
+ <style scoped>
64
+ :deep(.p-dropdown-label) {
65
+ text-align: left;
66
+ }
67
+ </style>
@@ -0,0 +1,85 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement, JsonSchema } from "@jsonforms/core";
3
+ import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed } from "vue";
5
+ import MultiSelect from "primevue/multiselect";
6
+
7
+ defineOptions({ name: "JfEnumArray" });
8
+
9
+ const props = defineProps(rendererProps<ControlElement>());
10
+ const { control, handleChange } = useJsonFormsControl(props);
11
+
12
+ type Opt = { label: string; value: unknown };
13
+ const toOptions = (schema?: JsonSchema): Opt[] => {
14
+ if (!schema) return [];
15
+ const s = schema as {
16
+ enum?: unknown[];
17
+ oneOf?: Array<{ title?: string; const: unknown }>;
18
+ };
19
+ if (Array.isArray(s.enum))
20
+ return s.enum.map((v: unknown) => ({ label: String(v), value: v }));
21
+ if (Array.isArray(s.oneOf))
22
+ return s.oneOf.map((o) => ({
23
+ label: o.title ?? String(o.const),
24
+ value: o.const,
25
+ }));
26
+ return [];
27
+ };
28
+
29
+ const options = computed(() =>
30
+ toOptions((control.value.schema as { items?: JsonSchema })?.items),
31
+ );
32
+ const placeholder = computed<string | undefined>(
33
+ () =>
34
+ (control.value.uischema as { options?: { placeholder?: string } })?.options
35
+ ?.placeholder ?? control.value.description,
36
+ );
37
+
38
+ // order-insensitive shallow equality for primitive enums
39
+ const sameSet = (a: unknown[], b: unknown[]) => {
40
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
41
+ return false;
42
+ const s = new Set(b);
43
+ return a.every((v) => s.has(v));
44
+ };
45
+
46
+ // v-model with guard to avoid recursive updates
47
+ const model = computed<unknown[]>({
48
+ get() {
49
+ const curr = Array.isArray(control.value.data) ? control.value.data : [];
50
+ // return a fresh copy so PrimeMultiSelect can't mutate JSONForms' array in place
51
+ return [...curr];
52
+ },
53
+ set(val) {
54
+ const next = Array.isArray(val) ? [...val] : [];
55
+ const curr = Array.isArray(control.value.data) ? control.value.data : [];
56
+ if (!sameSet(curr, next)) handleChange(control.value.path, next);
57
+ },
58
+ });
59
+ </script>
60
+
61
+ <template>
62
+ <div class="flex flex-column gap-2">
63
+ <label v-if="control.label" class="text-color text-left">{{
64
+ control.label
65
+ }}</label>
66
+ <div v-if="control.description" class="text-color-secondary text-left">
67
+ {{ control.description }}
68
+ </div>
69
+
70
+ <MultiSelect
71
+ v-model="model"
72
+ class="w-full"
73
+ :options="options"
74
+ option-label="label"
75
+ option-value="value"
76
+ data-key="value"
77
+ display="chip"
78
+ :disabled="!control.enabled"
79
+ :aria-invalid="!!control.errors || undefined"
80
+ :placeholder="placeholder"
81
+ />
82
+
83
+ <small v-if="control.errors" class="p-error">{{ control.errors }}</small>
84
+ </div>
85
+ </template>
@@ -0,0 +1,42 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement } from "@jsonforms/core";
3
+ import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed } from "vue";
5
+ import InputNumber from "primevue/inputnumber";
6
+
7
+ defineOptions({ name: "JfNumber" });
8
+
9
+ const props = defineProps(rendererProps<ControlElement>());
10
+ const { control, handleChange } = useJsonFormsControl(props);
11
+
12
+ const placeholder = computed<string | undefined>(
13
+ () =>
14
+ (control.value.uischema as { options?: { placeholder?: string } })?.options
15
+ ?.placeholder ?? control.value.description,
16
+ );
17
+
18
+ const onNumber = (val: number | null) =>
19
+ handleChange(control.value.path, val ?? undefined);
20
+ </script>
21
+
22
+ <template>
23
+ <div class="flex flex-column gap-2">
24
+ <label v-if="control.label" class="text-color text-left">{{
25
+ control.label
26
+ }}</label>
27
+ <div v-if="control.description" class="text-color-secondary text-left">
28
+ {{ control.description }}
29
+ </div>
30
+ <InputNumber
31
+ class="w-full"
32
+ input-class="w-full"
33
+ :use-grouping="false"
34
+ :model-value="typeof control.data === 'number' ? control.data : null"
35
+ :placeholder="placeholder"
36
+ :disabled="!control.enabled"
37
+ :aria-invalid="!!control.errors || undefined"
38
+ @update:model-value="onNumber"
39
+ />
40
+ <small v-if="control.errors" class="p-error">{{ control.errors }}</small>
41
+ </div>
42
+ </template>
@@ -0,0 +1,47 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement } from "@jsonforms/core";
3
+ import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed } from "vue";
5
+ import InputText from "primevue/inputtext";
6
+
7
+ defineOptions({ name: "JfText" });
8
+
9
+ const props = defineProps(rendererProps<ControlElement>());
10
+ const { control, handleChange } = useJsonFormsControl(props);
11
+
12
+ const placeholder = computed<string | undefined>(
13
+ () =>
14
+ (control.value.uischema as { options?: { placeholder?: string } })?.options
15
+ ?.placeholder ?? control.value.description,
16
+ );
17
+
18
+ function onInput(val: string | undefined) {
19
+ const newValue = val ?? "";
20
+ if ((control.value.data ?? "") !== newValue) {
21
+ handleChange(control.value.path, newValue);
22
+ }
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+ <div class="flex flex-column gap-2">
28
+ <label v-if="control.label" class="text-color text-left">{{
29
+ control.label
30
+ }}</label>
31
+ <div v-if="control.description" class="text-color-secondary text-left">
32
+ {{ control.description }}
33
+ </div>
34
+ <InputText
35
+ class="w-full"
36
+ :model-value="control.data ?? ''"
37
+ :disabled="!control.enabled"
38
+ :aria-invalid="!!control.errors || undefined"
39
+ :placeholder="placeholder"
40
+ autocapitalize="off"
41
+ autocomplete="off"
42
+ spellcheck="false"
43
+ @update:model-value="onInput"
44
+ />
45
+ <small v-if="control.errors" class="p-error">{{ control.errors }}</small>
46
+ </div>
47
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import type { ControlElement } from "@jsonforms/core";
3
+ import { rendererProps, useJsonFormsControl } from "@jsonforms/vue";
4
+ import { computed } from "vue";
5
+ import Textarea from "primevue/textarea";
6
+
7
+ defineOptions({ name: "JfTextArea" });
8
+
9
+ const props = defineProps(rendererProps<ControlElement>());
10
+ const { control, handleChange } = useJsonFormsControl(props);
11
+
12
+ const placeholder = computed<string | undefined>(
13
+ () =>
14
+ (control.value.uischema as { options?: { placeholder?: string } })?.options
15
+ ?.placeholder ?? control.value.description,
16
+ );
17
+
18
+ function onInput(val: string | undefined) {
19
+ const newValue = val ?? "";
20
+ if ((control.value.data ?? "") !== newValue) {
21
+ handleChange(control.value.path, newValue);
22
+ }
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+ <div class="flex flex-column gap-2">
28
+ <label v-if="control.label" class="text-color text-left">{{
29
+ control.label
30
+ }}</label>
31
+ <div v-if="control.description" class="text-color-secondary text-left">
32
+ {{ control.description }}
33
+ </div>
34
+ <Textarea
35
+ class="w-full"
36
+ :model-value="control.data ?? ''"
37
+ :disabled="!control.enabled"
38
+ :aria-invalid="!!control.errors || undefined"
39
+ :placeholder="placeholder"
40
+ :rows="4"
41
+ :auto-resize="true"
42
+ @update:model-value="onInput"
43
+ />
44
+ <small v-if="control.errors" class="p-error">{{ control.errors }}</small>
45
+ </div>
46
+ </template>