@narrative.io/jsonforms-provider-protocols 1.1.0-beta.0 → 1.1.0-beta.10
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/README.md +61 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/jsonforms-provider-protocols.css +1 -1
- package/dist/protocols/rest_api.d.ts +1 -0
- package/dist/protocols/rest_api.d.ts.map +1 -1
- package/dist/protocols/rest_api.js +6 -1
- package/dist/protocols/rest_api.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/composables/useDerive.d.ts +13 -0
- package/dist/vue/composables/useDerive.d.ts.map +1 -0
- package/dist/vue/composables/useDerive.js +71 -0
- package/dist/vue/composables/useDerive.js.map +1 -0
- package/dist/vue/index.d.ts +3 -2
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +26 -8
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.d.ts +20 -32
- package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.js +38 -6
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts +20 -32
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.js +152 -5
- package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnum.vue2.js +1 -61
- package/dist/vue/primevue/JfEnum.vue2.js.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts +20 -32
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.js +93 -13
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts +20 -32
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.js +87 -13
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts +20 -32
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfText.vue.js +131 -16
- package/dist/vue/primevue/JfText.vue.js.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts +20 -32
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.js +49 -11
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
- package/dist/vue/primevue/index.d.ts +2 -75
- package/dist/vue/primevue/index.d.ts.map +1 -1
- package/dist/vue/primevue/index.js +46 -28
- package/dist/vue/primevue/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +15 -2
- package/src/protocols/rest_api.ts +8 -1
- package/src/vue/components/ProviderMultiSelect.vue +108 -0
- package/src/vue/composables/useDerive.ts +105 -0
- package/src/vue/index.ts +36 -3
- package/src/vue/primevue/JfBoolean.vue +42 -5
- package/src/vue/primevue/JfEnum.vue +131 -20
- package/src/vue/primevue/JfEnumArray.vue +118 -14
- package/src/vue/primevue/JfNumber.vue +104 -13
- package/src/vue/primevue/JfText.vue +156 -13
- package/src/vue/primevue/JfTextArea.vue +57 -10
- package/src/vue/primevue/index.ts +48 -37
- package/src/vue/styles.css +5 -0
|
@@ -1,12 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: "JfEnumArray",
|
|
4
|
+
props: {
|
|
5
|
+
uischema: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
schema: {
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
path: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
enabled: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: undefined,
|
|
20
|
+
},
|
|
21
|
+
renderers: {
|
|
22
|
+
type: Array,
|
|
23
|
+
required: false,
|
|
24
|
+
},
|
|
25
|
+
cells: {
|
|
26
|
+
type: Array,
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
29
|
+
config: {
|
|
30
|
+
type: Object,
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
</script>
|
|
36
|
+
|
|
1
37
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
38
|
+
import type { JsonSchema } from "@jsonforms/core";
|
|
39
|
+
import type { ControlProps } from "@jsonforms/vue";
|
|
40
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
41
|
+
import { computed, inject, getCurrentInstance } from "vue";
|
|
42
|
+
import { useProvider } from "../composables/useProvider";
|
|
43
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
44
|
import MultiSelect from "primevue/multiselect";
|
|
6
45
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props =
|
|
46
|
+
// Access props from the component instance
|
|
47
|
+
const instance = getCurrentInstance()!;
|
|
48
|
+
const props = instance.props as unknown as ControlProps;
|
|
10
49
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
11
50
|
|
|
12
51
|
type Opt = { label: string; value: unknown };
|
|
@@ -26,14 +65,74 @@ const toOptions = (schema?: JsonSchema): Opt[] => {
|
|
|
26
65
|
return [];
|
|
27
66
|
};
|
|
28
67
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
// Provider support
|
|
69
|
+
const binding = computed(() => {
|
|
70
|
+
const provider = control.value.uischema?.options?.provider;
|
|
71
|
+
// Ensure load property is set to 'mount' by default
|
|
72
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
73
|
+
return { ...provider, load: "mount" };
|
|
74
|
+
}
|
|
75
|
+
return provider;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const deps = computed(
|
|
33
79
|
() =>
|
|
34
|
-
(
|
|
35
|
-
|
|
80
|
+
((
|
|
81
|
+
(control.value.schema as Record<string, unknown>)?.[
|
|
82
|
+
"x-provider"
|
|
83
|
+
] as Record<string, unknown>
|
|
84
|
+
)?.dependsOn as string[]) ?? [],
|
|
36
85
|
);
|
|
86
|
+
const depValues = computed(() => {
|
|
87
|
+
return deps.value.map((dep) => {
|
|
88
|
+
// Resolve dependency value from form data using JSON pointer-like path
|
|
89
|
+
const path = dep.startsWith("#/") ? dep.slice(2) : dep;
|
|
90
|
+
const keys = path.replace(/\//g, ".").split(".");
|
|
91
|
+
let value: unknown = rootData.value;
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
if (value && typeof value === "object" && key in value) {
|
|
94
|
+
value = (value as Record<string, unknown>)[key];
|
|
95
|
+
} else {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Get the root form data from JSONForms context for template URL resolution
|
|
104
|
+
const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
|
|
105
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
106
|
+
|
|
107
|
+
// Use provider if available, otherwise fall back to schema enum/oneOf
|
|
108
|
+
const {
|
|
109
|
+
items: providerItems,
|
|
110
|
+
loading,
|
|
111
|
+
error,
|
|
112
|
+
} = useProvider(binding, {
|
|
113
|
+
data: rootData,
|
|
114
|
+
path: control.value.path,
|
|
115
|
+
dependsOnValues: depValues.value,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const options = computed(() => {
|
|
119
|
+
// Use provider items if available, otherwise fall back to schema enum/oneOf
|
|
120
|
+
if (binding.value && providerItems.value.length > 0) {
|
|
121
|
+
return providerItems.value;
|
|
122
|
+
}
|
|
123
|
+
return toOptions((control.value.schema as { items?: JsonSchema })?.items);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Add derive functionality
|
|
127
|
+
useDerive({ control, handleChange });
|
|
128
|
+
|
|
129
|
+
const placeholder = computed<string | undefined>(() => {
|
|
130
|
+
if (loading.value) return "Loading…";
|
|
131
|
+
return (
|
|
132
|
+
(control.value.uischema as { options?: { placeholder?: string } })?.options
|
|
133
|
+
?.placeholder ?? control.value.description
|
|
134
|
+
);
|
|
135
|
+
});
|
|
37
136
|
|
|
38
137
|
// order-insensitive shallow equality for primitive enums
|
|
39
138
|
const sameSet = (a: unknown[], b: unknown[]) => {
|
|
@@ -75,11 +174,16 @@ const model = computed<unknown[]>({
|
|
|
75
174
|
option-value="value"
|
|
76
175
|
data-key="value"
|
|
77
176
|
display="chip"
|
|
78
|
-
:disabled="!control.enabled"
|
|
177
|
+
:disabled="!control.enabled || loading"
|
|
79
178
|
:aria-invalid="!!control.errors || undefined"
|
|
80
179
|
:placeholder="placeholder"
|
|
81
180
|
/>
|
|
82
181
|
|
|
83
|
-
<small v-if="
|
|
182
|
+
<small v-if="error" class="p-error" role="alert"
|
|
183
|
+
>Failed to load: {{ error }}</small
|
|
184
|
+
>
|
|
185
|
+
<small v-else-if="control.errors" class="p-error">{{
|
|
186
|
+
control.errors
|
|
187
|
+
}}</small>
|
|
84
188
|
</div>
|
|
85
189
|
</template>
|
|
@@ -1,22 +1,108 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: "JfNumber",
|
|
4
|
+
props: {
|
|
5
|
+
uischema: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
schema: {
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
path: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
enabled: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: undefined,
|
|
20
|
+
},
|
|
21
|
+
renderers: {
|
|
22
|
+
type: Array,
|
|
23
|
+
required: false,
|
|
24
|
+
},
|
|
25
|
+
cells: {
|
|
26
|
+
type: Array,
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
29
|
+
config: {
|
|
30
|
+
type: Object,
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
</script>
|
|
36
|
+
|
|
1
37
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import { computed } from "vue";
|
|
38
|
+
import type { ControlProps } from "@jsonforms/vue";
|
|
39
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
40
|
+
import { computed, ref, getCurrentInstance } from "vue";
|
|
41
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
42
|
import InputNumber from "primevue/inputnumber";
|
|
6
43
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props =
|
|
44
|
+
// Access props from the component instance
|
|
45
|
+
const instance = getCurrentInstance()!;
|
|
46
|
+
const props = instance.props as unknown as ControlProps;
|
|
10
47
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
11
48
|
|
|
12
|
-
const
|
|
49
|
+
const options = computed(
|
|
13
50
|
() =>
|
|
14
|
-
(control.value.uischema as { options?:
|
|
15
|
-
?.
|
|
51
|
+
(control.value.uischema as { options?: Record<string, unknown> })
|
|
52
|
+
?.options ?? {},
|
|
16
53
|
);
|
|
17
54
|
|
|
18
|
-
const
|
|
55
|
+
const placeholder = computed<string | undefined>(
|
|
56
|
+
() => (options.value.placeholder as string) ?? control.value.description,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Add derive functionality
|
|
60
|
+
useDerive({ control, handleChange });
|
|
61
|
+
|
|
62
|
+
// Currency and decimal configuration
|
|
63
|
+
const mode = computed(() => {
|
|
64
|
+
if (options.value.currency) return "currency";
|
|
65
|
+
if (options.value.decimal || typeof options.value.precision === "number")
|
|
66
|
+
return "decimal";
|
|
67
|
+
return undefined;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const currency = computed(() =>
|
|
71
|
+
typeof options.value.currency === "string" ? options.value.currency : "USD",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const minFractionDigits = computed(() => {
|
|
75
|
+
if (mode.value === "currency") return 2;
|
|
76
|
+
if (typeof options.value.precision === "number")
|
|
77
|
+
return options.value.precision;
|
|
78
|
+
return undefined;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const maxFractionDigits = computed(() => {
|
|
82
|
+
if (mode.value === "currency") return 2;
|
|
83
|
+
if (typeof options.value.precision === "number")
|
|
84
|
+
return options.value.precision;
|
|
85
|
+
return undefined;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const useGrouping = computed(() => {
|
|
89
|
+
// Enable grouping for currency by default, or if explicitly set
|
|
90
|
+
if (mode.value === "currency") return true;
|
|
91
|
+
return options.value.useGrouping === true;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Track user interaction
|
|
95
|
+
const hasInteracted = ref(false);
|
|
96
|
+
|
|
97
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
98
|
+
|
|
99
|
+
const onNumber = (val: number | null) => {
|
|
19
100
|
handleChange(control.value.path, val ?? undefined);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const onBlur = () => {
|
|
104
|
+
hasInteracted.value = true;
|
|
105
|
+
};
|
|
20
106
|
</script>
|
|
21
107
|
|
|
22
108
|
<template>
|
|
@@ -30,13 +116,18 @@ const onNumber = (val: number | null) =>
|
|
|
30
116
|
<InputNumber
|
|
31
117
|
class="w-full"
|
|
32
118
|
input-class="w-full"
|
|
33
|
-
:use-grouping="
|
|
119
|
+
:use-grouping="useGrouping"
|
|
120
|
+
:mode="mode"
|
|
121
|
+
:currency="currency"
|
|
122
|
+
:min-fraction-digits="minFractionDigits"
|
|
123
|
+
:max-fraction-digits="maxFractionDigits"
|
|
34
124
|
:model-value="typeof control.data === 'number' ? control.data : null"
|
|
35
125
|
:placeholder="placeholder"
|
|
36
126
|
:disabled="!control.enabled"
|
|
37
|
-
:aria-invalid="!!
|
|
127
|
+
:aria-invalid="!!showErrors || undefined"
|
|
38
128
|
@update:model-value="onNumber"
|
|
129
|
+
@blur="onBlur"
|
|
39
130
|
/>
|
|
40
|
-
<small v-if="
|
|
131
|
+
<small v-if="showErrors" class="p-error">{{ control.errors }}</small>
|
|
41
132
|
</div>
|
|
42
133
|
</template>
|
|
@@ -1,26 +1,150 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: "JfText",
|
|
4
|
+
props: {
|
|
5
|
+
uischema: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
schema: {
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
path: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
enabled: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: undefined,
|
|
20
|
+
},
|
|
21
|
+
renderers: {
|
|
22
|
+
type: Array,
|
|
23
|
+
required: false,
|
|
24
|
+
},
|
|
25
|
+
cells: {
|
|
26
|
+
type: Array,
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
29
|
+
config: {
|
|
30
|
+
type: Object,
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
</script>
|
|
36
|
+
|
|
1
37
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import { computed } from "vue";
|
|
38
|
+
import type { ControlProps } from "@jsonforms/vue";
|
|
39
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
40
|
+
import { computed, ref, inject, watch, getCurrentInstance } from "vue";
|
|
41
|
+
import { useProvider } from "../composables/useProvider";
|
|
42
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
43
|
import InputText from "primevue/inputtext";
|
|
44
|
+
import AutoComplete from "primevue/autocomplete";
|
|
6
45
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props =
|
|
46
|
+
// Access props from the component instance
|
|
47
|
+
const instance = getCurrentInstance()!;
|
|
48
|
+
const props = instance.props as unknown as ControlProps;
|
|
10
49
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
11
50
|
|
|
12
|
-
|
|
51
|
+
// Provider support for autocomplete functionality
|
|
52
|
+
const binding = computed(() => {
|
|
53
|
+
const provider = control.value.uischema?.options?.provider;
|
|
54
|
+
// Ensure load property is set to 'query' by default for autocomplete
|
|
55
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
56
|
+
return { ...provider, load: "query" };
|
|
57
|
+
}
|
|
58
|
+
return provider;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const deps = computed(
|
|
13
62
|
() =>
|
|
14
|
-
(
|
|
15
|
-
|
|
63
|
+
((
|
|
64
|
+
(control.value.schema as Record<string, unknown>)?.[
|
|
65
|
+
"x-provider"
|
|
66
|
+
] as Record<string, unknown>
|
|
67
|
+
)?.dependsOn as string[]) ?? [],
|
|
16
68
|
);
|
|
69
|
+
const depValues = computed(() => {
|
|
70
|
+
return deps.value.map((dep) => {
|
|
71
|
+
// Resolve dependency value from form data using JSON pointer-like path
|
|
72
|
+
const path = dep.startsWith("#/") ? dep.slice(2) : dep;
|
|
73
|
+
const keys = path.replace(/\//g, ".").split(".");
|
|
74
|
+
let value: unknown = rootData.value;
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
if (value && typeof value === "object" && key in value) {
|
|
77
|
+
value = (value as Record<string, unknown>)[key];
|
|
78
|
+
} else {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Get the root form data from JSONForms context for template URL resolution
|
|
87
|
+
const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
|
|
88
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
89
|
+
|
|
90
|
+
const query = ref("");
|
|
91
|
+
const { items, loading, error, reload } = useProvider(binding, {
|
|
92
|
+
data: rootData,
|
|
93
|
+
path: control.value.path,
|
|
94
|
+
uiQuery: query.value,
|
|
95
|
+
dependsOnValues: depValues.value,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
watch(query, () => {
|
|
99
|
+
if (binding.value?.load === "query") reload();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const placeholder = computed<string | undefined>(() => {
|
|
103
|
+
if (loading.value) return "Loading…";
|
|
104
|
+
return (
|
|
105
|
+
(control.value.uischema as { options?: { placeholder?: string } })?.options
|
|
106
|
+
?.placeholder ?? control.value.description
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const isAutocomplete = computed(() => !!binding.value);
|
|
111
|
+
|
|
112
|
+
// Add derive functionality
|
|
113
|
+
useDerive({ control, handleChange });
|
|
114
|
+
|
|
115
|
+
// Track user interaction
|
|
116
|
+
const hasInteracted = ref(false);
|
|
117
|
+
const hasFocused = ref(false);
|
|
118
|
+
|
|
119
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
17
120
|
|
|
18
121
|
function onInput(val: string | undefined) {
|
|
19
|
-
|
|
20
|
-
|
|
122
|
+
// Convert empty strings to undefined for proper required field validation
|
|
123
|
+
const newValue = val && val.trim() !== "" ? val : undefined;
|
|
124
|
+
if (control.value.data !== newValue) {
|
|
21
125
|
handleChange(control.value.path, newValue);
|
|
22
126
|
}
|
|
23
127
|
}
|
|
128
|
+
|
|
129
|
+
function onBlur() {
|
|
130
|
+
if (hasFocused.value) {
|
|
131
|
+
hasInteracted.value = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function onFocus() {
|
|
136
|
+
hasFocused.value = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Autocomplete specific handlers
|
|
140
|
+
const onComplete = (event: { query: string }) => {
|
|
141
|
+
query.value = event.query;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
|
|
145
|
+
const newValue = (event.value as { value?: unknown })?.value ?? event.value;
|
|
146
|
+
handleChange(control.value.path, newValue);
|
|
147
|
+
};
|
|
24
148
|
</script>
|
|
25
149
|
|
|
26
150
|
<template>
|
|
@@ -31,17 +155,36 @@ function onInput(val: string | undefined) {
|
|
|
31
155
|
<div v-if="control.description" class="text-color-secondary text-left">
|
|
32
156
|
{{ control.description }}
|
|
33
157
|
</div>
|
|
158
|
+
<AutoComplete
|
|
159
|
+
v-if="isAutocomplete"
|
|
160
|
+
class="w-full"
|
|
161
|
+
:model-value="control.data ?? ''"
|
|
162
|
+
:suggestions="items"
|
|
163
|
+
option-label="label"
|
|
164
|
+
:placeholder="placeholder"
|
|
165
|
+
:disabled="!control.enabled"
|
|
166
|
+
:aria-invalid="!!showErrors || undefined"
|
|
167
|
+
@complete="onComplete"
|
|
168
|
+
@item-select="onSelect"
|
|
169
|
+
@update:model-value="onInput"
|
|
170
|
+
@blur="onBlur"
|
|
171
|
+
@focus="onFocus"
|
|
172
|
+
/>
|
|
34
173
|
<InputText
|
|
174
|
+
v-else
|
|
35
175
|
class="w-full"
|
|
36
176
|
:model-value="control.data ?? ''"
|
|
37
177
|
:disabled="!control.enabled"
|
|
38
|
-
:aria-invalid="!!
|
|
178
|
+
:aria-invalid="!!showErrors || undefined"
|
|
39
179
|
:placeholder="placeholder"
|
|
40
180
|
autocapitalize="off"
|
|
41
181
|
autocomplete="off"
|
|
42
182
|
spellcheck="false"
|
|
43
183
|
@update:model-value="onInput"
|
|
184
|
+
@blur="onBlur"
|
|
185
|
+
@focus="onFocus"
|
|
44
186
|
/>
|
|
45
|
-
<small v-if="
|
|
187
|
+
<small v-if="error" class="p-error" role="alert">Failed: {{ error }}</small>
|
|
188
|
+
<small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
|
|
46
189
|
</div>
|
|
47
190
|
</template>
|
|
@@ -1,12 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: "JfTextArea",
|
|
4
|
+
props: {
|
|
5
|
+
uischema: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
schema: {
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
path: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
enabled: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: undefined,
|
|
20
|
+
},
|
|
21
|
+
renderers: {
|
|
22
|
+
type: Array,
|
|
23
|
+
required: false,
|
|
24
|
+
},
|
|
25
|
+
cells: {
|
|
26
|
+
type: Array,
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
29
|
+
config: {
|
|
30
|
+
type: Object,
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
</script>
|
|
36
|
+
|
|
1
37
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import { computed } from "vue";
|
|
38
|
+
import type { ControlProps } from "@jsonforms/vue";
|
|
39
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
40
|
+
import { computed, ref, getCurrentInstance } from "vue";
|
|
5
41
|
import Textarea from "primevue/textarea";
|
|
6
42
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props =
|
|
43
|
+
// Access props from the component instance
|
|
44
|
+
const instance = getCurrentInstance()!;
|
|
45
|
+
const props = instance.props as unknown as ControlProps;
|
|
10
46
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
11
47
|
|
|
12
48
|
const placeholder = computed<string | undefined>(
|
|
@@ -15,12 +51,22 @@ const placeholder = computed<string | undefined>(
|
|
|
15
51
|
?.placeholder ?? control.value.description,
|
|
16
52
|
);
|
|
17
53
|
|
|
54
|
+
// Track user interaction
|
|
55
|
+
const hasInteracted = ref(false);
|
|
56
|
+
|
|
57
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
58
|
+
|
|
18
59
|
function onInput(val: string | undefined) {
|
|
19
|
-
|
|
20
|
-
|
|
60
|
+
// Convert empty strings to undefined for proper required field validation
|
|
61
|
+
const newValue = val && val.trim() !== "" ? val : undefined;
|
|
62
|
+
if (control.value.data !== newValue) {
|
|
21
63
|
handleChange(control.value.path, newValue);
|
|
22
64
|
}
|
|
23
65
|
}
|
|
66
|
+
|
|
67
|
+
function onBlur() {
|
|
68
|
+
hasInteracted.value = true;
|
|
69
|
+
}
|
|
24
70
|
</script>
|
|
25
71
|
|
|
26
72
|
<template>
|
|
@@ -35,12 +81,13 @@ function onInput(val: string | undefined) {
|
|
|
35
81
|
class="w-full"
|
|
36
82
|
:model-value="control.data ?? ''"
|
|
37
83
|
:disabled="!control.enabled"
|
|
38
|
-
:aria-invalid="!!
|
|
84
|
+
:aria-invalid="!!showErrors || undefined"
|
|
39
85
|
:placeholder="placeholder"
|
|
40
86
|
:rows="4"
|
|
41
87
|
:auto-resize="true"
|
|
42
88
|
@update:model-value="onInput"
|
|
89
|
+
@blur="onBlur"
|
|
43
90
|
/>
|
|
44
|
-
<small v-if="
|
|
91
|
+
<small v-if="showErrors" class="p-error">{{ control.errors }}</small>
|
|
45
92
|
</div>
|
|
46
93
|
</template>
|
|
@@ -33,17 +33,8 @@ const injectLayoutStyles = () => {
|
|
|
33
33
|
// Inject styles when this module is imported
|
|
34
34
|
injectLayoutStyles();
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
isStringControl,
|
|
39
|
-
or,
|
|
40
|
-
isNumberControl,
|
|
41
|
-
isIntegerControl,
|
|
42
|
-
and,
|
|
43
|
-
isControl,
|
|
44
|
-
schemaMatches,
|
|
45
|
-
isBooleanControl,
|
|
46
|
-
} from "@jsonforms/core";
|
|
36
|
+
// Import types only to avoid circular dependencies
|
|
37
|
+
import type { JsonFormsRendererRegistryEntry } from "@jsonforms/core";
|
|
47
38
|
|
|
48
39
|
// helpers for enum detection
|
|
49
40
|
const isScalarEnum = (s?: object) => {
|
|
@@ -66,34 +57,54 @@ const isEnumArray = (s?: object) => {
|
|
|
66
57
|
);
|
|
67
58
|
};
|
|
68
59
|
|
|
69
|
-
// helper for multiline detection
|
|
70
|
-
const isMultilineString = (uischema: unknown, schema: unknown) => {
|
|
71
|
-
return (
|
|
72
|
-
isStringControl(uischema as never, schema as never, {} as never) &&
|
|
73
|
-
(uischema as { options?: { multi?: boolean } })?.options?.multi === true
|
|
74
|
-
);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
60
|
// Give PrimeVue renderers a high base rank; vanilla commonly uses small ranks (2–5)
|
|
78
61
|
const PRIME = 100;
|
|
79
62
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
63
|
+
// Create empty array first, populate it after module initialization to avoid circular deps
|
|
64
|
+
export const primevueRenderers: JsonFormsRendererRegistryEntry[] = [];
|
|
65
|
+
|
|
66
|
+
// Populate the renderers array after a microtask delay to avoid circular dependency
|
|
67
|
+
Promise.resolve().then(async () => {
|
|
68
|
+
const {
|
|
69
|
+
rankWith,
|
|
70
|
+
isStringControl,
|
|
71
|
+
or,
|
|
72
|
+
isNumberControl,
|
|
73
|
+
isIntegerControl,
|
|
74
|
+
and,
|
|
75
|
+
isControl,
|
|
76
|
+
schemaMatches,
|
|
77
|
+
isBooleanControl,
|
|
78
|
+
} = await import("@jsonforms/core");
|
|
79
|
+
|
|
80
|
+
// helper for multiline detection moved inside async block
|
|
81
|
+
const isMultilineString = (uischema: unknown, schema: unknown) => {
|
|
82
|
+
const controlUischema = uischema as { options?: { multi?: boolean } };
|
|
83
|
+
return and(isStringControl, () => controlUischema?.options?.multi === true)(
|
|
84
|
+
uischema as Parameters<typeof isStringControl>[0],
|
|
85
|
+
schema as Parameters<typeof isStringControl>[1],
|
|
86
|
+
{} as Parameters<typeof isStringControl>[2],
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
primevueRenderers.push(
|
|
91
|
+
// Multiline text has higher priority than regular text
|
|
92
|
+
{ tester: rankWith(PRIME + 4, isMultilineString), renderer: JfTextArea },
|
|
93
|
+
{ tester: rankWith(PRIME + 3, isStringControl), renderer: JfText },
|
|
94
|
+
{
|
|
95
|
+
tester: rankWith(PRIME + 4, or(isNumberControl, isIntegerControl)),
|
|
96
|
+
renderer: JfNumber,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
tester: rankWith(PRIME + 5, and(isControl, schemaMatches(isScalarEnum))),
|
|
100
|
+
renderer: JfEnum,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
tester: rankWith(PRIME + 6, and(isControl, schemaMatches(isEnumArray))),
|
|
104
|
+
renderer: JfEnumArray,
|
|
105
|
+
},
|
|
106
|
+
{ tester: rankWith(PRIME + 3, isBooleanControl), renderer: JfBoolean },
|
|
107
|
+
);
|
|
108
|
+
});
|
|
98
109
|
|
|
99
110
|
export { JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean };
|