@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
package/src/index.ts
CHANGED
|
@@ -13,8 +13,21 @@ export * from "./core/types";
|
|
|
13
13
|
// Protocol exports
|
|
14
14
|
export { RestApiProtocol } from "./protocols/rest_api";
|
|
15
15
|
|
|
16
|
-
// Vue exports
|
|
17
|
-
export
|
|
16
|
+
// Vue exports - using named imports to avoid potential bundling issues
|
|
17
|
+
export {
|
|
18
|
+
providerRenderers,
|
|
19
|
+
primevueRenderers,
|
|
20
|
+
ProviderAutocomplete,
|
|
21
|
+
ProviderSelect,
|
|
22
|
+
ProviderMultiSelect,
|
|
23
|
+
useProvider,
|
|
24
|
+
JfText,
|
|
25
|
+
JfTextArea,
|
|
26
|
+
JfNumber,
|
|
27
|
+
JfEnum,
|
|
28
|
+
JfEnumArray,
|
|
29
|
+
JfBoolean,
|
|
30
|
+
} from "./vue";
|
|
18
31
|
|
|
19
32
|
export interface ProviderConfig {
|
|
20
33
|
protocols?: Protocol[];
|
|
@@ -17,6 +17,7 @@ export type RestApiCfg = {
|
|
|
17
17
|
map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item
|
|
18
18
|
paginate?: { cursorPath: string; param: string; maxPages?: number };
|
|
19
19
|
auth?: AuthConfig;
|
|
20
|
+
showError?: boolean; // Whether to show error messages, defaults to true
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
function buildAuthHeaders(
|
|
@@ -122,7 +123,13 @@ export const RestApiProtocol = (): Protocol<RestApiCfg> => ({
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
const res = await fetch(url.toString(), requestInit);
|
|
125
|
-
if (!res.ok)
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
if (cfg.showError !== false) {
|
|
128
|
+
throw new Error(`REST ${res.status}`);
|
|
129
|
+
}
|
|
130
|
+
// If showError is false, return empty items instead of throwing
|
|
131
|
+
return { items: [], ttl: 0 };
|
|
132
|
+
}
|
|
126
133
|
const json = await res.json();
|
|
127
134
|
const items = jp(json, cfg.items);
|
|
128
135
|
for (const it of items) {
|
|
@@ -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>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { computed, watch, inject, type Ref } from "vue";
|
|
2
|
+
import { type ControlElement } from "@jsonforms/core";
|
|
3
|
+
|
|
4
|
+
interface DeriveOptions {
|
|
5
|
+
control: Ref<{
|
|
6
|
+
uischema: ControlElement;
|
|
7
|
+
path: string;
|
|
8
|
+
data: unknown;
|
|
9
|
+
}>;
|
|
10
|
+
handleChange: (path: string, value: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
14
|
+
// Get the root form data from JSONForms context
|
|
15
|
+
const injectedFormData = inject<{ value: unknown }>("formData", {
|
|
16
|
+
value: {},
|
|
17
|
+
});
|
|
18
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
19
|
+
|
|
20
|
+
// Get external data from context if available
|
|
21
|
+
const injectedExternalData = inject<{ value: unknown }>("externalData", {
|
|
22
|
+
value: {},
|
|
23
|
+
});
|
|
24
|
+
const externalData = computed(() => injectedExternalData.value || {});
|
|
25
|
+
|
|
26
|
+
// Extract derive configuration from uischema options
|
|
27
|
+
const deriveConfig = computed(() => {
|
|
28
|
+
const options = control.value.uischema?.options as
|
|
29
|
+
| { derive?: string; mode?: string }
|
|
30
|
+
| undefined;
|
|
31
|
+
return {
|
|
32
|
+
expression: options?.derive,
|
|
33
|
+
mode: options?.mode || "follow", // 'follow' = auto-update, 'manual' = user controlled
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Watch for changes in form data and external data and update derived field
|
|
38
|
+
watch(
|
|
39
|
+
[rootData, externalData, deriveConfig],
|
|
40
|
+
([data, extData, config]) => {
|
|
41
|
+
if (!config.expression || config.mode !== "follow") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const derivedValue = resolveDeriveExpression(
|
|
47
|
+
config.expression,
|
|
48
|
+
data,
|
|
49
|
+
extData,
|
|
50
|
+
);
|
|
51
|
+
if (derivedValue !== control.value.data) {
|
|
52
|
+
handleChange(control.value.path, derivedValue);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn(
|
|
56
|
+
`Failed to derive value for ${control.value.path}:`,
|
|
57
|
+
error,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{ deep: true, immediate: true },
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveDeriveExpression(
|
|
66
|
+
expression: string,
|
|
67
|
+
data: unknown,
|
|
68
|
+
externalData?: unknown,
|
|
69
|
+
): unknown {
|
|
70
|
+
// Handle externalData() syntax
|
|
71
|
+
if (expression.startsWith("externalData(") && expression.endsWith(")")) {
|
|
72
|
+
const propertyPath = expression.slice(13, -1); // Remove "externalData(" and ")"
|
|
73
|
+
return resolvePropertyPath(propertyPath, externalData);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle simple property paths like "country.name"
|
|
77
|
+
if (
|
|
78
|
+
!expression.includes("(") &&
|
|
79
|
+
!expression.includes("+") &&
|
|
80
|
+
!expression.includes("?")
|
|
81
|
+
) {
|
|
82
|
+
return resolvePropertyPath(expression, data);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For now, we'll only support simple property paths and externalData() calls
|
|
86
|
+
// Complex expressions would require a safe expression evaluator
|
|
87
|
+
return resolvePropertyPath(expression, data);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolvePropertyPath(path: string, data: unknown): unknown {
|
|
91
|
+
if (!path || !data) return "";
|
|
92
|
+
|
|
93
|
+
const keys = path.split(".");
|
|
94
|
+
let value: unknown = data;
|
|
95
|
+
|
|
96
|
+
for (const key of keys) {
|
|
97
|
+
if (value && typeof value === "object" && key in value) {
|
|
98
|
+
value = (value as Record<string, unknown>)[key];
|
|
99
|
+
} else {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return value;
|
|
105
|
+
}
|
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) => {
|
|
@@ -34,16 +35,48 @@ 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)
|
|
43
67
|
export { primevueRenderers } from "./primevue";
|
|
44
68
|
|
|
45
69
|
// Export individual components
|
|
46
|
-
export { ProviderAutocomplete, ProviderSelect };
|
|
70
|
+
export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };
|
|
47
71
|
export { useProvider } from "./composables/useProvider";
|
|
48
72
|
export * from "./testers";
|
|
49
|
-
|
|
73
|
+
|
|
74
|
+
// Export individual PrimeVue components using lazy evaluation to avoid circular deps
|
|
75
|
+
export {
|
|
76
|
+
JfText,
|
|
77
|
+
JfTextArea,
|
|
78
|
+
JfNumber,
|
|
79
|
+
JfEnum,
|
|
80
|
+
JfEnumArray,
|
|
81
|
+
JfBoolean,
|
|
82
|
+
} from "./primevue";
|
|
@@ -1,11 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: "JfBoolean",
|
|
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 {
|
|
38
|
+
import type { ControlProps } from "@jsonforms/vue";
|
|
39
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
40
|
+
import { getCurrentInstance } from "vue";
|
|
4
41
|
import Checkbox from "primevue/checkbox";
|
|
5
42
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const props =
|
|
43
|
+
// Access props from the component instance
|
|
44
|
+
const instance = getCurrentInstance()!;
|
|
45
|
+
const props = instance.props as unknown as ControlProps;
|
|
9
46
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
10
47
|
|
|
11
48
|
const onToggle = (val: boolean) => handleChange(control.value.path, val);
|
|
@@ -1,12 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Define props manually to avoid any potential circular dependency issues
|
|
3
|
+
export default {
|
|
4
|
+
name: "JfEnum",
|
|
5
|
+
props: {
|
|
6
|
+
uischema: {
|
|
7
|
+
type: Object,
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
schema: {
|
|
11
|
+
type: Object,
|
|
12
|
+
required: true,
|
|
13
|
+
},
|
|
14
|
+
path: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
enabled: {
|
|
19
|
+
type: Boolean,
|
|
20
|
+
default: undefined,
|
|
21
|
+
},
|
|
22
|
+
renderers: {
|
|
23
|
+
type: Array,
|
|
24
|
+
required: false,
|
|
25
|
+
},
|
|
26
|
+
cells: {
|
|
27
|
+
type: Array,
|
|
28
|
+
required: false,
|
|
29
|
+
},
|
|
30
|
+
config: {
|
|
31
|
+
type: Object,
|
|
32
|
+
required: false,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
</script>
|
|
37
|
+
|
|
1
38
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
39
|
+
import type { JsonSchema } from "@jsonforms/core";
|
|
40
|
+
import type { ControlProps } from "@jsonforms/vue";
|
|
41
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
42
|
+
import { computed, ref, inject, getCurrentInstance } from "vue";
|
|
43
|
+
import { useProvider } from "../composables/useProvider";
|
|
44
|
+
import { useDerive } from "../composables/useDerive";
|
|
5
45
|
import Dropdown from "primevue/dropdown";
|
|
6
46
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props =
|
|
47
|
+
// Access props from the component instance
|
|
48
|
+
const instance = getCurrentInstance()!;
|
|
49
|
+
const props = instance.props as unknown as ControlProps;
|
|
10
50
|
const { control, handleChange } = useJsonFormsControl(props);
|
|
11
51
|
|
|
12
52
|
type Opt = { label: string; value: unknown };
|
|
@@ -26,14 +66,87 @@ const toOptions = (schema?: JsonSchema): Opt[] => {
|
|
|
26
66
|
return [];
|
|
27
67
|
};
|
|
28
68
|
|
|
29
|
-
|
|
69
|
+
// Provider support
|
|
70
|
+
const binding = computed(() => {
|
|
71
|
+
const provider = control.value.uischema?.options?.provider;
|
|
72
|
+
// Ensure load property is set to 'mount' by default
|
|
73
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
74
|
+
return { ...provider, load: "mount" };
|
|
75
|
+
}
|
|
76
|
+
return provider;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const deps = computed(
|
|
30
80
|
() =>
|
|
31
|
-
(
|
|
32
|
-
|
|
81
|
+
((
|
|
82
|
+
(control.value.schema as Record<string, unknown>)?.[
|
|
83
|
+
"x-provider"
|
|
84
|
+
] as Record<string, unknown>
|
|
85
|
+
)?.dependsOn as string[]) ?? [],
|
|
33
86
|
);
|
|
87
|
+
const depValues = computed(() => {
|
|
88
|
+
return deps.value.map((dep) => {
|
|
89
|
+
// Resolve dependency value from form data using JSON pointer-like path
|
|
90
|
+
const path = dep.startsWith("#/") ? dep.slice(2) : dep;
|
|
91
|
+
const keys = path.replace(/\//g, ".").split(".");
|
|
92
|
+
let value: unknown = rootData.value;
|
|
93
|
+
for (const key of keys) {
|
|
94
|
+
if (value && typeof value === "object" && key in value) {
|
|
95
|
+
value = (value as Record<string, unknown>)[key];
|
|
96
|
+
} else {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Get the root form data from JSONForms context for template URL resolution
|
|
105
|
+
const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
|
|
106
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
107
|
+
|
|
108
|
+
// Use provider if available, otherwise fall back to schema enum/oneOf
|
|
109
|
+
const {
|
|
110
|
+
items: providerItems,
|
|
111
|
+
loading,
|
|
112
|
+
error,
|
|
113
|
+
} = useProvider(binding, {
|
|
114
|
+
data: rootData,
|
|
115
|
+
path: control.value.path,
|
|
116
|
+
dependsOnValues: depValues.value,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const placeholder = computed<string | undefined>(() => {
|
|
120
|
+
if (loading.value) return "Loading…";
|
|
121
|
+
return (
|
|
122
|
+
(control.value.uischema as { options?: { placeholder?: string } })?.options
|
|
123
|
+
?.placeholder ?? control.value.description
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const options = computed(() => {
|
|
128
|
+
// Use provider items if available, otherwise fall back to schema enum/oneOf
|
|
129
|
+
if (binding.value && providerItems.value.length > 0) {
|
|
130
|
+
return providerItems.value;
|
|
131
|
+
}
|
|
132
|
+
return toOptions(control.value.schema);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Add derive functionality
|
|
136
|
+
useDerive({ control, handleChange });
|
|
137
|
+
|
|
138
|
+
// Track user interaction
|
|
139
|
+
const hasInteracted = ref(false);
|
|
140
|
+
|
|
141
|
+
const showErrors = computed(() => hasInteracted.value && control.value.errors);
|
|
34
142
|
|
|
35
|
-
const
|
|
36
|
-
|
|
143
|
+
const onSelect = (val: unknown) => {
|
|
144
|
+
handleChange(control.value.path, val);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const onBlur = () => {
|
|
148
|
+
hasInteracted.value = true;
|
|
149
|
+
};
|
|
37
150
|
</script>
|
|
38
151
|
|
|
39
152
|
<template>
|
|
@@ -51,17 +164,15 @@ const onSelect = (val: unknown) => handleChange(control.value.path, val);
|
|
|
51
164
|
option-value="value"
|
|
52
165
|
:model-value="control.data ?? null"
|
|
53
166
|
:placeholder="placeholder"
|
|
54
|
-
:disabled="!control.enabled"
|
|
55
|
-
:aria-invalid="!!
|
|
167
|
+
:disabled="!control.enabled || loading"
|
|
168
|
+
:aria-invalid="!!showErrors || undefined"
|
|
56
169
|
:show-clear="true"
|
|
57
170
|
@update:model-value="onSelect"
|
|
171
|
+
@blur="onBlur"
|
|
58
172
|
/>
|
|
59
|
-
<small v-if="
|
|
173
|
+
<small v-if="error" class="p-error" role="alert"
|
|
174
|
+
>Failed to load: {{ error }}</small
|
|
175
|
+
>
|
|
176
|
+
<small v-else-if="showErrors" class="p-error">{{ control.errors }}</small>
|
|
60
177
|
</div>
|
|
61
178
|
</template>
|
|
62
|
-
|
|
63
|
-
<style scoped>
|
|
64
|
-
:deep(.p-dropdown-label) {
|
|
65
|
-
text-align: left;
|
|
66
|
-
}
|
|
67
|
-
</style>
|