@abgov/jsonforms-components 2.36.2 → 2.37.0
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/index.esm.js
CHANGED
|
@@ -13022,71 +13022,191 @@ function resolveScope(scope, data) {
|
|
|
13022
13022
|
const cleaned = scope.replace(/^#\/(properties\/)?/, '');
|
|
13023
13023
|
const parts = cleaned.split('/').filter(Boolean);
|
|
13024
13024
|
let cur = data;
|
|
13025
|
-
for (const
|
|
13026
|
-
if (cur == null
|
|
13027
|
-
cur
|
|
13025
|
+
for (const part of parts) {
|
|
13026
|
+
if (cur == null) return undefined;
|
|
13027
|
+
if (Array.isArray(cur) && /^\d+$/.test(part)) {
|
|
13028
|
+
cur = cur[Number(part)];
|
|
13029
|
+
} else if (typeof cur === 'object') {
|
|
13030
|
+
if (part === 'properties') continue;
|
|
13031
|
+
cur = cur[part];
|
|
13032
|
+
} else {
|
|
13033
|
+
return undefined;
|
|
13034
|
+
}
|
|
13028
13035
|
}
|
|
13029
13036
|
return cur;
|
|
13030
13037
|
}
|
|
13031
|
-
function
|
|
13032
|
-
if (!scope || typeof scope !== 'string')
|
|
13033
|
-
|
|
13038
|
+
function evaluateSum(scope, data) {
|
|
13039
|
+
if (!scope || typeof scope !== 'string') {
|
|
13040
|
+
return {
|
|
13041
|
+
value: undefined,
|
|
13042
|
+
error: 'SUM() requires a JSON pointer argument.'
|
|
13043
|
+
};
|
|
13044
|
+
}
|
|
13034
13045
|
const cleaned = scope.replace(/^#\/(properties\/)?/, '');
|
|
13035
13046
|
const parts = cleaned.split('/').filter(Boolean);
|
|
13036
|
-
if (parts.length < 2)
|
|
13037
|
-
|
|
13047
|
+
if (parts.length < 2) {
|
|
13048
|
+
return {
|
|
13049
|
+
value: undefined,
|
|
13050
|
+
error: `SUM() pointer "${scope}" must include an array and field name.`
|
|
13051
|
+
};
|
|
13052
|
+
}
|
|
13053
|
+
const fieldKey = parts[parts.length - 1];
|
|
13038
13054
|
const arrPath = parts.slice(0, -1);
|
|
13039
13055
|
let cur = data;
|
|
13040
|
-
for (const
|
|
13041
|
-
if (cur == null)
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13056
|
+
for (const segment of arrPath) {
|
|
13057
|
+
if (cur == null) {
|
|
13058
|
+
cur = undefined;
|
|
13059
|
+
break;
|
|
13060
|
+
}
|
|
13061
|
+
if (Array.isArray(cur) && /^\d+$/.test(segment)) {
|
|
13062
|
+
cur = cur[Number(segment)];
|
|
13063
|
+
} else if (typeof cur === 'object') {
|
|
13064
|
+
if (segment === 'properties') continue;
|
|
13065
|
+
cur = cur[segment];
|
|
13045
13066
|
} else {
|
|
13046
|
-
cur =
|
|
13067
|
+
cur = undefined;
|
|
13068
|
+
break;
|
|
13047
13069
|
}
|
|
13048
13070
|
}
|
|
13049
|
-
if (
|
|
13071
|
+
if (cur == null) {
|
|
13072
|
+
return {
|
|
13073
|
+
value: undefined,
|
|
13074
|
+
error: `Please provide values for: ${scope}`
|
|
13075
|
+
};
|
|
13076
|
+
}
|
|
13077
|
+
if (!Array.isArray(cur)) {
|
|
13078
|
+
return {
|
|
13079
|
+
value: undefined,
|
|
13080
|
+
error: `SUM() pointer "${scope}" does not resolve to an array.`
|
|
13081
|
+
};
|
|
13082
|
+
}
|
|
13083
|
+
const missingIndices = [];
|
|
13084
|
+
const invalidIndices = [];
|
|
13050
13085
|
let sum = 0;
|
|
13051
|
-
for (
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13086
|
+
for (let i = 0; i < cur.length; i++) {
|
|
13087
|
+
const row = cur[i];
|
|
13088
|
+
if (row == null || typeof row !== 'object') {
|
|
13089
|
+
missingIndices.push(i);
|
|
13090
|
+
continue;
|
|
13091
|
+
}
|
|
13092
|
+
const v = row[fieldKey];
|
|
13093
|
+
if (v === undefined || v === null || v === '') {
|
|
13094
|
+
missingIndices.push(i);
|
|
13095
|
+
} else if (typeof v !== 'number' || Number.isNaN(v)) {
|
|
13096
|
+
invalidIndices.push(i);
|
|
13097
|
+
} else {
|
|
13098
|
+
sum += v;
|
|
13099
|
+
}
|
|
13100
|
+
}
|
|
13101
|
+
if (invalidIndices.length > 0) {
|
|
13102
|
+
return {
|
|
13103
|
+
value: undefined,
|
|
13104
|
+
error: `Expected numeric values for: ${scope} at rows [${invalidIndices.join(', ')}]`
|
|
13105
|
+
};
|
|
13056
13106
|
}
|
|
13057
|
-
|
|
13107
|
+
if (missingIndices.length === cur.length) {
|
|
13108
|
+
return {
|
|
13109
|
+
value: undefined,
|
|
13110
|
+
error: `Please provide values for: ${scope}`
|
|
13111
|
+
};
|
|
13112
|
+
}
|
|
13113
|
+
return {
|
|
13114
|
+
value: sum,
|
|
13115
|
+
error: missingIndices.length ? `Some rows are missing values for: ${scope}` : undefined
|
|
13116
|
+
};
|
|
13058
13117
|
}
|
|
13118
|
+
/**
|
|
13119
|
+
* General expression evaluation.
|
|
13120
|
+
* - Supports SUM(#/properties/arr/c3)
|
|
13121
|
+
* - Supports arithmetic with JSON pointers:
|
|
13122
|
+
* "#/properties/x * #/properties/y + #/properties/z"
|
|
13123
|
+
*
|
|
13124
|
+
* Returns { value, error } and NEVER throws.
|
|
13125
|
+
*/
|
|
13059
13126
|
function evaluateExpression(expression, data) {
|
|
13060
|
-
if (!expression || typeof expression !== 'string')
|
|
13127
|
+
if (!expression || typeof expression !== 'string') {
|
|
13128
|
+
return {
|
|
13129
|
+
value: undefined,
|
|
13130
|
+
error: undefined
|
|
13131
|
+
};
|
|
13132
|
+
}
|
|
13061
13133
|
const trimmed = expression.trim();
|
|
13134
|
+
if (!trimmed) return {
|
|
13135
|
+
value: undefined,
|
|
13136
|
+
error: undefined
|
|
13137
|
+
};
|
|
13062
13138
|
const sumMatch = trimmed.match(/^SUM\(\s*['"]?(.+?)['"]?\s*\)$/i);
|
|
13063
13139
|
if (sumMatch) {
|
|
13064
|
-
|
|
13140
|
+
const pointer = sumMatch[1];
|
|
13141
|
+
return evaluateSum(pointer, data);
|
|
13065
13142
|
}
|
|
13066
|
-
// Find unique scope tokens like #/properties/x or #/arr/0/c3
|
|
13067
13143
|
const scopeRegex = /#\/(?:properties\/)?[^\s"')]+/g;
|
|
13068
13144
|
const matches = trimmed.match(scopeRegex) || [];
|
|
13069
13145
|
const uniqueScopes = Array.from(new Set(matches));
|
|
13070
|
-
|
|
13146
|
+
let exprForParse = trimmed;
|
|
13147
|
+
const scopeToVar = new Map();
|
|
13148
|
+
uniqueScopes.forEach((scope, index) => {
|
|
13149
|
+
const varName = `v${index}`;
|
|
13150
|
+
scopeToVar.set(scope, varName);
|
|
13151
|
+
const pattern = new RegExp(`['"]?${escapeRegExp(scope)}['"]?`, 'g');
|
|
13152
|
+
exprForParse = exprForParse.replace(pattern, varName);
|
|
13153
|
+
});
|
|
13154
|
+
let parsed;
|
|
13155
|
+
try {
|
|
13156
|
+
const parser = new Parser();
|
|
13157
|
+
parsed = parser.parse(exprForParse);
|
|
13158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13159
|
+
} catch (e) {
|
|
13160
|
+
return {
|
|
13161
|
+
value: undefined,
|
|
13162
|
+
error: 'Invalid expression syntax'
|
|
13163
|
+
};
|
|
13164
|
+
}
|
|
13071
13165
|
const vars = {};
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13166
|
+
const missingScopes = [];
|
|
13167
|
+
const invalidTypeScopes = [];
|
|
13168
|
+
for (const scope of uniqueScopes) {
|
|
13075
13169
|
const val = resolveScope(scope, data);
|
|
13076
|
-
if (val === undefined ||
|
|
13077
|
-
|
|
13170
|
+
if (val === undefined || val === null || val === '') {
|
|
13171
|
+
missingScopes.push(scope);
|
|
13172
|
+
continue;
|
|
13173
|
+
}
|
|
13174
|
+
if (typeof val !== 'number' || Number.isNaN(val)) {
|
|
13175
|
+
invalidTypeScopes.push(scope);
|
|
13176
|
+
continue;
|
|
13177
|
+
}
|
|
13178
|
+
const varName = scopeToVar.get(scope);
|
|
13078
13179
|
vars[varName] = val;
|
|
13079
|
-
const pattern = new RegExp(`['"]?${escapeRegExp(scope)}['"]?`, 'g');
|
|
13080
|
-
expr = expr.replace(pattern, varName);
|
|
13081
13180
|
}
|
|
13082
|
-
|
|
13181
|
+
if (invalidTypeScopes.length > 0) {
|
|
13182
|
+
return {
|
|
13183
|
+
value: undefined,
|
|
13184
|
+
error: `Expected numeric values for: ${invalidTypeScopes.join(', ')}`
|
|
13185
|
+
};
|
|
13186
|
+
}
|
|
13187
|
+
if (missingScopes.length > 0) {
|
|
13188
|
+
return {
|
|
13189
|
+
value: undefined,
|
|
13190
|
+
error: `Please provide values for: ${missingScopes.join(', ')}`
|
|
13191
|
+
};
|
|
13192
|
+
}
|
|
13083
13193
|
try {
|
|
13084
|
-
const parser = new Parser();
|
|
13085
|
-
const parsed = parser.parse(expr);
|
|
13086
13194
|
const result = parsed.evaluate(vars);
|
|
13087
|
-
|
|
13195
|
+
if (typeof result === 'number' && Number.isFinite(result)) {
|
|
13196
|
+
return {
|
|
13197
|
+
value: result,
|
|
13198
|
+
error: undefined
|
|
13199
|
+
};
|
|
13200
|
+
}
|
|
13201
|
+
return {
|
|
13202
|
+
value: undefined,
|
|
13203
|
+
error: 'Expression did not produce a numeric result'
|
|
13204
|
+
};
|
|
13088
13205
|
} catch (_a) {
|
|
13089
|
-
return
|
|
13206
|
+
return {
|
|
13207
|
+
value: undefined,
|
|
13208
|
+
error: 'Error while evaluating expression'
|
|
13209
|
+
};
|
|
13090
13210
|
}
|
|
13091
13211
|
}
|
|
13092
13212
|
|
|
@@ -13094,29 +13214,44 @@ const GoACalculation = props => {
|
|
|
13094
13214
|
var _a;
|
|
13095
13215
|
const {
|
|
13096
13216
|
uischema,
|
|
13097
|
-
data,
|
|
13098
13217
|
schema,
|
|
13099
13218
|
path,
|
|
13100
13219
|
id,
|
|
13101
13220
|
visible,
|
|
13102
13221
|
handleChange
|
|
13103
13222
|
} = props;
|
|
13104
|
-
const expression = schema === null || schema === void 0 ? void 0 : schema.description;
|
|
13105
13223
|
const label = typeof (uischema === null || uischema === void 0 ? void 0 : uischema.label) === 'string' ? uischema.label : undefined;
|
|
13224
|
+
const expression = schema === null || schema === void 0 ? void 0 : schema.description;
|
|
13106
13225
|
const {
|
|
13107
13226
|
core
|
|
13108
13227
|
} = useJsonForms();
|
|
13109
13228
|
const rootData = (_a = core === null || core === void 0 ? void 0 : core.data) !== null && _a !== void 0 ? _a : {};
|
|
13110
|
-
const
|
|
13111
|
-
|
|
13229
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
13230
|
+
const prevDataRef = useRef(rootData);
|
|
13231
|
+
useEffect(() => {
|
|
13232
|
+
setHasInteracted(was => was ? true : true);
|
|
13233
|
+
}, [rootData]);
|
|
13234
|
+
useEffect(() => {
|
|
13235
|
+
if (prevDataRef.current !== rootData) {
|
|
13236
|
+
setHasInteracted(true);
|
|
13237
|
+
prevDataRef.current = rootData;
|
|
13238
|
+
}
|
|
13239
|
+
}, [rootData]);
|
|
13240
|
+
const {
|
|
13241
|
+
value: computedValue,
|
|
13242
|
+
error
|
|
13243
|
+
} = evaluateExpression(expression, rootData);
|
|
13244
|
+
useEffect(() => {
|
|
13112
13245
|
if (computedValue !== undefined && typeof handleChange === 'function' && path) {
|
|
13113
13246
|
handleChange(path, computedValue);
|
|
13114
13247
|
}
|
|
13115
13248
|
}, [computedValue, handleChange, path]);
|
|
13249
|
+
const showError = hasInteracted && !!error;
|
|
13116
13250
|
return jsx(Visible, {
|
|
13117
13251
|
visible: visible,
|
|
13118
13252
|
children: jsx(GoAFormItem, {
|
|
13119
13253
|
label: label,
|
|
13254
|
+
error: showError ? error : '',
|
|
13120
13255
|
children: jsx(GoAInput, {
|
|
13121
13256
|
name: `computed-input-${id}`,
|
|
13122
13257
|
testId: `computed-input-${id}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abgov/jsonforms-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.37.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Government of Alberta - React renderers for JSON Forms based on the design system.",
|
|
6
6
|
"repository": "https://github.com/GovAlta/adsp-monorepo",
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export interface EvaluationResult {
|
|
2
|
+
value?: number;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
1
5
|
export declare function resolveScope(scope: string, data: unknown): unknown;
|
|
2
|
-
export declare function
|
|
3
|
-
|
|
6
|
+
export declare function evaluateSum(scope: string | undefined, data: unknown): EvaluationResult;
|
|
7
|
+
/**
|
|
8
|
+
* General expression evaluation.
|
|
9
|
+
* - Supports SUM(#/properties/arr/c3)
|
|
10
|
+
* - Supports arithmetic with JSON pointers:
|
|
11
|
+
* "#/properties/x * #/properties/y + #/properties/z"
|
|
12
|
+
*
|
|
13
|
+
* Returns { value, error } and NEVER throws.
|
|
14
|
+
*/
|
|
15
|
+
export declare function evaluateExpression(expression: string | undefined, data: unknown): EvaluationResult;
|