@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.
Files changed (58) hide show
  1. package/package.json +152 -152
  2. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +2 -1
  3. package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +1 -0
  4. package/src/lib/components/data-input/affixed-input.tsx +19 -5
  5. package/src/lib/components/data-input/boolean-input.tsx +9 -0
  6. package/src/lib/components/data-input/checkbox-input.tsx +8 -0
  7. package/src/lib/components/data-input/combination-mode-input.tsx +11 -2
  8. package/src/lib/components/data-input/configurable-operation-list-input.tsx +26 -401
  9. package/src/lib/components/data-input/custom-field-list-input.tsx +18 -25
  10. package/src/lib/components/data-input/customer-group-input.tsx +7 -11
  11. package/src/lib/components/data-input/datetime-input.tsx +9 -13
  12. package/src/lib/components/data-input/default-relation-input.tsx +29 -9
  13. package/src/lib/components/data-input/facet-value-input.tsx +15 -13
  14. package/src/lib/components/data-input/index.ts +2 -2
  15. package/src/lib/components/data-input/money-input.tsx +27 -20
  16. package/src/lib/components/data-input/number-input.tsx +48 -0
  17. package/src/lib/components/data-input/password-input.tsx +16 -0
  18. package/src/lib/components/data-input/{product-multi-selector.tsx → product-multi-selector-input.tsx} +8 -15
  19. package/src/lib/components/data-input/relation-input.tsx +7 -6
  20. package/src/lib/components/data-input/rich-text-input.tsx +10 -13
  21. package/src/lib/components/data-input/select-with-options.tsx +29 -17
  22. package/src/lib/components/data-input/struct-form-input.tsx +54 -59
  23. package/src/lib/components/data-input/text-input.tsx +9 -0
  24. package/src/lib/components/data-input/textarea-input.tsx +16 -0
  25. package/src/lib/components/data-table/filters/data-table-number-filter.tsx +3 -0
  26. package/src/lib/components/data-table/use-generated-columns.tsx +16 -5
  27. package/src/lib/components/shared/configurable-operation-arg-input.tsx +3 -10
  28. package/src/lib/components/shared/configurable-operation-input.tsx +1 -6
  29. package/src/lib/components/shared/configurable-operation-multi-selector.tsx +8 -5
  30. package/src/lib/components/shared/configurable-operation-selector.tsx +5 -5
  31. package/src/lib/components/shared/custom-fields-form.tsx +20 -49
  32. package/src/lib/components/shared/multi-select.tsx +1 -1
  33. package/src/lib/framework/component-registry/component-registry.tsx +9 -32
  34. package/src/lib/framework/component-registry/display-component.tsx +28 -0
  35. package/src/lib/framework/extension-api/display-component-extensions.tsx +0 -14
  36. package/src/lib/framework/extension-api/input-component-extensions.tsx +52 -34
  37. package/src/lib/framework/extension-api/logic/data-table.ts +4 -27
  38. package/src/lib/framework/extension-api/logic/form-components.ts +3 -2
  39. package/src/lib/framework/extension-api/types/detail-forms.ts +2 -38
  40. package/src/lib/framework/extension-api/types/form-components.ts +2 -4
  41. package/src/lib/framework/form-engine/custom-form-component-extensions.ts +0 -23
  42. package/src/lib/framework/form-engine/custom-form-component.tsx +8 -25
  43. package/src/lib/framework/form-engine/default-input-for-type.tsx +35 -0
  44. package/src/lib/framework/form-engine/form-control-adapter.tsx +192 -0
  45. package/src/lib/framework/form-engine/form-engine-types.ts +163 -0
  46. package/src/lib/framework/form-engine/form-schema-tools.ts +55 -71
  47. package/src/lib/framework/form-engine/overridden-form-component.tsx +2 -2
  48. package/src/lib/framework/form-engine/utils.ts +223 -0
  49. package/src/lib/{components/shared → framework/form-engine}/value-transformers.ts +9 -9
  50. package/src/lib/framework/registry/registry-types.ts +3 -5
  51. package/src/lib/graphql/graphql-env.d.ts +11 -7
  52. package/src/lib/index.ts +28 -1
  53. package/src/lib/providers/server-config.tsx +1 -0
  54. package/src/lib/components/shared/direct-form-component-map.tsx +0 -393
  55. package/src/lib/components/shared/universal-field-definition.ts +0 -118
  56. package/src/lib/components/shared/universal-form-input.tsx +0 -175
  57. package/src/lib/components/shared/universal-input-components.tsx +0 -291
  58. package/src/lib/framework/component-registry/dynamic-component.tsx +0 -58
