@narrative.io/jsonforms-provider-protocols 3.0.0-beta.23 → 3.0.0-beta.25
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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jsonforms-provider-protocols.css +4 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts +9 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts.map +1 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.js +8 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.js.map +1 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue2.js +142 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue2.js.map +1 -0
- package/dist/vue/index.d.ts +2 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +37 -7
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.js +22 -17
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
- package/dist/vue/utils/objectMultiSelect.d.ts +68 -0
- package/dist/vue/utils/objectMultiSelect.d.ts.map +1 -0
- package/dist/vue/utils/objectMultiSelect.js +72 -0
- package/dist/vue/utils/objectMultiSelect.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/vue/components/ProviderObjectMultiSelect.vue +169 -0
- package/src/vue/index.ts +52 -10
- package/src/vue/primevue/JfBoolean.vue +15 -10
- package/src/vue/utils/objectMultiSelect.ts +171 -0
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { UISchemaLike } from "./core/seedProjectionTargets";
|
|
|
14
14
|
export { RestApiProtocol } from "./protocols/rest_api";
|
|
15
15
|
export { createNoEvalAjv, transformUnit, transformErrors, } from "./no-eval-ajv";
|
|
16
16
|
export type { NoEvalAjv, NoEvalErrorObject, NoEvalValidateFunction, CreateNoEvalAjvOptions, } from "./no-eval-ajv";
|
|
17
|
-
export { providerRenderers, primevueRenderers, ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, useProvider, createDataLayer, useDataLayer, JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean, } from "./vue";
|
|
17
|
+
export { providerRenderers, primevueRenderers, ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, ProviderObjectMultiSelect, useProvider, createDataLayer, useDataLayer, JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean, } from "./vue";
|
|
18
18
|
export type { DataLayer } from "./vue";
|
|
19
19
|
export interface ProviderConfig {
|
|
20
20
|
protocols?: Protocol[];
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,EACzB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { default as default8 } from "./vue/primevue/JfBoolean.vue.js";
|
|
|
20
20
|
import { primevueRenderers } from "./vue/primevue/index.js";
|
|
21
21
|
import { default as default9 } from "./vue/components/ProviderSelect.vue.js";
|
|
22
22
|
import { default as default10 } from "./vue/components/ProviderMultiSelect.vue.js";
|
|
23
|
+
import { default as default11 } from "./vue/components/ProviderObjectMultiSelect.vue.js";
|
|
23
24
|
import { useProvider } from "./vue/composables/useProvider.js";
|
|
24
25
|
import { createDataLayer, useDataLayer } from "./vue/composables/useDataLayer.js";
|
|
25
26
|
const index = {
|
|
@@ -44,6 +45,7 @@ export {
|
|
|
44
45
|
default4 as JfTextArea,
|
|
45
46
|
default2 as ProviderAutocomplete,
|
|
46
47
|
default10 as ProviderMultiSelect,
|
|
48
|
+
default11 as ProviderObjectMultiSelect,
|
|
47
49
|
default9 as ProviderSelect,
|
|
48
50
|
RestApiProtocol,
|
|
49
51
|
applyTransformPipeline,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\nexport * from \"./core/projection\";\nexport * from \"./core/resolveScope\";\n// Core exports\nexport * from \"./core/types\";\nexport { initFormDataFromSchema } from \"./core/initFormData\";\nexport { seedProjectionTargets } from \"./core/seedProjectionTargets\";\nexport type { UISchemaLike } from \"./core/seedProjectionTargets\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// CSP-safe validator\nexport {\n createNoEvalAjv,\n transformUnit,\n transformErrors,\n} from \"./no-eval-ajv\";\nexport type {\n NoEvalAjv,\n NoEvalErrorObject,\n NoEvalValidateFunction,\n CreateNoEvalAjvOptions,\n} from \"./no-eval-ajv\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n useProvider,\n createDataLayer,\n useDataLayer,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\nexport type { DataLayer } from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\nexport * from \"./core/projection\";\nexport * from \"./core/resolveScope\";\n// Core exports\nexport * from \"./core/types\";\nexport { initFormDataFromSchema } from \"./core/initFormData\";\nexport { seedProjectionTargets } from \"./core/seedProjectionTargets\";\nexport type { UISchemaLike } from \"./core/seedProjectionTargets\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// CSP-safe validator\nexport {\n createNoEvalAjv,\n transformUnit,\n transformErrors,\n} from \"./no-eval-ajv\";\nexport type {\n NoEvalAjv,\n NoEvalErrorObject,\n NoEvalValidateFunction,\n CreateNoEvalAjvOptions,\n} from \"./no-eval-ajv\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n ProviderObjectMultiSelect,\n useProvider,\n createDataLayer,\n useDataLayer,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\nexport type { DataLayer } from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ControlElement, JsonSchema } from "@jsonforms/core";
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
uischema: ControlElement;
|
|
4
|
+
schema: JsonSchema;
|
|
5
|
+
path: string;
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
export default _default;
|
|
9
|
+
//# sourceMappingURL=ProviderObjectMultiSelect.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProviderObjectMultiSelect.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/components/ProviderObjectMultiSelect.vue"],"names":[],"mappings":"AA2KA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAiBlE,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;;AAgOF,wBAEG"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import _sfc_main from "./ProviderObjectMultiSelect.vue2.js";
|
|
2
|
+
/* empty css */
|
|
3
|
+
import _export_sfc from "../../_virtual/_plugin-vue_export-helper.js";
|
|
4
|
+
const ProviderObjectMultiSelect = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-2ee6c7be"]]);
|
|
5
|
+
export {
|
|
6
|
+
ProviderObjectMultiSelect as default
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=ProviderObjectMultiSelect.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProviderObjectMultiSelect.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { defineComponent, inject, computed, watch, createElementBlock, openBlock, createCommentVNode, createVNode, unref, toDisplayString } from "vue";
|
|
2
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
3
|
+
import { useProvider } from "../composables/useProvider.js";
|
|
4
|
+
import { useProjection } from "../composables/useProjection.js";
|
|
5
|
+
import { resolvePlaceholder } from "../utils/placeholder.js";
|
|
6
|
+
import { resolveItemsSchema, resolveObjectKeys, fromMultiSelectShape, sameObjectSet, toMultiSelectShape } from "../utils/objectMultiSelect.js";
|
|
7
|
+
import MultiSelect from "primevue/multiselect";
|
|
8
|
+
const _hoisted_1 = { class: "jf-control" };
|
|
9
|
+
const _hoisted_2 = {
|
|
10
|
+
key: 0,
|
|
11
|
+
class: "jf-label"
|
|
12
|
+
};
|
|
13
|
+
const _hoisted_3 = {
|
|
14
|
+
key: 1,
|
|
15
|
+
class: "jf-description"
|
|
16
|
+
};
|
|
17
|
+
const _hoisted_4 = {
|
|
18
|
+
key: 2,
|
|
19
|
+
class: "p-error",
|
|
20
|
+
role: "alert"
|
|
21
|
+
};
|
|
22
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
23
|
+
__name: "ProviderObjectMultiSelect",
|
|
24
|
+
props: {
|
|
25
|
+
uischema: {},
|
|
26
|
+
schema: {},
|
|
27
|
+
path: {}
|
|
28
|
+
},
|
|
29
|
+
setup(__props) {
|
|
30
|
+
const props = __props;
|
|
31
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
32
|
+
const {
|
|
33
|
+
projectedData,
|
|
34
|
+
projectedLabel,
|
|
35
|
+
handleProjectedChange: handleChange
|
|
36
|
+
} = useProjection(control, rawHandleChange);
|
|
37
|
+
const jsonforms = inject("jsonforms", null);
|
|
38
|
+
const rootSchema = computed(
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
() => jsonforms?.core?.schema ?? control.value.schema
|
|
41
|
+
);
|
|
42
|
+
const itemsSchema = computed(
|
|
43
|
+
() => resolveItemsSchema(
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
control.value.schema,
|
|
46
|
+
rootSchema.value
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
const objectKeys = computed(() => {
|
|
50
|
+
const resolved = resolveObjectKeys(
|
|
51
|
+
control.value.uischema?.options,
|
|
52
|
+
itemsSchema.value
|
|
53
|
+
);
|
|
54
|
+
if (!resolved) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"[ProviderObjectMultiSelect] objectKeys could not be resolved. Specify `uischema.options.objectKeys = { value, label }` or declare exactly two `required` properties on the array's items schema so they can be inferred."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return resolved;
|
|
60
|
+
});
|
|
61
|
+
const binding = computed(() => {
|
|
62
|
+
const provider = control.value.uischema?.options?.provider;
|
|
63
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
64
|
+
return { ...provider, load: "mount" };
|
|
65
|
+
}
|
|
66
|
+
return provider;
|
|
67
|
+
});
|
|
68
|
+
const deps = computed(
|
|
69
|
+
() => control.value.schema?.["x-provider"]?.dependsOn ?? []
|
|
70
|
+
);
|
|
71
|
+
const depValues = computed(() => deps.value.map(() => null));
|
|
72
|
+
const injectedFormData = inject("formData", { value: {} });
|
|
73
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
74
|
+
const { items, loading, error } = useProvider(binding, {
|
|
75
|
+
data: rootData,
|
|
76
|
+
path: control.value.path,
|
|
77
|
+
dependsOnValues: depValues
|
|
78
|
+
});
|
|
79
|
+
watch(
|
|
80
|
+
[items, loading],
|
|
81
|
+
([newItems, isLoading]) => {
|
|
82
|
+
if (!control.value.uischema?.options?.autoSelectSingle || isLoading || newItems.length !== 1) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const single = newItems[0];
|
|
86
|
+
const current = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
87
|
+
if (current.length === 0) {
|
|
88
|
+
handleChange(control.value.path, [
|
|
89
|
+
{
|
|
90
|
+
[objectKeys.value.value]: single.value,
|
|
91
|
+
[objectKeys.value.label]: single.label
|
|
92
|
+
}
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{ immediate: true }
|
|
97
|
+
);
|
|
98
|
+
const value = computed({
|
|
99
|
+
get() {
|
|
100
|
+
return toMultiSelectShape(projectedData.value, objectKeys.value);
|
|
101
|
+
},
|
|
102
|
+
set(val) {
|
|
103
|
+
const next = fromMultiSelectShape(val, objectKeys.value);
|
|
104
|
+
const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
105
|
+
if (!sameObjectSet(curr, next, objectKeys.value.value)) {
|
|
106
|
+
handleChange(control.value.path, next);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const placeholder = computed(() => {
|
|
111
|
+
if (loading.value) return "Loading…";
|
|
112
|
+
return resolvePlaceholder(
|
|
113
|
+
control.value.uischema,
|
|
114
|
+
projectedLabel.value,
|
|
115
|
+
"select"
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
return (_ctx, _cache) => {
|
|
119
|
+
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
120
|
+
unref(projectedLabel) ? (openBlock(), createElementBlock("label", _hoisted_2, toDisplayString(unref(projectedLabel)), 1)) : createCommentVNode("", true),
|
|
121
|
+
unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
|
|
122
|
+
createVNode(unref(MultiSelect), {
|
|
123
|
+
modelValue: value.value,
|
|
124
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => value.value = $event),
|
|
125
|
+
class: "w-full!",
|
|
126
|
+
options: unref(items),
|
|
127
|
+
"option-label": "label",
|
|
128
|
+
"data-key": "value",
|
|
129
|
+
display: "chip",
|
|
130
|
+
placeholder: placeholder.value,
|
|
131
|
+
disabled: !unref(control).enabled || unref(loading),
|
|
132
|
+
"show-clear": true
|
|
133
|
+
}, null, 8, ["modelValue", "options", "placeholder", "disabled"]),
|
|
134
|
+
unref(error) ? (openBlock(), createElementBlock("small", _hoisted_4, "Failed to load: " + toDisplayString(unref(error)), 1)) : createCommentVNode("", true)
|
|
135
|
+
]);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
export {
|
|
140
|
+
_sfc_main as default
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=ProviderObjectMultiSelect.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProviderObjectMultiSelect.vue2.js","sources":["../../../src/vue/components/ProviderObjectMultiSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ControlElement, JsonSchema } from \"@jsonforms/core\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { computed, inject, watch } from \"vue\";\nimport { useProvider } from \"../composables/useProvider\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { resolvePlaceholder } from \"../utils/placeholder\";\nimport {\n fromMultiSelectShape,\n resolveItemsSchema,\n resolveObjectKeys,\n sameObjectSet,\n toMultiSelectShape,\n type MultiSelectOption,\n type ObjectKeys,\n} from \"../utils/objectMultiSelect\";\nimport MultiSelect from \"primevue/multiselect\";\n\nconst props = defineProps<{\n uischema: ControlElement;\n schema: JsonSchema;\n path: string;\n}>();\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst {\n projectedData,\n projectedLabel,\n handleProjectedChange: handleChange,\n} = useProjection(control, rawHandleChange);\n\n// Pull root schema so item-schema $refs resolve against the consumer's $defs.\nconst jsonforms = inject<{\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n core?: { schema?: Record<string, any> };\n} | null>(\"jsonforms\", null);\nconst rootSchema = computed(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n () => (jsonforms?.core?.schema ?? control.value.schema) as Record<string, any>,\n);\n\nconst itemsSchema = computed(() =>\n resolveItemsSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n control.value.schema as Record<string, any>,\n rootSchema.value,\n ),\n);\n\nconst objectKeys = computed<ObjectKeys>(() => {\n const resolved = resolveObjectKeys(\n control.value.uischema?.options as\n | { objectKeys?: { value?: unknown; label?: unknown } }\n | undefined,\n itemsSchema.value,\n );\n if (!resolved) {\n throw new Error(\n \"[ProviderObjectMultiSelect] objectKeys could not be resolved. \" +\n \"Specify `uischema.options.objectKeys = { value, label }` or declare \" +\n \"exactly two `required` properties on the array's items schema so \" +\n \"they can be inferred.\",\n );\n }\n return resolved;\n});\n\nconst binding = computed(() => {\n const provider = control.value.uischema?.options?.provider;\n if (provider && typeof provider === \"object\" && !provider.load) {\n return { ...provider, load: \"mount\" };\n }\n return provider;\n});\n\nconst deps = computed(\n () =>\n ((\n (control.value.schema as Record<string, unknown>)?.[\n \"x-provider\"\n ] as Record<string, unknown>\n )?.dependsOn as string[]) ?? [],\n);\nconst depValues = computed(() => deps.value.map(() => null));\n\nconst injectedFormData = inject<{ value: unknown }>(\"formData\", { value: {} });\nconst rootData = computed(() => injectedFormData.value || {});\n\nconst { items, loading, error } = useProvider(binding, {\n data: rootData,\n path: control.value.path,\n dependsOnValues: depValues,\n});\n\n// Auto-select when provider returns only one item — paired-object variant.\nwatch(\n [items, loading],\n ([newItems, isLoading]) => {\n if (\n !control.value.uischema?.options?.autoSelectSingle ||\n isLoading ||\n newItems.length !== 1\n ) {\n return;\n }\n const single = newItems[0]!;\n const current = Array.isArray(projectedData.value) ? projectedData.value : [];\n if (current.length === 0) {\n handleChange(control.value.path, [\n {\n [objectKeys.value.value]: single.value,\n [objectKeys.value.label]: single.label,\n },\n ]);\n }\n },\n { immediate: true },\n);\n\nconst value = computed<MultiSelectOption[]>({\n get() {\n return toMultiSelectShape(projectedData.value, objectKeys.value);\n },\n set(val) {\n const next = fromMultiSelectShape(val, objectKeys.value);\n const curr = Array.isArray(projectedData.value) ? projectedData.value : [];\n if (!sameObjectSet(curr, next, objectKeys.value.value)) {\n handleChange(control.value.path, next);\n }\n },\n});\n\nconst placeholder = computed(() => {\n if (loading.value) return \"Loading…\";\n return resolvePlaceholder(\n control.value.uischema,\n projectedLabel.value,\n \"select\",\n );\n});\n</script>\n\n<template>\n <div class=\"jf-control\">\n <label v-if=\"projectedLabel\" class=\"jf-label\">{{ projectedLabel }}</label>\n <div v-if=\"control.description\" class=\"jf-description\">\n {{ control.description }}\n </div>\n <MultiSelect\n v-model=\"value\"\n class=\"w-full!\"\n :options=\"items\"\n option-label=\"label\"\n data-key=\"value\"\n display=\"chip\"\n :placeholder=\"placeholder\"\n :disabled=\"!control.enabled || loading\"\n :show-clear=\"true\"\n />\n <small v-if=\"error\" class=\"p-error\" role=\"alert\"\n >Failed to load: {{ error }}</small\n >\n </div>\n</template>\n\n<style scoped>\n:deep(.p-multiselect-label) {\n text-align: left;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_unref","_toDisplayString","_createVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,UAAM,QAAQ;AAKd,UAAM,EAAE,SAAS,cAAc,gBAAA,IAAoB,oBAAoB,KAAK;AAC5E,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,uBAAuB;AAAA,IAAA,IACrB,cAAc,SAAS,eAAe;AAG1C,UAAM,YAAY,OAGR,aAAa,IAAI;AAC3B,UAAM,aAAa;AAAA;AAAA,MAEjB,MAAO,WAAW,MAAM,UAAU,QAAQ,MAAM;AAAA,IAAA;AAGlD,UAAM,cAAc;AAAA,MAAS,MAC3B;AAAA;AAAA,QAEE,QAAQ,MAAM;AAAA,QACd,WAAW;AAAA,MAAA;AAAA,IACb;AAGF,UAAM,aAAa,SAAqB,MAAM;AAC5C,YAAM,WAAW;AAAA,QACf,QAAQ,MAAM,UAAU;AAAA,QAGxB,YAAY;AAAA,MAAA;AAEd,UAAI,CAAC,UAAU;AACb,cAAM,IAAI;AAAA,UACR;AAAA,QAAA;AAAA,MAKJ;AACA,aAAO;AAAA,IACT,CAAC;AAED,UAAM,UAAU,SAAS,MAAM;AAC7B,YAAM,WAAW,QAAQ,MAAM,UAAU,SAAS;AAClD,UAAI,YAAY,OAAO,aAAa,YAAY,CAAC,SAAS,MAAM;AAC9D,eAAO,EAAE,GAAG,UAAU,MAAM,QAAA;AAAA,MAC9B;AACA,aAAO;AAAA,IACT,CAAC;AAED,UAAM,OAAO;AAAA,MACX,MAEK,QAAQ,MAAM,SACb,YACF,GACC,aAA0B,CAAA;AAAA,IAAC;AAElC,UAAM,YAAY,SAAS,MAAM,KAAK,MAAM,IAAI,MAAM,IAAI,CAAC;AAE3D,UAAM,mBAAmB,OAA2B,YAAY,EAAE,OAAO,CAAA,GAAI;AAC7E,UAAM,WAAW,SAAS,MAAM,iBAAiB,SAAS,CAAA,CAAE;AAE5D,UAAM,EAAE,OAAO,SAAS,MAAA,IAAU,YAAY,SAAS;AAAA,MACrD,MAAM;AAAA,MACN,MAAM,QAAQ,MAAM;AAAA,MACpB,iBAAiB;AAAA,IAAA,CAClB;AAGD;AAAA,MACE,CAAC,OAAO,OAAO;AAAA,MACf,CAAC,CAAC,UAAU,SAAS,MAAM;AACzB,YACE,CAAC,QAAQ,MAAM,UAAU,SAAS,oBAClC,aACA,SAAS,WAAW,GACpB;AACA;AAAA,QACF;AACA,cAAM,SAAS,SAAS,CAAC;AACzB,cAAM,UAAU,MAAM,QAAQ,cAAc,KAAK,IAAI,cAAc,QAAQ,CAAA;AAC3E,YAAI,QAAQ,WAAW,GAAG;AACxB,uBAAa,QAAQ,MAAM,MAAM;AAAA,YAC/B;AAAA,cACE,CAAC,WAAW,MAAM,KAAK,GAAG,OAAO;AAAA,cACjC,CAAC,WAAW,MAAM,KAAK,GAAG,OAAO;AAAA,YAAA;AAAA,UACnC,CACD;AAAA,QACH;AAAA,MACF;AAAA,MACA,EAAE,WAAW,KAAA;AAAA,IAAK;AAGpB,UAAM,QAAQ,SAA8B;AAAA,MAC1C,MAAM;AACJ,eAAO,mBAAmB,cAAc,OAAO,WAAW,KAAK;AAAA,MACjE;AAAA,MACA,IAAI,KAAK;AACP,cAAM,OAAO,qBAAqB,KAAK,WAAW,KAAK;AACvD,cAAM,OAAO,MAAM,QAAQ,cAAc,KAAK,IAAI,cAAc,QAAQ,CAAA;AACxE,YAAI,CAAC,cAAc,MAAM,MAAM,WAAW,MAAM,KAAK,GAAG;AACtD,uBAAa,QAAQ,MAAM,MAAM,IAAI;AAAA,QACvC;AAAA,MACF;AAAA,IAAA,CACD;AAED,UAAM,cAAc,SAAS,MAAM;AACjC,UAAI,QAAQ,MAAO,QAAO;AAC1B,aAAO;AAAA,QACL,QAAQ,MAAM;AAAA,QACd,eAAe;AAAA,QACf;AAAA,MAAA;AAAA,IAEJ,CAAC;;AAIC,aAAAA,UAAA,GAAAC,mBAmBM,OAnBN,YAmBM;AAAA,QAlBSC,MAAA,cAAA,kBAAbD,mBAA0E,SAA1E,YAA0EE,gBAAzBD,MAAA,cAAA,CAAc,GAAA,CAAA;QACpDA,MAAA,OAAA,EAAQ,eAAnBF,UAAA,GAAAC,mBAEM,OAFN,YAEME,gBADDD,MAAA,OAAA,EAAQ,WAAW,GAAA,CAAA;QAExBE,YAUEF,MAAA,WAAA,GAAA;AAAA,sBATS,MAAA;AAAA,uEAAA,MAAK,QAAA;AAAA,UACd,OAAM;AAAA,UACL,SAASA,MAAA,KAAA;AAAA,UACV,gBAAa;AAAA,UACb,YAAS;AAAA,UACT,SAAQ;AAAA,UACP,aAAa,YAAA;AAAA,UACb,UAAQ,CAAGA,MAAA,OAAA,EAAQ,WAAWA,MAAA,OAAA;AAAA,UAC9B,cAAY;AAAA,QAAA;QAEFA,MAAA,KAAA,KAAbF,aAAAC,mBAEC,SAFD,YACG,qCAAmBC,MAAA,KAAA,CAAK,GAAA,CAAA;;;;;"}
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { UISchemaElement } from "@jsonforms/core";
|
|
|
2
2
|
import ProviderAutocomplete from "./components/ProviderAutocomplete.vue";
|
|
3
3
|
import ProviderSelect from "./components/ProviderSelect.vue";
|
|
4
4
|
import ProviderMultiSelect from "./components/ProviderMultiSelect.vue";
|
|
5
|
+
import ProviderObjectMultiSelect from "./components/ProviderObjectMultiSelect.vue";
|
|
5
6
|
export declare const providerRenderers: {
|
|
6
7
|
tester: (uischema: UISchemaElement, schema: import("@jsonforms/core").JsonSchema, context: import("@jsonforms/core").TesterContext) => number;
|
|
7
8
|
renderer: import("vue").DefineComponent<{
|
|
@@ -15,7 +16,7 @@ export declare const providerRenderers: {
|
|
|
15
16
|
}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
17
|
}[];
|
|
17
18
|
export { primevueRenderers, registerPrimevueRenderers } from "./primevue";
|
|
18
|
-
export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect };
|
|
19
|
+
export { ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, ProviderObjectMultiSelect, };
|
|
19
20
|
export { useProvider } from "./composables/useProvider";
|
|
20
21
|
export { useProjection } from "./composables/useProjection";
|
|
21
22
|
export type { ProjectionResult } from "./composables/useProjection";
|
package/dist/vue/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAYvD,OAAO,oBAAoB,MAAM,uCAAuC,CAAC;AACzE,OAAO,cAAc,MAAM,iCAAiC,CAAC;AAC7D,OAAO,mBAAmB,MAAM,sCAAsC,CAAC;AACvE,OAAO,yBAAyB,MAAM,4CAA4C,CAAC;AAoGnF,eAAO,MAAM,iBAAiB;;;;;;;;;;;GAQ7B,CAAC;AAGF,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,GAC1B,CAAC;AACF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC3E,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,cAAc,WAAW,CAAC;AAG1B,OAAO,EACL,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC"}
|
package/dist/vue/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { rankWith, and, or, isStringControl, isNumberControl, isIntegerControl, isControl } from "@jsonforms/core";
|
|
2
2
|
import { resolveScopeSchema } from "../core/resolveScope.js";
|
|
3
|
+
import { resolveItemsSchema } from "./utils/objectMultiSelect.js";
|
|
3
4
|
import _sfc_main from "./components/ProviderAutocomplete.vue.js";
|
|
4
5
|
import ProviderSelect from "./components/ProviderSelect.vue.js";
|
|
5
6
|
import ProviderMultiSelect from "./components/ProviderMultiSelect.vue.js";
|
|
7
|
+
import ProviderObjectMultiSelect from "./components/ProviderObjectMultiSelect.vue.js";
|
|
6
8
|
import { primevueRenderers, registerPrimevueRenderers } from "./primevue/index.js";
|
|
7
9
|
import { useProvider } from "./composables/useProvider.js";
|
|
8
10
|
import { useProjection } from "./composables/useProjection.js";
|
|
@@ -57,24 +59,51 @@ const providerAutocompleteTester = rankWith(
|
|
|
57
59
|
(uischema) => uischema?.options?.autocomplete === true
|
|
58
60
|
)
|
|
59
61
|
);
|
|
60
|
-
const
|
|
62
|
+
const getArraySchema = (uischema, schema) => {
|
|
61
63
|
const controlSchema = uischema;
|
|
62
|
-
if (controlSchema.type !== "Control" || !controlSchema.scope)
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
64
|
+
if (controlSchema.type !== "Control" || !controlSchema.scope) return void 0;
|
|
65
65
|
const propertySchema = resolveScopeSchema(
|
|
66
66
|
controlSchema.scope,
|
|
67
67
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
68
|
schema
|
|
69
69
|
);
|
|
70
|
-
|
|
70
|
+
if (propertySchema?.type !== "array") return void 0;
|
|
71
|
+
return propertySchema;
|
|
71
72
|
};
|
|
73
|
+
const isObjectArrayControl = (uischema, schema) => {
|
|
74
|
+
const arr = getArraySchema(uischema, schema);
|
|
75
|
+
if (!arr) return false;
|
|
76
|
+
const items = resolveItemsSchema(
|
|
77
|
+
arr,
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
schema
|
|
80
|
+
);
|
|
81
|
+
return items?.type === "object";
|
|
82
|
+
};
|
|
83
|
+
const isScalarArrayControl = (uischema, schema) => {
|
|
84
|
+
const arr = getArraySchema(uischema, schema);
|
|
85
|
+
if (!arr) return false;
|
|
86
|
+
const items = resolveItemsSchema(
|
|
87
|
+
arr,
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
schema
|
|
90
|
+
);
|
|
91
|
+
return items?.type !== "object";
|
|
92
|
+
};
|
|
93
|
+
const providerObjectMultiSelectTester = rankWith(
|
|
94
|
+
110,
|
|
95
|
+
// One higher than scalar multi-select so object-item arrays match first.
|
|
96
|
+
and(isObjectArrayControl, hasProvider)
|
|
97
|
+
);
|
|
72
98
|
const providerMultiSelectTester = rankWith(
|
|
73
99
|
109,
|
|
74
|
-
|
|
75
|
-
and(isArrayControl, hasProvider)
|
|
100
|
+
and(isScalarArrayControl, hasProvider)
|
|
76
101
|
);
|
|
77
102
|
const providerRenderers = [
|
|
103
|
+
{
|
|
104
|
+
tester: providerObjectMultiSelectTester,
|
|
105
|
+
renderer: ProviderObjectMultiSelect
|
|
106
|
+
},
|
|
78
107
|
{ tester: providerMultiSelectTester, renderer: ProviderMultiSelect },
|
|
79
108
|
{ tester: providerAutocompleteTester, renderer: _sfc_main },
|
|
80
109
|
{ tester: providerSelectTester, renderer: ProviderSelect }
|
|
@@ -88,6 +117,7 @@ export {
|
|
|
88
117
|
default3 as JfTextArea,
|
|
89
118
|
_sfc_main as ProviderAutocomplete,
|
|
90
119
|
ProviderMultiSelect,
|
|
120
|
+
ProviderObjectMultiSelect,
|
|
91
121
|
ProviderSelect,
|
|
92
122
|
createDataLayer,
|
|
93
123
|
primevueRenderers,
|
package/dist/vue/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/vue/index.ts"],"sourcesContent":["import type { UISchemaElement } from \"@jsonforms/core\";\nimport {\n and,\n isIntegerControl,\n isNumberControl,\n isStringControl,\n or,\n rankWith,\n isControl,\n} from \"@jsonforms/core\";\nimport { resolveScopeSchema } from \"../core/resolveScope\";\nimport ProviderAutocomplete from \"./components/ProviderAutocomplete.vue\";\nimport ProviderSelect from \"./components/ProviderSelect.vue\";\nimport ProviderMultiSelect from \"./components/ProviderMultiSelect.vue\";\n\n// Custom tester that checks if provider option exists (as object or boolean)\nconst hasProvider = (uischema: UISchemaElement) => {\n return uischema?.options?.provider !== undefined;\n};\n\n// Integer fallback tester — handles nested scopes like #/properties/parent/properties/child\nconst isIntegerScope = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { type?: string; scope?: string };\n if (ui?.type !== \"Control\" || !ui?.scope) return false;\n\n const propertySchema = resolveScopeSchema(\n ui.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return propertySchema?.type === \"integer\";\n};\n\n// Create specific testers for each component type\nconst providerSelectTester = rankWith(\n 107, // Higher than PrimeVue JfNumber integer renderer (106) so provider wins\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => !uischema?.options?.autocomplete,\n ),\n);\n\nconst providerAutocompleteTester = rankWith(\n 108, // Higher than providerSelectTester so autocomplete variant wins when flagged\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => uischema?.options?.autocomplete === true,\n ),\n);\n\n//
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/vue/index.ts"],"sourcesContent":["import type { UISchemaElement } from \"@jsonforms/core\";\nimport {\n and,\n isIntegerControl,\n isNumberControl,\n isStringControl,\n or,\n rankWith,\n isControl,\n} from \"@jsonforms/core\";\nimport { resolveScopeSchema } from \"../core/resolveScope\";\nimport { resolveItemsSchema } from \"./utils/objectMultiSelect\";\nimport ProviderAutocomplete from \"./components/ProviderAutocomplete.vue\";\nimport ProviderSelect from \"./components/ProviderSelect.vue\";\nimport ProviderMultiSelect from \"./components/ProviderMultiSelect.vue\";\nimport ProviderObjectMultiSelect from \"./components/ProviderObjectMultiSelect.vue\";\n\n// Custom tester that checks if provider option exists (as object or boolean)\nconst hasProvider = (uischema: UISchemaElement) => {\n return uischema?.options?.provider !== undefined;\n};\n\n// Integer fallback tester — handles nested scopes like #/properties/parent/properties/child\nconst isIntegerScope = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { type?: string; scope?: string };\n if (ui?.type !== \"Control\" || !ui?.scope) return false;\n\n const propertySchema = resolveScopeSchema(\n ui.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return propertySchema?.type === \"integer\";\n};\n\n// Create specific testers for each component type\nconst providerSelectTester = rankWith(\n 107, // Higher than PrimeVue JfNumber integer renderer (106) so provider wins\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => !uischema?.options?.autocomplete,\n ),\n);\n\nconst providerAutocompleteTester = rankWith(\n 108, // Higher than providerSelectTester so autocomplete variant wins when flagged\n and(\n or(\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and(isControl, isIntegerScope),\n ),\n hasProvider,\n (uischema) => uischema?.options?.autocomplete === true,\n ),\n);\n\n// Resolve the array schema at a control's scope, or undefined if the\n// control isn't an array. Shared by both array testers below.\nconst getArraySchema = (uischema: UISchemaElement, schema: unknown) => {\n const controlSchema = uischema as { type: string; scope?: string };\n if (controlSchema.type !== \"Control\" || !controlSchema.scope) return undefined;\n const propertySchema = resolveScopeSchema(\n controlSchema.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n if (propertySchema?.type !== \"array\") return undefined;\n return propertySchema;\n};\n\n// Object-item arrays — render with paired-object MultiSelect.\nconst isObjectArrayControl = (uischema: UISchemaElement, schema: unknown) => {\n const arr = getArraySchema(uischema, schema);\n if (!arr) return false;\n const items = resolveItemsSchema(\n arr,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n return items?.type === \"object\";\n};\n\n// Scalar-item arrays — the original tester. Excludes object-item arrays so\n// the new ObjectMultiSelect renderer can claim them at a higher rank.\nconst isScalarArrayControl = (uischema: UISchemaElement, schema: unknown) => {\n const arr = getArraySchema(uischema, schema);\n if (!arr) return false;\n const items = resolveItemsSchema(\n arr,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n // Treat unknown / no-items shapes as scalar (matches prior behavior where\n // any array control was eligible for ProviderMultiSelect).\n return items?.type !== \"object\";\n};\n\nconst providerObjectMultiSelectTester = rankWith(\n 110, // One higher than scalar multi-select so object-item arrays match first.\n and(isObjectArrayControl, hasProvider),\n);\n\nconst providerMultiSelectTester = rankWith(\n 109,\n and(isScalarArrayControl, hasProvider),\n);\n\nexport const providerRenderers = [\n {\n tester: providerObjectMultiSelectTester,\n renderer: ProviderObjectMultiSelect,\n },\n { tester: providerMultiSelectTester, renderer: ProviderMultiSelect },\n { tester: providerAutocompleteTester, renderer: ProviderAutocomplete },\n { tester: providerSelectTester, renderer: ProviderSelect },\n];\n\n// Export PrimeVue renderers (styles are auto-injected)\nexport { primevueRenderers, registerPrimevueRenderers } from \"./primevue\";\n\n// Export individual components\nexport {\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n ProviderObjectMultiSelect,\n};\nexport { useProvider } from \"./composables/useProvider\";\nexport { useProjection } from \"./composables/useProjection\";\nexport type { ProjectionResult } from \"./composables/useProjection\";\nexport { createDataLayer, useDataLayer } from \"./composables/useDataLayer\";\nexport type { DataLayer } from \"./composables/useDataLayer\";\nexport { useDeriveInitialValue } from \"./composables/useDeriveInitialValue\";\nexport type { DeriveInitialValueCfg } from \"./composables/useDeriveInitialValue\";\nexport { useDirtyValidation } from \"./composables/useDirtyValidation\";\nexport * from \"./testers\";\n\n// Export individual PrimeVue components using lazy evaluation to avoid circular deps\nexport {\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./primevue\";\n"],"names":["ProviderAutocomplete"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkBA,MAAM,cAAc,CAAC,aAA8B;AACjD,SAAO,UAAU,SAAS,aAAa;AACzC;AAGA,MAAM,iBAAiB,CAAC,UAAmB,WAAoB;AAC7D,QAAM,KAAK;AACX,MAAI,IAAI,SAAS,aAAa,CAAC,IAAI,MAAO,QAAO;AAEjD,QAAM,iBAAiB;AAAA,IACrB,GAAG;AAAA;AAAA,IAEH;AAAA,EAAA;AAEF,SAAO,gBAAgB,SAAS;AAClC;AAGA,MAAM,uBAAuB;AAAA,EAC3B;AAAA;AAAA,EACA;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,WAAW,cAAc;AAAA,IAAA;AAAA,IAE/B;AAAA,IACA,CAAC,aAAa,CAAC,UAAU,SAAS;AAAA,EAAA;AAEtC;AAEA,MAAM,6BAA6B;AAAA,EACjC;AAAA;AAAA,EACA;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,WAAW,cAAc;AAAA,IAAA;AAAA,IAE/B;AAAA,IACA,CAAC,aAAa,UAAU,SAAS,iBAAiB;AAAA,EAAA;AAEtD;AAIA,MAAM,iBAAiB,CAAC,UAA2B,WAAoB;AACrE,QAAM,gBAAgB;AACtB,MAAI,cAAc,SAAS,aAAa,CAAC,cAAc,MAAO,QAAO;AACrE,QAAM,iBAAiB;AAAA,IACrB,cAAc;AAAA;AAAA,IAEd;AAAA,EAAA;AAEF,MAAI,gBAAgB,SAAS,QAAS,QAAO;AAC7C,SAAO;AACT;AAGA,MAAM,uBAAuB,CAAC,UAA2B,WAAoB;AAC3E,QAAM,MAAM,eAAe,UAAU,MAAM;AAC3C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ;AAAA,IACZ;AAAA;AAAA,IAEA;AAAA,EAAA;AAEF,SAAO,OAAO,SAAS;AACzB;AAIA,MAAM,uBAAuB,CAAC,UAA2B,WAAoB;AAC3E,QAAM,MAAM,eAAe,UAAU,MAAM;AAC3C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ;AAAA,IACZ;AAAA;AAAA,IAEA;AAAA,EAAA;AAIF,SAAO,OAAO,SAAS;AACzB;AAEA,MAAM,kCAAkC;AAAA,EACtC;AAAA;AAAA,EACA,IAAI,sBAAsB,WAAW;AACvC;AAEA,MAAM,4BAA4B;AAAA,EAChC;AAAA,EACA,IAAI,sBAAsB,WAAW;AACvC;AAEO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,IACE,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,EAAE,QAAQ,2BAA2B,UAAU,oBAAA;AAAA,EAC/C,EAAE,QAAQ,4BAA4B,UAAUA,UAAA;AAAA,EAChD,EAAE,QAAQ,sBAAsB,UAAU,eAAA;AAC5C;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfBoolean.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/primevue/JfBoolean.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"JfBoolean.vue.d.ts","sourceRoot":"","sources":["../../../src/vue/primevue/JfBoolean.vue"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,wBAiMK"}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { defineComponent, getCurrentInstance, createElementBlock, openBlock,
|
|
1
|
+
import { defineComponent, getCurrentInstance, createElementBlock, openBlock, createElementVNode, createCommentVNode, createVNode, unref, normalizeClass, toDisplayString } from "vue";
|
|
2
2
|
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
3
3
|
import { useProjection } from "../composables/useProjection.js";
|
|
4
4
|
import { useDirtyValidation } from "../composables/useDirtyValidation.js";
|
|
5
5
|
import Checkbox from "primevue/checkbox";
|
|
6
|
-
const _hoisted_1 = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
};
|
|
10
|
-
const _hoisted_2 = {
|
|
6
|
+
const _hoisted_1 = { class: "jf-control" };
|
|
7
|
+
const _hoisted_2 = { style: { "display": "flex", "flex-direction": "row", "align-items": "center" } };
|
|
8
|
+
const _hoisted_3 = {
|
|
11
9
|
key: 0,
|
|
12
10
|
class: "jf-label"
|
|
13
11
|
};
|
|
14
|
-
const
|
|
12
|
+
const _hoisted_4 = {
|
|
13
|
+
key: 0,
|
|
14
|
+
class: "jf-description"
|
|
15
|
+
};
|
|
16
|
+
const _hoisted_5 = {
|
|
15
17
|
key: 1,
|
|
16
18
|
class: "p-error"
|
|
17
19
|
};
|
|
@@ -70,16 +72,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
70
72
|
};
|
|
71
73
|
return (_ctx, _cache) => {
|
|
72
74
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
createElementVNode("div", _hoisted_2, [
|
|
76
|
+
createVNode(unref(Checkbox), {
|
|
77
|
+
binary: true,
|
|
78
|
+
"model-value": !!unref(projectedData),
|
|
79
|
+
disabled: !unref(control).enabled,
|
|
80
|
+
class: normalizeClass({ "p-invalid": unref(showErrors) }),
|
|
81
|
+
"aria-invalid": unref(showErrors) || void 0,
|
|
82
|
+
"onUpdate:modelValue": onToggle
|
|
83
|
+
}, null, 8, ["model-value", "disabled", "class", "aria-invalid"]),
|
|
84
|
+
unref(projectedLabel) ? (openBlock(), createElementBlock("label", _hoisted_3, toDisplayString(unref(projectedLabel)), 1)) : createCommentVNode("", true)
|
|
85
|
+
]),
|
|
86
|
+
unref(control).description ? (openBlock(), createElementBlock("div", _hoisted_4, toDisplayString(unref(control).description), 1)) : createCommentVNode("", true),
|
|
87
|
+
unref(showErrors) ? (openBlock(), createElementBlock("small", _hoisted_5, toDisplayString(unref(projectedErrors)), 1)) : createCommentVNode("", true)
|
|
83
88
|
]);
|
|
84
89
|
};
|
|
85
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JfBoolean.vue.js","sources":["../../../src/vue/primevue/JfBoolean.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfBoolean\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { getCurrentInstance } from \"vue\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { useDirtyValidation } from \"../composables/useDirtyValidation\";\nimport Checkbox from \"primevue/checkbox\";\n\n// Access props from the component instance\nconst instance = getCurrentInstance()!;\nconst props = instance.props as unknown as ControlProps;\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst {\n projectedData,\n handleProjectedChange: handleChange,\n projectedErrors,\n projectedLabel,\n} = useProjection(control, rawHandleChange);\n\n// Track user interaction — errors only show after first toggle\nconst { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);\n\nconst onToggle = (val: boolean) => {\n markDirty();\n handleChange(control.value.path, val);\n};\n</script>\n\n<template>\n <div class=\"jf-control\" style=\"flex-direction: row; align-items: center\">\n
|
|
1
|
+
{"version":3,"file":"JfBoolean.vue.js","sources":["../../../src/vue/primevue/JfBoolean.vue"],"sourcesContent":["<script lang=\"ts\">\nexport default {\n name: \"JfBoolean\",\n props: {\n uischema: {\n type: Object,\n required: true,\n },\n schema: {\n type: Object,\n required: true,\n },\n path: {\n type: String,\n required: true,\n },\n enabled: {\n type: Boolean,\n default: undefined,\n },\n renderers: {\n type: Array,\n required: false,\n default: undefined,\n },\n cells: {\n type: Array,\n required: false,\n default: undefined,\n },\n config: {\n type: Object,\n required: false,\n default: undefined,\n },\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport type { ControlProps } from \"@jsonforms/vue\";\nimport { useJsonFormsControl } from \"@jsonforms/vue\";\nimport { getCurrentInstance } from \"vue\";\nimport { useProjection } from \"../composables/useProjection\";\nimport { useDirtyValidation } from \"../composables/useDirtyValidation\";\nimport Checkbox from \"primevue/checkbox\";\n\n// Access props from the component instance\nconst instance = getCurrentInstance()!;\nconst props = instance.props as unknown as ControlProps;\nconst { control, handleChange: rawHandleChange } = useJsonFormsControl(props);\nconst {\n projectedData,\n handleProjectedChange: handleChange,\n projectedErrors,\n projectedLabel,\n} = useProjection(control, rawHandleChange);\n\n// Track user interaction — errors only show after first toggle\nconst { showErrors, markDirty } = useDirtyValidation(control, projectedErrors);\n\nconst onToggle = (val: boolean) => {\n markDirty();\n handleChange(control.value.path, val);\n};\n</script>\n\n<template>\n <div class=\"jf-control\">\n <div style=\"display: flex; flex-direction: row; align-items: center\">\n <Checkbox\n :binary=\"true\"\n :model-value=\"!!projectedData\"\n :disabled=\"!control.enabled\"\n :class=\"{ 'p-invalid': showErrors }\"\n :aria-invalid=\"showErrors || undefined\"\n @update:model-value=\"onToggle\"\n />\n <label v-if=\"projectedLabel\" class=\"jf-label\">{{ projectedLabel }}</label>\n </div>\n <div v-if=\"control.description\" class=\"jf-description\">\n {{ control.description }}\n </div>\n <small v-if=\"showErrors\" class=\"p-error\">{{ projectedErrors }}</small>\n </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_toDisplayString"],"mappings":";;;;;;;;;;;;;;;;;;;AACA,MAAA,cAAe;AAAA,EACb,MAAM;AAAA,EACN,OAAO;AAAA,IACL,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,IAAA;AAAA,IAEX,WAAW;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,IAEX,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,IAEX,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;;;;AAYA,UAAM,WAAW,mBAAA;AACjB,UAAM,QAAQ,SAAS;AACvB,UAAM,EAAE,SAAS,cAAc,gBAAA,IAAoB,oBAAoB,KAAK;AAC5E,UAAM;AAAA,MACJ;AAAA,MACA,uBAAuB;AAAA,MACvB;AAAA,MACA;AAAA,IAAA,IACE,cAAc,SAAS,eAAe;AAG1C,UAAM,EAAE,YAAY,UAAA,IAAc,mBAAmB,SAAS,eAAe;AAE7E,UAAM,WAAW,CAAC,QAAiB;AACjC,gBAAA;AACA,mBAAa,QAAQ,MAAM,MAAM,GAAG;AAAA,IACtC;;AAIE,aAAAA,UAAA,GAAAC,mBAgBM,OAhBN,YAgBM;AAAA,QAfJC,mBAUM,OAVN,YAUM;AAAA,UATJC,YAOEC,MAAA,QAAA,GAAA;AAAA,YANC,QAAQ;AAAA,YACR,iBAAeA,MAAA,aAAA;AAAA,YACf,UAAQ,CAAGA,MAAA,OAAA,EAAQ;AAAA,YACnB,qCAAsBA,MAAA,UAAA,GAAU;AAAA,YAChC,gBAAcA,MAAA,UAAA,KAAc;AAAA,YAC5B,uBAAoB;AAAA,UAAA;UAEVA,MAAA,cAAA,kBAAbH,mBAA0E,SAA1E,YAA0EI,gBAAzBD,MAAA,cAAA,CAAc,GAAA,CAAA;;QAEtDA,MAAA,OAAA,EAAQ,eAAnBJ,UAAA,GAAAC,mBAEM,OAFN,YAEMI,gBADDD,MAAA,OAAA,EAAQ,WAAW,GAAA,CAAA;QAEXA,MAAA,UAAA,kBAAbH,mBAAsE,SAAtE,YAAsEI,gBAA1BD,MAAA,eAAA,CAAe,GAAA,CAAA;;;;;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for `ProviderObjectMultiSelect`: bidirectional translation between
|
|
3
|
+
* the form-data shape (paired objects with consumer-named keys) and the
|
|
4
|
+
* MultiSelect model shape (`{ value, label }` matching the provider's `map`
|
|
5
|
+
* config), plus `objectKeys` inference when the consumer doesn't specify
|
|
6
|
+
* them on the uischema.
|
|
7
|
+
*
|
|
8
|
+
* Pure functions — no Vue, no rendering — so the renderer can compose them
|
|
9
|
+
* and tests can exercise the logic in isolation.
|
|
10
|
+
*/
|
|
11
|
+
export interface ObjectKeys {
|
|
12
|
+
/** Property name on the form-data object that holds the identifier. */
|
|
13
|
+
value: string;
|
|
14
|
+
/** Property name on the form-data object that holds the display string. */
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MultiSelectOption {
|
|
18
|
+
value: unknown;
|
|
19
|
+
label: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the
|
|
24
|
+
* shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items
|
|
25
|
+
* already in `{ value, label }` shape pass through; missing keys yield
|
|
26
|
+
* `undefined` / `""` so the renderer doesn't blow up on partial data.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toMultiSelectShape(formData: unknown, keys: ObjectKeys): MultiSelectOption[];
|
|
29
|
+
/**
|
|
30
|
+
* Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items
|
|
31
|
+
* back into form-data shape using the consumer-specified property names.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fromMultiSelectShape(modelData: unknown, keys: ObjectKeys): Record<string, unknown>[];
|
|
34
|
+
/**
|
|
35
|
+
* Order-insensitive equality of two object-shape selections by the
|
|
36
|
+
* identifier property. Used to short-circuit redundant `handleChange`
|
|
37
|
+
* calls when the model emits the same selection under reference inequality.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sameObjectSet(a: unknown, b: unknown, identifierKey: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the items schema of an array control, dereferencing `items.$ref`
|
|
42
|
+
* against the root if present. Returns `undefined` if no items schema exists.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveItemsSchema(arraySchema: Record<string, any> | undefined, rootSchema: Record<string, any>): Record<string, any> | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Infer `objectKeys` from a resolved items schema when the consumer hasn't
|
|
47
|
+
* specified them on the uischema.
|
|
48
|
+
*
|
|
49
|
+
* Strategy: look at `items.required`. If it has exactly two entries, the
|
|
50
|
+
* one whose property has `format: 'uuid'` becomes `value`; the other
|
|
51
|
+
* becomes `label`. If neither has a uuid format, the first entry is
|
|
52
|
+
* `value`, the second is `label`. Returns `undefined` (and the renderer
|
|
53
|
+
* throws at mount) for any other shape — explicit `objectKeys` is required.
|
|
54
|
+
*/
|
|
55
|
+
export declare function inferObjectKeys(itemsSchema: Record<string, any> | undefined): ObjectKeys | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the active `objectKeys` for a control: prefer the explicit
|
|
58
|
+
* `uischema.options.objectKeys`, fall back to schema-driven inference.
|
|
59
|
+
* Returns `undefined` when neither is available; the renderer surfaces a
|
|
60
|
+
* runtime error in that case so the consumer knows to be explicit.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveObjectKeys(uischemaOptions: {
|
|
63
|
+
objectKeys?: {
|
|
64
|
+
value?: unknown;
|
|
65
|
+
label?: unknown;
|
|
66
|
+
};
|
|
67
|
+
} | undefined, itemsSchema: Record<string, any> | undefined): ObjectKeys | undefined;
|
|
68
|
+
//# sourceMappingURL=objectMultiSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectMultiSelect.d.ts","sourceRoot":"","sources":["../../../src/vue/utils/objectMultiSelect.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GACf,iBAAiB,EAAE,CAqBrB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,OAAO,EAClB,IAAI,EAAE,UAAU,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAW3B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,OAAO,EACV,CAAC,EAAE,OAAO,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAaT;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAEhC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAE5C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAOjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAE7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC3C,UAAU,GAAG,SAAS,CAcxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,eAAe,EAAE;IAAE,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,EAElF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC3C,UAAU,GAAG,SAAS,CAUxB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { deref } from "../../core/refs.js";
|
|
2
|
+
function toMultiSelectShape(formData, keys) {
|
|
3
|
+
if (!Array.isArray(formData)) return [];
|
|
4
|
+
return formData.filter((item) => item !== null && typeof item === "object").map((item) => {
|
|
5
|
+
const obj = item;
|
|
6
|
+
if (!(keys.value in obj) && !(keys.label in obj) && "value" in obj && "label" in obj) {
|
|
7
|
+
return obj;
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
value: obj[keys.value],
|
|
11
|
+
label: typeof obj[keys.label] === "string" ? obj[keys.label] : ""
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function fromMultiSelectShape(modelData, keys) {
|
|
16
|
+
if (!Array.isArray(modelData)) return [];
|
|
17
|
+
return modelData.filter((item) => item !== null && typeof item === "object").map((item) => {
|
|
18
|
+
const obj = item;
|
|
19
|
+
return {
|
|
20
|
+
[keys.value]: obj.value,
|
|
21
|
+
[keys.label]: obj.label
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function sameObjectSet(a, b, identifierKey) {
|
|
26
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const ids = new Set(
|
|
30
|
+
b.filter((x) => x !== null && typeof x === "object").map((x) => x[identifierKey])
|
|
31
|
+
);
|
|
32
|
+
return a.every((x) => {
|
|
33
|
+
if (x === null || typeof x !== "object") return false;
|
|
34
|
+
return ids.has(x[identifierKey]);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function resolveItemsSchema(arraySchema, rootSchema) {
|
|
38
|
+
if (!arraySchema || typeof arraySchema !== "object") return void 0;
|
|
39
|
+
const items = arraySchema.items;
|
|
40
|
+
if (!items || typeof items !== "object" || Array.isArray(items)) {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
return deref(items, rootSchema);
|
|
44
|
+
}
|
|
45
|
+
function inferObjectKeys(itemsSchema) {
|
|
46
|
+
if (!itemsSchema || itemsSchema.type !== "object") return void 0;
|
|
47
|
+
const required = itemsSchema.required;
|
|
48
|
+
if (!Array.isArray(required) || required.length !== 2) return void 0;
|
|
49
|
+
const [a, b] = required;
|
|
50
|
+
const props = itemsSchema.properties ?? {};
|
|
51
|
+
const aIsUuid = props[a]?.format === "uuid";
|
|
52
|
+
const bIsUuid = props[b]?.format === "uuid";
|
|
53
|
+
if (aIsUuid && !bIsUuid) return { value: a, label: b };
|
|
54
|
+
if (bIsUuid && !aIsUuid) return { value: b, label: a };
|
|
55
|
+
return { value: a, label: b };
|
|
56
|
+
}
|
|
57
|
+
function resolveObjectKeys(uischemaOptions, itemsSchema) {
|
|
58
|
+
const explicit = uischemaOptions?.objectKeys;
|
|
59
|
+
if (explicit && typeof explicit.value === "string" && typeof explicit.label === "string") {
|
|
60
|
+
return { value: explicit.value, label: explicit.label };
|
|
61
|
+
}
|
|
62
|
+
return inferObjectKeys(itemsSchema);
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
fromMultiSelectShape,
|
|
66
|
+
inferObjectKeys,
|
|
67
|
+
resolveItemsSchema,
|
|
68
|
+
resolveObjectKeys,
|
|
69
|
+
sameObjectSet,
|
|
70
|
+
toMultiSelectShape
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=objectMultiSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectMultiSelect.js","sources":["../../../src/vue/utils/objectMultiSelect.ts"],"sourcesContent":["import { deref } from \"../../core/refs\";\n\n/**\n * Helpers for `ProviderObjectMultiSelect`: bidirectional translation between\n * the form-data shape (paired objects with consumer-named keys) and the\n * MultiSelect model shape (`{ value, label }` matching the provider's `map`\n * config), plus `objectKeys` inference when the consumer doesn't specify\n * them on the uischema.\n *\n * Pure functions — no Vue, no rendering — so the renderer can compose them\n * and tests can exercise the logic in isolation.\n */\n\nexport interface ObjectKeys {\n /** Property name on the form-data object that holds the identifier. */\n value: string;\n /** Property name on the form-data object that holds the display string. */\n label: string;\n}\n\nexport interface MultiSelectOption {\n value: unknown;\n label: string;\n [key: string]: unknown;\n}\n\n/**\n * Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the\n * shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items\n * already in `{ value, label }` shape pass through; missing keys yield\n * `undefined` / `\"\"` so the renderer doesn't blow up on partial data.\n */\nexport function toMultiSelectShape(\n formData: unknown,\n keys: ObjectKeys,\n): MultiSelectOption[] {\n if (!Array.isArray(formData)) return [];\n return formData\n .filter((item) => item !== null && typeof item === \"object\")\n .map((item) => {\n const obj = item as Record<string, unknown>;\n // Tolerate already-translated items: if both `value` and `label` are\n // present and the form-data keys aren't, assume MultiSelect shape.\n if (\n !(keys.value in obj) &&\n !(keys.label in obj) &&\n \"value\" in obj &&\n \"label\" in obj\n ) {\n return obj as MultiSelectOption;\n }\n return {\n value: obj[keys.value],\n label: typeof obj[keys.label] === \"string\" ? (obj[keys.label] as string) : \"\",\n };\n });\n}\n\n/**\n * Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items\n * back into form-data shape using the consumer-specified property names.\n */\nexport function fromMultiSelectShape(\n modelData: unknown,\n keys: ObjectKeys,\n): Record<string, unknown>[] {\n if (!Array.isArray(modelData)) return [];\n return modelData\n .filter((item) => item !== null && typeof item === \"object\")\n .map((item) => {\n const obj = item as Record<string, unknown>;\n return {\n [keys.value]: obj.value,\n [keys.label]: obj.label,\n };\n });\n}\n\n/**\n * Order-insensitive equality of two object-shape selections by the\n * identifier property. Used to short-circuit redundant `handleChange`\n * calls when the model emits the same selection under reference inequality.\n */\nexport function sameObjectSet(\n a: unknown,\n b: unknown,\n identifierKey: string,\n): boolean {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {\n return false;\n }\n const ids = new Set(\n b\n .filter((x) => x !== null && typeof x === \"object\")\n .map((x) => (x as Record<string, unknown>)[identifierKey]),\n );\n return a.every((x) => {\n if (x === null || typeof x !== \"object\") return false;\n return ids.has((x as Record<string, unknown>)[identifierKey]);\n });\n}\n\n/**\n * Resolve the items schema of an array control, dereferencing `items.$ref`\n * against the root if present. Returns `undefined` if no items schema exists.\n */\nexport function resolveItemsSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n arraySchema: Record<string, any> | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!arraySchema || typeof arraySchema !== \"object\") return undefined;\n const items = arraySchema.items;\n if (!items || typeof items !== \"object\" || Array.isArray(items)) {\n return undefined;\n }\n return deref(items, rootSchema);\n}\n\n/**\n * Infer `objectKeys` from a resolved items schema when the consumer hasn't\n * specified them on the uischema.\n *\n * Strategy: look at `items.required`. If it has exactly two entries, the\n * one whose property has `format: 'uuid'` becomes `value`; the other\n * becomes `label`. If neither has a uuid format, the first entry is\n * `value`, the second is `label`. Returns `undefined` (and the renderer\n * throws at mount) for any other shape — explicit `objectKeys` is required.\n */\nexport function inferObjectKeys(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n itemsSchema: Record<string, any> | undefined,\n): ObjectKeys | undefined {\n if (!itemsSchema || itemsSchema.type !== \"object\") return undefined;\n const required = itemsSchema.required;\n if (!Array.isArray(required) || required.length !== 2) return undefined;\n const [a, b] = required as [string, string];\n const props = (itemsSchema.properties ?? {}) as Record<\n string,\n { format?: string }\n >;\n const aIsUuid = props[a]?.format === \"uuid\";\n const bIsUuid = props[b]?.format === \"uuid\";\n if (aIsUuid && !bIsUuid) return { value: a, label: b };\n if (bIsUuid && !aIsUuid) return { value: b, label: a };\n return { value: a, label: b };\n}\n\n/**\n * Resolve the active `objectKeys` for a control: prefer the explicit\n * `uischema.options.objectKeys`, fall back to schema-driven inference.\n * Returns `undefined` when neither is available; the renderer surfaces a\n * runtime error in that case so the consumer knows to be explicit.\n */\nexport function resolveObjectKeys(\n uischemaOptions: { objectKeys?: { value?: unknown; label?: unknown } } | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n itemsSchema: Record<string, any> | undefined,\n): ObjectKeys | undefined {\n const explicit = uischemaOptions?.objectKeys;\n if (\n explicit &&\n typeof explicit.value === \"string\" &&\n typeof explicit.label === \"string\"\n ) {\n return { value: explicit.value, label: explicit.label };\n }\n return inferObjectKeys(itemsSchema);\n}\n"],"names":[],"mappings":";AAgCO,SAAS,mBACd,UACA,MACqB;AACrB,MAAI,CAAC,MAAM,QAAQ,QAAQ,UAAU,CAAA;AACrC,SAAO,SACJ,OAAO,CAAC,SAAS,SAAS,QAAQ,OAAO,SAAS,QAAQ,EAC1D,IAAI,CAAC,SAAS;AACb,UAAM,MAAM;AAGZ,QACE,EAAE,KAAK,SAAS,QAChB,EAAE,KAAK,SAAS,QAChB,WAAW,OACX,WAAW,KACX;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,OAAO,IAAI,KAAK,KAAK;AAAA,MACrB,OAAO,OAAO,IAAI,KAAK,KAAK,MAAM,WAAY,IAAI,KAAK,KAAK,IAAe;AAAA,IAAA;AAAA,EAE/E,CAAC;AACL;AAMO,SAAS,qBACd,WACA,MAC2B;AAC3B,MAAI,CAAC,MAAM,QAAQ,SAAS,UAAU,CAAA;AACtC,SAAO,UACJ,OAAO,CAAC,SAAS,SAAS,QAAQ,OAAO,SAAS,QAAQ,EAC1D,IAAI,CAAC,SAAS;AACb,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,CAAC,KAAK,KAAK,GAAG,IAAI;AAAA,MAClB,CAAC,KAAK,KAAK,GAAG,IAAI;AAAA,IAAA;AAAA,EAEtB,CAAC;AACL;AAOO,SAAS,cACd,GACA,GACA,eACS;AACT,MAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ;AACnE,WAAO;AAAA,EACT;AACA,QAAM,MAAM,IAAI;AAAA,IACd,EACG,OAAO,CAAC,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ,EACjD,IAAI,CAAC,MAAO,EAA8B,aAAa,CAAC;AAAA,EAAA;AAE7D,SAAO,EAAE,MAAM,CAAC,MAAM;AACpB,QAAI,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AAChD,WAAO,IAAI,IAAK,EAA8B,aAAa,CAAC;AAAA,EAC9D,CAAC;AACH;AAMO,SAAS,mBAEd,aAEA,YAEiC;AACjC,MAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO;AAC5D,QAAM,QAAQ,YAAY;AAC1B,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO;AAAA,EACT;AACA,SAAO,MAAM,OAAO,UAAU;AAChC;AAYO,SAAS,gBAEd,aACwB;AACxB,MAAI,CAAC,eAAe,YAAY,SAAS,SAAU,QAAO;AAC1D,QAAM,WAAW,YAAY;AAC7B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,EAAG,QAAO;AAC9D,QAAM,CAAC,GAAG,CAAC,IAAI;AACf,QAAM,QAAS,YAAY,cAAc,CAAA;AAIzC,QAAM,UAAU,MAAM,CAAC,GAAG,WAAW;AACrC,QAAM,UAAU,MAAM,CAAC,GAAG,WAAW;AACrC,MAAI,WAAW,CAAC,QAAS,QAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AACnD,MAAI,WAAW,CAAC,QAAS,QAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AACnD,SAAO,EAAE,OAAO,GAAG,OAAO,EAAA;AAC5B;AAQO,SAAS,kBACd,iBAEA,aACwB;AACxB,QAAM,WAAW,iBAAiB;AAClC,MACE,YACA,OAAO,SAAS,UAAU,YAC1B,OAAO,SAAS,UAAU,UAC1B;AACA,WAAO,EAAE,OAAO,SAAS,OAAO,OAAO,SAAS,MAAA;AAAA,EAClD;AACA,SAAO,gBAAgB,WAAW;AACpC;"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ControlElement, JsonSchema } from "@jsonforms/core";
|
|
3
|
+
import { useJsonFormsControl } from "@jsonforms/vue";
|
|
4
|
+
import { computed, inject, watch } from "vue";
|
|
5
|
+
import { useProvider } from "../composables/useProvider";
|
|
6
|
+
import { useProjection } from "../composables/useProjection";
|
|
7
|
+
import { resolvePlaceholder } from "../utils/placeholder";
|
|
8
|
+
import {
|
|
9
|
+
fromMultiSelectShape,
|
|
10
|
+
resolveItemsSchema,
|
|
11
|
+
resolveObjectKeys,
|
|
12
|
+
sameObjectSet,
|
|
13
|
+
toMultiSelectShape,
|
|
14
|
+
type MultiSelectOption,
|
|
15
|
+
type ObjectKeys,
|
|
16
|
+
} from "../utils/objectMultiSelect";
|
|
17
|
+
import MultiSelect from "primevue/multiselect";
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
uischema: ControlElement;
|
|
21
|
+
schema: JsonSchema;
|
|
22
|
+
path: string;
|
|
23
|
+
}>();
|
|
24
|
+
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
25
|
+
const {
|
|
26
|
+
projectedData,
|
|
27
|
+
projectedLabel,
|
|
28
|
+
handleProjectedChange: handleChange,
|
|
29
|
+
} = useProjection(control, rawHandleChange);
|
|
30
|
+
|
|
31
|
+
// Pull root schema so item-schema $refs resolve against the consumer's $defs.
|
|
32
|
+
const jsonforms = inject<{
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
core?: { schema?: Record<string, any> };
|
|
35
|
+
} | null>("jsonforms", null);
|
|
36
|
+
const rootSchema = computed(
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
() => (jsonforms?.core?.schema ?? control.value.schema) as Record<string, any>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const itemsSchema = computed(() =>
|
|
42
|
+
resolveItemsSchema(
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
control.value.schema as Record<string, any>,
|
|
45
|
+
rootSchema.value,
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const objectKeys = computed<ObjectKeys>(() => {
|
|
50
|
+
const resolved = resolveObjectKeys(
|
|
51
|
+
control.value.uischema?.options as
|
|
52
|
+
| { objectKeys?: { value?: unknown; label?: unknown } }
|
|
53
|
+
| undefined,
|
|
54
|
+
itemsSchema.value,
|
|
55
|
+
);
|
|
56
|
+
if (!resolved) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"[ProviderObjectMultiSelect] objectKeys could not be resolved. " +
|
|
59
|
+
"Specify `uischema.options.objectKeys = { value, label }` or declare " +
|
|
60
|
+
"exactly two `required` properties on the array's items schema so " +
|
|
61
|
+
"they can be inferred.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return resolved;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const binding = computed(() => {
|
|
68
|
+
const provider = control.value.uischema?.options?.provider;
|
|
69
|
+
if (provider && typeof provider === "object" && !provider.load) {
|
|
70
|
+
return { ...provider, load: "mount" };
|
|
71
|
+
}
|
|
72
|
+
return provider;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const deps = computed(
|
|
76
|
+
() =>
|
|
77
|
+
((
|
|
78
|
+
(control.value.schema as Record<string, unknown>)?.[
|
|
79
|
+
"x-provider"
|
|
80
|
+
] as Record<string, unknown>
|
|
81
|
+
)?.dependsOn as string[]) ?? [],
|
|
82
|
+
);
|
|
83
|
+
const depValues = computed(() => deps.value.map(() => null));
|
|
84
|
+
|
|
85
|
+
const injectedFormData = inject<{ value: unknown }>("formData", { value: {} });
|
|
86
|
+
const rootData = computed(() => injectedFormData.value || {});
|
|
87
|
+
|
|
88
|
+
const { items, loading, error } = useProvider(binding, {
|
|
89
|
+
data: rootData,
|
|
90
|
+
path: control.value.path,
|
|
91
|
+
dependsOnValues: depValues,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Auto-select when provider returns only one item — paired-object variant.
|
|
95
|
+
watch(
|
|
96
|
+
[items, loading],
|
|
97
|
+
([newItems, isLoading]) => {
|
|
98
|
+
if (
|
|
99
|
+
!control.value.uischema?.options?.autoSelectSingle ||
|
|
100
|
+
isLoading ||
|
|
101
|
+
newItems.length !== 1
|
|
102
|
+
) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const single = newItems[0]!;
|
|
106
|
+
const current = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
107
|
+
if (current.length === 0) {
|
|
108
|
+
handleChange(control.value.path, [
|
|
109
|
+
{
|
|
110
|
+
[objectKeys.value.value]: single.value,
|
|
111
|
+
[objectKeys.value.label]: single.label,
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{ immediate: true },
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const value = computed<MultiSelectOption[]>({
|
|
120
|
+
get() {
|
|
121
|
+
return toMultiSelectShape(projectedData.value, objectKeys.value);
|
|
122
|
+
},
|
|
123
|
+
set(val) {
|
|
124
|
+
const next = fromMultiSelectShape(val, objectKeys.value);
|
|
125
|
+
const curr = Array.isArray(projectedData.value) ? projectedData.value : [];
|
|
126
|
+
if (!sameObjectSet(curr, next, objectKeys.value.value)) {
|
|
127
|
+
handleChange(control.value.path, next);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const placeholder = computed(() => {
|
|
133
|
+
if (loading.value) return "Loading…";
|
|
134
|
+
return resolvePlaceholder(
|
|
135
|
+
control.value.uischema,
|
|
136
|
+
projectedLabel.value,
|
|
137
|
+
"select",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<template>
|
|
143
|
+
<div class="jf-control">
|
|
144
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
145
|
+
<div v-if="control.description" class="jf-description">
|
|
146
|
+
{{ control.description }}
|
|
147
|
+
</div>
|
|
148
|
+
<MultiSelect
|
|
149
|
+
v-model="value"
|
|
150
|
+
class="w-full!"
|
|
151
|
+
:options="items"
|
|
152
|
+
option-label="label"
|
|
153
|
+
data-key="value"
|
|
154
|
+
display="chip"
|
|
155
|
+
:placeholder="placeholder"
|
|
156
|
+
:disabled="!control.enabled || loading"
|
|
157
|
+
:show-clear="true"
|
|
158
|
+
/>
|
|
159
|
+
<small v-if="error" class="p-error" role="alert"
|
|
160
|
+
>Failed to load: {{ error }}</small
|
|
161
|
+
>
|
|
162
|
+
</div>
|
|
163
|
+
</template>
|
|
164
|
+
|
|
165
|
+
<style scoped>
|
|
166
|
+
:deep(.p-multiselect-label) {
|
|
167
|
+
text-align: left;
|
|
168
|
+
}
|
|
169
|
+
</style>
|
package/src/vue/index.ts
CHANGED
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
isControl,
|
|
10
10
|
} from "@jsonforms/core";
|
|
11
11
|
import { resolveScopeSchema } from "../core/resolveScope";
|
|
12
|
+
import { resolveItemsSchema } from "./utils/objectMultiSelect";
|
|
12
13
|
import ProviderAutocomplete from "./components/ProviderAutocomplete.vue";
|
|
13
14
|
import ProviderSelect from "./components/ProviderSelect.vue";
|
|
14
15
|
import ProviderMultiSelect from "./components/ProviderMultiSelect.vue";
|
|
16
|
+
import ProviderObjectMultiSelect from "./components/ProviderObjectMultiSelect.vue";
|
|
15
17
|
|
|
16
18
|
// Custom tester that checks if provider option exists (as object or boolean)
|
|
17
19
|
const hasProvider = (uischema: UISchemaElement) => {
|
|
@@ -60,27 +62,62 @@ const providerAutocompleteTester = rankWith(
|
|
|
60
62
|
),
|
|
61
63
|
);
|
|
62
64
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
+
// Resolve the array schema at a control's scope, or undefined if the
|
|
66
|
+
// control isn't an array. Shared by both array testers below.
|
|
67
|
+
const getArraySchema = (uischema: UISchemaElement, schema: unknown) => {
|
|
65
68
|
const controlSchema = uischema as { type: string; scope?: string };
|
|
66
|
-
if (controlSchema.type !== "Control" || !controlSchema.scope)
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
69
|
+
if (controlSchema.type !== "Control" || !controlSchema.scope) return undefined;
|
|
70
70
|
const propertySchema = resolveScopeSchema(
|
|
71
71
|
controlSchema.scope,
|
|
72
72
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
73
|
schema as Record<string, any>,
|
|
74
74
|
);
|
|
75
|
-
|
|
75
|
+
if (propertySchema?.type !== "array") return undefined;
|
|
76
|
+
return propertySchema;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Object-item arrays — render with paired-object MultiSelect.
|
|
80
|
+
const isObjectArrayControl = (uischema: UISchemaElement, schema: unknown) => {
|
|
81
|
+
const arr = getArraySchema(uischema, schema);
|
|
82
|
+
if (!arr) return false;
|
|
83
|
+
const items = resolveItemsSchema(
|
|
84
|
+
arr,
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
schema as Record<string, any>,
|
|
87
|
+
);
|
|
88
|
+
return items?.type === "object";
|
|
76
89
|
};
|
|
77
90
|
|
|
91
|
+
// Scalar-item arrays — the original tester. Excludes object-item arrays so
|
|
92
|
+
// the new ObjectMultiSelect renderer can claim them at a higher rank.
|
|
93
|
+
const isScalarArrayControl = (uischema: UISchemaElement, schema: unknown) => {
|
|
94
|
+
const arr = getArraySchema(uischema, schema);
|
|
95
|
+
if (!arr) return false;
|
|
96
|
+
const items = resolveItemsSchema(
|
|
97
|
+
arr,
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
schema as Record<string, any>,
|
|
100
|
+
);
|
|
101
|
+
// Treat unknown / no-items shapes as scalar (matches prior behavior where
|
|
102
|
+
// any array control was eligible for ProviderMultiSelect).
|
|
103
|
+
return items?.type !== "object";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const providerObjectMultiSelectTester = rankWith(
|
|
107
|
+
110, // One higher than scalar multi-select so object-item arrays match first.
|
|
108
|
+
and(isObjectArrayControl, hasProvider),
|
|
109
|
+
);
|
|
110
|
+
|
|
78
111
|
const providerMultiSelectTester = rankWith(
|
|
79
|
-
109,
|
|
80
|
-
and(
|
|
112
|
+
109,
|
|
113
|
+
and(isScalarArrayControl, hasProvider),
|
|
81
114
|
);
|
|
82
115
|
|
|
83
116
|
export const providerRenderers = [
|
|
117
|
+
{
|
|
118
|
+
tester: providerObjectMultiSelectTester,
|
|
119
|
+
renderer: ProviderObjectMultiSelect,
|
|
120
|
+
},
|
|
84
121
|
{ tester: providerMultiSelectTester, renderer: ProviderMultiSelect },
|
|
85
122
|
{ tester: providerAutocompleteTester, renderer: ProviderAutocomplete },
|
|
86
123
|
{ tester: providerSelectTester, renderer: ProviderSelect },
|
|
@@ -90,7 +127,12 @@ export const providerRenderers = [
|
|
|
90
127
|
export { primevueRenderers, registerPrimevueRenderers } from "./primevue";
|
|
91
128
|
|
|
92
129
|
// Export individual components
|
|
93
|
-
export {
|
|
130
|
+
export {
|
|
131
|
+
ProviderAutocomplete,
|
|
132
|
+
ProviderSelect,
|
|
133
|
+
ProviderMultiSelect,
|
|
134
|
+
ProviderObjectMultiSelect,
|
|
135
|
+
};
|
|
94
136
|
export { useProvider } from "./composables/useProvider";
|
|
95
137
|
export { useProjection } from "./composables/useProjection";
|
|
96
138
|
export type { ProjectionResult } from "./composables/useProjection";
|
|
@@ -66,16 +66,21 @@ const onToggle = (val: boolean) => {
|
|
|
66
66
|
</script>
|
|
67
67
|
|
|
68
68
|
<template>
|
|
69
|
-
<div class="jf-control"
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
<div class="jf-control">
|
|
70
|
+
<div style="display: flex; flex-direction: row; align-items: center">
|
|
71
|
+
<Checkbox
|
|
72
|
+
:binary="true"
|
|
73
|
+
:model-value="!!projectedData"
|
|
74
|
+
:disabled="!control.enabled"
|
|
75
|
+
:class="{ 'p-invalid': showErrors }"
|
|
76
|
+
:aria-invalid="showErrors || undefined"
|
|
77
|
+
@update:model-value="onToggle"
|
|
78
|
+
/>
|
|
79
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
80
|
+
</div>
|
|
81
|
+
<div v-if="control.description" class="jf-description">
|
|
82
|
+
{{ control.description }}
|
|
83
|
+
</div>
|
|
79
84
|
<small v-if="showErrors" class="p-error">{{ projectedErrors }}</small>
|
|
80
85
|
</div>
|
|
81
86
|
</template>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { deref } from "../../core/refs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helpers for `ProviderObjectMultiSelect`: bidirectional translation between
|
|
5
|
+
* the form-data shape (paired objects with consumer-named keys) and the
|
|
6
|
+
* MultiSelect model shape (`{ value, label }` matching the provider's `map`
|
|
7
|
+
* config), plus `objectKeys` inference when the consumer doesn't specify
|
|
8
|
+
* them on the uischema.
|
|
9
|
+
*
|
|
10
|
+
* Pure functions — no Vue, no rendering — so the renderer can compose them
|
|
11
|
+
* and tests can exercise the logic in isolation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface ObjectKeys {
|
|
15
|
+
/** Property name on the form-data object that holds the identifier. */
|
|
16
|
+
value: string;
|
|
17
|
+
/** Property name on the form-data object that holds the display string. */
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MultiSelectOption {
|
|
22
|
+
value: unknown;
|
|
23
|
+
label: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Translate form-data items (`[{ [valueKey]: v, [labelKey]: l }]`) into the
|
|
29
|
+
* shape PrimeVue's `<MultiSelect>` expects (`[{ value, label }]`). Items
|
|
30
|
+
* already in `{ value, label }` shape pass through; missing keys yield
|
|
31
|
+
* `undefined` / `""` so the renderer doesn't blow up on partial data.
|
|
32
|
+
*/
|
|
33
|
+
export function toMultiSelectShape(
|
|
34
|
+
formData: unknown,
|
|
35
|
+
keys: ObjectKeys,
|
|
36
|
+
): MultiSelectOption[] {
|
|
37
|
+
if (!Array.isArray(formData)) return [];
|
|
38
|
+
return formData
|
|
39
|
+
.filter((item) => item !== null && typeof item === "object")
|
|
40
|
+
.map((item) => {
|
|
41
|
+
const obj = item as Record<string, unknown>;
|
|
42
|
+
// Tolerate already-translated items: if both `value` and `label` are
|
|
43
|
+
// present and the form-data keys aren't, assume MultiSelect shape.
|
|
44
|
+
if (
|
|
45
|
+
!(keys.value in obj) &&
|
|
46
|
+
!(keys.label in obj) &&
|
|
47
|
+
"value" in obj &&
|
|
48
|
+
"label" in obj
|
|
49
|
+
) {
|
|
50
|
+
return obj as MultiSelectOption;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
value: obj[keys.value],
|
|
54
|
+
label: typeof obj[keys.label] === "string" ? (obj[keys.label] as string) : "",
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inverse of `toMultiSelectShape`. Translates `<MultiSelect>` model items
|
|
61
|
+
* back into form-data shape using the consumer-specified property names.
|
|
62
|
+
*/
|
|
63
|
+
export function fromMultiSelectShape(
|
|
64
|
+
modelData: unknown,
|
|
65
|
+
keys: ObjectKeys,
|
|
66
|
+
): Record<string, unknown>[] {
|
|
67
|
+
if (!Array.isArray(modelData)) return [];
|
|
68
|
+
return modelData
|
|
69
|
+
.filter((item) => item !== null && typeof item === "object")
|
|
70
|
+
.map((item) => {
|
|
71
|
+
const obj = item as Record<string, unknown>;
|
|
72
|
+
return {
|
|
73
|
+
[keys.value]: obj.value,
|
|
74
|
+
[keys.label]: obj.label,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Order-insensitive equality of two object-shape selections by the
|
|
81
|
+
* identifier property. Used to short-circuit redundant `handleChange`
|
|
82
|
+
* calls when the model emits the same selection under reference inequality.
|
|
83
|
+
*/
|
|
84
|
+
export function sameObjectSet(
|
|
85
|
+
a: unknown,
|
|
86
|
+
b: unknown,
|
|
87
|
+
identifierKey: string,
|
|
88
|
+
): boolean {
|
|
89
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const ids = new Set(
|
|
93
|
+
b
|
|
94
|
+
.filter((x) => x !== null && typeof x === "object")
|
|
95
|
+
.map((x) => (x as Record<string, unknown>)[identifierKey]),
|
|
96
|
+
);
|
|
97
|
+
return a.every((x) => {
|
|
98
|
+
if (x === null || typeof x !== "object") return false;
|
|
99
|
+
return ids.has((x as Record<string, unknown>)[identifierKey]);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve the items schema of an array control, dereferencing `items.$ref`
|
|
105
|
+
* against the root if present. Returns `undefined` if no items schema exists.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveItemsSchema(
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
arraySchema: Record<string, any> | undefined,
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
rootSchema: Record<string, any>,
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
): Record<string, any> | undefined {
|
|
114
|
+
if (!arraySchema || typeof arraySchema !== "object") return undefined;
|
|
115
|
+
const items = arraySchema.items;
|
|
116
|
+
if (!items || typeof items !== "object" || Array.isArray(items)) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return deref(items, rootSchema);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Infer `objectKeys` from a resolved items schema when the consumer hasn't
|
|
124
|
+
* specified them on the uischema.
|
|
125
|
+
*
|
|
126
|
+
* Strategy: look at `items.required`. If it has exactly two entries, the
|
|
127
|
+
* one whose property has `format: 'uuid'` becomes `value`; the other
|
|
128
|
+
* becomes `label`. If neither has a uuid format, the first entry is
|
|
129
|
+
* `value`, the second is `label`. Returns `undefined` (and the renderer
|
|
130
|
+
* throws at mount) for any other shape — explicit `objectKeys` is required.
|
|
131
|
+
*/
|
|
132
|
+
export function inferObjectKeys(
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
|
+
itemsSchema: Record<string, any> | undefined,
|
|
135
|
+
): ObjectKeys | undefined {
|
|
136
|
+
if (!itemsSchema || itemsSchema.type !== "object") return undefined;
|
|
137
|
+
const required = itemsSchema.required;
|
|
138
|
+
if (!Array.isArray(required) || required.length !== 2) return undefined;
|
|
139
|
+
const [a, b] = required as [string, string];
|
|
140
|
+
const props = (itemsSchema.properties ?? {}) as Record<
|
|
141
|
+
string,
|
|
142
|
+
{ format?: string }
|
|
143
|
+
>;
|
|
144
|
+
const aIsUuid = props[a]?.format === "uuid";
|
|
145
|
+
const bIsUuid = props[b]?.format === "uuid";
|
|
146
|
+
if (aIsUuid && !bIsUuid) return { value: a, label: b };
|
|
147
|
+
if (bIsUuid && !aIsUuid) return { value: b, label: a };
|
|
148
|
+
return { value: a, label: b };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve the active `objectKeys` for a control: prefer the explicit
|
|
153
|
+
* `uischema.options.objectKeys`, fall back to schema-driven inference.
|
|
154
|
+
* Returns `undefined` when neither is available; the renderer surfaces a
|
|
155
|
+
* runtime error in that case so the consumer knows to be explicit.
|
|
156
|
+
*/
|
|
157
|
+
export function resolveObjectKeys(
|
|
158
|
+
uischemaOptions: { objectKeys?: { value?: unknown; label?: unknown } } | undefined,
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
itemsSchema: Record<string, any> | undefined,
|
|
161
|
+
): ObjectKeys | undefined {
|
|
162
|
+
const explicit = uischemaOptions?.objectKeys;
|
|
163
|
+
if (
|
|
164
|
+
explicit &&
|
|
165
|
+
typeof explicit.value === "string" &&
|
|
166
|
+
typeof explicit.label === "string"
|
|
167
|
+
) {
|
|
168
|
+
return { value: explicit.value, label: explicit.label };
|
|
169
|
+
}
|
|
170
|
+
return inferObjectKeys(itemsSchema);
|
|
171
|
+
}
|