@vendure/dashboard 3.4.1-master-202508210231 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +152 -152
- package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +2 -1
- package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +1 -0
- package/src/lib/components/data-input/affixed-input.tsx +19 -5
- package/src/lib/components/data-input/boolean-input.tsx +9 -0
- package/src/lib/components/data-input/checkbox-input.tsx +8 -0
- package/src/lib/components/data-input/combination-mode-input.tsx +11 -2
- package/src/lib/components/data-input/configurable-operation-list-input.tsx +26 -401
- package/src/lib/components/data-input/custom-field-list-input.tsx +18 -25
- package/src/lib/components/data-input/customer-group-input.tsx +7 -11
- package/src/lib/components/data-input/datetime-input.tsx +9 -13
- package/src/lib/components/data-input/default-relation-input.tsx +29 -9
- package/src/lib/components/data-input/facet-value-input.tsx +15 -13
- package/src/lib/components/data-input/index.ts +2 -2
- package/src/lib/components/data-input/money-input.tsx +27 -20
- package/src/lib/components/data-input/number-input.tsx +48 -0
- package/src/lib/components/data-input/password-input.tsx +16 -0
- package/src/lib/components/data-input/{product-multi-selector.tsx → product-multi-selector-input.tsx} +8 -15
- package/src/lib/components/data-input/relation-input.tsx +7 -6
- package/src/lib/components/data-input/rich-text-input.tsx +10 -13
- package/src/lib/components/data-input/select-with-options.tsx +29 -17
- package/src/lib/components/data-input/struct-form-input.tsx +54 -59
- package/src/lib/components/data-input/text-input.tsx +9 -0
- package/src/lib/components/data-input/textarea-input.tsx +16 -0
- package/src/lib/components/data-table/filters/data-table-number-filter.tsx +3 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +16 -5
- package/src/lib/components/shared/configurable-operation-arg-input.tsx +3 -10
- package/src/lib/components/shared/configurable-operation-input.tsx +1 -6
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +8 -5
- package/src/lib/components/shared/configurable-operation-selector.tsx +5 -5
- package/src/lib/components/shared/custom-fields-form.tsx +20 -49
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/framework/component-registry/component-registry.tsx +9 -32
- package/src/lib/framework/component-registry/display-component.tsx +28 -0
- package/src/lib/framework/extension-api/display-component-extensions.tsx +0 -14
- package/src/lib/framework/extension-api/input-component-extensions.tsx +52 -34
- package/src/lib/framework/extension-api/logic/data-table.ts +4 -27
- package/src/lib/framework/extension-api/logic/form-components.ts +3 -2
- package/src/lib/framework/extension-api/types/detail-forms.ts +2 -38
- package/src/lib/framework/extension-api/types/form-components.ts +2 -4
- package/src/lib/framework/form-engine/custom-form-component-extensions.ts +0 -23
- package/src/lib/framework/form-engine/custom-form-component.tsx +8 -25
- package/src/lib/framework/form-engine/default-input-for-type.tsx +35 -0
- package/src/lib/framework/form-engine/form-control-adapter.tsx +192 -0
- package/src/lib/framework/form-engine/form-engine-types.ts +163 -0
- package/src/lib/framework/form-engine/form-schema-tools.ts +55 -71
- package/src/lib/framework/form-engine/overridden-form-component.tsx +2 -2
- package/src/lib/framework/form-engine/utils.ts +223 -0
- package/src/lib/{components/shared → framework/form-engine}/value-transformers.ts +9 -9
- package/src/lib/framework/registry/registry-types.ts +3 -5
- package/src/lib/graphql/graphql-env.d.ts +11 -7
- package/src/lib/index.ts +28 -1
- package/src/lib/providers/server-config.tsx +1 -0
- package/src/lib/components/shared/direct-form-component-map.tsx +0 -393
- package/src/lib/components/shared/universal-field-definition.ts +0 -118
- package/src/lib/components/shared/universal-form-input.tsx +0 -175
- package/src/lib/components/shared/universal-input-components.tsx +0 -291
- package/src/lib/framework/component-registry/dynamic-component.tsx +0 -58
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useComponentRegistry } from './component-registry.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @description
|
|
6
|
+
* This component is used to delegate the rendering of a component to the component registry.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* <Delegate component="money.display.default" value={100} />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export function DisplayComponent(
|
|
16
|
+
props: Readonly<{
|
|
17
|
+
id: string;
|
|
18
|
+
value: any;
|
|
19
|
+
}>,
|
|
20
|
+
): React.ReactNode {
|
|
21
|
+
const { getDisplayComponent } = useComponentRegistry();
|
|
22
|
+
const Component = getDisplayComponent(props.id.toString());
|
|
23
|
+
if (!Component) {
|
|
24
|
+
throw new Error(`Component with id ${props.id.toString()} not found`);
|
|
25
|
+
}
|
|
26
|
+
const { value, ...rest } = props;
|
|
27
|
+
return <Component value={value} {...rest} />;
|
|
28
|
+
}
|
|
@@ -22,20 +22,6 @@ export function getDisplayComponent(id: string): DataDisplayComponent | undefine
|
|
|
22
22
|
return globalRegistry.get('displayComponents').get(id);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* @description
|
|
27
|
-
* Gets a display component using the targeting properties.
|
|
28
|
-
* Uses the same key pattern as registration: pageId_blockId_fieldName
|
|
29
|
-
*/
|
|
30
|
-
export function getTargetedDisplayComponent(
|
|
31
|
-
pageId: string,
|
|
32
|
-
blockId: string,
|
|
33
|
-
field: string,
|
|
34
|
-
): DataDisplayComponent | undefined {
|
|
35
|
-
const key = generateDisplayComponentKey(pageId, blockId, field);
|
|
36
|
-
return globalRegistry.get('displayComponents').get(key);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
25
|
/**
|
|
40
26
|
* @description
|
|
41
27
|
* Generates a display component key based on the targeting properties.
|
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
import { CombinationModeInput } from '@/vdb/components/data-input/combination-mode-input.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { DefaultRelationInput } from '@/vdb/components/data-input/default-relation-input.js';
|
|
3
|
+
import {
|
|
4
|
+
CustomerGroupInput,
|
|
5
|
+
FacetValueInput,
|
|
6
|
+
MoneyInput,
|
|
7
|
+
ProductMultiInput,
|
|
8
|
+
RichTextInput,
|
|
9
|
+
SelectWithOptions,
|
|
10
|
+
} from '@/vdb/components/data-input/index.js';
|
|
11
|
+
import { PasswordInput } from '@/vdb/components/data-input/password-input.js';
|
|
12
|
+
import { TextareaInput } from '@/vdb/components/data-input/textarea-input.js';
|
|
13
|
+
import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
9
14
|
import { globalRegistry } from '../registry/global-registry.js';
|
|
10
15
|
|
|
11
|
-
globalRegistry.register('inputComponents', new Map<string,
|
|
12
|
-
|
|
13
|
-
// Create component functions for built-in components
|
|
14
|
-
const TextInput: DataInputComponent = props => (
|
|
15
|
-
<Input {...props} onChange={e => props.onChange(e.target.value)} />
|
|
16
|
-
);
|
|
17
|
-
const NumberInput: DataInputComponent = props => (
|
|
18
|
-
<Input {...props} onChange={e => props.onChange(e.target.valueAsNumber)} type="number" />
|
|
19
|
-
);
|
|
20
|
-
const CheckboxInput: DataInputComponent = props => (
|
|
21
|
-
<Checkbox
|
|
22
|
-
{...props}
|
|
23
|
-
checked={props.value === 'true' || props.value === true}
|
|
24
|
-
onCheckedChange={value => props.onChange(value)}
|
|
25
|
-
/>
|
|
26
|
-
);
|
|
16
|
+
globalRegistry.register('inputComponents', new Map<string, DashboardFormComponent>());
|
|
27
17
|
|
|
28
18
|
// Register built-in input components
|
|
29
19
|
const inputComponents = globalRegistry.get('inputComponents');
|
|
30
|
-
inputComponents.set('
|
|
31
|
-
inputComponents.set('
|
|
32
|
-
inputComponents.set('
|
|
33
|
-
inputComponents.set('
|
|
34
|
-
inputComponents.set('
|
|
35
|
-
inputComponents.set('
|
|
36
|
-
inputComponents.set('
|
|
37
|
-
inputComponents.set('
|
|
20
|
+
inputComponents.set('facet-value-input', FacetValueInput);
|
|
21
|
+
inputComponents.set('combination-mode-input', CombinationModeInput);
|
|
22
|
+
inputComponents.set('product-multi-input', ProductMultiInput);
|
|
23
|
+
inputComponents.set('currency-form-input', MoneyInput);
|
|
24
|
+
inputComponents.set('customer-group-form-input', CustomerGroupInput);
|
|
25
|
+
inputComponents.set('facet-value-form-input', FacetValueInput);
|
|
26
|
+
inputComponents.set('json-editor-form-input', TextareaInput);
|
|
27
|
+
inputComponents.set('textarea-form-input', TextareaInput);
|
|
28
|
+
inputComponents.set('html-editor-form-input', RichTextInput);
|
|
29
|
+
inputComponents.set('rich-text-form-input', RichTextInput);
|
|
30
|
+
inputComponents.set('password-form-input', PasswordInput);
|
|
31
|
+
inputComponents.set('product-selector-form-input', DefaultRelationInput);
|
|
32
|
+
inputComponents.set('relation-form-input', DefaultRelationInput);
|
|
33
|
+
inputComponents.set('select-form-input', SelectWithOptions);
|
|
34
|
+
inputComponents.set('product-multi-form-input', ProductMultiInput);
|
|
35
|
+
inputComponents.set('combination-mode-form-input', CombinationModeInput);
|
|
38
36
|
|
|
39
|
-
export function getInputComponent(id: string):
|
|
40
|
-
|
|
37
|
+
export function getInputComponent(id: string | undefined): DashboardFormComponent | undefined {
|
|
38
|
+
if (!id) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const inputComponent = globalRegistry.get('inputComponents').get(id);
|
|
42
|
+
return inputComponent;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -58,7 +60,7 @@ export function addInputComponent({
|
|
|
58
60
|
pageId: string;
|
|
59
61
|
blockId: string;
|
|
60
62
|
field: string;
|
|
61
|
-
component:
|
|
63
|
+
component: DashboardFormComponent;
|
|
62
64
|
}) {
|
|
63
65
|
const inputComponents = globalRegistry.get('inputComponents');
|
|
64
66
|
|
|
@@ -71,3 +73,19 @@ export function addInputComponent({
|
|
|
71
73
|
}
|
|
72
74
|
inputComponents.set(key, component);
|
|
73
75
|
}
|
|
76
|
+
|
|
77
|
+
export function addCustomFieldInputComponent({
|
|
78
|
+
id,
|
|
79
|
+
component,
|
|
80
|
+
}: {
|
|
81
|
+
id: string;
|
|
82
|
+
component: DashboardFormComponent;
|
|
83
|
+
}) {
|
|
84
|
+
const inputComponents = globalRegistry.get('inputComponents');
|
|
85
|
+
|
|
86
|
+
if (inputComponents.has(id)) {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.warn(`Input component with key "${id}" is already registered and will be overwritten.`);
|
|
89
|
+
}
|
|
90
|
+
inputComponents.set(id, component);
|
|
91
|
+
}
|
|
@@ -4,28 +4,6 @@ import { addBulkAction, addListQueryDocument } from '../../data-table/data-table
|
|
|
4
4
|
import { addDisplayComponent } from '../display-component-extensions.js';
|
|
5
5
|
import { DashboardDataTableExtensionDefinition } from '../types/index.js';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* @description
|
|
9
|
-
* Generates a data table display component key based on the pageId and column name.
|
|
10
|
-
* Uses the pattern: pageId_columnName
|
|
11
|
-
*/
|
|
12
|
-
export function generateDataTableDisplayComponentKey(pageId: string, column: string): string {
|
|
13
|
-
return `${pageId}_${column}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @description
|
|
18
|
-
* Adds a display component for a specific column in a data table.
|
|
19
|
-
*/
|
|
20
|
-
export function addDataTableDisplayComponent(
|
|
21
|
-
pageId: string,
|
|
22
|
-
column: string,
|
|
23
|
-
component: React.ComponentType<{ value: any; [key: string]: any }>,
|
|
24
|
-
) {
|
|
25
|
-
const key = generateDataTableDisplayComponentKey(pageId, column);
|
|
26
|
-
addDisplayComponent({ pageId, blockId: 'list-table', field: column, component });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
7
|
export function registerDataTableExtensions(dataTables?: DashboardDataTableExtensionDefinition[]) {
|
|
30
8
|
if (dataTables) {
|
|
31
9
|
for (const dataTable of dataTables) {
|
|
@@ -48,11 +26,10 @@ export function registerDataTableExtensions(dataTables?: DashboardDataTableExten
|
|
|
48
26
|
}
|
|
49
27
|
if (dataTable.displayComponents?.length) {
|
|
50
28
|
for (const displayComponent of dataTable.displayComponents) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
29
|
+
const blockId = dataTable.blockId ?? 'list-table';
|
|
30
|
+
const { pageId } = dataTable;
|
|
31
|
+
const { column, component } = displayComponent;
|
|
32
|
+
addDisplayComponent({ pageId, blockId, field: column, component });
|
|
56
33
|
}
|
|
57
34
|
}
|
|
58
35
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { addCustomFieldInputComponent } from '@/vdb/framework/extension-api/input-component-extensions.js';
|
|
2
|
+
|
|
2
3
|
import { DashboardCustomFormComponents } from '../types/form-components.js';
|
|
3
4
|
|
|
4
5
|
export function registerFormComponentExtensions(customFormComponents?: DashboardCustomFormComponents) {
|
|
@@ -6,7 +7,7 @@ export function registerFormComponentExtensions(customFormComponents?: Dashboard
|
|
|
6
7
|
// Handle custom field components
|
|
7
8
|
if (customFormComponents.customFields) {
|
|
8
9
|
for (const component of customFormComponents.customFields) {
|
|
9
|
-
|
|
10
|
+
addCustomFieldInputComponent(component);
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DataDisplayComponent,
|
|
3
|
-
DataInputComponent,
|
|
4
|
-
} from '@/vdb/framework/component-registry/component-registry.js';
|
|
1
|
+
import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
5
2
|
import { DocumentNode } from 'graphql';
|
|
6
3
|
|
|
7
4
|
/**
|
|
@@ -29,35 +26,7 @@ export interface DashboardDetailFormInputComponent {
|
|
|
29
26
|
* The React component that will be rendered as the input.
|
|
30
27
|
* It should accept `value`, `onChange`, and other standard input props.
|
|
31
28
|
*/
|
|
32
|
-
component:
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @description
|
|
37
|
-
* Allows you to define custom display components for specific fields in detail forms.
|
|
38
|
-
* The pageId is already defined in the detail form extension, so only the blockId and field are needed.
|
|
39
|
-
*
|
|
40
|
-
* @docsCategory extensions
|
|
41
|
-
* @docsPage DetailForms
|
|
42
|
-
* @since 3.4.0
|
|
43
|
-
*/
|
|
44
|
-
export interface DashboardDetailFormDisplayComponent {
|
|
45
|
-
/**
|
|
46
|
-
* @description
|
|
47
|
-
* The ID of the block where this display component should be used.
|
|
48
|
-
*/
|
|
49
|
-
blockId: string;
|
|
50
|
-
/**
|
|
51
|
-
* @description
|
|
52
|
-
* The name of the field where this display component should be used.
|
|
53
|
-
*/
|
|
54
|
-
field: string;
|
|
55
|
-
/**
|
|
56
|
-
* @description
|
|
57
|
-
* The React component that will be rendered as the display.
|
|
58
|
-
* It should accept `value` and other standard display props.
|
|
59
|
-
*/
|
|
60
|
-
component: DataDisplayComponent;
|
|
29
|
+
component: DashboardFormComponent;
|
|
61
30
|
}
|
|
62
31
|
|
|
63
32
|
/**
|
|
@@ -86,9 +55,4 @@ export interface DashboardDetailFormExtensionDefinition {
|
|
|
86
55
|
* Custom input components for specific fields in the detail form.
|
|
87
56
|
*/
|
|
88
57
|
inputs?: DashboardDetailFormInputComponent[];
|
|
89
|
-
/**
|
|
90
|
-
* @description
|
|
91
|
-
* Custom display components for specific fields in the detail form.
|
|
92
|
-
*/
|
|
93
|
-
displays?: DashboardDetailFormDisplayComponent[];
|
|
94
58
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { CustomFormComponentInputProps } from '../../form-engine/custom-form-component.js';
|
|
1
|
+
import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* @description
|
|
@@ -21,7 +19,7 @@ export interface DashboardCustomFormComponent {
|
|
|
21
19
|
* @description
|
|
22
20
|
* The React component that will be rendered as the custom form input.
|
|
23
21
|
*/
|
|
24
|
-
component:
|
|
22
|
+
component: DashboardFormComponent;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/**
|
|
@@ -1,32 +1,9 @@
|
|
|
1
1
|
import { DocumentNode } from 'graphql';
|
|
2
2
|
|
|
3
|
-
import { DashboardCustomFormComponent } from '../extension-api/extension-api-types.js';
|
|
4
3
|
import { globalRegistry } from '../registry/global-registry.js';
|
|
5
4
|
|
|
6
|
-
import { CustomFormComponentInputProps } from './custom-form-component.js';
|
|
7
|
-
|
|
8
|
-
globalRegistry.register(
|
|
9
|
-
'customFormComponents',
|
|
10
|
-
new Map<string, React.FunctionComponent<CustomFormComponentInputProps>>(),
|
|
11
|
-
);
|
|
12
|
-
|
|
13
5
|
globalRegistry.register('detailQueryDocumentRegistry', new Map<string, DocumentNode[]>());
|
|
14
6
|
|
|
15
|
-
export function getCustomFormComponent(
|
|
16
|
-
id: string,
|
|
17
|
-
): React.FunctionComponent<CustomFormComponentInputProps> | undefined {
|
|
18
|
-
return globalRegistry.get('customFormComponents').get(id);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function addCustomFormComponent({ id, component }: DashboardCustomFormComponent) {
|
|
22
|
-
const customFormComponents = globalRegistry.get('customFormComponents');
|
|
23
|
-
if (customFormComponents.has(id)) {
|
|
24
|
-
// eslint-disable-next-line no-console
|
|
25
|
-
console.warn(`Custom form component with id "${id}" is already registered and will be overwritten.`);
|
|
26
|
-
}
|
|
27
|
-
customFormComponents.set(id, component);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
7
|
export function getDetailQueryDocuments(pageId: string): DocumentNode[] {
|
|
31
8
|
return globalRegistry.get('detailQueryDocumentRegistry').get(pageId) || [];
|
|
32
9
|
}
|
|
@@ -1,33 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
ControllerFieldState,
|
|
4
|
-
ControllerRenderProps,
|
|
5
|
-
FieldPath,
|
|
6
|
-
FieldValues,
|
|
7
|
-
UseFormStateReturn,
|
|
8
|
-
} from 'react-hook-form';
|
|
9
|
-
import { getCustomFormComponent } from './custom-form-component-extensions.js';
|
|
1
|
+
import { getInputComponent } from '@/vdb/framework/extension-api/input-component-extensions.js';
|
|
10
2
|
|
|
11
|
-
|
|
12
|
-
fieldProps: CustomFormComponentInputProps;
|
|
13
|
-
fieldDef: Pick<CustomFieldConfig, 'ui' | 'type' | 'name'>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface CustomFormComponentInputProps<
|
|
17
|
-
TFieldValues extends FieldValues = FieldValues,
|
|
18
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
19
|
-
> {
|
|
20
|
-
field: ControllerRenderProps<TFieldValues, TName>;
|
|
21
|
-
fieldState: ControllerFieldState;
|
|
22
|
-
formState: UseFormStateReturn<TFieldValues>;
|
|
23
|
-
}
|
|
3
|
+
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
24
4
|
|
|
25
|
-
export function CustomFormComponent(
|
|
26
|
-
|
|
5
|
+
export function CustomFormComponent(props: DashboardFormComponentProps) {
|
|
6
|
+
if (!props.fieldDef) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const Component = getInputComponent(props.fieldDef.ui?.component);
|
|
27
10
|
|
|
28
11
|
if (!Component) {
|
|
29
12
|
return null;
|
|
30
13
|
}
|
|
31
14
|
|
|
32
|
-
return <Component {...
|
|
15
|
+
return <Component {...props} />;
|
|
33
16
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BooleanInput } from '@/vdb/components/data-input/boolean-input.js';
|
|
2
|
+
import { DefaultRelationInput } from '@/vdb/components/data-input/default-relation-input.js';
|
|
3
|
+
import { DateTimeInput, SelectWithOptions } from '@/vdb/components/data-input/index.js';
|
|
4
|
+
import { NumberInput } from '@/vdb/components/data-input/number-input.js';
|
|
5
|
+
import { TextInput } from '@/vdb/components/data-input/text-input.js';
|
|
6
|
+
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
7
|
+
import { isStringFieldWithOptions } from '@/vdb/framework/form-engine/utils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Consolidated input component for rendering form inputs based on field type
|
|
11
|
+
* This replaces the duplicate implementations in custom fields and config args
|
|
12
|
+
*/
|
|
13
|
+
export function DefaultInputForType({ fieldDef, ...fieldProps }: Readonly<DashboardFormComponentProps>) {
|
|
14
|
+
const type = fieldDef?.type;
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'int':
|
|
17
|
+
case 'float':
|
|
18
|
+
return <NumberInput {...fieldProps} fieldDef={fieldDef} />;
|
|
19
|
+
case 'boolean':
|
|
20
|
+
return <BooleanInput {...fieldProps} fieldDef={fieldDef} />;
|
|
21
|
+
case 'datetime':
|
|
22
|
+
return <DateTimeInput {...fieldProps} fieldDef={fieldDef} />;
|
|
23
|
+
case 'relation':
|
|
24
|
+
return <DefaultRelationInput {...fieldProps} fieldDef={fieldDef} />;
|
|
25
|
+
case 'string': {
|
|
26
|
+
if (fieldDef && isStringFieldWithOptions(fieldDef)) {
|
|
27
|
+
return <SelectWithOptions {...fieldProps} fieldDef={fieldDef} />;
|
|
28
|
+
} else {
|
|
29
|
+
return <TextInput {...fieldProps} fieldDef={fieldDef} />;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
default:
|
|
33
|
+
return <TextInput {...fieldProps} fieldDef={fieldDef} />;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { JSX, useMemo } from 'react';
|
|
2
|
+
import { ControllerRenderProps } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
import { CustomFieldListInput } from '@/vdb/components/data-input/custom-field-list-input.js';
|
|
5
|
+
import { StructFormInput } from '@/vdb/components/data-input/struct-form-input.js';
|
|
6
|
+
import { ConfigurableOperationListInput } from '../../components/data-input/configurable-operation-list-input.js';
|
|
7
|
+
|
|
8
|
+
import { getInputComponent } from '@/vdb/framework/extension-api/input-component-extensions.js';
|
|
9
|
+
import {
|
|
10
|
+
ConfigurableFieldDef,
|
|
11
|
+
DashboardFormComponent,
|
|
12
|
+
} from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
13
|
+
import { isCustomFieldConfig } from '@/vdb/framework/form-engine/utils.js';
|
|
14
|
+
import { DefaultInputForType } from './default-input-for-type.js';
|
|
15
|
+
import { transformValue, ValueMode } from './value-transformers.js';
|
|
16
|
+
|
|
17
|
+
export interface FormControlAdapterProps {
|
|
18
|
+
fieldDef: ConfigurableFieldDef;
|
|
19
|
+
field: ControllerRenderProps<any, any>;
|
|
20
|
+
valueMode: ValueMode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets the default value for list inputs based on field type
|
|
25
|
+
*/
|
|
26
|
+
function getDefaultValueForType(fieldType: string): any {
|
|
27
|
+
switch (fieldType) {
|
|
28
|
+
case 'string':
|
|
29
|
+
case 'localeString':
|
|
30
|
+
case 'localeText':
|
|
31
|
+
return '';
|
|
32
|
+
case 'int':
|
|
33
|
+
case 'float':
|
|
34
|
+
return 0;
|
|
35
|
+
case 'boolean':
|
|
36
|
+
return false;
|
|
37
|
+
default:
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validates if a custom component is correctly configured for list fields
|
|
44
|
+
*/
|
|
45
|
+
function validateCustomComponent(
|
|
46
|
+
CustomComponent: any,
|
|
47
|
+
componentId: string,
|
|
48
|
+
fieldName: string,
|
|
49
|
+
isList: boolean,
|
|
50
|
+
): void {
|
|
51
|
+
if (!CustomComponent.metadata?.isListInput || CustomComponent.metadata.isListInput === 'dynamic') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const isConfiguredForList = CustomComponent.metadata.isListInput === true;
|
|
56
|
+
if (isConfiguredForList !== isList) {
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.warn([
|
|
59
|
+
`Custom component ${componentId} is not correctly configured for the ${fieldName} field:`,
|
|
60
|
+
`The component ${isConfiguredForList ? 'is' : 'is not'} configured as a list input, but the field ${isList ? 'is' : 'is not'} a list field.`,
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Determines if a custom component can be used for the given field configuration
|
|
67
|
+
*/
|
|
68
|
+
function canUseCustomComponent(
|
|
69
|
+
CustomComponent: DashboardFormComponent | undefined,
|
|
70
|
+
isList: boolean,
|
|
71
|
+
): CustomComponent is DashboardFormComponent {
|
|
72
|
+
if (!CustomComponent) return false;
|
|
73
|
+
|
|
74
|
+
const listInputMode = CustomComponent.metadata?.isListInput;
|
|
75
|
+
|
|
76
|
+
// Dynamic components can handle both list and non-list
|
|
77
|
+
if (listInputMode === 'dynamic') return true;
|
|
78
|
+
|
|
79
|
+
// Exact match: both are list or both are non-list
|
|
80
|
+
return (isList && listInputMode === true) || (!isList && listInputMode !== true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Renders struct field inputs
|
|
85
|
+
*/
|
|
86
|
+
function renderStructField(
|
|
87
|
+
fieldDef: ConfigurableFieldDef,
|
|
88
|
+
field: ControllerRenderProps<any, any>,
|
|
89
|
+
fieldWithTransform: ControllerRenderProps<any, any>,
|
|
90
|
+
isList: boolean,
|
|
91
|
+
isReadonly: boolean,
|
|
92
|
+
): JSX.Element {
|
|
93
|
+
if (isList) {
|
|
94
|
+
return (
|
|
95
|
+
<CustomFieldListInput
|
|
96
|
+
{...field}
|
|
97
|
+
disabled={isReadonly}
|
|
98
|
+
renderInput={(index, inputField) => (
|
|
99
|
+
<StructFormInput {...fieldWithTransform} fieldDef={fieldDef} />
|
|
100
|
+
)}
|
|
101
|
+
defaultValue={{}}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return <StructFormInput {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Renders list field inputs
|
|
110
|
+
*/
|
|
111
|
+
function renderListField(
|
|
112
|
+
fieldDef: ConfigurableFieldDef,
|
|
113
|
+
field: ControllerRenderProps<any, any>,
|
|
114
|
+
fieldWithTransform: ControllerRenderProps<any, any>,
|
|
115
|
+
valueMode: ValueMode,
|
|
116
|
+
isReadonly: boolean,
|
|
117
|
+
): JSX.Element {
|
|
118
|
+
if (valueMode === 'json-string') {
|
|
119
|
+
return <ConfigurableOperationListInput {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (fieldDef.type === 'relation') {
|
|
123
|
+
return <DefaultInputForType {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (fieldDef.type === 'string') {
|
|
127
|
+
return <DefaultInputForType {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<CustomFieldListInput
|
|
132
|
+
{...field}
|
|
133
|
+
disabled={isReadonly}
|
|
134
|
+
renderInput={(index, inputField) => <DefaultInputForType {...inputField} fieldDef={fieldDef} />}
|
|
135
|
+
defaultValue={getDefaultValueForType(fieldDef.type)}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* This is a wrapper component around the final DashboardFormComponent instances.
|
|
142
|
+
*
|
|
143
|
+
* It is responsible for ensuring the correct props get passed to the final component,
|
|
144
|
+
* and for handling differences between form control use between:
|
|
145
|
+
*
|
|
146
|
+
* - Auto-generated forms
|
|
147
|
+
* - Custom field forms
|
|
148
|
+
* - Configurable operation forms
|
|
149
|
+
*/
|
|
150
|
+
export function FormControlAdapter({ fieldDef, field, valueMode }: Readonly<FormControlAdapterProps>) {
|
|
151
|
+
const isList = fieldDef.list ?? false;
|
|
152
|
+
const isReadonly = isCustomFieldConfig(fieldDef) ? fieldDef.readonly === true : false;
|
|
153
|
+
const componentId = fieldDef.ui?.component as string | undefined;
|
|
154
|
+
|
|
155
|
+
const fieldWithTransform = useMemo(() => {
|
|
156
|
+
const fieldOnChange = field.onChange.bind(field);
|
|
157
|
+
const transformedField: FormControlAdapterProps['field'] = {
|
|
158
|
+
...field,
|
|
159
|
+
value: transformValue(field.value, fieldDef, valueMode, 'parse'),
|
|
160
|
+
onChange: (newValue: any) => {
|
|
161
|
+
const serializedValue = transformValue(newValue, fieldDef, valueMode, 'serialize');
|
|
162
|
+
fieldOnChange(serializedValue);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
return transformedField;
|
|
166
|
+
}, [field.name, field.value, field.onChange, fieldDef, valueMode]);
|
|
167
|
+
|
|
168
|
+
const CustomComponent = getInputComponent(componentId);
|
|
169
|
+
|
|
170
|
+
// Try to use custom component if available and compatible
|
|
171
|
+
if (canUseCustomComponent(CustomComponent, isList)) {
|
|
172
|
+
return <CustomComponent {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Validate custom component configuration for debugging
|
|
176
|
+
if (CustomComponent) {
|
|
177
|
+
validateCustomComponent(CustomComponent, componentId!, fieldDef.name, isList);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle struct fields (custom fields mode only)
|
|
181
|
+
if (fieldDef.type === 'struct' && valueMode === 'native') {
|
|
182
|
+
return renderStructField(fieldDef, field, fieldWithTransform, isList, isReadonly);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Handle list fields
|
|
186
|
+
if (isList) {
|
|
187
|
+
return renderListField(fieldDef, field, fieldWithTransform, valueMode, isReadonly);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Default case: non-list, non-struct fields
|
|
191
|
+
return <DefaultInputForType {...fieldWithTransform} fieldDef={fieldDef} />;
|
|
192
|
+
}
|