@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.
- package/dist/jsonforms-provider-protocols.css +10 -2
- package/dist/protocols/rest_api.d.ts.map +1 -1
- package/dist/protocols/rest_api.js +5 -1
- package/dist/protocols/rest_api.js.map +1 -1
- package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderAutocomplete.vue.js +36 -23
- package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.d.ts +9 -0
- package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -0
- package/dist/vue/components/ProviderMultiSelect.vue.js +8 -0
- package/dist/vue/components/ProviderMultiSelect.vue.js.map +1 -0
- package/dist/vue/components/ProviderMultiSelect.vue2.js +95 -0
- package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -0
- package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderSelect.vue.js +1 -1
- package/dist/vue/components/ProviderSelect.vue2.js +34 -28
- package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
- package/dist/vue/composables/useProvider.d.ts +10 -2
- package/dist/vue/composables/useProvider.d.ts.map +1 -1
- package/dist/vue/composables/useProvider.js +4 -2
- package/dist/vue/composables/useProvider.js.map +1 -1
- package/dist/vue/index.d.ts +4 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +38 -4
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.d.ts +75 -0
- package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -0
- package/dist/vue/primevue/JfBoolean.vue.js +36 -0
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -0
- package/dist/vue/primevue/JfBoolean.vue2.js +5 -0
- package/dist/vue/primevue/JfBoolean.vue2.js.map +1 -0
- package/dist/vue/primevue/JfEnum.vue.d.ts +75 -0
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -0
- package/dist/vue/primevue/JfEnum.vue.js +8 -0
- package/dist/vue/primevue/JfEnum.vue.js.map +1 -0
- package/dist/vue/primevue/JfEnum.vue2.js +65 -0
- package/dist/vue/primevue/JfEnum.vue2.js.map +1 -0
- package/dist/vue/primevue/JfEnumArray.vue.d.ts +75 -0
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -0
- package/dist/vue/primevue/JfEnumArray.vue.js +84 -0
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -0
- package/dist/vue/primevue/JfEnumArray.vue2.js +5 -0
- package/dist/vue/primevue/JfEnumArray.vue2.js.map +1 -0
- package/dist/vue/primevue/JfNumber.vue.d.ts +75 -0
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -0
- package/dist/vue/primevue/JfNumber.vue.js +50 -0
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -0
- package/dist/vue/primevue/JfNumber.vue2.js +5 -0
- package/dist/vue/primevue/JfNumber.vue2.js.map +1 -0
- package/dist/vue/primevue/JfText.vue.d.ts +75 -0
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -0
- package/dist/vue/primevue/JfText.vue.js +56 -0
- package/dist/vue/primevue/JfText.vue.js.map +1 -0
- package/dist/vue/primevue/JfText.vue2.js +5 -0
- package/dist/vue/primevue/JfText.vue2.js.map +1 -0
- package/dist/vue/primevue/JfTextArea.vue.d.ts +75 -0
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -0
- package/dist/vue/primevue/JfTextArea.vue.js +55 -0
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -0
- package/dist/vue/primevue/JfTextArea.vue2.js +5 -0
- package/dist/vue/primevue/JfTextArea.vue2.js.map +1 -0
- package/dist/vue/primevue/index.d.ts +83 -0
- package/dist/vue/primevue/index.d.ts.map +1 -0
- package/dist/vue/primevue/index.js +71 -0
- package/dist/vue/primevue/index.js.map +1 -0
- package/package.json +7 -2
- package/src/protocols/rest_api.ts +8 -1
- package/src/vue/components/ProviderAutocomplete.vue +31 -12
- package/src/vue/components/ProviderMultiSelect.vue +108 -0
- package/src/vue/components/ProviderSelect.vue +40 -21
- package/src/vue/composables/useProvider.ts +3 -2
- package/src/vue/index.ts +33 -4
- package/src/vue/primevue/JfBoolean.vue +26 -0
- package/src/vue/primevue/JfEnum.vue +67 -0
- package/src/vue/primevue/JfEnumArray.vue +85 -0
- package/src/vue/primevue/JfNumber.vue +42 -0
- package/src/vue/primevue/JfText.vue +47 -0
- package/src/vue/primevue/JfTextArea.vue +46 -0
- package/src/vue/primevue/index.ts +99 -0
- 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="
|
|
41
|
-
<label
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
<
|
|
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,
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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="
|
|
55
|
-
<label
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
.
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
61
|
+
{ tester: providerMultiSelectTester, renderer: ProviderMultiSelect },
|
|
39
62
|
{ tester: providerAutocompleteTester, renderer: ProviderAutocomplete },
|
|
63
|
+
{ tester: providerSelectTester, renderer: ProviderSelect },
|
|
40
64
|
];
|
|
41
65
|
|
|
42
|
-
|
|
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>
|