@canard/schema-form
Version:
React-based component library that renders forms based on JSON Schema with plugin system support for validators and UI components
1,285 lines (1,209 loc) • 193 kB
JavaScript
'use strict';
const filter$1 = require('@winglet/common-utils/filter');
const object$1 = require('@winglet/common-utils/object');
const error = require('@winglet/common-utils/error');
const object = require('@winglet/react-utils/object');
const jsxRuntime = require('react/jsx-runtime');
const filter = require('@winglet/json-schema/filter');
const react = require('react');
const array = require('@winglet/common-utils/array');
const hook = require('@winglet/react-utils/hook');
const filter$2 = require('@winglet/react-utils/filter');
const hoc = require('@winglet/react-utils/hoc');
const pointer = require('@winglet/json/pointer');
const _function = require('@winglet/common-utils/function');
const scanner$1 = require('@winglet/json-schema/scanner');
const math = require('@winglet/common-utils/math');
const constant = require('@winglet/common-utils/constant');
const lib = require('@winglet/common-utils/lib');
const scheduler = require('@winglet/common-utils/scheduler');
class JsonSchemaError extends error.BaseError {
constructor(code, message, details = {}) {
super('JSON_SCHEMA_ERROR', code, message, details);
this.name = 'JsonSchemaError';
}
}
const isJsonSchemaError = (error) => error instanceof JsonSchemaError;
class SchemaFormError extends error.BaseError {
constructor(code, message, details = {}) {
super('SCHEMA_FORM_ERROR', code, message, details);
this.name = 'SchemaFormError';
}
}
const isSchemaFormError = (error) => error instanceof SchemaFormError;
class UnhandledError extends error.BaseError {
constructor(code, message, details = {}) {
super('UNHANDLED_ERROR', code, message, details);
this.name = 'UnhandledError';
}
}
const isUnhandledError = (error) => error instanceof UnhandledError;
class ValidationError extends error.BaseError {
constructor(code, message, details = {}) {
super('VALIDATION_ERROR', code, message, details);
this.name = 'ValidationError';
}
}
const isValidationError = (error) => error instanceof ValidationError;
const FormInputRenderer = ({ Input }) => (jsxRuntime.jsx(Input, {}));
const FormGroupRenderer = ({ node, depth, path, name, required, Input, errorMessage, style, className, }) => {
if (depth === 0)
return jsxRuntime.jsx(Input, {});
if (node.group === 'branch') {
return (jsxRuntime.jsxs("fieldset", { style: {
marginBottom: 5,
marginLeft: 5 * depth,
...style,
}, className: className, children: [jsxRuntime.jsx("legend", { children: node.name }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx("em", { style: { fontSize: '0.85em' }, children: errorMessage }) }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx(Input, {}) })] }));
}
else {
return (jsxRuntime.jsxs("div", { style: {
marginBottom: 5,
marginLeft: 5 * depth,
...style,
}, className: className, children: [node.parentNode && filter.isArraySchema(node.parentNode) === false && (jsxRuntime.jsxs("label", { htmlFor: path, style: { marginRight: 5 }, children: [name, " ", required && jsxRuntime.jsx("span", { style: { color: 'red' }, children: "*" })] })), jsxRuntime.jsx(Input, {}), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("em", { style: { fontSize: '0.85em' }, children: errorMessage })] }));
}
};
const FormLabelRenderer = ({ name }) => name;
const FormErrorRenderer = ({ errorMessage }) => (jsxRuntime.jsx("em", { children: errorMessage }));
const FormTypeInputArray = ({ node, readOnly, disabled, ChildNodeComponents, style, }) => {
const handleClick = react.useCallback(() => {
node.push();
}, [node]);
const handleRemoveClick = react.useCallback((index) => {
node.remove(index);
}, [node]);
return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 5, ...style }, children: [ChildNodeComponents &&
array.map(ChildNodeComponents, (ChildNodeComponent, i) => {
const key = ChildNodeComponent.key;
return (jsxRuntime.jsxs("div", { style: { display: 'flex' }, children: [jsxRuntime.jsx(ChildNodeComponent, {}, key), !readOnly && (jsxRuntime.jsx(Button, { title: "remove item", label: "x", disabled: disabled, onClick: () => handleRemoveClick(i) }))] }, key));
}), !readOnly && (jsxRuntime.jsxs("label", { style: {
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
cursor: 'pointer',
marginBottom: 5,
}, children: [jsxRuntime.jsx("div", { style: { marginRight: 10 }, children: "Add New Item" }), jsxRuntime.jsx(Button, { title: "add item", label: "+", disabled: disabled, onClick: handleClick, fontSize: "1rem" })] }))] }));
};
const Button = ({ title, label, disabled, onClick, size = '1.3rem', fontSize = '0.8rem', style, }) => {
return (jsxRuntime.jsx("button", { title: title, onClick: onClick, disabled: disabled, style: {
position: 'relative',
width: size,
height: size,
padding: 0,
border: 'none',
cursor: 'pointer',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
...style,
}, children: jsxRuntime.jsx("span", { style: {
fontSize,
lineHeight: 1,
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
}, children: label }) }));
};
const FormTypeInputArrayDefinition = {
Component: FormTypeInputArray,
test: { type: 'array' },
};
const FormTypeInputBoolean = ({ path, name, readOnly, disabled, defaultValue, onChange, style, className, }) => {
const handleChange = hook.useHandle((event) => {
onChange(event.target.checked);
});
return (jsxRuntime.jsx("input", { type: "checkbox", id: path, name: name, disabled: disabled || readOnly, defaultChecked: defaultValue ?? undefined, onChange: handleChange, style: style, className: className }));
};
const FormTypeInputBooleanDefinition = {
Component: FormTypeInputBoolean,
test: { type: 'boolean' },
};
const FormTypeInputDateFormat = ({ path, name, jsonSchema, readOnly, disabled, defaultValue, onChange, style, className, }) => {
const { type, max, min } = react.useMemo(() => ({
type: jsonSchema.format,
max: jsonSchema.options?.maximum,
min: jsonSchema.options?.minimum,
}), [jsonSchema]);
const handleChange = hook.useHandle((event) => {
onChange(event.target.value);
});
return (jsxRuntime.jsx("input", { type: type, id: path, name: name, readOnly: readOnly, disabled: disabled, max: max, min: min, defaultValue: defaultValue ?? undefined, onChange: handleChange, style: style, className: className }));
};
const FormTypeInputDateFormatDefinition = {
Component: FormTypeInputDateFormat,
test: {
type: 'string',
format: ['month', 'week', 'date', 'time', 'datetime-local'],
},
};
const FormTypeInputNumber = ({ path, name, jsonSchema, readOnly, disabled, defaultValue, onChange, placeholder, style, className, }) => {
const handleChange = hook.useHandle((event) => {
onChange(event.target.valueAsNumber);
});
return (jsxRuntime.jsx("input", { type: "number", id: path, name: name, step: jsonSchema.multipleOf, readOnly: readOnly, disabled: disabled, placeholder: placeholder, defaultValue: defaultValue ?? undefined, onChange: handleChange, style: style, className: className }));
};
const FormTypeInputNumberDefinition = {
Component: FormTypeInputNumber,
test: { type: ['number', 'integer'] },
};
const FormTypeInputObject = ({ ChildNodeComponents, }) => {
const children = react.useMemo(() => {
return ChildNodeComponents
? array.map(ChildNodeComponents, (ChildNodeComponent) => (jsxRuntime.jsx(ChildNodeComponent, {}, ChildNodeComponent.key)))
: null;
}, [ChildNodeComponents]);
return jsxRuntime.jsx(react.Fragment, { children: children });
};
const FormTypeInputObjectDefinition = {
Component: FormTypeInputObject,
test: { type: 'object' },
};
const FormTypeInputString = ({ path, name, readOnly, disabled, jsonSchema, defaultValue, onChange, placeholder, style, className, }) => {
const type = react.useMemo(() => {
if (jsonSchema?.format === 'password')
return 'password';
else if (jsonSchema?.format === 'email')
return 'email';
else
return 'text';
}, [jsonSchema?.format]);
const handleChange = hook.useHandle((event) => {
onChange(event.target.value);
});
return (jsxRuntime.jsx("input", { type: type, id: path, name: name, readOnly: readOnly, disabled: disabled, placeholder: placeholder, defaultValue: defaultValue ?? undefined, onChange: handleChange, style: style, className: className }));
};
const FormTypeInputStringDefinition = {
Component: FormTypeInputString,
test: { type: 'string' },
};
const FormTypeInputStringCheckbox = ({ path, name, jsonSchema, readOnly, disabled, defaultValue, onChange, context, alias, style, className, }) => {
const checkboxOptions = react.useMemo(() => jsonSchema.items?.enum
? array.map(jsonSchema.items.enum, (rawValue) => {
const value = '' + rawValue;
return {
value,
rawValue,
label: context.checkboxLabels?.[value] || alias?.[value] || value,
};
})
: [], [context, jsonSchema, alias]);
const handleChange = hook.useHandle((event) => {
const { value, checked } = event.target;
const rawValue = checkboxOptions.find((option) => option.value === value)?.rawValue;
if (rawValue === undefined)
return;
if (checked)
onChange((prev) => [...(prev || []), rawValue]);
else
onChange((prev) => prev?.filter((option) => option !== rawValue) || []);
});
return (jsxRuntime.jsx("div", { children: array.map(checkboxOptions, ({ value, rawValue, label }) => (jsxRuntime.jsxs("label", { style: style, className: className, children: [jsxRuntime.jsx("input", { type: "checkbox", id: path, name: name, readOnly: readOnly, disabled: disabled, value: value, defaultChecked: defaultValue?.includes(rawValue), onChange: handleChange }), label] }, value))) }));
};
const FormTypeInputStringCheckboxDefinition = {
Component: FormTypeInputStringCheckbox,
test: ({ type, jsonSchema }) => type === 'array' &&
jsonSchema.formType === 'checkbox' &&
filter.isStringSchema(jsonSchema.items) &&
!!jsonSchema.items?.enum?.length,
};
const FormTypeInputStringEnum = ({ path, name, jsonSchema, defaultValue, onChange, readOnly, disabled, context, alias, style, className, }) => {
const enumOptions = react.useMemo(() => jsonSchema.enum
? array.map(jsonSchema.enum, (rawValue) => {
const value = '' + rawValue;
return {
value,
rawValue,
label: context.enumLabels?.[value] || alias?.[value] || value,
};
})
: [], [context, jsonSchema, alias]);
const initialValue = react.useMemo(() => {
if (defaultValue === undefined)
return undefined;
if (defaultValue === null)
return '' + null;
return defaultValue;
}, [defaultValue]);
const handleChange = hook.useHandle((event) => {
const rawValue = enumOptions.find((option) => option.value === event.target.value)?.rawValue;
if (rawValue === undefined)
return;
onChange(rawValue);
});
return (jsxRuntime.jsx("select", { id: path, name: name, disabled: disabled || readOnly, defaultValue: initialValue, onChange: handleChange, style: style, className: className, children: array.map(enumOptions, ({ value, label }) => (jsxRuntime.jsx("option", { value: value, children: label }, value))) }));
};
const FormTypeInputStringEnumDefinition = {
Component: FormTypeInputStringEnum,
test: ({ type, jsonSchema }) => type === 'string' && !!jsonSchema.enum?.length,
};
const FormTypeInputStringRadio = ({ path, name, jsonSchema, readOnly, disabled, defaultValue, onChange, context, alias, style, className, }) => {
const radioOptions = react.useMemo(() => jsonSchema.enum
? array.map(jsonSchema.enum, (rawValue) => {
const value = '' + rawValue;
return {
value,
rawValue,
label: context.radioLabels?.[value] || alias?.[value] || value,
};
})
: [], [context, jsonSchema, alias]);
const handleChange = hook.useHandle((event) => {
const rawValue = radioOptions.find((option) => option.value === event.target.value)?.rawValue;
if (rawValue === undefined)
return;
onChange(rawValue);
});
return (jsxRuntime.jsx(react.Fragment, { children: array.map(radioOptions, ({ value, rawValue, label }) => (jsxRuntime.jsxs("label", { style: style, className: className, children: [jsxRuntime.jsx("input", { type: "radio", id: path, name: name, readOnly: readOnly, disabled: disabled, value: value, defaultChecked: rawValue === defaultValue, onChange: handleChange }), label] }, value))) }));
};
const FormTypeInputStringRadioDefinition = {
Component: FormTypeInputStringRadio,
test: ({ type, jsonSchema }) => type === 'string' &&
(jsonSchema.formType === 'radio' || jsonSchema.formType === 'radiogroup') &&
!!jsonSchema.enum?.length,
};
const FormTypeInputVirtual = ({ ChildNodeComponents, }) => {
return (jsxRuntime.jsx(react.Fragment, { children: ChildNodeComponents &&
array.map(ChildNodeComponents, (ChildNodeComponent) => (jsxRuntime.jsx(ChildNodeComponent, {}, ChildNodeComponent.key))) }));
};
const FormTypeInputVirtualDefinition = {
Component: FormTypeInputVirtual,
test: { type: 'virtual' },
};
const formTypeDefinitions = [
FormTypeInputDateFormatDefinition,
FormTypeInputStringCheckboxDefinition,
FormTypeInputStringRadioDefinition,
FormTypeInputStringEnumDefinition,
FormTypeInputVirtualDefinition,
FormTypeInputArrayDefinition,
FormTypeInputObjectDefinition,
FormTypeInputBooleanDefinition,
FormTypeInputStringDefinition,
FormTypeInputNumberDefinition,
];
const BIT_MASK_ALL = -1;
const BIT_MASK_NONE = 0;
const UNIT_SEPARATOR = '\x1F';
const START_OF_TEXT = '\x02';
const END_OF_TEXT = '\x03';
const ENHANCED_KEY = START_OF_TEXT + UNIT_SEPARATOR + END_OF_TEXT;
const transformErrors = (errors, key) => {
if (!filter$1.isArray(errors))
return [];
const result = new Array();
for (let i = 0, l = errors.length; i < l; i++) {
const error = errors[i];
if (error.dataPath.indexOf(ENHANCED_KEY) !== -1)
continue;
error.key = key ? ++sequence : undefined;
result[result.length] = error;
}
return result;
};
let sequence = 0;
const getErrorMessage = (keyword, errorMessages, context) => {
const errorMessage = errorMessages[keyword] || errorMessages.default;
if (typeof errorMessage === 'string')
return errorMessage;
if (errorMessage &&
typeof errorMessage === 'object' &&
context.locale !== undefined) {
const localeErrorMessage = errorMessage[context.locale];
if (typeof localeErrorMessage === 'string')
return localeErrorMessage;
}
return null;
};
const replacePattern = (errorMessage, details, value) => {
let message = errorMessage;
if (details && typeof details === 'object') {
const keys = Object.keys(details);
for (let i = 0, k = keys[0], l = keys.length; i < l; i++, k = keys[i])
message = message.replace('{' + k + '}', '' + details[k]);
}
message = message.replace('{value}', '' + value);
return message;
};
const formatError = (error, node, context) => {
const errorMessages = node.jsonSchema.errorMessages;
if (!errorMessages || !error.keyword)
return error.message;
const errorMessage = getErrorMessage(error.keyword, errorMessages, context);
if (errorMessage)
return replacePattern(errorMessage, error.details, node.value);
return error.message;
};
const JSONPointer = {
Root: pointer.JSONPointer.Root,
Fragment: pointer.JSONPointer.Fragment,
Separator: pointer.JSONPointer.Separator,
Parent: '..',
Current: '.',
Index: '*',
Context: '@',
};
const isAbsolutePath = (pointer) => pointer[0] === JSONPointer.Separator ||
(pointer[0] === JSONPointer.Fragment && pointer[1] === JSONPointer.Separator);
const joinSegment = (basePath = JSONPointer.Root, segment) => (segment !== '' ? basePath + JSONPointer.Separator + segment : basePath);
const stripFragment = (path) => path[0] === JSONPointer.Fragment
? path[2] !== undefined
? path.slice(1)
: JSONPointer.Root
: path === JSONPointer.Separator
? JSONPointer.Root
: path;
const INCLUDE_INDEX_REGEX = new RegExp(`^(\\${JSONPointer.Fragment})?\\${JSONPointer.Separator}(?:.*\\${JSONPointer.Separator})?\\${JSONPointer.Index}(?:\\${JSONPointer.Separator}.*)?(?<!\\${JSONPointer.Separator})$`);
const normalizeFormTypeInputMap = (formTypeInputMap) => {
if (!formTypeInputMap)
return [];
const result = [];
const keys = Object.keys(formTypeInputMap);
for (let i = 0, k = keys[0], l = keys.length; i < l; i++, k = keys[i]) {
const Component = formTypeInputMap[k];
if (!filter$2.isReactComponent(Component))
continue;
if (INCLUDE_INDEX_REGEX.test(k))
result.push({
test: formTypeTestFnFactory$1(k),
Component: hoc.withErrorBoundary(Component),
});
else
result.push({
test: pathExactMatchFnFactory(k),
Component: hoc.withErrorBoundary(Component),
});
}
return result;
};
const pathExactMatchFnFactory = (inputPath) => {
try {
const path = stripFragment(inputPath);
const regex = path ? new RegExp(path) : null;
return (hint) => {
if (hint.path === path)
return true;
if (regex?.test(hint.path))
return true;
return false;
};
}
catch (error) {
throw new SchemaFormError('FORM_TYPE_INPUT_MAP', `FormTypeInputMap contains an invalid key pattern.: ${inputPath}`, { path: inputPath, error });
}
};
const formTypeTestFnFactory$1 = (path) => {
const segments = stripFragment(path).split(JSONPointer.Separator);
return (hint) => {
const hintSegments = hint.path.split(JSONPointer.Separator);
if (segments.length !== hintSegments.length)
return false;
for (let i = 0, l = segments.length; i < l; i++) {
const segment = segments[i];
const hintSegment = hintSegments[i];
if (segment === JSONPointer.Index) {
if (!filter$1.isArrayIndex(hintSegment))
return false;
}
else if (segment !== hintSegment)
return false;
}
return true;
};
};
const normalizeFormTypeInputDefinitions = (formTypeInputDefinitions) => {
if (!formTypeInputDefinitions)
return [];
const result = [];
for (const { Component, test } of formTypeInputDefinitions) {
if (!filter$2.isReactComponent(Component))
continue;
if (filter$1.isFunction(test))
result.push({
test,
Component: hoc.withErrorBoundary(Component),
});
else if (filter$1.isPlainObject(test)) {
result.push({
test: formTypeTestFnFactory(test),
Component: hoc.withErrorBoundary(Component),
});
}
}
return result;
};
const formTypeTestFnFactory = (test) => {
const keys = Object.keys(test);
return (hint) => {
for (let i = 0, k = keys[0], l = keys.length; i < l; i++, k = keys[i]) {
const reference = test[k];
const subject = hint[k];
if (filter$1.isArray(reference)) {
if (reference.indexOf(subject) === -1)
return false;
}
else {
if (reference !== subject)
return false;
}
}
return true;
};
};
const defaultRenderKit = {
FormGroup: FormGroupRenderer,
FormLabel: FormLabelRenderer,
FormInput: FormInputRenderer,
FormError: FormErrorRenderer,
};
const defaultFormTypeInputDefinitions = normalizeFormTypeInputDefinitions(formTypeDefinitions);
const defaultFormatError = formatError;
class PluginManager {
static #renderKit = defaultRenderKit;
static #formTypeInputDefinitions = defaultFormTypeInputDefinitions;
static #validator;
static #formatError = defaultFormatError;
static get FormGroup() {
return PluginManager.#renderKit.FormGroup;
}
static get FormLabel() {
return PluginManager.#renderKit.FormLabel;
}
static get FormInput() {
return PluginManager.#renderKit.FormInput;
}
static get FormError() {
return PluginManager.#renderKit.FormError;
}
static get formatError() {
return PluginManager.#formatError;
}
static get formTypeInputDefinitions() {
return PluginManager.#formTypeInputDefinitions;
}
static get validator() {
return PluginManager.#validator;
}
static reset() {
PluginManager.#renderKit = defaultRenderKit;
PluginManager.#formTypeInputDefinitions = defaultFormTypeInputDefinitions;
PluginManager.#validator = undefined;
PluginManager.#formatError = defaultFormatError;
}
static appendRenderKit(renderKit) {
if (!renderKit)
return;
PluginManager.#renderKit = {
...PluginManager.#renderKit,
...object.remainOnlyReactComponent(renderKit),
};
}
static appendFormTypeInputDefinitions(formTypeInputDefinitions) {
if (!formTypeInputDefinitions)
return;
PluginManager.#formTypeInputDefinitions = [
...normalizeFormTypeInputDefinitions(formTypeInputDefinitions),
...PluginManager.#formTypeInputDefinitions,
];
}
static appendValidator(validator) {
if (!validator)
return;
PluginManager.#validator = validator;
}
static appendFormatError(formatError) {
if (!formatError)
return;
PluginManager.#formatError = formatError;
}
}
const RegisteredPlugin = new Set();
const registerPlugin = (plugin) => {
if (plugin === null)
PluginManager.reset();
if (!filter$1.isPlainObject(plugin))
return;
const hash = object$1.stableSerialize(plugin);
if (RegisteredPlugin.has(hash))
return;
try {
const { formTypeInputDefinitions, validator, formatError, ...renderKit } = plugin;
PluginManager.appendRenderKit(renderKit);
PluginManager.appendFormTypeInputDefinitions(formTypeInputDefinitions);
PluginManager.appendValidator(validator);
PluginManager.appendFormatError(formatError);
}
catch (error) {
throw new UnhandledError('REGISTER_PLUGIN', 'Failed to register plugin', {
plugin,
error,
});
}
RegisteredPlugin.add(hash);
};
const extractSchemaInfo = (jsonSchema) => {
if (jsonSchema === undefined)
return null;
const type = jsonSchema.type;
if (type === undefined)
return null;
if (filter$1.isArray(type)) {
if (type.length === 0 || type.length > 2)
return null;
if (type.length === 1)
return { type: type[0], nullable: type[0] === 'null' };
const nullIndex = type.indexOf('null');
if (nullIndex === -1)
return null;
return { type: type[nullIndex === 0 ? 1 : 0], nullable: true };
}
return {
type,
nullable: type === 'null' || jsonSchema.nullable === true,
};
};
const getReferenceTable = (jsonSchema) => {
const referenceTable = new Map();
new scanner$1.JsonSchemaScanner({
visitor: {
exit: ({ schema, hasReference }) => {
if (hasReference && typeof schema.$ref === 'string')
referenceTable.set(schema.$ref, pointer.getValue(jsonSchema, schema.$ref));
},
},
}).scan(jsonSchema);
if (referenceTable.size === 0)
return null;
return referenceTable;
};
const getResolveSchemaScanner = (referenceTable, maxDepth) => new scanner$1.JsonSchemaScanner({
options: {
resolveReference: (path, entry) => {
const { $ref: _, ...preferredSchema } = entry.schema;
const referenceSchema = referenceTable.get(path);
if (referenceSchema === undefined)
return;
if (filter$1.isEmptyObject(preferredSchema))
return referenceSchema;
return object$1.merge(object$1.clone(referenceSchema), preferredSchema);
},
maxDepth,
},
});
const getResolveSchema = (jsonSchema, maxDepth = 1) => {
const table = getReferenceTable(jsonSchema);
const scanner = table ? getResolveSchemaScanner(table, maxDepth) : null;
return scanner
? (schema) => schema !== undefined ? scanner.scan(schema).getValue() : undefined
: null;
};
const processOneOfSchema = (schema, variant) => object$1.merge(schema, {
properties: {
[ENHANCED_KEY]: {
const: variant,
},
},
});
const transformCondition = (schema, virtual) => {
const transformed = Object.assign({}, schema);
if (schema.required?.length) {
const result = transformVirtualFields(schema.required, virtual);
transformed.required = result.required;
result.virtualRequired.length &&
(transformed.virtualRequired = result.virtualRequired);
}
schema.then && (transformed.then = transformCondition(schema.then, virtual));
schema.else && (transformed.else = transformCondition(schema.else, virtual));
return transformed;
};
const transformVirtualFields = (required, virtual) => {
const requiredKeys = [];
const virtualRequiredKeys = [];
for (let i = 0, il = required.length; i < il; i++) {
const key = required[i];
const fields = virtual[key]?.fields;
if (fields) {
for (let j = 0, jl = fields.length; j < jl; j++)
requiredKeys.indexOf(fields[j]) === -1 && requiredKeys.push(fields[j]);
virtualRequiredKeys.indexOf(key) === -1 && virtualRequiredKeys.push(key);
}
else
requiredKeys.indexOf(key) === -1 && requiredKeys.push(key);
}
return { required: requiredKeys, virtualRequired: virtualRequiredKeys };
};
const processVirtualSchema = (schema) => {
if (schema.virtual === undefined)
return null;
let expired = false;
if (schema.required) {
schema = transformCondition(schema, schema.virtual);
expired = true;
}
if (schema.then) {
schema.then = transformCondition(schema.then, schema.virtual);
expired = true;
}
if (schema.else) {
schema.else = transformCondition(schema.else, schema.virtual);
expired = true;
}
return expired ? schema : null;
};
const preprocessSchema = (schema) => scanner.scan(schema).getValue() || schema;
const scanner = new scanner$1.JsonSchemaScanner({
options: {
mutate: (entry) => {
let schema = entry.schema;
let idle = true;
if (filter.isObjectSchema(schema)) {
const processed = processVirtualSchema(schema);
schema = processed || schema;
if (idle)
idle = processed === null;
}
if (entry.keyword === 'oneOf') {
schema = processOneOfSchema(schema, entry.variant);
idle = false;
}
if (idle)
return;
return schema;
},
},
});
const getCloneDepth = (schema) => filter.isObjectSchema(schema) ? 3 : filter.isArraySchema(schema) ? 2 : 1;
const distributeAllOfProperties = (base, source) => {
if (source.properties === undefined)
return;
if (base.properties === undefined)
base.properties = source.properties;
else {
const properties = base.properties;
const keys = Object.keys(source.properties);
for (let i = 0, k = keys[0], l = keys.length; i < l; i++, k = keys[i]) {
const subSchema = source.properties[k];
if (properties[k] === undefined)
properties[k] = subSchema;
else
distributeSchema(properties[k], subSchema);
}
}
};
const distributeAllOfItems = (base, source) => {
if (base.items === false || source.items === undefined)
return;
else if (source.items === false)
base.items = false;
else if (base.items === undefined)
base.items = source.items;
else
distributeSchema(base.items, source.items);
};
const distributeSchema = (base, source) => {
if (filter$1.isArray(base.allOf))
base.allOf.push(source);
else
base.allOf = [source];
};
const intersectBooleanOr = (baseBool, sourceBool) => {
if (baseBool === undefined && sourceBool === undefined)
return undefined;
if (baseBool === undefined)
return sourceBool;
if (sourceBool === undefined)
return baseBool;
return baseBool || sourceBool;
};
const intersectConst = (baseConst, sourceConst) => {
if (baseConst === undefined && sourceConst === undefined)
return undefined;
if (baseConst === undefined)
return sourceConst;
if (sourceConst === undefined)
return baseConst;
if (baseConst !== sourceConst)
throw new JsonSchemaError('CONFLICTING_CONST_VALUES', `Conflicting const values: ${baseConst} vs ${sourceConst}`);
return baseConst;
};
const intersectEnum = (baseEnum, sourceEnum, deepEqual) => {
if (!baseEnum && !sourceEnum)
return undefined;
if (!baseEnum)
return sourceEnum;
if (!sourceEnum)
return baseEnum;
const intersected = deepEqual
? array.intersectionWith(baseEnum, sourceEnum, object$1.equals)
: array.intersectionLite(baseEnum, sourceEnum);
if (intersected.length === 0)
throw new JsonSchemaError('EMPTY_ENUM_INTERSECTION', 'Enum values must have at least one common value');
return intersected;
};
const intersectMaximum = (baseMax, sourceMax) => {
if (baseMax === undefined && sourceMax === undefined)
return undefined;
if (baseMax === undefined)
return sourceMax;
if (sourceMax === undefined)
return baseMax;
return math.minLite(baseMax, sourceMax);
};
const intersectMinimum = (baseMin, sourceMin) => {
if (baseMin === undefined && sourceMin === undefined)
return undefined;
if (baseMin === undefined)
return sourceMin;
if (sourceMin === undefined)
return baseMin;
return math.maxLite(baseMin, sourceMin);
};
const FIRST_WIN_FIELDS = [
'title',
'description',
'$comment',
'examples',
'default',
'readOnly',
'writeOnly',
'format',
'additionalProperties',
'patternProperties',
'prefixItems',
];
const SPECIAL_FIELDS = [
'type',
'enum',
'const',
'required',
'nullable',
'pattern',
'multipleOf',
'minimum',
'maximum',
'exclusiveMinimum',
'exclusiveMaximum',
'minLength',
'maxLength',
'minItems',
'maxItems',
'minProperties',
'maxProperties',
'minContains',
'maxContains',
'uniqueItems',
'propertyNames',
'properties',
'items',
];
const IGNORE_FIELDS = [
'allOf',
'anyOf',
'oneOf',
'not',
'if',
'then',
'else',
'dependencies',
'dependentRequired',
'dependentSchemas',
'unevaluatedProperties',
'unevaluatedItems',
'contains',
];
const EXCLUDE_FIELDS = new Set([
...FIRST_WIN_FIELDS,
...SPECIAL_FIELDS,
...IGNORE_FIELDS,
]);
const processFirstWinFields = (base, source) => {
for (let i = 0, l = FIRST_WIN_FIELDS.length; i < l; i++) {
const field = FIRST_WIN_FIELDS[i];
const baseValue = base[field];
const sourceValue = source[field];
if (baseValue !== undefined)
base[field] = baseValue;
else if (sourceValue !== undefined)
base[field] = sourceValue;
}
};
const processOverwriteFields = (base, source) => {
const keys = Object.keys(source);
for (let i = 0, k = keys[0], l = keys.length; i < l; i++, k = keys[i]) {
const value = source[k];
if (EXCLUDE_FIELDS.has(k) || value === undefined)
continue;
base[k] = value;
}
};
const processSchemaType = (base, source) => {
const baseInfo = extractSchemaInfo(base);
if (baseInfo === null)
return;
const sourceInfo = extractSchemaInfo(source);
const sourceNullable = (sourceInfo?.nullable ?? source.nullable) !== false;
const schemaType = sourceInfo !== null
? intersectSchemaType(baseInfo.type, sourceInfo.type)
: baseInfo.type;
if (base.nullable !== undefined)
base.nullable = undefined;
if (baseInfo.nullable && sourceNullable)
base.type = (schemaType !== 'null' ? [schemaType, 'null'] : 'null');
else
base.type = schemaType;
};
const intersectSchemaType = (baseType, sourceType) => (baseType === 'number' && sourceType === 'integer' ? 'integer' : baseType);
const unionRequired = (baseRequired, sourceRequired) => {
if (!baseRequired && !sourceRequired)
return undefined;
if (!baseRequired)
return sourceRequired;
if (!sourceRequired)
return baseRequired;
return array.unique([...baseRequired, ...sourceRequired]);
};
const validateRange = (min, max, errorMessage = 'Invalid range: min > max') => {
if (min !== undefined && max !== undefined && min > max) {
throw new JsonSchemaError('INVALID_RANGE', `${errorMessage} (${min} > ${max})`);
}
};
const intersectArraySchema = (base, source) => {
processSchemaType(base, source);
processFirstWinFields(base, source);
processOverwriteFields(base, source);
distributeAllOfItems(base, source);
const enumResult = intersectEnum(base.enum, source.enum, true);
const constResult = intersectConst(base.const, source.const);
const requiredResult = unionRequired(base.required, source.required);
const minItems = intersectMinimum(base.minItems, source.minItems);
const maxItems = intersectMaximum(base.maxItems, source.maxItems);
const minContains = intersectMinimum(base.minContains, source.minContains);
const maxContains = intersectMaximum(base.maxContains, source.maxContains);
const uniqueItems = intersectBooleanOr(base.uniqueItems, source.uniqueItems);
validateRange(minItems, maxItems, 'Invalid array constraints: minItems');
validateRange(minContains, maxContains, 'Invalid array constraints: minContains');
if (enumResult !== undefined)
base.enum = enumResult;
if (constResult !== undefined)
base.const = constResult;
if (requiredResult !== undefined)
base.required = requiredResult;
if (minItems !== undefined)
base.minItems = minItems;
if (maxItems !== undefined)
base.maxItems = maxItems;
if (minContains !== undefined)
base.minContains = minContains;
if (maxContains !== undefined)
base.maxContains = maxContains;
if (uniqueItems !== undefined)
base.uniqueItems = uniqueItems;
return base;
};
const intersectBooleanSchema = (base, source) => {
processSchemaType(base, source);
processFirstWinFields(base, source);
processOverwriteFields(base, source);
const enumResult = intersectEnum(base.enum, source.enum);
const constResult = intersectConst(base.const, source.const);
const requiredResult = unionRequired(base.required, source.required);
if (enumResult !== undefined)
base.enum = enumResult;
if (constResult !== undefined)
base.const = constResult;
if (requiredResult !== undefined)
base.required = requiredResult;
return base;
};
const intersectNullSchema = (base, source) => {
processSchemaType(base, source);
processFirstWinFields(base, source);
processOverwriteFields(base, source);
const enumResult = intersectEnum(base.enum, source.enum);
const constResult = intersectConst(base.const, source.const);
const requiredResult = unionRequired(base.required, source.required);
if (enumResult !== undefined)
base.enum = enumResult;
if (constResult !== undefined)
base.const = constResult;
if (requiredResult !== undefined)
base.required = requiredResult;
return base;
};
const intersectMultipleOf = (baseMultiple, sourceMultiple) => {
if (baseMultiple === undefined && sourceMultiple === undefined)
return undefined;
if (baseMultiple === undefined)
return sourceMultiple;
if (sourceMultiple === undefined)
return baseMultiple;
return math.lcm(baseMultiple, sourceMultiple);
};
const intersectNumberSchema = (base, source) => {
processSchemaType(base, source);
processFirstWinFields(base, source);
processOverwriteFields(base, source);
const enumResult = intersectEnum(base.enum, source.enum);
const constResult = intersectConst(base.const, source.const);
const requiredResult = unionRequired(base.required, source.required);
const minimum = intersectMinimum(base.minimum, source.minimum);
const maximum = intersectMaximum(base.maximum, source.maximum);
const exclusiveMinimum = intersectMinimum(base.exclusiveMinimum, source.exclusiveMinimum);
const exclusiveMaximum = intersectMaximum(base.exclusiveMaximum, source.exclusiveMaximum);
const multipleOf = intersectMultipleOf(base.multipleOf, source.multipleOf);
validateRange(minimum, maximum, 'Invalid number constraints: minimum');
validateRange(exclusiveMinimum, exclusiveMaximum, 'Invalid number constraints: exclusiveMinimum');
if (enumResult !== undefined)
base.enum = enumResult;
if (constResult !== undefined)
base.const = constResult;
if (requiredResult !== undefined)
base.required = requiredResult;
if (minimum !== undefined)
base.minimum = minimum;
if (maximum !== undefined)
base.maximum = maximum;
if (exclusiveMinimum !== undefined)
base.exclusiveMinimum = exclusiveMinimum;
if (exclusiveMaximum !== undefined)
base.exclusiveMaximum = exclusiveMaximum;
if (multipleOf !== undefined)
base.multipleOf = multipleOf;
return base;
};
const intersectPattern = (basePattern, sourcePattern) => {
if (!basePattern && !sourcePattern)
return undefined;
if (!basePattern)
return sourcePattern;
if (!sourcePattern)
return basePattern;
return '(?=' + basePattern + ')(?=' + sourcePattern + ')';
};
const intersectStringSchema = (base, source) => {
processSchemaType(base, source);
processFirstWinFields(base, source);
processOverwriteFields(base, source);
const enumResult = intersectEnum(base.enum, source.enum);
const constResult = intersectConst(base.const, source.const);
const requiredResult = unionRequired(base.required, source.required);
const pattern = intersectPattern(base.pattern, source.pattern);
const minLength = intersectMinimum(base.minLength, source.minLength);
const maxLength = intersectMaximum(base.maxLength, source.maxLength);
validateRange(minLength, maxLength, 'Invalid string constraints: minLength');
if (enumResult !== undefined)
base.enum = enumResult;
if (constResult !== undefined)
base.const = constResult;
if (requiredResult !== undefined)
base.required = requiredResult;
if (pattern !== undefined)
base.pattern = pattern;
if (minLength !== undefined)
base.minLength = minLength;
if (maxLength !== undefined)
base.maxLength = maxLength;
return base;
};
const intersectObjectSchema = (base, source) => {
processSchemaType(base, source);
processFirstWinFields(base, source);
processOverwriteFields(base, source);
distributeAllOfProperties(base, source);
const enumResult = intersectEnum(base.enum, source.enum, true);
const constResult = intersectConst(base.const, source.const);
const requiredResult = unionRequired(base.required, source.required);
const propertyNames = base.propertyNames && source.propertyNames
? intersectStringSchema(base.propertyNames, source.propertyNames)
: base.propertyNames || source.propertyNames;
const minProperties = intersectMinimum(base.minProperties, source.minProperties);
const maxProperties = intersectMaximum(base.maxProperties, source.maxProperties);
validateRange(minProperties, maxProperties, 'Invalid object constraints: minProperties');
if (enumResult !== undefined)
base.enum = enumResult;
if (constResult !== undefined)
base.const = constResult;
if (requiredResult !== undefined)
base.required = requiredResult;
if (propertyNames !== undefined)
base.propertyNames = propertyNames;
if (minProperties !== undefined)
base.minProperties = minProperties;
if (maxProperties !== undefined)
base.maxProperties = maxProperties;
return base;
};
const getMergeSchemaHandler = (schema) => {
const schemaInfo = extractSchemaInfo(schema);
switch (schemaInfo?.type) {
case 'array':
return intersectArraySchema;
case 'boolean':
return intersectBooleanSchema;
case 'null':
return intersectNullSchema;
case 'number':
case 'integer':
return intersectNumberSchema;
case 'object':
return intersectObjectSchema;
case 'string':
return intersectStringSchema;
}
return null;
};
const validateCompatibility = (schema, allOfSchema) => allOfSchema.type === undefined || filter.isCompatibleSchemaType(schema, allOfSchema);
const processAllOfSchema = (schema) => {
if (!schema.allOf?.length)
return schema;
const mergeHandler = getMergeSchemaHandler(schema);
if (!mergeHandler)
return schema;
const { allOf, ...rest } = schema;
schema = object$1.cloneLite(rest, getCloneDepth(schema));
for (let i = 0, l = allOf.length; i < l; i++) {
const allOfSchema = allOf[i];
if (validateCompatibility(schema, allOfSchema) === false)
throw new JsonSchemaError('ALL_OF_TYPE_REDEFINITION', 'Type cannot be redefined in allOf schema. It must either be omitted or match the parent schema type.', { schema, allOfSchema });
schema = mergeHandler(schema, allOfSchema);
}
return schema;
};
const stripSchemaExtensions = (jsonSchema) => new scanner$1.JsonSchemaScanner({ options: { mutate } }).scan(jsonSchema).getValue() ||
jsonSchema;
const mutate = ({ schema, }) => {
if (schema == null)
return;
if (schema.FormTypeInput === undefined &&
schema.FormTypeInputProps === undefined &&
schema.FormTypeRendererProps === undefined &&
schema.errorMessages === undefined &&
schema.options === undefined)
return;
const { FormTypeInput, FormTypeInputProps, FormTypeRendererProps, errorMessages, options, ...stripedSchema } = schema;
return stripedSchema;
};
const isTerminalType = (type) => type === 'boolean' ||
type === 'number' ||
type === 'integer' ||
type === 'string' ||
type === 'null';
const isBranchType = (type) => type === 'array' || type === 'object' || type === 'virtual';
exports.ValidationMode = void 0;
(function (ValidationMode) {
ValidationMode[ValidationMode["None"] = 0] = "None";
ValidationMode[ValidationMode["OnChange"] = 1] = "OnChange";
ValidationMode[ValidationMode["OnRequest"] = 2] = "OnRequest";
})(exports.ValidationMode || (exports.ValidationMode = {}));
var NodeEventType;
(function (NodeEventType) {
NodeEventType[NodeEventType["Initialized"] = 1] = "Initialized";
NodeEventType[NodeEventType["UpdatePath"] = 2] = "UpdatePath";
NodeEventType[NodeEventType["UpdateValue"] = 4] = "UpdateValue";
NodeEventType[NodeEventType["UpdateState"] = 8] = "UpdateState";
NodeEventType[NodeEventType["UpdateError"] = 16] = "UpdateError";
NodeEventType[NodeEventType["UpdateGlobalError"] = 32] = "UpdateGlobalError";
NodeEventType[NodeEventType["UpdateChildren"] = 64] = "UpdateChildren";
NodeEventType[NodeEventType["UpdateComputedProperties"] = 128] = "UpdateComputedProperties";
NodeEventType[NodeEventType["Focused"] = 256] = "Focused";
NodeEventType[NodeEventType["Blurred"] = 512] = "Blurred";
NodeEventType[NodeEventType["RequestFocus"] = 1024] = "RequestFocus";
NodeEventType[NodeEventType["RequestSelect"] = 2048] = "RequestSelect";
NodeEventType[NodeEventType["RequestRefresh"] = 4096] = "RequestRefresh";
NodeEventType[NodeEventType["RequestEmitChange"] = 8192] = "RequestEmitChange";
})(NodeEventType || (NodeEventType = {}));
exports.NodeEventType = void 0;
(function (PublicNodeEventType) {
PublicNodeEventType[PublicNodeEventType["UpdateValue"] = 4] = "UpdateValue";
PublicNodeEventType[PublicNodeEventType["UpdateState"] = 8] = "UpdateState";
PublicNodeEventType[PublicNodeEventType["UpdateError"] = 16] = "UpdateError";
PublicNodeEventType[PublicNodeEventType["RequestFocus"] = 1024] = "RequestFocus";
PublicNodeEventType[PublicNodeEventType["RequestSelect"] = 2048] = "RequestSelect";
})(exports.NodeEventType || (exports.NodeEventType = {}));
exports.NodeState = void 0;
(function (NodeState) {
NodeState[NodeState["Dirty"] = 1] = "Dirty";
NodeState[NodeState["Touched"] = 2] = "Touched";
NodeState[NodeState["ShowError"] = 4] = "ShowError";
})(exports.NodeState || (exports.NodeState = {}));
var SetValueOption;
(function (SetValueOption) {
SetValueOption[SetValueOption["None"] = 0] = "None";
SetValueOption[SetValueOption["Replace"] = 1] = "Replace";
SetValueOption[SetValueOption["EmitChange"] = 2] = "EmitChange";
SetValueOption[SetValueOption["Propagate"] = 4] = "Propagate";
SetValueOption[SetValueOption["Refresh"] = 8] = "Refresh";
SetValueOption[SetValueOption["Batch"] = 16] = "Batch";
SetValueOption[SetValueOption["Isolate"] = 32] = "Isolate";
SetValueOption[SetValueOption["Normalize"] = 64] = "Normalize";
SetValueOption[SetValueOption["PublishUpdateEvent"] = 128] = "PublishUpdateEvent";
SetValueOption[SetValueOption["BatchedEmitChange"] = 18] = "BatchedEmitChange";
SetValueOption[SetValueOption["Default"] = 130] = "Default";
SetValueOption[SetValueOption["BatchDefault"] = 146] = "BatchDefault";
SetValueOption[SetValueOption["Reset"] = 151] = "Reset";
SetValueOption[SetValueOption["IsolateReset"] = 183] = "IsolateReset";
SetValueOption[SetValueOption["StableReset"] = 223] = "StableReset";
SetValueOption[SetValueOption["Merge"] = 190] = "Merge";
SetValueOption[SetValueOption["Overwrite"] = 191] = "Overwrite";
})(SetValueOption || (SetValueOption = {}));
exports.SetValueOption = void 0;
(function (PublicSetValueOption) {
PublicSetValueOption[PublicSetValueOption["Merge"] = 190] = "Merge";
PublicSetValueOption[PublicSetValueOption["Overwrite"] = 191] = "Overwrite";
})(exports.SetValueOption || (exports.SetValueOption = {}));
const getEmptyValue = (type) => {
if (type === 'array')
return [];
if (type === 'object')
return {};
return undefined;
};
const getDefaultValue = (jsonSchema) => {
if (jsonSchema.default !== undefined)
return jsonSchema.default;
if (jsonSchema.type === 'virtual')
return [];
const schemaInfo = extractSchemaInfo(jsonSchema);
if (schemaInfo === null)
return undefined;
return getEmptyValue(schemaInfo.type);
};
const getObjectDefaultValue = (jsonSchema, inputDefault) => {
const defaultValue = inputDefault !== undefined ? inputDefault : jsonSchema.default;
const result = defaultValue || {};
new scanner$1.JsonSchemaScanner({
visitor: {
enter: ({ schema, dataPath }) => {
if (lib.hasOwnProperty(schema, 'default'))
pointer.setValue(result, dataPath, schema.default, SET_VALUE_OPTIONS);
},
},
}).scan(jsonSchema);
if (filter$1.isEmptyObject(result))
return defaultValue;
return result;
};
const SET_VALUE_OPTIONS = {
overwrite: false,
preserveNull: false,
};
const afterMicrotask = (handler) => {
let macrotaskId;
const callback = () => {
handler();
macrotaskId = undefined;
};
return () => {
if (macrotaskId)
scheduler.cancelMacrotaskSafe(macrotaskId);
macrotaskId = scheduler.scheduleMacrotaskSafe(callback);
};
};
const checkDefinedValue = (value) => {
if (value === null)
return true;
if (typeof value === 'object') {
for (const key in value)
if (lib.hasOwnProperty(value, key))