UNPKG

@jsonforms/core

Version:

Core module of JSON Forms

1,513 lines (1,486 loc) 94.9 kB
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