@narrative.io/jsonforms-provider-protocols 3.0.0-beta.2 → 3.0.0-beta.21
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/core/initFormData.d.ts +17 -0
- package/dist/core/initFormData.d.ts.map +1 -0
- package/dist/core/initFormData.js +99 -0
- package/dist/core/initFormData.js.map +1 -0
- package/dist/core/projection.d.ts +4 -0
- package/dist/core/projection.d.ts.map +1 -1
- package/dist/core/projection.js +17 -14
- package/dist/core/projection.js.map +1 -1
- package/dist/core/refs.d.ts +58 -0
- package/dist/core/refs.d.ts.map +1 -0
- package/dist/core/refs.js +70 -0
- package/dist/core/refs.js.map +1 -0
- package/dist/core/resolveScope.d.ts +6 -0
- package/dist/core/resolveScope.d.ts.map +1 -1
- package/dist/core/resolveScope.js +14 -8
- package/dist/core/resolveScope.js.map +1 -1
- package/dist/core/transforms.d.ts.map +1 -1
- package/dist/core/transforms.js +3 -1
- package/dist/core/transforms.js.map +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/jsonforms-provider-protocols.css +2 -2
- package/dist/no-eval-ajv.d.ts +70 -0
- package/dist/no-eval-ajv.d.ts.map +1 -0
- package/dist/no-eval-ajv.js +247 -0
- package/dist/no-eval-ajv.js.map +1 -0
- package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderAutocomplete.vue.js +10 -6
- package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue2.js +17 -9
- package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
- package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderSelect.vue.js +1 -1
- package/dist/vue/components/ProviderSelect.vue2.js +19 -9
- package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
- package/dist/vue/composables/useDataLayer.d.ts +1 -0
- package/dist/vue/composables/useDataLayer.d.ts.map +1 -1
- package/dist/vue/composables/useDataLayer.js +1 -0
- package/dist/vue/composables/useDataLayer.js.map +1 -1
- package/dist/vue/composables/useDerive.d.ts +1 -1
- package/dist/vue/composables/useDerive.d.ts.map +1 -1
- package/dist/vue/composables/useDerive.js +19 -2
- package/dist/vue/composables/useDerive.js.map +1 -1
- package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
- package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
- package/dist/vue/composables/useDeriveInitialValue.js +125 -0
- package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
- package/dist/vue/composables/useDirtyValidation.d.ts +3 -3
- package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -1
- package/dist/vue/composables/useDirtyValidation.js +2 -2
- package/dist/vue/composables/useDirtyValidation.js.map +1 -1
- package/dist/vue/composables/useProjection.d.ts +7 -0
- package/dist/vue/composables/useProjection.d.ts.map +1 -1
- package/dist/vue/composables/useProjection.js +87 -4
- package/dist/vue/composables/useProjection.js.map +1 -1
- package/dist/vue/composables/useProvider.d.ts +2 -2
- package/dist/vue/composables/useProvider.d.ts.map +1 -1
- package/dist/vue/composables/useProvider.js +14 -10
- package/dist/vue/composables/useProvider.js.map +1 -1
- package/dist/vue/index.d.ts +2 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +30 -10
- 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 +17 -6
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.js +22 -10
- package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.js +20 -10
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.js +18 -10
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfText.vue.js +27 -12
- package/dist/vue/primevue/JfText.vue.js.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.js +15 -9
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
- package/dist/vue/primevue/index.d.ts.map +1 -1
- package/dist/vue/primevue/index.js +93 -16
- package/dist/vue/primevue/index.js.map +1 -1
- package/dist/vue/utils/autoSelect.js.map +1 -1
- package/dist/vue/utils/placeholder.d.ts +17 -0
- package/dist/vue/utils/placeholder.d.ts.map +1 -0
- package/dist/vue/utils/placeholder.js +17 -0
- package/dist/vue/utils/placeholder.js.map +1 -0
- package/package.json +10 -2
- package/src/core/initFormData.ts +208 -0
- package/src/core/projection.ts +33 -22
- package/src/core/refs.ts +166 -0
- package/src/core/resolveScope.ts +23 -8
- package/src/core/transforms.ts +33 -6
- package/src/core/types.ts +1 -0
- package/src/index.ts +14 -2
- package/src/no-eval-ajv.ts +381 -0
- package/src/vue/components/ProviderAutocomplete.vue +9 -7
- package/src/vue/components/ProviderMultiSelect.vue +20 -15
- package/src/vue/components/ProviderSelect.vue +21 -14
- package/src/vue/composables/useDataLayer.ts +1 -1
- package/src/vue/composables/useDerive.ts +46 -3
- package/src/vue/composables/useDeriveInitialValue.ts +195 -0
- package/src/vue/composables/useDirtyValidation.ts +8 -3
- package/src/vue/composables/useProjection.ts +172 -1
- package/src/vue/composables/useProvider.ts +28 -11
- package/src/vue/index.ts +28 -9
- package/src/vue/primevue/JfBoolean.vue +10 -5
- package/src/vue/primevue/JfEnum.vue +23 -14
- package/src/vue/primevue/JfEnumArray.vue +22 -17
- package/src/vue/primevue/JfNumber.vue +20 -12
- package/src/vue/primevue/JfText.vue +31 -16
- package/src/vue/primevue/JfTextArea.vue +15 -13
- package/src/vue/primevue/index.ts +104 -23
- package/src/vue/styles.css +26 -1
- package/src/vue/utils/autoSelect.ts +2 -2
- package/src/vue/utils/placeholder.ts +42 -0
package/src/core/resolveScope.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { deepDeref, deref, tryCombinatorBranches } from "./refs";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Resolve a JSON Forms scope path to its schema within a root schema.
|
|
3
5
|
* Handles nested paths like "#/properties/parent/properties/child".
|
|
@@ -6,6 +8,12 @@
|
|
|
6
8
|
* - "properties" segments navigate into object `.properties`
|
|
7
9
|
* - "items" segments navigate into array `.items`
|
|
8
10
|
* - all other segments index directly into the current object
|
|
11
|
+
*
|
|
12
|
+
* Dereferences `$ref` nodes transparently at every step, and falls through
|
|
13
|
+
* to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
|
|
14
|
+
* directly — picks the first branch that satisfies the navigation. The
|
|
15
|
+
* returned schema is deep-dereferenced so downstream walkers can operate on
|
|
16
|
+
* a self-contained sub-schema without needing the original root.
|
|
9
17
|
*/
|
|
10
18
|
export function resolveScopeSchema(
|
|
11
19
|
scope: string,
|
|
@@ -17,23 +25,30 @@ export function resolveScopeSchema(
|
|
|
17
25
|
|
|
18
26
|
// Remove the leading "#/" and split into segments
|
|
19
27
|
const path = scope.replace(/^#\/?/, "");
|
|
20
|
-
if (!path) return rootSchema;
|
|
28
|
+
if (!path) return deepDeref(rootSchema, rootSchema);
|
|
21
29
|
|
|
22
30
|
const segments = path.split("/");
|
|
23
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
32
|
let current: any = rootSchema;
|
|
25
33
|
|
|
26
34
|
for (const segment of segments) {
|
|
35
|
+
current = deref(current, rootSchema);
|
|
27
36
|
if (!current || typeof current !== "object") return undefined;
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const navigate = (node: Record<string, any>): unknown => {
|
|
40
|
+
if (segment === "properties") return node.properties;
|
|
41
|
+
if (segment === "items") return node.items;
|
|
42
|
+
return node[segment];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let next = navigate(current);
|
|
46
|
+
if (next === undefined) {
|
|
47
|
+
next = tryCombinatorBranches(current, rootSchema, navigate);
|
|
35
48
|
}
|
|
49
|
+
if (next === undefined) return undefined;
|
|
50
|
+
current = next;
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
return current;
|
|
53
|
+
return deepDeref(current, rootSchema);
|
|
39
54
|
}
|
package/src/core/transforms.ts
CHANGED
|
@@ -14,7 +14,16 @@ export interface FlattenTransform extends Transform {
|
|
|
14
14
|
labelFormat?: string; // Optional format string like "{parent.name} → {name}"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export type FilterOperator =
|
|
17
|
+
export type FilterOperator =
|
|
18
|
+
| "eq"
|
|
19
|
+
| "neq"
|
|
20
|
+
| "empty"
|
|
21
|
+
| "notEmpty"
|
|
22
|
+
| "gt"
|
|
23
|
+
| "gte"
|
|
24
|
+
| "lt"
|
|
25
|
+
| "lte"
|
|
26
|
+
| "contains";
|
|
18
27
|
|
|
19
28
|
export interface FilterCondition {
|
|
20
29
|
key: string;
|
|
@@ -153,13 +162,29 @@ function evaluateCondition(
|
|
|
153
162
|
case "notEmpty":
|
|
154
163
|
return !isEmpty(value);
|
|
155
164
|
case "gt":
|
|
156
|
-
return
|
|
165
|
+
return (
|
|
166
|
+
typeof value === "number" &&
|
|
167
|
+
condition.values !== undefined &&
|
|
168
|
+
value > (condition.values[0] as number)
|
|
169
|
+
);
|
|
157
170
|
case "gte":
|
|
158
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
typeof value === "number" &&
|
|
173
|
+
condition.values !== undefined &&
|
|
174
|
+
value >= (condition.values[0] as number)
|
|
175
|
+
);
|
|
159
176
|
case "lt":
|
|
160
|
-
return
|
|
177
|
+
return (
|
|
178
|
+
typeof value === "number" &&
|
|
179
|
+
condition.values !== undefined &&
|
|
180
|
+
value < (condition.values[0] as number)
|
|
181
|
+
);
|
|
161
182
|
case "lte":
|
|
162
|
-
return
|
|
183
|
+
return (
|
|
184
|
+
typeof value === "number" &&
|
|
185
|
+
condition.values !== undefined &&
|
|
186
|
+
value <= (condition.values[0] as number)
|
|
187
|
+
);
|
|
163
188
|
case "contains":
|
|
164
189
|
if (typeof value === "string" && condition.values) {
|
|
165
190
|
return condition.values.some((v) => value.includes(String(v)));
|
|
@@ -208,7 +233,9 @@ function filterTransform(items: unknown[], config: Transform): unknown[] {
|
|
|
208
233
|
return items.filter((item) => {
|
|
209
234
|
if (typeof item !== "object" || item === null) return false;
|
|
210
235
|
const itemObj = item as Record<string, unknown>;
|
|
211
|
-
return conditions.every((condition) =>
|
|
236
|
+
return conditions.every((condition) =>
|
|
237
|
+
evaluateCondition(itemObj, condition),
|
|
238
|
+
);
|
|
212
239
|
});
|
|
213
240
|
}
|
|
214
241
|
|
package/src/core/types.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,8 +3,6 @@ import { cache as globalCache } from "./core/cache";
|
|
|
3
3
|
import { registry as globalRegistry } from "./core/registry";
|
|
4
4
|
import type { Protocol, AuthConfig } from "./core/types";
|
|
5
5
|
|
|
6
|
-
console.log("[jsonforms-provider-protocols] loaded v2.10.0 (symlink)");
|
|
7
|
-
|
|
8
6
|
export { cache } from "./core/cache";
|
|
9
7
|
export * from "./core/jsonpath";
|
|
10
8
|
export { registry } from "./core/registry";
|
|
@@ -14,10 +12,24 @@ export * from "./core/projection";
|
|
|
14
12
|
export * from "./core/resolveScope";
|
|
15
13
|
// Core exports
|
|
16
14
|
export * from "./core/types";
|
|
15
|
+
export { initFormDataFromSchema } from "./core/initFormData";
|
|
17
16
|
|
|
18
17
|
// Protocol exports
|
|
19
18
|
export { RestApiProtocol } from "./protocols/rest_api";
|
|
20
19
|
|
|
20
|
+
// CSP-safe validator
|
|
21
|
+
export {
|
|
22
|
+
createNoEvalAjv,
|
|
23
|
+
transformUnit,
|
|
24
|
+
transformErrors,
|
|
25
|
+
} from "./no-eval-ajv";
|
|
26
|
+
export type {
|
|
27
|
+
NoEvalAjv,
|
|
28
|
+
NoEvalErrorObject,
|
|
29
|
+
NoEvalValidateFunction,
|
|
30
|
+
CreateNoEvalAjvOptions,
|
|
31
|
+
} from "./no-eval-ajv";
|
|
32
|
+
|
|
21
33
|
// Vue exports - using named imports to avoid potential bundling issues
|
|
22
34
|
export {
|
|
23
35
|
providerRenderers,
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Validator,
|
|
3
|
+
type OutputUnit,
|
|
4
|
+
type Schema,
|
|
5
|
+
type SchemaDraft,
|
|
6
|
+
} from "@cfworker/json-schema";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CSP-safe JSON Schema validation backed by `@cfworker/json-schema`.
|
|
10
|
+
*
|
|
11
|
+
* Cloudflare Pages and similar strict-CSP environments forbid `new Function`,
|
|
12
|
+
* which is how AJV compiles validators. This factory returns an
|
|
13
|
+
* AJV-shaped facade whose `compile()` produces a synchronous validator
|
|
14
|
+
* function `(data) => boolean` with a mutable `errors` property — enough
|
|
15
|
+
* for `<JsonForms :ajv="..." />` to drop in without any other changes.
|
|
16
|
+
*
|
|
17
|
+
* Only `compile()` is functional; the rest of the surface (`addSchema`,
|
|
18
|
+
* `getSchema`, `removeSchema`, `addFormat`, `addKeyword`, `opts`) exists
|
|
19
|
+
* as no-ops for forward compatibility with plugins that probe the AJV
|
|
20
|
+
* interface but never reach those methods at runtime.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// AJV's ErrorObject shape. We don't import `ajv` here to keep the lib's
|
|
24
|
+
// zero-runtime-deps philosophy intact; the consumer's transitive AJV types
|
|
25
|
+
// are structurally compatible.
|
|
26
|
+
export interface NoEvalErrorObject {
|
|
27
|
+
instancePath: string;
|
|
28
|
+
schemaPath: string;
|
|
29
|
+
keyword: string;
|
|
30
|
+
params: Record<string, unknown>;
|
|
31
|
+
message?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface NoEvalValidateFunction {
|
|
35
|
+
(data: unknown): boolean;
|
|
36
|
+
errors: NoEvalErrorObject[] | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Minimal subset of AJV's surface that JsonForms touches. The factory
|
|
40
|
+
// returns this as `unknown as Ajv` at the call site; consumers cast on
|
|
41
|
+
// import or pass directly to `<JsonForms :ajv="..." />`.
|
|
42
|
+
export interface NoEvalAjv {
|
|
43
|
+
compile: (schema: unknown) => NoEvalValidateFunction;
|
|
44
|
+
addSchema: (schema: unknown, key?: string) => NoEvalAjv;
|
|
45
|
+
getSchema: (key: string) => NoEvalValidateFunction | undefined;
|
|
46
|
+
removeSchema: (schemaKeyRef?: unknown) => NoEvalAjv;
|
|
47
|
+
addFormat: (name: string, format: unknown) => NoEvalAjv;
|
|
48
|
+
addKeyword: (definition: unknown) => NoEvalAjv;
|
|
49
|
+
opts: Readonly<Record<string, unknown>>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CreateNoEvalAjvOptions {
|
|
53
|
+
/**
|
|
54
|
+
* JSON Schema draft to validate against. Defaults to `'2020-12'` to match
|
|
55
|
+
* the spec; schemas authored against draft-07 should pass `'7'`.
|
|
56
|
+
*/
|
|
57
|
+
draft?: SchemaDraft;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const KEYWORD_WRAPPERS = new Set([
|
|
61
|
+
"properties",
|
|
62
|
+
"items",
|
|
63
|
+
"prefixItems",
|
|
64
|
+
"$ref",
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
const QUOTED_RE = /(['"])((?:\\.|(?!\1).)*?)\1/g;
|
|
68
|
+
|
|
69
|
+
function findQuoted(s: string): string[] {
|
|
70
|
+
const out: string[] = [];
|
|
71
|
+
let m: RegExpExecArray | null;
|
|
72
|
+
QUOTED_RE.lastIndex = 0;
|
|
73
|
+
while ((m = QUOTED_RE.exec(s)) !== null) {
|
|
74
|
+
out.push(m[2]!);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function firstQuoted(s: string): string | undefined {
|
|
80
|
+
const all = findQuoted(s);
|
|
81
|
+
return all[0];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function allQuoted(s: string): string[] {
|
|
85
|
+
return findQuoted(s);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const NUMBER_RE = /-?\d+(?:\.\d+)?/g;
|
|
89
|
+
|
|
90
|
+
function extractNumbers(s: string): number[] {
|
|
91
|
+
const out: number[] = [];
|
|
92
|
+
let m: RegExpExecArray | null;
|
|
93
|
+
NUMBER_RE.lastIndex = 0;
|
|
94
|
+
while ((m = NUMBER_RE.exec(s)) !== null) {
|
|
95
|
+
out.push(Number(m[0]));
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function firstNumber(s: string): number | undefined {
|
|
101
|
+
const nums = extractNumbers(s);
|
|
102
|
+
return nums[0];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function stripPointerPrefix(p: string): string {
|
|
106
|
+
return p.startsWith("#") ? p.slice(1) : p;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface KeywordTransform {
|
|
110
|
+
params: Record<string, unknown>;
|
|
111
|
+
message: string | undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function transformByKeyword(unit: OutputUnit): KeywordTransform {
|
|
115
|
+
const { keyword, error } = unit;
|
|
116
|
+
|
|
117
|
+
switch (keyword) {
|
|
118
|
+
case "required": {
|
|
119
|
+
const prop = firstQuoted(error);
|
|
120
|
+
return {
|
|
121
|
+
params: { missingProperty: prop ?? "" },
|
|
122
|
+
message: prop
|
|
123
|
+
? `must have required property '${prop}'`
|
|
124
|
+
: "must have required property",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "type": {
|
|
129
|
+
const quoted = allQuoted(error);
|
|
130
|
+
// cfworker emits `Instance type "X" is invalid. Expected "Y".` (single)
|
|
131
|
+
// or `... Expected "Y" or "Z".` (union). Per AJV: `params.type` is the
|
|
132
|
+
// expected type as a string; for unions it's comma-separated.
|
|
133
|
+
const expected = quoted.slice(1).join(",") || quoted[0];
|
|
134
|
+
const message = expected ? `must be ${expected}` : "must be of expected type";
|
|
135
|
+
return {
|
|
136
|
+
params: expected ? { type: expected } : {},
|
|
137
|
+
message,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case "enum": {
|
|
142
|
+
// cfworker error: `Instance does not match any of [...].`
|
|
143
|
+
const match = error.match(/\[.*\]/);
|
|
144
|
+
let allowedValues: unknown[] = [];
|
|
145
|
+
if (match) {
|
|
146
|
+
try {
|
|
147
|
+
allowedValues = JSON.parse(match[0]);
|
|
148
|
+
} catch {
|
|
149
|
+
allowedValues = [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
params: { allowedValues },
|
|
154
|
+
message: "must be equal to one of the allowed values",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case "const":
|
|
159
|
+
return {
|
|
160
|
+
params: {},
|
|
161
|
+
message: "must be equal to constant",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
case "minimum": {
|
|
165
|
+
const limit = extractNumbers(error)[1];
|
|
166
|
+
return {
|
|
167
|
+
params: { comparison: ">=", limit: limit ?? 0 },
|
|
168
|
+
message: `must be >= ${limit ?? 0}`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case "exclusiveMinimum": {
|
|
173
|
+
const limit = extractNumbers(error)[1];
|
|
174
|
+
return {
|
|
175
|
+
params: { comparison: ">", limit: limit ?? 0 },
|
|
176
|
+
message: `must be > ${limit ?? 0}`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case "maximum": {
|
|
181
|
+
const limit = extractNumbers(error)[1];
|
|
182
|
+
return {
|
|
183
|
+
params: { comparison: "<=", limit: limit ?? 0 },
|
|
184
|
+
message: `must be <= ${limit ?? 0}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case "exclusiveMaximum": {
|
|
189
|
+
const limit = extractNumbers(error)[1];
|
|
190
|
+
return {
|
|
191
|
+
params: { comparison: "<", limit: limit ?? 0 },
|
|
192
|
+
message: `must be < ${limit ?? 0}`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case "minLength": {
|
|
197
|
+
const limit = firstNumber(error) ?? 0;
|
|
198
|
+
return {
|
|
199
|
+
params: { limit },
|
|
200
|
+
message: `must NOT have fewer than ${limit} characters`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case "maxLength": {
|
|
205
|
+
const limit = firstNumber(error) ?? 0;
|
|
206
|
+
return {
|
|
207
|
+
params: { limit },
|
|
208
|
+
message: `must NOT have more than ${limit} characters`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case "minItems": {
|
|
213
|
+
const limit = firstNumber(error) ?? 0;
|
|
214
|
+
return {
|
|
215
|
+
params: { limit },
|
|
216
|
+
message: `must NOT have fewer than ${limit} items`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "maxItems": {
|
|
221
|
+
const limit = firstNumber(error) ?? 0;
|
|
222
|
+
return {
|
|
223
|
+
params: { limit },
|
|
224
|
+
message: `must NOT have more than ${limit} items`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case "pattern": {
|
|
229
|
+
const pattern = firstQuoted(error) ?? "";
|
|
230
|
+
return {
|
|
231
|
+
params: { pattern },
|
|
232
|
+
message: `must match pattern "${pattern}"`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case "format": {
|
|
237
|
+
const format = firstQuoted(error) ?? "";
|
|
238
|
+
return {
|
|
239
|
+
params: { format },
|
|
240
|
+
message: `must match format "${format}"`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case "multipleOf": {
|
|
245
|
+
// cfworker emits two numbers: the value and the divisor.
|
|
246
|
+
const nums = extractNumbers(error);
|
|
247
|
+
const multipleOf = nums[1] ?? nums[0] ?? 1;
|
|
248
|
+
return {
|
|
249
|
+
params: { multipleOf },
|
|
250
|
+
message: `must be multiple of ${multipleOf}`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case "uniqueItems":
|
|
255
|
+
return {
|
|
256
|
+
params: {},
|
|
257
|
+
message: "must NOT have duplicate items",
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
case "additionalProperties": {
|
|
261
|
+
const additionalProperty = firstQuoted(error) ?? "";
|
|
262
|
+
return {
|
|
263
|
+
params: { additionalProperty },
|
|
264
|
+
message: `must NOT have additional property '${additionalProperty}'`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case "anyOf":
|
|
269
|
+
return { params: {}, message: "must match a schema in anyOf" };
|
|
270
|
+
|
|
271
|
+
case "oneOf":
|
|
272
|
+
return {
|
|
273
|
+
params: { passingSchemas: null },
|
|
274
|
+
message: "must match exactly one schema in oneOf",
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
case "allOf":
|
|
278
|
+
return { params: {}, message: "must match all schemas in allOf" };
|
|
279
|
+
|
|
280
|
+
case "not":
|
|
281
|
+
return { params: {}, message: "must NOT be valid against schema" };
|
|
282
|
+
|
|
283
|
+
default:
|
|
284
|
+
return { params: {}, message: undefined };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Transform a single cfworker `OutputUnit` into an AJV-compatible
|
|
290
|
+
* `ErrorObject`, or `null` if the unit is a structural wrapper that should
|
|
291
|
+
* be filtered out (`properties` / `items` / `prefixItems` / `$ref`).
|
|
292
|
+
*
|
|
293
|
+
* Exported for unit testing.
|
|
294
|
+
*/
|
|
295
|
+
export function transformUnit(unit: OutputUnit): NoEvalErrorObject | null {
|
|
296
|
+
if (KEYWORD_WRAPPERS.has(unit.keyword)) return null;
|
|
297
|
+
|
|
298
|
+
const { params, message } = transformByKeyword(unit);
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
instancePath: stripPointerPrefix(unit.instanceLocation),
|
|
302
|
+
schemaPath: unit.keywordLocation,
|
|
303
|
+
keyword: unit.keyword,
|
|
304
|
+
params,
|
|
305
|
+
message: message ?? unit.error,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Transform a cfworker `OutputUnit[]` into an AJV-compatible
|
|
311
|
+
* `ErrorObject[]`, with structural wrappers filtered out.
|
|
312
|
+
*
|
|
313
|
+
* Exported for unit testing.
|
|
314
|
+
*/
|
|
315
|
+
export function transformErrors(units: OutputUnit[]): NoEvalErrorObject[] {
|
|
316
|
+
const out: NoEvalErrorObject[] = [];
|
|
317
|
+
for (const unit of units) {
|
|
318
|
+
const transformed = transformUnit(unit);
|
|
319
|
+
if (transformed !== null) out.push(transformed);
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const FROZEN_OPTS: Readonly<Record<string, unknown>> = Object.freeze({
|
|
325
|
+
allErrors: true,
|
|
326
|
+
verbose: true,
|
|
327
|
+
errorDataPath: "",
|
|
328
|
+
strict: false,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Build a CSP-safe AJV-shaped validator backed by `@cfworker/json-schema`.
|
|
333
|
+
*
|
|
334
|
+
* Each `compile(schema)` call constructs a `Validator` and returns a
|
|
335
|
+
* synchronous validator function. The validator function's `errors`
|
|
336
|
+
* property is reassigned on every call, matching AJV's contract.
|
|
337
|
+
*
|
|
338
|
+
* Validators are memoized per-schema via a `WeakMap`, so JsonForms's
|
|
339
|
+
* repeated `compile()` calls on prop changes don't re-parse the schema
|
|
340
|
+
* graph each time.
|
|
341
|
+
*/
|
|
342
|
+
export function createNoEvalAjv(
|
|
343
|
+
options: CreateNoEvalAjvOptions = {},
|
|
344
|
+
): NoEvalAjv {
|
|
345
|
+
const draft: SchemaDraft = options.draft ?? "2020-12";
|
|
346
|
+
const cache = new WeakMap<object, NoEvalValidateFunction>();
|
|
347
|
+
|
|
348
|
+
const compile = (schema: unknown): NoEvalValidateFunction => {
|
|
349
|
+
if (schema && typeof schema === "object") {
|
|
350
|
+
const cached = cache.get(schema as object);
|
|
351
|
+
if (cached) return cached;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const validator = new Validator(schema as Schema | boolean, draft, false);
|
|
355
|
+
|
|
356
|
+
const fn: NoEvalValidateFunction = ((data: unknown) => {
|
|
357
|
+
const result = validator.validate(data);
|
|
358
|
+
fn.errors = result.valid ? null : transformErrors(result.errors);
|
|
359
|
+
return result.valid;
|
|
360
|
+
}) as NoEvalValidateFunction;
|
|
361
|
+
|
|
362
|
+
fn.errors = null;
|
|
363
|
+
|
|
364
|
+
if (schema && typeof schema === "object") {
|
|
365
|
+
cache.set(schema as object, fn);
|
|
366
|
+
}
|
|
367
|
+
return fn;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const facade: NoEvalAjv = {
|
|
371
|
+
compile,
|
|
372
|
+
addSchema: () => facade,
|
|
373
|
+
getSchema: () => undefined,
|
|
374
|
+
removeSchema: () => facade,
|
|
375
|
+
addFormat: () => facade,
|
|
376
|
+
addKeyword: () => facade,
|
|
377
|
+
opts: FROZEN_OPTS,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return facade;
|
|
381
|
+
}
|
|
@@ -12,7 +12,11 @@ const props = defineProps<{
|
|
|
12
12
|
path: string;
|
|
13
13
|
}>();
|
|
14
14
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
15
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
projectedData,
|
|
17
|
+
projectedLabel,
|
|
18
|
+
handleProjectedChange: handleChange,
|
|
19
|
+
} = useProjection(control, rawHandleChange);
|
|
16
20
|
|
|
17
21
|
const binding = computed(() => {
|
|
18
22
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -53,16 +57,14 @@ const onSelect = (event: { value?: { value?: unknown } | unknown }) => {
|
|
|
53
57
|
</script>
|
|
54
58
|
|
|
55
59
|
<template>
|
|
56
|
-
<div class="
|
|
57
|
-
<label v-if="
|
|
58
|
-
|
|
59
|
-
}}</label>
|
|
60
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
60
|
+
<div class="jf-control">
|
|
61
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
62
|
+
<div v-if="control.description" class="jf-description">
|
|
61
63
|
{{ control.description }}
|
|
62
64
|
</div>
|
|
63
65
|
<AutoComplete
|
|
64
66
|
v-model="value"
|
|
65
|
-
class="w-full"
|
|
67
|
+
class="w-full!"
|
|
66
68
|
:suggestions="items"
|
|
67
69
|
option-label="label"
|
|
68
70
|
:placeholder="placeholder"
|
|
@@ -5,6 +5,7 @@ import { computed, inject, watch } from "vue";
|
|
|
5
5
|
import { useProvider } from "../composables/useProvider";
|
|
6
6
|
import { useProjection } from "../composables/useProjection";
|
|
7
7
|
import { shouldAutoSelectMulti } from "../utils/autoSelect";
|
|
8
|
+
import { resolvePlaceholder } from "../utils/placeholder";
|
|
8
9
|
import MultiSelect from "primevue/multiselect";
|
|
9
10
|
|
|
10
11
|
const props = defineProps<{
|
|
@@ -13,7 +14,11 @@ const props = defineProps<{
|
|
|
13
14
|
path: string;
|
|
14
15
|
}>();
|
|
15
16
|
const { control, handleChange: rawHandleChange } = useJsonFormsControl(props);
|
|
16
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
projectedData,
|
|
19
|
+
projectedLabel,
|
|
20
|
+
handleProjectedChange: handleChange,
|
|
21
|
+
} = useProjection(control, rawHandleChange);
|
|
17
22
|
|
|
18
23
|
const binding = computed(() => {
|
|
19
24
|
const provider = control.value.uischema?.options?.provider;
|
|
@@ -41,7 +46,7 @@ const rootData = computed(() => injectedFormData.value || {});
|
|
|
41
46
|
const { items, loading, error } = useProvider(binding, {
|
|
42
47
|
data: rootData, // Pass the reactive reference
|
|
43
48
|
path: control.value.path,
|
|
44
|
-
dependsOnValues: depValues
|
|
49
|
+
dependsOnValues: depValues,
|
|
45
50
|
});
|
|
46
51
|
|
|
47
52
|
// Provider will automatically reload when rootData changes due to reactive cache key
|
|
@@ -55,14 +60,16 @@ watch(
|
|
|
55
60
|
control.value.uischema?.options?.autoSelectSingle === true,
|
|
56
61
|
isLoading,
|
|
57
62
|
items: newItems,
|
|
58
|
-
currentValue: Array.isArray(projectedData.value)
|
|
63
|
+
currentValue: Array.isArray(projectedData.value)
|
|
64
|
+
? projectedData.value
|
|
65
|
+
: [],
|
|
59
66
|
});
|
|
60
67
|
|
|
61
68
|
if (valueToSelect !== null) {
|
|
62
69
|
handleChange(control.value.path, valueToSelect);
|
|
63
70
|
}
|
|
64
71
|
},
|
|
65
|
-
{ immediate: true }
|
|
72
|
+
{ immediate: true },
|
|
66
73
|
);
|
|
67
74
|
|
|
68
75
|
// order-insensitive shallow equality for primitive arrays
|
|
@@ -89,25 +96,23 @@ const value = computed({
|
|
|
89
96
|
|
|
90
97
|
const placeholder = computed(() => {
|
|
91
98
|
if (loading.value) return "Loading…";
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
return resolvePlaceholder(
|
|
100
|
+
control.value.uischema,
|
|
101
|
+
projectedLabel.value,
|
|
102
|
+
"select",
|
|
103
|
+
);
|
|
97
104
|
});
|
|
98
105
|
</script>
|
|
99
106
|
|
|
100
107
|
<template>
|
|
101
|
-
<div class="
|
|
102
|
-
<label v-if="
|
|
103
|
-
|
|
104
|
-
}}</label>
|
|
105
|
-
<div v-if="control.description" class="text-color-secondary text-left">
|
|
108
|
+
<div class="jf-control">
|
|
109
|
+
<label v-if="projectedLabel" class="jf-label">{{ projectedLabel }}</label>
|
|
110
|
+
<div v-if="control.description" class="jf-description">
|
|
106
111
|
{{ control.description }}
|
|
107
112
|
</div>
|
|
108
113
|
<MultiSelect
|
|
109
114
|
v-model="value"
|
|
110
|
-
class="w-full"
|
|
115
|
+
class="w-full!"
|
|
111
116
|
:options="items"
|
|
112
117
|
option-label="label"
|
|
113
118
|
option-value="value"
|