UNPKG

@limetech/lime-elements

Version:
204 lines (203 loc) 8.19 kB
import JSONSchemaField from '@rjsf/core/lib/components/fields/SchemaField'; import React from 'react'; import { isEmpty, capitalize } from 'lodash-es'; import { resetDependentFields } from './field-helpers'; import { FieldTemplate } from '../templates'; import { getHelpComponent } from '../help'; import { TimePicker } from '../widgets/time-picker'; /** * If given a value and schema, check if the value should be translated * from null to undefined to avoid issues with validation * * This function needs to be used for several reasons: * 1. CustomEvent does not allow `detail` to equal `undefined`, but we can call onChange with `undefined` in React * 2. `null` is treated as a valid value in a jsonschema and with marshmallow and it has its own type (null) * 3. Without changing `null` to `undefined` there would be no way to remove a field from * from the submitted form data once it had any data. (when POSTing, undefined is not posted since its not valid json) * 4. The only applies to custom web components since widgets handle transforming null/'' to undefined depending on the widget * and its purpose. * * Example: * If I have an object `{ name?: string, email?: string }` that I am using a custom web component for `name`, * then initially, the formData will be `{}` which is fine because neither name or email are required. Then if i input a * value for name the formData will be `{ name: 'some_value' }` which is also fine. But then if I want to remove name * from the form data I would delete all the text from the name input field. Web components would emit this empty value * as '' or null. If the web component tries to emit `undefined`, null would be emitted instead because CustomEvent has * a default `detail` value of null * * @param value - the value associated with the schema * @param schema - the schema for the value * @returns whether or not null should be changed to undefined */ const shouldChangeToUndefined = (value, schema) => { return value === null && !schema.type.includes('null'); }; const hasCustomComponent = (schema) => { var _a, _b; const name = (_b = (_a = schema.lime) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.name; if (!name) { return false; } try { verifyCustomComponentIsDefined(name); } catch (_c) { console.warn(`Custom component ${name} not defined`); return false; } return true; }; const verifyCustomComponentIsDefined = (elementName) => { const supportsCustomElements = 'customElements' in window; if (!supportsCustomElements) { throw new Error('Custom form elements are not supported by this browser!'); } if (!customElements.get(elementName)) { throw new Error(`Custom form element '${elementName}' is not defined!`); } }; const getCustomComponent = (schema) => { var _a, _b, _c, _d; const name = (_b = (_a = schema.lime) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.name; const props = ((_d = (_c = schema.lime) === null || _c === void 0 ? void 0 : _c.component) === null || _d === void 0 ? void 0 : _d.props) || {}; return { name: name, props: props }; }; /** * Create properties from the factory that is set on `limel-form` * * @param formContext - the form context * @param schema - the schema for the current field * @returns the properties created by the factory */ export function getFactoryProps(formContext, schema) { const factory = formContext.propsFactory; if (typeof factory !== 'function') { return {}; } const props = factory(schema); if (!props) { return {}; } return props; } export class SchemaField extends React.Component { constructor(props) { super(props); this.state = { modified: false, }; this.handleChange = this.handleChange.bind(this); this.handleCustomComponentChange = this.handleCustomComponentChange.bind(this); } hasValue() { const value = this.getValue(); if (!value) { return false; } if (Array.isArray(value)) { return value.length > 0; } if (value instanceof Date) { return true; } if (typeof value === 'object') { return !isEmpty(value); } return true; } getLabel() { const { schema } = this.props; return schema.title; } isInvalid() { const { modified } = this.state; const { errorSchema } = this.props; return ((modified || this.hasValue() || !this.isRequired()) && !isEmpty(errorSchema)); } isRequired() { const { required, schema } = this.props; return required || schema.minItems > 0; } getHelperText() { var _a, _b, _c; const { errorSchema, schema } = this.props; if (!this.isInvalid()) { const helperText = (_c = (_b = (_a = schema.lime) === null || _a === void 0 ? void 0 : _a.component) === null || _b === void 0 ? void 0 : _b.props) === null || _c === void 0 ? void 0 : _c.helperText; return helperText || schema.description; } if (!isEmpty(errorSchema) && '__errors' in errorSchema) { return capitalize(errorSchema.__errors[0]); } return schema.description; } getValue() { const { formData } = this.props; return formData; } handleCustomComponentChange(event) { const { schema } = this.props; event.stopPropagation(); let value = event.nativeEvent.detail; if (shouldChangeToUndefined(value, schema)) { value = undefined; } this.handleChange(value); } handleChange(data) { const { formData, schema } = this.props; const { rootSchema } = this.props.registry; this.setState({ modified: true }); const newData = resetDependentFields(formData, data, schema, rootSchema); this.props.onChange(newData); } buildCustomComponentProps() { const { disabled, readonly, name, registry, schema, errorSchema, idSchema, } = this.props; const factoryProps = getFactoryProps(registry.formContext, schema); return Object.assign(Object.assign({}, factoryProps), { value: this.getValue(), required: this.isRequired(), readonly: readonly || schema.readOnly, disabled: disabled, invalid: this.isInvalid(), label: this.getLabel(), helperText: this.getHelperText(), ref: (element) => { element.formInfo = { schema: schema, rootSchema: registry.formContext.schema, errorSchema: errorSchema, rootValue: registry.formContext.rootValue, name: name, schemaPath: this.getSchemaPath(idSchema === null || idSchema === void 0 ? void 0 : idSchema.$id), }; return () => { }; } }); } renderCustomComponent(props) { const { name, props: userDefinedComponentProps } = getCustomComponent(props.schema); verifyCustomComponentIsDefined(name); const component = React.createElement(name, Object.assign(Object.assign(Object.assign({}, userDefinedComponentProps), this.buildCustomComponentProps()), { onChange: this.handleCustomComponentChange })); return React.createElement(FieldTemplate, Object.assign(Object.assign({}, this.props), { classNames: 'form-group field field-custom' }), component, getHelpComponent(props.schema)); } render() { if (hasCustomComponent(this.props.schema)) { return this.renderCustomComponent(this.props); } const fieldProps = Object.assign(Object.assign({}, this.props), { onChange: this.handleChange }); if (this.props.schema.format === 'time') { fieldProps.uiSchema = Object.assign({ 'ui:widget': TimePicker }, fieldProps.uiSchema); } return React.createElement(JSONSchemaField, fieldProps); } /** * Gets the path to the current property within the schema * * @param schemaId - the id of the schema * @returns an array with the schema path for the current property * @example * const schemaId = 'root_sections_0_controls_0_name'; * const schemaPath = getSchemaPath(schemaId); * // ➡ ['sections', '0', 'controls', '0', 'name'] */ getSchemaPath(schemaId) { if (schemaId === undefined) { return undefined; } return schemaId.replace('root_', '').split('_'); } } //# sourceMappingURL=schema-field.js.map