@jsonforms/core
Version:
Core module of JSON Forms
1,513 lines (1,486 loc) • 94.9 kB
JavaScript
import isEmpty from 'lodash/isEmpty';
import startCase from 'lodash/startCase';
import keys from 'lodash/keys';
import range from 'lodash/range';
import get from 'lodash/get';
import has from 'lodash/has';
import find from 'lodash/find';
import isArray from 'lodash/isArray';
import includes from 'lodash/includes';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import filter from 'lodash/filter';
import isEqual from 'lodash/isEqual';
import merge from 'lodash/merge';
import cloneDeep from 'lodash/cloneDeep';
import setFp from 'lodash/fp/set';
import unsetFp from 'lodash/fp/unset';
import isFunction from 'lodash/isFunction';
import maxBy from 'lodash/maxBy';
import remove from 'lodash/remove';
import endsWith from 'lodash/endsWith';
import last from 'lodash/last';
import reduce from 'lodash/reduce';
import toPairs from 'lodash/toPairs';
import isUndefined from 'lodash/isUndefined';
const ADDITIONAL_PROPERTIES = 'additionalProperties';
const REQUIRED_PROPERTIES = 'required';
const distinct = (properties, discriminator) => {
const known = {};
return properties.filter((item) => {
const discriminatorValue = discriminator(item);
if (Object.prototype.hasOwnProperty.call(known, discriminatorValue)) {
return false;
}
else {
known[discriminatorValue] = true;
return true;
}
});
};
class Gen {
constructor(findOption) {
this.findOption = findOption;
this.schemaObject = (data) => {
const props = this.properties(data);
const schema = {
type: 'object',
properties: props,
additionalProperties: this.findOption(props)(ADDITIONAL_PROPERTIES),
};
const required = this.findOption(props)(REQUIRED_PROPERTIES);
if (required.length > 0) {
schema.required = required;
}
return schema;
};
this.properties = (data) => {
const emptyProps = {};
return Object.keys(data).reduce((acc, propName) => {
acc[propName] = this.property(data[propName]);
return acc;
}, emptyProps);
};
this.property = (data) => {
switch (typeof data) {
case 'string':
return { type: 'string' };
case 'boolean':
return { type: 'boolean' };
case 'number':
if (Number.isInteger(data)) {
return { type: 'integer' };
}
return { type: 'number' };
case 'object':
if (data == null) {
return { type: 'null' };
}
return this.schemaObjectOrArray(data);
default:
return {};
}
};
this.schemaObjectOrArray = (data) => {
if (data instanceof Array) {
return this.schemaArray(data);
}
else {
return this.schemaObject(data);
}
};
this.schemaArray = (data) => {
if (data.length > 0) {
const allProperties = data.map(this.property);
const uniqueProperties = distinct(allProperties, (prop) => JSON.stringify(prop));
if (uniqueProperties.length === 1) {
return {
type: 'array',
items: uniqueProperties[0],
};
}
else {
return {
type: 'array',
items: {
oneOf: uniqueProperties,
},
};
}
}
else {
return {
type: 'array',
items: {},
};
}
};
}
}
const generateJsonSchema = (
instance, options = {}) => {
const findOption = (props) => (optionName) => {
switch (optionName) {
case ADDITIONAL_PROPERTIES:
if (Object.prototype.hasOwnProperty.call(options, ADDITIONAL_PROPERTIES)) {
return options[ADDITIONAL_PROPERTIES];
}
return true;
case REQUIRED_PROPERTIES:
if (Object.prototype.hasOwnProperty.call(options, REQUIRED_PROPERTIES)) {
return options[REQUIRED_PROPERTIES](props);
}
return Object.keys(props);
default:
return;
}
};
const gen = new Gen(findOption);
return gen.schemaObject(instance);
};
const usedIds = new Set();
const makeId = (idBase, iteration) => iteration <= 1 ? idBase : idBase + iteration.toString();
const isUniqueId = (idBase, iteration) => {
const newID = makeId(idBase, iteration);
return !usedIds.has(newID);
};
const createId = (proposedId) => {
if (proposedId === undefined) {
proposedId = 'undefined';
}
let tries = 0;
while (!isUniqueId(proposedId, tries)) {
tries++;
}
const newID = makeId(proposedId, tries);
usedIds.add(newID);
return newID;
};
const removeId = (id) => usedIds.delete(id);
const clearAllIds = () => usedIds.clear();
const compose = (path1, path2) => {
let p1 = path1;
if (!isEmpty(path1) && !isEmpty(path2) && !path2.startsWith('[')) {
p1 = path1 + '.';
}
if (isEmpty(p1)) {
return path2;
}
else if (isEmpty(path2)) {
return p1;
}
else {
return `${p1}${path2}`;
}
};
const toDataPathSegments = (schemaPath) => {
const s = schemaPath
.replace(/(anyOf|allOf|oneOf)\/[\d]+\//g, '')
.replace(/(then|else)\//g, '');
const segments = s.split('/');
const decodedSegments = segments.map(decode);
const startFromRoot = decodedSegments[0] === '#' || decodedSegments[0] === '';
const startIndex = startFromRoot ? 2 : 1;
return range(startIndex, decodedSegments.length, 2).map((idx) => decodedSegments[idx]);
};
const toDataPath = (schemaPath) => {
return toDataPathSegments(schemaPath).join('.');
};
const encode = (segment) => segment?.replace(/~/g, '~0').replace(/\//g, '~1');
const decode = (pointerSegment) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~');
const getPropPath = (path) => {
return `/properties/${path
.split('.')
.map((p) => encode(p))
.join('/properties/')}`;
};
const deriveLabel = (controlElement, schemaElement) => {
if (schemaElement && typeof schemaElement.title === 'string') {
return schemaElement.title;
}
if (typeof controlElement.scope === 'string') {
const ref = controlElement.scope;
const label = decode(ref.substr(ref.lastIndexOf('/') + 1));
return startCase(label);
}
return '';
};
const createCleanLabel = (label) => {
return startCase(label.replace('_', ' '));
};
const createLabelDescriptionFrom = (withLabel, schema) => {
const labelProperty = withLabel.label;
if (typeof labelProperty === 'boolean') {
return labelDescription(deriveLabel(withLabel, schema), labelProperty);
}
if (typeof labelProperty === 'string') {
return labelDescription(labelProperty, true);
}
if (typeof labelProperty === 'object') {
const label = typeof labelProperty.text === 'string'
? labelProperty.text
: deriveLabel(withLabel, schema);
const show = typeof labelProperty.show === 'boolean' ? labelProperty.show : true;
return labelDescription(label, show);
}
return labelDescription(deriveLabel(withLabel, schema), true);
};
const labelDescription = (text, show) => ({
text: text,
show: show,
});
const isObjectSchema$1 = (schema) => {
return schema.properties !== undefined;
};
const isArraySchema = (schema) => {
return schema.type === 'array' && schema.items !== undefined;
};
const resolveData = (instance, dataPath) => {
if (isEmpty(dataPath)) {
return instance;
}
const dataPathSegments = dataPath.split('.');
return dataPathSegments.reduce((curInstance, decodedSegment) => {
if (!curInstance ||
!Object.prototype.hasOwnProperty.call(curInstance, decodedSegment)) {
return undefined;
}
return curInstance[decodedSegment];
}, instance);
};
const findAllRefs = (schema, result = {}, resolveTuples = false) => {
if (isObjectSchema$1(schema)) {
Object.keys(schema.properties).forEach((key) => findAllRefs(schema.properties[key], result));
}
if (isArraySchema(schema)) {
if (Array.isArray(schema.items)) {
if (resolveTuples) {
const items = schema.items;
items.forEach((child) => findAllRefs(child, result));
}
}
else {
findAllRefs(schema.items, result);
}
}
if (Array.isArray(schema.anyOf)) {
const anyOf = schema.anyOf;
anyOf.forEach((child) => findAllRefs(child, result));
}
if (schema.$ref !== undefined) {
result[schema.$ref] = schema;
}
return result;
};
const invalidSegment = (pathSegment) => pathSegment === '#' || pathSegment === undefined || pathSegment === '';
const resolveSchema = (schema, schemaPath, rootSchema) => {
const segments = schemaPath?.split('/').map(decode);
return resolveSchemaWithSegments(schema, segments, rootSchema);
};
const resolveSchemaWithSegments = (schema, pathSegments, rootSchema) => {
if (typeof schema?.$ref === 'string') {
schema = resolveSchema(rootSchema, schema.$ref, rootSchema);
}
if (!pathSegments || pathSegments.length === 0) {
return schema;
}
if (isEmpty(schema)) {
return undefined;
}
const [segment, ...remainingSegments] = pathSegments;
if (invalidSegment(segment)) {
return resolveSchemaWithSegments(schema, remainingSegments, rootSchema);
}
const singleSegmentResolveSchema = get(schema, segment);
const resolvedSchema = resolveSchemaWithSegments(singleSegmentResolveSchema, remainingSegments, rootSchema);
if (resolvedSchema) {
return resolvedSchema;
}
if (segment === 'properties' || segment === 'items') {
let alternativeResolveResult = undefined;
const subSchemas = [].concat(schema.oneOf ?? [], schema.allOf ?? [], schema.anyOf ?? [], schema.then ?? [], schema.else ?? []);
for (const subSchema of subSchemas) {
alternativeResolveResult = resolveSchemaWithSegments(subSchema, [segment, ...remainingSegments], rootSchema);
if (alternativeResolveResult) {
break;
}
}
return alternativeResolveResult;
}
return undefined;
};
const Draft4 = {
id: 'http://json-schema.org/draft-04/schema#',
$schema: 'http://json-schema.org/draft-04/schema#',
description: 'Core schema meta-schema',
definitions: {
schemaArray: {
type: 'array',
minItems: 1,
items: { $ref: '#' },
},
positiveInteger: {
type: 'integer',
minimum: 0,
},
positiveIntegerDefault0: {
allOf: [{ $ref: '#/definitions/positiveInteger' }, { default: 0 }],
},
simpleTypes: {
enum: [
'array',
'boolean',
'integer',
'null',
'number',
'object',
'string',
],
},
stringArray: {
type: 'array',
items: { type: 'string' },
minItems: 1,
uniqueItems: true,
},
},
type: 'object',
properties: {
id: {
type: 'string',
format: 'uri',
},
$schema: {
type: 'string',
format: 'uri',
},
title: {
type: 'string',
},
description: {
type: 'string',
},
default: {},
multipleOf: {
type: 'number',
minimum: 0,
exclusiveMinimum: true,
},
maximum: {
type: 'number',
},
exclusiveMaximum: {
type: 'boolean',
default: false,
},
minimum: {
type: 'number',
},
exclusiveMinimum: {
type: 'boolean',
default: false,
},
maxLength: { $ref: '#/definitions/positiveInteger' },
minLength: { $ref: '#/definitions/positiveIntegerDefault0' },
pattern: {
type: 'string',
format: 'regex',
},
additionalItems: {
anyOf: [{ type: 'boolean' }, { $ref: '#' }],
default: {},
},
items: {
anyOf: [{ $ref: '#' }, { $ref: '#/definitions/schemaArray' }],
default: {},
},
maxItems: { $ref: '#/definitions/positiveInteger' },
minItems: { $ref: '#/definitions/positiveIntegerDefault0' },
uniqueItems: {
type: 'boolean',
default: false,
},
maxProperties: { $ref: '#/definitions/positiveInteger' },
minProperties: { $ref: '#/definitions/positiveIntegerDefault0' },
required: { $ref: '#/definitions/stringArray' },
additionalProperties: {
anyOf: [{ type: 'boolean' }, { $ref: '#' }],
default: {},
},
definitions: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
properties: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
patternProperties: {
type: 'object',
additionalProperties: { $ref: '#' },
default: {},
},
dependencies: {
type: 'object',
additionalProperties: {
anyOf: [{ $ref: '#' }, { $ref: '#/definitions/stringArray' }],
},
},
enum: {
type: 'array',
minItems: 1,
uniqueItems: true,
},
type: {
anyOf: [
{ $ref: '#/definitions/simpleTypes' },
{
type: 'array',
items: { $ref: '#/definitions/simpleTypes' },
minItems: 1,
uniqueItems: true,
},
],
},
allOf: { $ref: '#/definitions/schemaArray' },
anyOf: { $ref: '#/definitions/schemaArray' },
oneOf: { $ref: '#/definitions/schemaArray' },
not: { $ref: '#' },
},
dependencies: {
exclusiveMaximum: ['maximum'],
exclusiveMinimum: ['minimum'],
},
default: {},
};
var RuleEffect;
(function (RuleEffect) {
RuleEffect["HIDE"] = "HIDE";
RuleEffect["SHOW"] = "SHOW";
RuleEffect["ENABLE"] = "ENABLE";
RuleEffect["DISABLE"] = "DISABLE";
})(RuleEffect || (RuleEffect = {}));
const setReadonlyPropertyValue = (value) => (child) => {
if (!child.options) {
child.options = {};
}
child.options.readonly = value;
};
const setReadonly = (uischema) => {
iterateSchema(uischema, setReadonlyPropertyValue(true));
};
const unsetReadonly = (uischema) => {
iterateSchema(uischema, setReadonlyPropertyValue(false));
};
const iterateSchema = (uischema, toApply) => {
if (isEmpty(uischema)) {
return;
}
if (isLayout(uischema)) {
uischema.elements.forEach((child) => iterateSchema(child, toApply));
return;
}
toApply(uischema);
};
const findUiControl = (uiSchema, path) => {
if (isControlElement(uiSchema)) {
if (isScoped(uiSchema) && uiSchema.scope.endsWith(getPropPath(path))) {
return uiSchema;
}
else if (uiSchema.options?.detail) {
return findUiControl(uiSchema.options.detail, path);
}
}
if (isLayout(uiSchema)) {
for (const elem of uiSchema.elements) {
const result = findUiControl(elem, path);
if (result !== undefined)
return result;
}
}
return undefined;
};
const composeWithUi = (scopableUi, path) => {
if (!isScoped(scopableUi)) {
return path ?? '';
}
const segments = toDataPathSegments(scopableUi.scope);
if (isEmpty(segments)) {
return path ?? '';
}
return compose(path, segments.join('.'));
};
const isInternationalized = (element) => typeof element === 'object' &&
element !== null &&
typeof element.i18n === 'string';
const isGroup = (layout) => layout.type === 'Group';
const isLayout = (uischema) => uischema.elements !== undefined;
const isScopable = (obj) => !!obj && typeof obj === 'object';
const isScoped = (obj) => isScopable(obj) && typeof obj.scope === 'string';
const isLabelable = (obj) => !!obj && typeof obj === 'object';
const isLabeled = (obj) => isLabelable(obj) && ['string', 'boolean'].includes(typeof obj.label);
const isControlElement = (uiSchema) => uiSchema.type === 'Control';
const isOrCondition = (condition) => condition.type === 'OR';
const isAndCondition = (condition) => condition.type === 'AND';
const isLeafCondition = (condition) => condition.type === 'LEAF';
const isSchemaCondition = (condition) => has(condition, 'schema');
const isValidateFunctionCondition = (condition) => has(condition, 'validate') &&
typeof condition.validate === 'function';
const getConditionScope = (condition, path) => {
return composeWithUi(condition, path);
};
const evaluateCondition = (data, uischema, condition, path, ajv) => {
if (isAndCondition(condition)) {
return condition.conditions.reduce((acc, cur) => acc && evaluateCondition(data, uischema, cur, path, ajv), true);
}
else if (isOrCondition(condition)) {
return condition.conditions.reduce((acc, cur) => acc || evaluateCondition(data, uischema, cur, path, ajv), false);
}
else if (isLeafCondition(condition)) {
const value = resolveData(data, getConditionScope(condition, path));
return value === condition.expectedValue;
}
else if (isSchemaCondition(condition)) {
const value = resolveData(data, getConditionScope(condition, path));
if (condition.failWhenUndefined && value === undefined) {
return false;
}
return ajv.validate(condition.schema, value);
}
else if (isValidateFunctionCondition(condition)) {
const value = resolveData(data, getConditionScope(condition, path));
const context = {
data: value,
fullData: data,
path,
uischemaElement: uischema,
};
return condition.validate(context);
}
else {
return true;
}
};
const isRuleFulfilled = (uischema, data, path, ajv) => {
const condition = uischema.rule.condition;
return evaluateCondition(data, uischema, condition, path, ajv);
};
const evalVisibility = (uischema, data, path = undefined, ajv) => {
const fulfilled = isRuleFulfilled(uischema, data, path, ajv);
switch (uischema.rule.effect) {
case RuleEffect.HIDE:
return !fulfilled;
case RuleEffect.SHOW:
return fulfilled;
default:
return true;
}
};
const evalEnablement = (uischema, data, path = undefined, ajv) => {
const fulfilled = isRuleFulfilled(uischema, data, path, ajv);
switch (uischema.rule.effect) {
case RuleEffect.DISABLE:
return !fulfilled;
case RuleEffect.ENABLE:
return fulfilled;
default:
return true;
}
};
const hasShowRule = (uischema) => {
if (uischema.rule &&
(uischema.rule.effect === RuleEffect.SHOW ||
uischema.rule.effect === RuleEffect.HIDE)) {
return true;
}
return false;
};
const hasEnableRule = (uischema) => {
if (uischema.rule &&
(uischema.rule.effect === RuleEffect.ENABLE ||
uischema.rule.effect === RuleEffect.DISABLE)) {
return true;
}
return false;
};
const isVisible = (uischema, data, path = undefined, ajv) => {
if (uischema.rule) {
return evalVisibility(uischema, data, path, ajv);
}
return true;
};
const isEnabled = (uischema, data, path = undefined, ajv) => {
if (uischema.rule) {
return evalEnablement(uischema, data, path, ajv);
}
return true;
};
const getFirstPrimitiveProp = (schema) => {
if (schema &&
typeof schema === 'object' &&
'properties' in schema &&
schema.properties) {
return find(Object.keys(schema.properties), (propName) => {
const prop = schema.properties[propName];
return (prop &&
typeof prop === 'object' &&
'type' in prop &&
(prop.type === 'string' ||
prop.type === 'number' ||
prop.type === 'integer'));
});
}
return undefined;
};
const isOneOfEnumSchema = (schema) => !!schema &&
Object.prototype.hasOwnProperty.call(schema, 'oneOf') &&
schema.oneOf &&
schema.oneOf.every((s) => s.const !== undefined);
const isEnumSchema = (schema) => !!schema &&
typeof schema === 'object' &&
(Object.prototype.hasOwnProperty.call(schema, 'enum') ||
Object.prototype.hasOwnProperty.call(schema, 'const'));
const convertDateToString = (date, format) => {
const dateString = date.toISOString();
if (format === 'date-time') {
return dateString;
}
else if (format === 'date') {
return dateString.split('T')[0];
}
else if (format === 'time') {
return dateString.split('T')[1].split('.')[0];
}
return dateString;
};
const convertToValidClassName = (s) => s.replace('#', 'root').replace(new RegExp('/', 'g'), '_');
const hasType = (jsonSchema, expected) => {
return includes(deriveTypes(jsonSchema), expected);
};
const deriveTypes = (jsonSchema) => {
if (isEmpty(jsonSchema)) {
return [];
}
if (!isEmpty(jsonSchema.type) && typeof jsonSchema.type === 'string') {
return [jsonSchema.type];
}
if (isArray(jsonSchema.type)) {
return jsonSchema.type;
}
if (!isEmpty(jsonSchema.properties) ||
!isEmpty(jsonSchema.additionalProperties)) {
return ['object'];
}
if (!isEmpty(jsonSchema.items)) {
return ['array'];
}
if (!isEmpty(jsonSchema.enum)) {
const types = new Set();
jsonSchema.enum.forEach((enumElement) => {
if (typeof enumElement === 'string') {
types.add('string');
}
else {
deriveTypes(enumElement).forEach((type) => types.add(type));
}
});
return Array.from(types);
}
if (!isEmpty(jsonSchema.allOf)) {
const allOfType = find(jsonSchema.allOf, (schema) => deriveTypes(schema).length !== 0);
if (allOfType) {
return deriveTypes(allOfType);
}
}
return [];
};
const Resolve = {
schema: resolveSchema,
data: resolveData,
};
const fromScoped = (scopable) => toDataPathSegments(scopable.scope).join('.');
const Paths = {
compose: compose,
fromScoped,
};
const Runtime = {
isEnabled(uischema, data, ajv) {
return isEnabled(uischema, data, undefined, ajv);
},
isVisible(uischema, data, ajv) {
return isVisible(uischema, data, undefined, ajv);
},
};
const createAjv = (options) => {
const ajv = new Ajv({
allErrors: true,
verbose: true,
strict: false,
addUsedSchema: false,
...options,
});
addFormats(ajv);
return ajv;
};
const validate = (validator, data) => {
if (validator === undefined) {
return [];
}
const valid = validator(data);
if (valid) {
return [];
}
return validator.errors;
};
const defaultDateFormat = 'YYYY-MM-DD';
const defaultTimeFormat = 'HH:mm:ss';
const defaultDateTimeFormat = 'YYYY-MM-DDTHH:mm:ss.sssZ';
const getInvalidProperty = (error) => {
switch (error.keyword) {
case 'required':
case 'dependencies':
return error.params.missingProperty;
case 'additionalProperties':
return error.params.additionalProperty;
default:
return undefined;
}
};
const getControlPath = (error) => {
let controlPath = error.dataPath || error.instancePath || '';
controlPath = controlPath.replace(/\//g, '.');
const invalidProperty = getInvalidProperty(error);
if (invalidProperty !== undefined && !controlPath.endsWith(invalidProperty)) {
controlPath = `${controlPath}.${invalidProperty}`;
}
controlPath = controlPath.replace(/^./, '');
controlPath = decode(controlPath);
return controlPath;
};
const errorsAt = (instancePath, schema, matchPath) => (errors) => {
const combinatorPaths = filter(errors, (error) => error.keyword === 'oneOf' || error.keyword === 'anyOf').map((error) => getControlPath(error));
return filter(errors, (error) => {
if (filteredErrorKeywords.indexOf(error.keyword) !== -1 &&
!isOneOfEnumSchema(error.parentSchema)) {
return false;
}
const controlPath = getControlPath(error);
let result = matchPath(controlPath);
const parentSchema = error.parentSchema;
if (result &&
!isObjectSchema(parentSchema) &&
!isOneOfEnumSchema(parentSchema) &&
combinatorPaths.findIndex((p) => instancePath.startsWith(p)) !== -1) {
result = result && isEqual(parentSchema, schema);
}
return result;
});
};
const isObjectSchema = (schema) => {
return schema?.type === 'object' || !!schema?.properties;
};
const filteredErrorKeywords = [
'additionalProperties',
'allOf',
'anyOf',
'oneOf',
];
const formatErrorMessage = (errors) => {
if (errors === undefined || errors === null) {
return '';
}
return errors.join('\n');
};
const Helpers = {
createLabelDescriptionFrom,
convertToValidClassName,
};
const createLayout = (layoutType) => ({
type: layoutType,
elements: [],
});
const createControlElement = (ref) => ({
type: 'Control',
scope: ref,
});
const wrapInLayoutIfNecessary = (uischema, layoutType) => {
if (!isEmpty(uischema) && !isLayout(uischema)) {
const verticalLayout = createLayout(layoutType);
verticalLayout.elements.push(uischema);
return verticalLayout;
}
return uischema;
};
const addLabel = (layout, labelName) => {
if (!isEmpty(labelName)) {
const fixedLabel = startCase(labelName);
if (isGroup(layout)) {
layout.label = fixedLabel;
}
else {
const label = {
type: 'Label',
text: fixedLabel,
};
layout.elements.push(label);
}
}
};
const isCombinator = (jsonSchema) => {
return (!isEmpty(jsonSchema) &&
(!isEmpty(jsonSchema.oneOf) ||
!isEmpty(jsonSchema.anyOf) ||
!isEmpty(jsonSchema.allOf)));
};
const generateUISchema = (jsonSchema, schemaElements, currentRef, schemaName, layoutType, rootSchema) => {
if (!isEmpty(jsonSchema) && jsonSchema.$ref !== undefined) {
return generateUISchema(resolveSchema(rootSchema, jsonSchema.$ref, rootSchema), schemaElements, currentRef, schemaName, layoutType, rootSchema);
}
if (isCombinator(jsonSchema)) {
const controlObject = createControlElement(currentRef);
schemaElements.push(controlObject);
return controlObject;
}
const types = deriveTypes(jsonSchema);
if (types.length === 0) {
return null;
}
if (types.length > 1) {
const controlObject = createControlElement(currentRef);
schemaElements.push(controlObject);
return controlObject;
}
if (currentRef === '#' && types[0] === 'object') {
const layout = createLayout(layoutType);
schemaElements.push(layout);
if (jsonSchema.properties && keys(jsonSchema.properties).length > 1) {
addLabel(layout, schemaName);
}
if (!isEmpty(jsonSchema.properties)) {
const nextRef = currentRef + '/properties';
Object.keys(jsonSchema.properties).map((propName) => {
let value = jsonSchema.properties[propName];
const ref = `${nextRef}/${encode(propName)}`;
if (value.$ref !== undefined) {
value = resolveSchema(rootSchema, value.$ref, rootSchema);
}
generateUISchema(value, layout.elements, ref, propName, layoutType, rootSchema);
});
}
return layout;
}
switch (types[0]) {
case 'object':
case 'array':
case 'string':
case 'number':
case 'integer':
case 'null':
case 'boolean': {
const controlObject = createControlElement(currentRef);
schemaElements.push(controlObject);
return controlObject;
}
default:
throw new Error('Unknown type: ' + JSON.stringify(jsonSchema));
}
};
const generateDefaultUISchema = (jsonSchema, layoutType = 'VerticalLayout', prefix = '#', rootSchema = jsonSchema) => wrapInLayoutIfNecessary(generateUISchema(jsonSchema, [], prefix, '', layoutType, rootSchema), layoutType);
const Generate = {
jsonSchema: generateJsonSchema,
uiSchema: generateDefaultUISchema,
controlElement: createControlElement,
};
const INIT = 'jsonforms/INIT';
const UPDATE_CORE = 'jsonforms/UPDATE_CORE';
const SET_AJV = 'jsonforms/SET_AJV';
const UPDATE_DATA = 'jsonforms/UPDATE';
const UPDATE_ERRORS = 'jsonforms/UPDATE_ERRORS';
const VALIDATE = 'jsonforms/VALIDATE';
const ADD_RENDERER = 'jsonforms/ADD_RENDERER';
const REMOVE_RENDERER = 'jsonforms/REMOVE_RENDERER';
const ADD_CELL = 'jsonforms/ADD_CELL';
const REMOVE_CELL = 'jsonforms/REMOVE_CELL';
const SET_CONFIG = 'jsonforms/SET_CONFIG';
const ADD_UI_SCHEMA = 'jsonforms/ADD_UI_SCHEMA';
const REMOVE_UI_SCHEMA = 'jsonforms/REMOVE_UI_SCHEMA';
const SET_SCHEMA = 'jsonforms/SET_SCHEMA';
const SET_UISCHEMA = 'jsonforms/SET_UISCHEMA';
const SET_VALIDATION_MODE = 'jsonforms/SET_VALIDATION_MODE';
const SET_LOCALE = 'jsonforms/SET_LOCALE';
const SET_TRANSLATOR = 'jsonforms/SET_TRANSLATOR';
const UPDATE_I18N = 'jsonforms/UPDATE_I18N';
const ADD_DEFAULT_DATA = 'jsonforms/ADD_DEFAULT_DATA';
const REMOVE_DEFAULT_DATA = 'jsonforms/REMOVE_DEFAULT_DATA';
const isUpdateArrayContext = (context) => {
if (!('type' in context)) {
return false;
}
if (typeof context.type !== 'string') {
return false;
}
switch (context.type) {
case 'ADD': {
return ('values' in context &&
Array.isArray(context.values) &&
context.values.length > 0);
}
case 'REMOVE': {
return ('indices' in context &&
Array.isArray(context.indices) &&
context.indices.length > 0 &&
context.indices.every((i) => typeof i === 'number'));
}
case 'MOVE': {
return ('moves' in context &&
Array.isArray(context.moves) &&
context.moves.length > 0 &&
context.moves.every((m) => typeof m === 'object' &&
m !== null &&
'from' in m &&
'to' in m &&
typeof m.from === 'number' &&
typeof m.to === 'number'));
}
default:
return false;
}
};
const init = (data, schema = generateJsonSchema(data), uischema, options) => ({
type: INIT,
data,
schema,
uischema: typeof uischema === 'object' ? uischema : generateDefaultUISchema(schema),
options,
});
const updateCore = (data, schema, uischema, options) => ({
type: UPDATE_CORE,
data,
schema,
uischema,
options,
});
const registerDefaultData = (schemaPath, data) => ({
type: ADD_DEFAULT_DATA,
schemaPath,
data,
});
const unregisterDefaultData = (schemaPath) => ({
type: REMOVE_DEFAULT_DATA,
schemaPath,
});
const setAjv = (ajv) => ({
type: SET_AJV,
ajv,
});
const update = (path, updater, context) => ({
type: UPDATE_DATA,
path,
updater,
context,
});
const updateErrors = (errors) => ({
type: UPDATE_ERRORS,
errors,
});
const registerRenderer = (tester, renderer) => ({
type: ADD_RENDERER,
tester,
renderer,
});
const registerCell = (tester, cell) => ({
type: ADD_CELL,
tester,
cell,
});
const unregisterCell = (tester, cell) => ({
type: REMOVE_CELL,
tester,
cell,
});
const unregisterRenderer = (tester, renderer) => ({
type: REMOVE_RENDERER,
tester,
renderer,
});
const setConfig = (config) => ({
type: SET_CONFIG,
config,
});
const setValidationMode = (validationMode) => ({
type: SET_VALIDATION_MODE,
validationMode,
});
const registerUISchema = (tester, uischema) => {
return {
type: ADD_UI_SCHEMA,
tester,
uischema,
};
};
const unregisterUISchema = (tester) => {
return {
type: REMOVE_UI_SCHEMA,
tester,
};
};
const setLocale = (locale) => ({
type: SET_LOCALE,
locale,
});
const setSchema = (schema) => ({
type: SET_SCHEMA,
schema,
});
const setTranslator = (translator, errorTranslator) => ({
type: SET_TRANSLATOR,
translator,
errorTranslator,
});
const updateI18n = (locale, translator, errorTranslator) => ({
type: UPDATE_I18N,
locale,
translator,
errorTranslator,
});
const setUISchema = (uischema) => ({
type: SET_UISCHEMA,
uischema,
});
var index$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
INIT: INIT,
UPDATE_CORE: UPDATE_CORE,
SET_AJV: SET_AJV,
UPDATE_DATA: UPDATE_DATA,
UPDATE_ERRORS: UPDATE_ERRORS,
VALIDATE: VALIDATE,
ADD_RENDERER: ADD_RENDERER,
REMOVE_RENDERER: REMOVE_RENDERER,
ADD_CELL: ADD_CELL,
REMOVE_CELL: REMOVE_CELL,
SET_CONFIG: SET_CONFIG,
ADD_UI_SCHEMA: ADD_UI_SCHEMA,
REMOVE_UI_SCHEMA: REMOVE_UI_SCHEMA,
SET_SCHEMA: SET_SCHEMA,
SET_UISCHEMA: SET_UISCHEMA,
SET_VALIDATION_MODE: SET_VALIDATION_MODE,
SET_LOCALE: SET_LOCALE,
SET_TRANSLATOR: SET_TRANSLATOR,
UPDATE_I18N: UPDATE_I18N,
ADD_DEFAULT_DATA: ADD_DEFAULT_DATA,
REMOVE_DEFAULT_DATA: REMOVE_DEFAULT_DATA,
isUpdateArrayContext: isUpdateArrayContext,
init: init,
updateCore: updateCore,
registerDefaultData: registerDefaultData,
unregisterDefaultData: unregisterDefaultData,
setAjv: setAjv,
update: update,
updateErrors: updateErrors,
registerRenderer: registerRenderer,
registerCell: registerCell,
unregisterCell: unregisterCell,
unregisterRenderer: unregisterRenderer,
setConfig: setConfig,
setValidationMode: setValidationMode,
registerUISchema: registerUISchema,
unregisterUISchema: unregisterUISchema,
setLocale: setLocale,
setSchema: setSchema,
setTranslator: setTranslator,
updateI18n: updateI18n,
setUISchema: setUISchema
});
const cellReducer = (state = [], { type, tester, cell }) => {
switch (type) {
case ADD_CELL:
return state.concat([{ tester, cell }]);
case REMOVE_CELL:
return state.filter((t) => t.tester !== tester);
default:
return state;
}
};
const configDefault = {
restrict: false,
trim: false,
showUnfocusedDescription: false,
hideRequiredAsterisk: false,
};
const applyDefaultConfiguration = (config = {}) => merge({}, configDefault, config);
const configReducer = (state = applyDefaultConfiguration(), action) => {
switch (action.type) {
case SET_CONFIG:
return applyDefaultConfiguration(action.config);
default:
return state;
}
};
const initState = {
data: {},
schema: {},
uischema: undefined,
errors: [],
validator: undefined,
ajv: undefined,
validationMode: 'ValidateAndShow',
additionalErrors: [],
};
const getValidationMode = (state, action) => {
if (action && hasValidationModeOption(action.options)) {
return action.options.validationMode;
}
return state.validationMode;
};
const hasValidationModeOption = (option) => {
if (option) {
return option.validationMode !== undefined;
}
return false;
};
const hasAdditionalErrorsOption = (option) => {
if (option) {
return option.additionalErrors !== undefined;
}
return false;
};
const getAdditionalErrors = (state, action) => {
if (action && hasAdditionalErrorsOption(action.options)) {
return action.options.additionalErrors;
}
return state.additionalErrors;
};
const getOrCreateAjv = (state, action) => {
if (action) {
if (hasAjvOption(action.options)) {
return action.options.ajv;
}
else if (action.options !== undefined) {
if (isFunction(action.options.compile)) {
return action.options;
}
}
}
return state.ajv ? state.ajv : createAjv();
};
const hasAjvOption = (option) => {
if (option) {
return option.ajv !== undefined;
}
return false;
};
const coreReducer = (state = initState, action) => {
switch (action.type) {
case INIT: {
const thisAjv = getOrCreateAjv(state, action);
const validationMode = getValidationMode(state, action);
const v = validationMode === 'NoValidation'
? undefined
: thisAjv.compile(action.schema);
const e = validate(v, action.data);
const additionalErrors = getAdditionalErrors(state, action);
return {
...state,
data: action.data,
schema: action.schema,
uischema: action.uischema,
additionalErrors,
errors: e,
validator: v,
ajv: thisAjv,
validationMode,
};
}
case UPDATE_CORE: {
const thisAjv = getOrCreateAjv(state, action);
const validationMode = getValidationMode(state, action);
let validator = state.validator;
let errors = state.errors;
if (state.schema !== action.schema ||
state.validationMode !== validationMode ||
state.ajv !== thisAjv) {
validator =
validationMode === 'NoValidation'
? undefined
: thisAjv.compile(action.schema);
errors = validate(validator, action.data);
}
else if (state.data !== action.data) {
errors = validate(validator, action.data);
}
const additionalErrors = getAdditionalErrors(state, action);
const stateChanged = state.data !== action.data ||
state.schema !== action.schema ||
state.uischema !== action.uischema ||
state.ajv !== thisAjv ||
state.errors !== errors ||
state.validator !== validator ||
state.validationMode !== validationMode ||
state.additionalErrors !== additionalErrors;
return stateChanged
? {
...state,
data: action.data,
schema: action.schema,
uischema: action.uischema,
ajv: thisAjv,
errors: isEqual(errors, state.errors) ? state.errors : errors,
validator: validator,
validationMode: validationMode,
additionalErrors,
}
: state;
}
case SET_AJV: {
const currentAjv = action.ajv;
const validator = state.validationMode === 'NoValidation'
? undefined
: currentAjv.compile(state.schema);
const errors = validate(validator, state.data);
return {
...state,
validator,
errors,
};
}
case SET_SCHEMA: {
const needsNewValidator = action.schema && state.ajv && state.validationMode !== 'NoValidation';
const v = needsNewValidator
? state.ajv.compile(action.schema)
: state.validator;
const errors = validate(v, state.data);
return {
...state,
validator: v,
schema: action.schema,
errors,
};
}
case SET_UISCHEMA: {
return {
...state,
uischema: action.uischema,
};
}
case UPDATE_DATA: {
if (action.path === undefined || action.path === null) {
return state;
}
else if (action.path === '') {
const result = action.updater(cloneDeep(state.data));
const errors = validate(state.validator, result);
return {
...state,
data: result,
errors,
};
}
else {
const oldData = get(state.data, action.path);
const newData = action.updater(cloneDeep(oldData));
let newState;
if (newData !== undefined) {
newState = setFp(action.path, newData, state.data === undefined ? {} : state.data);
}
else {
newState = unsetFp(action.path, state.data === undefined ? {} : state.data);
}
const errors = validate(state.validator, newState);
return {
...state,
data: newState,
errors,
};
}
}
case UPDATE_ERRORS: {
return {
...state,
errors: action.errors,
};
}
case SET_VALIDATION_MODE: {
if (state.validationMode === action.validationMode) {
return state;
}
if (action.validationMode === 'NoValidation') {
const errors = validate(undefined, state.data);
return {
...state,
errors,
validationMode: action.validationMode,
};
}
if (state.validationMode === 'NoValidation') {
const validator = state.ajv.compile(state.schema);
const errors = validate(validator, state.data);
return {
...state,
validator,
errors,
validationMode: action.validationMode,
};
}
return {
...state,
validationMode: action.validationMode,
};
}
default:
return state;
}
};
const defaultDataReducer = (state = [], action) => {
switch (action.type) {
case ADD_DEFAULT_DATA:
return state.concat([
{ schemaPath: action.schemaPath, data: action.data },
]);
case REMOVE_DEFAULT_DATA:
return state.filter((t) => t.schemaPath !== action.schemaPath);
default:
return state;
}
};
const getDefaultData = (state) => extractDefaultData(get(state, 'jsonforms.defaultData'));
const extractDefaultData = (state) => state;
var ArrayTranslationEnum;
(function (ArrayTranslationEnum) {
ArrayTranslationEnum["addTooltip"] = "addTooltip";
ArrayTranslationEnum["addAriaLabel"] = "addAriaLabel";
ArrayTranslationEnum["removeTooltip"] = "removeTooltip";
ArrayTranslationEnum["upAriaLabel"] = "upAriaLabel";
ArrayTranslationEnum["downAriaLabel"] = "downAriaLabel";
ArrayTranslationEnum["noSelection"] = "noSelection";
ArrayTranslationEnum["removeAriaLabel"] = "removeAriaLabel";
ArrayTranslationEnum["noDataMessage"] = "noDataMessage";
ArrayTranslationEnum["deleteDialogTitle"] = "deleteDialogTitle";
ArrayTranslationEnum["deleteDialogMessage"] = "deleteDialogMessage";
ArrayTranslationEnum["deleteDialogAccept"] = "deleteDialogAccept";
ArrayTranslationEnum["deleteDialogDecline"] = "deleteDialogDecline";
ArrayTranslationEnum["up"] = "up";
ArrayTranslationEnum["down"] = "down";
})(ArrayTranslationEnum || (ArrayTranslationEnum = {}));
const arrayDefaultTranslations = [
{
key: ArrayTranslationEnum.addTooltip,
default: (input) => (input ? `Add to ${input}` : 'Add'),
},
{
key: ArrayTranslationEnum.addAriaLabel,
default: (input) => (input ? `Add to ${input} button` : 'Add button'),
},
{ key: ArrayTranslationEnum.removeTooltip, default: () => 'Delete' },
{ key: ArrayTranslationEnum.removeAriaLabel, default: () => 'Delete button' },
{ key: ArrayTranslationEnum.upAriaLabel, default: () => `Move item up` },
{ key: ArrayTranslationEnum.up, default: () => 'Up' },
{ key: ArrayTranslationEnum.down, default: () => 'Down' },
{ key: ArrayTranslationEnum.downAriaLabel, default: () => `Move item down` },
{ key: ArrayTranslationEnum.noDataMessage, default: () => 'No data' },
{ key: ArrayTranslationEnum.noSelection, default: () => 'No selection' },
{
key: ArrayTranslationEnum.deleteDialogTitle,
default: () => 'Confirm Deletion',
},
{
key: ArrayTranslationEnum.deleteDialogMessage,
default: () => 'Are you sure you want to delete the selected entry?',
},
{ key: ArrayTranslationEnum.deleteDialogAccept, default: () => 'Yes' },
{ key: ArrayTranslationEnum.deleteDialogDecline, default: () => 'No' },
];
var CombinatorTranslationEnum;
(function (CombinatorTranslationEnum) {
CombinatorTranslationEnum["clearDialogTitle"] = "clearDialogTitle";
CombinatorTranslationEnum["clearDialogMessage"] = "clearDialogMessage";
CombinatorTranslationEnum["clearDialogAccept"] = "clearDialogAccept";
CombinatorTranslationEnum["clearDialogDecline"] = "clearDialogDecline";
})(CombinatorTranslationEnum || (CombinatorTranslationEnum = {}));
const combinatorDefaultTranslations = [
{
key: CombinatorTranslationEnum.clearDialogTitle,
default: () => 'Clear form?',
},
{
key: CombinatorTranslationEnum.clearDialogMessage,
default: () => 'Your data will be cleared. Do you want to proceed?',
},
{ key: CombinatorTranslationEnum.clearDialogAccept, default: () => 'Yes' },
{ key: CombinatorTranslationEnum.clearDialogDecline, default: () => 'No' },
];
const getI18nKeyPrefixBySchema = (schema, uischema) => {
if (isInternationalized(uischema)) {
return uischema.i18n;
}
return schema?.i18n ?? undefined;
};
const transformPathToI18nPrefix = (path) => {
return (path
?.split('.')
.filter((segment) => !/^\d+$/.test(segment))
.join('.') || 'root');
};
const getI18nKeyPrefix = (schema, uischema, path) => {
return (getI18nKeyPrefixBySchema(schema, uischema) ??
transformPathToI18nPrefix(path));
};
const getI18nKey = (schema, uischema, path, key) => {
return `${getI18nKeyPrefix(schema, uischema, path)}.${key}`;
};
const addI18nKeyToPrefix = (i18nKeyPrefix, key) => {
return `${i18nKeyPrefix}.${key}`;
};
const defaultTranslator = (_id, defaultMessage) => defaultMessage;
const defaultErrorTranslator = (error, t, uischema) => {
const i18nKey = getI18nKey(error.parentSchema, uischema, getControlPath(error), `error.${error.keyword}`);
const specializedKeywordMessage = t(i18nKey, undefined, { error });
if (specializedKeywordMessage !== undefined) {
return specializedKeywordMessage;
}
const genericKeywordMessage = t(`error.${error.keyword}`, undefined, {
error,
});
if (genericKeywordMessage !== undefined) {
return genericKeywordMessage;
}
const messageCustomization = t(error.message, undefined, { error });
if (messageCustomization !== undefined) {
return messageCustomization;
}
if (error.keyword === 'required' &&
error.message?.startsWith('must have required property')) {
return t('is a required property', 'is a required property', { error });
}
return error.message;
};
const getCombinedErrorMessage = (errors, et, t, schema, uischema, path) => {
if (errors.length > 0 && t) {
const customErrorKey = getI18nKey(schema, uischema, path, 'error.custom');
const specializedErrorMessage = t(customErrorKey, undefined, {
schema,
uischema,
path,
errors,
});
if (specializedErrorMessage !== undefined) {
return specializedErrorMessage;
}
}
return formatErrorMessage(errors.map((error) => et(error, t, uischema)));
};
const deriveLabelForUISchemaElement = (uischema, t) => {
if (uischema.label === false) {
return undefined;
}
if ((uischema.label === undefined ||
uischema.label === null ||
uischema.label === true) &&
!isInternationalized(uischema)) {
return undefined;
}
const stringifiedLabel = typeof uischema.label === 'string'
? uischema.label
: JSON.st