@canard/schema-form-mui-plugin
Version:
Material-UI (MUI) components plugin for @canard/schema-form providing pre-built form inputs with modern MUI styling and MUI X integration
759 lines (736 loc) • 33.3 kB
JavaScript
import { jsx, jsxs } from 'react/jsx-runtime';
import { FormHelperText, Box, FormLabel as FormLabel$1, Button, FormControlLabel, Checkbox, Switch, TextField, RadioGroup, Radio, Typography, Slider, FormGroup as FormGroup$1, FormControl, InputLabel, Select, MenuItem, Stack } from '@mui/material';
import { Delete, Add as Add$1 } from '@mui/icons-material';
import { map } from '@winglet/common-utils/array';
import { useHandle } from '@winglet/react-utils/hook';
import { useMemo, useState, useRef, useEffect } from 'react';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import dayjs from 'dayjs';
import { LocalizationProvider as LocalizationProvider$1, TimePicker } from '@mui/x-date-pickers';
const FormError = ({ errorMessage }) => (jsx(FormHelperText, { error: true, children: errorMessage }));
const FormGroup = ({ node, depth, Input, errorMessage, }) => {
if (depth === 0)
return jsx(Input, {});
if (node.group === 'branch') {
return (jsx(Box, { component: "fieldset", sx: {
marginBottom: 1,
marginLeft: depth * 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
padding: 1,
}, children: jsx(Input, {}) }));
}
else {
return (jsxs(Box, { sx: {
marginBottom: 2,
marginLeft: depth * 2,
}, children: [jsx(Input, {}), jsx(FormHelperText, { error: true, children: errorMessage })] }));
}
};
const FormInput = ({ Input }) => jsx(Input, {});
const FormLabel = ({ name, path, required }) => (jsx(FormLabel$1, { htmlFor: path, required: required, children: name }));
const Add = (props) => (jsx(Button, { color: "primary", startIcon: jsx(Add$1, {}), ...props, children: "Add" }));
const Remove = (props) => (jsx(Button, { color: "primary", startIcon: jsx(Delete, {}), ...props }));
const FormTypeInputArray = ({ node, readOnly, disabled, ChildNodeComponents, style, }) => {
const handleClick = useHandle(() => {
node.push();
});
const handleRemoveClick = useHandle((index) => {
node.remove(index);
});
return (jsxs("div", { style: style, children: [ChildNodeComponents &&
map(ChildNodeComponents, (ChildNodeComponent, i) => {
const key = ChildNodeComponent.key;
return (jsxs("div", { style: { display: 'flex' }, children: [jsx("div", { style: { flex: 1 }, children: jsx(ChildNodeComponent, { hideLabel: true }, key) }), !readOnly && (jsx(Remove, { title: "remove", disabled: disabled, onClick: () => handleRemoveClick(i) }))] }, key));
}), !readOnly && (jsx("div", { style: { marginLeft: 20 }, children: jsx(Add, { title: "add", disabled: disabled, onClick: handleClick }) }))] }));
};
const FormTypeInputArrayDefinition = {
Component: FormTypeInputArray,
test: {
type: 'array',
},
};
const FormTypeInputBoolean = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp = 'medium', hideLabel, }) => {
const [label, size] = useMemo(() => {
if (hideLabel)
return [undefined, sizeProp || context.size];
return [labelProp || jsonSchema.label || name, sizeProp || context.size];
}, [jsonSchema, context, labelProp, name, sizeProp, hideLabel]);
const [indeterminate, defaultChecked] = useMemo(() => {
const isIndeterminate = defaultValue !== undefined && typeof defaultValue !== 'boolean';
return [isIndeterminate, !!defaultValue];
}, [defaultValue]);
const handleChange = useHandle((event) => {
onChange(event.target.checked);
});
return (jsx(FormControlLabel, { label: label, htmlFor: path, required: required, disabled: disabled, control: jsx(Checkbox, { id: path, name: name, disabled: disabled, indeterminate: indeterminate, defaultChecked: defaultChecked, onChange: handleChange, size: size }) }));
};
const FormTypeInputBooleanDefinition = {
Component: FormTypeInputBoolean,
test: ({ type }) => type === 'boolean',
};
const FormTypeInputBooleanSwitch = ({ path, name, required, jsonSchema, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp = 'medium', hideLabel, }) => {
const handleChange = useHandle((event) => {
onChange(event.target.checked);
});
const [label, size] = useMemo(() => {
if (hideLabel)
return [undefined, sizeProp || context.size];
return [labelProp || jsonSchema.label || name, sizeProp || context.size];
}, [jsonSchema, context, labelProp, name, sizeProp, hideLabel]);
return (jsx(FormControlLabel, { label: label, htmlFor: path, required: required, disabled: disabled, labelPlacement: "start", control: jsx(Switch, { id: path, name: name, defaultChecked: defaultValue === true, onChange: handleChange, disabled: disabled, size: size }) }));
};
const FormTypeInputBooleanSwitchDefinition = {
Component: FormTypeInputBooleanSwitch,
test: ({ type, formType }) => type === 'boolean' && formType === 'switch',
};
const FormTypeInputDate = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const { minDate, maxDate } = useMemo(() => {
return {
minDate: jsonSchema.minimum ? dayjs(jsonSchema.minimum) : undefined,
maxDate: jsonSchema.maximum ? dayjs(jsonSchema.maximum) : undefined,
};
}, [jsonSchema.minimum, jsonSchema.maximum]);
const handleChange = useHandle((newValue) => {
if (newValue && newValue.isValid()) {
onChange(newValue.format('YYYY-MM-DD'));
}
else {
onChange(null);
}
});
const disableDate = useHandle((date) => {
if (minDate && date.isBefore(minDate, 'day'))
return true;
if (maxDate && date.isAfter(maxDate, 'day'))
return true;
return false;
});
return (jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: jsx(DatePicker, { label: label, defaultValue: defaultValue ? dayjs(defaultValue) : null, onChange: handleChange, disabled: disabled, minDate: minDate, maxDate: maxDate, shouldDisableDate: disableDate, slotProps: {
textField: {
id: path,
name,
required,
size,
variant,
fullWidth,
},
} }) }));
};
const FormTypeInputDateDefinition = {
Component: FormTypeInputDate,
test: ({ type, format }) => type === 'string' && format === 'date',
};
const FormTypeInputMonth = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const handleChange = useHandle((newValue) => {
if (newValue && newValue.isValid()) {
onChange(newValue.format('YYYY-MM'));
}
else {
onChange(null);
}
});
return (jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: jsx(DatePicker, { label: label, defaultValue: defaultValue ? dayjs(defaultValue) : null, onChange: handleChange, disabled: disabled, views: ['year', 'month'], openTo: "month", format: "YYYY-MM", slotProps: {
textField: {
id: path,
name,
required,
size,
variant,
fullWidth,
},
} }) }));
};
const FormTypeInputMonthDefinition = {
Component: FormTypeInputMonth,
test: ({ type, format }) => type === 'string' && format === 'month',
};
const FormTypeInputNumber = ({ path, name, jsonSchema, required, readOnly, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const handleChange = useHandle((event) => {
const inputValue = event.target.value;
if (inputValue === '') {
onChange(null);
return;
}
const numericValue = jsonSchema.type === 'integer'
? parseInt(inputValue, 10)
: parseFloat(inputValue);
if (!isNaN(numericValue)) {
onChange(numericValue);
}
});
return (jsx(TextField, { id: path, name: name, type: "number", variant: variant, fullWidth: fullWidth, label: label, required: required, size: size, placeholder: jsonSchema.placeholder, defaultValue: defaultValue ?? undefined, onChange: handleChange, disabled: disabled, slotProps: {
input: {
readOnly,
inputProps: {
min: jsonSchema.minimum,
max: jsonSchema.maximum,
step: jsonSchema.multipleOf ||
(jsonSchema.type === 'integer' ? 1 : 'any'),
},
},
} }));
};
const FormTypeInputNumberDefinition = {
Component: FormTypeInputNumber,
test: {
type: ['number', 'integer'],
},
};
const FormTypeInputRadioGroup = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp = 'medium', row = true, hideLabel, }) => {
const [label, size] = useMemo(() => {
if (hideLabel)
return [undefined, sizeProp || context.size];
return [labelProp || jsonSchema.label || name, sizeProp || context.size];
}, [jsonSchema, context, labelProp, name, sizeProp, hideLabel]);
const options = useMemo(() => {
const enumValues = jsonSchema.enum || [];
const radioLabels = jsonSchema.radioLabels;
const alias = jsonSchema.options?.alias || {};
return enumValues.map((rawValue, index) => {
const value = '' + rawValue;
return {
value,
rawValue,
label: radioLabels?.[index] || alias[value] || value,
};
});
}, [jsonSchema]);
const initialValue = useMemo(() => options.find((option) => option.rawValue === defaultValue)?.value, [defaultValue, options]);
const handleChange = useHandle((event) => {
const newValue = event.target.value;
const selectedOption = options.find((opt) => opt.value === newValue);
if (selectedOption) {
onChange(selectedOption.rawValue);
}
});
return (jsx(FormControlLabel, { label: label, htmlFor: path, required: required, disabled: disabled, labelPlacement: "start", style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
gap: 8,
}, control: jsx(RadioGroup, { name: name, defaultValue: initialValue, onChange: handleChange, row: row, children: options.map((option) => (jsx(FormControlLabel, { value: option.value, disabled: disabled, control: jsx(Radio, { size: size, id: `${path}-${option.value}` }), label: option.label }, option.value))) }) }));
};
const FormTypeInputRadioGroupDefinition = {
Component: FormTypeInputRadioGroup,
test: ({ type, formType, jsonSchema }) => (type === 'string' || type === 'number' || type === 'integer') &&
(formType === 'radio' || formType === 'radiogroup') &&
!!jsonSchema.enum?.length,
};
const FormTypeInputSlider = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp = 'medium', showMarks = false, hideLabel, }) => {
const [label, size] = useMemo(() => {
if (hideLabel)
return [undefined, sizeProp || context.size];
return [labelProp || jsonSchema.label || name, sizeProp || context.size];
}, [jsonSchema, context, labelProp, name, sizeProp, hideLabel]);
const min = jsonSchema.minimum ?? 0;
const max = jsonSchema.maximum ?? 100;
const step = jsonSchema.multipleOf ?? 1;
const isLazy = jsonSchema.lazy ?? false;
const handleChange = useHandle((_, newValue) => {
if (!isLazy) {
onChange(Array.isArray(newValue) ? newValue[0] : newValue);
}
});
const handleChangeCommitted = useHandle((_, newValue) => {
if (isLazy) {
onChange(Array.isArray(newValue) ? newValue[0] : newValue);
}
});
return (jsxs(Box, { sx: { px: 2 }, children: [label && (jsxs(Typography, { variant: "body2", component: "label", htmlFor: path, sx: { mb: 1, display: 'block' }, children: [label, required && ' *'] })), jsx(Slider, { id: path, name: name, defaultValue: typeof defaultValue === 'number' ? defaultValue : min, min: min, max: max, step: step, onChange: handleChange, onChangeCommitted: handleChangeCommitted, disabled: disabled, size: size, marks: showMarks, valueLabelDisplay: "auto", sx: { mt: 1 } }), jsxs(Box, { sx: { display: 'flex', justifyContent: 'space-between', mt: 1 }, children: [jsx(Typography, { variant: "caption", color: "text.secondary", children: min }), jsx(Typography, { variant: "caption", color: "text.secondary", children: max })] })] }));
};
const FormTypeInputSliderDefinition = {
Component: FormTypeInputSlider,
test: ({ type, formType }) => (type === 'number' || type === 'integer') && formType === 'slider',
};
const FormTypeInputString = ({ path, name, jsonSchema, required, readOnly, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const isPassword = useMemo(() => {
return (jsonSchema.format === 'password' || jsonSchema.formType === 'password');
}, [jsonSchema]);
const handleChange = useHandle((event) => {
onChange(event.target.value);
});
return (jsx(TextField, { id: path, name: name, type: isPassword ? 'password' : 'text', variant: variant, fullWidth: fullWidth, placeholder: jsonSchema.placeholder, label: label, required: required, disabled: disabled, defaultValue: defaultValue ?? undefined, onChange: handleChange, size: size, slotProps: {
input: {
readOnly,
},
} }));
};
const FormTypeInputStringDefinition = {
Component: FormTypeInputString,
test: {
type: 'string',
},
};
const FormTypeInputStringCheckbox = ({ path, name, jsonSchema, required, disabled, defaultValue = [], onChange, context, label: labelProp, size: sizeProp = 'medium', row = true, hideLabel, }) => {
const [label, size] = useMemo(() => {
if (hideLabel)
return [undefined, sizeProp || context.size];
return [labelProp || jsonSchema.label || name, sizeProp || context.size];
}, [hideLabel, sizeProp, context.size, labelProp, jsonSchema, name]);
const options = useMemo(() => {
const enumValues = jsonSchema.items.enum || [];
const checkboxLabels = jsonSchema.items?.options?.alias;
return enumValues.map((rawValue) => {
const value = '' + rawValue;
return {
value,
rawValue,
label: checkboxLabels?.[value] || value,
};
});
}, [jsonSchema]);
const [value, setValue] = useState(defaultValue.map((v) => '' + v));
const handleToggle = useHandle((optionValue) => {
const currentValues = value;
const isSelected = currentValues.includes(optionValue);
let newValues;
if (isSelected) {
newValues = currentValues.filter((val) => val !== optionValue);
}
else {
newValues = [...currentValues, optionValue];
}
setValue(newValues);
onChange(newValues);
});
return (jsx(FormControlLabel, { label: label, htmlFor: path, required: required, disabled: disabled, labelPlacement: "start", style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
gap: 8,
}, control: jsx(FormGroup$1, { row: row, children: options.map((option) => {
const isChecked = value.includes(option.value);
return (jsx(FormControlLabel, { disabled: disabled, control: jsx(Checkbox, { id: `${path}-${option.value}`, name: `${name}[]`, checked: isChecked, onChange: () => handleToggle(option.value), size: size }), label: option.label }, option.value));
}) }) }));
};
const FormTypeInputStringCheckboxDefinition = {
Component: FormTypeInputStringCheckbox,
test: ({ type, formType, jsonSchema }) => type === 'array' &&
formType === 'checkbox' &&
jsonSchema.items?.type === 'string' &&
jsonSchema.items?.enum &&
jsonSchema.items.enum.length > 0,
};
const FormTypeInputStringEnum = ({ path, name, jsonSchema, required, readOnly, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const options = useMemo(() => jsonSchema.enum?.map((rawValue) => {
const value = '' + rawValue;
return {
value,
rawValue,
label: jsonSchema.options?.alias?.[value] || value,
};
}) || [], [jsonSchema]);
const handleChange = useHandle((event) => {
const rawValue = options.find((option) => option.value === event.target.value)?.rawValue;
if (rawValue === undefined)
return;
onChange(rawValue);
});
const labelId = useMemo(() => `label-${path}`, [path]);
const initialValue = useMemo(() => {
if (defaultValue === undefined)
return undefined;
if (defaultValue === null)
return '' + null;
return defaultValue;
}, [defaultValue]);
return (jsxs(FormControl, { fullWidth: fullWidth, variant: variant, children: [jsx(InputLabel, { id: labelId, children: label }), jsx(Select, { id: path, name: name, labelId: labelId, label: label, required: required, readOnly: readOnly, defaultValue: initialValue, onChange: handleChange, disabled: disabled, size: size, children: options.map((option) => (jsx(MenuItem, { value: option.value, children: option.label }, option.value))) })] }));
};
const FormTypeInputStringEnumDefinition = {
Component: FormTypeInputStringEnum,
test: ({ type, jsonSchema }) => type === 'string' && !!jsonSchema.enum?.length,
};
const FormTypeInputStringSwitch = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp = 'medium', hideLabel, }) => {
const [label, size] = useMemo(() => {
if (hideLabel)
return [undefined, sizeProp || context.size];
return [labelProp || jsonSchema.label || name, sizeProp || context.size];
}, [hideLabel, sizeProp, context.size, labelProp, jsonSchema, name]);
const { offValue, onValue, offLabel, onLabel } = useMemo(() => {
const [first, second] = jsonSchema.enum;
const [firstLabel, secondLabel] = jsonSchema.switchLabels || [
first,
second,
];
return {
offValue: first,
onValue: second,
offLabel: firstLabel,
onLabel: secondLabel,
};
}, [jsonSchema.enum, jsonSchema.switchLabels]);
const handleChange = useHandle((event) => {
onChange(event.target.checked ? onValue : offValue);
});
const switchSize = jsonSchema.switchSize || size;
return (jsx(FormControlLabel, { label: label, htmlFor: path, required: required, disabled: disabled, labelPlacement: "start", sx: {
alignItems: 'center',
gap: 1,
}, control: jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: 'center' }, children: [jsx(Typography, { children: offLabel }), jsx(Switch, { id: path, name: name, defaultChecked: defaultValue === onValue, onChange: handleChange, disabled: disabled, size: switchSize }), jsx(Typography, { children: onLabel })] }) }));
};
const FormTypeInputStringSwitchDefinition = {
Component: FormTypeInputStringSwitch,
test: ({ type, formType, jsonSchema }) => type === 'string' && formType === 'switch' && jsonSchema.enum?.length === 2,
};
const FormTypeInputTextarea = ({ path, name, jsonSchema, required, readOnly, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, minRows, maxRows, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const handleChange = useHandle((event) => {
onChange(event.target.value);
});
const finalMinRows = minRows ?? jsonSchema.minRows ?? 3;
const finalMaxRows = maxRows ?? jsonSchema.maxRows ?? 8;
return (jsx(TextField, { id: path, name: name, multiline: true, variant: variant, fullWidth: fullWidth, label: label, required: required, size: size, placeholder: jsonSchema.placeholder, defaultValue: defaultValue, onChange: handleChange, disabled: disabled, minRows: finalMinRows, maxRows: finalMaxRows, slotProps: {
input: {
readOnly,
},
}, sx: {
'& .MuiInputBase-root': {
alignItems: 'flex-start',
},
} }));
};
const FormTypeInputTextareaDefinition = {
Component: FormTypeInputTextarea,
test: ({ type, format, formType }) => type === 'string' && (format === 'textarea' || formType === 'textarea'),
};
const FormTypeInputTime = ({ path, name, jsonSchema, required, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, ampm: ampmProp, hideLabel, }) => {
const [label, size, variant, fullWidth, ampm] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
ampmProp ?? jsonSchema.ampm,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
ampmProp ?? jsonSchema.ampm,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
ampmProp,
hideLabel,
]);
const timeValue = useMemo(() => {
if (!defaultValue)
return null;
const today = dayjs().format('YYYY-MM-DD');
return dayjs(`${today}T${defaultValue}`);
}, [defaultValue]);
const handleChange = useHandle((newValue) => {
if (newValue && newValue.isValid()) {
onChange(newValue.format('HH:mm:ss'));
}
else {
onChange(null);
}
});
return (jsx(LocalizationProvider$1, { dateAdapter: AdapterDayjs, children: jsx(TimePicker, { label: label, defaultValue: timeValue, onChange: handleChange, disabled: disabled, ampm: ampm, slotProps: {
textField: {
id: path,
name,
required,
size,
variant,
fullWidth,
},
} }) }));
};
const FormTypeInputTimeDefinition = {
Component: FormTypeInputTime,
test: ({ type, format }) => type === 'string' && format === 'time',
};
const DEFAULT_PROTOCOLS = ['http', 'https'];
const normalizeProtocol = (protocol) => {
return protocol.replace(/:\/\/$/, '').replace(/:$/, '');
};
const getProtocolSeparator = (protocol) => {
const normalizedProtocol = normalizeProtocol(protocol);
return ['mailto', 'tel'].includes(normalizedProtocol) ? ':' : '://';
};
const formatProtocolDisplay = (protocol) => {
const normalizedProtocol = normalizeProtocol(protocol);
const separator = getProtocolSeparator(normalizedProtocol);
return `${normalizedProtocol}${separator}`;
};
const parseUri = (uri) => {
const singleColonMatch = uri.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/);
if (singleColonMatch && ['mailto', 'tel'].includes(singleColonMatch[1])) {
return {
protocol: singleColonMatch[1],
path: singleColonMatch[2],
};
}
const doubleColonMatch = uri.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/(.*)$/);
if (doubleColonMatch) {
return {
protocol: doubleColonMatch[1],
path: doubleColonMatch[2],
};
}
return null;
};
const FormTypeInputUri = ({ path, name, jsonSchema, required, readOnly, disabled, defaultValue, onChange, context, label: labelProp, size: sizeProp, variant: variantProp, fullWidth: fullWidthProp, protocols: protocolsProp, hideLabel, }) => {
const [label, size, variant, fullWidth] = useMemo(() => {
if (hideLabel)
return [
undefined,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
return [
labelProp || jsonSchema.label || name,
sizeProp || context.size,
variantProp || context.variant,
fullWidthProp ?? context.fullWidth,
];
}, [
jsonSchema,
context,
labelProp,
name,
sizeProp,
variantProp,
fullWidthProp,
hideLabel,
]);
const normalizedProtocols = useMemo(() => {
const rawProtocols = protocolsProp || jsonSchema.options?.protocols || DEFAULT_PROTOCOLS;
return rawProtocols.map(normalizeProtocol);
}, [protocolsProp, jsonSchema]);
const textFieldRef = useRef(null);
const [protocol, setProtocol] = useState(normalizedProtocols[0]);
const { initialProtocol, initialUri } = useMemo(() => {
if (!defaultValue) {
return {
initialProtocol: normalizedProtocols[0],
initialUri: '',
};
}
const parsed = parseUri(defaultValue);
if (parsed) {
const normalizedDetected = normalizeProtocol(parsed.protocol);
return {
initialProtocol: normalizedProtocols.includes(normalizedDetected)
? normalizedDetected
: normalizedProtocols[0],
initialUri: parsed.path,
};
}
return {
initialProtocol: normalizedProtocols[0],
initialUri: defaultValue,
};
}, [defaultValue, normalizedProtocols]);
useEffect(() => {
setProtocol(initialProtocol);
}, [initialProtocol]);
const handleProtocolChange = useHandle((event) => {
const newProtocol = event.target.value;
setProtocol(newProtocol);
const currentUri = textFieldRef.current?.value || '';
const separator = getProtocolSeparator(newProtocol);
const newValue = currentUri
? `${newProtocol}${separator}${currentUri}`
: `${newProtocol}${separator}`;
onChange(newValue);
});
const handleUriChange = useHandle((event) => {
const newUri = event.target.value;
const separator = getProtocolSeparator(protocol);
const newValue = newUri ? `${protocol}${separator}${newUri}` : '';
onChange(newValue);
});
return (jsxs(Box, { children: [jsx(InputLabel, { htmlFor: path, required: required, children: label }), jsxs(Box, { sx: { display: 'flex', gap: 1, alignItems: 'flex-start' }, children: [jsx(Select, { value: protocol, onChange: handleProtocolChange, disabled: disabled || readOnly, size: size, displayEmpty: true, children: normalizedProtocols.map((prot) => (jsx(MenuItem, { value: prot, children: formatProtocolDisplay(prot) }, prot))) }), jsx(TextField, { inputRef: textFieldRef, id: path, name: name, variant: variant, fullWidth: fullWidth, size: size, placeholder: jsonSchema.placeholder, defaultValue: initialUri, onChange: handleUriChange, disabled: disabled, slotProps: {
input: {
readOnly,
},
} })] })] }));
};
const FormTypeInputUriDefinition = {
Component: FormTypeInputUri,
test: ({ type, format, formType }) => type === 'string' && (format === 'uri' || formType === 'uri'),
};
const formTypeInputDefinitions = [
FormTypeInputBooleanSwitchDefinition,
FormTypeInputStringCheckboxDefinition,
FormTypeInputStringSwitchDefinition,
FormTypeInputUriDefinition,
FormTypeInputMonthDefinition,
FormTypeInputDateDefinition,
FormTypeInputTimeDefinition,
FormTypeInputRadioGroupDefinition,
FormTypeInputStringEnumDefinition,
FormTypeInputArrayDefinition,
FormTypeInputSliderDefinition,
FormTypeInputTextareaDefinition,
FormTypeInputStringDefinition,
FormTypeInputNumberDefinition,
FormTypeInputBooleanDefinition,
];
const plugin = {
FormGroup,
FormLabel,
FormInput,
FormError,
formTypeInputDefinitions,
};
export { plugin };