import React, { useCallback, useMemo } from 'react' import { composePaths, computeLabel, createDefaultValue, isObjectArrayWithNesting, isPrimitiveArrayControl, rankWith, findUISchema, Resolve, } from '@jsonforms/core' import { JsonFormsDispatch, withJsonFormsArrayLayoutProps, } from '@jsonforms/react' import range from 'lodash/range' import merge from 'lodash/merge' import { Box, IconButton, Tooltip, Typography } from '@material-ui/core' import { Add, Delete } from '@material-ui/icons' import { makeStyles } from '@material-ui/core/styles' const useStyles = makeStyles((theme) => ({ arrayItem: { position: 'relative', padding: theme.spacing(2), marginBottom: theme.spacing(2), border: `1px solid ${theme.palette.divider}`, borderRadius: theme.shape.borderRadius, '&:last-child': { marginBottom: 0, }, }, deleteButton: { position: 'absolute', top: theme.spacing(1), right: theme.spacing(1), }, itemContent: { paddingRight: theme.spacing(4), // Space for delete button }, })) // Default translations for array controls const defaultTranslations = { addTooltip: 'Add', addAriaLabel: 'Add button', removeTooltip: 'Delete', removeAriaLabel: 'Delete button', noDataMessage: 'No data', } // Simplified array item renderer - clean card layout // eslint-disable-next-line react-refresh/only-export-components const ArrayItem = ({ index, path, schema, uischema, uischemas, rootSchema, renderers, cells, enabled, removeItems, translations, disableRemove, }) => { const classes = useStyles() const childPath = composePaths(path, `${index}`) const foundUISchema = useMemo( () => findUISchema( uischemas, schema, uischema.scope, path, undefined, uischema, rootSchema, ), [uischemas, schema, path, uischema, rootSchema], ) return ( {enabled && !disableRemove && ( removeItems(path, [index])()} size="small" aria-label={translations.removeAriaLabel} > )} ) } // Array toolbar with add button // eslint-disable-next-line react-refresh/only-export-components const ArrayToolbar = ({ label, description, enabled, addItem, path, createDefault, translations, disableAdd, }) => ( {label} {!disableAdd && ( )} {description && ( {description} )} ) const useArrayStyles = makeStyles((theme) => ({ container: { marginBottom: theme.spacing(2), }, })) // Main array layout component - items always expanded // eslint-disable-next-line react-refresh/only-export-components const AlwaysExpandedArrayLayoutComponent = (props) => { const arrayClasses = useArrayStyles() const { enabled, data, path, schema, uischema, addItem, removeItems, renderers, cells, label, description, required, rootSchema, config, uischemas, disableAdd, disableRemove, } = props const innerCreateDefaultValue = useCallback( () => createDefaultValue(schema, rootSchema), [schema, rootSchema], ) const appliedUiSchemaOptions = merge({}, config, uischema.options) const doDisableAdd = disableAdd || appliedUiSchemaOptions.disableAdd const doDisableRemove = disableRemove || appliedUiSchemaOptions.disableRemove const translations = defaultTranslations return (
{data > 0 ? ( range(data).map((index) => ( )) ) : ( {translations.noDataMessage} )}
) } // Wrap with JSONForms HOC const WrappedArrayLayout = withJsonFormsArrayLayoutProps( AlwaysExpandedArrayLayoutComponent, ) // Custom tester that matches arrays but NOT enum arrays // Enum arrays should be handled by MaterialEnumArrayRenderer (for checkboxes) const isNonEnumArrayControl = (uischema, schema) => { // First check if it matches our base conditions (object array or primitive array) const baseCheck = isObjectArrayWithNesting(uischema, schema) || isPrimitiveArrayControl(uischema, schema) if (!baseCheck) { return false } // Resolve the actual schema for this control using JSONForms utility const rootSchema = schema const resolved = Resolve.schema(rootSchema, uischema?.scope, rootSchema) // Exclude enum arrays (uniqueItems + oneOf/enum) - let MaterialEnumArrayRenderer handle them if (resolved?.uniqueItems && resolved?.items) { const { items } = resolved if (items.oneOf?.every((e) => e.const !== undefined) || items.enum) { return false } } return true } // Export as a renderer entry with high priority (5 > default 4) // Matches both object arrays with nesting and primitive arrays, but NOT enum arrays export const AlwaysExpandedArrayLayout = { tester: rankWith(5, isNonEnumArrayControl), renderer: WrappedArrayLayout, }