@@ -10,30 +10,22 @@ import {
10
10
  import { Input } from '@/vdb/components/ui/input.js';
11
11
  import { Switch } from '@/vdb/components/ui/switch.js';
12
12
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
13
- import { structCustomFieldFragment } from '@/vdb/providers/server-config.js';
14
- import { ResultOf } from 'gql.tada';
15
13
  import { CheckIcon, PencilIcon, X } from 'lucide-react';
16
14
  import React, { useMemo, useState } from 'react';
17
- import { Control, ControllerRenderProps, useWatch } from 'react-hook-form';
15
+ import { ControllerRenderProps, useFormContext, useWatch } from 'react-hook-form';
18
16
 
19
17
  // Import the form input component we already have
18
+ import {
19
+ DashboardFormComponentProps,
20
+ StructCustomFieldConfig,
21
+ StructField,
22
+ } from '@/vdb/framework/form-engine/form-engine-types.js';
23
+ import { isStructFieldConfig } from '@/vdb/framework/form-engine/utils.js';
24
+ import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
20
25
  import { CustomFieldListInput } from './custom-field-list-input.js';
21
26
  import { DateTimeInput } from './datetime-input.js';
22
27
  import { SelectWithOptions } from './select-with-options.js';
23
28
 
24
- // Use the generated types from GraphQL fragments
25
- type StructCustomFieldConfig = ResultOf<typeof structCustomFieldFragment>;
26
- type StructField = StructCustomFieldConfig['fields'][number];
27
-
28
- interface StructFormInputProps {
29
- field: ControllerRenderProps<any, any>;
30
- fieldDef: StructCustomFieldConfig;
31
- control: Control<any, any>;
32
- getTranslation: (
33
- input: Array<{ languageCode: string; value: string }> | null | undefined,
34
- ) => string | undefined;
35
- }
36
-
37
29
  interface DisplayModeProps {
38
30
  fieldDef: StructCustomFieldConfig;
39
31
  watchedStructValue: Record<string, any>;
@@ -84,42 +76,30 @@ function DisplayMode({
84
76
  );
85
77
  }
86
78
 
87
- export function StructFormInput({ field, fieldDef, control, getTranslation }: StructFormInputProps) {
79
+ export function StructFormInput({ fieldDef, ...field }: Readonly<DashboardFormComponentProps>) {
88
80
  const { formatDate } = useLocalFormat();
89
- const isReadonly = fieldDef.readonly ?? false;
90
81
  const [isEditing, setIsEditing] = useState(false);
82
+ const { control } = useFormContext();
83
+ const { value, name } = field;
91
84
 
92
85
  // Watch the struct field for changes to update display mode
93
86
  const watchedStructValue =
94
87
  useWatch({
95
88
  control,
96
- name: field.name,
97
- defaultValue: field.value || {},
89
+ name,
90
+ defaultValue: value || {},
98
91
  }) || {};
99
92
 
100
- // Helper function to format field value for display
101
- const formatFieldValue = (value: any, structField: StructField) => {
102
- if (value == null) return '-';
103
- if (structField.list) {
104
- if (Array.isArray(value)) {
105
- return value.length ? value.join(', ') : '-';
106
- }
107
- return '-';
108
- }
109
- switch (structField.type) {
110
- case 'boolean':
111
- return (
112
- <span className={`inline-flex items-center ${value ? 'text-green-600' : 'text-red-500'}`}>
113
- {value ? <CheckIcon className="h-4 w-4" /> : <X className="h-4 w-4" />}
114
- </span>
115
- );
116
- case 'datetime':
117
- return value ? formatDate(value, { dateStyle: 'short', timeStyle: 'short' }) : '-';
118
- default:
119
- return value.toString();
120
- }
93
+ const {
94
+ settings: { displayLanguage },
95
+ } = useUserSettings();
96
+
97
+ const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
98
+ return input?.find(t => t.languageCode === displayLanguage)?.value;
121
99
  };
122
100
 
101
+ const isReadonly = fieldDef?.readonly === true;
102
+
123
103
  // Helper function to render individual struct field inputs
124
104
  const renderStructFieldInput = (
125
105
  structField: StructField,
@@ -135,12 +115,7 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
135
115
  const stringField = structField as any; // GraphQL union types need casting
136
116
  if (stringField.options && stringField.options.length > 0) {
137
117
  return (
138
- <SelectWithOptions
139
- field={singleField}
140
- options={stringField.options}
141
- disabled={isReadonly}
142
- isListField={false}
143
- />
118
+ <SelectWithOptions {...singleField} fieldDef={stringField} isListField={false} />
144
119
  );
145
120
  }
146
121
  return (
@@ -187,13 +162,7 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
187
162
  />
188
163
  );
189
164
  case 'datetime':
190
- return (
191
- <DateTimeInput
192
- value={singleField.value}
193
- onChange={singleField.onChange}
194
- disabled={isReadonly}
195
- />
196
- );
165
+ return <DateTimeInput {...singleField} />;
197
166
  default:
198
167
  return (
199
168
  <Input
@@ -213,8 +182,8 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
213
182
  if (stringField.options && stringField.options.length > 0) {
214
183
  return (
215
184
  <SelectWithOptions
216
- field={inputField}
217
- options={stringField.options}
185
+ {...inputField}
186
+ fieldDef={stringField}
218
187
  disabled={isReadonly}
219
188
  isListField={isList}
220
189
  />
@@ -245,11 +214,10 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
245
214
 
246
215
  return (
247
216
  <CustomFieldListInput
248
- field={inputField}
217
+ {...inputField}
249
218
  disabled={isReadonly}
250
219
  renderInput={(index, listItemField) => renderSingleStructInput(listItemField)}
251
220
  defaultValue={getDefaultValue()}
252
- isFullWidth={needsFullWidth}
253
221
  />
254
222
  );
255
223
  }
@@ -275,7 +243,7 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
275
243
  </Button>
276
244
  </div>
277
245
  )}
278
- {fieldDef.fields.map(structField => (
246
+ {fieldDef?.fields?.map(structField => (
279
247
  <FormField
280
248
  key={structField.name}
281
249
  control={control}
@@ -309,6 +277,33 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
309
277
  [fieldDef, control, field.name, getTranslation, renderStructFieldInput, isReadonly],
310
278
  );
311
279
 
280
+ if (!fieldDef || !isStructFieldConfig(fieldDef)) {
281
+ return null;
282
+ }
283
+
284
+ // Helper function to format field value for display
285
+ const formatFieldValue = (value: any, structField: StructField) => {
286
+ if (value == null) return '-';
287
+ if (structField.list) {
288
+ if (Array.isArray(value)) {
289
+ return value.length ? value.join(', ') : '-';
290
+ }
291
+ return '-';
292
+ }
293
+ switch (structField.type) {
294
+ case 'boolean':
295
+ return (
296
+ <span className={`inline-flex items-center ${value ? 'text-green-600' : 'text-red-500'}`}>
297
+ {value ? <CheckIcon className="h-4 w-4" /> : <X className="h-4 w-4" />}
298
+ </span>
299
+ );
300
+ case 'datetime':
301
+ return value ? formatDate(value, { dateStyle: 'short', timeStyle: 'short' }) : '-';
302
+ default:
303
+ return value.toString();
304
+ }
305
+ };
306
+
312
307
  return isEditing ? (
313
308
  EditMode
314
309
  ) : (
@@ -0,0 +1,9 @@
1
+ // Simple built-in components using the single DashboardFormComponent interface
2
+ import { Input } from '@/vdb/components/ui/input.js';
3
+ import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
4
+ import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
5
+
6
+ export const TextInput: DashboardFormComponent = ({ value, onChange, fieldDef }) => {
7
+ const readOnly = isReadonlyField(fieldDef);
8
+ return <Input value={value || ''} onChange={e => onChange(e.target.value)} disabled={readOnly} />;
9
+ };
@@ -0,0 +1,16 @@
1
+ import { Textarea } from '@/vdb/components/ui/textarea.js';
2
+ import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
3
+ import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
4
+
5
+ export function TextareaInput(props: Readonly<DashboardFormComponentProps>) {
6
+ const readOnly = props.disabled || isReadonlyField(props.fieldDef);
7
+ return (
8
+ <Textarea
9
+ ref={props.ref}
10
+ onBlur={props.onBlur}
11
+ value={props.value}
12
+ onChange={e => props.onChange(e.target.value)}
13
+ disabled={readOnly}
14
+ />
15
+ );
16
+ }
@@ -73,6 +73,9 @@ export function DataTableNumberFilter({
73
73
  if (mode === 'money') {
74
74
  return (
75
75
  <MoneyInput
76
+ ref={() => {}}
77
+ onBlur={() => {}}
78
+ name="amount"
76
79
  value={parseFloat(value) || 0}
77
80
  onChange={newValue => onChange(newValue.toString())}
78
81
  currency={activeChannel?.defaultCurrencyCode ?? 'USD'}
@@ -1,5 +1,9 @@
1
- import { DisplayComponent } from '@/vdb/framework/component-registry/dynamic-component.js';
2
- import { FieldInfo, getTypeFieldInfo, getOperationVariablesFields } from '@/vdb/framework/document-introspection/get-document-structure.js';
1
+ import { DisplayComponent } from '@/vdb/framework/component-registry/display-component.js';
2
+ import {
3
+ FieldInfo,
4
+ getOperationVariablesFields,
5
+ getTypeFieldInfo,
6
+ } from '@/vdb/framework/document-introspection/get-document-structure.js';
3
7
  import { api } from '@/vdb/graphql/api.js';
4
8
  import { Trans, useLingui } from '@/vdb/lib/trans.js';
5
9
  import { TypedDocumentNode } from '@graphql-typed-document-node/core';
@@ -250,14 +254,21 @@ function DeleteMutationRowAction({
250
254
  }>) {
251
255
  const { refetchPaginatedList } = usePaginatedList();
252
256
  const { i18n } = useLingui();
253
-
257
+
254
258
  // Inspect the mutation variables to determine if it expects 'id' or 'ids'
255
259
  const mutationVariables = getOperationVariablesFields(deleteMutation);
256
260
  const hasIdsParameter = mutationVariables.some(field => field.name === 'ids');
257
-
261
+
258
262
  const { mutate: deleteMutationFn } = useMutation({
259
263
  mutationFn: api.mutate(deleteMutation),
260
- onSuccess: (result: { [key: string]: { result: 'DELETED' | 'NOT_DELETED'; message: string } | { result: 'DELETED' | 'NOT_DELETED'; message: string }[] }) => {
264
+ onSuccess: (result: {
265
+ [key: string]:
266
+ | { result: 'DELETED' | 'NOT_DELETED'; message: string }
267
+ | {
268
+ result: 'DELETED' | 'NOT_DELETED';
269
+ message: string;
270
+ }[];
271
+ }) => {
261
272
  const unwrappedResult = Object.values(result)[0];
262
273
  // Handle both single result and array of results
263
274
  const resultToCheck = Array.isArray(unwrappedResult) ? unwrappedResult[0] : unwrappedResult;
@@ -1,26 +1,21 @@
1
1
  import { ConfigurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
2
- import { configArgToUniversal } from './universal-field-definition.js';
3
- import { UniversalFormInput } from './universal-form-input.js';
2
+ import { FormControlAdapter } from '../../framework/form-engine/form-control-adapter.js';
4
3
 
5
4
  export interface ConfigurableOperationArgInputProps {
6
5
  definition: ConfigurableOperationDefFragment['args'][number];
7
6
  readOnly?: boolean;
8
7
  value: string;
9
8
  onChange: (value: any) => void;
10
- position?: number;
11
9
  }
12
10
 
13
11
  export function ConfigurableOperationArgInput({
14
12
  definition,
15
13
  value,
16
14
  onChange,
17
- readOnly,
18
- position,
19
15
  }: Readonly<ConfigurableOperationArgInputProps>) {
20
- const universalFieldDef = configArgToUniversal(definition);
21
16
  return (
22
- <UniversalFormInput
23
- fieldDef={universalFieldDef}
17
+ <FormControlAdapter
18
+ fieldDef={definition}
24
19
  field={{
25
20
  value,
26
21
  onChange,
@@ -29,8 +24,6 @@ export function ConfigurableOperationArgInput({
29
24
  ref: () => {},
30
25
  }}
31
26
  valueMode="json-string"
32
- disabled={readOnly}
33
- position={position}
34
27
  />
35
28
  );
36
29
  }
@@ -22,7 +22,6 @@ export function ConfigurableOperationInput({
22
22
  operationDefinition,
23
23
  readonly,
24
24
  removable,
25
- position,
26
25
  hideDescription,
27
26
  value,
28
27
  onChange,
@@ -90,10 +89,7 @@ export function ConfigurableOperationInput({
90
89
  className={`grid gap-4 ${operationDefinition.args.length === 1 ? 'grid-cols-1' : 'grid-cols-1 sm:grid-cols-2'}`}
91
90
  >
92
91
  {operationDefinition.args
93
- .filter(
94
- arg =>
95
- arg.ui?.component !== 'combination-mode-form-input',
96
- )
92
+ .filter(arg => arg.ui?.component !== 'combination-mode-form-input')
97
93
  .map(arg => {
98
94
  const argValue =
99
95
  value.arguments.find(a => a.name === arg.name)?.value || '';
@@ -114,7 +110,6 @@ export function ConfigurableOperationInput({
114
110
  handleInputChange(arg.name, value)
115
111
  }
116
112
  readOnly={readonly}
117
- position={position}
118
113
  />
119
114
  </FormControl>
120
115
  </FormItem>
@@ -1,3 +1,4 @@
1
+ import { CombinationModeInput } from '@/vdb/components/data-input/combination-mode-input.js';
1
2
  import { Button } from '@/vdb/components/ui/button.js';
2
3
  import {
3
4
  DropdownMenu,
@@ -5,7 +6,6 @@ import {
5
6
  DropdownMenuItem,
6
7
  DropdownMenuTrigger,
7
8
  } from '@/vdb/components/ui/dropdown-menu.js';
8
- import { InputComponent } from '@/vdb/framework/component-registry/dynamic-component.js';
9
9
  import { api } from '@/vdb/graphql/api.js';
10
10
  import { ConfigurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
11
11
  import { Trans } from '@/vdb/lib/trans.js';
@@ -179,13 +179,14 @@ export function ConfigurableOperationMultiSelector({
179
179
  if (!operationDef) {
180
180
  return null;
181
181
  }
182
- const hasCombinationMode = operation.arguments.find(arg => arg.name === 'combineWithAnd');
182
+ const hasCombinationMode = operation.arguments.find(
183
+ arg => arg.name === 'combineWithAnd',
184
+ );
183
185
  return (
184
186
  <div key={index + operation.code}>
185
187
  {index > 0 && hasCombinationMode ? (
186
188
  <div className="my-2">
187
- <InputComponent
188
- id="vendure:combinationModeInput"
189
+ <CombinationModeInput
189
190
  value={
190
191
  operation.arguments.find(arg => arg.name === 'combineWithAnd')
191
192
  ?.value ?? 'true'
@@ -193,6 +194,9 @@ export function ConfigurableOperationMultiSelector({
193
194
  onChange={(newValue: boolean | string) =>
194
195
  onCombinationModeChange(index, newValue)
195
196
  }
197
+ name={''}
198
+ ref={() => {}}
199
+ onBlur={() => {}}
196
200
  position={index}
197
201
  />
198
202
  </div>
@@ -204,7 +208,6 @@ export function ConfigurableOperationMultiSelector({
204
208
  value={operation}
205
209
  onChange={value => onOperationValueChange(operation, value)}
206
210
  onRemove={() => onOperationRemove(index)}
207
- position={index}
208
211
  />
209
212
  </div>
210
213
  );
@@ -41,19 +41,19 @@ type QueryData = {
41
41
 
42
42
  /**
43
43
  * ConfigurableOperationSelector - A reusable component for selecting a single configurable operation
44
- *
44
+ *
45
45
  * This component provides a standardized interface for selecting configurable operations such as:
46
46
  * - Payment method handlers
47
- * - Payment eligibility checkers
47
+ * - Payment eligibility checkers
48
48
  * - Shipping calculators
49
49
  * - Shipping eligibility checkers
50
- *
50
+ *
51
51
  * Features:
52
52
  * - Displays the selected operation with its configuration form
53
53
  * - Provides a dropdown to select from available operations
54
54
  * - Handles operation selection with default argument values
55
55
  * - Supports removal of selected operations
56
- *
56
+ *
57
57
  * @example
58
58
  * ```tsx
59
59
  * <ConfigurableOperationSelector
@@ -122,7 +122,7 @@ export function ConfigurableOperationSelector({
122
122
  <ConfigurableOperationInput
123
123
  operationDefinition={operationDef}
124
124
  value={value}
125
- onChange={value => onOperationValueChange(value)}
125
+ onChange={v => onOperationValueChange(v)}
126
126
  onRemove={() => onOperationRemove()}
127
127
  />
128
128
  </div>
@@ -17,9 +17,8 @@ import { customFieldConfigFragment } from '@/vdb/providers/server-config.js';
17
17
  import { ResultOf } from 'gql.tada';
18
18
  import React, { useMemo } from 'react';
19
19
  import { Control } from 'react-hook-form';
20
+ import { FormControlAdapter } from '../../framework/form-engine/form-control-adapter.js';
20
21
  import { TranslatableFormField } from './translatable-form-field.js';
21
- import { customFieldToUniversal } from './universal-field-definition.js';
22
- import { UniversalFormInput } from './universal-form-input.js';
23
22
 
24
23
  type CustomFieldConfig = ResultOf<typeof customFieldConfigFragment>;
25
24
 
@@ -30,15 +29,7 @@ interface CustomFieldsFormProps {
30
29
  }
31
30
 
32
31
  export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readonly<CustomFieldsFormProps>) {
33
- const {
34
- settings: { displayLanguage },
35
- } = useUserSettings();
36
32
  const { i18n } = useLingui();
37
-
38
- const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
39
- return input?.find(t => t.languageCode === displayLanguage)?.value;
40
- };
41
-
42
33
  const customFields = useCustomFieldConfig(entityType);
43
34
 
44
35
  const getCustomFieldBaseName = (fieldDef: CustomFieldConfig) => {
@@ -94,7 +85,6 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
94
85
  fieldDef={fieldDef}
95
86
  control={control}
96
87
  fieldName={getFieldName(fieldDef)}
97
- getTranslation={getTranslation}
98
88
  />
99
89
  ))}
100
90
  </div>
@@ -120,7 +110,6 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
120
110
  fieldDef={fieldDef}
121
111
  control={control}
122
112
  fieldName={getFieldName(fieldDef)}
123
- getTranslation={getTranslation}
124
113
  />
125
114
  ))}
126
115
  </div>
@@ -134,12 +123,16 @@ interface CustomFieldItemProps {
134
123
  fieldDef: CustomFieldConfig;
135
124
  control: Control<any, any>;
136
125
  fieldName: string;
137
- getTranslation: (
138
- input: Array<{ languageCode: string; value: string }> | null | undefined,
139
- ) => string | undefined;
140
126
  }
141
127
 
142
- function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Readonly<CustomFieldItemProps>) {
128
+ function CustomFieldItem({ fieldDef, control, fieldName }: Readonly<CustomFieldItemProps>) {
129
+ const {
130
+ settings: { displayLanguage },
131
+ } = useUserSettings();
132
+
133
+ const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
134
+ return input?.find(t => t.languageCode === displayLanguage)?.value;
135
+ };
143
136
  const hasCustomFormComponent = fieldDef.ui?.component;
144
137
  const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
145
138
  const shouldBeFullWidth = fieldDef.ui?.fullWidth === true;
@@ -160,22 +153,13 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
160
153
  {hasCustomFormComponent ? (
161
154
  <CustomFormComponent
162
155
  fieldDef={fieldDef}
163
- fieldProps={{
164
- ...props,
165
- field: {
166
- ...field,
167
- disabled: fieldDef.readonly ?? false,
168
- },
169
- }}
156
+ {...field}
170
157
  />
171
158
  ) : (
172
- <UniversalFormInput
173
- fieldDef={customFieldToUniversal(fieldDef)}
159
+ <FormControlAdapter
160
+ fieldDef={fieldDef}
174
161
  field={field}
175
162
  valueMode="native"
176
- disabled={fieldDef.readonly ?? false}
177
- control={control}
178
- getTranslation={getTranslation}
179
163
  />
180
164
  )}
181
165
  </FormControl>
@@ -203,13 +187,7 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
203
187
  >
204
188
  <CustomFormComponent
205
189
  fieldDef={fieldDef}
206
- fieldProps={{
207
- ...fieldProps,
208
- field: {
209
- ...fieldProps.field,
210
- disabled: fieldDef.readonly ?? false,
211
- },
212
- }}
190
+ {...fieldProps.field}
213
191
  />
214
192
  </CustomFieldFormItem>
215
193
  )}
@@ -234,14 +212,12 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
234
212
  <FormLabel>{getTranslation(fieldDef.label) ?? fieldDef.name}</FormLabel>
235
213
  <FormControl>
236
214
  <CustomFieldListInput
237
- field={field}
215
+ {...field}
238
216
  disabled={isReadonly}
239
217
  renderInput={(index, inputField) => (
240
218
  <StructFormInput
241
- field={inputField}
242
- fieldDef={fieldDef as any}
243
- control={control}
244
- getTranslation={getTranslation}
219
+ {...inputField}
220
+ fieldDef={fieldDef}
245
221
  />
246
222
  )}
247
223
  defaultValue={{}} // Empty struct object as default
@@ -268,10 +244,8 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
268
244
  <FormLabel>{getTranslation(fieldDef.label) ?? fieldDef.name}</FormLabel>
269
245
  <FormControl>
270
246
  <StructFormInput
271
- field={field}
272
- fieldDef={fieldDef as any}
273
- control={control}
274
- getTranslation={getTranslation}
247
+ {...field}
248
+ fieldDef={fieldDef}
275
249
  />
276
250
  </FormControl>
277
251
  <FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
@@ -295,13 +269,10 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
295
269
  getTranslation={getTranslation}
296
270
  fieldName={fieldDef.name}
297
271
  >
298
- <UniversalFormInput
299
- fieldDef={customFieldToUniversal(fieldDef)}
272
+ <FormControlAdapter
273
+ fieldDef={fieldDef}
300
274
  field={field}
301
275
  valueMode="native"
302
- disabled={fieldDef.readonly ?? false}
303
- control={control}
304
- getTranslation={getTranslation}
305
276
  />
306
277
  </CustomFieldFormItem>
307
278
  )}
@@ -63,7 +63,7 @@ export function MultiSelect<T extends boolean>(props: MultiSelectProps<T>) {
63
63
 
64
64
  const renderTrigger = () => {
65
65
  if (multiple) {
66
- const selectedValues = value as string[];
66
+ const selectedValues: string[] = typeof value === 'string' ? [value] : value;
67
67
  return (
68
68
  <Button
69
69
  variant="outline"
@@ -1,28 +1,24 @@
1
+ import {
2
+ DashboardFormComponent,
3
+ DashboardFormComponentProps,
4
+ } from '@/vdb/framework/form-engine/form-engine-types.js';
1
5
  import * as React from 'react';
2
- import { ControllerRenderProps, FieldPath, FieldValues } from 'react-hook-form';
3
- import { addDisplayComponent, getDisplayComponent } from '../extension-api/display-component-extensions.js';
4
- import { addInputComponent, getInputComponent } from '../extension-api/input-component-extensions.js';
6
+ import { getDisplayComponent } from '../extension-api/display-component-extensions.js';
7
+ import { getInputComponent } from '../extension-api/input-component-extensions.js';
5
8
 
6
9
  export interface ComponentRegistryEntry<Props extends Record<string, any>> {
7
10
  component: React.ComponentType<Props>;
8
11
  }
9
12
 
10
- // Basic component types
11
-
13
+ // Display component interface (unchanged)
12
14
  export interface DataDisplayComponentProps {
13
15
  value: any;
14
- [key: string]: any;
15
- }
16
16
 
17
- export interface DataInputComponentProps<
18
- TFieldValues extends FieldValues = FieldValues,
19
- TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
20
- > extends ControllerRenderProps<TFieldValues, TName> {
21
17
  [key: string]: any;
22
18
  }
23
19
 
24
20
  export type DataDisplayComponent = React.ComponentType<DataDisplayComponentProps>;
25
- export type DataInputComponent = React.ComponentType<DataInputComponentProps>;
21
+ export type { DashboardFormComponentProps as DataInputComponentProps };
26
22
 
27
23
  // Component registry hook that uses the global registry
28
24
  export function useComponentRegistry() {
@@ -30,27 +26,8 @@ export function useComponentRegistry() {
30
26
  getDisplayComponent: (id: string): DataDisplayComponent | undefined => {
31
27
  return getDisplayComponent(id);
32
28
  },
33
- getInputComponent: (id: string): DataInputComponent | undefined => {
29
+ getInputComponent: (id: string): DashboardFormComponent | undefined => {
34
30
  return getInputComponent(id);
35
31
  },
36
32
  };
37
33
  }
38
-
39
- // Legacy registration functions - these now delegate to the global registry
40
- export function registerInputComponent(
41
- pageId: string,
42
- blockId: string,
43
- field: string,
44
- component: DataInputComponent,
45
- ) {
46
- addInputComponent({ pageId, blockId, field, component });
47
- }
48
-
49
- export function registerDisplayComponent(
50
- pageId: string,
51
- blockId: string,
52
- field: string,
53
- component: DataDisplayComponent,
54
- ) {
55
- addDisplayComponent({ pageId, blockId, field, component });
56
- }