I’m using 3.5.1. Here is my partial package.json
...
"@jsonforms/core": "^3.5.1",
"@jsonforms/react": "^3.5.1",
"@jsonforms/vanilla-renderers": "^3.5.1",
...
I’m using a control for oneOf, called one-of-control.tsx
import * as _ from 'lodash-es';
import { useCallback, useMemo, useState } from 'react';
import {
CombinatorRendererProps,
createCombinatorRenderInfos,
isOneOfControl,
RankedTester,
rankWith,
} from '@jsonforms/core';
import CombinatorProperties from './combinator-properties';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@dcx-dac/ui';
import { JsonFormsDispatch, withJsonFormsOneOfProps } from '@jsonforms/react';
import { TabSwitchConfirmDialog } from './components/tab-switch-confirm-dialog';
import { withVanillaControlProps } from '@jsonforms/vanilla-renderers';
const OneOfControl = ({
id,
path,
data,
cells,
schema,
visible,
uischema,
uischemas,
renderers,
rootSchema,
handleChange,
indexOfFittingSchema,
}: CombinatorRendererProps) => {
const oneOfRenderInfos = useMemo(
() => createCombinatorRenderInfos((schema as JsonSchema).oneOf, rootSchema, 'oneOf', uischema, path, uischemas),
[schema, rootSchema, uischema, path, uischemas]
);
const [value, setValue] = useState(oneOfRenderInfos[indexOfFittingSchema || 0].label);
const [nextValue, setNextValue] = useState('');
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const openNewTab = (value: string) => {
// ❌ Cannot use this code because it will cause validation errors
// const index = oneOfRenderInfos.findIndex(({ label }) => label === value);
// const defaultValue = createDefaultValue(oneOfRenderInfos[index].schema, rootSchema);
// handleChange(path, defaultValue);
// remove property "path" from data to prevent validation errors
handleChange(path, undefined);
setValue(value);
};
const handleTabChange = useCallback(
(value: string) => {
setNextValue(value);
if (_.isEmpty(data)) {
openNewTab(value);
} else {
setConfirmDialogOpen(true);
}
},
[data, setConfirmDialogOpen, setNextValue, openNewTab]
);
if (!visible) return null;
return (
<>
<CombinatorProperties path={path} schema={schema} rootSchema={rootSchema} combinatorKeyword="oneOf" />
<Tabs
value={value}
onValueChange={handleTabChange}
defaultValue={oneOfRenderInfos[indexOfFittingSchema || 0].label}
>
<div className="w-full flex justify-center">
<TabsList>
{oneOfRenderInfos.map(({ label }) => {
return (
<TabsTrigger key={`${label.toLowerCase()}-tab-trigger`} value={label}>
{label}
</TabsTrigger>
);
})}
</TabsList>
</div>
{oneOfRenderInfos.map(({ label, schema, uischema }) => (
<TabsContent value={label} key={`${label.toLowerCase()}-tab-content`}>
<JsonFormsDispatch path={path} cells={cells} schema={schema} uischema={uischema} renderers={renderers} />
</TabsContent>
))}
</Tabs>
<TabSwitchConfirmDialog
id={'oneOf-' + id}
open={confirmDialogOpen}
onCancel={() => setConfirmDialogOpen(false)}
onConfirm={() => {
openNewTab(nextValue);
setConfirmDialogOpen(false);
}}
onOpenChange={setConfirmDialogOpen}
/>
</>
);
};
export const oneOfControlTester: RankedTester = rankWith(3, isOneOfControl);
export default withVanillaControlProps(withJsonFormsOneOfProps(OneOfControl));
and an array-control.tsx
import * as _ from 'lodash-es';
import {
ArrayControlProps,
ArrayTranslations,
composePaths,
ControlElement,
createDefaultValue,
findUISchema,
Helpers,
isObjectArrayControl,
isPrimitiveArrayControl,
or,
RankedTester,
rankWith,
} from '@jsonforms/core';
import { VanillaRendererProps, withVanillaControlProps } from '@jsonforms/vanilla-renderers';
import {
JsonFormsDispatch,
withArrayTranslationProps,
withJsonFormsArrayControlProps,
withTranslateProps,
} from '@jsonforms/react';
import * as React from 'react';
import { Button, cn, Label, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@dcx-dac/ui';
import { ArrowDown, ArrowUp, PlusIcon, XIcon } from 'lucide-react';
const ArrayControl = ({
data,
label,
path,
schema,
errors,
moveUp,
addItem,
enabled,
moveDown,
uischema,
required,
uischemas,
renderers,
rootSchema,
removeItems,
handleChange,
}: ArrayControlProps & VanillaRendererProps & { translations: ArrayTranslations }) => {
const childUiSchema = React.useMemo(
() => findUISchema(uischemas, schema, uischema.scope, path, 'HorizontalLayout', uischema, rootSchema),
[uischemas, schema, uischema.scope, path, uischema, rootSchema]
);
return (
<div className="flex flex-col space-y-2">
<header className="flex justify-between items-center">
<Label>
{label}
{required ? '*' : ''}
</Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
className="h-8 w-8 rounded-full"
onClick={addItem(path, createDefaultValue(schema, rootSchema))}
>
<PlusIcon />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Add {label.toLowerCase()}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</header>
{errors && errors.length > 0 && <p className={cn('h-4 text-[0.8rem] font-medium text-destructive')}>{errors}</p>}
{!data || !Array.isArray(data) || data.length === 0 ? (
<p className="text-center text-muted-foreground">No {label.toLowerCase()} added yet.</p>
) : (
data.map((button, index) => {
const childPath = composePaths(path, `${index}`);
return (
<div key={`button-${index + 1}`} className="flex items-center space-x-4">
<JsonFormsDispatch
key={childPath}
path={childPath}
schema={schema}
uischema={childUiSchema || uischema}
renderers={renderers}
/>
<div className="flex">
<Button
size="icon"
variant="ghost"
disabled={!enabled || index === 0}
aria-label="Move up"
onClick={moveUp!(path, index)}
>
<ArrowUp />
</Button>
<Button
size="icon"
variant="ghost"
disabled={!enabled || index === data.length - 1}
aria-label="Move down"
onClick={moveDown!(path, index)}
>
<ArrowDown />
</Button>
<Button
size="icon"
variant="ghost"
disabled={!enabled}
aria-label="Remove"
onClick={() => {
if (window.confirm('Are you sure you wish to delete this item?')) {
removeItems!(path, [index])();
// ---------- here is where I have to remove the "connections" property ----------
// if there is only one item left, remove the property from the data
if (data.length === 1) {
handleChange(path, undefined);
}
}
}}
>
<XIcon />
</Button>
</div>
</div>
);
})
)}
</div>
);
};
export const ArrayControlRenderer = ({
schema,
uischema,
data,
path,
rootSchema,
uischemas,
addItem,
getStyle,
getStyleAsClassName,
removeItems,
moveUp,
moveDown,
id,
visible,
enabled,
errors,
translations,
arraySchema,
...rest
}: ArrayControlProps & VanillaRendererProps & { translations: ArrayTranslations }) => {
const controlElement = uischema as ControlElement;
const labelDescription = Helpers.createLabelDescriptionFrom(controlElement, schema);
const label = labelDescription.show ? labelDescription.text : '';
const controlClassName = `control ${Helpers.convertToValidClassName(controlElement.scope)}`;
const fieldSetClassName = getStyleAsClassName?.('array.layout');
const buttonClassName = getStyleAsClassName?.('array.button');
const childrenClassName = getStyleAsClassName?.('array.children');
const classNames: { [className: string]: string } = {
wrapper: controlClassName,
fieldSet: fieldSetClassName,
button: buttonClassName,
children: childrenClassName,
};
return (
<ArrayControl
classNames={classNames}
data={data}
label={label}
path={path}
schema={schema}
arraySchema={arraySchema}
errors={errors}
addItem={addItem}
removeItems={removeItems}
moveUp={moveUp}
moveDown={moveDown}
uischema={uischema}
uischemas={uischemas}
getStyleAsClassName={getStyleAsClassName}
rootSchema={rootSchema}
id={id}
visible={visible}
enabled={enabled}
getStyle={getStyle}
translations={translations}
{...rest}
/>
);
};
export const arrayControlTester: RankedTester = rankWith(1000, or(isObjectArrayControl, isPrimitiveArrayControl));
export default withVanillaControlProps(
withJsonFormsArrayControlProps(withTranslateProps(withArrayTranslationProps(ArrayControlRenderer)))
);