UNPKG

shinkansen-pinion

Version:
688 lines (610 loc) 17.3 kB
/** * @typedef {PinionTypes.ObjectLiteralType} ObjectLiteralType * @typedef {PinionTypes.ObjectType} ObjectType * @typedef {PinionTypes.ArrayLiteralType} ArrayLiteralType * @typedef {PinionTypes.ArrayType} ArrayType * * @typedef {PinionTypes.FieldType} FieldType * * @typedef {PinionTypes.MetaType} MetaType * @typedef {PinionTypes.MetaEnumType} MetaEnumType * @typedef {PinionTypes.MetaAnyOfType} MetaAnyOfType * @typedef {PinionTypes.MetaOneOfType} MetaOneOfType * @typedef {PinionTypes.MetaFieldType} MetaFieldType * * @typedef {PinionTypes.ElementsType} ElementsType * @typedef {PinionTypes.ElementsEnumType} ElementsEnumType * @typedef {PinionTypes.ElementsAnyOfType} ElementsAnyOfType * @typedef {PinionTypes.ElementsOneOfType} ElementsOneOfType * @typedef {PinionTypes.ElementsFieldType} ElementsFieldType * * @typedef {PinionTypes.OnEventType} OnEventType * @typedef {PinionTypes.PinionType} PinionType * @typedef {PinionTypes.ParamsType} ParamsType * @typedef {PinionTypes.GroupProps} GroupProps * @typedef {PinionTypes.FieldProps} FieldProps * @typedef {PinionTypes.PinionProps} PinionProps */ import React from 'react' import PropTypes from 'prop-types' import debug from 'debug' import FieldsetSprocket from '#pinion/components/sprockets/fieldset' import RadioCog from '#pinion/components/cogs/radio' import SelectCog from '#pinion/components/cogs/select' import TextCog from '#pinion/components/cogs/text' import hasMetaEnum from '#pinion/common/meta/has-enum' import hasMetaAnyOf from '#pinion/common/meta/has-any-of' import hasMetaOneOf from '#pinion/common/meta/has-one-of' import hasMetaField from '#pinion/common/meta/has-field' import hasElementsEnum from '#pinion/common/elements/has-enum' import hasElementsAnyOf from '#pinion/common/elements/has-any-of' import hasElementsOneOf from '#pinion/common/elements/has-one-of' import hasElementsField from '#pinion/common/elements/has-field' import hasComponent from '#pinion/transformers/common/has-component' import getComponent from '#pinion/transformers/common/get-component' import { getError } from '#pinion/transformers/error-message' function DEFAULT_HANDLE_EVENT () { // } /** * @type {ParamsType} */ const DEFAULT_PARAMS = { components: {}, errors: [] } const log = debug('shinkansen-pinion/pinion') log('`shinkansen` is awake') /** * @param {string} value * @param {number} index * @returns {string} */ const getKey = (value, index) => `${value}-${index}` /** * @param {FieldType} field * @returns {string | number | boolean | object | null | undefined} */ function getDefaultValue (field) { if ('defaultValue' in field) { return ( field.defaultValue ) } } /** * @param {FieldType} field * @returns {string | number | boolean | object | null | undefined} */ function getValue (field) { if ('value' in field) { return ( field.value ) } return ( getDefaultValue(field) ) } /** * @param {MetaEnumType} meta * @param {ElementsEnumType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderToRadiosForEnum ({ items = [], selectedItems = [], uri }, { title, description, enum: { id, name = id, isRequired = false } }, { errors }, onChange = DEFAULT_HANDLE_EVENT) { log('renderToRadiosForEnum') return ( <FieldsetSprocket title={title} description={description} errorMessage={getError(errors, uri)}> {items.map((value, index) => { const key = getKey(uri, index) return ( <RadioCog key={key} title={String(value)} value={String(index)} id={key} name={name} onChange={onChange} required={isRequired} checked={selectedItems.includes(index)} /> ) })} </FieldsetSprocket> ) } /** * @param {MetaAnyOfType} meta * @param {ElementsAnyOfType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderToRadiosForAnyOf ({ items = [], selectedItems = [], uri }, { title, description, anyOf: { id, name = id, isRequired = false } }, { errors }, onChange = DEFAULT_HANDLE_EVENT) { log('renderToRadiosForAnyOf') return ( <FieldsetSprocket title={title} description={description} errorMessage={getError(errors, uri)}> {items.map((item, index) => { const { elements: { field: { id } = {} } } = item if (id) { const { meta: { uri }, elements: { title, description } } = item return ( <RadioCog key={uri} title={title} description={description} value={String(index)} id={id} name={name} onChange={onChange} required={isRequired} checked={selectedItems.includes(index)} /> ) } return null })} </FieldsetSprocket> ) } /** * @param {MetaOneOfType} meta * @param {ElementsOneOfType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderToRadiosForOneOf ({ items = [], selectedItems = [], uri }, { title, description, oneOf: { id, name = id, isRequired = false } }, { errors }, onChange = DEFAULT_HANDLE_EVENT) { log('renderToRadiosForOneOf') return ( <FieldsetSprocket title={title} description={description} errorMessage={getError(errors, uri)}> {items.map((item, index) => { const { elements: { field: { id } = {} } } = item if (id) { const { meta: { uri }, elements: { title, description } } = item return ( <RadioCog key={uri} title={title} description={description} value={String(index)} id={id} name={name} onChange={onChange} required={isRequired} checked={selectedItems.includes(index)} /> ) } return null })} </FieldsetSprocket> ) } /** * @param {MetaEnumType} meta * @param {ElementsEnumType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderToSelectForEnum ({ items = [], selectedItems = [], uri }, { title, description, enum: { id, name = id, isRequired = false } }, { errors }, onChange = DEFAULT_HANDLE_EVENT) { log('renderToSelectForEnum') return ( <SelectCog title={title} description={description} errorMessage={getError(errors, uri)} name={name} onChange={onChange} required={isRequired}> {items.map((value, index) => { return ( <option key={getKey(uri, index)} value={String(index)} selected={selectedItems.includes(index)}> {String(value)} </option> ) })} </SelectCog> ) } /** * @param {MetaAnyOfType} meta * @param {ElementsAnyOfType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderToSelectForAnyOf ({ items = [], selectedItems = [], uri }, { title, description, anyOf: { id, name = id, isRequired = false } }, { errors }, onChange = DEFAULT_HANDLE_EVENT) { log('renderToSelectForAnyOf') return ( <SelectCog title={title} description={description} errorMessage={getError(errors, uri)} name={name} onChange={onChange} required={isRequired}> {items.map(({ meta: { uri }, elements: { title } }, index) => { return ( <option key={uri} value={String(index)} selected={selectedItems.includes(index)}> {title} </option> ) })} </SelectCog> ) } /** * @param {MetaOneOfType} meta * @param {ElementsOneOfType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderToSelectForOneOf ({ items = [], selectedItems = [], uri }, { title, description, oneOf: { id, name = id, isRequired = false } }, { errors }, onChange = DEFAULT_HANDLE_EVENT) { log('renderToSelectForOneOf') return ( <SelectCog title={title} description={description} errorMessage={getError(errors, uri)} name={name} onChange={onChange} required={isRequired}> {items.map(({ meta: { uri }, elements: { title } }, index) => { return ( <option key={uri} value={String(index)} selected={selectedItems.includes(index)}> {title} </option> ) })} </SelectCog> ) } /** * @param {MetaFieldType} meta * @param {ElementsFieldType} elements * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element | null} */ export function renderToField (meta, elements, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT) { log('renderToField') const { field } = elements if (field) { const { uri } = meta const { title, description } = elements const { id, isRequired = false } = field const { errors } = params return ( <TextCog title={title} description={description} name={id} value={getValue(field)} errorMessage={getError(errors, uri)} required={isRequired} onChange={onChange} /> ) } return null } /** * @param {GroupProps & { component?: ObjectLiteralType | ObjectType }} group * @returns {React.JSX.Element | null} */ export function ComponentGroup ({ component, meta, elements, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT }) { log('ComponentGroup', component, meta, elements, params, onChange) return null } ComponentGroup.propTypes = { component: PropTypes.shape({}).isRequired, meta: PropTypes.shape({}).isRequired, elements: PropTypes.shape({}).isRequired, params: PropTypes.shape({}).isRequired, onChange: PropTypes.func } /** * @param {FieldProps & { component?: ObjectLiteralType | ObjectType }} field * @returns {React.JSX.Element | null} */ export function ComponentField ({ component, meta, elements, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT }) { log('ComponentField', component, meta, elements, params, onChange) return null } ComponentField.propTypes = { component: PropTypes.shape({}).isRequired, meta: PropTypes.shape({}).isRequired, elements: PropTypes.shape({}).isRequired, params: PropTypes.shape({}).isRequired, onChange: PropTypes.func } /** * @overload * @param {{ meta: MetaEnumType, elements: ElementsEnumType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @overload * @param {{ meta: MetaAnyOfType, elements: ElementsAnyOfType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @overload * @param {{ meta: MetaOneOfType, elements: ElementsOneOfType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @overload * @param {{ meta: MetaFieldType, elements: ElementsFieldType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @param {GroupProps} groupProps * @returns {React.JSX.Element | null} */ export function Group ({ meta, elements, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT }) { log('Group') const { fields = [] } = elements if (fields.length) { const { uri } = meta const { title, description } = elements const { errors } = params return ( <FieldsetSprocket title={title} description={description} errorMessage={getError(errors, uri)}> {fields.map((pinion) => { const { meta: { uri } = {} } = pinion return ( <Pinion key={uri} pinion={pinion} params={params} onChange={onChange} /> ) })} </FieldsetSprocket> ) } return null } Group.propTypes = { meta: PropTypes.shape({ uri: PropTypes.string.isRequired }).isRequired, elements: PropTypes.shape({ title: PropTypes.string, description: PropTypes.string, fields: PropTypes.array }).isRequired, params: PropTypes.shape({}).isRequired, onChange: PropTypes.func } /** * @overload * @param {{ meta: MetaEnumType, elements: ElementsEnumType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @overload * @param {{ meta: MetaAnyOfType, elements: ElementsAnyOfType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @overload * @param {{ meta: MetaOneOfType, elements: ElementsOneOfType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @overload * @param {{ meta: MetaFieldType, elements: ElementsFieldType, params: ParamsType, onChange: OnEventType }} fieldProps * @returns {React.JSX.Element | null} * * @param {FieldProps} fieldProps * @returns {React.JSX.Element | null} */ export function Field ({ meta, elements, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT }) { log('Field') if (hasMetaEnum(meta)) { log('Field (`enum`)') if (hasElementsEnum(elements)) { return renderToRadiosForEnum(meta, elements, params, onChange) } } else { if (hasMetaAnyOf(meta)) { log('Field (`anyOf`)') if (hasElementsAnyOf(elements)) { return renderToRadiosForAnyOf(meta, elements, params, onChange) } } else { if (hasMetaOneOf(meta)) { log('Field (`oneOf`)') if (hasElementsOneOf(elements)) { return renderToRadiosForOneOf(meta, elements, params, onChange) } } else { if (hasMetaField(meta)) { log('Field (`field`)') if (hasElementsField(elements)) { return renderToField(meta, elements, params, onChange) } } } } } return null } Field.propTypes = { meta: PropTypes.shape({ uri: PropTypes.string.isRequired }).isRequired, elements: PropTypes.shape({ title: PropTypes.string, description: PropTypes.string, fields: PropTypes.array }).isRequired, params: PropTypes.shape({}).isRequired, onChange: PropTypes.func } /** * @param {PinionType} pinion * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderGroup ({ meta, elements }, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT) { log('renderGroup') if (hasComponent(meta)) { const component = getComponent(meta) return ( <ComponentGroup component={component} meta={meta} elements={elements} params={params} onChange={onChange} /> ) } return ( <Group meta={meta} elements={elements} params={params} onChange={onChange} /> ) } /** * @param {PinionType} pinion * @param {ParamsType} params * @param {OnEventType} onChange * @returns {React.JSX.Element} */ export function renderField ({ meta, elements }, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT) { log('renderField') if (hasComponent(meta)) { const component = getComponent(meta) return ( <ComponentField component={component} meta={meta} elements={elements} params={params} onChange={onChange} /> ) } return ( <Field meta={meta} elements={elements} params={params} onChange={onChange} /> ) } /** * @param {PinionProps} pinionProps * @returns {React.JSX.Element} */ export default function Pinion ({ pinion, params = DEFAULT_PARAMS, onChange = DEFAULT_HANDLE_EVENT }) { log('Pinion') const { meta: { type } = {} } = pinion if (type === 'object' || type === 'array') { return ( renderGroup(pinion, params, onChange) ) } return ( renderField(pinion, params, onChange) ) } Pinion.propTypes = { pinion: PropTypes.shape({ meta: PropTypes.shape({}).isRequired, elements: PropTypes.shape({}).isRequired }).isRequired, params: PropTypes.shape({ components: PropTypes.shape({}), errors: PropTypes.arrayOf(PropTypes.shape({})) }), onChange: PropTypes.func }