UNPKG

@jsonforms/material-renderers

Version:

Material Renderer Set for JSON Forms

779 lines (746 loc) 101 kB
import React, { useState, useCallback, useMemo, Fragment, useEffect } from 'react'; import { rankWith, isAllOfControl, findMatchingUISchema, createCombinatorRenderInfos, Generate, isLayout, isAnyOfControl, createDefaultValue, Resolve, encode, Paths, formatErrorMessage, errorAt, or, isObjectArrayControl, isPrimitiveArrayControl, isDescriptionHidden, getAjv, and, uiTypeIs, schemaMatches, hasType, schemaSubPathMatches, resolveSchema, showAsRequired, isObjectControl, findUISchema, isOneOfControl, isObjectArray, computeLabel, composePaths, isBooleanControl, optionIs, isDateControl, defaultDateFormat, isDateTimeControl, defaultDateTimeFormat, isEnumControl, isIntegerControl, isTimeControl, isNumberControl, isOneOfEnumControl, isRangeControl, isStringControl, defaultTimeFormat, createId, removeId, update, moveUp, moveDown, computeChildLabel, withIncreasedRank, isVisible, deriveLabelForUISchemaElement, isObjectArrayWithNesting, isNumberFormatControl, categorizationHasCategory } from '@jsonforms/core'; import { withJsonFormsAllOfProps, JsonFormsDispatch, withJsonFormsAnyOfProps, DispatchCell, useJsonForms, withJsonFormsArrayLayoutProps, withTranslateProps, withArrayTranslationProps, withJsonFormsMultiEnumProps, withJsonFormsDetailProps, withJsonFormsOneOfProps, withJsonFormsLabelProps, withJsonFormsMasterListItemProps, withJsonFormsControlProps, Control, withJsonFormsEnumProps, withJsonFormsOneOfEnumProps, withJsonFormsContext, withJsonFormsLayoutProps, withJsonFormsCellProps, withJsonFormsEnumCellProps, withJsonFormsOneOfEnumCellProps } from '@jsonforms/react'; import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button, Tabs, Tab, TableCell, styled as styled$1, Badge, Tooltip, TableRow, Stack, Grid, Typography, FormHelperText, IconButton, Table, TableHead, TableBody, Autocomplete, TextField, Checkbox, useThemeProps, Input, FilledInput, OutlinedInput, useTheme, InputAdornment, Select, MenuItem, Switch, FormControl, FormLabel, FormGroup, FormControlLabel, Toolbar, ListItemButton, ListItemAvatar, Avatar, ListItemText, ListItemSecondaryAction, List, InputLabel, RadioGroup, Radio, Slider, Accordion, AccordionSummary, AccordionDetails, Card, CardHeader, CardContent, AppBar, Stepper, Step, StepButton } from '@mui/material'; import omit from 'lodash/omit'; import isEmpty from 'lodash/isEmpty'; import union from 'lodash/union'; import startCase from 'lodash/startCase'; import range from 'lodash/range'; import DeleteIcon from '@mui/icons-material/Delete'; import ArrowDownward from '@mui/icons-material/ArrowDownward'; import ArrowUpward from '@mui/icons-material/ArrowUpward'; import { styled } from '@mui/material/styles'; import AddIcon from '@mui/icons-material/Add'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import merge from 'lodash/merge'; import dayjs from 'dayjs'; import customParsing from 'dayjs/plugin/customParseFormat'; import debounce from 'lodash/debounce'; import Close from '@mui/icons-material/Close'; import map from 'lodash/map'; import { LocalizationProvider, DatePicker, DateTimePicker, TimePicker } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; const MaterialAllOfRenderer = ({ schema, rootSchema, visible, renderers, cells, path, uischemas, uischema, }) => { const delegateUISchema = findMatchingUISchema(uischemas)(schema, uischema.scope, path); if (!visible) { return null; } if (delegateUISchema) { return (React.createElement(JsonFormsDispatch, { schema: schema, uischema: delegateUISchema, path: path, renderers: renderers, cells: cells })); } const allOfRenderInfos = createCombinatorRenderInfos(schema.allOf, rootSchema, 'allOf', uischema, path, uischemas); return (React.createElement(React.Fragment, null, allOfRenderInfos.map((allOfRenderInfo, allOfIndex) => (React.createElement(JsonFormsDispatch, { key: allOfIndex, schema: allOfRenderInfo.schema, uischema: allOfRenderInfo.uischema, path: path, renderers: renderers, cells: cells }))))); }; const materialAllOfControlTester = rankWith(3, isAllOfControl); var MaterialAllOfRenderer$1 = withJsonFormsAllOfProps(MaterialAllOfRenderer); class CombinatorProperties extends React.Component { render() { const { schema, combinatorKeyword, path, rootSchema } = this.props; const otherProps = omit(schema, combinatorKeyword); const foundUISchema = Generate.uiSchema(otherProps, 'VerticalLayout', undefined, rootSchema); let isLayoutWithElements = false; if (foundUISchema !== null && isLayout(foundUISchema)) { isLayoutWithElements = foundUISchema.elements.length > 0; } if (isLayoutWithElements) { return (React.createElement(JsonFormsDispatch, { schema: otherProps, path: path, uischema: foundUISchema })); } return null; } } const TabSwitchConfirmDialog = ({ open, handleClose, confirm, cancel, id, }) => { return (React.createElement(Dialog, { open: open, onClose: handleClose, "aria-labelledby": 'alert-dialog-title', "aria-describedby": 'alert-dialog-description' }, React.createElement(DialogTitle, { id: 'alert-dialog-title' }, 'Clear form?'), React.createElement(DialogContent, null, React.createElement(DialogContentText, { id: 'alert-dialog-description' }, "Your data will be cleared if you navigate away from this tab. Do you want to proceed?")), React.createElement(DialogActions, null, React.createElement(Button, { onClick: cancel, color: 'primary' }, "No"), React.createElement(Button, { onClick: confirm, color: 'primary', autoFocus: true, id: `${id}-confirm-yes` }, "Yes")))); }; const MaterialAnyOfRenderer = ({ handleChange, schema, rootSchema, indexOfFittingSchema, visible, path, renderers, cells, uischema, uischemas, id, data, }) => { const [selectedAnyOf, setSelectedAnyOf] = useState(indexOfFittingSchema || 0); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [newSelectedIndex, setNewSelectedIndex] = useState(0); const handleClose = useCallback(() => setConfirmDialogOpen(false), [setConfirmDialogOpen]); const handleTabChange = useCallback((_event, newIndex) => { if (isEmpty(data) || typeof data === typeof createDefaultValue(anyOfRenderInfos[newIndex].schema, rootSchema)) { setSelectedAnyOf(newIndex); } else { setNewSelectedIndex(newIndex); setConfirmDialogOpen(true); } }, [setConfirmDialogOpen, setSelectedAnyOf, data]); const openNewTab = (newIndex) => { handleChange(path, createDefaultValue(anyOfRenderInfos[newIndex].schema, rootSchema)); setSelectedAnyOf(newIndex); }; const confirm = useCallback(() => { openNewTab(newSelectedIndex); setConfirmDialogOpen(false); }, [handleChange, createDefaultValue, newSelectedIndex]); const anyOf = 'anyOf'; const anyOfRenderInfos = createCombinatorRenderInfos(schema.anyOf, rootSchema, anyOf, uischema, path, uischemas); if (!visible) { return null; } return (React.createElement(React.Fragment, null, React.createElement(CombinatorProperties, { schema: schema, combinatorKeyword: anyOf, path: path, rootSchema: rootSchema }), React.createElement(Tabs, { value: selectedAnyOf, onChange: handleTabChange }, anyOfRenderInfos.map((anyOfRenderInfo) => (React.createElement(Tab, { key: anyOfRenderInfo.label, label: anyOfRenderInfo.label })))), anyOfRenderInfos.map((anyOfRenderInfo, anyOfIndex) => selectedAnyOf === anyOfIndex && (React.createElement(JsonFormsDispatch, { key: anyOfIndex, schema: anyOfRenderInfo.schema, uischema: anyOfRenderInfo.uischema, path: path, renderers: renderers, cells: cells }))), React.createElement(TabSwitchConfirmDialog, { cancel: handleClose, confirm: confirm, id: 'anyOf-' + id, open: confirmDialogOpen, handleClose: handleClose }))); }; const materialAnyOfControlTester = rankWith(3, isAnyOfControl); var MaterialAnyOfRenderer$1 = withJsonFormsAnyOfProps(MaterialAnyOfRenderer); const StyledTableCell = styled(TableCell)({ borderBottom: 'none', }); const NoBorderTableCell = ({ children, ...otherProps }) => (React.createElement(StyledTableCell, { ...otherProps }, children)); const StyledBadge = styled$1(Badge)(({ theme }) => ({ color: theme.palette.error.main, })); const ValidationIcon = ({ errorMessages, id }) => { return (React.createElement(Tooltip, { id: id, title: errorMessages }, React.createElement(StyledBadge, { badgeContent: errorMessages.split('\n').length }, React.createElement(ErrorOutlineIcon, { color: 'inherit' })))); }; const fixedCellSmall = { paddingLeft: 0, paddingRight: 0, }; const TableToolbar = React.memo(function TableToolbar({ numColumns, errors, label, description, path, addItem, schema, enabled, translations, rootSchema, disableAdd, }) { return (React.createElement(TableRow, null, React.createElement(NoBorderTableCell, { colSpan: numColumns }, React.createElement(Stack, null, React.createElement(Grid, { container: true, justifyContent: 'flex-start', alignItems: 'center', spacing: 2 }, React.createElement(Grid, { item: true }, React.createElement(Typography, { variant: 'h6' }, label)), React.createElement(Grid, { item: true }, errors.length !== 0 && (React.createElement(Grid, { item: true }, React.createElement(ValidationIcon, { id: 'tooltip-validation', errorMessages: errors }))))), description && React.createElement(FormHelperText, null, description))), enabled && !disableAdd ? (React.createElement(NoBorderTableCell, { align: 'right', style: fixedCellSmall }, React.createElement(Tooltip, { id: 'tooltip-add', title: translations.addTooltip, placement: 'bottom' }, React.createElement(IconButton, { "aria-label": translations.addAriaLabel, onClick: addItem(path, createDefaultValue(schema, rootSchema)), size: 'large' }, React.createElement(AddIcon, null))))) : null)); }); const styles = { fixedCell: { width: '150px', height: '50px', paddingLeft: 0, paddingRight: 0, textAlign: 'center', }, fixedCellSmall: { width: '50px', height: '50px', paddingLeft: 0, paddingRight: 0, textAlign: 'center', }, }; const generateCells = (Cell, schema, rowPath, enabled, cells) => { if (schema.type === 'object') { return getValidColumnProps(schema).map((prop) => { const cellPath = Paths.compose(rowPath, prop); const props = { propName: prop, schema, title: schema.properties?.[prop]?.title ?? startCase(prop), rowPath, cellPath, enabled, cells, }; return React.createElement(Cell, { key: cellPath, ...props }); }); } else { const props = { schema, rowPath, cellPath: rowPath, enabled, }; return React.createElement(Cell, { key: rowPath, ...props }); } }; const getValidColumnProps = (scopedSchema) => { if (scopedSchema.type === 'object' && typeof scopedSchema.properties === 'object') { return Object.keys(scopedSchema.properties).filter((prop) => scopedSchema.properties[prop].type !== 'array'); } return ['']; }; const EmptyTable = ({ numColumns, translations }) => (React.createElement(TableRow, null, React.createElement(NoBorderTableCell, { colSpan: numColumns }, React.createElement(Typography, { align: 'center' }, translations.noDataMessage)))); const TableHeaderCell = React.memo(function TableHeaderCell({ title, }) { return React.createElement(TableCell, null, title); }); const ctxToNonEmptyCellProps = (ctx, ownProps) => { const path = ownProps.rowPath + (ownProps.schema.type === 'object' ? '.' + ownProps.propName : ''); const errors = formatErrorMessage(union(errorAt(path, ownProps.schema)(ctx.core).map((error) => error.message))); return { rowPath: ownProps.rowPath, propName: ownProps.propName, schema: ownProps.schema, rootSchema: ctx.core.schema, errors, path, enabled: ownProps.enabled, cells: ownProps.cells || ctx.cells, renderers: ownProps.renderers || ctx.renderers, }; }; const controlWithoutLabel = (scope) => ({ type: 'Control', scope: scope, label: false, }); const NonEmptyCellComponent = React.memo(function NonEmptyCellComponent({ path, propName, schema, rootSchema, errors, enabled, renderers, cells, isValid, }) { return (React.createElement(NoBorderTableCell, null, schema.properties ? (React.createElement(DispatchCell, { schema: Resolve.schema(schema, `#/properties/${encode(propName)}`, rootSchema), uischema: controlWithoutLabel(`#/properties/${encode(propName)}`), path: path, enabled: enabled, renderers: renderers, cells: cells })) : (React.createElement(DispatchCell, { schema: schema, uischema: controlWithoutLabel('#'), path: path, enabled: enabled, renderers: renderers, cells: cells })), React.createElement(FormHelperText, { error: !isValid }, !isValid && errors))); }); const NonEmptyCell = (ownProps) => { const ctx = useJsonForms(); const emptyCellProps = ctxToNonEmptyCellProps(ctx, ownProps); const isValid = isEmpty(emptyCellProps.errors); return React.createElement(NonEmptyCellComponent, { ...emptyCellProps, isValid: isValid }); }; const NonEmptyRowComponent = ({ childPath, schema, rowIndex, openDeleteDialog, moveUpCreator, moveDownCreator, enableUp, enableDown, showSortButtons, enabled, cells, path, translations, disableRemove, }) => { const moveUp = useMemo(() => moveUpCreator(path, rowIndex), [moveUpCreator, path, rowIndex]); const moveDown = useMemo(() => moveDownCreator(path, rowIndex), [moveDownCreator, path, rowIndex]); return (React.createElement(TableRow, { key: childPath, hover: true }, generateCells(NonEmptyCell, schema, childPath, enabled, cells), enabled ? (React.createElement(NoBorderTableCell, { style: showSortButtons ? styles.fixedCell : styles.fixedCellSmall }, React.createElement(Grid, { container: true, direction: 'row', justifyContent: 'flex-end', alignItems: 'center' }, showSortButtons ? (React.createElement(Fragment, null, React.createElement(Grid, { item: true }, React.createElement(Tooltip, { id: 'tooltip-up', title: translations.up, placement: 'bottom', open: enableUp ? undefined : false }, React.createElement(IconButton, { "aria-label": translations.upAriaLabel, onClick: moveUp, disabled: !enableUp, size: 'large' }, React.createElement(ArrowUpward, null)))), React.createElement(Grid, { item: true }, React.createElement(Tooltip, { id: 'tooltip-down', title: translations.down, placement: 'bottom', open: enableDown ? undefined : false }, React.createElement(IconButton, { "aria-label": translations.downAriaLabel, onClick: moveDown, disabled: !enableDown, size: 'large' }, React.createElement(ArrowDownward, null)))))) : null, !disableRemove ? (React.createElement(Grid, { item: true }, React.createElement(Tooltip, { id: 'tooltip-remove', title: translations.removeTooltip, placement: 'bottom' }, React.createElement(IconButton, { "aria-label": translations.removeAriaLabel, onClick: () => openDeleteDialog(childPath, rowIndex), size: 'large' }, React.createElement(DeleteIcon, null))))) : null))) : null)); }; const NonEmptyRow = React.memo(NonEmptyRowComponent); const TableRows = ({ data, path, schema, openDeleteDialog, moveUp, moveDown, uischema, config, enabled, cells, translations, disableRemove, }) => { const isEmptyTable = data === 0; if (isEmptyTable) { return (React.createElement(EmptyTable, { numColumns: getValidColumnProps(schema).length + 1, translations: translations })); } const appliedUiSchemaOptions = merge({}, config, uischema.options); return (React.createElement(React.Fragment, null, range(data).map((index) => { const childPath = Paths.compose(path, `${index}`); return (React.createElement(NonEmptyRow, { key: childPath, childPath: childPath, rowIndex: index, schema: schema, openDeleteDialog: openDeleteDialog, moveUpCreator: moveUp, moveDownCreator: moveDown, enableUp: index !== 0, enableDown: index !== data - 1, showSortButtons: appliedUiSchemaOptions.showSortButtons || appliedUiSchemaOptions.showArrayTableSortButtons, enabled: enabled, cells: cells, path: path, translations: translations, disableRemove: disableRemove })); }))); }; class MaterialTableControl extends React.Component { constructor() { super(...arguments); this.addItem = (path, value) => this.props.addItem(path, value); } render() { const { label, description, path, schema, rootSchema, uischema, errors, openDeleteDialog, visible, enabled, cells, translations, disableAdd, disableRemove, config, } = this.props; const appliedUiSchemaOptions = merge({}, config, uischema.options); const doDisableAdd = disableAdd || appliedUiSchemaOptions.disableAdd; const doDisableRemove = disableRemove || appliedUiSchemaOptions.disableRemove; const controlElement = uischema; const isObjectSchema = schema.type === 'object'; const headerCells = isObjectSchema ? generateCells(TableHeaderCell, schema, path, enabled, cells) : undefined; if (!visible) { return null; } return (React.createElement(Table, null, React.createElement(TableHead, null, React.createElement(TableToolbar, { errors: errors, label: label, description: description, addItem: this.addItem, numColumns: isObjectSchema ? headerCells.length : 1, path: path, uischema: controlElement, schema: schema, rootSchema: rootSchema, enabled: enabled, translations: translations, disableAdd: doDisableAdd }), isObjectSchema && (React.createElement(TableRow, null, headerCells, enabled ? React.createElement(TableCell, null) : null))), React.createElement(TableBody, null, React.createElement(TableRows, { openDeleteDialog: openDeleteDialog, translations: translations, ...this.props, disableRemove: doDisableRemove })))); } } const DeleteDialog = React.memo(function DeleteDialog({ open, onClose, onConfirm, onCancel, title, message, acceptText, declineText, }) { return (React.createElement(Dialog, { open: open, keepMounted: true, onClose: onClose, "aria-labelledby": 'alert-dialog-confirmdelete-title', "aria-describedby": 'alert-dialog-confirmdelete-description' }, React.createElement(DialogTitle, { id: 'alert-dialog-confirmdelete-title' }, title), React.createElement(DialogContent, null, React.createElement(DialogContentText, { id: 'alert-dialog-confirmdelete-description' }, message)), React.createElement(DialogActions, null, React.createElement(Button, { onClick: onCancel, color: 'primary' }, declineText), React.createElement(Button, { onClick: onConfirm, color: 'primary' }, acceptText)))); }); const MaterialArrayControlRenderer = (props) => { const [open, setOpen] = useState(false); const [path, setPath] = useState(undefined); const [rowData, setRowData] = useState(undefined); const { removeItems, visible, translations } = props; const openDeleteDialog = useCallback((p, rowIndex) => { setOpen(true); setPath(p); setRowData(rowIndex); }, [setOpen, setPath, setRowData]); const deleteCancel = useCallback(() => setOpen(false), [setOpen]); const deleteConfirm = useCallback(() => { const p = path.substring(0, path.lastIndexOf('.')); removeItems(p, [rowData])(); setOpen(false); }, [setOpen, path, rowData]); const deleteClose = useCallback(() => setOpen(false), [setOpen]); if (!visible) { return null; } return (React.createElement(React.Fragment, null, React.createElement(MaterialTableControl, { ...props, openDeleteDialog: openDeleteDialog, translations: translations }), React.createElement(DeleteDialog, { open: open, onCancel: deleteCancel, onConfirm: deleteConfirm, onClose: deleteClose, acceptText: translations.deleteDialogAccept, declineText: translations.deleteDialogDecline, title: translations.deleteDialogTitle, message: translations.deleteDialogMessage }))); }; const materialArrayControlTester = rankWith(3, or(isObjectArrayControl, isPrimitiveArrayControl)); var MaterialArrayControlRenderer$1 = withJsonFormsArrayLayoutProps(withTranslateProps(withArrayTranslationProps(MaterialArrayControlRenderer))); const useFocus = () => { const [focused, setFocused] = useState(false); const onFocus = useCallback(() => setFocused(true), []); const onBlur = useCallback(() => setFocused(false), []); return [focused, onFocus, onBlur]; }; const MuiAutocomplete = (props) => { const { description, errors, visible, required, label, data, className, id, enabled, uischema, path, handleChange, options, config, getOptionLabel, renderOption, filterOptions, isValid, } = props; const appliedUiSchemaOptions = merge({}, config, uischema.options); const [inputValue, setInputValue] = React.useState(data ?? ''); const [focused, onFocus, onBlur] = useFocus(); const findOption = options.find((o) => o.value === data) ?? null; const showDescription = !isDescriptionHidden(visible, description, focused, appliedUiSchemaOptions.showUnfocusedDescription); const firstFormHelperText = showDescription ? description : !isValid ? errors : null; const secondFormHelperText = showDescription && !isValid ? errors : null; if (!visible) { return null; } return (React.createElement(React.Fragment, null, React.createElement(Autocomplete, { className: className, id: id, disabled: !enabled, value: findOption, onChange: (_event, newValue) => { handleChange(path, newValue?.value); }, inputValue: inputValue, onInputChange: (_event, newInputValue) => { setInputValue(newInputValue); }, autoHighlight: true, autoComplete: true, fullWidth: true, options: options, getOptionLabel: getOptionLabel || ((option) => option?.label), freeSolo: false, renderInput: (params) => { return (React.createElement(TextField, { label: label, type: 'text', inputProps: params.inputProps, inputRef: params.InputProps.ref, autoFocus: appliedUiSchemaOptions.focus, disabled: !enabled, ...params, id: id, required: required && !appliedUiSchemaOptions.hideRequiredAsterisk, error: !isValid, fullWidth: !appliedUiSchemaOptions.trim, InputLabelProps: data ? { shrink: true } : undefined, onFocus: onFocus, onBlur: onBlur, focused: focused })); }, renderOption: renderOption, filterOptions: filterOptions }), React.createElement(FormHelperText, { error: !isValid && !showDescription }, firstFormHelperText), React.createElement(FormHelperText, { error: !isValid }, secondFormHelperText))); }; const MuiCheckbox = React.memo(function MuiCheckbox(props) { const { data, className, id, enabled, uischema, path, handleChange, config, inputProps, } = props; const appliedUiSchemaOptions = merge({}, config, uischema.options); const inputPropsMerged = merge({}, inputProps, { autoFocus: !!appliedUiSchemaOptions.focus, }); const checked = !!data; return (React.createElement(Checkbox, { checked: checked, onChange: (_ev, isChecked) => handleChange(path, isChecked), className: className, id: id, disabled: !enabled, inputProps: inputPropsMerged })); }); dayjs.extend(customParsing); const createOnChangeHandler = (path, handleChange, saveFormat) => (value) => { if (!value) { handleChange(path, undefined); } else if (value.toString() !== 'Invalid Date') { const formatedDate = formatDate(value, saveFormat); handleChange(path, formatedDate); } }; const createOnBlurHandler = (path, handleChange, format, saveFormat, rerenderChild, onBlur) => (e) => { const date = dayjs(e.target.value, format); const formatedDate = formatDate(date, saveFormat); if (formatedDate.toString() === 'Invalid Date') { handleChange(path, undefined); rerenderChild(); } else { handleChange(path, formatedDate); } onBlur(); }; const formatDate = (date, saveFormat) => { let formatedDate = date.format(saveFormat); const indexOfYear = saveFormat.indexOf('YYYY'); if (date.year() < 1000 && indexOfYear !== -1) { const stringUpToYear = formatedDate.slice(0, indexOfYear); const stringFromYear = formatedDate.slice(indexOfYear); if (date.year() >= 100) { formatedDate = [stringUpToYear, 0, stringFromYear].join(''); } else if (date.year() >= 10) { formatedDate = [stringUpToYear, 0, 0, stringFromYear].join(''); } else if (date.year() >= 1) { formatedDate = [stringUpToYear, 0, 0, 0, stringFromYear].join(''); } } return formatedDate; }; const getData = (data, saveFormat) => { if (!data) { return null; } const dayjsData = dayjs(data, saveFormat); if (dayjsData.toString() === 'Invalid Date') { return null; } return dayjsData; }; const renderLayoutElements = (elements, schema, path, enabled, renderers, cells) => { return elements.map((child, index) => (React.createElement(Grid, { item: true, key: `${path}-${index}`, xs: true }, React.createElement(JsonFormsDispatch, { uischema: child, schema: schema, path: path, enabled: enabled, renderers: renderers, cells: cells })))); }; const MaterialLayoutRendererComponent = ({ visible, elements, schema, path, enabled, direction, renderers, cells, }) => { if (isEmpty(elements) || !visible) { return null; } else { return (React.createElement(Grid, { container: true, direction: direction, spacing: direction === 'row' ? 2 : 0 }, renderLayoutElements(elements, schema, path, enabled, renderers, cells))); } }; const MaterialLayoutRenderer = React.memo(MaterialLayoutRendererComponent); const withAjvProps = (Component) => function WithAjvProps(props) { const ctx = useJsonForms(); const ajv = getAjv({ jsonforms: { ...ctx } }); return React.createElement(Component, { ...props, ajv: ajv }); }; const variantToInput = { standard: Input, filled: FilledInput, outlined: OutlinedInput, }; const defaultInputVariant = 'outlined'; function useInputVariant() { const { variant = defaultInputVariant } = useThemeProps({ props: {}, name: 'MuiTextField', }); return variant; } function useInputComponent() { const variant = useInputVariant(); return variantToInput[variant] ?? variantToInput[defaultInputVariant]; } const eventToValue$3 = (ev) => ev.target.value; const useDebouncedChange = (handleChange, defaultValue, data, path, eventToValueFunction = eventToValue$3, timeout = 300, flushOnBlur = false, focused = false) => { const [input, setInput] = useState(data ?? defaultValue); useEffect(() => { setInput(data ?? defaultValue); }, [data]); const debouncedUpdate = useCallback(debounce((newValue) => handleChange(path, newValue), timeout), [handleChange, path, timeout]); useEffect(() => { if (!focused && flushOnBlur) { debouncedUpdate.flush(); } }, [focused, flushOnBlur, debouncedUpdate]); const onChange = useCallback((ev) => { const newValue = eventToValueFunction(ev); setInput(newValue ?? defaultValue); debouncedUpdate(newValue); }, [debouncedUpdate, eventToValueFunction]); const onClear = useCallback(() => { setInput(defaultValue); handleChange(path, undefined); }, [defaultValue, handleChange, path]); return [input, onChange, onClear]; }; const i18nDefaults = { 'enum.none': 'None', }; const toNumber$1 = (value) => value === '' ? undefined : parseInt(value, 10); const eventToValue$2 = (ev) => toNumber$1(ev.target.value); const MuiInputInteger = React.memo(function MuiInputInteger(props) { const [focused, onFocus, onBlur] = useFocus(); const { data, className, id, enabled, uischema, isValid, path, handleChange, config, label, } = props; const InputComponent = useInputComponent(); const inputProps = { step: '1' }; const appliedUiSchemaOptions = merge({}, config, uischema.options); const [inputValue, onChange] = useDebouncedChange(handleChange, '', data, path, eventToValue$2, undefined, true, focused); return (React.createElement(InputComponent, { label: label, type: 'number', value: inputValue, onFocus: onFocus, onBlur: onBlur, onChange: onChange, className: className, id: id, disabled: !enabled, autoFocus: appliedUiSchemaOptions.focus, inputProps: inputProps, fullWidth: true, error: !isValid })); }); const toNumber = (value) => value === '' ? undefined : parseFloat(value); const eventToValue$1 = (ev) => toNumber(ev.target.value); const MuiInputNumber = React.memo(function MuiInputNumber(props) { const [focused, onFocus, onBlur] = useFocus(); const { data, className, id, enabled, uischema, isValid, path, handleChange, config, label, } = props; const InputComponent = useInputComponent(); const inputProps = { step: '0.1' }; const appliedUiSchemaOptions = merge({}, config, uischema.options); const [inputValue, onChange] = useDebouncedChange(handleChange, '', data, path, eventToValue$1, undefined, true, focused); return (React.createElement(InputComponent, { type: 'number', label: label, value: inputValue, onChange: onChange, onFocus: onFocus, onBlur: onBlur, className: className, id: id, disabled: !enabled, autoFocus: appliedUiSchemaOptions.focus, inputProps: inputProps, fullWidth: true, error: !isValid })); }); const MuiInputNumberFormat = React.memo(function MuiInputNumberFormat(props) { const [focused, onFocus, onBlur] = useFocus(); const { className, id, enabled, uischema, isValid, path, handleChange, schema, config, label, } = props; const InputComponent = useInputComponent(); const maxLength = schema.maxLength; const appliedUiSchemaOptions = merge({}, config, uischema.options); let inputProps; if (appliedUiSchemaOptions.restrict) { inputProps = { maxLength: maxLength }; } else { inputProps = {}; } const formattedNumber = props.toFormatted(props.data); const validStringNumber = useCallback((ev) => props.fromFormatted(ev.currentTarget.value), [props.fromFormatted]); const [inputValue, onChange] = useDebouncedChange(handleChange, '', formattedNumber, path, validStringNumber, undefined, true, focused); return (React.createElement(InputComponent, { type: 'text', value: inputValue, onChange: onChange, onFocus: onFocus, onBlur: onBlur, className: className, id: id, label: label, disabled: !enabled, autoFocus: appliedUiSchemaOptions.focus, multiline: appliedUiSchemaOptions.multi, fullWidth: !appliedUiSchemaOptions.trim || maxLength === undefined, inputProps: inputProps, error: !isValid })); }); const eventToValue = (ev) => ev.target.value === '' ? undefined : ev.target.value; const MuiInputText = React.memo(function MuiInputText(props) { const [focused, onFocus, onBlur] = useFocus(); const [showAdornment, setShowAdornment] = useState(false); const { data, config, className, id, enabled, uischema, isValid, path, handleChange, schema, muiInputProps, label, inputComponent, } = props; const InputComponent = useInputComponent(); const maxLength = schema.maxLength; const appliedUiSchemaOptions = merge({}, config, uischema.options); let inputProps; if (appliedUiSchemaOptions.restrict) { inputProps = { maxLength: maxLength }; } else { inputProps = {}; } inputProps = merge(inputProps, muiInputProps); if (appliedUiSchemaOptions.trim && maxLength !== undefined) { inputProps.size = maxLength; } const [inputText, onChange, onClear] = useDebouncedChange(handleChange, '', data, path, eventToValue, undefined, true, focused); const onPointerEnter = () => setShowAdornment(true); const onPointerLeave = () => setShowAdornment(false); const theme = useTheme(); const closeStyle = { background: theme.jsonforms?.input?.delete?.background || theme.palette.background.default, borderRadius: '50%', }; return (React.createElement(InputComponent, { label: label, type: appliedUiSchemaOptions.format === 'password' ? 'password' : 'text', value: inputText, onChange: onChange, className: className, onBlur: onBlur, onFocus: onFocus, id: id, disabled: !enabled, autoFocus: appliedUiSchemaOptions.focus, multiline: appliedUiSchemaOptions.multi, fullWidth: !appliedUiSchemaOptions.trim || maxLength === undefined, inputProps: inputProps, error: !isValid, onPointerEnter: onPointerEnter, onPointerLeave: onPointerLeave, endAdornment: React.createElement(InputAdornment, { position: 'end', style: { display: !showAdornment || !enabled || data === undefined ? 'none' : 'flex', position: 'absolute', right: 0, } }, React.createElement(IconButton, { "aria-label": 'Clear input field', onClick: onClear, size: 'large' }, React.createElement(Close, { style: closeStyle }))), inputComponent: inputComponent })); }); const MuiInputTime = React.memo(function MuiInputTime(props) { const { data, className, id, enabled, uischema, isValid, path, handleChange, config, label, } = props; const InputComponent = useInputComponent(); const appliedUiSchemaOptions = merge({}, config, uischema.options); const [inputValue, onChange] = useDebouncedChange(handleChange, '', data, path); return (React.createElement(InputComponent, { type: 'time', value: inputValue, onChange: onChange, className: className, id: id, label: label, disabled: !enabled, autoFocus: appliedUiSchemaOptions.focus, fullWidth: true, error: !isValid })); }); const MuiSelect = React.memo(function MuiSelect(props) { const { data, className, id, enabled, schema, uischema, isValid, path, handleChange, options, config, label, t, multiple, } = props; const appliedUiSchemaOptions = merge({}, config, uischema.options); const noneOptionLabel = useMemo(() => t('enum.none', i18nDefaults['enum.none'], { schema, uischema, path }), [t, schema, uischema, path]); return (React.createElement(Select, { className: className, id: id, label: label, disabled: !enabled, autoFocus: appliedUiSchemaOptions.focus, value: data !== undefined ? data : '', onChange: (ev) => handleChange(path, ev.target.value || undefined), fullWidth: true, multiple: multiple || false, error: !isValid }, [ React.createElement(MenuItem, { value: '', key: 'jsonforms.enum.none' }, React.createElement("em", null, noneOptionLabel)), ].concat(options.map((optionValue) => (React.createElement(MenuItem, { value: optionValue.value, key: optionValue.value }, optionValue.label)))))); }); const MuiToggle = React.memo(function MuiToggle(props) { const { data, className, id, enabled, uischema, path, handleChange, config, inputProps, } = props; const appliedUiSchemaOptions = merge({}, config, uischema.options); const inputPropsMerged = merge({}, inputProps, { autoFocus: !!appliedUiSchemaOptions.focus, }); const checked = !!data; return (React.createElement(Switch, { checked: checked, onChange: (_ev, isChecked) => handleChange(path, isChecked), className: className, id: id, disabled: !enabled, inputProps: inputPropsMerged })); }); const MaterialEnumArrayRenderer = ({ config, id, schema, visible, errors, description, label, required, path, options, data, addItem, removeItem, handleChange: _handleChange, ...otherProps }) => { const [focused, onFocus, onBlur] = useFocus(); const isValid = errors.length === 0; const appliedUiSchemaOptions = merge({}, config, otherProps.uischema.options); const showDescription = !isDescriptionHidden(visible, description, focused, appliedUiSchemaOptions.showUnfocusedDescription); if (!visible) { return null; } return (React.createElement(FormControl, { component: 'fieldset', fullWidth: !appliedUiSchemaOptions.trim, onFocus: onFocus, onBlur: onBlur }, React.createElement(FormLabel, { error: !isValid, component: 'legend', required: showAsRequired(required, appliedUiSchemaOptions.hideRequiredAsterisk) }, label), React.createElement(FormGroup, { row: true }, options.map((option, index) => { const optionPath = Paths.compose(path, `${index}`); const checkboxValue = data?.includes(option.value) ? option.value : undefined; return (React.createElement(FormControlLabel, { id: id + '-label-' + option.value, key: option.value, control: React.createElement(MuiCheckbox, { id: id + '-' + option.value, key: 'checkbox-' + option.value, isValid: isEmpty(errors), path: optionPath, handleChange: (_childPath, newValue) => newValue ? addItem(path, option.value) : removeItem(path, option.value), data: checkboxValue, errors: errors, schema: schema, visible: visible, ...otherProps }), label: option.label })); })), React.createElement(FormHelperText, { error: !isValid }, !isValid ? errors : showDescription ? description : null))); }; const hasOneOfItems = (schema) => schema.oneOf !== undefined && schema.oneOf.length > 0 && schema.oneOf.every((entry) => { return entry.const !== undefined; }); const hasEnumItems = (schema) => schema.type === 'string' && schema.enum !== undefined; const materialEnumArrayRendererTester = rankWith(5, and(uiTypeIs('Control'), and(schemaMatches((schema) => hasType(schema, 'array') && !Array.isArray(schema.items) && schema.uniqueItems === true), schemaSubPathMatches('items', (schema, rootSchema) => { const resolvedSchema = schema.$ref ? resolveSchema(rootSchema, schema.$ref, rootSchema) : schema; return hasOneOfItems(resolvedSchema) || hasEnumItems(resolvedSchema); })))); var MaterialEnumArrayRenderer$1 = withJsonFormsMultiEnumProps(MaterialEnumArrayRenderer); const MaterialObjectRenderer = ({ renderers, cells, uischemas, schema, label, path, visible, enabled, uischema, rootSchema, }) => { const detailUiSchema = useMemo(() => findUISchema(uischemas, schema, uischema.scope, path, () => isEmpty(path) ? Generate.uiSchema(schema, 'VerticalLayout', undefined, rootSchema) : { ...Generate.uiSchema(schema, 'Group', undefined, rootSchema), label, }, uischema, rootSchema), [uischemas, schema, uischema.scope, path, label, uischema, rootSchema]); if (!visible) { return null; } return (React.createElement(JsonFormsDispatch, { visible: visible, enabled: enabled, schema: schema, uischema: detailUiSchema, path: path, renderers: renderers, cells: cells })); }; const materialObjectControlTester = rankWith(2, isObjectControl); var MaterialObjectRenderer$1 = withJsonFormsDetailProps(MaterialObjectRenderer); const MaterialOneOfRenderer = ({ handleChange, schema, path, renderers, cells, rootSchema, id, visible, indexOfFittingSchema, uischema, uischemas, data, }) => { const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [selectedIndex, setSelectedIndex] = useState(indexOfFittingSchema || 0); const [newSelectedIndex, setNewSelectedIndex] = useState(0); const handleClose = useCallback(() => setConfirmDialogOpen(false), [setConfirmDialogOpen]); const cancel = useCallback(() => { setConfirmDialogOpen(false); }, [setConfirmDialogOpen]); const oneOfRenderInfos = useMemo(() => createCombinatorRenderInfos(schema.oneOf, rootSchema, 'oneOf', uischema, path, uischemas), [schema, rootSchema, uischema, path, uischemas]); const openNewTab = (newIndex) => { handleChange(path, createDefaultValue(oneOfRenderInfos[newIndex].schema, rootSchema)); setSelectedIndex(newIndex); }; const confirm = useCallback(() => { openNewTab(newSelectedIndex); setConfirmDialogOpen(false); }, [handleChange, createDefaultValue, newSelectedIndex]); const handleTabChange = useCallback((_event, newOneOfIndex) => { setNewSelectedIndex(newOneOfIndex); if (isEmpty(data)) { openNewTab(newOneOfIndex); } else { setConfirmDialogOpen(true); } }, [setConfirmDialogOpen, setSelectedIndex, data]); if (!visible) { return null; } return (React.createElement(React.Fragment, null, React.createElement(CombinatorProperties, { schema: schema, combinatorKeyword: 'oneOf', path: path, rootSchema: rootSchema }), React.createElement(Tabs, { value: selectedIndex, onChange: handleTabChange }, oneOfRenderInfos.map((oneOfRenderInfo) => (React.createElement(Tab, { key: oneOfRenderInfo.label, label: oneOfRenderInfo.label })))), oneOfRenderInfos.map((oneOfRenderInfo, oneOfIndex) => selectedIndex === oneOfIndex && (React.createElement(JsonFormsDispatch, { key: oneOfIndex, schema: oneOfRenderInfo.schema, uischema: oneOfRenderInfo.uischema, path: path, renderers: renderers, cells: cells }))), React.createElement(TabSwitchConfirmDialog, { cancel: cancel, confirm: confirm, id: 'oneOf-' + id, open: confirmDialogOpen, handleClose: handleClose }))); }; const materialOneOfControlTester = rankWith(3, isOneOfControl); var MaterialOneOfRenderer$1 = withJsonFormsOneOfProps(MaterialOneOfRenderer); const materialLabelRendererTester = rankWith(1, uiTypeIs('Label')); const MaterialLabelRenderer = ({ text, visible }) => { if (!visible) { return null; } return React.createElement(Typography, { variant: 'h6' }, text); }; var MaterialLabelRenderer$1 = withJsonFormsLabelProps(MaterialLabelRenderer); const ArrayLayoutToolbar = React.memo(function ArrayLayoutToolbar({ label, description, errors, addItem, path, enabled, createDefault, translations, disableAdd, }) { return (React.createElement(Toolbar, { disableGutters: true }, React.createElement(Stack, null, React.createElement(Grid, { container: true, alignItems: 'center', justifyContent: 'space-between' }, React.createElement(Grid, { item: true }, React.createElement(Grid, { container: true, justifyContent: 'flex-start', alignItems: 'center', spacing: 2 }, React.createElement(Grid, { item: true }, React.createElement(Typography, { variant: 'h6' }, label)), React.createElement(Grid, { item: true }, errors.length !== 0 && (React.createElement(Grid, { item: true }, React.createElement(ValidationIcon, { id: 'tooltip-validation', errorMessages: errors })))))), enabled && !disableAdd && (React.createElement(Grid, { item: true }, React.createElement(Grid, { container: true }, React.createElement(Grid, { item: true }, React.createElement(Tooltip, { id: 'tooltip-add', title: translations.addTooltip, placement: 'bottom' }, React.createElement(IconButton, { "aria-label": translations.addTooltip, onClick: addItem(path, createDefault()), size: 'large' }, React.createElement(AddIcon, null)))))))), description && React.createElement(FormHelperText, null, description)))); }); const ListWithDetailMasterItem = ({ index, childLabel, selected, enabled, handleSelect, removeItem, path, translations, disableRemove, }) => { return (React.createElement(ListItemButton, { selected: selected, onClick: handleSelect(index) }, React.createElement(ListItemAvatar, null, React.createElement(Avatar, { "aria-label": 'Index' }, index + 1)), React.createElement(ListItemText, { primary: childLabel }), enabled && !disableRemove && (React.createElement(ListItemSecondaryAction, null, React.createElement(Tooltip, { id: 'tooltip-remove', title: translations.removeTooltip, placement: 'bottom' }, React.createElement(IconButton, { "aria-label": translations.removeAriaLabel, onClick: removeItem(path, index), size: 'large' }, React.createElement(DeleteIcon, null))))))); }; var ListWithDetailMasterItem$1 = withJsonFormsMasterListItemProps(ListWithDetailMasterItem); const MaterialListWithDetailRenderer = ({ uischemas, schema, uischema, path, enabled, errors, visible, label, required, removeItems, addItem, data, renderers, cells, config, rootSchema, description, disableAdd, disableRemove, translations, }) => { const [selectedIndex, setSelectedIndex] = useState(undefined); const handleRemoveItem = useCallback((p, value) => () => { removeItems(p, [value])(); if (selectedIndex === value) { setSelectedIndex(undefined); } else if (selectedIndex > value) { setSelectedIndex(selectedIndex - 1); } }, [removeItems, setSelectedIndex]); const handleListItemClick = useCallback((index) => () => setSelectedIndex(index), [setSelectedIndex]); const handleCreateDefaultValue = useCallback(() => createDefaultValue(schema, rootSchema), [createDefaultValue]); const foundUISchema = useMemo(() => findUISchema(uischemas, schema, uischema.scope, path, undefined, uischema, rootSchema), [uischemas, schema, uischema.scope, path, uischema, rootSchema]); const appliedUiSchemaOptions = merge({}, config, uischema.options); const doDisableAdd = disableAdd || appliedUiSchemaOptions.disableAdd; const doDisableRemove = disableRemove || appliedUiSchemaOptions.disableRemove; React.useEffect(() => { setSelectedIndex(undefined); }, [schema]); if (!visible) { return null; } return (React.createElement(React.Fragment, null, React.createElement(ArrayLayoutToolbar, { translations: translations, label: computeLabel(label, required, appliedUiSchemaOptions.hideRequiredAsterisk), description: description, errors: errors, path: path, enabled: enabled, addItem: addItem, createDefault: handleCreateDefaultValue, disableAdd: doDisableAdd }), React.createElement(Grid, { container: true, direction: 'row', spacing: 2 }, React.createElement(Grid, { item: true, xs: 3 }, React.createElement(List, null, data > 0 ? (map(range(data), (index) => (React.createElement(ListWithDetailMasterItem$1, { index: index, path: path, schema: schema, enabled: enabled, handleSelect: handleListItemClick, removeItem: handleRemoveItem, selected: selectedIndex === index, key: index, uischema: foundUISchema, childLabelProp: appliedUiSchemaOptions.elementLabelProp, translations: translations, disableRemove: doDisableRemove })))) : (React.createElement("p", null, translations.noDataMessage)))), React.createElement(Grid, { item: true, xs: true }, selectedIndex !== undefined ? (React.createElement(JsonFormsDispatch, { renderers: renderers, cells: cells, visible: visible, schema: schema, uischema: foundUISchema, path: composePaths(path, `${selectedIndex}`) })) : (React.createElement(Typography, { variant: 'h6' }, translations.noSelection)))))); }; const materialListWithDetailTester = rankWith(4, and(uiTypeIs('ListWithDetail'), isObjectArray)); var MaterialListWithDetailRenderer$1 = withJsonFormsArrayLayoutProps(withTranslateProps(withArrayTranslationProps(MaterialListWithDetailRenderer))); const MaterialInputControl = (props) => { const [focused, onFocus, onBlur] = useFocus(); const { id, description, errors, label, uischema, visible, required, config, input, } = props; const variant = useInputVariant(); const isValid = errors.length === 0; const appliedUiSchemaOptions = merge({}, config, uischema.options); const showDescription = !isDescriptionHidden(visible, description, focused, appliedUiSchemaOptions.showUnfocusedDescription); const firstFormHelperText = showDescription ? description : !isValid ? errors : null; const secondFormHelperText = showDescription && !isValid ? errors : null; const InnerComponent = input; if (!visible) { return null; } return (React.createElement(FormControl, { fullWidth: !appliedUiSchemaOptions.trim, onFocus: onFocus, onBlur: onBlur, variant: variant, id: id }, React.createElement(InputLabel, { htmlFor: id + '-input', error: !isValid, required: showAsRequired(required, appliedUiSchemaOptions.hideRequiredAsterisk) }, label), React.createElement(InnerComponent, { ...props, id: id + '-input', isValid: isValid, visible: visible }), React.createElement(FormHelperText, { error: !isValid && !showDescription }, firstFormHelp