@limetech/lime-elements
Version:
369 lines (368 loc) • 11.2 kB
JavaScript
import { h, } from '@stencil/core';
import React from 'react';
import { createRoot } from 'react-dom/client';
import JSONSchemaForm from '@rjsf/core';
import retargetEvents from 'react-shadow-dom-retarget-events';
import { ArrayFieldTemplate, FieldTemplate, ObjectFieldTemplate, } from './templates';
import { SchemaField as CustomSchemaField } from './fields/schema-field';
import { ArrayField as CustomArrayField } from './fields/array-field';
import { ObjectField as CustomObjectField } from './fields/object-field';
import { widgets } from './widgets';
import { createRandomString } from '../../util/random-string';
import Ajv from 'ajv';
import { isInteger } from './validators';
import { mapValues } from 'lodash-es';
/**
* @exampleComponent limel-example-form
* @exampleComponent limel-example-nested-form
* @exampleComponent limel-example-list-form
* @exampleComponent limel-example-dynamic-form
* @exampleComponent limel-example-custom-component-form
* @exampleComponent limel-example-props-factory-form
* @exampleComponent limel-example-form-layout
* @exampleComponent limel-example-form-span-fields
* @exampleComponent limel-example-custom-error-message
* @exampleComponent limel-example-server-errors
* @exampleComponent limel-example-form-with-help
* @exampleComponent limel-example-form-row-layout
*/
export class Form {
constructor() {
this.isValid = true;
this.schema = {};
this.value = undefined;
this.disabled = false;
this.propsFactory = undefined;
this.transformErrors = undefined;
this.errors = undefined;
this.handleChange = this.handleChange.bind(this);
this.getCustomErrorMessages = this.getCustomErrorMessages.bind(this);
}
connectedCallback() {
this.initialize();
}
componentWillLoad() {
this.setSchemaId();
this.createValidator();
}
componentDidLoad() {
this.initialize();
}
initialize() {
if (!this.host.shadowRoot.querySelector('.root')) {
return;
}
this.reactRender();
retargetEvents(this.host.shadowRoot);
this.validateForm(this.value);
}
componentDidUpdate() {
this.reactRender();
this.validateForm(this.value);
}
disconnectedCallback() {
if (this.root) {
this.root.unmount();
this.root = undefined;
}
}
render() {
return h("div", { class: "root" });
}
reactRender() {
if (!this.root) {
const rootElement = this.host.shadowRoot.querySelector('.root');
this.root = createRoot(rootElement);
}
this.root.render(React.createElement(JSONSchemaForm, {
schema: this.modifiedSchema,
formData: this.value,
onChange: this.handleChange,
widgets: widgets,
liveValidate: true,
showErrorList: false,
extraErrors: this.getExtraErrors(this.errors),
FieldTemplate: FieldTemplate,
ArrayFieldTemplate: ArrayFieldTemplate,
ObjectFieldTemplate: ObjectFieldTemplate,
disabled: this.disabled,
transformErrors: this.getCustomErrorMessages,
formContext: {
schema: this.modifiedSchema,
rootValue: this.value,
propsFactory: this.propsFactory,
},
fields: {
SchemaField: CustomSchemaField,
ArrayField: CustomArrayField,
ObjectField: CustomObjectField,
},
}, []));
}
handleChange(event) {
this.change.emit(event.formData);
}
validateForm(value) {
const isValid = this.validator(value) === true;
const errors = this.getValidationErrors();
const status = {
valid: isValid,
errors: errors,
};
if (this.isValid !== status.valid || !status.valid) {
this.validate.emit(status);
}
this.isValid = status.valid;
}
setSchema() {
this.setSchemaId();
this.createValidator();
}
setSchemaId() {
// Due to a bug in react-jsonschema-form, validation will stop working if the schema is updated.
// A workaround at the moment is to always give it a unique ID
// https://github.com/rjsf-team/react-jsonschema-form/issues/1563
const id = `${this.schema.$id}-${createRandomString()}`;
this.modifiedSchema = Object.assign(Object.assign({}, this.schema), { id: id, $id: id });
}
createValidator() {
const validator = new Ajv({
unknownFormats: 'ignore',
allErrors: true,
multipleOfPrecision: 2,
}).addFormat('integer', isInteger);
this.validator = validator.compile(this.schema);
}
getValidationErrors() {
const errors = this.validator.errors || [];
return errors.map((error) => {
let property = error.dataPath;
if (error.keyword === 'required') {
property = error.params.missingProperty;
}
return {
name: error.keyword,
property: property,
message: error.message,
schemaPath: error.schemaPath,
};
});
}
getExtraErrors(errors) {
if (!errors) {
return;
}
return mapValues(errors, (error) => {
if (Array.isArray(error)) {
return { __errors: error };
}
return this.getExtraErrors(error);
});
}
getCustomErrorMessages(originalErrors) {
if (!this.transformErrors) {
return originalErrors;
}
const errors = originalErrors.map((error) => {
return {
name: error.name,
params: error.params,
property: error.property,
message: error.message,
// For some reason 'schemaPath' is missing from the AjvError type definition:
// https://github.com/rjsf-team/react-jsonschema-form/issues/2140
schemaPath: error['schemaPath'],
};
});
// Use `.call({}, …)` here to bind `this` to an empty object to prevent
// the consumer submitted `transformErrors` from getting access to our
// component's internals. /Ads
return this.transformErrors
.call({}, errors)
.map((transformedError) => {
const originalError = originalErrors.find((error) => {
return transformedError.property === error.property;
});
return Object.assign(Object.assign({}, originalError), { message: transformedError.message });
});
}
static get is() { return "limel-form"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["form.scss"]
};
}
static get styleUrls() {
return {
"$": ["form.css"]
};
}
static get properties() {
return {
"schema": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "FormSchema",
"resolved": "FormSchema<any>",
"references": {
"FormSchema": {
"location": "import",
"path": "./form.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The schema used to render the form"
},
"defaultValue": "{}"
},
"value": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "object",
"resolved": "object",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Value of the form"
}
},
"disabled": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to disable the whole form."
},
"attribute": "disabled",
"reflect": false,
"defaultValue": "false"
},
"propsFactory": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "(schema: FormSchema) => Record<string, any>",
"resolved": "(schema: FormSchema<any>) => Record<string, any>",
"references": {
"FormSchema": {
"location": "import",
"path": "./form.types"
},
"Record": {
"location": "global"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Factory for creating properties for custom form components\n\nWhen using custom components in the form some properties might have to be\nset dynamically. If this factory is set, it will be called with the\ncurrent schema for the field for each custom component in the form. The\nfactory must return an object where each key is the name of the property\nthat should be set, along with its value."
}
},
"transformErrors": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "(errors: FormError[]) => FormError[]",
"resolved": "(errors: FormError[]) => FormError[]",
"references": {
"FormError": {
"location": "import",
"path": "./form.types"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom function to customize the default error messages"
}
},
"errors": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "ValidationError",
"resolved": "{ [key: string]: string[] | ValidationError; }",
"references": {
"ValidationError": {
"location": "import",
"path": "./form.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Extra errors to display in the form. Typical use case is asynchronous\nerrors generated server side."
}
}
};
}
static get events() {
return [{
"method": "change",
"name": "change",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when a change is made within the form"
},
"complexType": {
"original": "object",
"resolved": "object",
"references": {}
}
}, {
"method": "validate",
"name": "validate",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the validity of the form changes, or when\na change is made to an invalid form"
},
"complexType": {
"original": "ValidationStatus",
"resolved": "ValidationStatus",
"references": {
"ValidationStatus": {
"location": "import",
"path": "./form.types"
}
}
}
}];
}
static get elementRef() { return "host"; }
static get watchers() {
return [{
"propName": "schema",
"methodName": "setSchema"
}];
}
}
//# sourceMappingURL=form.js.map